summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.gitattributes4
-rw-r--r--.github/workflows/android_builds.yml1
-rw-r--r--.github/workflows/godot_cpp_test.yml10
-rw-r--r--.github/workflows/windows_builds.yml11
-rw-r--r--SConstruct46
-rw-r--r--core/SCsub2
-rw-r--r--core/core_bind.compat.inc16
-rw-r--r--core/core_bind.cpp12
-rw-r--r--core/core_bind.h9
-rw-r--r--core/core_constants.cpp1
-rw-r--r--core/debugger/remote_debugger_peer.cpp3
-rw-r--r--core/doc_data.cpp6
-rw-r--r--core/doc_data.h22
-rw-r--r--core/extension/extension_api_dump.cpp3
-rw-r--r--core/extension/gdextension.cpp10
-rw-r--r--core/extension/gdextension_interface.cpp12
-rw-r--r--core/extension/gdextension_interface.h18
-rw-r--r--core/extension/gdextension_manager.cpp93
-rw-r--r--core/extension/gdextension_manager.h3
-rw-r--r--core/io/file_access.cpp111
-rw-r--r--core/io/file_access.h8
-rw-r--r--core/io/file_access_compressed.cpp39
-rw-r--r--core/io/file_access_compressed.h3
-rw-r--r--core/io/file_access_encrypted.cpp46
-rw-r--r--core/io/file_access_encrypted.h2
-rw-r--r--core/io/file_access_memory.cpp18
-rw-r--r--core/io/file_access_memory.h3
-rw-r--r--core/io/file_access_pack.cpp15
-rw-r--r--core/io/file_access_pack.h4
-rw-r--r--core/io/file_access_zip.cpp8
-rw-r--r--core/io/file_access_zip.h3
-rw-r--r--core/io/image.cpp2
-rw-r--r--core/io/json.cpp748
-rw-r--r--core/io/json.h3
-rw-r--r--core/io/packed_data_container.h2
-rw-r--r--core/io/plist.cpp4
-rw-r--r--core/io/resource.cpp121
-rw-r--r--core/io/resource_format_binary.cpp15
-rw-r--r--core/io/resource_importer.cpp23
-rw-r--r--core/io/resource_importer.h7
-rw-r--r--core/io/resource_loader.cpp216
-rw-r--r--core/io/resource_loader.h16
-rw-r--r--core/math/a_star.cpp28
-rw-r--r--core/math/a_star.h4
-rw-r--r--core/math/a_star_grid_2d.cpp219
-rw-r--r--core/math/a_star_grid_2d.h27
-rw-r--r--core/math/math_funcs.h3
-rw-r--r--core/math/random_pcg.cpp5
-rw-r--r--core/object/class_db.cpp26
-rw-r--r--core/object/class_db.h1
-rw-r--r--core/object/object.cpp107
-rw-r--r--core/object/object.h29
-rw-r--r--core/object/worker_thread_pool.cpp44
-rw-r--r--core/os/os.h12
-rw-r--r--core/os/pool_allocator.cpp588
-rw-r--r--core/os/pool_allocator.h148
-rw-r--r--core/string/string_name.cpp103
-rw-r--r--core/string/string_name.h11
-rw-r--r--core/string/translation_server.cpp5
-rw-r--r--core/typedefs.h3
-rw-r--r--core/variant/binder_common.h4
-rw-r--r--core/variant/callable.cpp2
-rw-r--r--core/variant/dictionary.cpp293
-rw-r--r--core/variant/dictionary.h16
-rw-r--r--core/variant/typed_dictionary.h342
-rw-r--r--core/variant/variant.cpp9
-rw-r--r--core/variant/variant.h1
-rw-r--r--core/variant/variant_call.cpp13
-rw-r--r--core/variant/variant_construct.cpp1
-rw-r--r--core/variant/variant_construct.h115
-rw-r--r--core/variant/variant_internal.h4
-rw-r--r--core/variant/variant_parser.cpp244
-rw-r--r--core/variant/variant_setget.cpp95
-rw-r--r--core/variant/variant_utility.cpp10
-rw-r--r--doc/class.xsd2
-rw-r--r--doc/classes/@GlobalScope.xml13
-rw-r--r--doc/classes/AStar2D.xml2
-rw-r--r--doc/classes/AStar3D.xml2
-rw-r--r--doc/classes/AStarGrid2D.xml9
-rw-r--r--doc/classes/AnimationMixer.xml14
-rw-r--r--doc/classes/AudioStreamPlayer.xml2
-rw-r--r--doc/classes/AudioStreamPlayer2D.xml2
-rw-r--r--doc/classes/AudioStreamPlayer3D.xml2
-rw-r--r--doc/classes/AudioStreamWAV.xml19
-rw-r--r--doc/classes/CPUParticles3D.xml4
-rw-r--r--doc/classes/CanvasItem.xml2
-rw-r--r--doc/classes/Control.xml7
-rw-r--r--doc/classes/Dictionary.xml95
-rw-r--r--doc/classes/DisplayServer.xml2
-rw-r--r--doc/classes/EditorContextMenuPlugin.xml49
-rw-r--r--doc/classes/EditorExportPlatform.xml206
-rw-r--r--doc/classes/EditorExportPlatformExtension.xml282
-rw-r--r--doc/classes/EditorExportPlugin.xml12
-rw-r--r--doc/classes/EditorExportPreset.xml186
-rw-r--r--doc/classes/EditorInterface.xml12
-rw-r--r--doc/classes/EditorPlugin.xml43
-rw-r--r--doc/classes/EditorResourcePreviewGenerator.xml4
-rw-r--r--doc/classes/EditorSettings.xml47
-rw-r--r--doc/classes/EditorUndoRedoManager.xml15
-rw-r--r--doc/classes/GDExtensionManager.xml14
-rw-r--r--doc/classes/ImporterMesh.xml4
-rw-r--r--doc/classes/JSON.xml20
-rw-r--r--doc/classes/JavaClass.xml26
-rw-r--r--doc/classes/JavaClassWrapper.xml9
-rw-r--r--doc/classes/JavaObject.xml21
-rw-r--r--doc/classes/Label.xml2
-rw-r--r--doc/classes/Label3D.xml2
-rw-r--r--doc/classes/MenuBar.xml8
-rw-r--r--doc/classes/MeshInstance3D.xml8
-rw-r--r--doc/classes/OS.xml2
-rw-r--r--doc/classes/Object.xml49
-rw-r--r--doc/classes/PackedScene.xml6
-rw-r--r--doc/classes/ParticleProcessMaterial.xml4
-rw-r--r--doc/classes/PathFollow2D.xml1
-rw-r--r--doc/classes/PathFollow3D.xml1
-rw-r--r--doc/classes/PolygonPathFinder.xml45
-rw-r--r--doc/classes/PortableCompressedTexture2D.xml2
-rw-r--r--doc/classes/ProjectSettings.xml7
-rw-r--r--doc/classes/PropertyTweener.xml1
-rw-r--r--doc/classes/RenderingServer.xml9
-rw-r--r--doc/classes/ResourceImporterWAV.xml22
-rw-r--r--doc/classes/ResourceLoader.xml8
-rw-r--r--doc/classes/ResourceUID.xml2
-rw-r--r--doc/classes/RichTextLabel.xml9
-rw-r--r--doc/classes/ScriptEditor.xml6
-rw-r--r--doc/classes/SpinBox.xml4
-rw-r--r--doc/classes/SpriteFrames.xml8
-rw-r--r--doc/classes/TextMesh.xml2
-rw-r--r--doc/classes/TextParagraph.xml2
-rw-r--r--doc/classes/TileData.xml57
-rw-r--r--doc/classes/TileMapLayer.xml3
-rw-r--r--doc/classes/TreeItem.xml10
-rw-r--r--doc/classes/Tweener.xml2
-rw-r--r--doc/classes/VehicleWheel3D.xml12
-rw-r--r--doc/classes/Viewport.xml6
-rwxr-xr-xdoc/tools/make_rst.py37
-rw-r--r--drivers/gles3/rasterizer_canvas_gles3.cpp15
-rw-r--r--drivers/gles3/rasterizer_canvas_gles3.h4
-rw-r--r--drivers/gles3/rasterizer_gles3.h1
-rw-r--r--drivers/gles3/rasterizer_scene_gles3.cpp2
-rw-r--r--drivers/gles3/shaders/scene.glsl18
-rw-r--r--drivers/gles3/shaders/stdlib_inc.glsl18
-rw-r--r--drivers/gles3/storage/config.cpp2
-rw-r--r--drivers/gles3/storage/light_storage.h2
-rw-r--r--drivers/gles3/storage/mesh_storage.cpp1
-rw-r--r--drivers/gles3/storage/texture_storage.cpp22
-rw-r--r--drivers/metal/metal_objects.h1
-rw-r--r--drivers/metal/metal_objects.mm8
-rw-r--r--drivers/metal/pixel_formats.mm2
-rw-r--r--drivers/metal/rendering_context_driver_metal.h145
-rw-r--r--drivers/metal/rendering_context_driver_metal.mm99
-rw-r--r--drivers/metal/rendering_device_driver_metal.mm2
-rw-r--r--drivers/unix/dir_access_unix.cpp6
-rw-r--r--drivers/unix/file_access_unix.cpp93
-rw-r--r--drivers/unix/file_access_unix.h8
-rw-r--r--drivers/unix/file_access_unix_pipe.cpp41
-rw-r--r--drivers/unix/file_access_unix_pipe.h4
-rw-r--r--drivers/unix/os_unix.cpp6
-rw-r--r--drivers/unix/os_unix.h2
-rw-r--r--drivers/vulkan/rendering_device_driver_vulkan.cpp11
-rw-r--r--drivers/windows/dir_access_windows.cpp192
-rw-r--r--drivers/windows/file_access_windows.cpp231
-rw-r--r--drivers/windows/file_access_windows.h8
-rw-r--r--drivers/windows/file_access_windows_pipe.cpp36
-rw-r--r--drivers/windows/file_access_windows_pipe.h4
-rw-r--r--editor/action_map_editor.cpp2
-rw-r--r--editor/animation_bezier_editor.cpp12
-rw-r--r--editor/animation_track_editor.cpp244
-rw-r--r--editor/animation_track_editor.h11
-rw-r--r--editor/code_editor.cpp21
-rw-r--r--editor/code_editor.h2
-rw-r--r--editor/connections_dialog.cpp21
-rw-r--r--editor/debugger/editor_debugger_inspector.cpp2
-rw-r--r--editor/debugger/editor_debugger_node.cpp2
-rw-r--r--editor/debugger/editor_debugger_tree.cpp16
-rw-r--r--editor/debugger/editor_debugger_tree.h1
-rw-r--r--editor/debugger/editor_visual_profiler.cpp4
-rw-r--r--editor/doc_tools.cpp20
-rw-r--r--editor/editor_asset_installer.cpp4
-rw-r--r--editor/editor_audio_buses.cpp8
-rw-r--r--editor/editor_build_profile.cpp16
-rw-r--r--editor/editor_build_profile.h2
-rw-r--r--editor/editor_data.cpp135
-rw-r--r--editor/editor_data.h30
-rw-r--r--editor/editor_feature_profile.cpp12
-rw-r--r--editor/editor_file_system.cpp350
-rw-r--r--editor/editor_file_system.h11
-rw-r--r--editor/editor_help.cpp72
-rw-r--r--editor/editor_help_search.cpp549
-rw-r--r--editor/editor_help_search.h44
-rw-r--r--editor/editor_inspector.cpp10
-rw-r--r--editor/editor_inspector.h1
-rw-r--r--editor/editor_interface.compat.inc48
-rw-r--r--editor/editor_interface.cpp18
-rw-r--r--editor/editor_interface.h13
-rw-r--r--editor/editor_locale_dialog.cpp4
-rw-r--r--editor/editor_log.cpp4
-rw-r--r--editor/editor_log.h2
-rw-r--r--editor/editor_node.cpp228
-rw-r--r--editor/editor_node.h16
-rw-r--r--editor/editor_properties.cpp2
-rw-r--r--editor/editor_properties_array_dict.cpp135
-rw-r--r--editor/editor_properties_array_dict.h12
-rw-r--r--editor/editor_properties_vector.cpp10
-rw-r--r--editor/editor_resource_picker.cpp18
-rw-r--r--editor/editor_resource_picker.h2
-rw-r--r--editor/editor_resource_preview.cpp4
-rw-r--r--editor/editor_run_native.cpp10
-rw-r--r--editor/editor_settings.cpp18
-rw-r--r--editor/editor_settings_dialog.cpp127
-rw-r--r--editor/editor_settings_dialog.h6
-rw-r--r--editor/editor_undo_redo_manager.cpp7
-rw-r--r--editor/editor_undo_redo_manager.h2
-rw-r--r--editor/export/editor_export.cpp18
-rw-r--r--editor/export/editor_export.h2
-rw-r--r--editor/export/editor_export_platform.cpp264
-rw-r--r--editor/export/editor_export_platform.h105
-rw-r--r--editor/export/editor_export_platform_extension.cpp317
-rw-r--r--editor/export/editor_export_platform_extension.h149
-rw-r--r--editor/export/editor_export_platform_pc.cpp6
-rw-r--r--editor/export/editor_export_platform_pc.h8
-rw-r--r--editor/export/editor_export_plugin.cpp13
-rw-r--r--editor/export/editor_export_plugin.h1
-rw-r--r--editor/export/editor_export_preset.cpp42
-rw-r--r--editor/export/editor_export_preset.h7
-rw-r--r--editor/export/export_template_manager.cpp110
-rw-r--r--editor/export/export_template_manager.h2
-rw-r--r--editor/export/project_export.cpp6
-rw-r--r--editor/filesystem_dock.cpp21
-rw-r--r--editor/groups_editor.cpp2
-rw-r--r--editor/gui/editor_bottom_panel.cpp4
-rw-r--r--editor/gui/editor_file_dialog.cpp7
-rw-r--r--editor/gui/editor_object_selector.cpp1
-rw-r--r--editor/gui/editor_run_bar.cpp2
-rw-r--r--editor/gui/editor_spin_slider.cpp22
-rw-r--r--editor/gui/scene_tree_editor.cpp59
-rw-r--r--editor/gui/scene_tree_editor.h9
-rw-r--r--editor/history_dock.cpp8
-rw-r--r--editor/icons/MemberConstructor.svg1
-rw-r--r--editor/icons/MemberOperator.svg1
-rw-r--r--editor/icons/SnapKeys.svg1
-rw-r--r--editor/icons/SnapTimeline.svg1
-rw-r--r--editor/icons/editor_icons_builders.py20
-rw-r--r--editor/import/3d/post_import_plugin_skeleton_rest_fixer.cpp4
-rw-r--r--editor/import/3d/post_import_plugin_skeleton_rest_fixer.h2
-rw-r--r--editor/import/3d/resource_importer_scene.cpp111
-rw-r--r--editor/import/3d/resource_importer_scene.h17
-rw-r--r--editor/import/3d/scene_import_settings.cpp82
-rw-r--r--editor/import/3d/scene_import_settings.h6
-rw-r--r--editor/import/audio_stream_import_settings.cpp6
-rw-r--r--editor/import/dynamic_font_import_settings.cpp16
-rw-r--r--editor/import/resource_importer_layered_texture.cpp2
-rw-r--r--editor/import/resource_importer_wav.cpp14
-rw-r--r--editor/import_dock.cpp2
-rw-r--r--editor/input_event_configuration_dialog.cpp4
-rw-r--r--editor/inspector_dock.cpp2
-rw-r--r--editor/plugins/animation_blend_space_1d_editor.cpp2
-rw-r--r--editor/plugins/animation_blend_space_2d_editor.cpp2
-rw-r--r--editor/plugins/animation_player_editor_plugin.cpp171
-rw-r--r--editor/plugins/animation_player_editor_plugin.h4
-rw-r--r--editor/plugins/canvas_item_editor_plugin.cpp73
-rw-r--r--editor/plugins/control_editor_plugin.cpp2
-rw-r--r--editor/plugins/curve_editor_plugin.cpp2
-rw-r--r--editor/plugins/editor_context_menu_plugin.cpp65
-rw-r--r--editor/plugins/editor_context_menu_plugin.h70
-rw-r--r--editor/plugins/editor_plugin.cpp31
-rw-r--r--editor/plugins/editor_plugin.h16
-rw-r--r--editor/plugins/gizmos/joint_3d_gizmo_plugin.cpp12
-rw-r--r--editor/plugins/gizmos/joint_3d_gizmo_plugin.h2
-rw-r--r--editor/plugins/gradient_editor_plugin.cpp2
-rw-r--r--editor/plugins/gradient_texture_2d_editor_plugin.cpp2
-rw-r--r--editor/plugins/node_3d_editor_gizmos.cpp9
-rw-r--r--editor/plugins/node_3d_editor_gizmos.h2
-rw-r--r--editor/plugins/node_3d_editor_plugin.cpp260
-rw-r--r--editor/plugins/node_3d_editor_plugin.h35
-rw-r--r--editor/plugins/particle_process_material_editor_plugin.cpp2
-rw-r--r--editor/plugins/path_2d_editor_plugin.cpp87
-rw-r--r--editor/plugins/path_2d_editor_plugin.h5
-rw-r--r--editor/plugins/path_3d_editor_plugin.cpp55
-rw-r--r--editor/plugins/path_3d_editor_plugin.h9
-rw-r--r--editor/plugins/physical_bone_3d_editor_plugin.cpp2
-rw-r--r--editor/plugins/polygon_2d_editor_plugin.cpp6
-rw-r--r--editor/plugins/script_editor_plugin.cpp144
-rw-r--r--editor/plugins/script_editor_plugin.h7
-rw-r--r--editor/plugins/script_text_editor.cpp30
-rw-r--r--editor/plugins/shader_editor_plugin.cpp26
-rw-r--r--editor/plugins/shader_editor_plugin.h2
-rw-r--r--editor/plugins/skeleton_3d_editor_plugin.cpp245
-rw-r--r--editor/plugins/skeleton_3d_editor_plugin.h19
-rw-r--r--editor/plugins/sprite_frames_editor_plugin.cpp44
-rw-r--r--editor/plugins/sprite_frames_editor_plugin.h2
-rw-r--r--editor/plugins/style_box_editor_plugin.cpp2
-rw-r--r--editor/plugins/text_editor.cpp16
-rw-r--r--editor/plugins/text_shader_editor.cpp1
-rw-r--r--editor/plugins/texture_editor_plugin.cpp2
-rw-r--r--editor/plugins/theme_editor_plugin.cpp19
-rw-r--r--editor/plugins/theme_editor_plugin.h1
-rw-r--r--editor/plugins/tiles/tile_data_editors.cpp44
-rw-r--r--editor/plugins/tiles/tile_map_layer_editor.cpp12
-rw-r--r--editor/plugins/tiles/tile_set_atlas_source_editor.cpp77
-rw-r--r--editor/plugins/version_control_editor_plugin.cpp2
-rw-r--r--editor/plugins/visual_shader_editor_plugin.cpp23
-rw-r--r--editor/plugins/visual_shader_editor_plugin.h1
-rw-r--r--editor/progress_dialog.cpp8
-rw-r--r--editor/progress_dialog.h2
-rw-r--r--editor/project_manager/project_dialog.cpp2
-rw-r--r--editor/project_settings_editor.cpp2
-rw-r--r--editor/property_selector.cpp17
-rw-r--r--editor/register_editor_types.cpp5
-rw-r--r--editor/rename_dialog.cpp2
-rw-r--r--editor/run_instances_dialog.cpp2
-rw-r--r--editor/scene_tree_dock.cpp110
-rw-r--r--editor/scene_tree_dock.h3
-rw-r--r--editor/script_create_dialog.cpp39
-rw-r--r--editor/shader_create_dialog.cpp2
-rw-r--r--editor/themes/editor_theme_manager.cpp12
-rw-r--r--gles3_builders.py18
-rw-r--r--glsl_builders.py28
-rw-r--r--godot.manifest5
-rw-r--r--main/main.cpp31
-rw-r--r--methods.py71
-rw-r--r--misc/dist/macos_template.app/Contents/Info.plist11
-rw-r--r--misc/extension_api_validation/4.3-stable.expected32
-rw-r--r--modules/betsy/bc6h.glsl96
-rw-r--r--modules/betsy/image_compress_betsy.cpp14
-rw-r--r--modules/fbx/editor/editor_scene_importer_fbx2gltf.cpp2
-rw-r--r--modules/fbx/editor/editor_scene_importer_fbx2gltf.h2
-rw-r--r--modules/fbx/editor/editor_scene_importer_ufbx.cpp2
-rw-r--r--modules/fbx/editor/editor_scene_importer_ufbx.h2
-rw-r--r--modules/fbx/fbx_document.cpp9
-rw-r--r--modules/gdscript/editor/gdscript_docgen.cpp91
-rw-r--r--modules/gdscript/editor/gdscript_translation_parser_plugin.cpp2
-rw-r--r--modules/gdscript/gdscript.cpp85
-rw-r--r--modules/gdscript/gdscript.h7
-rw-r--r--modules/gdscript/gdscript_analyzer.cpp362
-rw-r--r--modules/gdscript/gdscript_analyzer.h2
-rw-r--r--modules/gdscript/gdscript_byte_codegen.cpp84
-rw-r--r--modules/gdscript/gdscript_byte_codegen.h1
-rw-r--r--modules/gdscript/gdscript_codegen.h1
-rw-r--r--modules/gdscript/gdscript_compiler.cpp24
-rw-r--r--modules/gdscript/gdscript_disassembler.cpp107
-rw-r--r--modules/gdscript/gdscript_editor.cpp28
-rw-r--r--modules/gdscript/gdscript_function.h43
-rw-r--r--modules/gdscript/gdscript_lambda_callable.cpp4
-rw-r--r--modules/gdscript/gdscript_parser.cpp289
-rw-r--r--modules/gdscript/gdscript_parser.h31
-rw-r--r--modules/gdscript/gdscript_utility_functions.cpp2
-rw-r--r--modules/gdscript/gdscript_vm.cpp194
-rw-r--r--modules/gdscript/gdscript_warning.cpp3
-rw-r--r--modules/gdscript/gdscript_warning.h2
-rw-r--r--modules/gdscript/language_server/gdscript_language_protocol.cpp6
-rw-r--r--modules/gdscript/language_server/gdscript_text_document.cpp2
-rw-r--r--modules/gdscript/language_server/godot_lsp.h2
-rw-r--r--modules/gdscript/tests/scripts/analyzer/errors/for_loop_wrong_specified_type_with_literal_dictionary.gd3
-rw-r--r--modules/gdscript/tests/scripts/analyzer/errors/for_loop_wrong_specified_type_with_literal_dictionary.out2
-rw-r--r--modules/gdscript/tests/scripts/analyzer/errors/typed_dictionary_assign_differently_typed.gd4
-rw-r--r--modules/gdscript/tests/scripts/analyzer/errors/typed_dictionary_assign_differently_typed.out2
-rw-r--r--modules/gdscript/tests/scripts/analyzer/errors/typed_dictionary_assignment.gd2
-rw-r--r--modules/gdscript/tests/scripts/analyzer/errors/typed_dictionary_assignment.out2
-rw-r--r--modules/gdscript/tests/scripts/analyzer/errors/typed_dictionary_init_with_unconvertible_in_literal.gd4
-rw-r--r--modules/gdscript/tests/scripts/analyzer/errors/typed_dictionary_init_with_unconvertible_in_literal.out2
-rw-r--r--modules/gdscript/tests/scripts/analyzer/errors/typed_dictionary_pass_differently_to_typed.gd7
-rw-r--r--modules/gdscript/tests/scripts/analyzer/errors/typed_dictionary_pass_differently_to_typed.out2
-rw-r--r--modules/gdscript/tests/scripts/analyzer/features/assymetric_assignment_good.gd9
-rw-r--r--modules/gdscript/tests/scripts/analyzer/features/const_conversions.gd25
-rw-r--r--modules/gdscript/tests/scripts/analyzer/features/enums_in_range_call.gd2
-rw-r--r--modules/gdscript/tests/scripts/analyzer/features/export_enum_as_dictionary.gd2
-rw-r--r--modules/gdscript/tests/scripts/analyzer/features/for_range_usage.gd2
-rw-r--r--modules/gdscript/tests/scripts/analyzer/features/function_match_parent_signature_with_extra_parameters.gd4
-rw-r--r--modules/gdscript/tests/scripts/analyzer/features/return_conversions.gd24
-rw-r--r--modules/gdscript/tests/scripts/analyzer/features/ternary_hard_infer.gd4
-rw-r--r--modules/gdscript/tests/scripts/analyzer/features/type_test_usage.gd193
-rw-r--r--modules/gdscript/tests/scripts/analyzer/features/typed_array_usage.gd159
-rw-r--r--modules/gdscript/tests/scripts/analyzer/features/typed_dictionary_as_default_parameter.gd20
-rw-r--r--modules/gdscript/tests/scripts/analyzer/features/typed_dictionary_as_default_parameter.out11
-rw-r--r--modules/gdscript/tests/scripts/analyzer/features/typed_dictionary_inferred_access_isnt_constant.gd4
-rw-r--r--modules/gdscript/tests/scripts/analyzer/features/typed_dictionary_inferred_access_isnt_constant.out2
-rw-r--r--modules/gdscript/tests/scripts/analyzer/features/typed_dictionary_usage.gd214
-rw-r--r--modules/gdscript/tests/scripts/analyzer/features/typed_dictionary_usage.out2
-rw-r--r--modules/gdscript/tests/scripts/analyzer/features/typed_dictionary_with_custom_class.gd9
-rw-r--r--modules/gdscript/tests/scripts/analyzer/features/typed_dictionary_with_custom_class.out2
-rw-r--r--modules/gdscript/tests/scripts/analyzer/warnings/confusable_capture_reassignment.out4
-rw-r--r--modules/gdscript/tests/scripts/analyzer/warnings/non_tool_extends_tool.gd7
-rw-r--r--modules/gdscript/tests/scripts/analyzer/warnings/non_tool_extends_tool.notest.gd1
-rw-r--r--modules/gdscript/tests/scripts/analyzer/warnings/non_tool_extends_tool.out9
-rw-r--r--modules/gdscript/tests/scripts/analyzer/warnings/non_tool_extends_tool_ignored.gd9
-rw-r--r--modules/gdscript/tests/scripts/analyzer/warnings/non_tool_extends_tool_ignored.out1
-rw-r--r--modules/gdscript/tests/scripts/analyzer/warnings/unused_signal.gd29
-rw-r--r--modules/gdscript/tests/scripts/analyzer/warnings/unused_signal.out8
-rw-r--r--modules/gdscript/tests/scripts/completion/assignment_options/enum_attribute.cfg19
-rw-r--r--modules/gdscript/tests/scripts/completion/assignment_options/enum_attribute.gd7
-rw-r--r--modules/gdscript/tests/scripts/completion/assignment_options/enum_attribute_identifier.cfg19
-rw-r--r--modules/gdscript/tests/scripts/completion/assignment_options/enum_attribute_identifier.gd7
-rw-r--r--modules/gdscript/tests/scripts/completion/assignment_options/enum_identifier.cfg12
-rw-r--r--modules/gdscript/tests/scripts/completion/assignment_options/enum_identifier.gd7
-rw-r--r--modules/gdscript/tests/scripts/completion/assignment_options/enum_no_identifier.cfg12
-rw-r--r--modules/gdscript/tests/scripts/completion/assignment_options/enum_no_identifier.gd7
-rw-r--r--modules/gdscript/tests/scripts/parser/features/annotations.gd2
-rw-r--r--modules/gdscript/tests/scripts/parser/features/class.gd4
-rw-r--r--modules/gdscript/tests/scripts/parser/features/dictionary_lua_style.out2
-rw-r--r--modules/gdscript/tests/scripts/parser/features/dictionary_mixed_syntax.out2
-rw-r--r--modules/gdscript/tests/scripts/parser/features/export_arrays.gd2
-rw-r--r--modules/gdscript/tests/scripts/parser/features/export_enum.gd2
-rw-r--r--modules/gdscript/tests/scripts/parser/features/export_variable.gd1
-rw-r--r--modules/gdscript/tests/scripts/parser/features/good_continue_in_lambda.gd2
-rw-r--r--modules/gdscript/tests/scripts/parser/features/truthiness.gd40
-rw-r--r--modules/gdscript/tests/scripts/parser/features/truthiness.out65
-rw-r--r--modules/gdscript/tests/scripts/parser/features/typed_dictionaries.gd5
-rw-r--r--modules/gdscript/tests/scripts/parser/features/typed_dictionaries.out3
-rw-r--r--modules/gdscript/tests/scripts/runtime/errors/typed_dictionary_assign_basic_to_typed.gd4
-rw-r--r--modules/gdscript/tests/scripts/runtime/errors/typed_dictionary_assign_basic_to_typed.out6
-rw-r--r--modules/gdscript/tests/scripts/runtime/errors/typed_dictionary_assign_differently_typed.gd4
-rw-r--r--modules/gdscript/tests/scripts/runtime/errors/typed_dictionary_assign_differently_typed.out6
-rw-r--r--modules/gdscript/tests/scripts/runtime/errors/typed_dictionary_assign_wrong_to_typed.gd7
-rw-r--r--modules/gdscript/tests/scripts/runtime/errors/typed_dictionary_assign_wrong_to_typed.out5
-rw-r--r--modules/gdscript/tests/scripts/runtime/errors/typed_dictionary_pass_basic_to_typed.gd7
-rw-r--r--modules/gdscript/tests/scripts/runtime/errors/typed_dictionary_pass_basic_to_typed.out6
-rw-r--r--modules/gdscript/tests/scripts/runtime/errors/typed_dictionary_pass_differently_to_typed.gd7
-rw-r--r--modules/gdscript/tests/scripts/runtime/errors/typed_dictionary_pass_differently_to_typed.out6
-rw-r--r--modules/gdscript/tests/scripts/runtime/features/array_string_stringname_equivalent.gd2
-rw-r--r--modules/gdscript/tests/scripts/runtime/features/constants_are_read_only.gd9
-rw-r--r--modules/gdscript/tests/scripts/runtime/features/conversions_from_native_members.gd4
-rw-r--r--modules/gdscript/tests/scripts/runtime/features/default_set_beforehand.gd14
-rw-r--r--modules/gdscript/tests/scripts/runtime/features/dictionary_string_stringname_equivalent.gd4
-rw-r--r--modules/gdscript/tests/scripts/runtime/features/dictionary_string_stringname_equivalent.out2
-rw-r--r--modules/gdscript/tests/scripts/runtime/features/export_group_no_name_conflict_with_properties.gd2
-rw-r--r--modules/gdscript/tests/scripts/runtime/features/for_loop_iterator_specified_types.gd9
-rw-r--r--modules/gdscript/tests/scripts/runtime/features/for_loop_iterator_specified_types.out4
-rw-r--r--modules/gdscript/tests/scripts/runtime/features/match_with_pattern_guards.gd2
-rw-r--r--modules/gdscript/tests/scripts/runtime/features/member_info.gd26
-rw-r--r--modules/gdscript/tests/scripts/runtime/features/member_info.out24
-rw-r--r--modules/gdscript/tests/scripts/runtime/features/member_info_inheritance.gd2
-rw-r--r--modules/gdscript/tests/scripts/runtime/features/metatypes.gd1
-rw-r--r--modules/gdscript/tests/scripts/runtime/features/set_does_not_leak.gd4
-rw-r--r--modules/gdscript/tests/scripts/runtime/features/single_underscore_node_name.gd2
-rw-r--r--modules/gdscript/tests/scripts/runtime/features/standalone_calls_do_not_write_to_nil.gd14
-rw-r--r--modules/gdscript/tests/scripts/runtime/features/typed_array_init_with_untyped_in_literal.gd4
-rw-r--r--modules/gdscript/tests/scripts/runtime/features/typed_dictionary_implicit_cast_param.gd6
-rw-r--r--modules/gdscript/tests/scripts/runtime/features/typed_dictionary_implicit_cast_param.out3
-rw-r--r--modules/gdscript/tests/scripts/runtime/features/typed_dictionary_init_with_untyped_in_literal.gd7
-rw-r--r--modules/gdscript/tests/scripts/runtime/features/typed_dictionary_init_with_untyped_in_literal.out2
-rw-r--r--modules/gdscript/tests/scripts/utils.notest.gd25
-rw-r--r--modules/gltf/doc_classes/GLTFDocument.xml7
-rw-r--r--modules/gltf/editor/editor_scene_importer_blend.cpp2
-rw-r--r--modules/gltf/editor/editor_scene_importer_blend.h2
-rw-r--r--modules/gltf/editor/editor_scene_importer_gltf.cpp2
-rw-r--r--modules/gltf/editor/editor_scene_importer_gltf.h2
-rw-r--r--modules/gltf/extensions/gltf_document_extension.cpp50
-rw-r--r--modules/gltf/extensions/gltf_document_extension_convert_importer_mesh.cpp2
-rw-r--r--modules/gltf/extensions/physics/gltf_physics_shape.cpp2
-rw-r--r--modules/gltf/gltf_document.cpp109
-rw-r--r--modules/gltf/gltf_document.h2
-rw-r--r--modules/gltf/tests/test_gltf_extras.h165
-rw-r--r--modules/interactive_music/audio_stream_interactive.cpp8
-rw-r--r--modules/interactive_music/doc_classes/AudioStreamInteractive.xml2
-rw-r--r--modules/lightmapper_rd/lightmapper_rd.cpp52
-rw-r--r--modules/lightmapper_rd/lightmapper_rd.h1
-rw-r--r--modules/lightmapper_rd/lm_compute.glsl27
-rw-r--r--modules/minimp3/audio_stream_mp3.cpp32
-rw-r--r--modules/minimp3/audio_stream_mp3.h2
-rwxr-xr-xmodules/mono/build_scripts/build_assemblies.py19
-rw-r--r--modules/mono/csharp_script.cpp2
-rw-r--r--modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/GeneratedSources/ExportedFields_ScriptProperties.generated.cs2
-rw-r--r--modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/GeneratedSources/ExportedProperties_ScriptProperties.generated.cs2
-rw-r--r--modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/GodotEnums.cs3
-rw-r--r--modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/MarshalUtils.cs8
-rw-r--r--modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptPropertiesGenerator.cs69
-rw-r--r--modules/mono/editor/GodotTools/GodotTools.BuildLogger/GodotBuildLogger.cs4
-rw-r--r--modules/mono/editor/GodotTools/GodotTools/Build/BuildManager.cs5
-rw-r--r--modules/mono/editor/GodotTools/GodotTools/Export/ExportPlugin.cs60
-rw-r--r--modules/mono/editor/bindings_generator.cpp15
-rw-r--r--modules/mono/editor/hostfxr_resolver.cpp4
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp/GodotSharp.csproj2
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharpEditor/GodotSharpEditor.csproj2
-rw-r--r--modules/mono/godotsharp_dirs.cpp2
-rw-r--r--modules/multiplayer/editor/editor_network_profiler.cpp4
-rw-r--r--modules/multiplayer/scene_rpc_interface.cpp2
-rw-r--r--modules/navigation/2d/nav_mesh_generator_2d.cpp79
-rw-r--r--modules/navigation/3d/nav_mesh_generator_3d.cpp79
-rw-r--r--modules/navigation/3d/nav_mesh_queries_3d.cpp715
-rw-r--r--modules/navigation/3d/nav_mesh_queries_3d.h54
-rw-r--r--modules/navigation/nav_map.cpp617
-rw-r--r--modules/navigation/nav_map.h14
-rw-r--r--modules/navigation/nav_region.cpp78
-rw-r--r--modules/navigation/nav_utils.h172
-rw-r--r--modules/noise/noise_texture_2d.cpp3
-rw-r--r--modules/noise/noise_texture_3d.cpp3
-rw-r--r--modules/noise/tests/test_noise_texture_2d.h4
-rw-r--r--modules/noise/tests/test_noise_texture_3d.h4
-rw-r--r--modules/openxr/openxr_api.cpp29
-rw-r--r--modules/regex/doc_classes/RegEx.xml6
-rw-r--r--modules/regex/regex.compat.inc46
-rw-r--r--modules/regex/regex.cpp25
-rw-r--r--modules/regex/regex.h10
-rw-r--r--modules/regex/tests/test_regex.h50
-rw-r--r--modules/text_server_adv/text_server_adv.cpp722
-rw-r--r--modules/text_server_adv/text_server_adv.h13
-rw-r--r--modules/text_server_fb/text_server_fb.cpp656
-rw-r--r--modules/text_server_fb/text_server_fb.h13
-rw-r--r--modules/upnp/upnp.cpp12
-rw-r--r--modules/zip/zip_packer.cpp2
-rw-r--r--modules/zip/zip_reader.cpp2
-rw-r--r--platform/SCsub6
-rw-r--r--platform/android/api/api.cpp30
-rw-r--r--platform/android/api/java_class_wrapper.h49
-rw-r--r--platform/android/api/jni_singleton.h5
-rw-r--r--platform/android/display_server_android.cpp8
-rw-r--r--platform/android/export/export_plugin.cpp26
-rw-r--r--platform/android/export/export_plugin.h8
-rw-r--r--platform/android/file_access_android.cpp96
-rw-r--r--platform/android/file_access_android.h9
-rw-r--r--platform/android/file_access_filesystem_jandroid.cpp62
-rw-r--r--platform/android/file_access_filesystem_jandroid.h8
-rw-r--r--platform/android/java/app/build.gradle1
-rw-r--r--platform/android/java/app/settings.gradle1
-rw-r--r--platform/android/java/build.gradle45
-rw-r--r--platform/android/java/editor/build.gradle45
-rw-r--r--platform/android/java/editor/src/google/java/org/godotengine/editor/GodotEditor.kt39
-rw-r--r--platform/android/java/editor/src/main/AndroidManifest.xml5
-rw-r--r--platform/android/java/editor/src/main/java/org/godotengine/editor/BaseGodotEditor.kt (renamed from platform/android/java/editor/src/main/java/org/godotengine/editor/GodotEditor.kt)248
-rw-r--r--platform/android/java/editor/src/main/java/org/godotengine/editor/EditorMessageDispatcher.kt196
-rw-r--r--platform/android/java/editor/src/main/java/org/godotengine/editor/EditorWindowInfo.kt28
-rw-r--r--platform/android/java/editor/src/main/java/org/godotengine/editor/GodotGame.kt150
-rw-r--r--platform/android/java/editor/src/main/res/drawable/ic_play_window_foreground.xml25
-rw-r--r--platform/android/java/editor/src/main/res/drawable/outline_fullscreen_exit_48.xml12
-rw-r--r--platform/android/java/editor/src/main/res/drawable/pip_button_activated_bg_drawable.xml10
-rw-r--r--platform/android/java/editor/src/main/res/drawable/pip_button_bg_drawable.xml9
-rw-r--r--platform/android/java/editor/src/main/res/drawable/pip_button_default_bg_drawable.xml10
-rw-r--r--platform/android/java/editor/src/main/res/layout/godot_game_layout.xml25
-rw-r--r--platform/android/java/editor/src/main/res/mipmap-anydpi-v26/ic_play_window.xml5
-rw-r--r--platform/android/java/editor/src/main/res/mipmap-hdpi/ic_play_window.pngbin0 -> 1954 bytes
-rw-r--r--platform/android/java/editor/src/main/res/mipmap-mdpi/ic_play_window.pngbin0 -> 1350 bytes
-rw-r--r--platform/android/java/editor/src/main/res/mipmap-xhdpi/ic_play_window.pngbin0 -> 2617 bytes
-rw-r--r--platform/android/java/editor/src/main/res/mipmap-xxhdpi/ic_play_window.pngbin0 -> 3923 bytes
-rw-r--r--platform/android/java/editor/src/main/res/mipmap-xxxhdpi/ic_play_window.pngbin0 -> 5480 bytes
-rw-r--r--platform/android/java/editor/src/main/res/values/dimens.xml2
-rw-r--r--platform/android/java/editor/src/main/res/values/strings.xml2
-rw-r--r--platform/android/java/editor/src/meta/AndroidManifest.xml99
-rw-r--r--platform/android/java/editor/src/meta/assets/vr_splash.pngbin0 -> 14766 bytes
-rw-r--r--platform/android/java/editor/src/meta/java/org/godotengine/editor/GodotEditor.kt94
-rw-r--r--platform/android/java/editor/src/meta/java/org/godotengine/editor/GodotXRGame.kt71
-rw-r--r--platform/android/java/lib/build.gradle4
-rw-r--r--platform/android/java/lib/src/org/godotengine/godot/Godot.kt114
-rw-r--r--platform/android/java/lib/src/org/godotengine/godot/GodotActivity.kt8
-rw-r--r--platform/android/java/lib/src/org/godotengine/godot/GodotRenderView.java7
-rw-r--r--platform/android/java/lib/src/org/godotengine/godot/GodotVulkanRenderView.java7
-rw-r--r--platform/android/java/lib/src/org/godotengine/godot/utils/DeviceUtils.kt52
-rw-r--r--platform/android/java/lib/src/org/godotengine/godot/utils/ProcessPhoenix.java27
-rw-r--r--platform/android/java/nativeSrcsConfigs/CMakeLists.txt4
-rw-r--r--platform/android/java/settings.gradle1
-rw-r--r--platform/android/java_class_wrapper.cpp297
-rw-r--r--platform/android/java_godot_lib_jni.cpp11
-rw-r--r--platform/android/java_godot_wrapper.cpp20
-rw-r--r--platform/android/java_godot_wrapper.h5
-rw-r--r--platform/android/jni_utils.cpp22
-rw-r--r--platform/android/jni_utils.h2
-rw-r--r--platform/android/plugin/godot_plugin_jni.cpp3
-rw-r--r--platform/ios/export/export_plugin.cpp16
-rw-r--r--platform/ios/export/export_plugin.h6
-rw-r--r--platform/linuxbsd/export/export_plugin.cpp9
-rw-r--r--platform/linuxbsd/export/export_plugin.h4
-rw-r--r--platform/linuxbsd/freedesktop_portal_desktop.cpp27
-rw-r--r--platform/linuxbsd/wayland/display_server_wayland.cpp62
-rw-r--r--platform/linuxbsd/x11/display_server_x11.cpp76
-rw-r--r--platform/macos/display_server_macos.mm1
-rw-r--r--platform/macos/doc_classes/EditorExportPlatformMacOS.xml7
-rw-r--r--platform/macos/export/export_plugin.cpp18
-rw-r--r--platform/macos/export/export_plugin.h4
-rw-r--r--platform/web/api/api.cpp2
-rw-r--r--platform/web/export/export_plugin.cpp9
-rw-r--r--platform/web/export/export_plugin.h6
-rw-r--r--platform/web/javascript_bridge_singleton.cpp2
-rw-r--r--platform/web/os_web.cpp2
-rw-r--r--platform/web/os_web.h2
-rw-r--r--platform/windows/console_wrapper_windows.cpp4
-rw-r--r--platform/windows/crash_handler_windows_seh.cpp2
-rw-r--r--platform/windows/detect.py161
-rw-r--r--platform/windows/display_server_windows.cpp136
-rw-r--r--platform/windows/display_server_windows.h10
-rw-r--r--platform/windows/export/export_plugin.cpp11
-rw-r--r--platform/windows/export/export_plugin.h6
-rw-r--r--platform/windows/godot_res_wrap.rc5
-rw-r--r--platform/windows/os_windows.cpp193
-rw-r--r--platform/windows/os_windows.h3
-rw-r--r--platform/windows/windows_utils.cpp6
-rw-r--r--scene/2d/animated_sprite_2d.cpp4
-rw-r--r--scene/2d/audio_stream_player_2d.cpp9
-rw-r--r--scene/2d/canvas_modulate.cpp2
-rw-r--r--scene/2d/light_2d.cpp2
-rw-r--r--scene/2d/light_occluder_2d.cpp2
-rw-r--r--scene/2d/navigation_link_2d.cpp2
-rw-r--r--scene/2d/navigation_region_2d.cpp1
-rw-r--r--scene/2d/parallax_2d.cpp18
-rw-r--r--scene/2d/parallax_2d.h1
-rw-r--r--scene/2d/parallax_layer.cpp2
-rw-r--r--scene/2d/path_2d.cpp9
-rw-r--r--scene/2d/physics/collision_object_2d.cpp2
-rw-r--r--scene/2d/physics/collision_polygon_2d.cpp2
-rw-r--r--scene/2d/physics/collision_shape_2d.cpp2
-rw-r--r--scene/2d/physics/physical_bone_2d.cpp2
-rw-r--r--scene/2d/remote_transform_2d.cpp12
-rw-r--r--scene/2d/skeleton_2d.cpp2
-rw-r--r--scene/2d/tile_map.cpp2
-rw-r--r--scene/2d/tile_map_layer.cpp121
-rw-r--r--scene/2d/tile_map_layer.h8
-rw-r--r--scene/3d/audio_stream_player_3d.cpp9
-rw-r--r--scene/3d/cpu_particles_3d.cpp29
-rw-r--r--scene/3d/cpu_particles_3d.h3
-rw-r--r--scene/3d/decal.cpp2
-rw-r--r--scene/3d/fog_volume.cpp2
-rw-r--r--scene/3d/gpu_particles_collision_3d.cpp2
-rw-r--r--scene/3d/lightmap_gi.cpp21
-rw-r--r--scene/3d/lightmap_gi.h5
-rw-r--r--scene/3d/mesh_instance_3d.cpp171
-rw-r--r--scene/3d/mesh_instance_3d.h1
-rw-r--r--scene/3d/navigation_link_3d.cpp2
-rw-r--r--scene/3d/navigation_region_3d.cpp2
-rw-r--r--scene/3d/occluder_instance_3d.cpp2
-rw-r--r--scene/3d/path_3d.cpp9
-rw-r--r--scene/3d/physical_bone_simulator_3d.cpp49
-rw-r--r--scene/3d/physical_bone_simulator_3d.h1
-rw-r--r--scene/3d/physics/collision_object_3d.cpp2
-rw-r--r--scene/3d/physics/collision_polygon_3d.cpp2
-rw-r--r--scene/3d/physics/collision_shape_3d.cpp2
-rw-r--r--scene/3d/physics/vehicle_body_3d.cpp12
-rw-r--r--scene/3d/physics/vehicle_body_3d.h4
-rw-r--r--scene/3d/remote_transform_3d.cpp12
-rw-r--r--scene/3d/soft_body_3d.cpp2
-rw-r--r--scene/3d/sprite_3d.cpp6
-rw-r--r--scene/3d/visual_instance_3d.cpp2
-rw-r--r--scene/3d/voxel_gi.cpp2
-rw-r--r--scene/3d/voxelizer.h3
-rw-r--r--scene/3d/xr_hand_modifier_3d.cpp12
-rw-r--r--scene/3d/xr_hand_modifier_3d.h2
-rw-r--r--scene/3d/xr_nodes.cpp6
-rw-r--r--scene/animation/animation_mixer.cpp134
-rw-r--r--scene/animation/animation_mixer.h1
-rw-r--r--scene/animation/animation_tree.cpp9
-rw-r--r--scene/animation/animation_tree.h2
-rw-r--r--scene/animation/tween.cpp3
-rw-r--r--scene/audio/audio_stream_player.cpp9
-rw-r--r--scene/gui/base_button.cpp2
-rw-r--r--scene/gui/code_edit.cpp97
-rw-r--r--scene/gui/code_edit.h3
-rw-r--r--scene/gui/color_picker.cpp6
-rw-r--r--scene/gui/control.cpp31
-rw-r--r--scene/gui/file_dialog.cpp7
-rw-r--r--scene/gui/line_edit.cpp56
-rw-r--r--scene/gui/menu_bar.cpp20
-rw-r--r--scene/gui/menu_button.cpp3
-rw-r--r--scene/gui/popup_menu.cpp8
-rw-r--r--scene/gui/popup_menu.h2
-rw-r--r--scene/gui/range.cpp2
-rw-r--r--scene/gui/rich_text_label.cpp897
-rw-r--r--scene/gui/rich_text_label.h14
-rw-r--r--scene/gui/spin_box.cpp9
-rw-r--r--scene/gui/spin_box.h5
-rw-r--r--scene/gui/subviewport_container.cpp2
-rw-r--r--scene/gui/text_edit.cpp490
-rw-r--r--scene/gui/text_edit.h35
-rw-r--r--scene/gui/tree.cpp21
-rw-r--r--scene/gui/tree.h3
-rw-r--r--scene/main/node.cpp10
-rw-r--r--scene/main/node.h2
-rw-r--r--scene/main/scene_tree.cpp8
-rw-r--r--scene/main/viewport.cpp37
-rw-r--r--scene/main/viewport.h3
-rw-r--r--scene/register_scene_types.cpp4
-rw-r--r--scene/resources/2d/navigation_mesh_source_geometry_data_2d.cpp2
-rw-r--r--scene/resources/2d/navigation_polygon.h3
-rw-r--r--scene/resources/2d/tile_set.compat.inc2
-rw-r--r--scene/resources/2d/tile_set.cpp172
-rw-r--r--scene/resources/2d/tile_set.h16
-rw-r--r--scene/resources/3d/fog_material.cpp3
-rw-r--r--scene/resources/3d/importer_mesh.cpp2
-rw-r--r--scene/resources/3d/navigation_mesh_source_geometry_data_3d.cpp2
-rw-r--r--scene/resources/3d/sky_material.cpp11
-rw-r--r--scene/resources/animation.cpp2
-rw-r--r--scene/resources/animation.h18
-rw-r--r--scene/resources/audio_stream_wav.cpp87
-rw-r--r--scene/resources/audio_stream_wav.h6
-rw-r--r--scene/resources/material.cpp2
-rw-r--r--scene/resources/mesh.cpp1
-rw-r--r--scene/resources/navigation_mesh.h5
-rw-r--r--scene/resources/packed_scene.cpp148
-rw-r--r--scene/resources/packed_scene.h2
-rw-r--r--scene/resources/particle_process_material.cpp32
-rw-r--r--scene/resources/particle_process_material.h4
-rw-r--r--scene/resources/portable_compressed_texture.cpp2
-rw-r--r--scene/resources/resource_format_text.cpp43
-rw-r--r--scene/resources/resource_format_text.h12
-rw-r--r--scene/resources/shader.cpp2
-rw-r--r--scene/resources/sprite_frames.cpp7
-rw-r--r--scene/resources/sprite_frames.h1
-rw-r--r--scene/resources/style_box_flat.cpp4
-rw-r--r--scene/resources/surface_tool.cpp8
-rw-r--r--scene/resources/text_paragraph.cpp47
-rw-r--r--scene/scene_string_names.cpp1
-rw-r--r--scene/scene_string_names.h1
-rw-r--r--scene/theme/default_theme.cpp2
-rw-r--r--scene/theme/icons/default_theme_icons_builders.py20
-rw-r--r--servers/audio/audio_stream.cpp10
-rw-r--r--servers/audio/effects/audio_effect_record.cpp12
-rw-r--r--servers/audio_server.cpp18
-rw-r--r--servers/display_server.cpp12
-rw-r--r--servers/navigation/navigation_globals.h66
-rw-r--r--servers/navigation_server_3d.cpp15
-rw-r--r--servers/rendering/renderer_rd/effects/taa.cpp4
-rw-r--r--servers/rendering/renderer_rd/environment/sky.cpp3
-rw-r--r--servers/rendering/renderer_rd/forward_clustered/render_forward_clustered.cpp2
-rw-r--r--servers/rendering/renderer_rd/shaders/effects/taa_resolve.glsl4
-rw-r--r--servers/rendering/renderer_rd/shaders/forward_clustered/scene_forward_clustered.glsl24
-rw-r--r--servers/rendering/renderer_rd/shaders/forward_mobile/scene_forward_mobile.glsl18
-rw-r--r--servers/rendering/renderer_rd/shaders/scene_forward_lights_inc.glsl61
-rw-r--r--servers/rendering/renderer_rd/storage_rd/light_storage.cpp2
-rw-r--r--servers/rendering/renderer_rd/storage_rd/light_storage.h2
-rw-r--r--servers/rendering/renderer_rd/storage_rd/mesh_storage.cpp1
-rw-r--r--servers/rendering/renderer_rd/storage_rd/texture_storage.cpp20
-rw-r--r--servers/rendering/renderer_scene_cull.cpp2
-rw-r--r--servers/rendering/rendering_device.cpp9
-rw-r--r--servers/rendering/rendering_device.h4
-rw-r--r--servers/rendering/shader_language.cpp39
-rw-r--r--servers/rendering/shader_language.h10
-rw-r--r--servers/rendering_server.cpp1
-rw-r--r--servers/rendering_server.h2
-rw-r--r--servers/text_server.cpp6
-rw-r--r--servers/xr_server.cpp4
-rw-r--r--tests/core/io/test_http_client.h2
-rw-r--r--tests/core/io/test_json_native.h160
-rw-r--r--tests/core/object/test_object.h25
-rw-r--r--tests/core/string/test_string.h31
-rw-r--r--tests/core/variant/test_dictionary.h42
-rwxr-xr-xtests/create_test.py15
-rw-r--r--tests/python_build/fixtures/gles3/vertex_fragment_expected_full.glsl28
-rw-r--r--tests/python_build/fixtures/glsl/compute_expected_full.glsl14
-rw-r--r--tests/python_build/fixtures/glsl/vertex_fragment_expected_full.glsl34
-rw-r--r--tests/python_build/fixtures/rd_glsl/compute_expected_full.glsl14
-rw-r--r--tests/python_build/fixtures/rd_glsl/vertex_fragment_expected_full.glsl27
-rw-r--r--tests/scene/test_audio_stream_wav.h20
-rw-r--r--tests/scene/test_button.h66
-rw-r--r--tests/scene/test_gradient_texture.h87
-rw-r--r--tests/scene/test_height_map_shape_3d.h122
-rw-r--r--tests/scene/test_option_button.h136
-rw-r--r--tests/scene/test_parallax_2d.h131
-rw-r--r--tests/scene/test_path_2d.h2
-rw-r--r--tests/scene/test_path_3d.h2
-rw-r--r--tests/scene/test_primitives.h6
-rw-r--r--tests/scene/test_text_edit.h44
-rw-r--r--tests/scene/test_tree.h24
-rw-r--r--tests/servers/test_navigation_server_3d.h160
-rw-r--r--tests/test_main.cpp8
-rw-r--r--thirdparty/README.md10
-rw-r--r--thirdparty/libwebp/patches/godot-clang-cl-fix.patch19
-rw-r--r--thirdparty/libwebp/src/dsp/cpu.h4
-rw-r--r--thirdparty/mbedtls/include/mbedtls/bignum.h2
-rw-r--r--thirdparty/mbedtls/include/mbedtls/build_info.h30
-rw-r--r--thirdparty/mbedtls/include/mbedtls/check_config.h14
-rw-r--r--thirdparty/mbedtls/include/mbedtls/config_adjust_legacy_crypto.h29
-rw-r--r--thirdparty/mbedtls/include/mbedtls/config_adjust_legacy_from_psa.h16
-rw-r--r--thirdparty/mbedtls/include/mbedtls/config_adjust_psa_from_legacy.h10
-rw-r--r--thirdparty/mbedtls/include/mbedtls/config_adjust_psa_superset_legacy.h10
-rw-r--r--thirdparty/mbedtls/include/mbedtls/config_adjust_ssl.h12
-rw-r--r--thirdparty/mbedtls/include/mbedtls/config_adjust_x509.h12
-rw-r--r--thirdparty/mbedtls/include/mbedtls/config_psa.h6
-rw-r--r--thirdparty/mbedtls/include/mbedtls/ctr_drbg.h33
-rw-r--r--thirdparty/mbedtls/include/mbedtls/ecdh.h2
-rw-r--r--thirdparty/mbedtls/include/mbedtls/ecp.h2
-rw-r--r--thirdparty/mbedtls/include/mbedtls/mbedtls_config.h64
-rw-r--r--thirdparty/mbedtls/include/mbedtls/pk.h60
-rw-r--r--thirdparty/mbedtls/include/mbedtls/ssl.h145
-rw-r--r--thirdparty/mbedtls/include/psa/crypto.h188
-rw-r--r--thirdparty/mbedtls/include/psa/crypto_adjust_auto_enabled.h10
-rw-r--r--thirdparty/mbedtls/include/psa/crypto_adjust_config_dependencies.h51
-rw-r--r--thirdparty/mbedtls/include/psa/crypto_adjust_config_key_pair_types.h10
-rw-r--r--thirdparty/mbedtls/include/psa/crypto_adjust_config_synonyms.h10
-rw-r--r--thirdparty/mbedtls/include/psa/crypto_extra.h10
-rw-r--r--thirdparty/mbedtls/include/psa/crypto_struct.h28
-rw-r--r--thirdparty/mbedtls/include/psa/crypto_types.h26
-rw-r--r--thirdparty/mbedtls/library/bignum.c31
-rw-r--r--thirdparty/mbedtls/library/bignum_core.c163
-rw-r--r--thirdparty/mbedtls/library/bignum_core.h70
-rw-r--r--thirdparty/mbedtls/library/bignum_internal.h50
-rw-r--r--thirdparty/mbedtls/library/block_cipher.c4
-rw-r--r--thirdparty/mbedtls/library/cipher.c3
-rw-r--r--thirdparty/mbedtls/library/common.h16
-rw-r--r--thirdparty/mbedtls/library/ctr_drbg.c128
-rw-r--r--thirdparty/mbedtls/library/entropy.c4
-rw-r--r--thirdparty/mbedtls/library/entropy_poll.c4
-rw-r--r--thirdparty/mbedtls/library/error.c2
-rw-r--r--thirdparty/mbedtls/library/lmots.c8
-rw-r--r--thirdparty/mbedtls/library/lms.c8
-rw-r--r--thirdparty/mbedtls/library/md.c6
-rw-r--r--thirdparty/mbedtls/library/net_sockets.c2
-rw-r--r--thirdparty/mbedtls/library/nist_kw.c4
-rw-r--r--thirdparty/mbedtls/library/pem.c4
-rw-r--r--thirdparty/mbedtls/library/pk.c46
-rw-r--r--thirdparty/mbedtls/library/platform_util.c2
-rw-r--r--thirdparty/mbedtls/library/psa_crypto_core.h128
-rw-r--r--thirdparty/mbedtls/library/psa_crypto_driver_wrappers.h9
-rw-r--r--thirdparty/mbedtls/library/psa_crypto_random_impl.h17
-rw-r--r--thirdparty/mbedtls/library/psa_crypto_rsa.h14
-rw-r--r--thirdparty/mbedtls/library/psa_crypto_slot_management.h79
-rw-r--r--thirdparty/mbedtls/library/rsa.c3
-rw-r--r--thirdparty/mbedtls/library/sha256.c4
-rw-r--r--thirdparty/mbedtls/library/ssl_cookie.c4
-rw-r--r--thirdparty/mbedtls/library/ssl_debug_helpers_generated.c2
-rw-r--r--thirdparty/mbedtls/library/ssl_misc.h96
-rw-r--r--thirdparty/mbedtls/library/ssl_msg.c39
-rw-r--r--thirdparty/mbedtls/library/ssl_ticket.c4
-rw-r--r--thirdparty/mbedtls/library/ssl_tls.c617
-rw-r--r--thirdparty/mbedtls/library/ssl_tls12_client.c6
-rw-r--r--thirdparty/mbedtls/library/ssl_tls12_server.c25
-rw-r--r--thirdparty/mbedtls/library/ssl_tls13_client.c7
-rw-r--r--thirdparty/mbedtls/library/ssl_tls13_generic.c169
-rw-r--r--thirdparty/mbedtls/library/ssl_tls13_server.c29
-rw-r--r--thirdparty/mbedtls/library/version_features.c3
-rw-r--r--thirdparty/mbedtls/library/x509_crt.c2
-rw-r--r--thirdparty/mbedtls/library/x509write_crt.c4
-rw-r--r--thirdparty/mbedtls/library/x509write_csr.c4
-rw-r--r--thirdparty/mbedtls/patches/msvc-redeclaration-bug.diff20
-rw-r--r--thirdparty/mbedtls/patches/no-flexible-arrays.diff132
-rw-r--r--thirdparty/misc/patches/qoa-min-fix.patch14
-rw-r--r--thirdparty/misc/qoa.h6
-rw-r--r--thirdparty/thorvg/inc/config.h2
-rw-r--r--thirdparty/thorvg/patches/pr2716-text-drawing-reliability.patch25
-rw-r--r--thirdparty/thorvg/patches/revert-tvgLines-bezier-precision-change.patch13
-rw-r--r--thirdparty/thorvg/src/common/tvgLines.cpp2
-rw-r--r--thirdparty/thorvg/src/common/tvgMath.cpp12
-rw-r--r--thirdparty/thorvg/src/common/tvgMath.h10
-rw-r--r--thirdparty/thorvg/src/loaders/svg/tvgSvgLoader.cpp16
-rw-r--r--thirdparty/thorvg/src/renderer/sw_engine/tvgSwCommon.h9
-rw-r--r--thirdparty/thorvg/src/renderer/sw_engine/tvgSwRaster.cpp2
-rw-r--r--thirdparty/thorvg/src/renderer/sw_engine/tvgSwRenderer.cpp5
-rw-r--r--thirdparty/thorvg/src/renderer/sw_engine/tvgSwRenderer.h2
-rw-r--r--thirdparty/thorvg/src/renderer/sw_engine/tvgSwShape.cpp2
-rw-r--r--thirdparty/thorvg/src/renderer/tvgLoadModule.h3
-rw-r--r--thirdparty/thorvg/src/renderer/tvgPaint.cpp2
-rw-r--r--thirdparty/thorvg/src/renderer/tvgPicture.cpp2
-rw-r--r--thirdparty/thorvg/src/renderer/tvgRender.h2
-rw-r--r--thirdparty/thorvg/src/renderer/tvgScene.h2
-rw-r--r--thirdparty/thorvg/src/renderer/tvgShape.h3
-rw-r--r--thirdparty/thorvg/src/renderer/tvgText.h20
-rwxr-xr-xthirdparty/thorvg/update-thorvg.sh18
843 files changed, 21232 insertions, 8558 deletions
diff --git a/.gitattributes b/.gitattributes
index 5af3e121a8..30d1acb497 100644
--- a/.gitattributes
+++ b/.gitattributes
@@ -1,6 +1,6 @@
# Properly detect languages on Github
-*.h linguist-language=cpp
-*.inc linguist-language=cpp
+*.h linguist-language=C++
+*.inc linguist-language=C++
thirdparty/* linguist-vendored
# Normalize EOL for all files that Git considers text files
diff --git a/.github/workflows/android_builds.yml b/.github/workflows/android_builds.yml
index ee75d53282..69ab830829 100644
--- a/.github/workflows/android_builds.yml
+++ b/.github/workflows/android_builds.yml
@@ -85,6 +85,7 @@ jobs:
run: |
cd platform/android/java
./gradlew generateGodotEditor
+ ./gradlew generateGodotMetaEditor
cd ../../..
ls -l bin/android_editor_builds/
diff --git a/.github/workflows/godot_cpp_test.yml b/.github/workflows/godot_cpp_test.yml
index e1c37bbc1e..e3223c799b 100644
--- a/.github/workflows/godot_cpp_test.yml
+++ b/.github/workflows/godot_cpp_test.yml
@@ -56,3 +56,13 @@ jobs:
cd godot-cpp/test
scons target=template_debug dev_build=yes
cd ../..
+
+ gdextension-c-compile:
+ runs-on: "ubuntu-20.04"
+ name: "Check GDExtension header with a C compiler"
+ steps:
+ - uses: actions/checkout@v4
+
+ - name: "Run C compiler on gdextension_interface.h"
+ run: |
+ gcc -c core/extension/gdextension_interface.h
diff --git a/.github/workflows/windows_builds.yml b/.github/workflows/windows_builds.yml
index 90629204e6..0c21576517 100644
--- a/.github/workflows/windows_builds.yml
+++ b/.github/workflows/windows_builds.yml
@@ -30,6 +30,14 @@ jobs:
# Skip debug symbols, they're way too big with MSVC.
sconsflags: debug_symbols=no vsproj=yes vsproj_gen_only=no windows_subsystem=console
bin: "./bin/godot.windows.editor.x86_64.exe"
+ artifact: true
+
+ - name: Editor w/ clang-cl (target=editor, tests=yes, use_llvm=yes)
+ cache-name: windows-editor-clang
+ target: editor
+ tests: true
+ sconsflags: debug_symbols=no windows_subsystem=console use_llvm=yes
+ bin: ./bin/godot.windows.editor.x86_64.llvm.exe
- name: Template (target=template_release)
cache-name: windows-template
@@ -37,6 +45,7 @@ jobs:
tests: true
sconsflags: debug_symbols=no tests=yes
bin: "./bin/godot.windows.template_release.x86_64.console.exe"
+ artifact: true
steps:
- uses: actions/checkout@v4
@@ -84,10 +93,12 @@ jobs:
continue-on-error: true
- name: Prepare artifact
+ if: ${{ matrix.artifact }}
run: |
Remove-Item bin/* -Include *.exp,*.lib,*.pdb -Force
- name: Upload artifact
+ if: ${{ matrix.artifact }}
uses: ./.github/actions/upload-artifact
with:
name: ${{ matrix.cache-name }}
diff --git a/SConstruct b/SConstruct
index d2cfe2732c..5c1ccd2449 100644
--- a/SConstruct
+++ b/SConstruct
@@ -237,6 +237,11 @@ opts.Add(BoolVariable("ninja", "Use the ninja backend for faster rebuilds", Fals
opts.Add(BoolVariable("ninja_auto_run", "Run ninja automatically after generating the ninja file", True))
opts.Add("ninja_file", "Path to the generated ninja file", "build.ninja")
opts.Add(BoolVariable("compiledb", "Generate compilation DB (`compile_commands.json`) for external tools", False))
+opts.Add(
+ "num_jobs",
+ "Use up to N jobs when compiling (equivalent to `-j N`). Defaults to max jobs - 1. Ignored if -j is used.",
+ "",
+)
opts.Add(BoolVariable("verbose", "Enable verbose output for the compilation", False))
opts.Add(BoolVariable("progress", "Show a progress indicator during compilation", True))
opts.Add(EnumVariable("warnings", "Level of compilation warnings", "all", ("extra", "all", "moderate", "no")))
@@ -540,16 +545,22 @@ initial_num_jobs = env.GetOption("num_jobs")
altered_num_jobs = initial_num_jobs + 1
env.SetOption("num_jobs", altered_num_jobs)
if env.GetOption("num_jobs") == altered_num_jobs:
- cpu_count = os.cpu_count()
- if cpu_count is None:
- print_warning("Couldn't auto-detect CPU count to configure build parallelism. Specify it with the -j argument.")
+ num_jobs = env.get("num_jobs", "")
+ if str(num_jobs).isdigit() and int(num_jobs) > 0:
+ env.SetOption("num_jobs", num_jobs)
else:
- safer_cpu_count = cpu_count if cpu_count <= 4 else cpu_count - 1
- print(
- "Auto-detected %d CPU cores available for build parallelism. Using %d cores by default. You can override it with the -j argument."
- % (cpu_count, safer_cpu_count)
- )
- env.SetOption("num_jobs", safer_cpu_count)
+ cpu_count = os.cpu_count()
+ if cpu_count is None:
+ print_warning(
+ "Couldn't auto-detect CPU count to configure build parallelism. Specify it with the `-j` or `num_jobs` arguments."
+ )
+ else:
+ safer_cpu_count = cpu_count if cpu_count <= 4 else cpu_count - 1
+ print(
+ "Auto-detected %d CPU cores available for build parallelism. Using %d cores by default. You can override it with the `-j` or `num_jobs` arguments."
+ % (cpu_count, safer_cpu_count)
+ )
+ env.SetOption("num_jobs", safer_cpu_count)
env.extra_suffix = ""
@@ -780,6 +791,8 @@ else:
# Note that this is still not complete conformance, as certain Windows-related headers
# don't compile under complete conformance.
env.Prepend(CCFLAGS=["/permissive-"])
+ # Allow use of `__cplusplus` macro to determine C++ standard universally.
+ env.Prepend(CXXFLAGS=["/Zc:__cplusplus"])
# Disable exception handling. Godot doesn't use exceptions anywhere, and this
# saves around 20% of binary size and very significant build time (GH-80513).
@@ -792,7 +805,7 @@ elif env.msvc:
env.Append(CXXFLAGS=["/EHsc"])
# Configure compiler warnings
-if env.msvc: # MSVC
+if env.msvc and not methods.using_clang(env): # MSVC
if env["warnings"] == "no":
env.Append(CCFLAGS=["/w"])
else:
@@ -842,8 +855,11 @@ else: # GCC, Clang
# for putting them in `Set` or `Map`. We don't mind about unreliable ordering.
common_warnings += ["-Wno-ordered-compare-function-pointers"]
+ # clang-cl will interpret `-Wall` as `-Weverything`, workaround with compatibility cast
+ W_ALL = "-Wall" if not env.msvc else "-W3"
+
if env["warnings"] == "extra":
- env.Append(CCFLAGS=["-Wall", "-Wextra", "-Wwrite-strings", "-Wno-unused-parameter"] + common_warnings)
+ env.Append(CCFLAGS=[W_ALL, "-Wextra", "-Wwrite-strings", "-Wno-unused-parameter"] + common_warnings)
env.Append(CXXFLAGS=["-Wctor-dtor-privacy", "-Wnon-virtual-dtor"])
if methods.using_gcc(env):
env.Append(
@@ -865,9 +881,9 @@ else: # GCC, Clang
elif methods.using_clang(env) or methods.using_emcc(env):
env.Append(CCFLAGS=["-Wimplicit-fallthrough"])
elif env["warnings"] == "all":
- env.Append(CCFLAGS=["-Wall"] + common_warnings)
+ env.Append(CCFLAGS=[W_ALL] + common_warnings)
elif env["warnings"] == "moderate":
- env.Append(CCFLAGS=["-Wall", "-Wno-unused"] + common_warnings)
+ env.Append(CCFLAGS=[W_ALL, "-Wno-unused"] + common_warnings)
else: # 'no'
env.Append(CCFLAGS=["-w"])
@@ -1021,7 +1037,9 @@ if env["vsproj"]:
if env["compiledb"]:
if env.scons_version < (4, 0, 0):
# Generating the compilation DB (`compile_commands.json`) requires SCons 4.0.0 or later.
- print_error("The `compiledb=yes` option requires SCons 4.0 or later, but your version is %s." % scons_raw_version)
+ print_error(
+ "The `compiledb=yes` option requires SCons 4.0 or later, but your version is %s." % scons_raw_version
+ )
Exit(255)
env.Tool("compilation_db")
diff --git a/core/SCsub b/core/SCsub
index 1bd4eae16c..c8267ae960 100644
--- a/core/SCsub
+++ b/core/SCsub
@@ -140,7 +140,7 @@ if env["builtin_zstd"]:
"decompress/zstd_decompress_block.c",
"decompress/zstd_decompress.c",
]
- if env["platform"] in ["android", "ios", "linuxbsd", "macos"]:
+ if env["platform"] in ["android", "ios", "linuxbsd", "macos"] and env["arch"] == "x86_64":
# Match platforms with ZSTD_ASM_SUPPORTED in common/portability_macros.h
thirdparty_zstd_sources.append("decompress/huf_decompress_amd64.S")
thirdparty_zstd_sources = [thirdparty_zstd_dir + file for file in thirdparty_zstd_sources]
diff --git a/core/core_bind.compat.inc b/core/core_bind.compat.inc
index 83b7b33e38..3e8ac3c5de 100644
--- a/core/core_bind.compat.inc
+++ b/core/core_bind.compat.inc
@@ -32,6 +32,8 @@
namespace core_bind {
+// Semaphore
+
void Semaphore::_post_bind_compat_93605() {
post(1);
}
@@ -40,6 +42,16 @@ void Semaphore::_bind_compatibility_methods() {
ClassDB::bind_compatibility_method(D_METHOD("post"), &Semaphore::_post_bind_compat_93605);
}
-}; // namespace core_bind
+// OS
+
+Dictionary OS::_execute_with_pipe_bind_compat_94434(const String &p_path, const Vector<String> &p_arguments) {
+ return execute_with_pipe(p_path, p_arguments, true);
+}
+
+void OS::_bind_compatibility_methods() {
+ ClassDB::bind_compatibility_method(D_METHOD("execute_with_pipe", "path", "arguments"), &OS::_execute_with_pipe_bind_compat_94434);
+}
+
+} // namespace core_bind
-#endif
+#endif // DISABLE_DEPRECATED
diff --git a/core/core_bind.cpp b/core/core_bind.cpp
index 5137930116..750598ab20 100644
--- a/core/core_bind.cpp
+++ b/core/core_bind.cpp
@@ -116,6 +116,11 @@ bool ResourceLoader::has_cached(const String &p_path) {
return ResourceCache::has(local_path);
}
+Ref<Resource> ResourceLoader::get_cached_ref(const String &p_path) {
+ String local_path = ProjectSettings::get_singleton()->localize_path(p_path);
+ return ResourceCache::get_ref(local_path);
+}
+
bool ResourceLoader::exists(const String &p_path, const String &p_type_hint) {
return ::ResourceLoader::exists(p_path, p_type_hint);
}
@@ -136,6 +141,7 @@ void ResourceLoader::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_abort_on_missing_resources", "abort"), &ResourceLoader::set_abort_on_missing_resources);
ClassDB::bind_method(D_METHOD("get_dependencies", "path"), &ResourceLoader::get_dependencies);
ClassDB::bind_method(D_METHOD("has_cached", "path"), &ResourceLoader::has_cached);
+ ClassDB::bind_method(D_METHOD("get_cached_ref", "path"), &ResourceLoader::get_cached_ref);
ClassDB::bind_method(D_METHOD("exists", "path", "type_hint"), &ResourceLoader::exists, DEFVAL(""));
ClassDB::bind_method(D_METHOD("get_resource_uid", "path"), &ResourceLoader::get_resource_uid);
@@ -308,12 +314,12 @@ int OS::execute(const String &p_path, const Vector<String> &p_arguments, Array r
return exitcode;
}
-Dictionary OS::execute_with_pipe(const String &p_path, const Vector<String> &p_arguments) {
+Dictionary OS::execute_with_pipe(const String &p_path, const Vector<String> &p_arguments, bool p_blocking) {
List<String> args;
for (const String &arg : p_arguments) {
args.push_back(arg);
}
- return ::OS::get_singleton()->execute_with_pipe(p_path, args);
+ return ::OS::get_singleton()->execute_with_pipe(p_path, args, p_blocking);
}
int OS::create_instance(const Vector<String> &p_arguments) {
@@ -613,7 +619,7 @@ void OS::_bind_methods() {
ClassDB::bind_method(D_METHOD("get_executable_path"), &OS::get_executable_path);
ClassDB::bind_method(D_METHOD("read_string_from_stdin"), &OS::read_string_from_stdin);
ClassDB::bind_method(D_METHOD("execute", "path", "arguments", "output", "read_stderr", "open_console"), &OS::execute, DEFVAL(Array()), DEFVAL(false), DEFVAL(false));
- ClassDB::bind_method(D_METHOD("execute_with_pipe", "path", "arguments"), &OS::execute_with_pipe);
+ ClassDB::bind_method(D_METHOD("execute_with_pipe", "path", "arguments", "blocking"), &OS::execute_with_pipe, DEFVAL(true));
ClassDB::bind_method(D_METHOD("create_process", "path", "arguments", "open_console"), &OS::create_process, DEFVAL(false));
ClassDB::bind_method(D_METHOD("create_instance", "arguments"), &OS::create_instance);
ClassDB::bind_method(D_METHOD("kill", "pid"), &OS::kill);
diff --git a/core/core_bind.h b/core/core_bind.h
index e953483391..e4ba0e8f56 100644
--- a/core/core_bind.h
+++ b/core/core_bind.h
@@ -83,6 +83,7 @@ public:
void set_abort_on_missing_resources(bool p_abort);
PackedStringArray get_dependencies(const String &p_path);
bool has_cached(const String &p_path);
+ Ref<Resource> get_cached_ref(const String &p_path);
bool exists(const String &p_path, const String &p_type_hint = "");
ResourceUID::ID get_resource_uid(const String &p_path);
@@ -127,6 +128,12 @@ protected:
static void _bind_methods();
static OS *singleton;
+#ifndef DISABLE_DEPRECATED
+ Dictionary _execute_with_pipe_bind_compat_94434(const String &p_path, const Vector<String> &p_arguments);
+
+ static void _bind_compatibility_methods();
+#endif
+
public:
enum RenderingDriver {
RENDERING_DRIVER_VULKAN,
@@ -160,7 +167,7 @@ public:
String get_executable_path() const;
String read_string_from_stdin();
int execute(const String &p_path, const Vector<String> &p_arguments, Array r_output = Array(), bool p_read_stderr = false, bool p_open_console = false);
- Dictionary execute_with_pipe(const String &p_path, const Vector<String> &p_arguments);
+ Dictionary execute_with_pipe(const String &p_path, const Vector<String> &p_arguments, bool p_blocking = true);
int create_process(const String &p_path, const Vector<String> &p_arguments, bool p_open_console = false);
int create_instance(const Vector<String> &p_arguments);
Error kill(int p_pid);
diff --git a/core/core_constants.cpp b/core/core_constants.cpp
index 5322e39ec0..68af5abf66 100644
--- a/core/core_constants.cpp
+++ b/core/core_constants.cpp
@@ -671,6 +671,7 @@ void register_global_constants() {
BIND_CORE_ENUM_CONSTANT(PROPERTY_HINT_INT_IS_OBJECTID);
BIND_CORE_ENUM_CONSTANT(PROPERTY_HINT_INT_IS_POINTER);
BIND_CORE_ENUM_CONSTANT(PROPERTY_HINT_ARRAY_TYPE);
+ BIND_CORE_ENUM_CONSTANT(PROPERTY_HINT_DICTIONARY_TYPE);
BIND_CORE_ENUM_CONSTANT(PROPERTY_HINT_LOCALE_ID);
BIND_CORE_ENUM_CONSTANT(PROPERTY_HINT_LOCALIZABLE_STRING);
BIND_CORE_ENUM_CONSTANT(PROPERTY_HINT_NODE_TYPE);
diff --git a/core/debugger/remote_debugger_peer.cpp b/core/debugger/remote_debugger_peer.cpp
index 21a9014626..9dca47a0b4 100644
--- a/core/debugger/remote_debugger_peer.cpp
+++ b/core/debugger/remote_debugger_peer.cpp
@@ -144,9 +144,8 @@ void RemoteDebuggerPeerTCP::_read_in() {
Error err = decode_variant(var, buf, in_pos, &read);
ERR_CONTINUE(read != in_pos || err != OK);
ERR_CONTINUE_MSG(var.get_type() != Variant::ARRAY, "Malformed packet received, not an Array.");
- mutex.lock();
+ MutexLock lock(mutex);
in_queue.push_back(var);
- mutex.unlock();
}
}
}
diff --git a/core/doc_data.cpp b/core/doc_data.cpp
index 672a36c35c..f40e878d52 100644
--- a/core/doc_data.cpp
+++ b/core/doc_data.cpp
@@ -33,6 +33,8 @@
String DocData::get_default_value_string(const Variant &p_value) {
if (p_value.get_type() == Variant::ARRAY) {
return Variant(Array(p_value, 0, StringName(), Variant())).get_construct_string().replace("\n", " ");
+ } else if (p_value.get_type() == Variant::DICTIONARY) {
+ return Variant(Dictionary(p_value, 0, StringName(), Variant(), 0, StringName(), Variant())).get_construct_string().replace("\n", " ");
} else {
return p_value.get_construct_string().replace("\n", " ");
}
@@ -57,6 +59,8 @@ void DocData::return_doc_from_retinfo(DocData::MethodDoc &p_method, const Proper
p_method.return_type = p_retinfo.class_name;
} else if (p_retinfo.type == Variant::ARRAY && p_retinfo.hint == PROPERTY_HINT_ARRAY_TYPE) {
p_method.return_type = p_retinfo.hint_string + "[]";
+ } else if (p_retinfo.type == Variant::DICTIONARY && p_retinfo.hint == PROPERTY_HINT_DICTIONARY_TYPE) {
+ p_method.return_type = "Dictionary[" + p_retinfo.hint_string.replace(";", ", ") + "]";
} else if (p_retinfo.hint == PROPERTY_HINT_RESOURCE_TYPE) {
p_method.return_type = p_retinfo.hint_string;
} else if (p_retinfo.type == Variant::NIL && p_retinfo.usage & PROPERTY_USAGE_NIL_IS_VARIANT) {
@@ -89,6 +93,8 @@ void DocData::argument_doc_from_arginfo(DocData::ArgumentDoc &p_argument, const
p_argument.type = p_arginfo.class_name;
} else if (p_arginfo.type == Variant::ARRAY && p_arginfo.hint == PROPERTY_HINT_ARRAY_TYPE) {
p_argument.type = p_arginfo.hint_string + "[]";
+ } else if (p_arginfo.type == Variant::DICTIONARY && p_arginfo.hint == PROPERTY_HINT_DICTIONARY_TYPE) {
+ p_argument.type = "Dictionary[" + p_arginfo.hint_string.replace(";", ", ") + "]";
} else if (p_arginfo.hint == PROPERTY_HINT_RESOURCE_TYPE) {
p_argument.type = p_arginfo.hint_string;
} else if (p_arginfo.type == Variant::NIL) {
diff --git a/core/doc_data.h b/core/doc_data.h
index 04bd55eaba..6a7f4355db 100644
--- a/core/doc_data.h
+++ b/core/doc_data.h
@@ -522,6 +522,10 @@ public:
String type;
String data_type;
String description;
+ bool is_deprecated = false;
+ String deprecated_message;
+ bool is_experimental = false;
+ String experimental_message;
String default_value;
String keywords;
bool operator<(const ThemeItemDoc &p_theme_item) const {
@@ -550,6 +554,16 @@ public:
doc.description = p_dict["description"];
}
+ if (p_dict.has("deprecated")) {
+ doc.is_deprecated = true;
+ doc.deprecated_message = p_dict["deprecated"];
+ }
+
+ if (p_dict.has("experimental")) {
+ doc.is_experimental = true;
+ doc.experimental_message = p_dict["experimental"];
+ }
+
if (p_dict.has("default_value")) {
doc.default_value = p_dict["default_value"];
}
@@ -579,6 +593,14 @@ public:
dict["description"] = p_doc.description;
}
+ if (p_doc.is_deprecated) {
+ dict["deprecated"] = p_doc.deprecated_message;
+ }
+
+ if (p_doc.is_experimental) {
+ dict["experimental"] = p_doc.experimental_message;
+ }
+
if (!p_doc.default_value.is_empty()) {
dict["default_value"] = p_doc.default_value;
}
diff --git a/core/extension/extension_api_dump.cpp b/core/extension/extension_api_dump.cpp
index 848b6f3886..296ebc901f 100644
--- a/core/extension/extension_api_dump.cpp
+++ b/core/extension/extension_api_dump.cpp
@@ -60,6 +60,9 @@ static String get_property_info_type_name(const PropertyInfo &p_info) {
if (p_info.type == Variant::ARRAY && (p_info.hint == PROPERTY_HINT_ARRAY_TYPE)) {
return String("typedarray::") + p_info.hint_string;
}
+ if (p_info.type == Variant::DICTIONARY && (p_info.hint == PROPERTY_HINT_DICTIONARY_TYPE)) {
+ return String("typeddictionary::") + p_info.hint_string;
+ }
if (p_info.type == Variant::INT && (p_info.usage & (PROPERTY_USAGE_CLASS_IS_ENUM))) {
return String("enum::") + String(p_info.class_name);
}
diff --git a/core/extension/gdextension.cpp b/core/extension/gdextension.cpp
index c9e609cddc..d4b50facb2 100644
--- a/core/extension/gdextension.cpp
+++ b/core/extension/gdextension.cpp
@@ -675,15 +675,13 @@ GDExtensionInterfaceFunctionPtr GDExtension::get_interface_function(const String
}
Error GDExtension::open_library(const String &p_path, const Ref<GDExtensionLoader> &p_loader) {
- ERR_FAIL_NULL_V_MSG(p_loader, FAILED, "Can't open GDExtension without a loader.");
+ ERR_FAIL_COND_V_MSG(p_loader.is_null(), FAILED, "Can't open GDExtension without a loader.");
loader = p_loader;
- String abs_path = ProjectSettings::get_singleton()->globalize_path(p_path);
+ Error err = loader->open_library(p_path);
- Error err = loader->open_library(abs_path);
-
- ERR_FAIL_COND_V_MSG(err == ERR_FILE_NOT_FOUND, err, "GDExtension dynamic library not found: " + abs_path);
- ERR_FAIL_COND_V_MSG(err != OK, err, "Can't open GDExtension dynamic library: " + abs_path);
+ ERR_FAIL_COND_V_MSG(err == ERR_FILE_NOT_FOUND, err, "GDExtension dynamic library not found: " + p_path);
+ ERR_FAIL_COND_V_MSG(err != OK, err, "Can't open GDExtension dynamic library: " + p_path);
err = loader->initialize(&gdextension_get_proc_address, this, &initialization);
diff --git a/core/extension/gdextension_interface.cpp b/core/extension/gdextension_interface.cpp
index a5a0fc906a..ddf90f6130 100644
--- a/core/extension/gdextension_interface.cpp
+++ b/core/extension/gdextension_interface.cpp
@@ -1199,6 +1199,15 @@ static GDExtensionVariantPtr gdextension_dictionary_operator_index_const(GDExten
return (GDExtensionVariantPtr)&self->operator[](*(const Variant *)p_key);
}
+void gdextension_dictionary_set_typed(GDExtensionTypePtr p_self, GDExtensionVariantType p_key_type, GDExtensionConstStringNamePtr p_key_class_name, GDExtensionConstVariantPtr p_key_script, GDExtensionVariantType p_value_type, GDExtensionConstStringNamePtr p_value_class_name, GDExtensionConstVariantPtr p_value_script) {
+ Dictionary *self = reinterpret_cast<Dictionary *>(p_self);
+ const StringName *key_class_name = reinterpret_cast<const StringName *>(p_key_class_name);
+ const Variant *key_script = reinterpret_cast<const Variant *>(p_key_script);
+ const StringName *value_class_name = reinterpret_cast<const StringName *>(p_value_class_name);
+ const Variant *value_script = reinterpret_cast<const Variant *>(p_value_script);
+ self->set_typed((uint32_t)p_key_type, *key_class_name, *key_script, (uint32_t)p_value_type, *value_class_name, *value_script);
+}
+
/* OBJECT API */
static void gdextension_object_method_bind_call(GDExtensionMethodBindPtr p_method_bind, GDExtensionObjectPtr p_instance, const GDExtensionConstVariantPtr *p_args, GDExtensionInt p_arg_count, GDExtensionUninitializedVariantPtr r_return, GDExtensionCallError *r_error) {
@@ -1299,7 +1308,7 @@ static void gdextension_object_call_script_method(GDExtensionObjectPtr p_object,
const StringName method = *reinterpret_cast<const StringName *>(p_method);
const Variant **args = (const Variant **)p_args;
- Callable::CallError error;
+ Callable::CallError error; // TODO: Check `error`?
memnew_placement(r_return, Variant);
*(Variant *)r_return = o->callp(method, args, p_argument_count, error);
@@ -1679,6 +1688,7 @@ void gdextension_setup_interface() {
REGISTER_INTERFACE_FUNC(array_set_typed);
REGISTER_INTERFACE_FUNC(dictionary_operator_index);
REGISTER_INTERFACE_FUNC(dictionary_operator_index_const);
+ REGISTER_INTERFACE_FUNC(dictionary_set_typed);
REGISTER_INTERFACE_FUNC(object_method_bind_call);
REGISTER_INTERFACE_FUNC(object_method_bind_ptrcall);
REGISTER_INTERFACE_FUNC(object_destroy);
diff --git a/core/extension/gdextension_interface.h b/core/extension/gdextension_interface.h
index cac76d39bd..d3132baf1b 100644
--- a/core/extension/gdextension_interface.h
+++ b/core/extension/gdextension_interface.h
@@ -268,7 +268,7 @@ typedef void (*GDExtensionClassReference)(GDExtensionClassInstancePtr p_instance
typedef void (*GDExtensionClassUnreference)(GDExtensionClassInstancePtr p_instance);
typedef void (*GDExtensionClassCallVirtual)(GDExtensionClassInstancePtr p_instance, const GDExtensionConstTypePtr *p_args, GDExtensionTypePtr r_ret);
typedef GDExtensionObjectPtr (*GDExtensionClassCreateInstance)(void *p_class_userdata);
-typedef GDExtensionObjectPtr (*GDExtensionClassCreateInstance2)(void *p_class_userdata, bool p_notify_postinitialize);
+typedef GDExtensionObjectPtr (*GDExtensionClassCreateInstance2)(void *p_class_userdata, GDExtensionBool p_notify_postinitialize);
typedef void (*GDExtensionClassFreeInstance)(void *p_class_userdata, GDExtensionClassInstancePtr p_instance);
typedef GDExtensionClassInstancePtr (*GDExtensionClassRecreateInstance)(void *p_class_userdata, GDExtensionObjectPtr p_object);
typedef GDExtensionClassCallVirtual (*GDExtensionClassGetVirtual)(void *p_class_userdata, GDExtensionConstStringNamePtr p_name);
@@ -2372,6 +2372,22 @@ typedef GDExtensionVariantPtr (*GDExtensionInterfaceDictionaryOperatorIndex)(GDE
*/
typedef GDExtensionVariantPtr (*GDExtensionInterfaceDictionaryOperatorIndexConst)(GDExtensionConstTypePtr p_self, GDExtensionConstVariantPtr p_key);
+/**
+ * @name dictionary_set_typed
+ * @since 4.4
+ *
+ * Makes a Dictionary into a typed Dictionary.
+ *
+ * @param p_self A pointer to the Dictionary.
+ * @param p_key_type The type of Variant the Dictionary key will store.
+ * @param p_key_class_name A pointer to a StringName with the name of the object (if p_key_type is GDEXTENSION_VARIANT_TYPE_OBJECT).
+ * @param p_key_script A pointer to a Script object (if p_key_type is GDEXTENSION_VARIANT_TYPE_OBJECT and the base class is extended by a script).
+ * @param p_value_type The type of Variant the Dictionary value will store.
+ * @param p_value_class_name A pointer to a StringName with the name of the object (if p_value_type is GDEXTENSION_VARIANT_TYPE_OBJECT).
+ * @param p_value_script A pointer to a Script object (if p_value_type is GDEXTENSION_VARIANT_TYPE_OBJECT and the base class is extended by a script).
+ */
+typedef void (*GDExtensionInterfaceDictionarySetTyped)(GDExtensionTypePtr p_self, GDExtensionVariantType p_key_type, GDExtensionConstStringNamePtr p_key_class_name, GDExtensionConstVariantPtr p_key_script, GDExtensionVariantType p_value_type, GDExtensionConstStringNamePtr p_value_class_name, GDExtensionConstVariantPtr p_value_script);
+
/* INTERFACE: Object */
/**
diff --git a/core/extension/gdextension_manager.cpp b/core/extension/gdextension_manager.cpp
index eeae6b1996..01efe0d96e 100644
--- a/core/extension/gdextension_manager.cpp
+++ b/core/extension/gdextension_manager.cpp
@@ -32,14 +32,18 @@
#include "core/extension/gdextension_compat_hashes.h"
#include "core/extension/gdextension_library_loader.h"
+#include "core/io/dir_access.h"
#include "core/io/file_access.h"
#include "core/object/script_language.h"
-GDExtensionManager::LoadStatus GDExtensionManager::_load_extension_internal(const Ref<GDExtension> &p_extension) {
+GDExtensionManager::LoadStatus GDExtensionManager::_load_extension_internal(const Ref<GDExtension> &p_extension, bool p_first_load) {
if (level >= 0) { // Already initialized up to some level.
- int32_t minimum_level = p_extension->get_minimum_library_initialization_level();
- if (minimum_level < MIN(level, GDExtension::INITIALIZATION_LEVEL_SCENE)) {
- return LOAD_STATUS_NEEDS_RESTART;
+ int32_t minimum_level = 0;
+ if (!p_first_load) {
+ minimum_level = p_extension->get_minimum_library_initialization_level();
+ if (minimum_level < MIN(level, GDExtension::INITIALIZATION_LEVEL_SCENE)) {
+ return LOAD_STATUS_NEEDS_RESTART;
+ }
}
// Initialize up to current level.
for (int32_t i = minimum_level; i <= level; i++) {
@@ -51,10 +55,20 @@ GDExtensionManager::LoadStatus GDExtensionManager::_load_extension_internal(cons
gdextension_class_icon_paths[kv.key] = kv.value;
}
+#ifdef TOOLS_ENABLED
+ // Signals that a new extension is loaded so GDScript can register new class names.
+ emit_signal("extension_loaded", p_extension);
+#endif
+
return LOAD_STATUS_OK;
}
GDExtensionManager::LoadStatus GDExtensionManager::_unload_extension_internal(const Ref<GDExtension> &p_extension) {
+#ifdef TOOLS_ENABLED
+ // Signals that a new extension is unloading so GDScript can unregister class names.
+ emit_signal("extension_unloading", p_extension);
+#endif
+
if (level >= 0) { // Already initialized up to some level.
// Deinitialize down from current level.
for (int32_t i = level; i >= GDExtension::INITIALIZATION_LEVEL_CORE; i--) {
@@ -89,7 +103,7 @@ GDExtensionManager::LoadStatus GDExtensionManager::load_extension_with_loader(co
return LOAD_STATUS_FAILED;
}
- LoadStatus status = _load_extension_internal(extension);
+ LoadStatus status = _load_extension_internal(extension, true);
if (status != LOAD_STATUS_OK) {
return status;
}
@@ -135,7 +149,7 @@ GDExtensionManager::LoadStatus GDExtensionManager::reload_extension(const String
return LOAD_STATUS_FAILED;
}
- status = _load_extension_internal(extension);
+ status = _load_extension_internal(extension, false);
if (status != LOAD_STATUS_OK) {
return status;
}
@@ -274,6 +288,71 @@ void GDExtensionManager::reload_extensions() {
#endif
}
+bool GDExtensionManager::ensure_extensions_loaded(const HashSet<String> &p_extensions) {
+ Vector<String> extensions_added;
+ Vector<String> extensions_removed;
+
+ for (const String &E : p_extensions) {
+ if (!is_extension_loaded(E)) {
+ extensions_added.push_back(E);
+ }
+ }
+
+ Vector<String> loaded_extensions = get_loaded_extensions();
+ for (const String &loaded_extension : loaded_extensions) {
+ if (!p_extensions.has(loaded_extension)) {
+ // The extension may not have a .gdextension file.
+ if (!FileAccess::exists(loaded_extension)) {
+ extensions_removed.push_back(loaded_extension);
+ }
+ }
+ }
+
+ String extension_list_config_file = GDExtension::get_extension_list_config_file();
+ if (p_extensions.size()) {
+ if (extensions_added.size() || extensions_removed.size()) {
+ // Extensions were added or removed.
+ Ref<FileAccess> f = FileAccess::open(extension_list_config_file, FileAccess::WRITE);
+ for (const String &E : p_extensions) {
+ f->store_line(E);
+ }
+ }
+ } else {
+ if (loaded_extensions.size() || FileAccess::exists(extension_list_config_file)) {
+ // Extensions were removed.
+ Ref<DirAccess> da = DirAccess::create(DirAccess::ACCESS_RESOURCES);
+ da->remove(extension_list_config_file);
+ }
+ }
+
+ bool needs_restart = false;
+ for (const String &extension : extensions_added) {
+ GDExtensionManager::LoadStatus st = GDExtensionManager::get_singleton()->load_extension(extension);
+ if (st == GDExtensionManager::LOAD_STATUS_NEEDS_RESTART) {
+ needs_restart = true;
+ }
+ }
+
+ for (const String &extension : extensions_removed) {
+ GDExtensionManager::LoadStatus st = GDExtensionManager::get_singleton()->unload_extension(extension);
+ if (st == GDExtensionManager::LOAD_STATUS_NEEDS_RESTART) {
+ needs_restart = true;
+ }
+ }
+
+#ifdef TOOLS_ENABLED
+ if (extensions_added.size() || extensions_removed.size()) {
+ // Emitting extensions_reloaded so EditorNode can reload Inspector and regenerate documentation.
+ emit_signal("extensions_reloaded");
+
+ // Reload all scripts to clear out old references.
+ callable_mp_static(&GDExtensionManager::_reload_all_scripts).call_deferred();
+ }
+#endif
+
+ return needs_restart;
+}
+
GDExtensionManager *GDExtensionManager::get_singleton() {
return singleton;
}
@@ -294,6 +373,8 @@ void GDExtensionManager::_bind_methods() {
BIND_ENUM_CONSTANT(LOAD_STATUS_NEEDS_RESTART);
ADD_SIGNAL(MethodInfo("extensions_reloaded"));
+ ADD_SIGNAL(MethodInfo("extension_loaded", PropertyInfo(Variant::OBJECT, "extension", PROPERTY_HINT_RESOURCE_TYPE, "GDExtension")));
+ ADD_SIGNAL(MethodInfo("extension_unloading", PropertyInfo(Variant::OBJECT, "extension", PROPERTY_HINT_RESOURCE_TYPE, "GDExtension")));
}
GDExtensionManager *GDExtensionManager::singleton = nullptr;
diff --git a/core/extension/gdextension_manager.h b/core/extension/gdextension_manager.h
index b488189604..39a600474c 100644
--- a/core/extension/gdextension_manager.h
+++ b/core/extension/gdextension_manager.h
@@ -54,7 +54,7 @@ public:
};
private:
- LoadStatus _load_extension_internal(const Ref<GDExtension> &p_extension);
+ LoadStatus _load_extension_internal(const Ref<GDExtension> &p_extension, bool p_first_load);
LoadStatus _unload_extension_internal(const Ref<GDExtension> &p_extension);
#ifdef TOOLS_ENABLED
@@ -85,6 +85,7 @@ public:
void load_extensions();
void reload_extensions();
+ bool ensure_extensions_loaded(const HashSet<String> &p_extensions);
GDExtensionManager();
~GDExtensionManager();
diff --git a/core/io/file_access.cpp b/core/io/file_access.cpp
index c857d54925..d919243e6b 100644
--- a/core/io/file_access.cpp
+++ b/core/io/file_access.cpp
@@ -223,59 +223,44 @@ String FileAccess::fix_path(const String &p_path) const {
}
/* these are all implemented for ease of porting, then can later be optimized */
+uint8_t FileAccess::get_8() const {
+ uint8_t data = 0;
+ get_buffer(&data, sizeof(uint8_t));
-uint16_t FileAccess::get_16() const {
- uint16_t res;
- uint8_t a, b;
+ return data;
+}
- a = get_8();
- b = get_8();
+uint16_t FileAccess::get_16() const {
+ uint16_t data = 0;
+ get_buffer(reinterpret_cast<uint8_t *>(&data), sizeof(uint16_t));
if (big_endian) {
- SWAP(a, b);
+ data = BSWAP16(data);
}
- res = b;
- res <<= 8;
- res |= a;
-
- return res;
+ return data;
}
uint32_t FileAccess::get_32() const {
- uint32_t res;
- uint16_t a, b;
-
- a = get_16();
- b = get_16();
+ uint32_t data = 0;
+ get_buffer(reinterpret_cast<uint8_t *>(&data), sizeof(uint32_t));
if (big_endian) {
- SWAP(a, b);
+ data = BSWAP32(data);
}
- res = b;
- res <<= 16;
- res |= a;
-
- return res;
+ return data;
}
uint64_t FileAccess::get_64() const {
- uint64_t res;
- uint32_t a, b;
-
- a = get_32();
- b = get_32();
+ uint64_t data = 0;
+ get_buffer(reinterpret_cast<uint8_t *>(&data), sizeof(uint64_t));
if (big_endian) {
- SWAP(a, b);
+ data = BSWAP64(data);
}
- res = b;
- res <<= 32;
- res |= a;
-
- return res;
+ return data;
}
float FileAccess::get_float() const {
@@ -465,17 +450,6 @@ String FileAccess::get_as_text(bool p_skip_cr) const {
return text;
}
-uint64_t FileAccess::get_buffer(uint8_t *p_dst, uint64_t p_length) const {
- ERR_FAIL_COND_V(!p_dst && p_length > 0, -1);
-
- uint64_t i = 0;
- for (i = 0; i < p_length && !eof_reached(); i++) {
- p_dst[i] = get_8();
- }
-
- return i;
-}
-
Vector<uint8_t> FileAccess::get_buffer(int64_t p_length) const {
Vector<uint8_t> data;
@@ -488,7 +462,7 @@ Vector<uint8_t> FileAccess::get_buffer(int64_t p_length) const {
ERR_FAIL_COND_V_MSG(err != OK, data, "Can't resize data to " + itos(p_length) + " elements.");
uint8_t *w = data.ptrw();
- int64_t len = get_buffer(&w[0], p_length);
+ int64_t len = get_buffer(w, p_length);
if (len < p_length) {
data.resize(len);
@@ -512,46 +486,32 @@ String FileAccess::get_as_utf8_string(bool p_skip_cr) const {
return s;
}
-void FileAccess::store_16(uint16_t p_dest) {
- uint8_t a, b;
-
- a = p_dest & 0xFF;
- b = p_dest >> 8;
+void FileAccess::store_8(uint8_t p_dest) {
+ store_buffer(&p_dest, sizeof(uint8_t));
+}
+void FileAccess::store_16(uint16_t p_dest) {
if (big_endian) {
- SWAP(a, b);
+ p_dest = BSWAP16(p_dest);
}
- store_8(a);
- store_8(b);
+ store_buffer(reinterpret_cast<uint8_t *>(&p_dest), sizeof(uint16_t));
}
void FileAccess::store_32(uint32_t p_dest) {
- uint16_t a, b;
-
- a = p_dest & 0xFFFF;
- b = p_dest >> 16;
-
if (big_endian) {
- SWAP(a, b);
+ p_dest = BSWAP32(p_dest);
}
- store_16(a);
- store_16(b);
+ store_buffer(reinterpret_cast<uint8_t *>(&p_dest), sizeof(uint32_t));
}
void FileAccess::store_64(uint64_t p_dest) {
- uint32_t a, b;
-
- a = p_dest & 0xFFFFFFFF;
- b = p_dest >> 32;
-
if (big_endian) {
- SWAP(a, b);
+ p_dest = BSWAP64(p_dest);
}
- store_32(a);
- store_32(b);
+ store_buffer(reinterpret_cast<uint8_t *>(&p_dest), sizeof(uint64_t));
}
void FileAccess::store_real(real_t p_real) {
@@ -708,22 +668,11 @@ void FileAccess::store_csv_line(const Vector<String> &p_values, const String &p_
store_line(line);
}
-void FileAccess::store_buffer(const uint8_t *p_src, uint64_t p_length) {
- ERR_FAIL_COND(!p_src && p_length > 0);
- for (uint64_t i = 0; i < p_length; i++) {
- store_8(p_src[i]);
- }
-}
-
void FileAccess::store_buffer(const Vector<uint8_t> &p_buffer) {
uint64_t len = p_buffer.size();
- if (len == 0) {
- return;
- }
-
const uint8_t *r = p_buffer.ptr();
- store_buffer(&r[0], len);
+ store_buffer(r, len);
}
void FileAccess::store_var(const Variant &p_var, bool p_full_objects) {
diff --git a/core/io/file_access.h b/core/io/file_access.h
index 2ab84db4b6..2f4d1a8604 100644
--- a/core/io/file_access.h
+++ b/core/io/file_access.h
@@ -137,7 +137,7 @@ public:
virtual bool eof_reached() const = 0; ///< reading passed EOF
- virtual uint8_t get_8() const = 0; ///< get a byte
+ virtual uint8_t get_8() const; ///< get a byte
virtual uint16_t get_16() const; ///< get 16 bits uint
virtual uint32_t get_32() const; ///< get 32 bits uint
virtual uint64_t get_64() const; ///< get 64 bits uint
@@ -148,7 +148,7 @@ public:
Variant get_var(bool p_allow_objects = false) const;
- virtual uint64_t get_buffer(uint8_t *p_dst, uint64_t p_length) const; ///< get an array of bytes
+ virtual uint64_t get_buffer(uint8_t *p_dst, uint64_t p_length) const = 0; ///< get an array of bytes, needs to be overwritten by children.
Vector<uint8_t> get_buffer(int64_t p_length) const;
virtual String get_line() const;
virtual String get_token() const;
@@ -168,7 +168,7 @@ public:
virtual Error resize(int64_t p_length) = 0;
virtual void flush() = 0;
- virtual void store_8(uint8_t p_dest) = 0; ///< store a byte
+ virtual void store_8(uint8_t p_dest); ///< store a byte
virtual void store_16(uint16_t p_dest); ///< store 16 bits uint
virtual void store_32(uint32_t p_dest); ///< store 32 bits uint
virtual void store_64(uint64_t p_dest); ///< store 64 bits uint
@@ -184,7 +184,7 @@ public:
virtual void store_pascal_string(const String &p_string);
virtual String get_pascal_string();
- virtual void store_buffer(const uint8_t *p_src, uint64_t p_length); ///< store an array of bytes
+ virtual void store_buffer(const uint8_t *p_src, uint64_t p_length) = 0; ///< store an array of bytes, needs to be overwritten by children.
void store_buffer(const Vector<uint8_t> &p_buffer);
void store_var(const Variant &p_var, bool p_full_objects = false);
diff --git a/core/io/file_access_compressed.cpp b/core/io/file_access_compressed.cpp
index 0f00bd292c..3602baf8c5 100644
--- a/core/io/file_access_compressed.cpp
+++ b/core/io/file_access_compressed.cpp
@@ -260,38 +260,6 @@ bool FileAccessCompressed::eof_reached() const {
}
}
-uint8_t FileAccessCompressed::get_8() const {
- ERR_FAIL_COND_V_MSG(f.is_null(), 0, "File must be opened before use.");
- ERR_FAIL_COND_V_MSG(writing, 0, "File has not been opened in read mode.");
-
- if (at_end) {
- read_eof = true;
- return 0;
- }
-
- uint8_t ret = read_ptr[read_pos];
-
- read_pos++;
- if (read_pos >= read_block_size) {
- read_block++;
-
- if (read_block < read_block_count) {
- //read another block of compressed data
- f->get_buffer(comp_buffer.ptrw(), read_blocks[read_block].csize);
- int total = Compression::decompress(buffer.ptrw(), read_blocks.size() == 1 ? read_total : block_size, comp_buffer.ptr(), read_blocks[read_block].csize, cmode);
- ERR_FAIL_COND_V_MSG(total == -1, 0, "Compressed file is corrupt.");
- read_block_size = read_block == read_block_count - 1 ? read_total % block_size : block_size;
- read_pos = 0;
-
- } else {
- read_block--;
- at_end = true;
- }
- }
-
- return ret;
-}
-
uint64_t FileAccessCompressed::get_buffer(uint8_t *p_dst, uint64_t p_length) const {
ERR_FAIL_COND_V(!p_dst && p_length > 0, -1);
ERR_FAIL_COND_V_MSG(f.is_null(), -1, "File must be opened before use.");
@@ -341,12 +309,13 @@ void FileAccessCompressed::flush() {
// compressed files keep data in memory till close()
}
-void FileAccessCompressed::store_8(uint8_t p_dest) {
+void FileAccessCompressed::store_buffer(const uint8_t *p_src, uint64_t p_length) {
ERR_FAIL_COND_MSG(f.is_null(), "File must be opened before use.");
ERR_FAIL_COND_MSG(!writing, "File has not been opened in write mode.");
- WRITE_FIT(1);
- write_ptr[write_pos++] = p_dest;
+ WRITE_FIT(p_length);
+ memcpy(write_ptr + write_pos, p_src, p_length);
+ write_pos += p_length;
}
bool FileAccessCompressed::file_exists(const String &p_name) {
diff --git a/core/io/file_access_compressed.h b/core/io/file_access_compressed.h
index f706c82f8e..ea9837dd03 100644
--- a/core/io/file_access_compressed.h
+++ b/core/io/file_access_compressed.h
@@ -83,14 +83,13 @@ public:
virtual bool eof_reached() const override; ///< reading passed EOF
- virtual uint8_t get_8() const override; ///< get a byte
virtual uint64_t get_buffer(uint8_t *p_dst, uint64_t p_length) const override;
virtual Error get_error() const override; ///< get last error
virtual Error resize(int64_t p_length) override { return ERR_UNAVAILABLE; }
virtual void flush() override;
- virtual void store_8(uint8_t p_dest) override; ///< store a byte
+ virtual void store_buffer(const uint8_t *p_src, uint64_t p_length) override;
virtual bool file_exists(const String &p_name) override; ///< return true if a file exists
diff --git a/core/io/file_access_encrypted.cpp b/core/io/file_access_encrypted.cpp
index b689f5b628..13d1e0c8fc 100644
--- a/core/io/file_access_encrypted.cpp
+++ b/core/io/file_access_encrypted.cpp
@@ -37,7 +37,7 @@
#include <stdio.h>
Error FileAccessEncrypted::open_and_parse(Ref<FileAccess> p_base, const Vector<uint8_t> &p_key, Mode p_mode, bool p_with_magic) {
- ERR_FAIL_COND_V_MSG(file != nullptr, ERR_ALREADY_IN_USE, "Can't open file while another file from path '" + file->get_path_absolute() + "' is open.");
+ ERR_FAIL_COND_V_MSG(file.is_valid(), ERR_ALREADY_IN_USE, "Can't open file while another file from path '" + file->get_path_absolute() + "' is open.");
ERR_FAIL_COND_V(p_key.size() != 32, ERR_INVALID_PARAMETER);
pos = 0;
@@ -162,7 +162,7 @@ void FileAccessEncrypted::_close() {
}
bool FileAccessEncrypted::is_open() const {
- return file != nullptr;
+ return file.is_valid();
}
String FileAccessEncrypted::get_path() const {
@@ -206,26 +206,13 @@ bool FileAccessEncrypted::eof_reached() const {
return eofed;
}
-uint8_t FileAccessEncrypted::get_8() const {
- ERR_FAIL_COND_V_MSG(writing, 0, "File has not been opened in read mode.");
- if (pos >= get_length()) {
- eofed = true;
- return 0;
- }
-
- uint8_t b = data[pos];
- pos++;
- return b;
-}
-
uint64_t FileAccessEncrypted::get_buffer(uint8_t *p_dst, uint64_t p_length) const {
ERR_FAIL_COND_V(!p_dst && p_length > 0, -1);
ERR_FAIL_COND_V_MSG(writing, -1, "File has not been opened in read mode.");
uint64_t to_copy = MIN(p_length, get_length() - pos);
- for (uint64_t i = 0; i < to_copy; i++) {
- p_dst[i] = data[pos++];
- }
+ memcpy(p_dst, data.ptr() + pos, to_copy);
+ pos += to_copy;
if (to_copy < p_length) {
eofed = true;
@@ -242,17 +229,12 @@ void FileAccessEncrypted::store_buffer(const uint8_t *p_src, uint64_t p_length)
ERR_FAIL_COND_MSG(!writing, "File has not been opened in write mode.");
ERR_FAIL_COND(!p_src && p_length > 0);
- if (pos < get_length()) {
- for (uint64_t i = 0; i < p_length; i++) {
- store_8(p_src[i]);
- }
- } else if (pos == get_length()) {
+ if (pos + p_length >= get_length()) {
data.resize(pos + p_length);
- for (uint64_t i = 0; i < p_length; i++) {
- data.write[pos + i] = p_src[i];
- }
- pos += p_length;
}
+
+ memcpy(data.ptrw() + pos, p_src, p_length);
+ pos += p_length;
}
void FileAccessEncrypted::flush() {
@@ -261,18 +243,6 @@ void FileAccessEncrypted::flush() {
// encrypted files keep data in memory till close()
}
-void FileAccessEncrypted::store_8(uint8_t p_dest) {
- ERR_FAIL_COND_MSG(!writing, "File has not been opened in write mode.");
-
- if (pos < get_length()) {
- data.write[pos] = p_dest;
- pos++;
- } else if (pos == get_length()) {
- data.push_back(p_dest);
- pos++;
- }
-}
-
bool FileAccessEncrypted::file_exists(const String &p_name) {
Ref<FileAccess> fa = FileAccess::open(p_name, FileAccess::READ);
if (fa.is_null()) {
diff --git a/core/io/file_access_encrypted.h b/core/io/file_access_encrypted.h
index 42afe49a5e..5f8c803d60 100644
--- a/core/io/file_access_encrypted.h
+++ b/core/io/file_access_encrypted.h
@@ -73,14 +73,12 @@ public:
virtual bool eof_reached() const override; ///< reading passed EOF
- virtual uint8_t get_8() const override; ///< get a byte
virtual uint64_t get_buffer(uint8_t *p_dst, uint64_t p_length) const override;
virtual Error get_error() const override; ///< get last error
virtual Error resize(int64_t p_length) override { return ERR_UNAVAILABLE; }
virtual void flush() override;
- virtual void store_8(uint8_t p_dest) override; ///< store a byte
virtual void store_buffer(const uint8_t *p_src, uint64_t p_length) override; ///< store an array of bytes
virtual bool file_exists(const String &p_name) override; ///< return true if a file exists
diff --git a/core/io/file_access_memory.cpp b/core/io/file_access_memory.cpp
index 9521a4f666..1541a5ed4a 100644
--- a/core/io/file_access_memory.cpp
+++ b/core/io/file_access_memory.cpp
@@ -122,16 +122,6 @@ bool FileAccessMemory::eof_reached() const {
return pos >= length;
}
-uint8_t FileAccessMemory::get_8() const {
- uint8_t ret = 0;
- if (pos < length) {
- ret = data[pos];
- }
- ++pos;
-
- return ret;
-}
-
uint64_t FileAccessMemory::get_buffer(uint8_t *p_dst, uint64_t p_length) const {
ERR_FAIL_COND_V(!p_dst && p_length > 0, -1);
ERR_FAIL_NULL_V(data, -1);
@@ -157,16 +147,12 @@ void FileAccessMemory::flush() {
ERR_FAIL_NULL(data);
}
-void FileAccessMemory::store_8(uint8_t p_byte) {
- ERR_FAIL_NULL(data);
- ERR_FAIL_COND(pos >= length);
- data[pos++] = p_byte;
-}
-
void FileAccessMemory::store_buffer(const uint8_t *p_src, uint64_t p_length) {
ERR_FAIL_COND(!p_src && p_length > 0);
+
uint64_t left = length - pos;
uint64_t write = MIN(p_length, left);
+
if (write < p_length) {
WARN_PRINT("Writing less data than requested");
}
diff --git a/core/io/file_access_memory.h b/core/io/file_access_memory.h
index e9fbc26d75..39e1528d97 100644
--- a/core/io/file_access_memory.h
+++ b/core/io/file_access_memory.h
@@ -55,15 +55,12 @@ public:
virtual bool eof_reached() const override; ///< reading passed EOF
- virtual uint8_t get_8() const override; ///< get a byte
-
virtual uint64_t get_buffer(uint8_t *p_dst, uint64_t p_length) const override; ///< get an array of bytes
virtual Error get_error() const override; ///< get last error
virtual Error resize(int64_t p_length) override { return ERR_UNAVAILABLE; }
virtual void flush() override;
- virtual void store_8(uint8_t p_byte) override; ///< store a byte
virtual void store_buffer(const uint8_t *p_src, uint64_t p_length) override; ///< store an array of bytes
virtual bool file_exists(const String &p_name) override; ///< return true if a file exists
diff --git a/core/io/file_access_pack.cpp b/core/io/file_access_pack.cpp
index 02bf0a6039..eec27ce0aa 100644
--- a/core/io/file_access_pack.cpp
+++ b/core/io/file_access_pack.cpp
@@ -313,17 +313,6 @@ bool FileAccessPack::eof_reached() const {
return eof;
}
-uint8_t FileAccessPack::get_8() const {
- ERR_FAIL_COND_V_MSG(f.is_null(), 0, "File must be opened before use.");
- if (pos >= pf.size) {
- eof = true;
- return 0;
- }
-
- pos++;
- return f->get_8();
-}
-
uint64_t FileAccessPack::get_buffer(uint8_t *p_dst, uint64_t p_length) const {
ERR_FAIL_COND_V_MSG(f.is_null(), -1, "File must be opened before use.");
ERR_FAIL_COND_V(!p_dst && p_length > 0, -1);
@@ -366,10 +355,6 @@ void FileAccessPack::flush() {
ERR_FAIL();
}
-void FileAccessPack::store_8(uint8_t p_dest) {
- ERR_FAIL();
-}
-
void FileAccessPack::store_buffer(const uint8_t *p_src, uint64_t p_length) {
ERR_FAIL();
}
diff --git a/core/io/file_access_pack.h b/core/io/file_access_pack.h
index 594ac8f089..595a36bca4 100644
--- a/core/io/file_access_pack.h
+++ b/core/io/file_access_pack.h
@@ -169,8 +169,6 @@ public:
virtual bool eof_reached() const override;
- virtual uint8_t get_8() const override;
-
virtual uint64_t get_buffer(uint8_t *p_dst, uint64_t p_length) const override;
virtual void set_big_endian(bool p_big_endian) override;
@@ -179,8 +177,6 @@ public:
virtual Error resize(int64_t p_length) override { return ERR_UNAVAILABLE; }
virtual void flush() override;
- virtual void store_8(uint8_t p_dest) override;
-
virtual void store_buffer(const uint8_t *p_src, uint64_t p_length) override;
virtual bool file_exists(const String &p_name) override;
diff --git a/core/io/file_access_zip.cpp b/core/io/file_access_zip.cpp
index c0d1afc8e1..b33b7b35c3 100644
--- a/core/io/file_access_zip.cpp
+++ b/core/io/file_access_zip.cpp
@@ -291,12 +291,6 @@ bool FileAccessZip::eof_reached() const {
return at_eof;
}
-uint8_t FileAccessZip::get_8() const {
- uint8_t ret = 0;
- get_buffer(&ret, 1);
- return ret;
-}
-
uint64_t FileAccessZip::get_buffer(uint8_t *p_dst, uint64_t p_length) const {
ERR_FAIL_COND_V(!p_dst && p_length > 0, -1);
ERR_FAIL_NULL_V(zfile, -1);
@@ -328,7 +322,7 @@ void FileAccessZip::flush() {
ERR_FAIL();
}
-void FileAccessZip::store_8(uint8_t p_dest) {
+void FileAccessZip::store_buffer(const uint8_t *p_src, uint64_t p_length) {
ERR_FAIL();
}
diff --git a/core/io/file_access_zip.h b/core/io/file_access_zip.h
index 88b63e93e2..1e11e050df 100644
--- a/core/io/file_access_zip.h
+++ b/core/io/file_access_zip.h
@@ -95,14 +95,13 @@ public:
virtual bool eof_reached() const override; ///< reading passed EOF
- virtual uint8_t get_8() const override; ///< get a byte
virtual uint64_t get_buffer(uint8_t *p_dst, uint64_t p_length) const override;
virtual Error get_error() const override; ///< get last error
virtual Error resize(int64_t p_length) override { return ERR_UNAVAILABLE; }
virtual void flush() override;
- virtual void store_8(uint8_t p_dest) override; ///< store a byte
+ virtual void store_buffer(const uint8_t *p_src, uint64_t p_length) override;
virtual bool file_exists(const String &p_name) override; ///< return true if a file exists
diff --git a/core/io/image.cpp b/core/io/image.cpp
index f6065d984b..fcbe483e38 100644
--- a/core/io/image.cpp
+++ b/core/io/image.cpp
@@ -4225,7 +4225,7 @@ Dictionary Image::compute_image_metrics(const Ref<Image> p_compared_image, bool
result["root_mean_squared"] = INFINITY;
result["peak_snr"] = 0.0f;
- ERR_FAIL_NULL_V(p_compared_image, result);
+ ERR_FAIL_COND_V(p_compared_image.is_null(), result);
Error err = OK;
Ref<Image> compared_image = duplicate(true);
if (compared_image->is_compressed()) {
diff --git a/core/io/json.cpp b/core/io/json.cpp
index 61051727c1..664ff7857b 100644
--- a/core/io/json.cpp
+++ b/core/io/json.cpp
@@ -588,10 +588,756 @@ void JSON::_bind_methods() {
ClassDB::bind_method(D_METHOD("get_error_line"), &JSON::get_error_line);
ClassDB::bind_method(D_METHOD("get_error_message"), &JSON::get_error_message);
+ ClassDB::bind_static_method("JSON", D_METHOD("to_native", "json", "allow_classes", "allow_scripts"), &JSON::to_native, DEFVAL(false), DEFVAL(false));
+ ClassDB::bind_static_method("JSON", D_METHOD("from_native", "variant", "allow_classes", "allow_scripts"), &JSON::from_native, DEFVAL(false), DEFVAL(false));
+
ADD_PROPERTY(PropertyInfo(Variant::NIL, "data", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_NIL_IS_VARIANT), "set_data", "get_data"); // Ensures that it can be serialized as binary.
}
-////
+#define GDTYPE "__gdtype"
+#define VALUES "values"
+#define PASS_ARG p_allow_classes, p_allow_scripts
+
+Variant JSON::from_native(const Variant &p_variant, bool p_allow_classes, bool p_allow_scripts) {
+ switch (p_variant.get_type()) {
+ case Variant::NIL: {
+ Dictionary nil;
+ nil[GDTYPE] = Variant::get_type_name(p_variant.get_type());
+ return nil;
+ } break;
+ case Variant::BOOL: {
+ return p_variant;
+ } break;
+ case Variant::INT: {
+ return p_variant;
+ } break;
+ case Variant::FLOAT: {
+ return p_variant;
+ } break;
+ case Variant::STRING: {
+ return p_variant;
+ } break;
+ case Variant::VECTOR2: {
+ Dictionary d;
+ Vector2 v = p_variant;
+ Array values;
+ values.push_back(v.x);
+ values.push_back(v.y);
+ d[VALUES] = values;
+ d[GDTYPE] = Variant::get_type_name(p_variant.get_type());
+ return d;
+ } break;
+ case Variant::VECTOR2I: {
+ Dictionary d;
+ Vector2i v = p_variant;
+ Array values;
+ values.push_back(v.x);
+ values.push_back(v.y);
+ d[VALUES] = values;
+ d[GDTYPE] = Variant::get_type_name(p_variant.get_type());
+ return d;
+ } break;
+ case Variant::RECT2: {
+ Dictionary d;
+ Rect2 r = p_variant;
+ d["position"] = from_native(r.position);
+ d["size"] = from_native(r.size);
+ d[GDTYPE] = Variant::get_type_name(p_variant.get_type());
+ return d;
+ } break;
+ case Variant::RECT2I: {
+ Dictionary d;
+ Rect2i r = p_variant;
+ d["position"] = from_native(r.position);
+ d["size"] = from_native(r.size);
+ d[GDTYPE] = Variant::get_type_name(p_variant.get_type());
+ return d;
+ } break;
+ case Variant::VECTOR3: {
+ Dictionary d;
+ Vector3 v = p_variant;
+ Array values;
+ values.push_back(v.x);
+ values.push_back(v.y);
+ values.push_back(v.z);
+ d[VALUES] = values;
+ d[GDTYPE] = Variant::get_type_name(p_variant.get_type());
+ return d;
+ } break;
+ case Variant::VECTOR3I: {
+ Dictionary d;
+ Vector3i v = p_variant;
+ Array values;
+ values.push_back(v.x);
+ values.push_back(v.y);
+ values.push_back(v.z);
+ d[VALUES] = values;
+ d[GDTYPE] = Variant::get_type_name(p_variant.get_type());
+ return d;
+ } break;
+ case Variant::TRANSFORM2D: {
+ Dictionary d;
+ Transform2D t = p_variant;
+ d["x"] = from_native(t[0]);
+ d["y"] = from_native(t[1]);
+ d["origin"] = from_native(t[2]);
+ d[GDTYPE] = Variant::get_type_name(p_variant.get_type());
+ return d;
+ } break;
+ case Variant::VECTOR4: {
+ Dictionary d;
+ Vector4 v = p_variant;
+ Array values;
+ values.push_back(v.x);
+ values.push_back(v.y);
+ values.push_back(v.z);
+ values.push_back(v.w);
+ d[VALUES] = values;
+ d[GDTYPE] = Variant::get_type_name(p_variant.get_type());
+ return d;
+ } break;
+ case Variant::VECTOR4I: {
+ Dictionary d;
+ Vector4i v = p_variant;
+ Array values;
+ values.push_back(v.x);
+ values.push_back(v.y);
+ values.push_back(v.z);
+ values.push_back(v.w);
+ d[VALUES] = values;
+ d[GDTYPE] = Variant::get_type_name(p_variant.get_type());
+ return d;
+ } break;
+ case Variant::PLANE: {
+ Dictionary d;
+ Plane p = p_variant;
+ d["normal"] = from_native(p.normal);
+ d["d"] = p.d;
+ d[GDTYPE] = Variant::get_type_name(p_variant.get_type());
+ return d;
+ } break;
+ case Variant::QUATERNION: {
+ Dictionary d;
+ Quaternion q = p_variant;
+ Array values;
+ values.push_back(q.x);
+ values.push_back(q.y);
+ values.push_back(q.z);
+ values.push_back(q.w);
+ d[VALUES] = values;
+ d[GDTYPE] = Variant::get_type_name(p_variant.get_type());
+ return d;
+ } break;
+ case Variant::AABB: {
+ Dictionary d;
+ AABB aabb = p_variant;
+ d["position"] = from_native(aabb.position);
+ d["size"] = from_native(aabb.size);
+ d[GDTYPE] = Variant::get_type_name(p_variant.get_type());
+ return d;
+ } break;
+ case Variant::BASIS: {
+ Dictionary d;
+ Basis t = p_variant;
+ d["x"] = from_native(t.get_column(0));
+ d["y"] = from_native(t.get_column(1));
+ d["z"] = from_native(t.get_column(2));
+ d[GDTYPE] = Variant::get_type_name(p_variant.get_type());
+ return d;
+ } break;
+ case Variant::TRANSFORM3D: {
+ Dictionary d;
+ Transform3D t = p_variant;
+ d["basis"] = from_native(t.basis);
+ d["origin"] = from_native(t.origin);
+ d[GDTYPE] = Variant::get_type_name(p_variant.get_type());
+ return d;
+ } break;
+ case Variant::PROJECTION: {
+ Dictionary d;
+ Projection t = p_variant;
+ d["x"] = from_native(t[0]);
+ d["y"] = from_native(t[1]);
+ d["z"] = from_native(t[2]);
+ d["w"] = from_native(t[3]);
+ d[GDTYPE] = Variant::get_type_name(p_variant.get_type());
+ return d;
+ } break;
+ case Variant::COLOR: {
+ Dictionary d;
+ Color c = p_variant;
+ Array values;
+ values.push_back(c.r);
+ values.push_back(c.g);
+ values.push_back(c.b);
+ values.push_back(c.a);
+ d[VALUES] = values;
+ d[GDTYPE] = Variant::get_type_name(p_variant.get_type());
+ return d;
+ } break;
+ case Variant::STRING_NAME: {
+ Dictionary d;
+ d["name"] = String(p_variant);
+ d[GDTYPE] = Variant::get_type_name(p_variant.get_type());
+ return d;
+ } break;
+ case Variant::NODE_PATH: {
+ Dictionary d;
+ d["path"] = String(p_variant);
+ d[GDTYPE] = Variant::get_type_name(p_variant.get_type());
+ return d;
+ } break;
+ case Variant::RID: {
+ Dictionary d;
+ d[GDTYPE] = Variant::get_type_name(p_variant.get_type());
+ return d;
+ } break;
+ case Variant::OBJECT: {
+ Object *obj = p_variant.get_validated_object();
+
+ if (p_allow_classes && obj) {
+ Dictionary d;
+ List<PropertyInfo> property_list;
+ obj->get_property_list(&property_list);
+
+ d["type"] = obj->get_class();
+ Dictionary p;
+ for (const PropertyInfo &P : property_list) {
+ if (P.usage & PROPERTY_USAGE_STORAGE) {
+ if (P.name == "script" && !p_allow_scripts) {
+ continue;
+ }
+ p[P.name] = from_native(obj->get(P.name), PASS_ARG);
+ }
+ }
+ d["properties"] = p;
+ d[GDTYPE] = Variant::get_type_name(p_variant.get_type());
+ return d;
+ } else {
+ Dictionary nil;
+ nil[GDTYPE] = Variant::get_type_name(p_variant.get_type());
+ return nil;
+ }
+ } break;
+ case Variant::CALLABLE:
+ case Variant::SIGNAL: {
+ Dictionary nil;
+ nil[GDTYPE] = Variant::get_type_name(p_variant.get_type());
+ return nil;
+ } break;
+ case Variant::DICTIONARY: {
+ Dictionary d = p_variant;
+ List<Variant> keys;
+ d.get_key_list(&keys);
+ bool all_strings = true;
+ for (const Variant &K : keys) {
+ if (K.get_type() != Variant::STRING) {
+ all_strings = false;
+ break;
+ }
+ }
+
+ if (all_strings) {
+ Dictionary ret_dict;
+ for (const Variant &K : keys) {
+ ret_dict[K] = from_native(d[K], PASS_ARG);
+ }
+ return ret_dict;
+ } else {
+ Dictionary ret;
+ Array pairs;
+ for (const Variant &K : keys) {
+ Dictionary pair;
+ pair["key"] = from_native(K, PASS_ARG);
+ pair["value"] = from_native(d[K], PASS_ARG);
+ pairs.push_back(pair);
+ }
+ ret["pairs"] = pairs;
+ ret[GDTYPE] = Variant::get_type_name(p_variant.get_type());
+ return ret;
+ }
+ } break;
+ case Variant::ARRAY: {
+ Array arr = p_variant;
+ Array ret;
+ for (int i = 0; i < arr.size(); i++) {
+ ret.push_back(from_native(arr[i], PASS_ARG));
+ }
+ return ret;
+ } break;
+ case Variant::PACKED_BYTE_ARRAY: {
+ Dictionary d;
+ PackedByteArray arr = p_variant;
+ Array values;
+ for (int i = 0; i < arr.size(); i++) {
+ values.push_back(arr[i]);
+ }
+ d[VALUES] = values;
+ d[GDTYPE] = Variant::get_type_name(p_variant.get_type());
+ return d;
+ } break;
+ case Variant::PACKED_INT32_ARRAY: {
+ Dictionary d;
+ PackedInt32Array arr = p_variant;
+ Array values;
+ for (int i = 0; i < arr.size(); i++) {
+ values.push_back(arr[i]);
+ }
+ d[VALUES] = values;
+ d[GDTYPE] = Variant::get_type_name(p_variant.get_type());
+ return d;
+
+ } break;
+ case Variant::PACKED_INT64_ARRAY: {
+ Dictionary d;
+ PackedInt64Array arr = p_variant;
+ Array values;
+ for (int i = 0; i < arr.size(); i++) {
+ values.push_back(arr[i]);
+ }
+ d[VALUES] = values;
+ d[GDTYPE] = Variant::get_type_name(p_variant.get_type());
+ return d;
+ } break;
+ case Variant::PACKED_FLOAT32_ARRAY: {
+ Dictionary d;
+ PackedFloat32Array arr = p_variant;
+ Array values;
+ for (int i = 0; i < arr.size(); i++) {
+ values.push_back(arr[i]);
+ }
+ d[VALUES] = values;
+ d[GDTYPE] = Variant::get_type_name(p_variant.get_type());
+ return d;
+ } break;
+ case Variant::PACKED_FLOAT64_ARRAY: {
+ Dictionary d;
+ PackedFloat64Array arr = p_variant;
+ Array values;
+ for (int i = 0; i < arr.size(); i++) {
+ values.push_back(arr[i]);
+ }
+ d[VALUES] = values;
+ d[GDTYPE] = Variant::get_type_name(p_variant.get_type());
+ return d;
+ } break;
+ case Variant::PACKED_STRING_ARRAY: {
+ Dictionary d;
+ PackedStringArray arr = p_variant;
+ Array values;
+ for (int i = 0; i < arr.size(); i++) {
+ values.push_back(arr[i]);
+ }
+ d[VALUES] = values;
+ d[GDTYPE] = Variant::get_type_name(p_variant.get_type());
+ return d;
+ } break;
+ case Variant::PACKED_VECTOR2_ARRAY: {
+ Dictionary d;
+ PackedVector2Array arr = p_variant;
+ Array values;
+ for (int i = 0; i < arr.size(); i++) {
+ Vector2 v = arr[i];
+ values.push_back(v.x);
+ values.push_back(v.y);
+ }
+ d[VALUES] = values;
+ d[GDTYPE] = Variant::get_type_name(p_variant.get_type());
+ return d;
+ } break;
+ case Variant::PACKED_VECTOR3_ARRAY: {
+ Dictionary d;
+ PackedVector3Array arr = p_variant;
+ Array values;
+ for (int i = 0; i < arr.size(); i++) {
+ Vector3 v = arr[i];
+ values.push_back(v.x);
+ values.push_back(v.y);
+ values.push_back(v.z);
+ }
+ d[VALUES] = values;
+ d[GDTYPE] = Variant::get_type_name(p_variant.get_type());
+ return d;
+ } break;
+ case Variant::PACKED_COLOR_ARRAY: {
+ Dictionary d;
+ PackedColorArray arr = p_variant;
+ Array values;
+ for (int i = 0; i < arr.size(); i++) {
+ Color v = arr[i];
+ values.push_back(v.r);
+ values.push_back(v.g);
+ values.push_back(v.b);
+ values.push_back(v.a);
+ }
+ d[VALUES] = values;
+ d[GDTYPE] = Variant::get_type_name(p_variant.get_type());
+ return d;
+ } break;
+ case Variant::PACKED_VECTOR4_ARRAY: {
+ Dictionary d;
+ PackedVector4Array arr = p_variant;
+ Array values;
+ for (int i = 0; i < arr.size(); i++) {
+ Vector4 v = arr[i];
+ values.push_back(v.x);
+ values.push_back(v.y);
+ values.push_back(v.z);
+ values.push_back(v.w);
+ }
+ d[VALUES] = values;
+ d[GDTYPE] = Variant::get_type_name(p_variant.get_type());
+ return d;
+ } break;
+ default: {
+ ERR_PRINT(vformat("Unhandled conversion from native Variant type '%s' to JSON.", Variant::get_type_name(p_variant.get_type())));
+ } break;
+ }
+
+ Dictionary nil;
+ nil[GDTYPE] = Variant::get_type_name(p_variant.get_type());
+ return nil;
+}
+
+Variant JSON::to_native(const Variant &p_json, bool p_allow_classes, bool p_allow_scripts) {
+ switch (p_json.get_type()) {
+ case Variant::BOOL: {
+ return p_json;
+ } break;
+ case Variant::INT: {
+ return p_json;
+ } break;
+ case Variant::FLOAT: {
+ return p_json;
+ } break;
+ case Variant::STRING: {
+ return p_json;
+ } break;
+ case Variant::STRING_NAME: {
+ return p_json;
+ } break;
+ case Variant::CALLABLE: {
+ return p_json;
+ } break;
+ case Variant::DICTIONARY: {
+ Dictionary d = p_json;
+ if (d.has(GDTYPE)) {
+ // Specific Godot Variant types serialized to JSON.
+ String type = d[GDTYPE];
+ if (type == Variant::get_type_name(Variant::VECTOR2)) {
+ ERR_FAIL_COND_V(!d.has(VALUES), Variant());
+ Array values = d[VALUES];
+ ERR_FAIL_COND_V(values.size() != 2, Variant());
+ Vector2 v;
+ v.x = values[0];
+ v.y = values[1];
+ return v;
+ } else if (type == Variant::get_type_name(Variant::VECTOR2I)) {
+ ERR_FAIL_COND_V(!d.has(VALUES), Variant());
+ Array values = d[VALUES];
+ ERR_FAIL_COND_V(values.size() != 2, Variant());
+ Vector2i v;
+ v.x = values[0];
+ v.y = values[1];
+ return v;
+ } else if (type == Variant::get_type_name(Variant::RECT2)) {
+ ERR_FAIL_COND_V(!d.has("position"), Variant());
+ ERR_FAIL_COND_V(!d.has("size"), Variant());
+ Rect2 r;
+ r.position = to_native(d["position"]);
+ r.size = to_native(d["size"]);
+ return r;
+ } else if (type == Variant::get_type_name(Variant::RECT2I)) {
+ ERR_FAIL_COND_V(!d.has("position"), Variant());
+ ERR_FAIL_COND_V(!d.has("size"), Variant());
+ Rect2i r;
+ r.position = to_native(d["position"]);
+ r.size = to_native(d["size"]);
+ return r;
+ } else if (type == Variant::get_type_name(Variant::VECTOR3)) {
+ ERR_FAIL_COND_V(!d.has(VALUES), Variant());
+ Array values = d[VALUES];
+ ERR_FAIL_COND_V(values.size() != 3, Variant());
+ Vector3 v;
+ v.x = values[0];
+ v.y = values[1];
+ v.z = values[2];
+ return v;
+ } else if (type == Variant::get_type_name(Variant::VECTOR3I)) {
+ ERR_FAIL_COND_V(!d.has(VALUES), Variant());
+ Array values = d[VALUES];
+ ERR_FAIL_COND_V(values.size() != 3, Variant());
+ Vector3i v;
+ v.x = values[0];
+ v.y = values[1];
+ v.z = values[2];
+ return v;
+ } else if (type == Variant::get_type_name(Variant::TRANSFORM2D)) {
+ ERR_FAIL_COND_V(!d.has("x"), Variant());
+ ERR_FAIL_COND_V(!d.has("y"), Variant());
+ ERR_FAIL_COND_V(!d.has("origin"), Variant());
+ Transform2D t;
+ t[0] = to_native(d["x"]);
+ t[1] = to_native(d["y"]);
+ t[2] = to_native(d["origin"]);
+ return t;
+ } else if (type == Variant::get_type_name(Variant::VECTOR4)) {
+ ERR_FAIL_COND_V(!d.has(VALUES), Variant());
+ Array values = d[VALUES];
+ ERR_FAIL_COND_V(values.size() != 4, Variant());
+ Vector4 v;
+ v.x = values[0];
+ v.y = values[1];
+ v.z = values[2];
+ v.w = values[3];
+ return v;
+ } else if (type == Variant::get_type_name(Variant::VECTOR4I)) {
+ ERR_FAIL_COND_V(!d.has(VALUES), Variant());
+ Array values = d[VALUES];
+ ERR_FAIL_COND_V(values.size() != 4, Variant());
+ Vector4i v;
+ v.x = values[0];
+ v.y = values[1];
+ v.z = values[2];
+ v.w = values[3];
+ return v;
+ } else if (type == Variant::get_type_name(Variant::PLANE)) {
+ ERR_FAIL_COND_V(!d.has("normal"), Variant());
+ ERR_FAIL_COND_V(!d.has("d"), Variant());
+ Plane p;
+ p.normal = to_native(d["normal"]);
+ p.d = d["d"];
+ return p;
+ } else if (type == Variant::get_type_name(Variant::QUATERNION)) {
+ ERR_FAIL_COND_V(!d.has(VALUES), Variant());
+ Array values = d[VALUES];
+ ERR_FAIL_COND_V(values.size() != 4, Variant());
+ Quaternion v;
+ v.x = values[0];
+ v.y = values[1];
+ v.z = values[2];
+ v.w = values[3];
+ return v;
+ } else if (type == Variant::get_type_name(Variant::AABB)) {
+ ERR_FAIL_COND_V(!d.has("position"), Variant());
+ ERR_FAIL_COND_V(!d.has("size"), Variant());
+ AABB r;
+ r.position = to_native(d["position"]);
+ r.size = to_native(d["size"]);
+ return r;
+ } else if (type == Variant::get_type_name(Variant::BASIS)) {
+ ERR_FAIL_COND_V(!d.has("x"), Variant());
+ ERR_FAIL_COND_V(!d.has("y"), Variant());
+ ERR_FAIL_COND_V(!d.has("z"), Variant());
+ Basis b;
+ b.set_column(0, to_native(d["x"]));
+ b.set_column(1, to_native(d["y"]));
+ b.set_column(2, to_native(d["z"]));
+ return b;
+ } else if (type == Variant::get_type_name(Variant::TRANSFORM3D)) {
+ ERR_FAIL_COND_V(!d.has("basis"), Variant());
+ ERR_FAIL_COND_V(!d.has("origin"), Variant());
+ Transform3D t;
+ t.basis = to_native(d["basis"]);
+ t.origin = to_native(d["origin"]);
+ return t;
+ } else if (type == Variant::get_type_name(Variant::PROJECTION)) {
+ ERR_FAIL_COND_V(!d.has("x"), Variant());
+ ERR_FAIL_COND_V(!d.has("y"), Variant());
+ ERR_FAIL_COND_V(!d.has("z"), Variant());
+ ERR_FAIL_COND_V(!d.has("w"), Variant());
+ Projection p;
+ p[0] = to_native(d["x"]);
+ p[1] = to_native(d["y"]);
+ p[2] = to_native(d["z"]);
+ p[3] = to_native(d["w"]);
+ return p;
+ } else if (type == Variant::get_type_name(Variant::COLOR)) {
+ ERR_FAIL_COND_V(!d.has(VALUES), Variant());
+ Array values = d[VALUES];
+ ERR_FAIL_COND_V(values.size() != 4, Variant());
+ Color c;
+ c.r = values[0];
+ c.g = values[1];
+ c.b = values[2];
+ c.a = values[3];
+ return c;
+ } else if (type == Variant::get_type_name(Variant::NODE_PATH)) {
+ ERR_FAIL_COND_V(!d.has("path"), Variant());
+ NodePath np = d["path"];
+ return np;
+ } else if (type == Variant::get_type_name(Variant::STRING_NAME)) {
+ ERR_FAIL_COND_V(!d.has("name"), Variant());
+ StringName s = d["name"];
+ return s;
+ } else if (type == Variant::get_type_name(Variant::OBJECT)) {
+ ERR_FAIL_COND_V(!d.has("type"), Variant());
+ ERR_FAIL_COND_V(!d.has("properties"), Variant());
+
+ ERR_FAIL_COND_V(!p_allow_classes, Variant());
+
+ String obj_type = d["type"];
+ bool is_script = obj_type == "Script" || ClassDB::is_parent_class(obj_type, "Script");
+ ERR_FAIL_COND_V(!p_allow_scripts && is_script, Variant());
+ Object *obj = ClassDB::instantiate(obj_type);
+ ERR_FAIL_NULL_V(obj, Variant());
+
+ Dictionary p = d["properties"];
+
+ List<Variant> keys;
+ p.get_key_list(&keys);
+
+ for (const Variant &K : keys) {
+ String property = K;
+ Variant value = to_native(p[K], PASS_ARG);
+ obj->set(property, value);
+ }
+
+ Variant v(obj);
+
+ return v;
+ } else if (type == Variant::get_type_name(Variant::DICTIONARY)) {
+ ERR_FAIL_COND_V(!d.has("pairs"), Variant());
+ Array pairs = d["pairs"];
+ Dictionary r;
+ for (int i = 0; i < pairs.size(); i++) {
+ Dictionary p = pairs[i];
+ ERR_CONTINUE(!p.has("key"));
+ ERR_CONTINUE(!p.has("value"));
+ r[to_native(p["key"], PASS_ARG)] = to_native(p["value"]);
+ }
+ return r;
+ } else if (type == Variant::get_type_name(Variant::ARRAY)) {
+ ERR_PRINT(vformat("Unexpected Array with '%s' key. Arrays are supported natively.", GDTYPE));
+ } else if (type == Variant::get_type_name(Variant::PACKED_BYTE_ARRAY)) {
+ ERR_FAIL_COND_V(!d.has(VALUES), Variant());
+ Array values = d[VALUES];
+ PackedByteArray pbarr;
+ pbarr.resize(values.size());
+ for (int i = 0; i < pbarr.size(); i++) {
+ pbarr.write[i] = values[i];
+ }
+ return pbarr;
+ } else if (type == Variant::get_type_name(Variant::PACKED_INT32_ARRAY)) {
+ ERR_FAIL_COND_V(!d.has(VALUES), Variant());
+ Array values = d[VALUES];
+ PackedInt32Array arr;
+ arr.resize(values.size());
+ for (int i = 0; i < arr.size(); i++) {
+ arr.write[i] = values[i];
+ }
+ return arr;
+ } else if (type == Variant::get_type_name(Variant::PACKED_INT64_ARRAY)) {
+ ERR_FAIL_COND_V(!d.has(VALUES), Variant());
+ Array values = d[VALUES];
+ PackedInt64Array arr;
+ arr.resize(values.size());
+ for (int i = 0; i < arr.size(); i++) {
+ arr.write[i] = values[i];
+ }
+ return arr;
+ } else if (type == Variant::get_type_name(Variant::PACKED_FLOAT32_ARRAY)) {
+ ERR_FAIL_COND_V(!d.has(VALUES), Variant());
+ Array values = d[VALUES];
+ PackedFloat32Array arr;
+ arr.resize(values.size());
+ for (int i = 0; i < arr.size(); i++) {
+ arr.write[i] = values[i];
+ }
+ return arr;
+ } else if (type == Variant::get_type_name(Variant::PACKED_FLOAT64_ARRAY)) {
+ ERR_FAIL_COND_V(!d.has(VALUES), Variant());
+ Array values = d[VALUES];
+ PackedFloat64Array arr;
+ arr.resize(values.size());
+ for (int i = 0; i < arr.size(); i++) {
+ arr.write[i] = values[i];
+ }
+ return arr;
+ } else if (type == Variant::get_type_name(Variant::PACKED_STRING_ARRAY)) {
+ ERR_FAIL_COND_V(!d.has(VALUES), Variant());
+ Array values = d[VALUES];
+ PackedStringArray arr;
+ arr.resize(values.size());
+ for (int i = 0; i < arr.size(); i++) {
+ arr.write[i] = values[i];
+ }
+ return arr;
+ } else if (type == Variant::get_type_name(Variant::PACKED_VECTOR2_ARRAY)) {
+ ERR_FAIL_COND_V(!d.has(VALUES), Variant());
+ Array values = d[VALUES];
+ ERR_FAIL_COND_V(values.size() % 2 != 0, Variant());
+ PackedVector2Array arr;
+ arr.resize(values.size() / 2);
+ for (int i = 0; i < arr.size(); i++) {
+ arr.write[i] = Vector2(values[i * 2 + 0], values[i * 2 + 1]);
+ }
+ return arr;
+ } else if (type == Variant::get_type_name(Variant::PACKED_VECTOR3_ARRAY)) {
+ ERR_FAIL_COND_V(!d.has(VALUES), Variant());
+ Array values = d[VALUES];
+ ERR_FAIL_COND_V(values.size() % 3 != 0, Variant());
+ PackedVector3Array arr;
+ arr.resize(values.size() / 3);
+ for (int i = 0; i < arr.size(); i++) {
+ arr.write[i] = Vector3(values[i * 3 + 0], values[i * 3 + 1], values[i * 3 + 2]);
+ }
+ return arr;
+ } else if (type == Variant::get_type_name(Variant::PACKED_COLOR_ARRAY)) {
+ ERR_FAIL_COND_V(!d.has(VALUES), Variant());
+ Array values = d[VALUES];
+ ERR_FAIL_COND_V(values.size() % 4 != 0, Variant());
+ PackedColorArray arr;
+ arr.resize(values.size() / 4);
+ for (int i = 0; i < arr.size(); i++) {
+ arr.write[i] = Color(values[i * 4 + 0], values[i * 4 + 1], values[i * 4 + 2], values[i * 4 + 3]);
+ }
+ return arr;
+ } else if (type == Variant::get_type_name(Variant::PACKED_VECTOR4_ARRAY)) {
+ ERR_FAIL_COND_V(!d.has(VALUES), Variant());
+ Array values = d[VALUES];
+ ERR_FAIL_COND_V(values.size() % 4 != 0, Variant());
+ PackedVector4Array arr;
+ arr.resize(values.size() / 4);
+ for (int i = 0; i < arr.size(); i++) {
+ arr.write[i] = Vector4(values[i * 4 + 0], values[i * 4 + 1], values[i * 4 + 2], values[i * 4 + 3]);
+ }
+ return arr;
+ } else {
+ return Variant();
+ }
+ } else {
+ // Regular dictionary with string keys.
+ List<Variant> keys;
+ d.get_key_list(&keys);
+ Dictionary r;
+ for (const Variant &K : keys) {
+ r[K] = to_native(d[K], PASS_ARG);
+ }
+ return r;
+ }
+ } break;
+ case Variant::ARRAY: {
+ Array arr = p_json;
+ Array ret;
+ ret.resize(arr.size());
+ for (int i = 0; i < arr.size(); i++) {
+ ret[i] = to_native(arr[i], PASS_ARG);
+ }
+ return ret;
+ } break;
+ default: {
+ ERR_PRINT(vformat("Unhandled conversion from JSON type '%s' to native Variant type.", Variant::get_type_name(p_json.get_type())));
+ return Variant();
+ }
+ }
+
+ return Variant();
+}
+
+#undef GDTYPE
+#undef VALUES
+#undef PASS_ARG
////////////
diff --git a/core/io/json.h b/core/io/json.h
index 801fa29b4b..67b5e09afa 100644
--- a/core/io/json.h
+++ b/core/io/json.h
@@ -94,6 +94,9 @@ public:
void set_data(const Variant &p_data);
inline int get_error_line() const { return err_line; }
inline String get_error_message() const { return err_str; }
+
+ static Variant from_native(const Variant &p_variant, bool p_allow_classes = false, bool p_allow_scripts = false);
+ static Variant to_native(const Variant &p_json, bool p_allow_classes = false, bool p_allow_scripts = false);
};
class ResourceFormatLoaderJSON : public ResourceFormatLoader {
diff --git a/core/io/packed_data_container.h b/core/io/packed_data_container.h
index cc9996101e..f4ffa09022 100644
--- a/core/io/packed_data_container.h
+++ b/core/io/packed_data_container.h
@@ -36,7 +36,7 @@
class PackedDataContainer : public Resource {
GDCLASS(PackedDataContainer, Resource);
- enum {
+ enum : uint32_t {
TYPE_DICT = 0xFFFFFFFF,
TYPE_ARRAY = 0xFFFFFFFE,
};
diff --git a/core/io/plist.cpp b/core/io/plist.cpp
index 86737609bf..8d91e6dec2 100644
--- a/core/io/plist.cpp
+++ b/core/io/plist.cpp
@@ -814,7 +814,7 @@ bool PList::load_string(const String &p_string, String &r_err_out) {
}
PackedByteArray PList::save_asn1() const {
- if (root == nullptr) {
+ if (root.is_null()) {
ERR_FAIL_V_MSG(PackedByteArray(), "PList: Invalid PList, no root node.");
}
size_t size = root->get_asn1_size(1);
@@ -848,7 +848,7 @@ PackedByteArray PList::save_asn1() const {
}
String PList::save_text() const {
- if (root == nullptr) {
+ if (root.is_null()) {
ERR_FAIL_V_MSG(String(), "PList: Invalid PList, no root node.");
}
diff --git a/core/io/resource.cpp b/core/io/resource.cpp
index 598c99c188..6177cba6a4 100644
--- a/core/io/resource.cpp
+++ b/core/io/resource.cpp
@@ -40,12 +40,12 @@
#include <stdio.h>
void Resource::emit_changed() {
- if (ResourceLoader::is_within_load() && MessageQueue::get_main_singleton() != MessageQueue::get_singleton() && !MessageQueue::get_singleton()->is_flushing()) {
- // Let the connection happen on the call queue, later, since signals are not thread-safe.
- call_deferred("emit_signal", CoreStringName(changed));
- } else {
- emit_signal(CoreStringName(changed));
+ if (ResourceLoader::is_within_load() && !Thread::is_main_thread()) {
+ ResourceLoader::resource_changed_emit(this);
+ return;
}
+
+ emit_signal(CoreStringName(changed));
}
void Resource::_resource_path_changed() {
@@ -60,32 +60,32 @@ void Resource::set_path(const String &p_path, bool p_take_over) {
p_take_over = false; // Can't take over an empty path
}
- ResourceCache::lock.lock();
+ {
+ MutexLock lock(ResourceCache::lock);
- if (!path_cache.is_empty()) {
- ResourceCache::resources.erase(path_cache);
- }
+ if (!path_cache.is_empty()) {
+ ResourceCache::resources.erase(path_cache);
+ }
- path_cache = "";
+ path_cache = "";
- Ref<Resource> existing = ResourceCache::get_ref(p_path);
+ Ref<Resource> existing = ResourceCache::get_ref(p_path);
- if (existing.is_valid()) {
- if (p_take_over) {
- existing->path_cache = String();
- ResourceCache::resources.erase(p_path);
- } else {
- ResourceCache::lock.unlock();
- ERR_FAIL_MSG("Another resource is loaded from path '" + p_path + "' (possible cyclic resource inclusion).");
+ if (existing.is_valid()) {
+ if (p_take_over) {
+ existing->path_cache = String();
+ ResourceCache::resources.erase(p_path);
+ } else {
+ ERR_FAIL_MSG("Another resource is loaded from path '" + p_path + "' (possible cyclic resource inclusion).");
+ }
}
- }
- path_cache = p_path;
+ path_cache = p_path;
- if (!path_cache.is_empty()) {
- ResourceCache::resources[path_cache] = this;
+ if (!path_cache.is_empty()) {
+ ResourceCache::resources[path_cache] = this;
+ }
}
- ResourceCache::lock.unlock();
_resource_path_changed();
}
@@ -166,22 +166,22 @@ bool Resource::editor_can_reload_from_file() {
}
void Resource::connect_changed(const Callable &p_callable, uint32_t p_flags) {
- if (ResourceLoader::is_within_load() && MessageQueue::get_main_singleton() != MessageQueue::get_singleton() && !MessageQueue::get_singleton()->is_flushing()) {
- // Let the check and connection happen on the call queue, later, since signals are not thread-safe.
- callable_mp(this, &Resource::connect_changed).call_deferred(p_callable, p_flags);
+ if (ResourceLoader::is_within_load() && !Thread::is_main_thread()) {
+ ResourceLoader::resource_changed_connect(this, p_callable, p_flags);
return;
}
+
if (!is_connected(CoreStringName(changed), p_callable) || p_flags & CONNECT_REFERENCE_COUNTED) {
connect(CoreStringName(changed), p_callable, p_flags);
}
}
void Resource::disconnect_changed(const Callable &p_callable) {
- if (ResourceLoader::is_within_load() && MessageQueue::get_main_singleton() != MessageQueue::get_singleton() && !MessageQueue::get_singleton()->is_flushing()) {
- // Let the check and disconnection happen on the call queue, later, since signals are not thread-safe.
- callable_mp(this, &Resource::disconnect_changed).call_deferred(p_callable);
+ if (ResourceLoader::is_within_load() && !Thread::is_main_thread()) {
+ ResourceLoader::resource_changed_disconnect(this, p_callable);
return;
}
+
if (is_connected(CoreStringName(changed), p_callable)) {
disconnect(CoreStringName(changed), p_callable);
}
@@ -486,15 +486,13 @@ void Resource::set_as_translation_remapped(bool p_remapped) {
return;
}
- ResourceCache::lock.lock();
+ MutexLock lock(ResourceCache::lock);
if (p_remapped) {
ResourceLoader::remapped_list.add(&remapped_list);
} else {
ResourceLoader::remapped_list.remove(&remapped_list);
}
-
- ResourceCache::lock.unlock();
}
#ifdef TOOLS_ENABLED
@@ -564,14 +562,13 @@ Resource::~Resource() {
return;
}
- ResourceCache::lock.lock();
+ MutexLock lock(ResourceCache::lock);
// Only unregister from the cache if this is the actual resource listed there.
// (Other resources can have the same value in `path_cache` if loaded with `CACHE_IGNORE`.)
HashMap<String, Resource *>::Iterator E = ResourceCache::resources.find(path_cache);
if (likely(E && E->value == this)) {
ResourceCache::resources.remove(E);
}
- ResourceCache::lock.unlock();
}
HashMap<String, Resource *> ResourceCache::resources;
@@ -600,18 +597,20 @@ void ResourceCache::clear() {
}
bool ResourceCache::has(const String &p_path) {
- lock.lock();
+ Resource **res = nullptr;
- Resource **res = resources.getptr(p_path);
+ {
+ MutexLock mutex_lock(lock);
- if (res && (*res)->get_reference_count() == 0) {
- // This resource is in the process of being deleted, ignore its existence.
- (*res)->path_cache = String();
- resources.erase(p_path);
- res = nullptr;
- }
+ res = resources.getptr(p_path);
- lock.unlock();
+ if (res && (*res)->get_reference_count() == 0) {
+ // This resource is in the process of being deleted, ignore its existence.
+ (*res)->path_cache = String();
+ resources.erase(p_path);
+ res = nullptr;
+ }
+ }
if (!res) {
return false;
@@ -622,28 +621,27 @@ bool ResourceCache::has(const String &p_path) {
Ref<Resource> ResourceCache::get_ref(const String &p_path) {
Ref<Resource> ref;
- lock.lock();
-
- Resource **res = resources.getptr(p_path);
+ {
+ MutexLock mutex_lock(lock);
+ Resource **res = resources.getptr(p_path);
- if (res) {
- ref = Ref<Resource>(*res);
- }
+ if (res) {
+ ref = Ref<Resource>(*res);
+ }
- if (res && !ref.is_valid()) {
- // This resource is in the process of being deleted, ignore its existence
- (*res)->path_cache = String();
- resources.erase(p_path);
- res = nullptr;
+ if (res && !ref.is_valid()) {
+ // This resource is in the process of being deleted, ignore its existence
+ (*res)->path_cache = String();
+ resources.erase(p_path);
+ res = nullptr;
+ }
}
- lock.unlock();
-
return ref;
}
void ResourceCache::get_cached_resources(List<Ref<Resource>> *p_resources) {
- lock.lock();
+ MutexLock mutex_lock(lock);
LocalVector<String> to_remove;
@@ -663,14 +661,9 @@ void ResourceCache::get_cached_resources(List<Ref<Resource>> *p_resources) {
for (const String &E : to_remove) {
resources.erase(E);
}
-
- lock.unlock();
}
int ResourceCache::get_cached_resource_count() {
- lock.lock();
- int rc = resources.size();
- lock.unlock();
-
- return rc;
+ MutexLock mutex_lock(lock);
+ return resources.size();
}
diff --git a/core/io/resource_format_binary.cpp b/core/io/resource_format_binary.cpp
index f71257fa76..41a8a569d0 100644
--- a/core/io/resource_format_binary.cpp
+++ b/core/io/resource_format_binary.cpp
@@ -857,6 +857,19 @@ Error ResourceLoaderBinary::load() {
}
}
+ if (value.get_type() == Variant::DICTIONARY) {
+ Dictionary set_dict = value;
+ bool is_get_valid = false;
+ Variant get_value = res->get(name, &is_get_valid);
+ if (is_get_valid && get_value.get_type() == Variant::DICTIONARY) {
+ Dictionary get_dict = get_value;
+ if (!set_dict.is_same_typed(get_dict)) {
+ value = Dictionary(set_dict, get_dict.get_typed_key_builtin(), get_dict.get_typed_key_class_name(), get_dict.get_typed_key_script(),
+ get_dict.get_typed_value_builtin(), get_dict.get_typed_value_class_name(), get_dict.get_typed_value_script());
+ }
+ }
+ }
+
if (set_valid) {
res->set(name, value);
}
@@ -2064,6 +2077,8 @@ void ResourceFormatSaverBinaryInstance::_find_resources(const Variant &p_variant
case Variant::DICTIONARY: {
Dictionary d = p_variant;
+ _find_resources(d.get_typed_key_script());
+ _find_resources(d.get_typed_value_script());
List<Variant> keys;
d.get_key_list(&keys);
for (const Variant &E : keys) {
diff --git a/core/io/resource_importer.cpp b/core/io/resource_importer.cpp
index b4c43abe00..a572dd562e 100644
--- a/core/io/resource_importer.cpp
+++ b/core/io/resource_importer.cpp
@@ -35,6 +35,8 @@
#include "core/os/os.h"
#include "core/variant/variant_parser.h"
+ResourceFormatImporterLoadOnStartup ResourceImporter::load_on_startup = nullptr;
+
bool ResourceFormatImporter::SortImporterByName::operator()(const Ref<ResourceImporter> &p_a, const Ref<ResourceImporter> &p_b) const {
return p_a->get_importer_name() < p_b->get_importer_name();
}
@@ -137,6 +139,20 @@ Error ResourceFormatImporter::_get_path_and_type(const String &p_path, PathAndTy
}
Ref<Resource> ResourceFormatImporter::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) {
+#ifdef TOOLS_ENABLED
+ // When loading a resource on startup, we use the load_on_startup callback,
+ // which executes the loading in the EditorFileSystem. It can reimport
+ // the resource and retry the load, allowing the resource to be loaded
+ // even if it is not yet imported.
+ if (ResourceImporter::load_on_startup != nullptr) {
+ return ResourceImporter::load_on_startup(this, p_path, r_error, p_use_sub_threads, r_progress, p_cache_mode);
+ }
+#endif
+
+ return load_internal(p_path, r_error, p_use_sub_threads, r_progress, p_cache_mode, false);
+}
+
+Ref<Resource> ResourceFormatImporter::load_internal(const String &p_path, Error *r_error, bool p_use_sub_threads, float *r_progress, CacheMode p_cache_mode, bool p_silence_errors) {
PathAndType pat;
Error err = _get_path_and_type(p_path, pat);
@@ -148,6 +164,13 @@ Ref<Resource> ResourceFormatImporter::load(const String &p_path, const String &p
return Ref<Resource>();
}
+ if (p_silence_errors) {
+ // Note: Some importers do not create files in the .godot folder, so we need to check if the path is empty.
+ if (!pat.path.is_empty() && !FileAccess::exists(pat.path)) {
+ return Ref<Resource>();
+ }
+ }
+
Ref<Resource> res = ResourceLoader::_load(pat.path, p_path, pat.type, p_cache_mode, r_error, p_use_sub_threads, r_progress);
#ifdef TOOLS_ENABLED
diff --git a/core/io/resource_importer.h b/core/io/resource_importer.h
index 7b1806c3d2..6ea5d0972a 100644
--- a/core/io/resource_importer.h
+++ b/core/io/resource_importer.h
@@ -35,6 +35,9 @@
#include "core/io/resource_saver.h"
class ResourceImporter;
+class ResourceFormatImporter;
+
+typedef Ref<Resource> (*ResourceFormatImporterLoadOnStartup)(ResourceFormatImporter *p_importer, const String &p_path, Error *r_error, bool p_use_sub_threads, float *r_progress, ResourceFormatLoader::CacheMode p_cache_mode);
class ResourceFormatImporter : public ResourceFormatLoader {
struct PathAndType {
@@ -60,6 +63,7 @@ class ResourceFormatImporter : public ResourceFormatLoader {
public:
static ResourceFormatImporter *get_singleton() { return singleton; }
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) override;
+ Ref<Resource> load_internal(const String &p_path, Error *r_error, bool p_use_sub_threads, float *r_progress, CacheMode p_cache_mode, bool p_silence_errors);
virtual void get_recognized_extensions(List<String> *p_extensions) const override;
virtual void get_recognized_extensions_for_type(const String &p_type, List<String> *p_extensions) const override;
virtual bool recognize_path(const String &p_path, const String &p_for_type = String()) const override;
@@ -94,6 +98,7 @@ public:
String get_import_base_path(const String &p_for_file) const;
Error get_resource_import_info(const String &p_path, StringName &r_type, ResourceUID::ID &r_uid, String &r_import_group_file) const;
+
ResourceFormatImporter();
};
@@ -104,6 +109,8 @@ protected:
static void _bind_methods();
public:
+ static ResourceFormatImporterLoadOnStartup load_on_startup;
+
virtual String get_importer_name() const = 0;
virtual String get_visible_name() const = 0;
virtual void get_recognized_extensions(List<String> *p_extensions) const = 0;
diff --git a/core/io/resource_loader.cpp b/core/io/resource_loader.cpp
index d2c4668d12..f29f9eef98 100644
--- a/core/io/resource_loader.cpp
+++ b/core/io/resource_loader.cpp
@@ -31,6 +31,7 @@
#include "resource_loader.h"
#include "core/config/project_settings.h"
+#include "core/core_bind.h"
#include "core/io/file_access.h"
#include "core/io/resource_importer.h"
#include "core/object/script_language.h"
@@ -227,28 +228,32 @@ void ResourceFormatLoader::_bind_methods() {
// This should be robust enough to be called redundantly without issues.
void ResourceLoader::LoadToken::clear() {
- thread_load_mutex.lock();
-
WorkerThreadPool::TaskID task_to_await = 0;
- // User-facing tokens shouldn't be deleted until completely claimed.
- DEV_ASSERT(user_rc == 0 && user_path.is_empty());
+ {
+ MutexLock thread_load_lock(thread_load_mutex);
+ // User-facing tokens shouldn't be deleted until completely claimed.
+ DEV_ASSERT(user_rc == 0 && user_path.is_empty());
- if (!local_path.is_empty()) { // Empty is used for the special case where the load task is not registered.
- DEV_ASSERT(thread_load_tasks.has(local_path));
- ThreadLoadTask &load_task = thread_load_tasks[local_path];
- if (load_task.task_id && !load_task.awaited) {
- task_to_await = load_task.task_id;
+ if (!local_path.is_empty()) {
+ if (task_if_unregistered) {
+ memdelete(task_if_unregistered);
+ task_if_unregistered = nullptr;
+ } else {
+ DEV_ASSERT(thread_load_tasks.has(local_path));
+ ThreadLoadTask &load_task = thread_load_tasks[local_path];
+ if (load_task.task_id && !load_task.awaited) {
+ task_to_await = load_task.task_id;
+ }
+ // Removing a task which is still in progress would be catastrophic.
+ // Tokens must be alive until the task thread function is done.
+ DEV_ASSERT(load_task.status == THREAD_LOAD_FAILED || load_task.status == THREAD_LOAD_LOADED);
+ thread_load_tasks.erase(local_path);
+ }
+ local_path.clear(); // Mark as already cleared.
}
- // Removing a task which is still in progress would be catastrophic.
- // Tokens must be alive until the task thread function is done.
- DEV_ASSERT(load_task.status == THREAD_LOAD_FAILED || load_task.status == THREAD_LOAD_LOADED);
- thread_load_tasks.erase(local_path);
- local_path.clear();
}
- thread_load_mutex.unlock();
-
// If task is unused, await it here, locally, now the token data is consistent.
if (task_to_await) {
PREPARE_FOR_WTP_WAIT
@@ -265,7 +270,7 @@ Ref<Resource> ResourceLoader::_load(const String &p_path, const String &p_origin
const String &original_path = p_original_path.is_empty() ? p_path : p_original_path;
load_nesting++;
if (load_paths_stack.size()) {
- thread_load_mutex.lock();
+ MutexLock thread_load_lock(thread_load_mutex);
const String &parent_task_path = load_paths_stack.get(load_paths_stack.size() - 1);
HashMap<String, ThreadLoadTask>::Iterator E = thread_load_tasks.find(parent_task_path);
// Avoid double-tracking, for progress reporting, resources that boil down to a remapped path containing the real payload (e.g., imported resources).
@@ -273,7 +278,6 @@ Ref<Resource> ResourceLoader::_load(const String &p_path, const String &p_origin
if (E && !is_remapped_load) {
E->value.sub_tasks.insert(p_original_path);
}
- thread_load_mutex.unlock();
}
load_paths_stack.push_back(original_path);
@@ -315,16 +319,20 @@ Ref<Resource> ResourceLoader::_load(const String &p_path, const String &p_origin
}
// This implementation must allow re-entrancy for a task that started awaiting in a deeper stack frame.
+// The load task token must be manually re-referenced before this is called, which includes threaded runs.
void ResourceLoader::_run_load_task(void *p_userdata) {
ThreadLoadTask &load_task = *(ThreadLoadTask *)p_userdata;
- thread_load_mutex.lock();
- if (cleaning_tasks) {
- load_task.status = THREAD_LOAD_FAILED;
- thread_load_mutex.unlock();
- return;
+ {
+ MutexLock thread_load_lock(thread_load_mutex);
+ if (cleaning_tasks) {
+ load_task.status = THREAD_LOAD_FAILED;
+ return;
+ }
}
- thread_load_mutex.unlock();
+
+ ThreadLoadTask *curr_load_task_backup = curr_load_task;
+ curr_load_task = &load_task;
// Thread-safe either if it's the current thread or a brand new one.
CallQueue *own_mq_override = nullptr;
@@ -343,12 +351,19 @@ void ResourceLoader::_run_load_task(void *p_userdata) {
bool xl_remapped = false;
const String &remapped_path = _path_remap(load_task.local_path, &xl_remapped);
+
+ print_verbose("Loading resource: " + remapped_path);
+
Error load_err = OK;
Ref<Resource> res = _load(remapped_path, remapped_path != load_task.local_path ? load_task.local_path : String(), load_task.type_hint, load_task.cache_mode, &load_err, load_task.use_sub_threads, &load_task.progress);
if (MessageQueue::get_singleton() != MessageQueue::get_main_singleton()) {
MessageQueue::get_singleton()->flush();
}
+ if (res.is_null()) {
+ print_verbose("Failed loading resource: " + remapped_path);
+ }
+
thread_load_mutex.lock();
load_task.resource = res;
@@ -435,6 +450,9 @@ void ResourceLoader::_run_load_task(void *p_userdata) {
}
}
+ // It's safe now to let the task go in case no one else was grabbing the token.
+ load_task.load_token->unreference();
+
if (unlock_pending) {
thread_load_mutex.unlock();
}
@@ -446,6 +464,8 @@ void ResourceLoader::_run_load_task(void *p_userdata) {
}
DEV_ASSERT(load_paths_stack.is_empty());
}
+
+ curr_load_task = curr_load_task_backup;
}
static String _validate_local_path(const String &p_path) {
@@ -516,9 +536,7 @@ Ref<ResourceLoader::LoadToken> ResourceLoader::_load_start(const String &p_path,
Ref<LoadToken> load_token;
bool must_not_register = false;
- ThreadLoadTask unregistered_load_task; // Once set, must be valid up to the call to do the load.
ThreadLoadTask *load_task_ptr = nullptr;
- bool run_on_current_thread = false;
{
MutexLock thread_load_lock(thread_load_mutex);
@@ -532,6 +550,11 @@ Ref<ResourceLoader::LoadToken> ResourceLoader::_load_start(const String &p_path,
if (!ignoring_cache && thread_load_tasks.has(local_path)) {
load_token = Ref<LoadToken>(thread_load_tasks[local_path].load_token);
if (load_token.is_valid()) {
+ if (p_for_user) {
+ // Load task exists, with no user tokens at the moment.
+ // Let's "attach" to it.
+ _load_threaded_request_setup_user_token(load_token.ptr(), p_path);
+ }
return load_token;
} else {
// The token is dying (reached 0 on another thread).
@@ -568,12 +591,11 @@ Ref<ResourceLoader::LoadToken> ResourceLoader::_load_start(const String &p_path,
}
}
- // If we want to ignore cache, but there's another task loading it, we can't add this one to the map and we also have to finish within scope.
+ // If we want to ignore cache, but there's another task loading it, we can't add this one to the map.
must_not_register = ignoring_cache && thread_load_tasks.has(local_path);
if (must_not_register) {
- load_token->local_path.clear();
- unregistered_load_task = load_task;
- load_task_ptr = &unregistered_load_task;
+ load_token->task_if_unregistered = memnew(ThreadLoadTask(load_task));
+ load_task_ptr = load_token->task_if_unregistered;
} else {
DEV_ASSERT(!thread_load_tasks.has(local_path));
HashMap<String, ResourceLoader::ThreadLoadTask>::Iterator E = thread_load_tasks.insert(local_path, load_task);
@@ -581,9 +603,12 @@ Ref<ResourceLoader::LoadToken> ResourceLoader::_load_start(const String &p_path,
}
}
- run_on_current_thread = must_not_register || p_thread_mode == LOAD_THREAD_FROM_CURRENT;
+ // It's important to keep the token alive because until the load completes,
+ // which includes before the thread start, it may happen that no one is grabbing
+ // the token anymore so it's released.
+ load_task_ptr->load_token->reference();
- if (run_on_current_thread) {
+ if (p_thread_mode == LOAD_THREAD_FROM_CURRENT) {
// The current thread may happen to be a thread from the pool.
WorkerThreadPool::TaskID tid = WorkerThreadPool::get_singleton()->get_caller_task_id();
if (tid != WorkerThreadPool::INVALID_TASK_ID) {
@@ -596,11 +621,8 @@ Ref<ResourceLoader::LoadToken> ResourceLoader::_load_start(const String &p_path,
}
} // MutexLock(thread_load_mutex).
- if (run_on_current_thread) {
+ if (p_thread_mode == LOAD_THREAD_FROM_CURRENT) {
_run_load_task(load_task_ptr);
- if (must_not_register) {
- load_token->res_if_unregistered = load_task_ptr->resource;
- }
}
return load_token;
@@ -728,7 +750,10 @@ Ref<Resource> ResourceLoader::_load_complete_inner(LoadToken &p_load_token, Erro
*r_error = OK;
}
- if (!p_load_token.local_path.is_empty()) {
+ ThreadLoadTask *load_task_ptr = nullptr;
+ if (p_load_token.task_if_unregistered) {
+ load_task_ptr = p_load_token.task_if_unregistered;
+ } else {
if (!thread_load_tasks.has(p_load_token.local_path)) {
if (r_error) {
*r_error = ERR_BUG;
@@ -767,6 +792,7 @@ Ref<Resource> ResourceLoader::_load_complete_inner(LoadToken &p_load_token, Erro
// resource loading that means that the task to wait for can be restarted here to break the
// cycle, with as much recursion into this process as needed.
// When the stack is eventually unrolled, the original load will have been notified to go on.
+ load_task.load_token->reference();
_run_load_task(&load_task);
}
@@ -799,22 +825,51 @@ Ref<Resource> ResourceLoader::_load_complete_inner(LoadToken &p_load_token, Erro
load_task.error = FAILED;
}
- Ref<Resource> resource = load_task.resource;
- if (r_error) {
- *r_error = load_task.error;
- }
- return resource;
- } else {
- // Special case of an unregistered task.
- // The resource should have been loaded by now.
- Ref<Resource> resource = p_load_token.res_if_unregistered;
- if (!resource.is_valid()) {
- if (r_error) {
- *r_error = FAILED;
+ load_task_ptr = &load_task;
+ }
+
+ p_thread_load_lock.temp_unlock();
+
+ Ref<Resource> resource = load_task_ptr->resource;
+ if (r_error) {
+ *r_error = load_task_ptr->error;
+ }
+
+ if (resource.is_valid()) {
+ if (curr_load_task) {
+ // A task awaiting another => Let the awaiter accumulate the resource changed connections.
+ DEV_ASSERT(curr_load_task != load_task_ptr);
+ for (const ThreadLoadTask::ResourceChangedConnection &rcc : load_task_ptr->resource_changed_connections) {
+ curr_load_task->resource_changed_connections.push_back(rcc);
+ }
+ } else {
+ // A leaf task being awaited => Propagate the resource changed connections.
+ if (Thread::is_main_thread()) {
+ // On the main thread it's safe to migrate the connections to the standard signal mechanism.
+ for (const ThreadLoadTask::ResourceChangedConnection &rcc : load_task_ptr->resource_changed_connections) {
+ if (rcc.callable.is_valid()) {
+ rcc.source->connect_changed(rcc.callable, rcc.flags);
+ }
+ }
+ } else {
+ // On non-main threads, we have to queue and call it done when processed.
+ if (!load_task_ptr->resource_changed_connections.is_empty()) {
+ for (const ThreadLoadTask::ResourceChangedConnection &rcc : load_task_ptr->resource_changed_connections) {
+ if (rcc.callable.is_valid()) {
+ MessageQueue::get_main_singleton()->push_callable(callable_mp(rcc.source, &Resource::connect_changed).bind(rcc.callable, rcc.flags));
+ }
+ }
+ core_bind::Semaphore done;
+ MessageQueue::get_main_singleton()->push_callable(callable_mp(&done, &core_bind::Semaphore::post));
+ done.wait();
+ }
}
}
- return resource;
}
+
+ p_thread_load_lock.temp_relock();
+
+ return resource;
}
bool ResourceLoader::_ensure_load_progress() {
@@ -828,6 +883,50 @@ bool ResourceLoader::_ensure_load_progress() {
return true;
}
+void ResourceLoader::resource_changed_connect(Resource *p_source, const Callable &p_callable, uint32_t p_flags) {
+ print_lt(vformat("%d\t%ud:%s\t" FUNCTION_STR "\t%d", Thread::get_caller_id(), p_source->get_instance_id(), p_source->get_class(), p_callable.get_object_id()));
+
+ MutexLock lock(thread_load_mutex);
+
+ for (const ThreadLoadTask::ResourceChangedConnection &rcc : curr_load_task->resource_changed_connections) {
+ if (unlikely(rcc.source == p_source && rcc.callable == p_callable)) {
+ return;
+ }
+ }
+
+ ThreadLoadTask::ResourceChangedConnection rcc;
+ rcc.source = p_source;
+ rcc.callable = p_callable;
+ rcc.flags = p_flags;
+ curr_load_task->resource_changed_connections.push_back(rcc);
+}
+
+void ResourceLoader::resource_changed_disconnect(Resource *p_source, const Callable &p_callable) {
+ print_lt(vformat("%d\t%ud:%s\t" FUNCTION_STR "t%d", Thread::get_caller_id(), p_source->get_instance_id(), p_source->get_class(), p_callable.get_object_id()));
+
+ MutexLock lock(thread_load_mutex);
+
+ for (uint32_t i = 0; i < curr_load_task->resource_changed_connections.size(); ++i) {
+ const ThreadLoadTask::ResourceChangedConnection &rcc = curr_load_task->resource_changed_connections[i];
+ if (unlikely(rcc.source == p_source && rcc.callable == p_callable)) {
+ curr_load_task->resource_changed_connections.remove_at_unordered(i);
+ return;
+ }
+ }
+}
+
+void ResourceLoader::resource_changed_emit(Resource *p_source) {
+ print_lt(vformat("%d\t%ud:%s\t" FUNCTION_STR, Thread::get_caller_id(), p_source->get_instance_id(), p_source->get_class()));
+
+ MutexLock lock(thread_load_mutex);
+
+ for (const ThreadLoadTask::ResourceChangedConnection &rcc : curr_load_task->resource_changed_connections) {
+ if (unlikely(rcc.source == p_source)) {
+ rcc.callable.call();
+ }
+ }
+}
+
Ref<Resource> ResourceLoader::ensure_resource_ref_override_for_outer_load(const String &p_path, const String &p_res_type) {
ERR_FAIL_COND_V(load_nesting == 0, Ref<Resource>()); // It makes no sense to use this from nesting level 0.
const String &local_path = _validate_local_path(p_path);
@@ -1158,17 +1257,17 @@ String ResourceLoader::path_remap(const String &p_path) {
}
void ResourceLoader::reload_translation_remaps() {
- ResourceCache::lock.lock();
-
List<Resource *> to_reload;
- SelfList<Resource> *E = remapped_list.first();
- while (E) {
- to_reload.push_back(E->self());
- E = E->next();
- }
+ {
+ MutexLock lock(ResourceCache::lock);
+ SelfList<Resource> *E = remapped_list.first();
- ResourceCache::lock.unlock();
+ while (E) {
+ to_reload.push_back(E->self());
+ E = E->next();
+ }
+ }
//now just make sure to not delete any of these resources while changing locale..
while (to_reload.front()) {
@@ -1358,6 +1457,7 @@ bool ResourceLoader::timestamp_on_load = false;
thread_local int ResourceLoader::load_nesting = 0;
thread_local Vector<String> ResourceLoader::load_paths_stack;
thread_local HashMap<int, HashMap<String, Ref<Resource>>> ResourceLoader::res_ref_overrides;
+thread_local ResourceLoader::ThreadLoadTask *ResourceLoader::curr_load_task = nullptr;
SafeBinaryMutex<ResourceLoader::BINARY_MUTEX_TAG> &_get_res_loader_mutex() {
return ResourceLoader::thread_load_mutex;
diff --git a/core/io/resource_loader.h b/core/io/resource_loader.h
index f75bf019fb..caaf9f8f45 100644
--- a/core/io/resource_loader.h
+++ b/core/io/resource_loader.h
@@ -106,6 +106,8 @@ class ResourceLoader {
MAX_LOADERS = 64
};
+ struct ThreadLoadTask;
+
public:
enum ThreadLoadStatus {
THREAD_LOAD_INVALID_RESOURCE,
@@ -124,7 +126,7 @@ public:
String local_path;
String user_path;
uint32_t user_rc = 0; // Having user RC implies regular RC incremented in one, until the user RC reaches zero.
- Ref<Resource> res_if_unregistered;
+ ThreadLoadTask *task_if_unregistered = nullptr;
void clear();
@@ -187,6 +189,13 @@ private:
Ref<Resource> resource;
bool use_sub_threads = false;
HashSet<String> sub_tasks;
+
+ struct ResourceChangedConnection {
+ Resource *source = nullptr;
+ Callable callable;
+ uint32_t flags = 0;
+ };
+ LocalVector<ResourceChangedConnection> resource_changed_connections;
};
static void _run_load_task(void *p_userdata);
@@ -194,6 +203,7 @@ private:
static thread_local int load_nesting;
static thread_local HashMap<int, HashMap<String, Ref<Resource>>> res_ref_overrides; // Outermost key is nesting level.
static thread_local Vector<String> load_paths_stack;
+ static thread_local ThreadLoadTask *curr_load_task;
static SafeBinaryMutex<BINARY_MUTEX_TAG> thread_load_mutex;
friend SafeBinaryMutex<BINARY_MUTEX_TAG> &_get_res_loader_mutex();
@@ -214,6 +224,10 @@ public:
static bool is_within_load() { return load_nesting > 0; };
+ static void resource_changed_connect(Resource *p_source, const Callable &p_callable, uint32_t p_flags);
+ static void resource_changed_disconnect(Resource *p_source, const Callable &p_callable);
+ static void resource_changed_emit(Resource *p_source);
+
static Ref<Resource> load(const String &p_path, const String &p_type_hint = "", ResourceFormatLoader::CacheMode p_cache_mode = ResourceFormatLoader::CACHE_MODE_REUSE, Error *r_error = nullptr);
static bool exists(const String &p_path, const String &p_type_hint = "");
diff --git a/core/math/a_star.cpp b/core/math/a_star.cpp
index 4497604947..c53fd3d330 100644
--- a/core/math/a_star.cpp
+++ b/core/math/a_star.cpp
@@ -391,9 +391,9 @@ bool AStar3D::_solve(Point *begin_point, Point *end_point) {
return found_route;
}
-real_t AStar3D::_estimate_cost(int64_t p_from_id, int64_t p_to_id) {
+real_t AStar3D::_estimate_cost(int64_t p_from_id, int64_t p_end_id) {
real_t scost;
- if (GDVIRTUAL_CALL(_estimate_cost, p_from_id, p_to_id, scost)) {
+ if (GDVIRTUAL_CALL(_estimate_cost, p_from_id, p_end_id, scost)) {
return scost;
}
@@ -401,11 +401,11 @@ real_t AStar3D::_estimate_cost(int64_t p_from_id, int64_t p_to_id) {
bool from_exists = points.lookup(p_from_id, from_point);
ERR_FAIL_COND_V_MSG(!from_exists, 0, vformat("Can't estimate cost. Point with id: %d doesn't exist.", p_from_id));
- Point *to_point = nullptr;
- bool to_exists = points.lookup(p_to_id, to_point);
- ERR_FAIL_COND_V_MSG(!to_exists, 0, vformat("Can't estimate cost. Point with id: %d doesn't exist.", p_to_id));
+ Point *end_point = nullptr;
+ bool end_exists = points.lookup(p_end_id, end_point);
+ ERR_FAIL_COND_V_MSG(!end_exists, 0, vformat("Can't estimate cost. Point with id: %d doesn't exist.", p_end_id));
- return from_point->pos.distance_to(to_point->pos);
+ return from_point->pos.distance_to(end_point->pos);
}
real_t AStar3D::_compute_cost(int64_t p_from_id, int64_t p_to_id) {
@@ -579,7 +579,7 @@ void AStar3D::_bind_methods() {
ClassDB::bind_method(D_METHOD("get_point_path", "from_id", "to_id", "allow_partial_path"), &AStar3D::get_point_path, DEFVAL(false));
ClassDB::bind_method(D_METHOD("get_id_path", "from_id", "to_id", "allow_partial_path"), &AStar3D::get_id_path, DEFVAL(false));
- GDVIRTUAL_BIND(_estimate_cost, "from_id", "to_id")
+ GDVIRTUAL_BIND(_estimate_cost, "from_id", "end_id")
GDVIRTUAL_BIND(_compute_cost, "from_id", "to_id")
}
@@ -675,9 +675,9 @@ Vector2 AStar2D::get_closest_position_in_segment(const Vector2 &p_point) const {
return Vector2(p.x, p.y);
}
-real_t AStar2D::_estimate_cost(int64_t p_from_id, int64_t p_to_id) {
+real_t AStar2D::_estimate_cost(int64_t p_from_id, int64_t p_end_id) {
real_t scost;
- if (GDVIRTUAL_CALL(_estimate_cost, p_from_id, p_to_id, scost)) {
+ if (GDVIRTUAL_CALL(_estimate_cost, p_from_id, p_end_id, scost)) {
return scost;
}
@@ -685,11 +685,11 @@ real_t AStar2D::_estimate_cost(int64_t p_from_id, int64_t p_to_id) {
bool from_exists = astar.points.lookup(p_from_id, from_point);
ERR_FAIL_COND_V_MSG(!from_exists, 0, vformat("Can't estimate cost. Point with id: %d doesn't exist.", p_from_id));
- AStar3D::Point *to_point = nullptr;
- bool to_exists = astar.points.lookup(p_to_id, to_point);
- ERR_FAIL_COND_V_MSG(!to_exists, 0, vformat("Can't estimate cost. Point with id: %d doesn't exist.", p_to_id));
+ AStar3D::Point *end_point = nullptr;
+ bool to_exists = astar.points.lookup(p_end_id, end_point);
+ ERR_FAIL_COND_V_MSG(!to_exists, 0, vformat("Can't estimate cost. Point with id: %d doesn't exist.", p_end_id));
- return from_point->pos.distance_to(to_point->pos);
+ return from_point->pos.distance_to(end_point->pos);
}
real_t AStar2D::_compute_cost(int64_t p_from_id, int64_t p_to_id) {
@@ -918,6 +918,6 @@ void AStar2D::_bind_methods() {
ClassDB::bind_method(D_METHOD("get_point_path", "from_id", "to_id", "allow_partial_path"), &AStar2D::get_point_path, DEFVAL(false));
ClassDB::bind_method(D_METHOD("get_id_path", "from_id", "to_id", "allow_partial_path"), &AStar2D::get_id_path, DEFVAL(false));
- GDVIRTUAL_BIND(_estimate_cost, "from_id", "to_id")
+ GDVIRTUAL_BIND(_estimate_cost, "from_id", "end_id")
GDVIRTUAL_BIND(_compute_cost, "from_id", "to_id")
}
diff --git a/core/math/a_star.h b/core/math/a_star.h
index 8e054c4789..143a3bec61 100644
--- a/core/math/a_star.h
+++ b/core/math/a_star.h
@@ -120,7 +120,7 @@ class AStar3D : public RefCounted {
protected:
static void _bind_methods();
- virtual real_t _estimate_cost(int64_t p_from_id, int64_t p_to_id);
+ virtual real_t _estimate_cost(int64_t p_from_id, int64_t p_end_id);
virtual real_t _compute_cost(int64_t p_from_id, int64_t p_to_id);
GDVIRTUAL2RC(real_t, _estimate_cost, int64_t, int64_t)
@@ -176,7 +176,7 @@ class AStar2D : public RefCounted {
protected:
static void _bind_methods();
- virtual real_t _estimate_cost(int64_t p_from_id, int64_t p_to_id);
+ virtual real_t _estimate_cost(int64_t p_from_id, int64_t p_end_id);
virtual real_t _compute_cost(int64_t p_from_id, int64_t p_to_id);
GDVIRTUAL2RC(real_t, _estimate_cost, int64_t, int64_t)
diff --git a/core/math/a_star_grid_2d.cpp b/core/math/a_star_grid_2d.cpp
index 984bb1c9c1..c40ee5b4d7 100644
--- a/core/math/a_star_grid_2d.cpp
+++ b/core/math/a_star_grid_2d.cpp
@@ -127,13 +127,18 @@ void AStarGrid2D::update() {
}
points.clear();
+ solid_mask.clear();
const int32_t end_x = region.get_end().x;
const int32_t end_y = region.get_end().y;
const Vector2 half_cell_size = cell_size / 2;
+ for (int32_t x = region.position.x; x < end_x + 2; x++) {
+ solid_mask.push_back(true);
+ }
for (int32_t y = region.position.y; y < end_y; y++) {
LocalVector<Point> line;
+ solid_mask.push_back(true);
for (int32_t x = region.position.x; x < end_x; x++) {
Vector2 v = offset;
switch (cell_shape) {
@@ -150,10 +155,16 @@ void AStarGrid2D::update() {
break;
}
line.push_back(Point(Vector2i(x, y), v));
+ solid_mask.push_back(false);
}
+ solid_mask.push_back(true);
points.push_back(line);
}
+ for (int32_t x = region.position.x; x < end_x + 2; x++) {
+ solid_mask.push_back(true);
+ }
+
dirty = false;
}
@@ -207,13 +218,13 @@ AStarGrid2D::Heuristic AStarGrid2D::get_default_estimate_heuristic() const {
void AStarGrid2D::set_point_solid(const Vector2i &p_id, bool p_solid) {
ERR_FAIL_COND_MSG(dirty, "Grid is not initialized. Call the update method.");
ERR_FAIL_COND_MSG(!is_in_boundsv(p_id), vformat("Can't set if point is disabled. Point %s out of bounds %s.", p_id, region));
- _get_point_unchecked(p_id)->solid = p_solid;
+ _set_solid_unchecked(p_id, p_solid);
}
bool AStarGrid2D::is_point_solid(const Vector2i &p_id) const {
ERR_FAIL_COND_V_MSG(dirty, false, "Grid is not initialized. Call the update method.");
ERR_FAIL_COND_V_MSG(!is_in_boundsv(p_id), false, vformat("Can't get if point is disabled. Point %s out of bounds %s.", p_id, region));
- return _get_point_unchecked(p_id)->solid;
+ return _get_solid_unchecked(p_id);
}
void AStarGrid2D::set_point_weight_scale(const Vector2i &p_id, real_t p_weight_scale) {
@@ -238,7 +249,7 @@ void AStarGrid2D::fill_solid_region(const Rect2i &p_region, bool p_solid) {
for (int32_t y = safe_region.position.y; y < end_y; y++) {
for (int32_t x = safe_region.position.x; x < end_x; x++) {
- _get_point_unchecked(x, y)->solid = p_solid;
+ _set_solid_unchecked(x, y, p_solid);
}
}
}
@@ -259,13 +270,6 @@ void AStarGrid2D::fill_weight_scale_region(const Rect2i &p_region, real_t p_weig
}
AStarGrid2D::Point *AStarGrid2D::_jump(Point *p_from, Point *p_to) {
- if (!p_to || p_to->solid) {
- return nullptr;
- }
- if (p_to == end) {
- return p_to;
- }
-
int32_t from_x = p_from->id.x;
int32_t from_y = p_from->id.y;
@@ -276,72 +280,109 @@ AStarGrid2D::Point *AStarGrid2D::_jump(Point *p_from, Point *p_to) {
int32_t dy = to_y - from_y;
if (diagonal_mode == DIAGONAL_MODE_ALWAYS || diagonal_mode == DIAGONAL_MODE_AT_LEAST_ONE_WALKABLE) {
- if (dx != 0 && dy != 0) {
- if ((_is_walkable(to_x - dx, to_y + dy) && !_is_walkable(to_x - dx, to_y)) || (_is_walkable(to_x + dx, to_y - dy) && !_is_walkable(to_x, to_y - dy))) {
- return p_to;
- }
- if (_jump(p_to, _get_point(to_x + dx, to_y)) != nullptr) {
- return p_to;
+ if (dx == 0 || dy == 0) {
+ return _forced_successor(to_x, to_y, dx, dy);
+ }
+
+ while (_is_walkable(to_x, to_y) && (diagonal_mode == DIAGONAL_MODE_ALWAYS || _is_walkable(to_x, to_y - dy) || _is_walkable(to_x - dx, to_y))) {
+ if (end->id.x == to_x && end->id.y == to_y) {
+ return end;
}
- if (_jump(p_to, _get_point(to_x, to_y + dy)) != nullptr) {
- return p_to;
+
+ if ((_is_walkable(to_x - dx, to_y + dy) && !_is_walkable(to_x - dx, to_y)) || (_is_walkable(to_x + dx, to_y - dy) && !_is_walkable(to_x, to_y - dy))) {
+ return _get_point_unchecked(to_x, to_y);
}
- } else {
- if (dx != 0) {
- if ((_is_walkable(to_x + dx, to_y + 1) && !_is_walkable(to_x, to_y + 1)) || (_is_walkable(to_x + dx, to_y - 1) && !_is_walkable(to_x, to_y - 1))) {
- return p_to;
- }
- } else {
- if ((_is_walkable(to_x + 1, to_y + dy) && !_is_walkable(to_x + 1, to_y)) || (_is_walkable(to_x - 1, to_y + dy) && !_is_walkable(to_x - 1, to_y))) {
- return p_to;
- }
+
+ if (_forced_successor(to_x + dx, to_y, dx, 0) != nullptr || _forced_successor(to_x, to_y + dy, 0, dy) != nullptr) {
+ return _get_point_unchecked(to_x, to_y);
}
+
+ to_x += dx;
+ to_y += dy;
}
- if (_is_walkable(to_x + dx, to_y + dy) && (diagonal_mode == DIAGONAL_MODE_ALWAYS || (_is_walkable(to_x + dx, to_y) || _is_walkable(to_x, to_y + dy)))) {
- return _jump(p_to, _get_point(to_x + dx, to_y + dy));
- }
+
} else if (diagonal_mode == DIAGONAL_MODE_ONLY_IF_NO_OBSTACLES) {
- if (dx != 0 && dy != 0) {
- if ((_is_walkable(to_x + dx, to_y + dy) && !_is_walkable(to_x, to_y + dy)) || !_is_walkable(to_x + dx, to_y)) {
- return p_to;
- }
- if (_jump(p_to, _get_point(to_x + dx, to_y)) != nullptr) {
- return p_to;
+ if (dx == 0 || dy == 0) {
+ return _forced_successor(from_x, from_y, dx, dy, true);
+ }
+
+ while (_is_walkable(to_x, to_y) && _is_walkable(to_x, to_y - dy) && _is_walkable(to_x - dx, to_y)) {
+ if (end->id.x == to_x && end->id.y == to_y) {
+ return end;
}
- if (_jump(p_to, _get_point(to_x, to_y + dy)) != nullptr) {
- return p_to;
+
+ if ((_is_walkable(to_x + dx, to_y + dy) && !_is_walkable(to_x, to_y + dy)) || !_is_walkable(to_x + dx, to_y)) {
+ return _get_point_unchecked(to_x, to_y);
}
- } else {
- if (dx != 0) {
- if ((_is_walkable(to_x, to_y + 1) && !_is_walkable(to_x - dx, to_y + 1)) || (_is_walkable(to_x, to_y - 1) && !_is_walkable(to_x - dx, to_y - 1))) {
- return p_to;
- }
- } else {
- if ((_is_walkable(to_x + 1, to_y) && !_is_walkable(to_x + 1, to_y - dy)) || (_is_walkable(to_x - 1, to_y) && !_is_walkable(to_x - 1, to_y - dy))) {
- return p_to;
- }
+
+ if (_forced_successor(to_x, to_y, dx, 0) != nullptr || _forced_successor(to_x, to_y, 0, dy) != nullptr) {
+ return _get_point_unchecked(to_x, to_y);
}
+
+ to_x += dx;
+ to_y += dy;
}
- if (_is_walkable(to_x + dx, to_y + dy) && _is_walkable(to_x + dx, to_y) && _is_walkable(to_x, to_y + dy)) {
- return _jump(p_to, _get_point(to_x + dx, to_y + dy));
- }
+
} else { // DIAGONAL_MODE_NEVER
- if (dx != 0) {
- if ((_is_walkable(to_x, to_y - 1) && !_is_walkable(to_x - dx, to_y - 1)) || (_is_walkable(to_x, to_y + 1) && !_is_walkable(to_x - dx, to_y + 1))) {
- return p_to;
+ if (dy == 0) {
+ return _forced_successor(from_x, from_y, dx, 0, true);
+ }
+
+ while (_is_walkable(to_x, to_y)) {
+ if (end->id.x == to_x && end->id.y == to_y) {
+ return end;
}
- } else if (dy != 0) {
+
if ((_is_walkable(to_x - 1, to_y) && !_is_walkable(to_x - 1, to_y - dy)) || (_is_walkable(to_x + 1, to_y) && !_is_walkable(to_x + 1, to_y - dy))) {
- return p_to;
- }
- if (_jump(p_to, _get_point(to_x + 1, to_y)) != nullptr) {
- return p_to;
+ return _get_point_unchecked(to_x, to_y);
}
- if (_jump(p_to, _get_point(to_x - 1, to_y)) != nullptr) {
- return p_to;
+
+ if (_forced_successor(to_x, to_y, 1, 0, true) != nullptr || _forced_successor(to_x, to_y, -1, 0, true) != nullptr) {
+ return _get_point_unchecked(to_x, to_y);
}
+
+ to_y += dy;
}
- return _jump(p_to, _get_point(to_x + dx, to_y + dy));
+ }
+
+ return nullptr;
+}
+
+AStarGrid2D::Point *AStarGrid2D::_forced_successor(int32_t p_x, int32_t p_y, int32_t p_dx, int32_t p_dy, bool p_inclusive) {
+ // Remembering previous results can improve performance.
+ bool l_prev = false, r_prev = false, l = false, r = false;
+
+ int32_t o_x = p_x, o_y = p_y;
+ if (p_inclusive) {
+ o_x += p_dx;
+ o_y += p_dy;
+ }
+
+ int32_t l_x = p_x - p_dy, l_y = p_y - p_dx;
+ int32_t r_x = p_x + p_dy, r_y = p_y + p_dx;
+
+ while (_is_walkable(o_x, o_y)) {
+ if (end->id.x == o_x && end->id.y == o_y) {
+ return end;
+ }
+
+ l_prev = l || _is_walkable(l_x, l_y);
+ r_prev = r || _is_walkable(r_x, r_y);
+
+ l_x += p_dx;
+ l_y += p_dy;
+ r_x += p_dx;
+ r_y += p_dy;
+
+ l = _is_walkable(l_x, l_y);
+ r = _is_walkable(r_x, r_y);
+
+ if ((l && !l_prev) || (r && !r_prev)) {
+ return _get_point_unchecked(o_x, o_y);
+ }
+
+ o_x += p_dx;
+ o_y += p_dy;
}
return nullptr;
}
@@ -394,19 +435,19 @@ void AStarGrid2D::_get_nbors(Point *p_point, LocalVector<Point *> &r_nbors) {
}
}
- if (top && !top->solid) {
+ if (top && !_get_solid_unchecked(top->id)) {
r_nbors.push_back(top);
ts0 = true;
}
- if (right && !right->solid) {
+ if (right && !_get_solid_unchecked(right->id)) {
r_nbors.push_back(right);
ts1 = true;
}
- if (bottom && !bottom->solid) {
+ if (bottom && !_get_solid_unchecked(bottom->id)) {
r_nbors.push_back(bottom);
ts2 = true;
}
- if (left && !left->solid) {
+ if (left && !_get_solid_unchecked(left->id)) {
r_nbors.push_back(left);
ts3 = true;
}
@@ -436,16 +477,16 @@ void AStarGrid2D::_get_nbors(Point *p_point, LocalVector<Point *> &r_nbors) {
break;
}
- if (td0 && (top_left && !top_left->solid)) {
+ if (td0 && (top_left && !_get_solid_unchecked(top_left->id))) {
r_nbors.push_back(top_left);
}
- if (td1 && (top_right && !top_right->solid)) {
+ if (td1 && (top_right && !_get_solid_unchecked(top_right->id))) {
r_nbors.push_back(top_right);
}
- if (td2 && (bottom_right && !bottom_right->solid)) {
+ if (td2 && (bottom_right && !_get_solid_unchecked(bottom_right->id))) {
r_nbors.push_back(bottom_right);
}
- if (td3 && (bottom_left && !bottom_left->solid)) {
+ if (td3 && (bottom_left && !_get_solid_unchecked(bottom_left->id))) {
r_nbors.push_back(bottom_left);
}
}
@@ -454,7 +495,7 @@ bool AStarGrid2D::_solve(Point *p_begin_point, Point *p_end_point) {
last_closest_point = nullptr;
pass++;
- if (p_end_point->solid) {
+ if (_get_solid_unchecked(p_end_point->id)) {
return false;
}
@@ -500,7 +541,7 @@ bool AStarGrid2D::_solve(Point *p_begin_point, Point *p_end_point) {
continue;
}
} else {
- if (e->solid || e->closed_pass == pass) {
+ if (_get_solid_unchecked(e->id) || e->closed_pass == pass) {
continue;
}
weight_scale = e->weight_scale;
@@ -535,12 +576,12 @@ bool AStarGrid2D::_solve(Point *p_begin_point, Point *p_end_point) {
return found_route;
}
-real_t AStarGrid2D::_estimate_cost(const Vector2i &p_from_id, const Vector2i &p_to_id) {
+real_t AStarGrid2D::_estimate_cost(const Vector2i &p_from_id, const Vector2i &p_end_id) {
real_t scost;
- if (GDVIRTUAL_CALL(_estimate_cost, p_from_id, p_to_id, scost)) {
+ if (GDVIRTUAL_CALL(_estimate_cost, p_from_id, p_end_id, scost)) {
return scost;
}
- return heuristics[default_estimate_heuristic](p_from_id, p_to_id);
+ return heuristics[default_estimate_heuristic](p_from_id, p_end_id);
}
real_t AStarGrid2D::_compute_cost(const Vector2i &p_from_id, const Vector2i &p_to_id) {
@@ -562,6 +603,33 @@ Vector2 AStarGrid2D::get_point_position(const Vector2i &p_id) const {
return _get_point_unchecked(p_id)->pos;
}
+TypedArray<Dictionary> AStarGrid2D::get_point_data_in_region(const Rect2i &p_region) const {
+ ERR_FAIL_COND_V_MSG(dirty, TypedArray<Dictionary>(), "Grid is not initialized. Call the update method.");
+ const Rect2i inter_region = region.intersection(p_region);
+
+ const int32_t start_x = inter_region.position.x - region.position.x;
+ const int32_t start_y = inter_region.position.y - region.position.y;
+ const int32_t end_x = inter_region.get_end().x - region.position.x;
+ const int32_t end_y = inter_region.get_end().y - region.position.y;
+
+ TypedArray<Dictionary> data;
+
+ for (int32_t y = start_y; y < end_y; y++) {
+ for (int32_t x = start_x; x < end_x; x++) {
+ const Point &p = points[y][x];
+
+ Dictionary dict;
+ dict["id"] = p.id;
+ dict["position"] = p.pos;
+ dict["solid"] = _get_solid_unchecked(p.id);
+ dict["weight_scale"] = p.weight_scale;
+ data.push_back(dict);
+ }
+ }
+
+ return data;
+}
+
Vector<Vector2> AStarGrid2D::get_point_path(const Vector2i &p_from_id, const Vector2i &p_to_id, bool p_allow_partial_path) {
ERR_FAIL_COND_V_MSG(dirty, Vector<Vector2>(), "Grid is not initialized. Call the update method.");
ERR_FAIL_COND_V_MSG(!is_in_boundsv(p_from_id), Vector<Vector2>(), vformat("Can't get id path. Point %s out of bounds %s.", p_from_id, region));
@@ -698,10 +766,11 @@ void AStarGrid2D::_bind_methods() {
ClassDB::bind_method(D_METHOD("clear"), &AStarGrid2D::clear);
ClassDB::bind_method(D_METHOD("get_point_position", "id"), &AStarGrid2D::get_point_position);
+ ClassDB::bind_method(D_METHOD("get_point_data_in_region", "region"), &AStarGrid2D::get_point_data_in_region);
ClassDB::bind_method(D_METHOD("get_point_path", "from_id", "to_id", "allow_partial_path"), &AStarGrid2D::get_point_path, DEFVAL(false));
ClassDB::bind_method(D_METHOD("get_id_path", "from_id", "to_id", "allow_partial_path"), &AStarGrid2D::get_id_path, DEFVAL(false));
- GDVIRTUAL_BIND(_estimate_cost, "from_id", "to_id")
+ GDVIRTUAL_BIND(_estimate_cost, "from_id", "end_id")
GDVIRTUAL_BIND(_compute_cost, "from_id", "to_id")
ADD_PROPERTY(PropertyInfo(Variant::RECT2I, "region"), "set_region", "get_region");
diff --git a/core/math/a_star_grid_2d.h b/core/math/a_star_grid_2d.h
index 1a9f6dcc11..f5ac472f09 100644
--- a/core/math/a_star_grid_2d.h
+++ b/core/math/a_star_grid_2d.h
@@ -78,7 +78,6 @@ private:
struct Point {
Vector2i id;
- bool solid = false;
Vector2 pos;
real_t weight_scale = 1.0;
@@ -111,6 +110,7 @@ private:
}
};
+ LocalVector<bool> solid_mask;
LocalVector<LocalVector<Point>> points;
Point *end = nullptr;
Point *last_closest_point = nullptr;
@@ -118,11 +118,12 @@ private:
uint64_t pass = 1;
private: // Internal routines.
+ _FORCE_INLINE_ size_t _to_mask_index(int32_t p_x, int32_t p_y) const {
+ return ((p_y - region.position.y + 1) * (region.size.x + 2)) + p_x - region.position.x + 1;
+ }
+
_FORCE_INLINE_ bool _is_walkable(int32_t p_x, int32_t p_y) const {
- if (region.has_point(Vector2i(p_x, p_y))) {
- return !points[p_y - region.position.y][p_x - region.position.x].solid;
- }
- return false;
+ return !solid_mask[_to_mask_index(p_x, p_y)];
}
_FORCE_INLINE_ Point *_get_point(int32_t p_x, int32_t p_y) {
@@ -132,6 +133,18 @@ private: // Internal routines.
return nullptr;
}
+ _FORCE_INLINE_ void _set_solid_unchecked(int32_t p_x, int32_t p_y, bool p_solid) {
+ solid_mask[_to_mask_index(p_x, p_y)] = p_solid;
+ }
+
+ _FORCE_INLINE_ void _set_solid_unchecked(const Vector2i &p_id, bool p_solid) {
+ solid_mask[_to_mask_index(p_id.x, p_id.y)] = p_solid;
+ }
+
+ _FORCE_INLINE_ bool _get_solid_unchecked(const Vector2i &p_id) const {
+ return solid_mask[_to_mask_index(p_id.x, p_id.y)];
+ }
+
_FORCE_INLINE_ Point *_get_point_unchecked(int32_t p_x, int32_t p_y) {
return &points[p_y - region.position.y][p_x - region.position.x];
}
@@ -146,12 +159,13 @@ private: // Internal routines.
void _get_nbors(Point *p_point, LocalVector<Point *> &r_nbors);
Point *_jump(Point *p_from, Point *p_to);
+ Point *_forced_successor(int32_t p_x, int32_t p_y, int32_t p_dx, int32_t p_dy, bool p_inclusive = false);
bool _solve(Point *p_begin_point, Point *p_end_point);
protected:
static void _bind_methods();
- virtual real_t _estimate_cost(const Vector2i &p_from_id, const Vector2i &p_to_id);
+ virtual real_t _estimate_cost(const Vector2i &p_from_id, const Vector2i &p_end_id);
virtual real_t _compute_cost(const Vector2i &p_from_id, const Vector2i &p_to_id);
GDVIRTUAL2RC(real_t, _estimate_cost, Vector2i, Vector2i)
@@ -209,6 +223,7 @@ public:
void clear();
Vector2 get_point_position(const Vector2i &p_id) const;
+ TypedArray<Dictionary> get_point_data_in_region(const Rect2i &p_region) const;
Vector<Vector2> get_point_path(const Vector2i &p_from, const Vector2i &p_to, bool p_allow_partial_path = false);
TypedArray<Vector2i> get_id_path(const Vector2i &p_from, const Vector2i &p_to, bool p_allow_partial_path = false);
};
diff --git a/core/math/math_funcs.h b/core/math/math_funcs.h
index fd53ed28fd..1afc5f4bbb 100644
--- a/core/math/math_funcs.h
+++ b/core/math/math_funcs.h
@@ -105,6 +105,9 @@ public:
static _ALWAYS_INLINE_ double fmod(double p_x, double p_y) { return ::fmod(p_x, p_y); }
static _ALWAYS_INLINE_ float fmod(float p_x, float p_y) { return ::fmodf(p_x, p_y); }
+ static _ALWAYS_INLINE_ double modf(double p_x, double *r_y) { return ::modf(p_x, r_y); }
+ static _ALWAYS_INLINE_ float modf(float p_x, float *r_y) { return ::modff(p_x, r_y); }
+
static _ALWAYS_INLINE_ double floor(double p_x) { return ::floor(p_x); }
static _ALWAYS_INLINE_ float floor(float p_x) { return ::floorf(p_x); }
diff --git a/core/math/random_pcg.cpp b/core/math/random_pcg.cpp
index 55787a0b57..c286a60421 100644
--- a/core/math/random_pcg.cpp
+++ b/core/math/random_pcg.cpp
@@ -60,6 +60,11 @@ int64_t RandomPCG::rand_weighted(const Vector<float> &p_weights) {
}
}
+ for (int64_t i = weights_size - 1; i >= 0; --i) {
+ if (weights[i] > 0) {
+ return i;
+ }
+ }
return -1;
}
diff --git a/core/object/class_db.cpp b/core/object/class_db.cpp
index 5c793a676f..a65411629f 100644
--- a/core/object/class_db.cpp
+++ b/core/object/class_db.cpp
@@ -181,7 +181,7 @@ public:
return 0;
}
- static GDExtensionObjectPtr placeholder_class_create_instance(void *p_class_userdata, bool p_notify_postinitialize) {
+ static GDExtensionObjectPtr placeholder_class_create_instance(void *p_class_userdata, GDExtensionBool p_notify_postinitialize) {
ClassDB::ClassInfo *ti = (ClassDB::ClassInfo *)p_class_userdata;
// Find the closest native parent, that isn't a runtime class.
@@ -192,7 +192,7 @@ public:
ERR_FAIL_NULL_V(native_parent->creation_func, nullptr);
// Construct a placeholder.
- Object *obj = native_parent->creation_func(p_notify_postinitialize);
+ Object *obj = native_parent->creation_func(static_cast<bool>(p_notify_postinitialize));
// ClassDB::set_object_extension_instance() won't be called for placeholders.
// We need need to make sure that all the things it would have done (even if
@@ -271,6 +271,22 @@ void ClassDB::get_extensions_class_list(List<StringName> *p_classes) {
p_classes->sort_custom<StringName::AlphCompare>();
}
+
+void ClassDB::get_extension_class_list(const Ref<GDExtension> &p_extension, List<StringName> *p_classes) {
+ OBJTYPE_RLOCK;
+
+ for (const KeyValue<StringName, ClassInfo> &E : classes) {
+ if (E.value.api != API_EXTENSION && E.value.api != API_EDITOR_EXTENSION) {
+ continue;
+ }
+ if (!E.value.gdextension || E.value.gdextension->library != p_extension.ptr()) {
+ continue;
+ }
+ p_classes->push_back(E.key);
+ }
+
+ p_classes->sort_custom<StringName::AlphCompare>();
+}
#endif
void ClassDB::get_inheriters_from_class(const StringName &p_class, List<StringName> *p_classes) {
@@ -1632,14 +1648,16 @@ bool ClassDB::get_property(Object *p_object, const StringName &p_property, Varia
Variant index = psg->index;
const Variant *arg[1] = { &index };
Callable::CallError ce;
- r_value = p_object->callp(psg->getter, arg, 1, ce);
+ const Variant value = p_object->callp(psg->getter, arg, 1, ce);
+ r_value = (ce.error == Callable::CallError::CALL_OK) ? value : Variant();
} else {
Callable::CallError ce;
if (psg->_getptr) {
r_value = psg->_getptr->call(p_object, nullptr, 0, ce);
} else {
- r_value = p_object->callp(psg->getter, nullptr, 0, ce);
+ const Variant value = p_object->callp(psg->getter, nullptr, 0, ce);
+ r_value = (ce.error == Callable::CallError::CALL_OK) ? value : Variant();
}
}
return true;
diff --git a/core/object/class_db.h b/core/object/class_db.h
index d6a95b58e2..620092a6c4 100644
--- a/core/object/class_db.h
+++ b/core/object/class_db.h
@@ -285,6 +285,7 @@ public:
static void get_class_list(List<StringName> *p_classes);
#ifdef TOOLS_ENABLED
static void get_extensions_class_list(List<StringName> *p_classes);
+ static void get_extension_class_list(const Ref<GDExtension> &p_extension, List<StringName> *p_classes);
static ObjectGDExtension *get_placeholder_extension(const StringName &p_class);
#endif
static void get_inheriters_from_class(const StringName &p_class, List<StringName> *p_classes);
diff --git a/core/object/object.cpp b/core/object/object.cpp
index a2330ecd04..d6b7d7a7fe 100644
--- a/core/object/object.cpp
+++ b/core/object/object.cpp
@@ -605,7 +605,7 @@ Variant Object::_call_bind(const Variant **p_args, int p_argcount, Callable::Cal
return Variant();
}
- if (p_args[0]->get_type() != Variant::STRING_NAME && p_args[0]->get_type() != Variant::STRING) {
+ if (!p_args[0]->is_string()) {
r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT;
r_error.argument = 0;
r_error.expected = Variant::STRING_NAME;
@@ -624,7 +624,7 @@ Variant Object::_call_deferred_bind(const Variant **p_args, int p_argcount, Call
return Variant();
}
- if (p_args[0]->get_type() != Variant::STRING_NAME && p_args[0]->get_type() != Variant::STRING) {
+ if (!p_args[0]->is_string()) {
r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT;
r_error.argument = 0;
r_error.expected = Variant::STRING_NAME;
@@ -720,7 +720,7 @@ Variant Object::getvar(const Variant &p_key, bool *r_valid) const {
*r_valid = false;
}
- if (p_key.get_type() == Variant::STRING_NAME || p_key.get_type() == Variant::STRING) {
+ if (p_key.is_string()) {
return get(p_key, r_valid);
}
return Variant();
@@ -730,7 +730,7 @@ void Object::setvar(const Variant &p_key, const Variant &p_value, bool *r_valid)
if (r_valid) {
*r_valid = false;
}
- if (p_key.get_type() == Variant::STRING_NAME || p_key.get_type() == Variant::STRING) {
+ if (p_key.is_string()) {
return set(p_key, p_value, r_valid);
}
}
@@ -746,7 +746,7 @@ Variant Object::callv(const StringName &p_method, const Array &p_args) {
}
Callable::CallError ce;
- Variant ret = callp(p_method, argptrs, p_args.size(), ce);
+ const Variant ret = callp(p_method, argptrs, p_args.size(), ce);
if (ce.error != Callable::CallError::CALL_OK) {
ERR_FAIL_V_MSG(Variant(), "Error calling method from 'callv': " + Variant::get_call_error_text(this, p_method, argptrs, p_args.size(), ce) + ".");
}
@@ -787,7 +787,7 @@ Variant Object::callp(const StringName &p_method, const Variant **p_args, int p_
if (script_instance) {
ret = script_instance->callp(p_method, p_args, p_argcount, r_error);
- //force jumptable
+ // Force jump table.
switch (r_error.error) {
case Callable::CallError::CALL_OK:
return ret;
@@ -1023,6 +1023,14 @@ void Object::remove_meta(const StringName &p_name) {
set_meta(p_name, Variant());
}
+void Object::merge_meta_from(const Object *p_src) {
+ List<StringName> meta_keys;
+ p_src->get_meta_list(&meta_keys);
+ for (const StringName &key : meta_keys) {
+ set_meta(key, p_src->get_meta(key));
+ }
+}
+
TypedArray<Dictionary> Object::_get_property_list_bind() const {
List<PropertyInfo> lpi;
get_property_list(&lpi);
@@ -1096,7 +1104,7 @@ Error Object::_emit_signal(const Variant **p_args, int p_argcount, Callable::Cal
ERR_FAIL_V(Error::ERR_INVALID_PARAMETER);
}
- if (unlikely(p_args[0]->get_type() != Variant::STRING_NAME && p_args[0]->get_type() != Variant::STRING)) {
+ if (unlikely(!p_args[0]->is_string())) {
r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT;
r_error.argument = 0;
r_error.expected = Variant::STRING_NAME;
@@ -1716,33 +1724,65 @@ void Object::_bind_methods() {
#define BIND_OBJ_CORE_METHOD(m_method) \
::ClassDB::add_virtual_method(get_class_static(), m_method, true, Vector<String>(), true);
- MethodInfo notification_mi("_notification", PropertyInfo(Variant::INT, "what"));
- notification_mi.arguments_metadata.push_back(GodotTypeInfo::Metadata::METADATA_INT_IS_INT32);
- BIND_OBJ_CORE_METHOD(notification_mi);
- BIND_OBJ_CORE_METHOD(MethodInfo(Variant::BOOL, "_set", PropertyInfo(Variant::STRING_NAME, "property"), PropertyInfo(Variant::NIL, "value")));
+ BIND_OBJ_CORE_METHOD(MethodInfo("_init"));
+
+ BIND_OBJ_CORE_METHOD(MethodInfo(Variant::STRING, "_to_string"));
+
+ {
+ MethodInfo mi("_notification");
+ mi.arguments.push_back(PropertyInfo(Variant::INT, "what"));
+ mi.arguments_metadata.push_back(GodotTypeInfo::Metadata::METADATA_INT_IS_INT32);
+ BIND_OBJ_CORE_METHOD(mi);
+ }
+
+ {
+ MethodInfo mi("_set");
+ mi.arguments.push_back(PropertyInfo(Variant::STRING_NAME, "property"));
+ mi.arguments.push_back(PropertyInfo(Variant::NIL, "value", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_NIL_IS_VARIANT));
+ mi.return_val.type = Variant::BOOL;
+ BIND_OBJ_CORE_METHOD(mi);
+ }
+
#ifdef TOOLS_ENABLED
- MethodInfo miget("_get", PropertyInfo(Variant::STRING_NAME, "property"));
- miget.return_val.name = "Variant";
- miget.return_val.usage |= PROPERTY_USAGE_NIL_IS_VARIANT;
- BIND_OBJ_CORE_METHOD(miget);
+ {
+ MethodInfo mi("_get");
+ mi.arguments.push_back(PropertyInfo(Variant::STRING_NAME, "property"));
+ mi.return_val.usage |= PROPERTY_USAGE_NIL_IS_VARIANT;
+ BIND_OBJ_CORE_METHOD(mi);
+ }
- MethodInfo plget("_get_property_list");
- plget.return_val.type = Variant::ARRAY;
- plget.return_val.hint = PROPERTY_HINT_ARRAY_TYPE;
- plget.return_val.hint_string = "Dictionary";
- BIND_OBJ_CORE_METHOD(plget);
+ {
+ MethodInfo mi("_get_property_list");
+ mi.return_val.type = Variant::ARRAY;
+ mi.return_val.hint = PROPERTY_HINT_ARRAY_TYPE;
+ mi.return_val.hint_string = "Dictionary";
+ BIND_OBJ_CORE_METHOD(mi);
+ }
BIND_OBJ_CORE_METHOD(MethodInfo(Variant::NIL, "_validate_property", PropertyInfo(Variant::DICTIONARY, "property")));
BIND_OBJ_CORE_METHOD(MethodInfo(Variant::BOOL, "_property_can_revert", PropertyInfo(Variant::STRING_NAME, "property")));
- MethodInfo mipgr("_property_get_revert", PropertyInfo(Variant::STRING_NAME, "property"));
- mipgr.return_val.name = "Variant";
- mipgr.return_val.usage |= PROPERTY_USAGE_NIL_IS_VARIANT;
- BIND_OBJ_CORE_METHOD(mipgr);
+ {
+ MethodInfo mi("_property_get_revert");
+ mi.arguments.push_back(PropertyInfo(Variant::STRING_NAME, "property"));
+ mi.return_val.usage |= PROPERTY_USAGE_NIL_IS_VARIANT;
+ BIND_OBJ_CORE_METHOD(mi);
+ }
+
+ // These are actually `Variant` methods, but that doesn't matter since scripts can't inherit built-in types.
+
+ BIND_OBJ_CORE_METHOD(MethodInfo(Variant::BOOL, "_iter_init", PropertyInfo(Variant::ARRAY, "iter")));
+
+ BIND_OBJ_CORE_METHOD(MethodInfo(Variant::BOOL, "_iter_next", PropertyInfo(Variant::ARRAY, "iter")));
+
+ {
+ MethodInfo mi("_iter_get");
+ mi.arguments.push_back(PropertyInfo(Variant::NIL, "iter", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_NIL_IS_VARIANT));
+ mi.return_val.usage |= PROPERTY_USAGE_NIL_IS_VARIANT;
+ BIND_OBJ_CORE_METHOD(mi);
+ }
#endif
- BIND_OBJ_CORE_METHOD(MethodInfo("_init"));
- BIND_OBJ_CORE_METHOD(MethodInfo(Variant::STRING, "_to_string"));
BIND_CONSTANT(NOTIFICATION_POSTINITIALIZE);
BIND_CONSTANT(NOTIFICATION_PREDELETE);
@@ -1904,7 +1944,7 @@ void Object::set_instance_binding(void *p_token, void *p_binding, const GDExtens
void *Object::get_instance_binding(void *p_token, const GDExtensionInstanceBindingCallbacks *p_callbacks) {
void *binding = nullptr;
- _instance_binding_mutex.lock();
+ MutexLock instance_binding_lock(_instance_binding_mutex);
for (uint32_t i = 0; i < _instance_binding_count; i++) {
if (_instance_bindings[i].token == p_token) {
binding = _instance_bindings[i].binding;
@@ -1935,14 +1975,12 @@ void *Object::get_instance_binding(void *p_token, const GDExtensionInstanceBindi
_instance_binding_count++;
}
- _instance_binding_mutex.unlock();
-
return binding;
}
bool Object::has_instance_binding(void *p_token) {
bool found = false;
- _instance_binding_mutex.lock();
+ MutexLock instance_binding_lock(_instance_binding_mutex);
for (uint32_t i = 0; i < _instance_binding_count; i++) {
if (_instance_bindings[i].token == p_token) {
found = true;
@@ -1950,14 +1988,12 @@ bool Object::has_instance_binding(void *p_token) {
}
}
- _instance_binding_mutex.unlock();
-
return found;
}
void Object::free_instance_binding(void *p_token) {
bool found = false;
- _instance_binding_mutex.lock();
+ MutexLock instance_binding_lock(_instance_binding_mutex);
for (uint32_t i = 0; i < _instance_binding_count; i++) {
if (!found && _instance_bindings[i].token == p_token) {
if (_instance_bindings[i].free_callback) {
@@ -1976,7 +2012,6 @@ void Object::free_instance_binding(void *p_token) {
if (found) {
_instance_binding_count--;
}
- _instance_binding_mutex.unlock();
}
#ifdef TOOLS_ENABLED
@@ -2294,7 +2329,7 @@ void ObjectDB::cleanup() {
// Ensure calling the native classes because if a leaked instance has a script
// that overrides any of those methods, it'd not be OK to call them at this point,
// now the scripting languages have already been terminated.
- MethodBind *node_get_name = ClassDB::get_method("Node", "get_name");
+ MethodBind *node_get_path = ClassDB::get_method("Node", "get_path");
MethodBind *resource_get_path = ClassDB::get_method("Resource", "get_path");
Callable::CallError call_error;
@@ -2304,7 +2339,7 @@ void ObjectDB::cleanup() {
String extra_info;
if (obj->is_class("Node")) {
- extra_info = " - Node name: " + String(node_get_name->call(obj, nullptr, 0, call_error));
+ extra_info = " - Node path: " + String(node_get_path->call(obj, nullptr, 0, call_error));
}
if (obj->is_class("Resource")) {
extra_info = " - Resource path: " + String(resource_get_path->call(obj, nullptr, 0, call_error));
diff --git a/core/object/object.h b/core/object/object.h
index 7307b7ede0..19e6fc5d47 100644
--- a/core/object/object.h
+++ b/core/object/object.h
@@ -86,6 +86,7 @@ enum PropertyHint {
PROPERTY_HINT_HIDE_QUATERNION_EDIT, /// Only Node3D::transform should hide the quaternion editor.
PROPERTY_HINT_PASSWORD,
PROPERTY_HINT_LAYERS_AVOIDANCE,
+ PROPERTY_HINT_DICTIONARY_TYPE,
PROPERTY_HINT_MAX,
};
@@ -387,18 +388,6 @@ struct ObjectGDExtension {
* much alone defines the object model.
*/
-#define REVERSE_GET_PROPERTY_LIST \
-public: \
- _FORCE_INLINE_ bool _is_gpl_reversed() const { return true; }; \
- \
-private:
-
-#define UNREVERSE_GET_PROPERTY_LIST \
-public: \
- _FORCE_INLINE_ bool _is_gpl_reversed() const { return false; }; \
- \
-private:
-
#define GDCLASS(m_class, m_inherits) \
private: \
void operator=(const m_class &p_rval) {} \
@@ -510,15 +499,10 @@ protected:
m_inherits::_get_property_listv(p_list, p_reversed); \
} \
p_list->push_back(PropertyInfo(Variant::NIL, get_class_static(), PROPERTY_HINT_NONE, get_class_static(), PROPERTY_USAGE_CATEGORY)); \
- if (!_is_gpl_reversed()) { \
- ::ClassDB::get_property_list(#m_class, p_list, true, this); \
- } \
+ ::ClassDB::get_property_list(#m_class, p_list, true, this); \
if (m_class::_get_get_property_list() != m_inherits::_get_get_property_list()) { \
_get_property_list(p_list); \
} \
- if (_is_gpl_reversed()) { \
- ::ClassDB::get_property_list(#m_class, p_list, true, this); \
- } \
if (p_reversed) { \
m_inherits::_get_property_listv(p_list, p_reversed); \
} \
@@ -684,7 +668,7 @@ protected:
_FORCE_INLINE_ bool _instance_binding_reference(bool p_reference) {
bool can_die = true;
if (_instance_bindings) {
- _instance_binding_mutex.lock();
+ MutexLock instance_binding_lock(_instance_binding_mutex);
for (uint32_t i = 0; i < _instance_binding_count; i++) {
if (_instance_bindings[i].reference_callback) {
if (!_instance_bindings[i].reference_callback(_instance_bindings[i].token, _instance_bindings[i].binding, p_reference)) {
@@ -692,7 +676,6 @@ protected:
}
}
}
- _instance_binding_mutex.unlock();
}
return can_die;
}
@@ -799,8 +782,6 @@ public:
return &ptr;
}
- bool _is_gpl_reversed() const { return false; }
-
void detach_from_objectdb();
_FORCE_INLINE_ ObjectID get_instance_id() const { return _instance_id; }
@@ -887,7 +868,8 @@ public:
argptrs[i] = &args[i];
}
Callable::CallError cerr;
- return callp(p_method, sizeof...(p_args) == 0 ? nullptr : (const Variant **)argptrs, sizeof...(p_args), cerr);
+ const Variant ret = callp(p_method, sizeof...(p_args) == 0 ? nullptr : (const Variant **)argptrs, sizeof...(p_args), cerr);
+ return (cerr.error == Callable::CallError::CALL_OK) ? ret : Variant();
}
void notification(int p_notification, bool p_reversed = false);
@@ -914,6 +896,7 @@ public:
MTVIRTUAL void remove_meta(const StringName &p_name);
MTVIRTUAL Variant get_meta(const StringName &p_name, const Variant &p_default = Variant()) const;
MTVIRTUAL void get_meta_list(List<StringName> *p_list) const;
+ MTVIRTUAL void merge_meta_from(const Object *p_src);
#ifdef TOOLS_ENABLED
void set_edited(bool p_edited);
diff --git a/core/object/worker_thread_pool.cpp b/core/object/worker_thread_pool.cpp
index 7fd43c4094..fe7bbd474c 100644
--- a/core/object/worker_thread_pool.cpp
+++ b/core/object/worker_thread_pool.cpp
@@ -127,9 +127,8 @@ void WorkerThreadPool::_process_task(Task *p_task) {
if (finished_users == max_users) {
// Get rid of the group, because nobody else is using it.
- task_mutex.lock();
+ MutexLock task_lock(task_mutex);
group_allocator.free(p_task->group);
- task_mutex.unlock();
}
// For groups, tasks get rid of themselves.
@@ -349,17 +348,13 @@ WorkerThreadPool::TaskID WorkerThreadPool::add_task(const Callable &p_action, bo
}
bool WorkerThreadPool::is_task_completed(TaskID p_task_id) const {
- task_mutex.lock();
+ MutexLock task_lock(task_mutex);
const Task *const *taskp = tasks.getptr(p_task_id);
if (!taskp) {
- task_mutex.unlock();
ERR_FAIL_V_MSG(false, "Invalid Task ID"); // Invalid task
}
- bool completed = (*taskp)->completed;
- task_mutex.unlock();
-
- return completed;
+ return (*taskp)->completed;
}
Error WorkerThreadPool::wait_for_task_completion(TaskID p_task_id) {
@@ -461,7 +456,10 @@ void WorkerThreadPool::_wait_collaboratively(ThreadData *p_caller_pool_thread, T
p_caller_pool_thread->signaled = false;
if (IS_WAIT_OVER) {
- p_caller_pool_thread->yield_is_over = false;
+ if (unlikely(p_task == ThreadData::YIELDING)) {
+ p_caller_pool_thread->yield_is_over = false;
+ }
+
if (!exit_threads && was_signaled) {
// This thread was awaken for some additional reason, but it's about to exit.
// Let's find out what may be pending and forward the requests.
@@ -519,10 +517,9 @@ void WorkerThreadPool::yield() {
}
void WorkerThreadPool::notify_yield_over(TaskID p_task_id) {
- task_mutex.lock();
+ MutexLock task_lock(task_mutex);
Task **taskp = tasks.getptr(p_task_id);
if (!taskp) {
- task_mutex.unlock();
ERR_FAIL_MSG("Invalid Task ID.");
}
Task *task = *taskp;
@@ -531,7 +528,6 @@ void WorkerThreadPool::notify_yield_over(TaskID p_task_id) {
// This avoids a race condition where a task is created and yield-over called before it's processed.
task->pending_notify_yield_over = true;
}
- task_mutex.unlock();
return;
}
@@ -539,8 +535,6 @@ void WorkerThreadPool::notify_yield_over(TaskID p_task_id) {
td.yield_is_over = true;
td.signaled = true;
td.cond_var.notify_one();
-
- task_mutex.unlock();
}
WorkerThreadPool::GroupID WorkerThreadPool::_add_group_task(const Callable &p_callable, void (*p_func)(void *, uint32_t), void *p_userdata, BaseTemplateUserdata *p_template_userdata, int p_elements, int p_tasks, bool p_high_priority, const String &p_description) {
@@ -598,26 +592,20 @@ WorkerThreadPool::GroupID WorkerThreadPool::add_group_task(const Callable &p_act
}
uint32_t WorkerThreadPool::get_group_processed_element_count(GroupID p_group) const {
- task_mutex.lock();
+ MutexLock task_lock(task_mutex);
const Group *const *groupp = groups.getptr(p_group);
if (!groupp) {
- task_mutex.unlock();
ERR_FAIL_V_MSG(0, "Invalid Group ID");
}
- uint32_t elements = (*groupp)->completed_index.get();
- task_mutex.unlock();
- return elements;
+ return (*groupp)->completed_index.get();
}
bool WorkerThreadPool::is_group_task_completed(GroupID p_group) const {
- task_mutex.lock();
+ MutexLock task_lock(task_mutex);
const Group *const *groupp = groups.getptr(p_group);
if (!groupp) {
- task_mutex.unlock();
ERR_FAIL_V_MSG(false, "Invalid Group ID");
}
- bool completed = (*groupp)->completed.is_set();
- task_mutex.unlock();
- return completed;
+ return (*groupp)->completed.is_set();
}
void WorkerThreadPool::wait_for_group_task_completion(GroupID p_group) {
@@ -641,15 +629,13 @@ void WorkerThreadPool::wait_for_group_task_completion(GroupID p_group) {
if (finished_users == max_users) {
// All tasks using this group are gone (finished before the group), so clear the group too.
- task_mutex.lock();
+ MutexLock task_lock(task_mutex);
group_allocator.free(group);
- task_mutex.unlock();
}
}
- task_mutex.lock(); // This mutex is needed when Physics 2D and/or 3D is selected to run on a separate thread.
+ MutexLock task_lock(task_mutex); // This mutex is needed when Physics 2D and/or 3D is selected to run on a separate thread.
groups.erase(p_group);
- task_mutex.unlock();
#endif
}
@@ -701,6 +687,8 @@ void WorkerThreadPool::init(int p_thread_count, float p_low_priority_task_ratio)
max_low_priority_threads = CLAMP(p_thread_count * p_low_priority_task_ratio, 1, p_thread_count - 1);
+ print_verbose(vformat("WorkerThreadPool: %d threads, %d max low-priority.", p_thread_count, max_low_priority_threads));
+
threads.resize(p_thread_count);
for (uint32_t i = 0; i < threads.size(); i++) {
diff --git a/core/os/os.h b/core/os/os.h
index 91e0ce9379..30d2a4266f 100644
--- a/core/os/os.h
+++ b/core/os/os.h
@@ -75,6 +75,7 @@ class OS {
int _display_driver_id = -1;
String _current_rendering_driver_name;
String _current_rendering_method;
+ bool _is_gles_over_gl = false;
RemoteFilesystemClient default_rfs;
@@ -111,9 +112,6 @@ protected:
virtual void initialize() = 0;
virtual void initialize_joypads() = 0;
- void set_current_rendering_driver_name(const String &p_driver_name) { _current_rendering_driver_name = p_driver_name; }
- void set_current_rendering_method(const String &p_name) { _current_rendering_method = p_name; }
-
void set_display_driver_id(int p_display_driver_id) { _display_driver_id = p_display_driver_id; }
virtual void set_main_loop(MainLoop *p_main_loop) = 0;
@@ -131,12 +129,18 @@ public:
static OS *get_singleton();
+ void set_current_rendering_driver_name(const String &p_driver_name) { _current_rendering_driver_name = p_driver_name; }
+ void set_current_rendering_method(const String &p_name) { _current_rendering_method = p_name; }
+ void set_gles_over_gl(bool p_enabled) { _is_gles_over_gl = p_enabled; }
+
String get_current_rendering_driver_name() const { return _current_rendering_driver_name; }
String get_current_rendering_method() const { return _current_rendering_method; }
+ bool get_gles_over_gl() const { return _is_gles_over_gl; }
int get_display_driver_id() const { return _display_driver_id; }
virtual Vector<String> get_video_adapter_driver_info() const = 0;
+ virtual bool get_user_prefers_integrated_gpu() const { return false; }
void print_error(const char *p_function, const char *p_file, int p_line, const char *p_code, const char *p_rationale, bool p_editor_notify = false, Logger::ErrorType p_type = Logger::ERR_ERROR);
void print(const char *p_format, ...) _PRINTF_FORMAT_ATTRIBUTE_2_3;
@@ -178,7 +182,7 @@ public:
virtual Vector<String> get_system_font_path_for_text(const String &p_font_name, const String &p_text, const String &p_locale = String(), const String &p_script = String(), int p_weight = 400, int p_stretch = 100, bool p_italic = false) const { return Vector<String>(); };
virtual String get_executable_path() const;
virtual Error execute(const String &p_path, const List<String> &p_arguments, String *r_pipe = nullptr, int *r_exitcode = nullptr, bool read_stderr = false, Mutex *p_pipe_mutex = nullptr, bool p_open_console = false) = 0;
- virtual Dictionary execute_with_pipe(const String &p_path, const List<String> &p_arguments) { return Dictionary(); }
+ virtual Dictionary execute_with_pipe(const String &p_path, const List<String> &p_arguments, bool p_blocking = true) { return Dictionary(); }
virtual Error create_process(const String &p_path, const List<String> &p_arguments, ProcessID *r_child_id = nullptr, bool p_open_console = false) = 0;
virtual Error create_instance(const List<String> &p_arguments, ProcessID *r_child_id = nullptr) { return create_process(get_executable_path(), p_arguments, r_child_id); };
virtual Error kill(const ProcessID &p_pid) = 0;
diff --git a/core/os/pool_allocator.cpp b/core/os/pool_allocator.cpp
deleted file mode 100644
index 9a993cd14f..0000000000
--- a/core/os/pool_allocator.cpp
+++ /dev/null
@@ -1,588 +0,0 @@
-/**************************************************************************/
-/* pool_allocator.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 "pool_allocator.h"
-
-#include "core/error/error_macros.h"
-#include "core/os/memory.h"
-#include "core/os/os.h"
-#include "core/string/print_string.h"
-
-#define COMPACT_CHUNK(m_entry, m_to_pos) \
- if constexpr (true) { \
- void *_dst = &((unsigned char *)pool)[m_to_pos]; \
- void *_src = &((unsigned char *)pool)[(m_entry).pos]; \
- memmove(_dst, _src, aligned((m_entry).len)); \
- (m_entry).pos = m_to_pos; \
- } else \
- ((void)0)
-
-void PoolAllocator::mt_lock() const {
-}
-
-void PoolAllocator::mt_unlock() const {
-}
-
-bool PoolAllocator::get_free_entry(EntryArrayPos *p_pos) {
- if (entry_count == entry_max) {
- return false;
- }
-
- for (int i = 0; i < entry_max; i++) {
- if (entry_array[i].len == 0) {
- *p_pos = i;
- return true;
- }
- }
-
- ERR_PRINT("Out of memory Chunks!");
-
- return false; //
-}
-
-/**
- * Find a hole
- * @param p_pos The hole is behind the block pointed by this variable upon return. if pos==entry_count, then allocate at end
- * @param p_for_size hole size
- * @return false if hole found, true if no hole found
- */
-bool PoolAllocator::find_hole(EntryArrayPos *p_pos, int p_for_size) {
- /* position where previous entry ends. Defaults to zero (begin of pool) */
-
- int prev_entry_end_pos = 0;
-
- for (int i = 0; i < entry_count; i++) {
- Entry &entry = entry_array[entry_indices[i]];
-
- /* determine hole size to previous entry */
-
- int hole_size = entry.pos - prev_entry_end_pos;
-
- /* determine if what we want fits in that hole */
- if (hole_size >= p_for_size) {
- *p_pos = i;
- return true;
- }
-
- /* prepare for next one */
- prev_entry_end_pos = entry_end(entry);
- }
-
- /* No holes between entries, check at the end..*/
-
- if ((pool_size - prev_entry_end_pos) >= p_for_size) {
- *p_pos = entry_count;
- return true;
- }
-
- return false;
-}
-
-void PoolAllocator::compact(int p_up_to) {
- uint32_t prev_entry_end_pos = 0;
-
- if (p_up_to < 0) {
- p_up_to = entry_count;
- }
- for (int i = 0; i < p_up_to; i++) {
- Entry &entry = entry_array[entry_indices[i]];
-
- /* determine hole size to previous entry */
-
- int hole_size = entry.pos - prev_entry_end_pos;
-
- /* if we can compact, do it */
- if (hole_size > 0 && !entry.lock) {
- COMPACT_CHUNK(entry, prev_entry_end_pos);
- }
-
- /* prepare for next one */
- prev_entry_end_pos = entry_end(entry);
- }
-}
-
-void PoolAllocator::compact_up(int p_from) {
- uint32_t next_entry_end_pos = pool_size; // - static_area_size;
-
- for (int i = entry_count - 1; i >= p_from; i--) {
- Entry &entry = entry_array[entry_indices[i]];
-
- /* determine hole size for next entry */
-
- int hole_size = next_entry_end_pos - (entry.pos + aligned(entry.len));
-
- /* if we can compact, do it */
- if (hole_size > 0 && !entry.lock) {
- COMPACT_CHUNK(entry, (next_entry_end_pos - aligned(entry.len)));
- }
-
- /* prepare for next one */
- next_entry_end_pos = entry.pos;
- }
-}
-
-bool PoolAllocator::find_entry_index(EntryIndicesPos *p_map_pos, const Entry *p_entry) {
- EntryArrayPos entry_pos = entry_max;
-
- for (int i = 0; i < entry_count; i++) {
- if (&entry_array[entry_indices[i]] == p_entry) {
- entry_pos = i;
- break;
- }
- }
-
- if (entry_pos == entry_max) {
- return false;
- }
-
- *p_map_pos = entry_pos;
- return true;
-}
-
-PoolAllocator::ID PoolAllocator::alloc(int p_size) {
- ERR_FAIL_COND_V(p_size < 1, POOL_ALLOCATOR_INVALID_ID);
- ERR_FAIL_COND_V(p_size > free_mem, POOL_ALLOCATOR_INVALID_ID);
-
- mt_lock();
-
- if (entry_count == entry_max) {
- mt_unlock();
- ERR_PRINT("entry_count==entry_max");
- return POOL_ALLOCATOR_INVALID_ID;
- }
-
- int size_to_alloc = aligned(p_size);
-
- EntryIndicesPos new_entry_indices_pos;
-
- if (!find_hole(&new_entry_indices_pos, size_to_alloc)) {
- /* No hole could be found, try compacting mem */
- compact();
- /* Then search again */
-
- if (!find_hole(&new_entry_indices_pos, size_to_alloc)) {
- mt_unlock();
- ERR_FAIL_V_MSG(POOL_ALLOCATOR_INVALID_ID, "Memory can't be compacted further.");
- }
- }
-
- EntryArrayPos new_entry_array_pos;
-
- bool found_free_entry = get_free_entry(&new_entry_array_pos);
-
- if (!found_free_entry) {
- mt_unlock();
- ERR_FAIL_V_MSG(POOL_ALLOCATOR_INVALID_ID, "No free entry found in PoolAllocator.");
- }
-
- /* move all entry indices up, make room for this one */
- for (int i = entry_count; i > new_entry_indices_pos; i--) {
- entry_indices[i] = entry_indices[i - 1];
- }
-
- entry_indices[new_entry_indices_pos] = new_entry_array_pos;
-
- entry_count++;
-
- Entry &entry = entry_array[entry_indices[new_entry_indices_pos]];
-
- entry.len = p_size;
- entry.pos = (new_entry_indices_pos == 0) ? 0 : entry_end(entry_array[entry_indices[new_entry_indices_pos - 1]]); //alloc either at beginning or end of previous
- entry.lock = 0;
- entry.check = (check_count++) & CHECK_MASK;
- free_mem -= size_to_alloc;
- if (free_mem < free_mem_peak) {
- free_mem_peak = free_mem;
- }
-
- ID retval = (entry_indices[new_entry_indices_pos] << CHECK_BITS) | entry.check;
- mt_unlock();
-
- //ERR_FAIL_COND_V( (uintptr_t)get(retval)%align != 0, retval );
-
- return retval;
-}
-
-PoolAllocator::Entry *PoolAllocator::get_entry(ID p_mem) {
- unsigned int check = p_mem & CHECK_MASK;
- int entry = p_mem >> CHECK_BITS;
- ERR_FAIL_INDEX_V(entry, entry_max, nullptr);
- ERR_FAIL_COND_V(entry_array[entry].check != check, nullptr);
- ERR_FAIL_COND_V(entry_array[entry].len == 0, nullptr);
-
- return &entry_array[entry];
-}
-
-const PoolAllocator::Entry *PoolAllocator::get_entry(ID p_mem) const {
- unsigned int check = p_mem & CHECK_MASK;
- int entry = p_mem >> CHECK_BITS;
- ERR_FAIL_INDEX_V(entry, entry_max, nullptr);
- ERR_FAIL_COND_V(entry_array[entry].check != check, nullptr);
- ERR_FAIL_COND_V(entry_array[entry].len == 0, nullptr);
-
- return &entry_array[entry];
-}
-
-void PoolAllocator::free(ID p_mem) {
- mt_lock();
- Entry *e = get_entry(p_mem);
- if (!e) {
- mt_unlock();
- ERR_PRINT("!e");
- return;
- }
- if (e->lock) {
- mt_unlock();
- ERR_PRINT("e->lock");
- return;
- }
-
- EntryIndicesPos entry_indices_pos;
-
- bool index_found = find_entry_index(&entry_indices_pos, e);
- if (!index_found) {
- mt_unlock();
- ERR_FAIL_COND(!index_found);
- }
-
- for (int i = entry_indices_pos; i < (entry_count - 1); i++) {
- entry_indices[i] = entry_indices[i + 1];
- }
-
- entry_count--;
- free_mem += aligned(e->len);
- e->clear();
- mt_unlock();
-}
-
-int PoolAllocator::get_size(ID p_mem) const {
- int size;
- mt_lock();
-
- const Entry *e = get_entry(p_mem);
- if (!e) {
- mt_unlock();
- ERR_PRINT("!e");
- return 0;
- }
-
- size = e->len;
-
- mt_unlock();
-
- return size;
-}
-
-Error PoolAllocator::resize(ID p_mem, int p_new_size) {
- mt_lock();
- Entry *e = get_entry(p_mem);
-
- if (!e) {
- mt_unlock();
- ERR_FAIL_NULL_V(e, ERR_INVALID_PARAMETER);
- }
-
- if (needs_locking && e->lock) {
- mt_unlock();
- ERR_FAIL_COND_V(e->lock, ERR_ALREADY_IN_USE);
- }
-
- uint32_t alloc_size = aligned(p_new_size);
-
- if ((uint32_t)aligned(e->len) == alloc_size) {
- e->len = p_new_size;
- mt_unlock();
- return OK;
- } else if (e->len > (uint32_t)p_new_size) {
- free_mem += aligned(e->len);
- free_mem -= alloc_size;
- e->len = p_new_size;
- mt_unlock();
- return OK;
- }
-
- //p_new_size = align(p_new_size)
- int _free = free_mem; // - static_area_size;
-
- if (uint32_t(_free + aligned(e->len)) < alloc_size) {
- mt_unlock();
- ERR_FAIL_V(ERR_OUT_OF_MEMORY);
- }
-
- EntryIndicesPos entry_indices_pos;
-
- bool index_found = find_entry_index(&entry_indices_pos, e);
-
- if (!index_found) {
- mt_unlock();
- ERR_FAIL_COND_V(!index_found, ERR_BUG);
- }
-
- //no need to move stuff around, it fits before the next block
- uint32_t next_pos;
- if (entry_indices_pos + 1 == entry_count) {
- next_pos = pool_size; // - static_area_size;
- } else {
- next_pos = entry_array[entry_indices[entry_indices_pos + 1]].pos;
- }
-
- if ((next_pos - e->pos) > alloc_size) {
- free_mem += aligned(e->len);
- e->len = p_new_size;
- free_mem -= alloc_size;
- mt_unlock();
- return OK;
- }
- //it doesn't fit, compact around BEFORE current index (make room behind)
-
- compact(entry_indices_pos + 1);
-
- if ((next_pos - e->pos) > alloc_size) {
- //now fits! hooray!
- free_mem += aligned(e->len);
- e->len = p_new_size;
- free_mem -= alloc_size;
- mt_unlock();
- if (free_mem < free_mem_peak) {
- free_mem_peak = free_mem;
- }
- return OK;
- }
-
- //STILL doesn't fit, compact around AFTER current index (make room after)
-
- compact_up(entry_indices_pos + 1);
-
- if ((entry_array[entry_indices[entry_indices_pos + 1]].pos - e->pos) > alloc_size) {
- //now fits! hooray!
- free_mem += aligned(e->len);
- e->len = p_new_size;
- free_mem -= alloc_size;
- mt_unlock();
- if (free_mem < free_mem_peak) {
- free_mem_peak = free_mem;
- }
- return OK;
- }
-
- mt_unlock();
- ERR_FAIL_V(ERR_OUT_OF_MEMORY);
-}
-
-Error PoolAllocator::lock(ID p_mem) {
- if (!needs_locking) {
- return OK;
- }
- mt_lock();
- Entry *e = get_entry(p_mem);
- if (!e) {
- mt_unlock();
- ERR_PRINT("!e");
- return ERR_INVALID_PARAMETER;
- }
- e->lock++;
- mt_unlock();
- return OK;
-}
-
-bool PoolAllocator::is_locked(ID p_mem) const {
- if (!needs_locking) {
- return false;
- }
-
- mt_lock();
- const Entry *e = const_cast<PoolAllocator *>(this)->get_entry(p_mem);
- if (!e) {
- mt_unlock();
- ERR_PRINT("!e");
- return false;
- }
- bool locked = e->lock;
- mt_unlock();
- return locked;
-}
-
-const void *PoolAllocator::get(ID p_mem) const {
- if (!needs_locking) {
- const Entry *e = get_entry(p_mem);
- ERR_FAIL_NULL_V(e, nullptr);
- return &pool[e->pos];
- }
-
- mt_lock();
- const Entry *e = get_entry(p_mem);
-
- if (!e) {
- mt_unlock();
- ERR_FAIL_NULL_V(e, nullptr);
- }
- if (e->lock == 0) {
- mt_unlock();
- ERR_PRINT("e->lock == 0");
- return nullptr;
- }
-
- if ((int)e->pos >= pool_size) {
- mt_unlock();
- ERR_PRINT("e->pos<0 || e->pos>=pool_size");
- return nullptr;
- }
- const void *ptr = &pool[e->pos];
-
- mt_unlock();
-
- return ptr;
-}
-
-void *PoolAllocator::get(ID p_mem) {
- if (!needs_locking) {
- Entry *e = get_entry(p_mem);
- ERR_FAIL_NULL_V(e, nullptr);
- return &pool[e->pos];
- }
-
- mt_lock();
- Entry *e = get_entry(p_mem);
-
- if (!e) {
- mt_unlock();
- ERR_FAIL_NULL_V(e, nullptr);
- }
- if (e->lock == 0) {
- mt_unlock();
- ERR_PRINT("e->lock == 0");
- return nullptr;
- }
-
- if ((int)e->pos >= pool_size) {
- mt_unlock();
- ERR_PRINT("e->pos<0 || e->pos>=pool_size");
- return nullptr;
- }
- void *ptr = &pool[e->pos];
-
- mt_unlock();
-
- return ptr;
-}
-
-void PoolAllocator::unlock(ID p_mem) {
- if (!needs_locking) {
- return;
- }
- mt_lock();
- Entry *e = get_entry(p_mem);
- if (!e) {
- mt_unlock();
- ERR_FAIL_NULL(e);
- }
- if (e->lock == 0) {
- mt_unlock();
- ERR_PRINT("e->lock == 0");
- return;
- }
- e->lock--;
- mt_unlock();
-}
-
-int PoolAllocator::get_used_mem() const {
- return pool_size - free_mem;
-}
-
-int PoolAllocator::get_free_peak() {
- return free_mem_peak;
-}
-
-int PoolAllocator::get_free_mem() {
- return free_mem;
-}
-
-void PoolAllocator::create_pool(void *p_mem, int p_size, int p_max_entries) {
- pool = (uint8_t *)p_mem;
- pool_size = p_size;
-
- entry_array = memnew_arr(Entry, p_max_entries);
- entry_indices = memnew_arr(int, p_max_entries);
- entry_max = p_max_entries;
- entry_count = 0;
-
- free_mem = p_size;
- free_mem_peak = p_size;
-
- check_count = 0;
-}
-
-PoolAllocator::PoolAllocator(int p_size, bool p_needs_locking, int p_max_entries) {
- mem_ptr = memalloc(p_size);
- ERR_FAIL_NULL(mem_ptr);
- align = 1;
- create_pool(mem_ptr, p_size, p_max_entries);
- needs_locking = p_needs_locking;
-}
-
-PoolAllocator::PoolAllocator(void *p_mem, int p_size, int p_align, bool p_needs_locking, int p_max_entries) {
- if (p_align > 1) {
- uint8_t *mem8 = (uint8_t *)p_mem;
- uint64_t ofs = (uint64_t)mem8;
- if (ofs % p_align) {
- int dif = p_align - (ofs % p_align);
- mem8 += p_align - (ofs % p_align);
- p_size -= dif;
- p_mem = (void *)mem8;
- }
- }
-
- create_pool(p_mem, p_size, p_max_entries);
- needs_locking = p_needs_locking;
- align = p_align;
- mem_ptr = nullptr;
-}
-
-PoolAllocator::PoolAllocator(int p_align, int p_size, bool p_needs_locking, int p_max_entries) {
- ERR_FAIL_COND(p_align < 1);
- mem_ptr = Memory::alloc_static(p_size + p_align, true);
- uint8_t *mem8 = (uint8_t *)mem_ptr;
- uint64_t ofs = (uint64_t)mem8;
- if (ofs % p_align) {
- mem8 += p_align - (ofs % p_align);
- }
- create_pool(mem8, p_size, p_max_entries);
- needs_locking = p_needs_locking;
- align = p_align;
-}
-
-PoolAllocator::~PoolAllocator() {
- if (mem_ptr) {
- memfree(mem_ptr);
- }
-
- memdelete_arr(entry_array);
- memdelete_arr(entry_indices);
-}
diff --git a/core/os/pool_allocator.h b/core/os/pool_allocator.h
deleted file mode 100644
index be8b6e061b..0000000000
--- a/core/os/pool_allocator.h
+++ /dev/null
@@ -1,148 +0,0 @@
-/**************************************************************************/
-/* pool_allocator.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 POOL_ALLOCATOR_H
-#define POOL_ALLOCATOR_H
-
-#include "core/typedefs.h"
-
-/**
- * Generic Pool Allocator.
- * This is a generic memory pool allocator, with locking, compacting and alignment. (@TODO alignment)
- * It used as a standard way to manage allocation in a specific region of memory, such as texture memory,
- * audio sample memory, or just any kind of memory overall.
- * (@TODO) abstraction should be greater, because in many platforms, you need to manage a nonreachable memory.
- */
-
-enum {
- POOL_ALLOCATOR_INVALID_ID = -1 ///< default invalid value. use INVALID_ID( id ) to test
-};
-
-class PoolAllocator {
-public:
- typedef int ID;
-
-private:
- enum {
- CHECK_BITS = 8,
- CHECK_LEN = (1 << CHECK_BITS),
- CHECK_MASK = CHECK_LEN - 1
-
- };
-
- struct Entry {
- unsigned int pos = 0;
- unsigned int len = 0;
- unsigned int lock = 0;
- unsigned int check = 0;
-
- inline void clear() {
- pos = 0;
- len = 0;
- lock = 0;
- check = 0;
- }
- Entry() {}
- };
-
- typedef int EntryArrayPos;
- typedef int EntryIndicesPos;
-
- Entry *entry_array = nullptr;
- int *entry_indices = nullptr;
- int entry_max = 0;
- int entry_count = 0;
-
- uint8_t *pool = nullptr;
- void *mem_ptr = nullptr;
- int pool_size = 0;
-
- int free_mem = 0;
- int free_mem_peak = 0;
-
- unsigned int check_count = 0;
- int align = 1;
-
- bool needs_locking = false;
-
- inline int entry_end(const Entry &p_entry) const {
- return p_entry.pos + aligned(p_entry.len);
- }
- inline int aligned(int p_size) const {
- int rem = p_size % align;
- if (rem) {
- p_size += align - rem;
- }
-
- return p_size;
- }
-
- void compact(int p_up_to = -1);
- void compact_up(int p_from = 0);
- bool get_free_entry(EntryArrayPos *p_pos);
- bool find_hole(EntryArrayPos *p_pos, int p_for_size);
- bool find_entry_index(EntryIndicesPos *p_map_pos, const Entry *p_entry);
- Entry *get_entry(ID p_mem);
- const Entry *get_entry(ID p_mem) const;
-
- void create_pool(void *p_mem, int p_size, int p_max_entries);
-
-protected:
- virtual void mt_lock() const; ///< Reimplement for custom mt locking
- virtual void mt_unlock() const; ///< Reimplement for custom mt locking
-
-public:
- enum {
- DEFAULT_MAX_ALLOCS = 4096,
- };
-
- ID alloc(int p_size); ///< Alloc memory, get an ID on success, POOL_ALOCATOR_INVALID_ID on failure
- void free(ID p_mem); ///< Free allocated memory
- Error resize(ID p_mem, int p_new_size); ///< resize a memory chunk
- int get_size(ID p_mem) const;
-
- int get_free_mem(); ///< get free memory
- int get_used_mem() const;
- int get_free_peak(); ///< get free memory
-
- Error lock(ID p_mem); //@todo move this out
- void *get(ID p_mem);
- const void *get(ID p_mem) const;
- void unlock(ID p_mem);
- bool is_locked(ID p_mem) const;
-
- PoolAllocator(int p_size, bool p_needs_locking = false, int p_max_entries = DEFAULT_MAX_ALLOCS);
- PoolAllocator(void *p_mem, int p_size, int p_align = 1, bool p_needs_locking = false, int p_max_entries = DEFAULT_MAX_ALLOCS);
- PoolAllocator(int p_align, int p_size, bool p_needs_locking = false, int p_max_entries = DEFAULT_MAX_ALLOCS);
-
- virtual ~PoolAllocator();
-};
-
-#endif // POOL_ALLOCATOR_H
diff --git a/core/string/string_name.cpp b/core/string/string_name.cpp
index 5d59d65f92..0294dbfbbc 100644
--- a/core/string/string_name.cpp
+++ b/core/string/string_name.cpp
@@ -39,6 +39,30 @@ StaticCString StaticCString::create(const char *p_ptr) {
return scs;
}
+bool StringName::_Data::operator==(const String &p_name) const {
+ if (cname) {
+ return p_name == cname;
+ } else {
+ return name == p_name;
+ }
+}
+
+bool StringName::_Data::operator!=(const String &p_name) const {
+ return !operator==(p_name);
+}
+
+bool StringName::_Data::operator==(const char *p_name) const {
+ if (cname) {
+ return strcmp(cname, p_name) == 0;
+ } else {
+ return name == p_name;
+ }
+}
+
+bool StringName::_Data::operator!=(const char *p_name) const {
+ return !operator==(p_name);
+}
+
StringName _scs_create(const char *p_chr, bool p_static) {
return (p_chr[0] ? StringName(StaticCString::create(p_chr), p_static) : StringName());
}
@@ -139,19 +163,19 @@ void StringName::unref() {
}
bool StringName::operator==(const String &p_name) const {
- if (!_data) {
- return (p_name.length() == 0);
+ if (_data) {
+ return _data->operator==(p_name);
}
- return (_data->get_name() == p_name);
+ return p_name.is_empty();
}
bool StringName::operator==(const char *p_name) const {
- if (!_data) {
- return (p_name[0] == 0);
+ if (_data) {
+ return _data->operator==(p_name);
}
- return (_data->get_name() == p_name);
+ return p_name[0] == 0;
}
bool StringName::operator!=(const String &p_name) const {
@@ -168,9 +192,47 @@ bool StringName::operator!=(const StringName &p_name) const {
return _data != p_name._data;
}
-void StringName::operator=(const StringName &p_name) {
+char32_t StringName::operator[](int p_index) const {
+ if (_data) {
+ if (_data->cname) {
+ CRASH_BAD_INDEX(p_index, static_cast<long>(strlen(_data->cname)));
+ return _data->cname[p_index];
+ } else {
+ return _data->name[p_index];
+ }
+ }
+
+ CRASH_BAD_INDEX(p_index, 0);
+ return 0;
+}
+
+int StringName::length() const {
+ if (_data) {
+ if (_data->cname) {
+ return strlen(_data->cname);
+ } else {
+ return _data->name.length();
+ }
+ }
+
+ return 0;
+}
+
+bool StringName::is_empty() const {
+ if (_data) {
+ if (_data->cname) {
+ return _data->cname[0] == 0;
+ } else {
+ return _data->name.is_empty();
+ }
+ }
+
+ return true;
+}
+
+StringName &StringName::operator=(const StringName &p_name) {
if (this == &p_name) {
- return;
+ return *this;
}
unref();
@@ -178,6 +240,8 @@ void StringName::operator=(const StringName &p_name) {
if (p_name._data && p_name._data->refcount.ref()) {
_data = p_name._data;
}
+
+ return *this;
}
StringName::StringName(const StringName &p_name) {
@@ -191,11 +255,10 @@ StringName::StringName(const StringName &p_name) {
}
void StringName::assign_static_unique_class_name(StringName *ptr, const char *p_name) {
- mutex.lock();
+ MutexLock lock(mutex);
if (*ptr == StringName()) {
*ptr = StringName(p_name, true);
}
- mutex.unlock();
}
StringName::StringName(const char *p_name, bool p_static) {
@@ -217,7 +280,7 @@ StringName::StringName(const char *p_name, bool p_static) {
while (_data) {
// compare hash first
- if (_data->hash == hash && _data->get_name() == p_name) {
+ if (_data->hash == hash && _data->operator==(p_name)) {
break;
}
_data = _data->next;
@@ -276,7 +339,7 @@ StringName::StringName(const StaticCString &p_static_string, bool p_static) {
while (_data) {
// compare hash first
- if (_data->hash == hash && _data->get_name() == p_static_string.ptr) {
+ if (_data->hash == hash && _data->operator==(p_static_string.ptr)) {
break;
}
_data = _data->next;
@@ -334,7 +397,7 @@ StringName::StringName(const String &p_name, bool p_static) {
_data = _table[idx];
while (_data) {
- if (_data->hash == hash && _data->get_name() == p_name) {
+ if (_data->hash == hash && _data->operator==(p_name)) {
break;
}
_data = _data->next;
@@ -393,7 +456,7 @@ StringName StringName::search(const char *p_name) {
while (_data) {
// compare hash first
- if (_data->hash == hash && _data->get_name() == p_name) {
+ if (_data->hash == hash && _data->operator==(p_name)) {
break;
}
_data = _data->next;
@@ -430,7 +493,7 @@ StringName StringName::search(const char32_t *p_name) {
while (_data) {
// compare hash first
- if (_data->hash == hash && _data->get_name() == p_name) {
+ if (_data->hash == hash && _data->operator==(p_name)) {
break;
}
_data = _data->next;
@@ -456,7 +519,7 @@ StringName StringName::search(const String &p_name) {
while (_data) {
// compare hash first
- if (_data->hash == hash && p_name == _data->get_name()) {
+ if (_data->hash == hash && _data->operator==(p_name)) {
break;
}
_data = _data->next;
@@ -475,15 +538,15 @@ StringName StringName::search(const String &p_name) {
}
bool operator==(const String &p_name, const StringName &p_string_name) {
- return p_name == p_string_name.operator String();
+ return p_string_name.operator==(p_name);
}
bool operator!=(const String &p_name, const StringName &p_string_name) {
- return p_name != p_string_name.operator String();
+ return p_string_name.operator!=(p_name);
}
bool operator==(const char *p_name, const StringName &p_string_name) {
- return p_name == p_string_name.operator String();
+ return p_string_name.operator==(p_name);
}
bool operator!=(const char *p_name, const StringName &p_string_name) {
- return p_name != p_string_name.operator String();
+ return p_string_name.operator!=(p_name);
}
diff --git a/core/string/string_name.h b/core/string/string_name.h
index 0eb98cf64b..288e2c7520 100644
--- a/core/string/string_name.h
+++ b/core/string/string_name.h
@@ -60,6 +60,11 @@ class StringName {
uint32_t debug_references = 0;
#endif
String get_name() const { return cname ? String(cname) : name; }
+ bool operator==(const String &p_name) const;
+ bool operator!=(const String &p_name) const;
+ bool operator==(const char *p_name) const;
+ bool operator!=(const char *p_name) const;
+
int idx = 0;
uint32_t hash = 0;
_Data *prev = nullptr;
@@ -99,6 +104,10 @@ public:
bool operator!=(const String &p_name) const;
bool operator!=(const char *p_name) const;
+ char32_t operator[](int p_index) const;
+ int length() const;
+ bool is_empty() const;
+
_FORCE_INLINE_ bool is_node_unique_name() const {
if (!_data) {
return false;
@@ -175,7 +184,7 @@ public:
}
};
- void operator=(const StringName &p_name);
+ StringName &operator=(const StringName &p_name);
StringName(const char *p_name, bool p_static = false);
StringName(const StringName &p_name);
StringName(const String &p_name, bool p_static = false);
diff --git a/core/string/translation_server.cpp b/core/string/translation_server.cpp
index 6e784881d0..4ac79ad10a 100644
--- a/core/string/translation_server.cpp
+++ b/core/string/translation_server.cpp
@@ -284,6 +284,11 @@ String TranslationServer::_standardize_locale(const String &p_locale, bool p_add
}
int TranslationServer::compare_locales(const String &p_locale_a, const String &p_locale_b) const {
+ if (p_locale_a == p_locale_b) {
+ // Exact match.
+ return 10;
+ }
+
String locale_a = _standardize_locale(p_locale_a, true);
String locale_b = _standardize_locale(p_locale_b, true);
diff --git a/core/typedefs.h b/core/typedefs.h
index 0de803293d..35c4668581 100644
--- a/core/typedefs.h
+++ b/core/typedefs.h
@@ -44,6 +44,9 @@
#include "core/error/error_list.h"
#include <cstdint>
+// Ensure that C++ standard is at least C++17. If on MSVC, also ensures that the `Zc:__cplusplus` flag is present.
+static_assert(__cplusplus >= 201703L);
+
// Turn argument to string constant:
// https://gcc.gnu.org/onlinedocs/cpp/Stringizing.html#Stringizing
#ifndef _STR
diff --git a/core/variant/binder_common.h b/core/variant/binder_common.h
index 61b90e2a26..fa49767d46 100644
--- a/core/variant/binder_common.h
+++ b/core/variant/binder_common.h
@@ -214,11 +214,11 @@ struct VariantCaster<char32_t> {
template <>
struct PtrToArg<char32_t> {
_FORCE_INLINE_ static char32_t convert(const void *p_ptr) {
- return char32_t(*reinterpret_cast<const int *>(p_ptr));
+ return char32_t(*reinterpret_cast<const int64_t *>(p_ptr));
}
typedef int64_t EncodeT;
_FORCE_INLINE_ static void encode(char32_t p_val, const void *p_ptr) {
- *(int *)p_ptr = p_val;
+ *(int64_t *)p_ptr = p_val;
}
};
diff --git a/core/variant/callable.cpp b/core/variant/callable.cpp
index 667aae879c..9dff5c1e91 100644
--- a/core/variant/callable.cpp
+++ b/core/variant/callable.cpp
@@ -112,7 +112,7 @@ Error Callable::rpcp(int p_id, const Variant **p_arguments, int p_argcount, Call
argptrs[i + 2] = p_arguments[i];
}
- CallError tmp;
+ CallError tmp; // TODO: Check `tmp`?
Error err = (Error)obj->callp(SNAME("rpc_id"), argptrs, argcount, tmp).operator int64_t();
r_call_error.error = Callable::CallError::CALL_OK;
diff --git a/core/variant/dictionary.cpp b/core/variant/dictionary.cpp
index 7416101d51..f2522a4545 100644
--- a/core/variant/dictionary.cpp
+++ b/core/variant/dictionary.cpp
@@ -32,6 +32,7 @@
#include "core/templates/hash_map.h"
#include "core/templates/safe_refcount.h"
+#include "core/variant/container_type_validate.h"
#include "core/variant/variant.h"
// required in this order by VariantInternal, do not remove this comment.
#include "core/object/class_db.h"
@@ -43,6 +44,9 @@ struct DictionaryPrivate {
SafeRefCount refcount;
Variant *read_only = nullptr; // If enabled, a pointer is used to a temporary value that is used to return read-only values.
HashMap<Variant, Variant, VariantHasher, StringLikeVariantComparator> variant_map;
+ ContainerTypeValidate typed_key;
+ ContainerTypeValidate typed_value;
+ Variant *typed_fallback = nullptr; // Allows a typed dictionary to return dummy values when attempting an invalid access.
};
void Dictionary::get_key_list(List<Variant> *p_keys) const {
@@ -81,15 +85,7 @@ Variant Dictionary::get_value_at_index(int p_index) const {
Variant &Dictionary::operator[](const Variant &p_key) {
if (unlikely(_p->read_only)) {
- if (p_key.get_type() == Variant::STRING_NAME) {
- const StringName *sn = VariantInternal::get_string_name(&p_key);
- const String &key = sn->operator String();
- if (likely(_p->variant_map.has(key))) {
- *_p->read_only = _p->variant_map[key];
- } else {
- *_p->read_only = Variant();
- }
- } else if (likely(_p->variant_map.has(p_key))) {
+ if (likely(_p->variant_map.has(p_key))) {
*_p->read_only = _p->variant_map[p_key];
} else {
*_p->read_only = Variant();
@@ -97,12 +93,7 @@ Variant &Dictionary::operator[](const Variant &p_key) {
return *_p->read_only;
} else {
- if (p_key.get_type() == Variant::STRING_NAME) {
- const StringName *sn = VariantInternal::get_string_name(&p_key);
- return _p->variant_map[sn->operator String()];
- } else {
- return _p->variant_map[p_key];
- }
+ return _p->variant_map[p_key];
}
}
@@ -133,7 +124,9 @@ Variant *Dictionary::getptr(const Variant &p_key) {
}
Variant Dictionary::get_valid(const Variant &p_key) const {
- HashMap<Variant, Variant, VariantHasher, StringLikeVariantComparator>::ConstIterator E(_p->variant_map.find(p_key));
+ Variant key = p_key;
+ ERR_FAIL_COND_V(!_p->typed_key.validate(key, "get_valid"), Variant());
+ HashMap<Variant, Variant, VariantHasher, StringLikeVariantComparator>::ConstIterator E(_p->variant_map.find(key));
if (!E) {
return Variant();
@@ -142,7 +135,9 @@ Variant Dictionary::get_valid(const Variant &p_key) const {
}
Variant Dictionary::get(const Variant &p_key, const Variant &p_default) const {
- const Variant *result = getptr(p_key);
+ Variant key = p_key;
+ ERR_FAIL_COND_V(!_p->typed_key.validate(key, "get"), p_default);
+ const Variant *result = getptr(key);
if (!result) {
return p_default;
}
@@ -151,10 +146,14 @@ Variant Dictionary::get(const Variant &p_key, const Variant &p_default) const {
}
Variant Dictionary::get_or_add(const Variant &p_key, const Variant &p_default) {
- const Variant *result = getptr(p_key);
+ Variant key = p_key;
+ ERR_FAIL_COND_V(!_p->typed_key.validate(key, "get"), p_default);
+ const Variant *result = getptr(key);
if (!result) {
- operator[](p_key) = p_default;
- return p_default;
+ Variant value = p_default;
+ ERR_FAIL_COND_V(!_p->typed_value.validate(value, "add"), value);
+ operator[](key) = value;
+ return value;
}
return *result;
}
@@ -168,12 +167,16 @@ bool Dictionary::is_empty() const {
}
bool Dictionary::has(const Variant &p_key) const {
+ Variant key = p_key;
+ ERR_FAIL_COND_V(!_p->typed_key.validate(key, "use 'has'"), false);
return _p->variant_map.has(p_key);
}
bool Dictionary::has_all(const Array &p_keys) const {
for (int i = 0; i < p_keys.size(); i++) {
- if (!has(p_keys[i])) {
+ Variant key = p_keys[i];
+ ERR_FAIL_COND_V(!_p->typed_key.validate(key, "use 'has_all'"), false);
+ if (!has(key)) {
return false;
}
}
@@ -181,8 +184,10 @@ bool Dictionary::has_all(const Array &p_keys) const {
}
Variant Dictionary::find_key(const Variant &p_value) const {
+ Variant value = p_value;
+ ERR_FAIL_COND_V(!_p->typed_value.validate(value, "find_key"), Variant());
for (const KeyValue<Variant, Variant> &E : _p->variant_map) {
- if (E.value == p_value) {
+ if (E.value == value) {
return E.key;
}
}
@@ -190,8 +195,10 @@ Variant Dictionary::find_key(const Variant &p_value) const {
}
bool Dictionary::erase(const Variant &p_key) {
+ Variant key = p_key;
+ ERR_FAIL_COND_V(!_p->typed_key.validate(key, "erase"), false);
ERR_FAIL_COND_V_MSG(_p->read_only, false, "Dictionary is in read-only state.");
- return _p->variant_map.erase(p_key);
+ return _p->variant_map.erase(key);
}
bool Dictionary::operator==(const Dictionary &p_dictionary) const {
@@ -251,8 +258,12 @@ void Dictionary::clear() {
void Dictionary::merge(const Dictionary &p_dictionary, bool p_overwrite) {
ERR_FAIL_COND_MSG(_p->read_only, "Dictionary is in read-only state.");
for (const KeyValue<Variant, Variant> &E : p_dictionary._p->variant_map) {
- if (p_overwrite || !has(E.key)) {
- operator[](E.key) = E.value;
+ Variant key = E.key;
+ Variant value = E.value;
+ ERR_FAIL_COND(!_p->typed_key.validate(key, "merge"));
+ ERR_FAIL_COND(!_p->typed_key.validate(value, "merge"));
+ if (p_overwrite || !has(key)) {
+ operator[](key) = value;
}
}
}
@@ -269,6 +280,9 @@ void Dictionary::_unref() const {
if (_p->read_only) {
memdelete(_p->read_only);
}
+ if (_p->typed_fallback) {
+ memdelete(_p->typed_fallback);
+ }
memdelete(_p);
}
_p = nullptr;
@@ -297,6 +311,9 @@ uint32_t Dictionary::recursive_hash(int recursion_count) const {
Array Dictionary::keys() const {
Array varr;
+ if (is_typed_key()) {
+ varr.set_typed(get_typed_key_builtin(), get_typed_key_class_name(), get_typed_key_script());
+ }
if (_p->variant_map.is_empty()) {
return varr;
}
@@ -314,6 +331,9 @@ Array Dictionary::keys() const {
Array Dictionary::values() const {
Array varr;
+ if (is_typed_value()) {
+ varr.set_typed(get_typed_value_builtin(), get_typed_value_class_name(), get_typed_value_script());
+ }
if (_p->variant_map.is_empty()) {
return varr;
}
@@ -329,6 +349,146 @@ Array Dictionary::values() const {
return varr;
}
+void Dictionary::assign(const Dictionary &p_dictionary) {
+ const ContainerTypeValidate &typed_key = _p->typed_key;
+ const ContainerTypeValidate &typed_key_source = p_dictionary._p->typed_key;
+
+ const ContainerTypeValidate &typed_value = _p->typed_value;
+ const ContainerTypeValidate &typed_value_source = p_dictionary._p->typed_value;
+
+ if ((typed_key == typed_key_source || typed_key.type == Variant::NIL || (typed_key_source.type == Variant::OBJECT && typed_key.can_reference(typed_key_source))) &&
+ (typed_value == typed_value_source || typed_value.type == Variant::NIL || (typed_value_source.type == Variant::OBJECT && typed_value.can_reference(typed_value_source)))) {
+ // From same to same or,
+ // from anything to variants or,
+ // from subclasses to base classes.
+ _p->variant_map = p_dictionary._p->variant_map;
+ return;
+ }
+
+ int size = p_dictionary._p->variant_map.size();
+ HashMap<Variant, Variant, VariantHasher, StringLikeVariantComparator> variant_map = HashMap<Variant, Variant, VariantHasher, StringLikeVariantComparator>(size);
+
+ Vector<Variant> key_array;
+ key_array.resize(size);
+ Variant *key_data = key_array.ptrw();
+
+ Vector<Variant> value_array;
+ value_array.resize(size);
+ Variant *value_data = value_array.ptrw();
+
+ if (typed_key == typed_key_source || typed_key.type == Variant::NIL || (typed_key_source.type == Variant::OBJECT && typed_key.can_reference(typed_key_source))) {
+ // From same to same or,
+ // from anything to variants or,
+ // from subclasses to base classes.
+ int i = 0;
+ for (const KeyValue<Variant, Variant> &E : p_dictionary._p->variant_map) {
+ const Variant *key = &E.key;
+ key_data[i++] = *key;
+ }
+ } else if ((typed_key_source.type == Variant::NIL && typed_key.type == Variant::OBJECT) || (typed_key_source.type == Variant::OBJECT && typed_key_source.can_reference(typed_key))) {
+ // From variants to objects or,
+ // from base classes to subclasses.
+ int i = 0;
+ for (const KeyValue<Variant, Variant> &E : p_dictionary._p->variant_map) {
+ const Variant *key = &E.key;
+ if (key->get_type() != Variant::NIL && (key->get_type() != Variant::OBJECT || !typed_key.validate_object(*key, "assign"))) {
+ ERR_FAIL_MSG(vformat(R"(Unable to convert key from "%s" to "%s".)", Variant::get_type_name(key->get_type()), Variant::get_type_name(typed_key.type)));
+ }
+ key_data[i++] = *key;
+ }
+ } else if (typed_key.type == Variant::OBJECT || typed_key_source.type == Variant::OBJECT) {
+ ERR_FAIL_MSG(vformat(R"(Cannot assign contents of "Dictionary[%s, %s]" to "Dictionary[%s, %s]".)", Variant::get_type_name(typed_key_source.type), Variant::get_type_name(typed_value_source.type),
+ Variant::get_type_name(typed_key.type), Variant::get_type_name(typed_value.type)));
+ } else if (typed_key_source.type == Variant::NIL && typed_key.type != Variant::OBJECT) {
+ // From variants to primitives.
+ int i = 0;
+ for (const KeyValue<Variant, Variant> &E : p_dictionary._p->variant_map) {
+ const Variant *key = &E.key;
+ if (key->get_type() == typed_key.type) {
+ key_data[i++] = *key;
+ continue;
+ }
+ if (!Variant::can_convert_strict(key->get_type(), typed_key.type)) {
+ ERR_FAIL_MSG(vformat(R"(Unable to convert key from "%s" to "%s".)", Variant::get_type_name(key->get_type()), Variant::get_type_name(typed_key.type)));
+ }
+ Callable::CallError ce;
+ Variant::construct(typed_key.type, key_data[i++], &key, 1, ce);
+ ERR_FAIL_COND_MSG(ce.error, vformat(R"(Unable to convert key from "%s" to "%s".)", Variant::get_type_name(key->get_type()), Variant::get_type_name(typed_key.type)));
+ }
+ } else if (Variant::can_convert_strict(typed_key_source.type, typed_key.type)) {
+ // From primitives to different convertible primitives.
+ int i = 0;
+ for (const KeyValue<Variant, Variant> &E : p_dictionary._p->variant_map) {
+ const Variant *key = &E.key;
+ Callable::CallError ce;
+ Variant::construct(typed_key.type, key_data[i++], &key, 1, ce);
+ ERR_FAIL_COND_MSG(ce.error, vformat(R"(Unable to convert key from "%s" to "%s".)", Variant::get_type_name(key->get_type()), Variant::get_type_name(typed_key.type)));
+ }
+ } else {
+ ERR_FAIL_MSG(vformat(R"(Cannot assign contents of "Dictionary[%s, %s]" to "Dictionary[%s, %s].)", Variant::get_type_name(typed_key_source.type), Variant::get_type_name(typed_value_source.type),
+ Variant::get_type_name(typed_key.type), Variant::get_type_name(typed_value.type)));
+ }
+
+ if (typed_value == typed_value_source || typed_value.type == Variant::NIL || (typed_value_source.type == Variant::OBJECT && typed_value.can_reference(typed_value_source))) {
+ // From same to same or,
+ // from anything to variants or,
+ // from subclasses to base classes.
+ int i = 0;
+ for (const KeyValue<Variant, Variant> &E : p_dictionary._p->variant_map) {
+ const Variant *value = &E.value;
+ value_data[i++] = *value;
+ }
+ } else if (((typed_value_source.type == Variant::NIL && typed_value.type == Variant::OBJECT) || (typed_value_source.type == Variant::OBJECT && typed_value_source.can_reference(typed_value)))) {
+ // From variants to objects or,
+ // from base classes to subclasses.
+ int i = 0;
+ for (const KeyValue<Variant, Variant> &E : p_dictionary._p->variant_map) {
+ const Variant *value = &E.value;
+ if (value->get_type() != Variant::NIL && (value->get_type() != Variant::OBJECT || !typed_value.validate_object(*value, "assign"))) {
+ ERR_FAIL_MSG(vformat(R"(Unable to convert value at key "%s" from "%s" to "%s".)", key_data[i], Variant::get_type_name(value->get_type()), Variant::get_type_name(typed_value.type)));
+ }
+ value_data[i++] = *value;
+ }
+ } else if (typed_value.type == Variant::OBJECT || typed_value_source.type == Variant::OBJECT) {
+ ERR_FAIL_MSG(vformat(R"(Cannot assign contents of "Dictionary[%s, %s]" to "Dictionary[%s, %s]".)", Variant::get_type_name(typed_key_source.type), Variant::get_type_name(typed_value_source.type),
+ Variant::get_type_name(typed_key.type), Variant::get_type_name(typed_value.type)));
+ } else if (typed_value_source.type == Variant::NIL && typed_value.type != Variant::OBJECT) {
+ // From variants to primitives.
+ int i = 0;
+ for (const KeyValue<Variant, Variant> &E : p_dictionary._p->variant_map) {
+ const Variant *value = &E.value;
+ if (value->get_type() == typed_value.type) {
+ value_data[i++] = *value;
+ continue;
+ }
+ if (!Variant::can_convert_strict(value->get_type(), typed_value.type)) {
+ ERR_FAIL_MSG(vformat(R"(Unable to convert value at key "%s" from "%s" to "%s".)", key_data[i], Variant::get_type_name(value->get_type()), Variant::get_type_name(typed_value.type)));
+ }
+ Callable::CallError ce;
+ Variant::construct(typed_value.type, value_data[i++], &value, 1, ce);
+ ERR_FAIL_COND_MSG(ce.error, vformat(R"(Unable to convert value at key "%s" from "%s" to "%s".)", key_data[i - 1], Variant::get_type_name(value->get_type()), Variant::get_type_name(typed_value.type)));
+ }
+ } else if (Variant::can_convert_strict(typed_value_source.type, typed_value.type)) {
+ // From primitives to different convertible primitives.
+ int i = 0;
+ for (const KeyValue<Variant, Variant> &E : p_dictionary._p->variant_map) {
+ const Variant *value = &E.value;
+ Callable::CallError ce;
+ Variant::construct(typed_value.type, value_data[i++], &value, 1, ce);
+ ERR_FAIL_COND_MSG(ce.error, vformat(R"(Unable to convert value at key "%s" from "%s" to "%s".)", key_data[i - 1], Variant::get_type_name(value->get_type()), Variant::get_type_name(typed_value.type)));
+ }
+ } else {
+ ERR_FAIL_MSG(vformat(R"(Cannot assign contents of "Dictionary[%s, %s]" to "Dictionary[%s, %s].)", Variant::get_type_name(typed_key_source.type), Variant::get_type_name(typed_value_source.type),
+ Variant::get_type_name(typed_key.type), Variant::get_type_name(typed_value.type)));
+ }
+
+ for (int i = 0; i < size; i++) {
+ variant_map.insert(key_data[i], value_data[i]);
+ }
+
+ _p->variant_map = variant_map;
+}
+
const Variant *Dictionary::next(const Variant *p_key) const {
if (p_key == nullptr) {
// caller wants to get the first element
@@ -337,7 +497,9 @@ const Variant *Dictionary::next(const Variant *p_key) const {
}
return nullptr;
}
- HashMap<Variant, Variant, VariantHasher, StringLikeVariantComparator>::Iterator E = _p->variant_map.find(*p_key);
+ Variant key = *p_key;
+ ERR_FAIL_COND_V(!_p->typed_key.validate(key, "next"), nullptr);
+ HashMap<Variant, Variant, VariantHasher, StringLikeVariantComparator>::Iterator E = _p->variant_map.find(key);
if (!E) {
return nullptr;
@@ -367,6 +529,8 @@ bool Dictionary::is_read_only() const {
Dictionary Dictionary::recursive_duplicate(bool p_deep, int recursion_count) const {
Dictionary n;
+ n._p->typed_key = _p->typed_key;
+ n._p->typed_value = _p->typed_value;
if (recursion_count > MAX_RECURSION) {
ERR_PRINT("Max recursion reached");
@@ -387,6 +551,76 @@ Dictionary Dictionary::recursive_duplicate(bool p_deep, int recursion_count) con
return n;
}
+void Dictionary::set_typed(uint32_t p_key_type, const StringName &p_key_class_name, const Variant &p_key_script, uint32_t p_value_type, const StringName &p_value_class_name, const Variant &p_value_script) {
+ ERR_FAIL_COND_MSG(_p->read_only, "Dictionary is in read-only state.");
+ ERR_FAIL_COND_MSG(_p->variant_map.size() > 0, "Type can only be set when dictionary is empty.");
+ ERR_FAIL_COND_MSG(_p->refcount.get() > 1, "Type can only be set when dictionary has no more than one user.");
+ ERR_FAIL_COND_MSG(_p->typed_key.type != Variant::NIL || _p->typed_value.type != Variant::NIL, "Type can only be set once.");
+ ERR_FAIL_COND_MSG((p_key_class_name != StringName() && p_key_type != Variant::OBJECT) || (p_value_class_name != StringName() && p_value_type != Variant::OBJECT), "Class names can only be set for type OBJECT.");
+ Ref<Script> key_script = p_key_script;
+ ERR_FAIL_COND_MSG(key_script.is_valid() && p_key_class_name == StringName(), "Script class can only be set together with base class name.");
+ Ref<Script> value_script = p_value_script;
+ ERR_FAIL_COND_MSG(value_script.is_valid() && p_value_class_name == StringName(), "Script class can only be set together with base class name.");
+
+ _p->typed_key.type = Variant::Type(p_key_type);
+ _p->typed_key.class_name = p_key_class_name;
+ _p->typed_key.script = key_script;
+ _p->typed_key.where = "TypedDictionary.Key";
+
+ _p->typed_value.type = Variant::Type(p_value_type);
+ _p->typed_value.class_name = p_value_class_name;
+ _p->typed_value.script = value_script;
+ _p->typed_value.where = "TypedDictionary.Value";
+}
+
+bool Dictionary::is_typed() const {
+ return is_typed_key() || is_typed_value();
+}
+
+bool Dictionary::is_typed_key() const {
+ return _p->typed_key.type != Variant::NIL;
+}
+
+bool Dictionary::is_typed_value() const {
+ return _p->typed_value.type != Variant::NIL;
+}
+
+bool Dictionary::is_same_typed(const Dictionary &p_other) const {
+ return is_same_typed_key(p_other) && is_same_typed_value(p_other);
+}
+
+bool Dictionary::is_same_typed_key(const Dictionary &p_other) const {
+ return _p->typed_key == p_other._p->typed_key;
+}
+
+bool Dictionary::is_same_typed_value(const Dictionary &p_other) const {
+ return _p->typed_value == p_other._p->typed_value;
+}
+
+uint32_t Dictionary::get_typed_key_builtin() const {
+ return _p->typed_key.type;
+}
+
+uint32_t Dictionary::get_typed_value_builtin() const {
+ return _p->typed_value.type;
+}
+
+StringName Dictionary::get_typed_key_class_name() const {
+ return _p->typed_key.class_name;
+}
+
+StringName Dictionary::get_typed_value_class_name() const {
+ return _p->typed_value.class_name;
+}
+
+Variant Dictionary::get_typed_key_script() const {
+ return _p->typed_key.script;
+}
+
+Variant Dictionary::get_typed_value_script() const {
+ return _p->typed_value.script;
+}
+
void Dictionary::operator=(const Dictionary &p_dictionary) {
if (this == &p_dictionary) {
return;
@@ -398,6 +632,13 @@ const void *Dictionary::id() const {
return _p;
}
+Dictionary::Dictionary(const Dictionary &p_base, uint32_t p_key_type, const StringName &p_key_class_name, const Variant &p_key_script, uint32_t p_value_type, const StringName &p_value_class_name, const Variant &p_value_script) {
+ _p = memnew(DictionaryPrivate);
+ _p->refcount.init();
+ set_typed(p_key_type, p_key_class_name, p_key_script, p_value_type, p_value_class_name, p_value_script);
+ assign(p_base);
+}
+
Dictionary::Dictionary(const Dictionary &p_from) {
_p = nullptr;
_ref(p_from);
diff --git a/core/variant/dictionary.h b/core/variant/dictionary.h
index 67178ee7b7..57fbefc8f2 100644
--- a/core/variant/dictionary.h
+++ b/core/variant/dictionary.h
@@ -80,6 +80,7 @@ public:
uint32_t recursive_hash(int recursion_count) const;
void operator=(const Dictionary &p_dictionary);
+ void assign(const Dictionary &p_dictionary);
const Variant *next(const Variant *p_key = nullptr) const;
Array keys() const;
@@ -88,11 +89,26 @@ public:
Dictionary duplicate(bool p_deep = false) const;
Dictionary recursive_duplicate(bool p_deep, int recursion_count) const;
+ void set_typed(uint32_t p_key_type, const StringName &p_key_class_name, const Variant &p_key_script, uint32_t p_value_type, const StringName &p_value_class_name, const Variant &p_value_script);
+ bool is_typed() const;
+ bool is_typed_key() const;
+ bool is_typed_value() const;
+ bool is_same_typed(const Dictionary &p_other) const;
+ bool is_same_typed_key(const Dictionary &p_other) const;
+ bool is_same_typed_value(const Dictionary &p_other) const;
+ uint32_t get_typed_key_builtin() const;
+ uint32_t get_typed_value_builtin() const;
+ StringName get_typed_key_class_name() const;
+ StringName get_typed_value_class_name() const;
+ Variant get_typed_key_script() const;
+ Variant get_typed_value_script() const;
+
void make_read_only();
bool is_read_only() const;
const void *id() const;
+ Dictionary(const Dictionary &p_base, uint32_t p_key_type, const StringName &p_key_class_name, const Variant &p_key_script, uint32_t p_value_type, const StringName &p_value_class_name, const Variant &p_value_script);
Dictionary(const Dictionary &p_from);
Dictionary();
~Dictionary();
diff --git a/core/variant/typed_dictionary.h b/core/variant/typed_dictionary.h
new file mode 100644
index 0000000000..67fc33b4fc
--- /dev/null
+++ b/core/variant/typed_dictionary.h
@@ -0,0 +1,342 @@
+/**************************************************************************/
+/* typed_dictionary.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 TYPED_DICTIONARY_H
+#define TYPED_DICTIONARY_H
+
+#include "core/object/object.h"
+#include "core/variant/binder_common.h"
+#include "core/variant/dictionary.h"
+#include "core/variant/method_ptrcall.h"
+#include "core/variant/type_info.h"
+#include "core/variant/variant.h"
+
+template <typename K, typename V>
+class TypedDictionary : public Dictionary {
+public:
+ _FORCE_INLINE_ void operator=(const Dictionary &p_dictionary) {
+ ERR_FAIL_COND_MSG(!is_same_typed(p_dictionary), "Cannot assign a dictionary with a different element type.");
+ Dictionary::operator=(p_dictionary);
+ }
+ _FORCE_INLINE_ TypedDictionary(const Variant &p_variant) :
+ TypedDictionary(Dictionary(p_variant)) {
+ }
+ _FORCE_INLINE_ TypedDictionary(const Dictionary &p_dictionary) {
+ set_typed(Variant::OBJECT, K::get_class_static(), Variant(), Variant::OBJECT, V::get_class_static(), Variant());
+ if (is_same_typed(p_dictionary)) {
+ Dictionary::operator=(p_dictionary);
+ } else {
+ assign(p_dictionary);
+ }
+ }
+ _FORCE_INLINE_ TypedDictionary() {
+ set_typed(Variant::OBJECT, K::get_class_static(), Variant(), Variant::OBJECT, V::get_class_static(), Variant());
+ }
+};
+
+template <typename K, typename V>
+struct VariantInternalAccessor<TypedDictionary<K, V>> {
+ static _FORCE_INLINE_ TypedDictionary<K, V> get(const Variant *v) { return *VariantInternal::get_dictionary(v); }
+ static _FORCE_INLINE_ void set(Variant *v, const TypedDictionary<K, V> &p_dictionary) { *VariantInternal::get_dictionary(v) = p_dictionary; }
+};
+
+template <typename K, typename V>
+struct VariantInternalAccessor<const TypedDictionary<K, V> &> {
+ static _FORCE_INLINE_ TypedDictionary<K, V> get(const Variant *v) { return *VariantInternal::get_dictionary(v); }
+ static _FORCE_INLINE_ void set(Variant *v, const TypedDictionary<K, V> &p_dictionary) { *VariantInternal::get_dictionary(v) = p_dictionary; }
+};
+
+template <typename K, typename V>
+struct PtrToArg<TypedDictionary<K, V>> {
+ _FORCE_INLINE_ static TypedDictionary<K, V> convert(const void *p_ptr) {
+ return TypedDictionary<K, V>(*reinterpret_cast<const Dictionary *>(p_ptr));
+ }
+ typedef Dictionary EncodeT;
+ _FORCE_INLINE_ static void encode(TypedDictionary<K, V> p_val, void *p_ptr) {
+ *(Dictionary *)p_ptr = p_val;
+ }
+};
+
+template <typename K, typename V>
+struct PtrToArg<const TypedDictionary<K, V> &> {
+ typedef Dictionary EncodeT;
+ _FORCE_INLINE_ static TypedDictionary<K, V>
+ convert(const void *p_ptr) {
+ return TypedDictionary<K, V>(*reinterpret_cast<const Dictionary *>(p_ptr));
+ }
+};
+
+template <typename K, typename V>
+struct GetTypeInfo<TypedDictionary<K, V>> {
+ static const Variant::Type VARIANT_TYPE = Variant::DICTIONARY;
+ static const GodotTypeInfo::Metadata METADATA = GodotTypeInfo::METADATA_NONE;
+ static inline PropertyInfo get_class_info() {
+ return PropertyInfo(Variant::DICTIONARY, String(), PROPERTY_HINT_DICTIONARY_TYPE, vformat("%s;%s", K::get_class_static(), V::get_class_static()));
+ }
+};
+
+template <typename K, typename V>
+struct GetTypeInfo<const TypedDictionary<K, V> &> {
+ static const Variant::Type VARIANT_TYPE = Variant::DICTIONARY;
+ static const GodotTypeInfo::Metadata METADATA = GodotTypeInfo::METADATA_NONE;
+ static inline PropertyInfo get_class_info() {
+ return PropertyInfo(Variant::DICTIONARY, String(), PROPERTY_HINT_DICTIONARY_TYPE, vformat("%s;%s", K::get_class_static(), V::get_class_static()));
+ }
+};
+
+// Specialization for the rest of the Variant types.
+
+#define MAKE_TYPED_DICTIONARY_WITH_OBJECT(m_type, m_variant_type) \
+ template <typename T> \
+ class TypedDictionary<T, m_type> : public Dictionary { \
+ public: \
+ _FORCE_INLINE_ void operator=(const Dictionary &p_dictionary) { \
+ ERR_FAIL_COND_MSG(!is_same_typed(p_dictionary), "Cannot assign an dictionary with a different element type."); \
+ Dictionary::operator=(p_dictionary); \
+ } \
+ _FORCE_INLINE_ TypedDictionary(const Variant &p_variant) : \
+ TypedDictionary(Dictionary(p_variant)) { \
+ } \
+ _FORCE_INLINE_ TypedDictionary(const Dictionary &p_dictionary) { \
+ set_typed(Variant::OBJECT, T::get_class_static(), Variant(), m_variant_type, StringName(), Variant()); \
+ if (is_same_typed(p_dictionary)) { \
+ Dictionary::operator=(p_dictionary); \
+ } else { \
+ assign(p_dictionary); \
+ } \
+ } \
+ _FORCE_INLINE_ TypedDictionary() { \
+ set_typed(Variant::OBJECT, T::get_class_static(), Variant(), m_variant_type, StringName(), Variant()); \
+ } \
+ }; \
+ template <typename T> \
+ struct GetTypeInfo<TypedDictionary<T, m_type>> { \
+ static const Variant::Type VARIANT_TYPE = Variant::DICTIONARY; \
+ static const GodotTypeInfo::Metadata METADATA = GodotTypeInfo::METADATA_NONE; \
+ static inline PropertyInfo get_class_info() { \
+ return PropertyInfo(Variant::DICTIONARY, String(), PROPERTY_HINT_DICTIONARY_TYPE, \
+ vformat("%s;%s", T::get_class_static(), m_variant_type == Variant::NIL ? "Variant" : Variant::get_type_name(m_variant_type))); \
+ } \
+ }; \
+ template <typename T> \
+ struct GetTypeInfo<const TypedDictionary<T, m_type> &> { \
+ static const Variant::Type VARIANT_TYPE = Variant::DICTIONARY; \
+ static const GodotTypeInfo::Metadata METADATA = GodotTypeInfo::METADATA_NONE; \
+ static inline PropertyInfo get_class_info() { \
+ return PropertyInfo(Variant::DICTIONARY, String(), PROPERTY_HINT_DICTIONARY_TYPE, \
+ vformat("%s;%s", T::get_class_static(), m_variant_type == Variant::NIL ? "Variant" : Variant::get_type_name(m_variant_type))); \
+ } \
+ }; \
+ template <typename T> \
+ class TypedDictionary<m_type, T> : public Dictionary { \
+ public: \
+ _FORCE_INLINE_ void operator=(const Dictionary &p_dictionary) { \
+ ERR_FAIL_COND_MSG(!is_same_typed(p_dictionary), "Cannot assign an dictionary with a different element type."); \
+ Dictionary::operator=(p_dictionary); \
+ } \
+ _FORCE_INLINE_ TypedDictionary(const Variant &p_variant) : \
+ TypedDictionary(Dictionary(p_variant)) { \
+ } \
+ _FORCE_INLINE_ TypedDictionary(const Dictionary &p_dictionary) { \
+ set_typed(m_variant_type, StringName(), Variant(), Variant::OBJECT, T::get_class_static(), Variant()); \
+ if (is_same_typed(p_dictionary)) { \
+ Dictionary::operator=(p_dictionary); \
+ } else { \
+ assign(p_dictionary); \
+ } \
+ } \
+ _FORCE_INLINE_ TypedDictionary() { \
+ set_typed(m_variant_type, StringName(), Variant(), Variant::OBJECT, T::get_class_static(), Variant()); \
+ } \
+ }; \
+ template <typename T> \
+ struct GetTypeInfo<TypedDictionary<m_type, T>> { \
+ static const Variant::Type VARIANT_TYPE = Variant::DICTIONARY; \
+ static const GodotTypeInfo::Metadata METADATA = GodotTypeInfo::METADATA_NONE; \
+ static inline PropertyInfo get_class_info() { \
+ return PropertyInfo(Variant::DICTIONARY, String(), PROPERTY_HINT_DICTIONARY_TYPE, \
+ vformat("%s;%s", m_variant_type == Variant::NIL ? "Variant" : Variant::get_type_name(m_variant_type), T::get_class_static())); \
+ } \
+ }; \
+ template <typename T> \
+ struct GetTypeInfo<const TypedDictionary<m_type, T> &> { \
+ static const Variant::Type VARIANT_TYPE = Variant::DICTIONARY; \
+ static const GodotTypeInfo::Metadata METADATA = GodotTypeInfo::METADATA_NONE; \
+ static inline PropertyInfo get_class_info() { \
+ return PropertyInfo(Variant::DICTIONARY, String(), PROPERTY_HINT_DICTIONARY_TYPE, \
+ vformat("%s;%s", m_variant_type == Variant::NIL ? "Variant" : Variant::get_type_name(m_variant_type), T::get_class_static())); \
+ } \
+ };
+
+#define MAKE_TYPED_DICTIONARY_EXPANDED(m_type_key, m_variant_type_key, m_type_value, m_variant_type_value) \
+ template <> \
+ class TypedDictionary<m_type_key, m_type_value> : public Dictionary { \
+ public: \
+ _FORCE_INLINE_ void operator=(const Dictionary &p_dictionary) { \
+ ERR_FAIL_COND_MSG(!is_same_typed(p_dictionary), "Cannot assign an dictionary with a different element type."); \
+ Dictionary::operator=(p_dictionary); \
+ } \
+ _FORCE_INLINE_ TypedDictionary(const Variant &p_variant) : \
+ TypedDictionary(Dictionary(p_variant)) { \
+ } \
+ _FORCE_INLINE_ TypedDictionary(const Dictionary &p_dictionary) { \
+ set_typed(m_variant_type_key, StringName(), Variant(), m_variant_type_value, StringName(), Variant()); \
+ if (is_same_typed(p_dictionary)) { \
+ Dictionary::operator=(p_dictionary); \
+ } else { \
+ assign(p_dictionary); \
+ } \
+ } \
+ _FORCE_INLINE_ TypedDictionary() { \
+ set_typed(m_variant_type_key, StringName(), Variant(), m_variant_type_value, StringName(), Variant()); \
+ } \
+ }; \
+ template <> \
+ struct GetTypeInfo<TypedDictionary<m_type_key, m_type_value>> { \
+ static const Variant::Type VARIANT_TYPE = Variant::DICTIONARY; \
+ static const GodotTypeInfo::Metadata METADATA = GodotTypeInfo::METADATA_NONE; \
+ static inline PropertyInfo get_class_info() { \
+ return PropertyInfo(Variant::DICTIONARY, String(), PROPERTY_HINT_DICTIONARY_TYPE, \
+ vformat("%s;%s", m_variant_type_key == Variant::NIL ? "Variant" : Variant::get_type_name(m_variant_type_key), \
+ m_variant_type_value == Variant::NIL ? "Variant" : Variant::get_type_name(m_variant_type_value))); \
+ } \
+ }; \
+ template <> \
+ struct GetTypeInfo<const TypedDictionary<m_type_key, m_type_value> &> { \
+ static const Variant::Type VARIANT_TYPE = Variant::DICTIONARY; \
+ static const GodotTypeInfo::Metadata METADATA = GodotTypeInfo::METADATA_NONE; \
+ static inline PropertyInfo get_class_info() { \
+ return PropertyInfo(Variant::DICTIONARY, String(), PROPERTY_HINT_DICTIONARY_TYPE, \
+ vformat("%s;%s", m_variant_type_key == Variant::NIL ? "Variant" : Variant::get_type_name(m_variant_type_key), \
+ m_variant_type_value == Variant::NIL ? "Variant" : Variant::get_type_name(m_variant_type_value))); \
+ } \
+ };
+
+#define MAKE_TYPED_DICTIONARY_NIL(m_type, m_variant_type) \
+ MAKE_TYPED_DICTIONARY_WITH_OBJECT(m_type, m_variant_type) \
+ MAKE_TYPED_DICTIONARY_EXPANDED(m_type, m_variant_type, bool, Variant::BOOL) \
+ MAKE_TYPED_DICTIONARY_EXPANDED(m_type, m_variant_type, uint8_t, Variant::INT) \
+ MAKE_TYPED_DICTIONARY_EXPANDED(m_type, m_variant_type, int8_t, Variant::INT) \
+ MAKE_TYPED_DICTIONARY_EXPANDED(m_type, m_variant_type, uint16_t, Variant::INT) \
+ MAKE_TYPED_DICTIONARY_EXPANDED(m_type, m_variant_type, int16_t, Variant::INT) \
+ MAKE_TYPED_DICTIONARY_EXPANDED(m_type, m_variant_type, uint32_t, Variant::INT) \
+ MAKE_TYPED_DICTIONARY_EXPANDED(m_type, m_variant_type, int32_t, Variant::INT) \
+ MAKE_TYPED_DICTIONARY_EXPANDED(m_type, m_variant_type, uint64_t, Variant::INT) \
+ MAKE_TYPED_DICTIONARY_EXPANDED(m_type, m_variant_type, int64_t, Variant::INT) \
+ MAKE_TYPED_DICTIONARY_EXPANDED(m_type, m_variant_type, float, Variant::FLOAT) \
+ MAKE_TYPED_DICTIONARY_EXPANDED(m_type, m_variant_type, double, Variant::FLOAT) \
+ MAKE_TYPED_DICTIONARY_EXPANDED(m_type, m_variant_type, String, Variant::STRING) \
+ MAKE_TYPED_DICTIONARY_EXPANDED(m_type, m_variant_type, Vector2, Variant::VECTOR2) \
+ MAKE_TYPED_DICTIONARY_EXPANDED(m_type, m_variant_type, Vector2i, Variant::VECTOR2I) \
+ MAKE_TYPED_DICTIONARY_EXPANDED(m_type, m_variant_type, Rect2, Variant::RECT2) \
+ MAKE_TYPED_DICTIONARY_EXPANDED(m_type, m_variant_type, Rect2i, Variant::RECT2I) \
+ MAKE_TYPED_DICTIONARY_EXPANDED(m_type, m_variant_type, Vector3, Variant::VECTOR3) \
+ MAKE_TYPED_DICTIONARY_EXPANDED(m_type, m_variant_type, Vector3i, Variant::VECTOR3I) \
+ MAKE_TYPED_DICTIONARY_EXPANDED(m_type, m_variant_type, Transform2D, Variant::TRANSFORM2D) \
+ MAKE_TYPED_DICTIONARY_EXPANDED(m_type, m_variant_type, Plane, Variant::PLANE) \
+ MAKE_TYPED_DICTIONARY_EXPANDED(m_type, m_variant_type, Quaternion, Variant::QUATERNION) \
+ MAKE_TYPED_DICTIONARY_EXPANDED(m_type, m_variant_type, AABB, Variant::AABB) \
+ MAKE_TYPED_DICTIONARY_EXPANDED(m_type, m_variant_type, Basis, Variant::BASIS) \
+ MAKE_TYPED_DICTIONARY_EXPANDED(m_type, m_variant_type, Transform3D, Variant::TRANSFORM3D) \
+ MAKE_TYPED_DICTIONARY_EXPANDED(m_type, m_variant_type, Color, Variant::COLOR) \
+ MAKE_TYPED_DICTIONARY_EXPANDED(m_type, m_variant_type, StringName, Variant::STRING_NAME) \
+ MAKE_TYPED_DICTIONARY_EXPANDED(m_type, m_variant_type, NodePath, Variant::NODE_PATH) \
+ MAKE_TYPED_DICTIONARY_EXPANDED(m_type, m_variant_type, RID, Variant::RID) \
+ MAKE_TYPED_DICTIONARY_EXPANDED(m_type, m_variant_type, Callable, Variant::CALLABLE) \
+ MAKE_TYPED_DICTIONARY_EXPANDED(m_type, m_variant_type, Signal, Variant::SIGNAL) \
+ MAKE_TYPED_DICTIONARY_EXPANDED(m_type, m_variant_type, Dictionary, Variant::DICTIONARY) \
+ MAKE_TYPED_DICTIONARY_EXPANDED(m_type, m_variant_type, Array, Variant::ARRAY) \
+ MAKE_TYPED_DICTIONARY_EXPANDED(m_type, m_variant_type, PackedByteArray, Variant::PACKED_BYTE_ARRAY) \
+ MAKE_TYPED_DICTIONARY_EXPANDED(m_type, m_variant_type, PackedInt32Array, Variant::PACKED_INT32_ARRAY) \
+ MAKE_TYPED_DICTIONARY_EXPANDED(m_type, m_variant_type, PackedInt64Array, Variant::PACKED_INT64_ARRAY) \
+ MAKE_TYPED_DICTIONARY_EXPANDED(m_type, m_variant_type, PackedFloat32Array, Variant::PACKED_FLOAT32_ARRAY) \
+ MAKE_TYPED_DICTIONARY_EXPANDED(m_type, m_variant_type, PackedFloat64Array, Variant::PACKED_FLOAT64_ARRAY) \
+ MAKE_TYPED_DICTIONARY_EXPANDED(m_type, m_variant_type, PackedStringArray, Variant::PACKED_STRING_ARRAY) \
+ MAKE_TYPED_DICTIONARY_EXPANDED(m_type, m_variant_type, PackedVector2Array, Variant::PACKED_VECTOR2_ARRAY) \
+ MAKE_TYPED_DICTIONARY_EXPANDED(m_type, m_variant_type, PackedVector3Array, Variant::PACKED_VECTOR3_ARRAY) \
+ MAKE_TYPED_DICTIONARY_EXPANDED(m_type, m_variant_type, PackedColorArray, Variant::PACKED_COLOR_ARRAY) \
+ MAKE_TYPED_DICTIONARY_EXPANDED(m_type, m_variant_type, PackedVector4Array, Variant::PACKED_VECTOR4_ARRAY) \
+ MAKE_TYPED_DICTIONARY_EXPANDED(m_type, m_variant_type, IPAddress, Variant::STRING)
+
+#define MAKE_TYPED_DICTIONARY(m_type, m_variant_type) \
+ MAKE_TYPED_DICTIONARY_EXPANDED(m_type, m_variant_type, Variant, Variant::NIL) \
+ MAKE_TYPED_DICTIONARY_NIL(m_type, m_variant_type)
+
+MAKE_TYPED_DICTIONARY_NIL(Variant, Variant::NIL)
+MAKE_TYPED_DICTIONARY(bool, Variant::BOOL)
+MAKE_TYPED_DICTIONARY(uint8_t, Variant::INT)
+MAKE_TYPED_DICTIONARY(int8_t, Variant::INT)
+MAKE_TYPED_DICTIONARY(uint16_t, Variant::INT)
+MAKE_TYPED_DICTIONARY(int16_t, Variant::INT)
+MAKE_TYPED_DICTIONARY(uint32_t, Variant::INT)
+MAKE_TYPED_DICTIONARY(int32_t, Variant::INT)
+MAKE_TYPED_DICTIONARY(uint64_t, Variant::INT)
+MAKE_TYPED_DICTIONARY(int64_t, Variant::INT)
+MAKE_TYPED_DICTIONARY(float, Variant::FLOAT)
+MAKE_TYPED_DICTIONARY(double, Variant::FLOAT)
+MAKE_TYPED_DICTIONARY(String, Variant::STRING)
+MAKE_TYPED_DICTIONARY(Vector2, Variant::VECTOR2)
+MAKE_TYPED_DICTIONARY(Vector2i, Variant::VECTOR2I)
+MAKE_TYPED_DICTIONARY(Rect2, Variant::RECT2)
+MAKE_TYPED_DICTIONARY(Rect2i, Variant::RECT2I)
+MAKE_TYPED_DICTIONARY(Vector3, Variant::VECTOR3)
+MAKE_TYPED_DICTIONARY(Vector3i, Variant::VECTOR3I)
+MAKE_TYPED_DICTIONARY(Transform2D, Variant::TRANSFORM2D)
+MAKE_TYPED_DICTIONARY(Plane, Variant::PLANE)
+MAKE_TYPED_DICTIONARY(Quaternion, Variant::QUATERNION)
+MAKE_TYPED_DICTIONARY(AABB, Variant::AABB)
+MAKE_TYPED_DICTIONARY(Basis, Variant::BASIS)
+MAKE_TYPED_DICTIONARY(Transform3D, Variant::TRANSFORM3D)
+MAKE_TYPED_DICTIONARY(Color, Variant::COLOR)
+MAKE_TYPED_DICTIONARY(StringName, Variant::STRING_NAME)
+MAKE_TYPED_DICTIONARY(NodePath, Variant::NODE_PATH)
+MAKE_TYPED_DICTIONARY(RID, Variant::RID)
+MAKE_TYPED_DICTIONARY(Callable, Variant::CALLABLE)
+MAKE_TYPED_DICTIONARY(Signal, Variant::SIGNAL)
+MAKE_TYPED_DICTIONARY(Dictionary, Variant::DICTIONARY)
+MAKE_TYPED_DICTIONARY(Array, Variant::ARRAY)
+MAKE_TYPED_DICTIONARY(PackedByteArray, Variant::PACKED_BYTE_ARRAY)
+MAKE_TYPED_DICTIONARY(PackedInt32Array, Variant::PACKED_INT32_ARRAY)
+MAKE_TYPED_DICTIONARY(PackedInt64Array, Variant::PACKED_INT64_ARRAY)
+MAKE_TYPED_DICTIONARY(PackedFloat32Array, Variant::PACKED_FLOAT32_ARRAY)
+MAKE_TYPED_DICTIONARY(PackedFloat64Array, Variant::PACKED_FLOAT64_ARRAY)
+MAKE_TYPED_DICTIONARY(PackedStringArray, Variant::PACKED_STRING_ARRAY)
+MAKE_TYPED_DICTIONARY(PackedVector2Array, Variant::PACKED_VECTOR2_ARRAY)
+MAKE_TYPED_DICTIONARY(PackedVector3Array, Variant::PACKED_VECTOR3_ARRAY)
+MAKE_TYPED_DICTIONARY(PackedColorArray, Variant::PACKED_COLOR_ARRAY)
+MAKE_TYPED_DICTIONARY(PackedVector4Array, Variant::PACKED_VECTOR4_ARRAY)
+MAKE_TYPED_DICTIONARY(IPAddress, Variant::STRING)
+
+#undef MAKE_TYPED_DICTIONARY
+#undef MAKE_TYPED_DICTIONARY_NIL
+#undef MAKE_TYPED_DICTIONARY_EXPANDED
+#undef MAKE_TYPED_DICTIONARY_WITH_OBJECT
+
+#endif // TYPED_DICTIONARY_H
diff --git a/core/variant/variant.cpp b/core/variant/variant.cpp
index c1ef31c784..186643b024 100644
--- a/core/variant/variant.cpp
+++ b/core/variant/variant.cpp
@@ -1072,13 +1072,6 @@ bool Variant::is_null() const {
}
}
-bool Variant::initialize_ref(Object *p_object) {
- RefCounted *ref_counted = const_cast<RefCounted *>(static_cast<const RefCounted *>(p_object));
- if (!ref_counted->init_ref()) {
- return false;
- }
- return true;
-}
void Variant::reference(const Variant &p_variant) {
switch (type) {
case NIL:
@@ -2120,7 +2113,7 @@ Variant::operator ::RID() const {
}
#endif
Callable::CallError ce;
- Variant ret = _get_obj().obj->callp(CoreStringName(get_rid), nullptr, 0, ce);
+ const Variant ret = _get_obj().obj->callp(CoreStringName(get_rid), nullptr, 0, ce);
if (ce.error == Callable::CallError::CALL_OK && ret.get_type() == Variant::RID) {
return ret;
}
diff --git a/core/variant/variant.h b/core/variant/variant.h
index 1cb3580c01..d4e4b330cd 100644
--- a/core/variant/variant.h
+++ b/core/variant/variant.h
@@ -254,7 +254,6 @@ private:
} _data alignas(8);
void reference(const Variant &p_variant);
- static bool initialize_ref(Object *p_object);
void _clear_internal();
diff --git a/core/variant/variant_call.cpp b/core/variant/variant_call.cpp
index 83f1f981b3..2da94de875 100644
--- a/core/variant/variant_call.cpp
+++ b/core/variant/variant_call.cpp
@@ -2254,6 +2254,7 @@ static void _register_variant_builtin_methods_misc() {
bind_method(Dictionary, size, sarray(), varray());
bind_method(Dictionary, is_empty, sarray(), varray());
bind_method(Dictionary, clear, sarray(), varray());
+ bind_method(Dictionary, assign, sarray("dictionary"), varray());
bind_method(Dictionary, merge, sarray("dictionary", "overwrite"), varray(false));
bind_method(Dictionary, merged, sarray("dictionary", "overwrite"), varray(false));
bind_method(Dictionary, has, sarray("key"), varray());
@@ -2266,6 +2267,18 @@ static void _register_variant_builtin_methods_misc() {
bind_method(Dictionary, duplicate, sarray("deep"), varray(false));
bind_method(Dictionary, get, sarray("key", "default"), varray(Variant()));
bind_method(Dictionary, get_or_add, sarray("key", "default"), varray(Variant()));
+ bind_method(Dictionary, is_typed, sarray(), varray());
+ bind_method(Dictionary, is_typed_key, sarray(), varray());
+ bind_method(Dictionary, is_typed_value, sarray(), varray());
+ bind_method(Dictionary, is_same_typed, sarray("dictionary"), varray());
+ bind_method(Dictionary, is_same_typed_key, sarray("dictionary"), varray());
+ bind_method(Dictionary, is_same_typed_value, sarray("dictionary"), varray());
+ bind_method(Dictionary, get_typed_key_builtin, sarray(), varray());
+ bind_method(Dictionary, get_typed_value_builtin, sarray(), varray());
+ bind_method(Dictionary, get_typed_key_class_name, sarray(), varray());
+ bind_method(Dictionary, get_typed_value_class_name, sarray(), varray());
+ bind_method(Dictionary, get_typed_key_script, sarray(), varray());
+ bind_method(Dictionary, get_typed_value_script, sarray(), varray());
bind_method(Dictionary, make_read_only, sarray(), varray());
bind_method(Dictionary, is_read_only, sarray(), varray());
bind_method(Dictionary, recursive_equal, sarray("dictionary", "recursion_count"), varray());
diff --git a/core/variant/variant_construct.cpp b/core/variant/variant_construct.cpp
index 1edae407c2..fb75a874e7 100644
--- a/core/variant/variant_construct.cpp
+++ b/core/variant/variant_construct.cpp
@@ -198,6 +198,7 @@ void Variant::_register_variant_constructors() {
add_constructor<VariantConstructNoArgs<Dictionary>>(sarray());
add_constructor<VariantConstructor<Dictionary, Dictionary>>(sarray("from"));
+ add_constructor<VariantConstructorTypedDictionary>(sarray("base", "key_type", "key_class_name", "key_script", "value_type", "value_class_name", "value_script"));
add_constructor<VariantConstructNoArgs<Array>>(sarray());
add_constructor<VariantConstructor<Array, Array>>(sarray("from"));
diff --git a/core/variant/variant_construct.h b/core/variant/variant_construct.h
index 5afdb884f6..68210a9451 100644
--- a/core/variant/variant_construct.h
+++ b/core/variant/variant_construct.h
@@ -232,7 +232,7 @@ template <typename T>
class VariantConstructorFromString {
public:
static void construct(Variant &r_ret, const Variant **p_args, Callable::CallError &r_error) {
- if (p_args[0]->get_type() != Variant::STRING) {
+ if (!p_args[0]->is_string()) {
r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT;
r_error.argument = 0;
r_error.expected = Variant::STRING;
@@ -240,7 +240,7 @@ public:
}
VariantTypeChanger<T>::change(&r_ret);
- const String &src_str = *VariantGetInternalPtr<String>::get_ptr(p_args[0]);
+ const String src_str = *p_args[0];
if (r_ret.get_type() == Variant::Type::INT) {
r_ret = src_str.to_int();
@@ -400,6 +400,112 @@ public:
}
};
+class VariantConstructorTypedDictionary {
+public:
+ static void construct(Variant &r_ret, const Variant **p_args, Callable::CallError &r_error) {
+ if (p_args[0]->get_type() != Variant::DICTIONARY) {
+ r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT;
+ r_error.argument = 0;
+ r_error.expected = Variant::DICTIONARY;
+ return;
+ }
+
+ if (p_args[1]->get_type() != Variant::INT) {
+ r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT;
+ r_error.argument = 1;
+ r_error.expected = Variant::INT;
+ return;
+ }
+
+ if (p_args[2]->get_type() != Variant::STRING_NAME) {
+ r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT;
+ r_error.argument = 2;
+ r_error.expected = Variant::STRING_NAME;
+ return;
+ }
+
+ if (p_args[4]->get_type() != Variant::INT) {
+ r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT;
+ r_error.argument = 4;
+ r_error.expected = Variant::INT;
+ return;
+ }
+
+ if (p_args[5]->get_type() != Variant::STRING_NAME) {
+ r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT;
+ r_error.argument = 5;
+ r_error.expected = Variant::STRING_NAME;
+ return;
+ }
+
+ const Dictionary &base_dict = *VariantGetInternalPtr<Dictionary>::get_ptr(p_args[0]);
+ const uint32_t key_type = p_args[1]->operator uint32_t();
+ const StringName &key_class_name = *VariantGetInternalPtr<StringName>::get_ptr(p_args[2]);
+ const uint32_t value_type = p_args[4]->operator uint32_t();
+ const StringName &value_class_name = *VariantGetInternalPtr<StringName>::get_ptr(p_args[5]);
+ r_ret = Dictionary(base_dict, key_type, key_class_name, *p_args[3], value_type, value_class_name, *p_args[6]);
+ }
+
+ static inline void validated_construct(Variant *r_ret, const Variant **p_args) {
+ const Dictionary &base_dict = *VariantGetInternalPtr<Dictionary>::get_ptr(p_args[0]);
+ const uint32_t key_type = p_args[1]->operator uint32_t();
+ const StringName &key_class_name = *VariantGetInternalPtr<StringName>::get_ptr(p_args[2]);
+ const uint32_t value_type = p_args[4]->operator uint32_t();
+ const StringName &value_class_name = *VariantGetInternalPtr<StringName>::get_ptr(p_args[5]);
+ *r_ret = Dictionary(base_dict, key_type, key_class_name, *p_args[3], value_type, value_class_name, *p_args[6]);
+ }
+
+ static void ptr_construct(void *base, const void **p_args) {
+ const Dictionary &base_dict = PtrToArg<Dictionary>::convert(p_args[0]);
+ const uint32_t key_type = PtrToArg<uint32_t>::convert(p_args[1]);
+ const StringName &key_class_name = PtrToArg<StringName>::convert(p_args[2]);
+ const Variant &key_script = PtrToArg<Variant>::convert(p_args[3]);
+ const uint32_t value_type = PtrToArg<uint32_t>::convert(p_args[4]);
+ const StringName &value_class_name = PtrToArg<StringName>::convert(p_args[5]);
+ const Variant &value_script = PtrToArg<Variant>::convert(p_args[6]);
+ Dictionary dst_arr = Dictionary(base_dict, key_type, key_class_name, key_script, value_type, value_class_name, value_script);
+
+ PtrConstruct<Dictionary>::construct(dst_arr, base);
+ }
+
+ static int get_argument_count() {
+ return 7;
+ }
+
+ static Variant::Type get_argument_type(int p_arg) {
+ switch (p_arg) {
+ case 0: {
+ return Variant::DICTIONARY;
+ } break;
+ case 1: {
+ return Variant::INT;
+ } break;
+ case 2: {
+ return Variant::STRING_NAME;
+ } break;
+ case 3: {
+ return Variant::NIL;
+ } break;
+ case 4: {
+ return Variant::INT;
+ } break;
+ case 5: {
+ return Variant::STRING_NAME;
+ } break;
+ case 6: {
+ return Variant::NIL;
+ } break;
+ default: {
+ return Variant::NIL;
+ } break;
+ }
+ }
+
+ static Variant::Type get_base_type() {
+ return Variant::DICTIONARY;
+ }
+};
+
class VariantConstructorTypedArray {
public:
static void construct(Variant &r_ret, const Variant **p_args, Callable::CallError &r_error) {
@@ -417,7 +523,7 @@ public:
return;
}
- if (p_args[2]->get_type() != Variant::STRING_NAME) {
+ if (!p_args[2]->is_string()) {
r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT;
r_error.argument = 2;
r_error.expected = Variant::STRING_NAME;
@@ -426,8 +532,7 @@ public:
const Array &base_arr = *VariantGetInternalPtr<Array>::get_ptr(p_args[0]);
const uint32_t type = p_args[1]->operator uint32_t();
- const StringName &class_name = *VariantGetInternalPtr<StringName>::get_ptr(p_args[2]);
- r_ret = Array(base_arr, type, class_name, *p_args[3]);
+ r_ret = Array(base_arr, type, *p_args[2], *p_args[3]);
}
static inline void validated_construct(Variant *r_ret, const Variant **p_args) {
diff --git a/core/variant/variant_internal.h b/core/variant/variant_internal.h
index c52ab6917b..58a45c0a1f 100644
--- a/core/variant/variant_internal.h
+++ b/core/variant/variant_internal.h
@@ -125,10 +125,6 @@ public:
}
}
- _FORCE_INLINE_ static bool initialize_ref(Object *object) {
- return Variant::initialize_ref(object);
- }
-
// Atomic types.
_FORCE_INLINE_ static bool *get_bool(Variant *v) { return &v->_data._bool; }
_FORCE_INLINE_ static const bool *get_bool(const Variant *v) { return &v->_data._bool; }
diff --git a/core/variant/variant_parser.cpp b/core/variant/variant_parser.cpp
index 9a0dd712ed..f5f96456d3 100644
--- a/core/variant/variant_parser.cpp
+++ b/core/variant/variant_parser.cpp
@@ -1140,6 +1140,146 @@ Error VariantParser::parse_value(Token &token, Variant &value, Stream *p_stream,
return ERR_PARSE_ERROR;
}
}
+ } else if (id == "Dictionary") {
+ Error err = OK;
+
+ get_token(p_stream, token, line, r_err_str);
+ if (token.type != TK_BRACKET_OPEN) {
+ r_err_str = "Expected '['";
+ return ERR_PARSE_ERROR;
+ }
+
+ get_token(p_stream, token, line, r_err_str);
+ if (token.type != TK_IDENTIFIER) {
+ r_err_str = "Expected type identifier for key";
+ return ERR_PARSE_ERROR;
+ }
+
+ static HashMap<StringName, Variant::Type> builtin_types;
+ 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;
+ }
+ }
+
+ Dictionary dict;
+ Variant::Type key_type = Variant::NIL;
+ StringName key_class_name;
+ Variant key_script;
+ bool got_comma_token = false;
+ if (builtin_types.has(token.value)) {
+ key_type = builtin_types.get(token.value);
+ } else if (token.value == "Resource" || token.value == "SubResource" || token.value == "ExtResource") {
+ Variant resource;
+ err = parse_value(token, resource, p_stream, line, r_err_str, p_res_parser);
+ if (err) {
+ if (token.value == "Resource" && err == ERR_PARSE_ERROR && r_err_str == "Expected '('" && token.type == TK_COMMA) {
+ err = OK;
+ r_err_str = String();
+ key_type = Variant::OBJECT;
+ key_class_name = token.value;
+ got_comma_token = true;
+ } else {
+ return err;
+ }
+ } else {
+ Ref<Script> script = resource;
+ if (script.is_valid() && script->is_valid()) {
+ key_type = Variant::OBJECT;
+ key_class_name = script->get_instance_base_type();
+ key_script = script;
+ }
+ }
+ } else if (ClassDB::class_exists(token.value)) {
+ key_type = Variant::OBJECT;
+ key_class_name = token.value;
+ }
+
+ if (!got_comma_token) {
+ get_token(p_stream, token, line, r_err_str);
+ if (token.type != TK_COMMA) {
+ r_err_str = "Expected ',' after key type";
+ return ERR_PARSE_ERROR;
+ }
+ }
+
+ get_token(p_stream, token, line, r_err_str);
+ if (token.type != TK_IDENTIFIER) {
+ r_err_str = "Expected type identifier for value";
+ return ERR_PARSE_ERROR;
+ }
+
+ Variant::Type value_type = Variant::NIL;
+ StringName value_class_name;
+ Variant value_script;
+ bool got_bracket_token = false;
+ if (builtin_types.has(token.value)) {
+ value_type = builtin_types.get(token.value);
+ } else if (token.value == "Resource" || token.value == "SubResource" || token.value == "ExtResource") {
+ Variant resource;
+ err = parse_value(token, resource, p_stream, line, r_err_str, p_res_parser);
+ if (err) {
+ if (token.value == "Resource" && err == ERR_PARSE_ERROR && r_err_str == "Expected '('" && token.type == TK_BRACKET_CLOSE) {
+ err = OK;
+ r_err_str = String();
+ value_type = Variant::OBJECT;
+ value_class_name = token.value;
+ got_comma_token = true;
+ } else {
+ return err;
+ }
+ } else {
+ Ref<Script> script = resource;
+ if (script.is_valid() && script->is_valid()) {
+ value_type = Variant::OBJECT;
+ value_class_name = script->get_instance_base_type();
+ value_script = script;
+ }
+ }
+ } else if (ClassDB::class_exists(token.value)) {
+ value_type = Variant::OBJECT;
+ value_class_name = token.value;
+ }
+
+ if (key_type != Variant::NIL || value_type != Variant::NIL) {
+ dict.set_typed(key_type, key_class_name, key_script, value_type, value_class_name, value_script);
+ }
+
+ if (!got_bracket_token) {
+ get_token(p_stream, token, line, r_err_str);
+ if (token.type != TK_BRACKET_CLOSE) {
+ r_err_str = "Expected ']'";
+ return ERR_PARSE_ERROR;
+ }
+ }
+
+ get_token(p_stream, token, line, r_err_str);
+ if (token.type != TK_PARENTHESIS_OPEN) {
+ r_err_str = "Expected '('";
+ return ERR_PARSE_ERROR;
+ }
+
+ get_token(p_stream, token, line, r_err_str);
+ if (token.type != TK_CURLY_BRACKET_OPEN) {
+ r_err_str = "Expected '{'";
+ return ERR_PARSE_ERROR;
+ }
+
+ Dictionary values;
+ err = _parse_dictionary(values, p_stream, line, r_err_str, p_res_parser);
+ if (err) {
+ return err;
+ }
+
+ get_token(p_stream, token, line, r_err_str);
+ if (token.type != TK_PARENTHESIS_CLOSE) {
+ r_err_str = "Expected ')'";
+ return ERR_PARSE_ERROR;
+ }
+
+ dict.assign(values);
+
+ value = dict;
} else if (id == "Array") {
Error err = OK;
@@ -2036,40 +2176,109 @@ Error VariantWriter::write(const Variant &p_variant, StoreStringFunc p_store_str
case Variant::DICTIONARY: {
Dictionary dict = p_variant;
+
+ if (dict.is_typed()) {
+ p_store_string_func(p_store_string_ud, "Dictionary[");
+
+ Variant::Type key_builtin_type = (Variant::Type)dict.get_typed_key_builtin();
+ StringName key_class_name = dict.get_typed_key_class_name();
+ Ref<Script> key_script = dict.get_typed_key_script();
+
+ if (key_script.is_valid()) {
+ String resource_text;
+ if (p_encode_res_func) {
+ resource_text = p_encode_res_func(p_encode_res_ud, key_script);
+ }
+ if (resource_text.is_empty() && key_script->get_path().is_resource_file()) {
+ resource_text = "Resource(\"" + key_script->get_path() + "\")";
+ }
+
+ if (!resource_text.is_empty()) {
+ p_store_string_func(p_store_string_ud, resource_text);
+ } else {
+ ERR_PRINT("Failed to encode a path to a custom script for a dictionary key type.");
+ p_store_string_func(p_store_string_ud, key_class_name);
+ }
+ } else if (key_class_name != StringName()) {
+ p_store_string_func(p_store_string_ud, key_class_name);
+ } else if (key_builtin_type == Variant::NIL) {
+ p_store_string_func(p_store_string_ud, "Variant");
+ } else {
+ p_store_string_func(p_store_string_ud, Variant::get_type_name(key_builtin_type));
+ }
+
+ p_store_string_func(p_store_string_ud, ", ");
+
+ Variant::Type value_builtin_type = (Variant::Type)dict.get_typed_value_builtin();
+ StringName value_class_name = dict.get_typed_value_class_name();
+ Ref<Script> value_script = dict.get_typed_value_script();
+
+ if (value_script.is_valid()) {
+ String resource_text;
+ if (p_encode_res_func) {
+ resource_text = p_encode_res_func(p_encode_res_ud, value_script);
+ }
+ if (resource_text.is_empty() && value_script->get_path().is_resource_file()) {
+ resource_text = "Resource(\"" + value_script->get_path() + "\")";
+ }
+
+ if (!resource_text.is_empty()) {
+ p_store_string_func(p_store_string_ud, resource_text);
+ } else {
+ ERR_PRINT("Failed to encode a path to a custom script for a dictionary value type.");
+ p_store_string_func(p_store_string_ud, value_class_name);
+ }
+ } else if (value_class_name != StringName()) {
+ p_store_string_func(p_store_string_ud, value_class_name);
+ } else if (value_builtin_type == Variant::NIL) {
+ p_store_string_func(p_store_string_ud, "Variant");
+ } else {
+ p_store_string_func(p_store_string_ud, Variant::get_type_name(value_builtin_type));
+ }
+
+ p_store_string_func(p_store_string_ud, "](");
+ }
+
if (unlikely(p_recursion_count > MAX_RECURSION)) {
ERR_PRINT("Max recursion reached");
p_store_string_func(p_store_string_ud, "{}");
} else {
- p_recursion_count++;
-
List<Variant> keys;
dict.get_key_list(&keys);
keys.sort();
- if (keys.is_empty()) { // Avoid unnecessary line break.
+ if (keys.is_empty()) {
+ // Avoid unnecessary line break.
p_store_string_func(p_store_string_ud, "{}");
- break;
- }
+ } else {
+ p_recursion_count++;
- p_store_string_func(p_store_string_ud, "{\n");
- for (List<Variant>::Element *E = keys.front(); E; E = E->next()) {
- write(E->get(), p_store_string_func, p_store_string_ud, p_encode_res_func, p_encode_res_ud, p_recursion_count, p_compat);
- p_store_string_func(p_store_string_ud, ": ");
- write(dict[E->get()], p_store_string_func, p_store_string_ud, p_encode_res_func, p_encode_res_ud, p_recursion_count, p_compat);
- if (E->next()) {
- p_store_string_func(p_store_string_ud, ",\n");
- } else {
- p_store_string_func(p_store_string_ud, "\n");
+ p_store_string_func(p_store_string_ud, "{\n");
+
+ for (List<Variant>::Element *E = keys.front(); E; E = E->next()) {
+ write(E->get(), p_store_string_func, p_store_string_ud, p_encode_res_func, p_encode_res_ud, p_recursion_count, p_compat);
+ p_store_string_func(p_store_string_ud, ": ");
+ write(dict[E->get()], p_store_string_func, p_store_string_ud, p_encode_res_func, p_encode_res_ud, p_recursion_count, p_compat);
+ if (E->next()) {
+ p_store_string_func(p_store_string_ud, ",\n");
+ } else {
+ p_store_string_func(p_store_string_ud, "\n");
+ }
}
+
+ p_store_string_func(p_store_string_ud, "}");
}
+ }
- p_store_string_func(p_store_string_ud, "}");
+ if (dict.is_typed()) {
+ p_store_string_func(p_store_string_ud, ")");
}
} break;
case Variant::ARRAY: {
Array array = p_variant;
- if (array.get_typed_builtin() != Variant::NIL) {
+
+ if (array.is_typed()) {
p_store_string_func(p_store_string_ud, "Array[");
Variant::Type builtin_type = (Variant::Type)array.get_typed_builtin();
@@ -2107,6 +2316,7 @@ Error VariantWriter::write(const Variant &p_variant, StoreStringFunc p_store_str
p_recursion_count++;
p_store_string_func(p_store_string_ud, "[");
+
bool first = true;
for (const Variant &var : array) {
if (first) {
@@ -2120,7 +2330,7 @@ Error VariantWriter::write(const Variant &p_variant, StoreStringFunc p_store_str
p_store_string_func(p_store_string_ud, "]");
}
- if (array.get_typed_builtin() != Variant::NIL) {
+ if (array.is_typed()) {
p_store_string_func(p_store_string_ud, ")");
}
} break;
diff --git a/core/variant/variant_setget.cpp b/core/variant/variant_setget.cpp
index 48176163a1..b60ff83cf1 100644
--- a/core/variant/variant_setget.cpp
+++ b/core/variant/variant_setget.cpp
@@ -703,6 +703,50 @@ struct VariantIndexedSetGet_Array {
static uint64_t get_indexed_size(const Variant *base) { return 0; }
};
+struct VariantIndexedSetGet_Dictionary {
+ static void get(const Variant *base, int64_t index, Variant *value, bool *oob) {
+ const Variant *ptr = VariantGetInternalPtr<Dictionary>::get_ptr(base)->getptr(index);
+ if (!ptr) {
+ *oob = true;
+ return;
+ }
+ *value = *ptr;
+ *oob = false;
+ }
+ static void ptr_get(const void *base, int64_t index, void *member) {
+ // Avoid ptrconvert for performance.
+ const Dictionary &v = *reinterpret_cast<const Dictionary *>(base);
+ const Variant *ptr = v.getptr(index);
+ NULL_TEST(ptr);
+ PtrToArg<Variant>::encode(*ptr, member);
+ }
+ static void set(Variant *base, int64_t index, const Variant *value, bool *valid, bool *oob) {
+ if (VariantGetInternalPtr<Dictionary>::get_ptr(base)->is_read_only()) {
+ *valid = false;
+ *oob = true;
+ return;
+ }
+ (*VariantGetInternalPtr<Dictionary>::get_ptr(base))[index] = *value;
+ *oob = false;
+ *valid = true;
+ }
+ static void validated_set(Variant *base, int64_t index, const Variant *value, bool *oob) {
+ if (VariantGetInternalPtr<Dictionary>::get_ptr(base)->is_read_only()) {
+ *oob = true;
+ return;
+ }
+ (*VariantGetInternalPtr<Dictionary>::get_ptr(base))[index] = *value;
+ *oob = false;
+ }
+ static void ptr_set(void *base, int64_t index, const void *member) {
+ Dictionary &v = *reinterpret_cast<Dictionary *>(base);
+ v[index] = PtrToArg<Variant>::convert(member);
+ }
+ static Variant::Type get_index_type() { return Variant::NIL; }
+ static uint32_t get_index_usage() { return PROPERTY_USAGE_DEFAULT; }
+ static uint64_t get_indexed_size(const Variant *base) { return VariantGetInternalPtr<Dictionary>::get_ptr(base)->size(); }
+};
+
struct VariantIndexedSetGet_String {
static void get(const Variant *base, int64_t index, Variant *value, bool *oob) {
int64_t length = VariantGetInternalPtr<String>::get_ptr(base)->length();
@@ -789,51 +833,6 @@ struct VariantIndexedSetGet_String {
static uint64_t get_indexed_size(const Variant *base) { return VariantInternal::get_string(base)->length(); }
};
-#define INDEXED_SETGET_STRUCT_DICT(m_base_type) \
- struct VariantIndexedSetGet_##m_base_type { \
- static void get(const Variant *base, int64_t index, Variant *value, bool *oob) { \
- const Variant *ptr = VariantGetInternalPtr<m_base_type>::get_ptr(base)->getptr(index); \
- if (!ptr) { \
- *oob = true; \
- return; \
- } \
- *value = *ptr; \
- *oob = false; \
- } \
- static void ptr_get(const void *base, int64_t index, void *member) { \
- /* avoid ptrconvert for performance*/ \
- const m_base_type &v = *reinterpret_cast<const m_base_type *>(base); \
- const Variant *ptr = v.getptr(index); \
- NULL_TEST(ptr); \
- PtrToArg<Variant>::encode(*ptr, member); \
- } \
- static void set(Variant *base, int64_t index, const Variant *value, bool *valid, bool *oob) { \
- if (VariantGetInternalPtr<m_base_type>::get_ptr(base)->is_read_only()) { \
- *valid = false; \
- *oob = true; \
- return; \
- } \
- (*VariantGetInternalPtr<m_base_type>::get_ptr(base))[index] = *value; \
- *oob = false; \
- *valid = true; \
- } \
- static void validated_set(Variant *base, int64_t index, const Variant *value, bool *oob) { \
- if (VariantGetInternalPtr<m_base_type>::get_ptr(base)->is_read_only()) { \
- *oob = true; \
- return; \
- } \
- (*VariantGetInternalPtr<m_base_type>::get_ptr(base))[index] = *value; \
- *oob = false; \
- } \
- static void ptr_set(void *base, int64_t index, const void *member) { \
- m_base_type &v = *reinterpret_cast<m_base_type *>(base); \
- v[index] = PtrToArg<Variant>::convert(member); \
- } \
- static Variant::Type get_index_type() { return Variant::NIL; } \
- static uint32_t get_index_usage() { return PROPERTY_USAGE_DEFAULT; } \
- static uint64_t get_indexed_size(const Variant *base) { return VariantGetInternalPtr<m_base_type>::get_ptr(base)->size(); } \
- };
-
INDEXED_SETGET_STRUCT_BULTIN_NUMERIC(Vector2, double, real_t, 2)
INDEXED_SETGET_STRUCT_BULTIN_NUMERIC(Vector2i, int64_t, int32_t, 2)
INDEXED_SETGET_STRUCT_BULTIN_NUMERIC(Vector3, double, real_t, 3)
@@ -858,8 +857,6 @@ INDEXED_SETGET_STRUCT_TYPED(PackedStringArray, String)
INDEXED_SETGET_STRUCT_TYPED(PackedColorArray, Color)
INDEXED_SETGET_STRUCT_TYPED(PackedVector4Array, Vector4)
-INDEXED_SETGET_STRUCT_DICT(Dictionary)
-
struct VariantIndexedSetterGetterInfo {
void (*setter)(Variant *base, int64_t index, const Variant *value, bool *valid, bool *oob) = nullptr;
void (*getter)(const Variant *base, int64_t index, Variant *value, bool *oob) = nullptr;
@@ -1288,8 +1285,8 @@ void Variant::get_property_list(List<PropertyInfo> *p_list) const {
List<Variant> keys;
dic->get_key_list(&keys);
for (const Variant &E : keys) {
- if (E.get_type() == Variant::STRING) {
- p_list->push_back(PropertyInfo(Variant::STRING, E));
+ if (E.is_string()) {
+ p_list->push_back(PropertyInfo(dic->get_valid(E).get_type(), E));
}
}
} else if (type == OBJECT) {
diff --git a/core/variant/variant_utility.cpp b/core/variant/variant_utility.cpp
index 7534a154a1..384fe6c4a6 100644
--- a/core/variant/variant_utility.cpp
+++ b/core/variant/variant_utility.cpp
@@ -452,12 +452,14 @@ Variant VariantUtilityFunctions::lerp(const Variant &from, const Variant &to, do
case Variant::QUATERNION:
case Variant::BASIS:
case Variant::COLOR:
+ case Variant::TRANSFORM2D:
+ case Variant::TRANSFORM3D:
break;
default:
r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT;
r_error.argument = 0;
r_error.expected = Variant::NIL;
- return R"(Argument "from" must be "int", "float", "Vector2", "Vector3", "Vector4", "Quaternion", "Basis, or "Color".)";
+ return R"(Argument "from" must be "int", "float", "Vector2", "Vector3", "Vector4", "Color", "Quaternion", "Basis", "Transform2D", or "Transform3D".)";
}
if (from.get_type() != to.get_type()) {
@@ -490,6 +492,12 @@ Variant VariantUtilityFunctions::lerp(const Variant &from, const Variant &to, do
case Variant::BASIS: {
return VariantInternalAccessor<Basis>::get(&from).slerp(VariantInternalAccessor<Basis>::get(&to), weight);
} break;
+ case Variant::TRANSFORM2D: {
+ return VariantInternalAccessor<Transform2D>::get(&from).interpolate_with(VariantInternalAccessor<Transform2D>::get(&to), weight);
+ } break;
+ case Variant::TRANSFORM3D: {
+ return VariantInternalAccessor<Transform3D>::get(&from).interpolate_with(VariantInternalAccessor<Transform3D>::get(&to), weight);
+ } break;
case Variant::COLOR: {
return VariantInternalAccessor<Color>::get(&from).lerp(VariantInternalAccessor<Color>::get(&to), weight);
} break;
diff --git a/doc/class.xsd b/doc/class.xsd
index f0e1241053..28e02e870d 100644
--- a/doc/class.xsd
+++ b/doc/class.xsd
@@ -246,6 +246,8 @@
<xs:attribute type="xs:string" name="data_type" />
<xs:attribute type="xs:string" name="type" />
<xs:attribute type="xs:string" name="default" use="optional" />
+ <xs:attribute type="xs:string" name="deprecated" use="optional" />
+ <xs:attribute type="xs:string" name="experimental" use="optional" />
<xs:attribute type="xs:string" name="keywords" use="optional" />
</xs:extension>
</xs:simpleContent>
diff --git a/doc/classes/@GlobalScope.xml b/doc/classes/@GlobalScope.xml
index 63f5947280..f222cbc969 100644
--- a/doc/classes/@GlobalScope.xml
+++ b/doc/classes/@GlobalScope.xml
@@ -90,7 +90,7 @@
<param index="0" name="from" type="float" />
<param index="1" name="to" type="float" />
<description>
- Returns the difference between the two angles, in the range of [code][-PI, +PI][/code]. When [param from] and [param to] are opposite, returns [code]-PI[/code] if [param from] is smaller than [param to], or [code]PI[/code] otherwise.
+ Returns the difference between the two angles (in radians), in the range of [code][-PI, +PI][/code]. When [param from] and [param to] are opposite, returns [code]-PI[/code] if [param from] is smaller than [param to], or [code]PI[/code] otherwise.
</description>
</method>
<method name="asin">
@@ -621,13 +621,13 @@
<param index="1" name="to" type="Variant" />
<param index="2" name="weight" type="Variant" />
<description>
- Linearly interpolates between two values by the factor defined in [param weight]. To perform interpolation, [param weight] should be between [code]0.0[/code] and [code]1.0[/code] (inclusive). However, values outside this range are allowed and can be used to perform [i]extrapolation[/i]. If this is not desired, use [method clamp] on the result of this function.
- Both [param from] and [param to] must be the same type. Supported types: [int], [float], [Vector2], [Vector3], [Vector4], [Color], [Quaternion], [Basis].
+ Linearly interpolates between two values by the factor defined in [param weight]. To perform interpolation, [param weight] should be between [code]0.0[/code] and [code]1.0[/code] (inclusive). However, values outside this range are allowed and can be used to perform [i]extrapolation[/i]. If this is not desired, use [method clampf] to limit [param weight].
+ Both [param from] and [param to] must be the same type. Supported types: [int], [float], [Vector2], [Vector3], [Vector4], [Color], [Quaternion], [Basis], [Transform2D], [Transform3D].
[codeblock]
lerp(0, 4, 0.75) # Returns 3.0
[/codeblock]
See also [method inverse_lerp] which performs the reverse of this operation. To perform eased interpolation with [method lerp], combine it with [method ease] or [method smoothstep]. See also [method remap] to map a continuous series of values to another.
- [b]Note:[/b] For better type safety, use [method lerpf], [method Vector2.lerp], [method Vector3.lerp], [method Vector4.lerp], [method Color.lerp], [method Quaternion.slerp] or [method Basis.slerp].
+ [b]Note:[/b] For better type safety, use [method lerpf], [method Vector2.lerp], [method Vector3.lerp], [method Vector4.lerp], [method Color.lerp], [method Quaternion.slerp], [method Basis.slerp], [method Transform2D.interpolate_with], or [method Transform3D.interpolate_with].
</description>
</method>
<method name="lerp_angle" keywords="interpolate">
@@ -2915,6 +2915,9 @@
<constant name="PROPERTY_HINT_ARRAY_TYPE" value="31" enum="PropertyHint">
Hints that a property is an [Array] with the stored type specified in the hint string.
</constant>
+ <constant name="PROPERTY_HINT_DICTIONARY_TYPE" value="38" enum="PropertyHint">
+ Hints that a property is a [Dictionary] with the stored types specified in the hint string.
+ </constant>
<constant name="PROPERTY_HINT_LOCALE_ID" value="32" enum="PropertyHint">
Hints that a string property is a locale code. Editing it will show a locale dialog for picking language and country.
</constant>
@@ -2930,7 +2933,7 @@
<constant name="PROPERTY_HINT_PASSWORD" value="36" enum="PropertyHint">
Hints that a string property is a password, and every character is replaced with the secret character.
</constant>
- <constant name="PROPERTY_HINT_MAX" value="38" enum="PropertyHint">
+ <constant name="PROPERTY_HINT_MAX" value="39" enum="PropertyHint">
Represents the size of the [enum PropertyHint] enum.
</constant>
<constant name="PROPERTY_USAGE_NONE" value="0" enum="PropertyUsageFlags" is_bitfield="true">
diff --git a/doc/classes/AStar2D.xml b/doc/classes/AStar2D.xml
index cfb7d00861..f3a1f6b985 100644
--- a/doc/classes/AStar2D.xml
+++ b/doc/classes/AStar2D.xml
@@ -22,7 +22,7 @@
<method name="_estimate_cost" qualifiers="virtual const">
<return type="float" />
<param index="0" name="from_id" type="int" />
- <param index="1" name="to_id" type="int" />
+ <param index="1" name="end_id" type="int" />
<description>
Called when estimating the cost between a point and the path's ending point.
Note that this function is hidden in the default [AStar2D] class.
diff --git a/doc/classes/AStar3D.xml b/doc/classes/AStar3D.xml
index 4448698c32..dad77cc7a8 100644
--- a/doc/classes/AStar3D.xml
+++ b/doc/classes/AStar3D.xml
@@ -51,7 +51,7 @@
<method name="_estimate_cost" qualifiers="virtual const">
<return type="float" />
<param index="0" name="from_id" type="int" />
- <param index="1" name="to_id" type="int" />
+ <param index="1" name="end_id" type="int" />
<description>
Called when estimating the cost between a point and the path's ending point.
Note that this function is hidden in the default [AStar3D] class.
diff --git a/doc/classes/AStarGrid2D.xml b/doc/classes/AStarGrid2D.xml
index 5ccc0c8f2a..2ee61bd939 100644
--- a/doc/classes/AStarGrid2D.xml
+++ b/doc/classes/AStarGrid2D.xml
@@ -41,7 +41,7 @@
<method name="_estimate_cost" qualifiers="virtual const">
<return type="float" />
<param index="0" name="from_id" type="Vector2i" />
- <param index="1" name="to_id" type="Vector2i" />
+ <param index="1" name="end_id" type="Vector2i" />
<description>
Called when estimating the cost between a point and the path's ending point.
Note that this function is hidden in the default [AStarGrid2D] class.
@@ -81,6 +81,13 @@
If there is no valid path to the target, and [param allow_partial_path] is [code]true[/code], returns a path to the point closest to the target that can be reached.
</description>
</method>
+ <method name="get_point_data_in_region" qualifiers="const">
+ <return type="Dictionary[]" />
+ <param index="0" name="region" type="Rect2i" />
+ <description>
+ Returns an array of dictionaries with point data ([code]id[/code]: [Vector2i], [code]position[/code]: [Vector2], [code]solid[/code]: [bool], [code]weight_scale[/code]: [float]) within a [param region].
+ </description>
+ </method>
<method name="get_point_path">
<return type="PackedVector2Array" />
<param index="0" name="from_id" type="Vector2i" />
diff --git a/doc/classes/AnimationMixer.xml b/doc/classes/AnimationMixer.xml
index dc1bee4336..d762ffa5a6 100644
--- a/doc/classes/AnimationMixer.xml
+++ b/doc/classes/AnimationMixer.xml
@@ -376,7 +376,19 @@
</constant>
<constant name="ANIMATION_CALLBACK_MODE_DISCRETE_FORCE_CONTINUOUS" value="2" enum="AnimationCallbackModeDiscrete">
Always treat the [constant Animation.UPDATE_DISCRETE] track value as [constant Animation.UPDATE_CONTINUOUS] with [constant Animation.INTERPOLATION_NEAREST]. This is the default behavior for [AnimationTree].
- If a value track has non-numeric type key values, it is internally converted to use [constant ANIMATION_CALLBACK_MODE_DISCRETE_RECESSIVE] with [constant Animation.UPDATE_DISCRETE].
+ If a value track has un-interpolatable type key values, it is internally converted to use [constant ANIMATION_CALLBACK_MODE_DISCRETE_RECESSIVE] with [constant Animation.UPDATE_DISCRETE].
+ Un-interpolatable type list:
+ - [constant @GlobalScope.TYPE_NIL]
+ - [constant @GlobalScope.TYPE_NODE_PATH]
+ - [constant @GlobalScope.TYPE_RID]
+ - [constant @GlobalScope.TYPE_OBJECT]
+ - [constant @GlobalScope.TYPE_CALLABLE]
+ - [constant @GlobalScope.TYPE_SIGNAL]
+ - [constant @GlobalScope.TYPE_DICTIONARY]
+ - [constant @GlobalScope.TYPE_PACKED_BYTE_ARRAY]
+ [constant @GlobalScope.TYPE_BOOL] and [constant @GlobalScope.TYPE_INT] are treated as [constant @GlobalScope.TYPE_FLOAT] during blending and rounded when the result is retrieved.
+ It is same for arrays and vectors with them such as [constant @GlobalScope.TYPE_PACKED_INT32_ARRAY] or [constant @GlobalScope.TYPE_VECTOR2I], they are treated as [constant @GlobalScope.TYPE_PACKED_FLOAT32_ARRAY] or [constant @GlobalScope.TYPE_VECTOR2]. Also note that for arrays, the size is also interpolated.
+ [constant @GlobalScope.TYPE_STRING] and [constant @GlobalScope.TYPE_STRING_NAME] are interpolated between character codes and lengths, but note that there is a difference in algorithm between interpolation between keys and interpolation by blending.
</constant>
</constants>
</class>
diff --git a/doc/classes/AudioStreamPlayer.xml b/doc/classes/AudioStreamPlayer.xml
index eecbb05540..93680de21e 100644
--- a/doc/classes/AudioStreamPlayer.xml
+++ b/doc/classes/AudioStreamPlayer.xml
@@ -77,7 +77,7 @@
<member name="playback_type" type="int" setter="set_playback_type" getter="get_playback_type" enum="AudioServer.PlaybackType" default="0" experimental="">
The playback type of the stream player. If set other than to the default value, it will force that playback type.
</member>
- <member name="playing" type="bool" setter="_set_playing" getter="is_playing" default="false">
+ <member name="playing" type="bool" setter="set_playing" getter="is_playing" default="false">
If [code]true[/code], this node is playing sounds. Setting this property has the same effect as [method play] and [method stop].
</member>
<member name="stream" type="AudioStream" setter="set_stream" getter="get_stream">
diff --git a/doc/classes/AudioStreamPlayer2D.xml b/doc/classes/AudioStreamPlayer2D.xml
index a3206ba1d6..71d2e1f0db 100644
--- a/doc/classes/AudioStreamPlayer2D.xml
+++ b/doc/classes/AudioStreamPlayer2D.xml
@@ -81,7 +81,7 @@
<member name="playback_type" type="int" setter="set_playback_type" getter="get_playback_type" enum="AudioServer.PlaybackType" default="0" experimental="">
The playback type of the stream player. If set other than to the default value, it will force that playback type.
</member>
- <member name="playing" type="bool" setter="_set_playing" getter="is_playing" default="false">
+ <member name="playing" type="bool" setter="set_playing" getter="is_playing" default="false">
If [code]true[/code], audio is playing or is queued to be played (see [method play]).
</member>
<member name="stream" type="AudioStream" setter="set_stream" getter="get_stream">
diff --git a/doc/classes/AudioStreamPlayer3D.xml b/doc/classes/AudioStreamPlayer3D.xml
index bf02caffb4..a14c53bbfb 100644
--- a/doc/classes/AudioStreamPlayer3D.xml
+++ b/doc/classes/AudioStreamPlayer3D.xml
@@ -102,7 +102,7 @@
<member name="playback_type" type="int" setter="set_playback_type" getter="get_playback_type" enum="AudioServer.PlaybackType" default="0" experimental="">
The playback type of the stream player. If set other than to the default value, it will force that playback type.
</member>
- <member name="playing" type="bool" setter="_set_playing" getter="is_playing" default="false">
+ <member name="playing" type="bool" setter="set_playing" getter="is_playing" default="false">
If [code]true[/code], audio is playing or is queued to be played (see [method play]).
</member>
<member name="stream" type="AudioStream" setter="set_stream" getter="get_stream">
diff --git a/doc/classes/AudioStreamWAV.xml b/doc/classes/AudioStreamWAV.xml
index 8a28514ed6..8d882deaee 100644
--- a/doc/classes/AudioStreamWAV.xml
+++ b/doc/classes/AudioStreamWAV.xml
@@ -15,7 +15,7 @@
<return type="int" enum="Error" />
<param index="0" name="path" type="String" />
<description>
- Saves the AudioStreamWAV as a WAV file to [param path]. Samples with IMA ADPCM or QOA formats can't be saved.
+ Saves the AudioStreamWAV as a WAV file to [param path]. Samples with IMA ADPCM or Quite OK Audio formats can't be saved.
[b]Note:[/b] A [code].wav[/code] extension is automatically appended to [param path] if it is missing.
</description>
</method>
@@ -23,19 +23,20 @@
<members>
<member name="data" type="PackedByteArray" setter="set_data" getter="get_data" default="PackedByteArray()">
Contains the audio data in bytes.
- [b]Note:[/b] This property expects signed PCM8 data. To convert unsigned PCM8 to signed PCM8, subtract 128 from each byte.
+ [b]Note:[/b] If [member format] is set to [constant FORMAT_8_BITS], this property expects signed 8-bit PCM data. To convert from unsigned 8-bit PCM, subtract 128 from each byte.
+ [b]Note:[/b] If [member format] is set to [constant FORMAT_QOA], this property expects data from a full QOA file.
</member>
<member name="format" type="int" setter="set_format" getter="get_format" enum="AudioStreamWAV.Format" default="0">
Audio format. See [enum Format] constants for values.
</member>
<member name="loop_begin" type="int" setter="set_loop_begin" getter="get_loop_begin" default="0">
- The loop start point (in number of samples, relative to the beginning of the stream). This information will be imported automatically from the WAV file if present.
+ The loop start point (in number of samples, relative to the beginning of the stream).
</member>
<member name="loop_end" type="int" setter="set_loop_end" getter="get_loop_end" default="0">
- The loop end point (in number of samples, relative to the beginning of the stream). This information will be imported automatically from the WAV file if present.
+ The loop end point (in number of samples, relative to the beginning of the stream).
</member>
<member name="loop_mode" type="int" setter="set_loop_mode" getter="get_loop_mode" enum="AudioStreamWAV.LoopMode" default="0">
- The loop mode. This information will be imported automatically from the WAV file if present. See [enum LoopMode] constants for values.
+ The loop mode. See [enum LoopMode] constants for values.
</member>
<member name="mix_rate" type="int" setter="set_mix_rate" getter="get_mix_rate" default="44100">
The sample rate for mixing this audio. Higher values require more storage space, but result in better quality.
@@ -48,16 +49,16 @@
</members>
<constants>
<constant name="FORMAT_8_BITS" value="0" enum="Format">
- 8-bit audio codec.
+ 8-bit PCM audio codec.
</constant>
<constant name="FORMAT_16_BITS" value="1" enum="Format">
- 16-bit audio codec.
+ 16-bit PCM audio codec.
</constant>
<constant name="FORMAT_IMA_ADPCM" value="2" enum="Format">
- Audio is compressed using IMA ADPCM.
+ Audio is lossily compressed as IMA ADPCM.
</constant>
<constant name="FORMAT_QOA" value="3" enum="Format">
- Audio is compressed as QOA ([url=https://qoaformat.org/]Quite OK Audio[/url]).
+ Audio is lossily compressed as [url=https://qoaformat.org/]Quite OK Audio[/url].
</constant>
<constant name="LOOP_DISABLED" value="0" enum="LoopMode">
Audio does not loop.
diff --git a/doc/classes/CPUParticles3D.xml b/doc/classes/CPUParticles3D.xml
index 27726ff8a2..d7770f2cd5 100644
--- a/doc/classes/CPUParticles3D.xml
+++ b/doc/classes/CPUParticles3D.xml
@@ -168,6 +168,10 @@
<member name="emission_ring_axis" type="Vector3" setter="set_emission_ring_axis" getter="get_emission_ring_axis">
The axis of the ring when using the emitter [constant EMISSION_SHAPE_RING].
</member>
+ <member name="emission_ring_cone_angle" type="float" setter="set_emission_ring_cone_angle" getter="get_emission_ring_cone_angle">
+ The angle of the cone when using the emitter [constant EMISSION_SHAPE_RING]. The default angle of 90 degrees results in a ring, while an angle of 0 degrees results in a cone. Intermediate values will result in a ring where one end is larger than the other.
+ [b]Note:[/b] Depending on [member emission_ring_height], the angle may be clamped if the ring's end is reached to form a perfect cone.
+ </member>
<member name="emission_ring_height" type="float" setter="set_emission_ring_height" getter="get_emission_ring_height">
The height of the ring when using the emitter [constant EMISSION_SHAPE_RING].
</member>
diff --git a/doc/classes/CanvasItem.xml b/doc/classes/CanvasItem.xml
index 186ee1b9c4..0a0223c550 100644
--- a/doc/classes/CanvasItem.xml
+++ b/doc/classes/CanvasItem.xml
@@ -96,6 +96,7 @@
<param index="3" name="texture" type="Texture2D" default="null" />
<description>
Draws a colored polygon of any number of points, convex or concave. Unlike [method draw_polygon], a single color must be specified for the whole polygon.
+ [b]Note:[/b] If you frequently redraw the same polygon with a large number of vertices, consider pre-calculating the triangulation with [method Geometry2D.triangulate_polygon] and using [method draw_mesh], [method draw_multimesh], or [method RenderingServer.canvas_item_add_triangle_array].
</description>
</method>
<method name="draw_dashed_line">
@@ -251,6 +252,7 @@
<param index="3" name="texture" type="Texture2D" default="null" />
<description>
Draws a solid polygon of any number of points, convex or concave. Unlike [method draw_colored_polygon], each point's color can be changed individually. See also [method draw_polyline] and [method draw_polyline_colors]. If you need more flexibility (such as being able to use bones), use [method RenderingServer.canvas_item_add_triangle_array] instead.
+ [b]Note:[/b] If you frequently redraw the same polygon with a large number of vertices, consider pre-calculating the triangulation with [method Geometry2D.triangulate_polygon] and using [method draw_mesh], [method draw_multimesh], or [method RenderingServer.canvas_item_add_triangle_array].
</description>
</method>
<method name="draw_polyline">
diff --git a/doc/classes/Control.xml b/doc/classes/Control.xml
index a0c76a3ad6..927ab9ae0e 100644
--- a/doc/classes/Control.xml
+++ b/doc/classes/Control.xml
@@ -1333,13 +1333,14 @@
Tells the parent [Container] to align the node with its end, either the bottom or the right edge. It is mutually exclusive with [constant SIZE_FILL] and other shrink size flags, but can be used with [constant SIZE_EXPAND] in some containers. Use with [member size_flags_horizontal] and [member size_flags_vertical].
</constant>
<constant name="MOUSE_FILTER_STOP" value="0" enum="MouseFilter">
- The control will receive mouse movement input events and mouse button input events if clicked on through [method _gui_input]. And the control will receive the [signal mouse_entered] and [signal mouse_exited] signals. These events are automatically marked as handled, and they will not propagate further to other controls. This also results in blocking signals in other controls.
+ The control will receive mouse movement input events and mouse button input events if clicked on through [method _gui_input]. The control will also receive the [signal mouse_entered] and [signal mouse_exited] signals. These events are automatically marked as handled, and they will not propagate further to other controls. This also results in blocking signals in other controls.
</constant>
<constant name="MOUSE_FILTER_PASS" value="1" enum="MouseFilter">
- The control will receive mouse movement input events and mouse button input events if clicked on through [method _gui_input]. And the control will receive the [signal mouse_entered] and [signal mouse_exited] signals. If this control does not handle the event, the parent control (if any) will be considered, and so on until there is no more parent control to potentially handle it. This also allows signals to fire in other controls. If no control handled it, the event will be passed to [method Node._shortcut_input] for further processing.
+ The control will receive mouse movement input events and mouse button input events if clicked on through [method _gui_input]. The control will also receive the [signal mouse_entered] and [signal mouse_exited] signals.
+ If this control does not handle the event, the event will propagate up to its parent control if it has one. The event is bubbled up the node hierarchy until it reaches a non-[CanvasItem], a control with [constant MOUSE_FILTER_STOP], or a [CanvasItem] with [member CanvasItem.top_level] enabled. This will allow signals to fire in all controls it reaches. If no control handled it, the event will be passed to [method Node._shortcut_input] for further processing.
</constant>
<constant name="MOUSE_FILTER_IGNORE" value="2" enum="MouseFilter">
- The control will not receive mouse movement input events and mouse button input events if clicked on through [method _gui_input]. The control will also not receive the [signal mouse_entered] nor [signal mouse_exited] signals. This will not block other controls from receiving these events or firing the signals. Ignored events will not be handled automatically.
+ The control will not receive any mouse movement input events nor mouse button input events through [method _gui_input]. The control will also not receive the [signal mouse_entered] nor [signal mouse_exited] signals. This will not block other controls from receiving these events or firing the signals. Ignored events will not be handled automatically. If a child has [constant MOUSE_FILTER_PASS] and an event was passed to this control, the event will further propagate up to the control's parent.
[b]Note:[/b] If the control has received [signal mouse_entered] but not [signal mouse_exited], changing the [member mouse_filter] to [constant MOUSE_FILTER_IGNORE] will cause [signal mouse_exited] to be emitted.
</constant>
<constant name="GROW_DIRECTION_BEGIN" value="0" enum="GrowDirection">
diff --git a/doc/classes/Dictionary.xml b/doc/classes/Dictionary.xml
index b8b4fc7b08..feb2a07e4a 100644
--- a/doc/classes/Dictionary.xml
+++ b/doc/classes/Dictionary.xml
@@ -150,6 +150,19 @@
</constructor>
<constructor name="Dictionary">
<return type="Dictionary" />
+ <param index="0" name="base" type="Dictionary" />
+ <param index="1" name="key_type" type="int" />
+ <param index="2" name="key_class_name" type="StringName" />
+ <param index="3" name="key_script" type="Variant" />
+ <param index="4" name="value_type" type="int" />
+ <param index="5" name="value_class_name" type="StringName" />
+ <param index="6" name="value_script" type="Variant" />
+ <description>
+ Creates a typed dictionary from the [param base] dictionary. A typed dictionary can only contain keys and values of the given types, or that inherit from the given classes, as described by this constructor's parameters.
+ </description>
+ </constructor>
+ <constructor name="Dictionary">
+ <return type="Dictionary" />
<param index="0" name="from" type="Dictionary" />
<description>
Returns the same dictionary as [param from]. If you need a copy of the dictionary, use [method duplicate].
@@ -157,6 +170,13 @@
</constructor>
</constructors>
<methods>
+ <method name="assign">
+ <return type="void" />
+ <param index="0" name="dictionary" type="Dictionary" />
+ <description>
+ Assigns elements of another [param dictionary] into the dictionary. Resizes the dictionary to match [param dictionary]. Performs type conversions if the dictionary is typed.
+ </description>
+ </method>
<method name="clear">
<return type="void" />
<description>
@@ -202,6 +222,42 @@
Gets a value and ensures the key is set. If the [param key] exists in the dictionary, this behaves like [method get]. Otherwise, the [param default] value is inserted into the dictionary and returned.
</description>
</method>
+ <method name="get_typed_key_builtin" qualifiers="const">
+ <return type="int" />
+ <description>
+ Returns the built-in [Variant] type of the typed dictionary's keys as a [enum Variant.Type] constant. If the keys are not typed, returns [constant TYPE_NIL]. See also [method is_typed_key].
+ </description>
+ </method>
+ <method name="get_typed_key_class_name" qualifiers="const">
+ <return type="StringName" />
+ <description>
+ Returns the [b]built-in[/b] class name of the typed dictionary's keys, if the built-in [Variant] type is [constant TYPE_OBJECT]. Otherwise, returns an empty [StringName]. See also [method is_typed_key] and [method Object.get_class].
+ </description>
+ </method>
+ <method name="get_typed_key_script" qualifiers="const">
+ <return type="Variant" />
+ <description>
+ Returns the [Script] instance associated with this typed dictionary's keys, or [code]null[/code] if it does not exist. See also [method is_typed_key].
+ </description>
+ </method>
+ <method name="get_typed_value_builtin" qualifiers="const">
+ <return type="int" />
+ <description>
+ Returns the built-in [Variant] type of the typed dictionary's values as a [enum Variant.Type] constant. If the values are not typed, returns [constant TYPE_NIL]. See also [method is_typed_value].
+ </description>
+ </method>
+ <method name="get_typed_value_class_name" qualifiers="const">
+ <return type="StringName" />
+ <description>
+ Returns the [b]built-in[/b] class name of the typed dictionary's values, if the built-in [Variant] type is [constant TYPE_OBJECT]. Otherwise, returns an empty [StringName]. See also [method is_typed_value] and [method Object.get_class].
+ </description>
+ </method>
+ <method name="get_typed_value_script" qualifiers="const">
+ <return type="Variant" />
+ <description>
+ Returns the [Script] instance associated with this typed dictionary's values, or [code]null[/code] if it does not exist. See also [method is_typed_value].
+ </description>
+ </method>
<method name="has" qualifiers="const">
<return type="bool" />
<param index="0" name="key" type="Variant" />
@@ -284,6 +340,45 @@
Returns [code]true[/code] if the dictionary is read-only. See [method make_read_only]. Dictionaries are automatically read-only if declared with [code]const[/code] keyword.
</description>
</method>
+ <method name="is_same_typed" qualifiers="const">
+ <return type="bool" />
+ <param index="0" name="dictionary" type="Dictionary" />
+ <description>
+ Returns [code]true[/code] if the dictionary is typed the same as [param dictionary].
+ </description>
+ </method>
+ <method name="is_same_typed_key" qualifiers="const">
+ <return type="bool" />
+ <param index="0" name="dictionary" type="Dictionary" />
+ <description>
+ Returns [code]true[/code] if the dictionary's keys are typed the same as [param dictionary]'s keys.
+ </description>
+ </method>
+ <method name="is_same_typed_value" qualifiers="const">
+ <return type="bool" />
+ <param index="0" name="dictionary" type="Dictionary" />
+ <description>
+ Returns [code]true[/code] if the dictionary's values are typed the same as [param dictionary]'s values.
+ </description>
+ </method>
+ <method name="is_typed" qualifiers="const">
+ <return type="bool" />
+ <description>
+ Returns [code]true[/code] if the dictionary is typed. Typed dictionaries can only store keys/values of their associated type and provide type safety for the [code][][/code] operator. Methods of typed dictionary still return [Variant].
+ </description>
+ </method>
+ <method name="is_typed_key" qualifiers="const">
+ <return type="bool" />
+ <description>
+ Returns [code]true[/code] if the dictionary's keys are typed.
+ </description>
+ </method>
+ <method name="is_typed_value" qualifiers="const">
+ <return type="bool" />
+ <description>
+ Returns [code]true[/code] if the dictionary's values are typed.
+ </description>
+ </method>
<method name="keys" qualifiers="const">
<return type="Array" />
<description>
diff --git a/doc/classes/DisplayServer.xml b/doc/classes/DisplayServer.xml
index 0bed5288bd..79064a88ba 100644
--- a/doc/classes/DisplayServer.xml
+++ b/doc/classes/DisplayServer.xml
@@ -1772,7 +1772,7 @@
<param index="0" name="window_id" type="int" />
<param index="1" name="parent_window_id" type="int" />
<description>
- Sets window transient parent. Transient window is will be destroyed with its transient parent and will return focus to their parent when closed. The transient window is displayed on top of a non-exclusive full-screen parent window. Transient windows can't enter full-screen mode.
+ Sets window transient parent. Transient window will be destroyed with its transient parent and will return focus to their parent when closed. The transient window is displayed on top of a non-exclusive full-screen parent window. Transient windows can't enter full-screen mode.
[b]Note:[/b] It's recommended to change this value using [member Window.transient] instead.
[b]Note:[/b] The behavior might be different depending on the platform.
</description>
diff --git a/doc/classes/EditorContextMenuPlugin.xml b/doc/classes/EditorContextMenuPlugin.xml
new file mode 100644
index 0000000000..7eeee3d7fd
--- /dev/null
+++ b/doc/classes/EditorContextMenuPlugin.xml
@@ -0,0 +1,49 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<class name="EditorContextMenuPlugin" inherits="RefCounted" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../class.xsd">
+ <brief_description>
+ Plugin for adding custom context menus in the editor.
+ </brief_description>
+ <description>
+ [EditorContextMenuPlugin] allows for the addition of custom options in the editor's context menu.
+ Currently, context menus are supported for three commonly used areas: the file system, scene tree, and editor script list panel.
+ </description>
+ <tutorials>
+ </tutorials>
+ <methods>
+ <method name="_popup_menu" qualifiers="virtual">
+ <return type="void" />
+ <param index="0" name="paths" type="PackedStringArray" />
+ <description>
+ Called when creating a context menu, custom options can be added by using the [method add_context_menu_item] function.
+ </description>
+ </method>
+ <method name="add_context_menu_item">
+ <return type="void" />
+ <param index="0" name="name" type="String" />
+ <param index="1" name="callback" type="Callable" />
+ <param index="2" name="icon" type="Texture2D" default="null" />
+ <param index="3" name="shortcut" type="Shortcut" default="null" />
+ <description>
+ Add custom options to the context menu of the currently specified slot.
+ To trigger a [param shortcut] before the context menu is created, please additionally call the [method add_menu_shortcut] function.
+ [codeblock]
+ func _popup_menu(paths):
+ add_context_menu_item("File Custom options", handle, ICON)
+ [/codeblock]
+ </description>
+ </method>
+ <method name="add_menu_shortcut">
+ <return type="void" />
+ <param index="0" name="shortcut" type="Shortcut" />
+ <param index="1" name="callback" type="Callable" />
+ <description>
+ To register the shortcut for the context menu, call this function within the [method Object._init] function, even if the context menu has not been created yet.
+ Note that this method should only be invoked from [method Object._init]; otherwise, the shortcut will not be registered correctly.
+ [codeblock]
+ func _init():
+ add_menu_shortcut(SHORTCUT, handle);
+ [/codeblock]
+ </description>
+ </method>
+ </methods>
+</class>
diff --git a/doc/classes/EditorExportPlatform.xml b/doc/classes/EditorExportPlatform.xml
index 0e5de79b25..d4084e84a0 100644
--- a/doc/classes/EditorExportPlatform.xml
+++ b/doc/classes/EditorExportPlatform.xml
@@ -11,11 +11,217 @@
<link title="Console support in Godot">$DOCS_URL/tutorials/platform/consoles.html</link>
</tutorials>
<methods>
+ <method name="add_message">
+ <return type="void" />
+ <param index="0" name="type" type="int" enum="EditorExportPlatform.ExportMessageType" />
+ <param index="1" name="category" type="String" />
+ <param index="2" name="message" type="String" />
+ <description>
+ Adds a message to the export log that will be displayed when exporting ends.
+ </description>
+ </method>
+ <method name="clear_messages">
+ <return type="void" />
+ <description>
+ Clears the export log.
+ </description>
+ </method>
+ <method name="create_preset">
+ <return type="EditorExportPreset" />
+ <description>
+ Create a new preset for this platform.
+ </description>
+ </method>
+ <method name="export_pack">
+ <return type="int" enum="Error" />
+ <param index="0" name="preset" type="EditorExportPreset" />
+ <param index="1" name="debug" type="bool" />
+ <param index="2" name="path" type="String" />
+ <param index="3" name="flags" type="int" enum="EditorExportPlatform.DebugFlags" is_bitfield="true" default="0" />
+ <description>
+ Creates a PCK archive at [param path] for the specified [param preset].
+ </description>
+ </method>
+ <method name="export_project">
+ <return type="int" enum="Error" />
+ <param index="0" name="preset" type="EditorExportPreset" />
+ <param index="1" name="debug" type="bool" />
+ <param index="2" name="path" type="String" />
+ <param index="3" name="flags" type="int" enum="EditorExportPlatform.DebugFlags" is_bitfield="true" default="0" />
+ <description>
+ Creates a full project at [param path] for the specified [param preset].
+ </description>
+ </method>
+ <method name="export_project_files">
+ <return type="int" enum="Error" />
+ <param index="0" name="preset" type="EditorExportPreset" />
+ <param index="1" name="debug" type="bool" />
+ <param index="2" name="save_cb" type="Callable" />
+ <param index="3" name="shared_cb" type="Callable" default="Callable()" />
+ <description>
+ Exports project files for the specified preset. This method can be used to implement custom export format, other than PCK and ZIP. One of the callbacks is called for each exported file.
+ [param save_cb] is called for all exported files and have the following arguments: [code]file_path: String[/code], [code]file_data: PackedByteArray[/code], [code]file_index: int[/code], [code]file_count: int[/code], [code]encryption_include_filters: PackedStringArray[/code], [code]encryption_exclude_filters: PackedStringArray[/code], [code]encryption_key: PackedByteArray[/code].
+ [param shared_cb] is called for exported native shared/static libraries and have the following arguments: [code]file_path: String[/code], [code]tags: PackedStringArray[/code], [code]target_folder: String[/code].
+ [b]Note:[/b] [code]file_index[/code] and [code]file_count[/code] are intended for progress tracking only and aren't necesserely unique and precise.
+ </description>
+ </method>
+ <method name="export_zip">
+ <return type="int" enum="Error" />
+ <param index="0" name="preset" type="EditorExportPreset" />
+ <param index="1" name="debug" type="bool" />
+ <param index="2" name="path" type="String" />
+ <param index="3" name="flags" type="int" enum="EditorExportPlatform.DebugFlags" is_bitfield="true" default="0" />
+ <description>
+ Create a ZIP archive at [param path] for the specified [param preset].
+ </description>
+ </method>
+ <method name="find_export_template" qualifiers="const">
+ <return type="Dictionary" />
+ <param index="0" name="template_file_name" type="String" />
+ <description>
+ Locates export template for the platform, and returns [Dictionary] with the following keys: [code]path: String[/code] and [code]error: String[/code]. This method is provided for convenience and custom export platforms aren't required to use it or keep export templates stored in the same way official templates are.
+ </description>
+ </method>
+ <method name="gen_export_flags">
+ <return type="PackedStringArray" />
+ <param index="0" name="flags" type="int" enum="EditorExportPlatform.DebugFlags" is_bitfield="true" />
+ <description>
+ Generates array of command line arguments for the default export templates for the debug flags and editor settings.
+ </description>
+ </method>
+ <method name="get_current_presets" qualifiers="const">
+ <return type="Array" />
+ <description>
+ Returns array of [EditorExportPreset]s for this platform.
+ </description>
+ </method>
+ <method name="get_forced_export_files" qualifiers="static">
+ <return type="PackedStringArray" />
+ <description>
+ Returns array of core file names that always should be exported regardless of preset config.
+ </description>
+ </method>
+ <method name="get_message_category" qualifiers="const">
+ <return type="String" />
+ <param index="0" name="index" type="int" />
+ <description>
+ Returns message category, for the message with [param index].
+ </description>
+ </method>
+ <method name="get_message_count" qualifiers="const">
+ <return type="int" />
+ <description>
+ Returns number of messages in the export log.
+ </description>
+ </method>
+ <method name="get_message_text" qualifiers="const">
+ <return type="String" />
+ <param index="0" name="index" type="int" />
+ <description>
+ Returns message text, for the message with [param index].
+ </description>
+ </method>
+ <method name="get_message_type" qualifiers="const">
+ <return type="int" enum="EditorExportPlatform.ExportMessageType" />
+ <param index="0" name="index" type="int" />
+ <description>
+ Returns message type, for the message with [param index].
+ </description>
+ </method>
<method name="get_os_name" qualifiers="const">
<return type="String" />
<description>
Returns the name of the export operating system handled by this [EditorExportPlatform] class, as a friendly string. Possible return values are [code]Windows[/code], [code]Linux[/code], [code]macOS[/code], [code]Android[/code], [code]iOS[/code], and [code]Web[/code].
</description>
</method>
+ <method name="get_worst_message_type" qualifiers="const">
+ <return type="int" enum="EditorExportPlatform.ExportMessageType" />
+ <description>
+ Returns most severe message type currently present in the export log.
+ </description>
+ </method>
+ <method name="save_pack">
+ <return type="Dictionary" />
+ <param index="0" name="preset" type="EditorExportPreset" />
+ <param index="1" name="debug" type="bool" />
+ <param index="2" name="path" type="String" />
+ <param index="3" name="embed" type="bool" default="false" />
+ <description>
+ Saves PCK archive and returns [Dictionary] with the following keys: [code]result: Error[/code], [code]so_files: Array[/code] (array of the shared/static objects which contains dictionaries with the following keys: [code]path: String[/code], [code]tags: PackedStringArray[/code], and [code]target_folder: String[/code]).
+ If [param embed] is [code]true[/code], PCK content is appended to the end of [param path] file and return [Dictionary] additionally include following keys: [code]embedded_start: int[/code] (embedded PCK offset) and [code]embedded_size: int[/code] (embedded PCK size).
+ </description>
+ </method>
+ <method name="save_zip">
+ <return type="Dictionary" />
+ <param index="0" name="preset" type="EditorExportPreset" />
+ <param index="1" name="debug" type="bool" />
+ <param index="2" name="path" type="String" />
+ <description>
+ Saves ZIP archive and returns [Dictionary] with the following keys: [code]result: Error[/code], [code]so_files: Array[/code] (array of the shared/static objects which contains dictionaries with the following keys: [code]path: String[/code], [code]tags: PackedStringArray[/code], and [code]target_folder: String[/code]).
+ </description>
+ </method>
+ <method name="ssh_push_to_remote" qualifiers="const">
+ <return type="int" enum="Error" />
+ <param index="0" name="host" type="String" />
+ <param index="1" name="port" type="String" />
+ <param index="2" name="scp_args" type="PackedStringArray" />
+ <param index="3" name="src_file" type="String" />
+ <param index="4" name="dst_file" type="String" />
+ <description>
+ Uploads specified file over SCP protocol to the remote host.
+ </description>
+ </method>
+ <method name="ssh_run_on_remote" qualifiers="const">
+ <return type="int" enum="Error" />
+ <param index="0" name="host" type="String" />
+ <param index="1" name="port" type="String" />
+ <param index="2" name="ssh_arg" type="PackedStringArray" />
+ <param index="3" name="cmd_args" type="String" />
+ <param index="4" name="output" type="Array" default="[]" />
+ <param index="5" name="port_fwd" type="int" default="-1" />
+ <description>
+ Executes specified command on the remote host via SSH protocol and returns command output in the [param output].
+ </description>
+ </method>
+ <method name="ssh_run_on_remote_no_wait" qualifiers="const">
+ <return type="int" />
+ <param index="0" name="host" type="String" />
+ <param index="1" name="port" type="String" />
+ <param index="2" name="ssh_args" type="PackedStringArray" />
+ <param index="3" name="cmd_args" type="String" />
+ <param index="4" name="port_fwd" type="int" default="-1" />
+ <description>
+ Executes specified command on the remote host via SSH protocol and returns process ID (on the remote host) without waiting for command to finish.
+ </description>
+ </method>
</methods>
+ <constants>
+ <constant name="EXPORT_MESSAGE_NONE" value="0" enum="ExportMessageType">
+ Invalid message type used as the default value when no type is specified.
+ </constant>
+ <constant name="EXPORT_MESSAGE_INFO" value="1" enum="ExportMessageType">
+ Message type for informational messages that have no effect on the export.
+ </constant>
+ <constant name="EXPORT_MESSAGE_WARNING" value="2" enum="ExportMessageType">
+ Message type for warning messages that should be addressed but still allow to complete the export.
+ </constant>
+ <constant name="EXPORT_MESSAGE_ERROR" value="3" enum="ExportMessageType">
+ Message type for error messages that must be addressed and fail the export.
+ </constant>
+ <constant name="DEBUG_FLAG_DUMB_CLIENT" value="1" enum="DebugFlags" is_bitfield="true">
+ Flag is set if remotely debugged project is expected to use remote file system. If set, [method gen_export_flags] will add [code]--remove-fs[/code] and [code]--remote-fs-password[/code] (if password is set in the editor settings) command line arguments to the list.
+ </constant>
+ <constant name="DEBUG_FLAG_REMOTE_DEBUG" value="2" enum="DebugFlags" is_bitfield="true">
+ Flag is set if remote debug is enabled. If set, [method gen_export_flags] will add [code]--remote-debug[/code] and [code]--breakpoints[/code] (if breakpoints are selected in the script editor or added by the plugin) command line arguments to the list.
+ </constant>
+ <constant name="DEBUG_FLAG_REMOTE_DEBUG_LOCALHOST" value="4" enum="DebugFlags" is_bitfield="true">
+ Flag is set if remotely debugged project is running on the localhost. If set, [method gen_export_flags] will use [code]localhost[/code] instead of [member EditorSettings.network/debug/remote_host] as remote debugger host.
+ </constant>
+ <constant name="DEBUG_FLAG_VIEW_COLLISIONS" value="8" enum="DebugFlags" is_bitfield="true">
+ Flag is set if "Visible Collision Shapes" remote debug option is enabled. If set, [method gen_export_flags] will add [code]--debug-collisions[/code] command line arguments to the list.
+ </constant>
+ <constant name="DEBUG_FLAG_VIEW_NAVIGATION" value="16" enum="DebugFlags" is_bitfield="true">
+ Flag is set if Visible Navigation" remote debug option is enabled. If set, [method gen_export_flags] will add [code]--debug-navigation[/code] command line arguments to the list.
+ </constant>
+ </constants>
</class>
diff --git a/doc/classes/EditorExportPlatformExtension.xml b/doc/classes/EditorExportPlatformExtension.xml
new file mode 100644
index 0000000000..ef589e2f58
--- /dev/null
+++ b/doc/classes/EditorExportPlatformExtension.xml
@@ -0,0 +1,282 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<class name="EditorExportPlatformExtension" inherits="EditorExportPlatform" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../class.xsd">
+ <brief_description>
+ Base class for custom [EditorExportPlatform] implementations (plugins).
+ </brief_description>
+ <description>
+ External [EditorExportPlatform] implementations should inherit from this class.
+ To use [EditorExportPlatform], register it using the [method EditorPlugin.add_export_platform] method first.
+ </description>
+ <tutorials>
+ </tutorials>
+ <methods>
+ <method name="_can_export" qualifiers="virtual const">
+ <return type="bool" />
+ <param index="0" name="preset" type="EditorExportPreset" />
+ <param index="1" name="debug" type="bool" />
+ <description>
+ [b]Optional.[/b]
+ Returns [code]true[/code], if specified [param preset] is valid and can be exported. Use [method set_config_error] and [method set_config_missing_templates] to set error details.
+ Usual implementation can call [method _has_valid_export_configuration] and [method _has_valid_project_configuration] to determine if export is possible.
+ </description>
+ </method>
+ <method name="_cleanup" qualifiers="virtual">
+ <return type="void" />
+ <description>
+ [b]Optional.[/b]
+ Called by the editor before platform is unregistered.
+ </description>
+ </method>
+ <method name="_export_pack" qualifiers="virtual">
+ <return type="int" enum="Error" />
+ <param index="0" name="preset" type="EditorExportPreset" />
+ <param index="1" name="debug" type="bool" />
+ <param index="2" name="path" type="String" />
+ <param index="3" name="flags" type="int" enum="EditorExportPlatform.DebugFlags" is_bitfield="true" />
+ <description>
+ [b]Optional.[/b]
+ Creates a PCK archive at [param path] for the specified [param preset].
+ This method is called when "Export PCK/ZIP" button is pressed in the export dialog, and PCK is selected as a file type.
+ </description>
+ </method>
+ <method name="_export_project" qualifiers="virtual">
+ <return type="int" enum="Error" />
+ <param index="0" name="preset" type="EditorExportPreset" />
+ <param index="1" name="debug" type="bool" />
+ <param index="2" name="path" type="String" />
+ <param index="3" name="flags" type="int" enum="EditorExportPlatform.DebugFlags" is_bitfield="true" />
+ <description>
+ [b]Required.[/b]
+ Creates a full project at [param path] for the specified [param preset].
+ This method is called when "Export" button is pressed in the export dialog.
+ This method implementation can call [method EditorExportPlatform.save_pack] or [method EditorExportPlatform.save_zip] to use default PCK/ZIP export process, or calls [method EditorExportPlatform.export_project_files] and implement custom callback for processing each exported file.
+ </description>
+ </method>
+ <method name="_export_zip" qualifiers="virtual">
+ <return type="int" enum="Error" />
+ <param index="0" name="preset" type="EditorExportPreset" />
+ <param index="1" name="debug" type="bool" />
+ <param index="2" name="path" type="String" />
+ <param index="3" name="flags" type="int" enum="EditorExportPlatform.DebugFlags" is_bitfield="true" />
+ <description>
+ [b]Optional.[/b]
+ Create a ZIP archive at [param path] for the specified [param preset].
+ This method is called when "Export PCK/ZIP" button is pressed in the export dialog, and ZIP is selected as a file type.
+ </description>
+ </method>
+ <method name="_get_binary_extensions" qualifiers="virtual const">
+ <return type="PackedStringArray" />
+ <param index="0" name="preset" type="EditorExportPreset" />
+ <description>
+ [b]Required.[/b]
+ Returns array of supported binary extensions for the full project export.
+ </description>
+ </method>
+ <method name="_get_debug_protocol" qualifiers="virtual const">
+ <return type="String" />
+ <description>
+ [b]Optional.[/b]
+ Returns protocol used for remote debugging. Default implementation return [code]tcp://[/code].
+ </description>
+ </method>
+ <method name="_get_device_architecture" qualifiers="virtual const">
+ <return type="String" />
+ <param index="0" name="device" type="int" />
+ <description>
+ [b]Optional.[/b]
+ Returns device architecture for one-click deploy.
+ </description>
+ </method>
+ <method name="_get_export_option_visibility" qualifiers="virtual const">
+ <return type="bool" />
+ <param index="0" name="preset" type="EditorExportPreset" />
+ <param index="1" name="option" type="String" />
+ <description>
+ [b]Optional.[/b]
+ Validates [param option] and returns visibility for the specified [param preset]. Default implementation return [code]true[/code] for all options.
+ </description>
+ </method>
+ <method name="_get_export_option_warning" qualifiers="virtual const">
+ <return type="String" />
+ <param index="0" name="preset" type="EditorExportPreset" />
+ <param index="1" name="option" type="StringName" />
+ <description>
+ [b]Optional.[/b]
+ Validates [param option] and returns warning message for the specified [param preset]. Default implementation return empty string for all options.
+ </description>
+ </method>
+ <method name="_get_export_options" qualifiers="virtual const">
+ <return type="Dictionary[]" />
+ <description>
+ [b]Optional.[/b]
+ Returns a property list, as an [Array] of dictionaries. Each [Dictionary] must at least contain the [code]name: StringName[/code] and [code]type: Variant.Type[/code] entries.
+ Additionally, the following keys are supported:
+ - [code]hint: PropertyHint[/code]
+ - [code]hint_string: String[/code]
+ - [code]usage: PropertyUsageFlags[/code]
+ - [code]class_name: StringName[/code]
+ - [code]default_value: Variant[/code], default value of the property.
+ - [code]update_visibility: bool[/code], if set to [code]true[/code], [method _get_export_option_visibility] is called for each property when this property is changed.
+ - [code]required: bool[/code], if set to [code]true[/code], this property warnings are critical, and should be resolved to make export possible. This value is a hint for the [method _has_valid_export_configuration] implementation, and not used by the engine directly.
+ See also [method Object._get_property_list].
+ </description>
+ </method>
+ <method name="_get_logo" qualifiers="virtual const">
+ <return type="Texture2D" />
+ <description>
+ [b]Required.[/b]
+ Returns platform logo displayed in the export dialog, logo should be 32x32 adjusted to the current editor scale, see [method EditorInterface.get_editor_scale].
+ </description>
+ </method>
+ <method name="_get_name" qualifiers="virtual const">
+ <return type="String" />
+ <description>
+ [b]Required.[/b]
+ Returns export platform name.
+ </description>
+ </method>
+ <method name="_get_option_icon" qualifiers="virtual const">
+ <return type="ImageTexture" />
+ <param index="0" name="device" type="int" />
+ <description>
+ [b]Optional.[/b]
+ Returns one-click deploy menu item icon for the specified [param device], icon should be 16x16 adjusted to the current editor scale, see [method EditorInterface.get_editor_scale].
+ </description>
+ </method>
+ <method name="_get_option_label" qualifiers="virtual const">
+ <return type="String" />
+ <param index="0" name="device" type="int" />
+ <description>
+ [b]Optional.[/b]
+ Returns one-click deploy menu item label for the specified [param device].
+ </description>
+ </method>
+ <method name="_get_option_tooltip" qualifiers="virtual const">
+ <return type="String" />
+ <param index="0" name="device" type="int" />
+ <description>
+ [b]Optional.[/b]
+ Returns one-click deploy menu item tooltip for the specified [param device].
+ </description>
+ </method>
+ <method name="_get_options_count" qualifiers="virtual const">
+ <return type="int" />
+ <description>
+ [b]Optional.[/b]
+ Returns number one-click deploy devices (or other one-click option displayed in the menu).
+ </description>
+ </method>
+ <method name="_get_options_tooltip" qualifiers="virtual const">
+ <return type="String" />
+ <description>
+ [b]Optional.[/b]
+ Returns tooltip of the one-click deploy menu button.
+ </description>
+ </method>
+ <method name="_get_os_name" qualifiers="virtual const">
+ <return type="String" />
+ <description>
+ [b]Required.[/b]
+ Returns target OS name.
+ </description>
+ </method>
+ <method name="_get_platform_features" qualifiers="virtual const">
+ <return type="PackedStringArray" />
+ <description>
+ [b]Required.[/b]
+ Returns array of platform specific features.
+ </description>
+ </method>
+ <method name="_get_preset_features" qualifiers="virtual const">
+ <return type="PackedStringArray" />
+ <param index="0" name="preset" type="EditorExportPreset" />
+ <description>
+ [b]Required.[/b]
+ Returns array of platform specific features for the specified [param preset].
+ </description>
+ </method>
+ <method name="_get_run_icon" qualifiers="virtual const">
+ <return type="Texture2D" />
+ <description>
+ [b]Optional.[/b]
+ Returns icon of the one-click deploy menu button, icon should be 16x16 adjusted to the current editor scale, see [method EditorInterface.get_editor_scale].
+ </description>
+ </method>
+ <method name="_has_valid_export_configuration" qualifiers="virtual const">
+ <return type="bool" />
+ <param index="0" name="preset" type="EditorExportPreset" />
+ <param index="1" name="debug" type="bool" />
+ <description>
+ [b]Required.[/b]
+ Returns [code]true[/code] if export configuration is valid.
+ </description>
+ </method>
+ <method name="_has_valid_project_configuration" qualifiers="virtual const">
+ <return type="bool" />
+ <param index="0" name="preset" type="EditorExportPreset" />
+ <description>
+ [b]Required.[/b]
+ Returns [code]true[/code] if project configuration is valid.
+ </description>
+ </method>
+ <method name="_is_executable" qualifiers="virtual const">
+ <return type="bool" />
+ <param index="0" name="path" type="String" />
+ <description>
+ [b]Optional.[/b]
+ Returns [code]true[/code] if specified file is a valid executable (native executable or script) for the target platform.
+ </description>
+ </method>
+ <method name="_poll_export" qualifiers="virtual">
+ <return type="bool" />
+ <description>
+ [b]Optional.[/b]
+ Returns [code]true[/code] if one-click deploy options are changed and editor interface should be updated.
+ </description>
+ </method>
+ <method name="_run" qualifiers="virtual">
+ <return type="int" enum="Error" />
+ <param index="0" name="preset" type="EditorExportPreset" />
+ <param index="1" name="device" type="int" />
+ <param index="2" name="debug_flags" type="int" enum="EditorExportPlatform.DebugFlags" is_bitfield="true" />
+ <description>
+ [b]Optional.[/b]
+ This method is called when [param device] one-click deploy menu option is selected.
+ Implementation should export project to a temporary location, upload and run it on the specific [param device], or perform another action associated with the menu item.
+ </description>
+ </method>
+ <method name="_should_update_export_options" qualifiers="virtual">
+ <return type="bool" />
+ <description>
+ [b]Optional.[/b]
+ Returns [code]true[/code] if export options list is changed and presets should be updated.
+ </description>
+ </method>
+ <method name="get_config_error" qualifiers="const">
+ <return type="String" />
+ <description>
+ Returns current configuration error message text. This method should be called only from the [method _can_export], [method _has_valid_export_configuration], or [method _has_valid_project_configuration] implementations.
+ </description>
+ </method>
+ <method name="get_config_missing_templates" qualifiers="const">
+ <return type="bool" />
+ <description>
+ Returns [code]true[/code] is export templates are missing from the current configuration. This method should be called only from the [method _can_export], [method _has_valid_export_configuration], or [method _has_valid_project_configuration] implementations.
+ </description>
+ </method>
+ <method name="set_config_error" qualifiers="const">
+ <return type="void" />
+ <param index="0" name="error_text" type="String" />
+ <description>
+ Sets current configuration error message text. This method should be called only from the [method _can_export], [method _has_valid_export_configuration], or [method _has_valid_project_configuration] implementations.
+ </description>
+ </method>
+ <method name="set_config_missing_templates" qualifiers="const">
+ <return type="void" />
+ <param index="0" name="missing_templates" type="bool" />
+ <description>
+ Set to [code]true[/code] is export templates are missing from the current configuration. This method should be called only from the [method _can_export], [method _has_valid_export_configuration], or [method _has_valid_project_configuration] implementations.
+ </description>
+ </method>
+ </methods>
+</class>
diff --git a/doc/classes/EditorExportPlugin.xml b/doc/classes/EditorExportPlugin.xml
index 9ef911a68d..42e1968eb0 100644
--- a/doc/classes/EditorExportPlugin.xml
+++ b/doc/classes/EditorExportPlugin.xml
@@ -305,6 +305,18 @@
In case of a directory code-sign will error if you place non code object in directory.
</description>
</method>
+ <method name="get_export_platform" qualifiers="const">
+ <return type="EditorExportPlatform" />
+ <description>
+ Returns currently used export platform.
+ </description>
+ </method>
+ <method name="get_export_preset" qualifiers="const">
+ <return type="EditorExportPreset" />
+ <description>
+ Returns currently used export preset.
+ </description>
+ </method>
<method name="get_option" qualifiers="const">
<return type="Variant" />
<param index="0" name="name" type="StringName" />
diff --git a/doc/classes/EditorExportPreset.xml b/doc/classes/EditorExportPreset.xml
new file mode 100644
index 0000000000..bba79364e4
--- /dev/null
+++ b/doc/classes/EditorExportPreset.xml
@@ -0,0 +1,186 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<class name="EditorExportPreset" inherits="RefCounted" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../class.xsd">
+ <brief_description>
+ Export preset configuration.
+ </brief_description>
+ <description>
+ Export preset configuration. Instances of [EditorExportPreset] by editor UI and intended to be used a read-only configuration passed to the [EditorExportPlatform] methods when exporting the project.
+ </description>
+ <tutorials>
+ </tutorials>
+ <methods>
+ <method name="are_advanced_options_enabled" qualifiers="const">
+ <return type="bool" />
+ <description>
+ Returns [code]true[/code], is "Advanced" toggle is enabled in the export dialog.
+ </description>
+ </method>
+ <method name="get_custom_features" qualifiers="const">
+ <return type="String" />
+ <description>
+ Returns string with a comma separated list of custom features.
+ </description>
+ </method>
+ <method name="get_customized_files" qualifiers="const">
+ <return type="Dictionary" />
+ <description>
+ Returns [Dictionary] of files selected in the "Resources" tab of the export dialog. Dictionary keys are file names and values are export mode - [code]"strip[/code], [code]"keep"[/code], or [code]"remove"[/code]. See also [method get_file_export_mode].
+ </description>
+ </method>
+ <method name="get_customized_files_count" qualifiers="const">
+ <return type="int" />
+ <description>
+ Returns number of files selected in the "Resources" tab of the export dialog.
+ </description>
+ </method>
+ <method name="get_encrypt_directory" qualifiers="const">
+ <return type="bool" />
+ <description>
+ Returns [code]true[/code], PCK directory encryption is enabled in the export dialog.
+ </description>
+ </method>
+ <method name="get_encrypt_pck" qualifiers="const">
+ <return type="bool" />
+ <description>
+ Returns [code]true[/code], PCK encryption is enabled in the export dialog.
+ </description>
+ </method>
+ <method name="get_encryption_ex_filter" qualifiers="const">
+ <return type="String" />
+ <description>
+ Returns file filters to exclude during PCK encryption.
+ </description>
+ </method>
+ <method name="get_encryption_in_filter" qualifiers="const">
+ <return type="String" />
+ <description>
+ Returns file filters to include during PCK encryption.
+ </description>
+ </method>
+ <method name="get_encryption_key" qualifiers="const">
+ <return type="String" />
+ <description>
+ Returns PCK encryption key.
+ </description>
+ </method>
+ <method name="get_exclude_filter" qualifiers="const">
+ <return type="String" />
+ <description>
+ Returns file filters to exclude during export.
+ </description>
+ </method>
+ <method name="get_export_filter" qualifiers="const">
+ <return type="int" enum="EditorExportPreset.ExportFilter" />
+ <description>
+ Returns export file filter mode selected in the "Resources" tab of the export dialog.
+ </description>
+ </method>
+ <method name="get_export_path" qualifiers="const">
+ <return type="String" />
+ <description>
+ Returns export target path.
+ </description>
+ </method>
+ <method name="get_file_export_mode" qualifiers="const">
+ <return type="int" enum="EditorExportPreset.FileExportMode" />
+ <param index="0" name="path" type="String" />
+ <param index="1" name="default" type="int" enum="EditorExportPreset.FileExportMode" default="0" />
+ <description>
+ Returns file export mode for the specified file.
+ </description>
+ </method>
+ <method name="get_files_to_export" qualifiers="const">
+ <return type="PackedStringArray" />
+ <description>
+ Returns array of files to export.
+ </description>
+ </method>
+ <method name="get_include_filter" qualifiers="const">
+ <return type="String" />
+ <description>
+ Returns file filters to include during export.
+ </description>
+ </method>
+ <method name="get_or_env" qualifiers="const">
+ <return type="Variant" />
+ <param index="0" name="name" type="StringName" />
+ <param index="1" name="env_var" type="String" />
+ <description>
+ Returns export option value or value of environment variable if it is set.
+ </description>
+ </method>
+ <method name="get_preset_name" qualifiers="const">
+ <return type="String" />
+ <description>
+ Returns export preset name.
+ </description>
+ </method>
+ <method name="get_script_export_mode" qualifiers="const">
+ <return type="int" />
+ <description>
+ Returns script export mode.
+ </description>
+ </method>
+ <method name="get_version" qualifiers="const">
+ <return type="String" />
+ <param index="0" name="name" type="StringName" />
+ <param index="1" name="windows_version" type="bool" />
+ <description>
+ Returns the preset's version number, or fall back to the [member ProjectSettings.application/config/version] project setting if set to an empty string.
+ If [param windows_version] is [code]true[/code], formats the returned version number to be compatible with Windows executable metadata.
+ </description>
+ </method>
+ <method name="has" qualifiers="const">
+ <return type="bool" />
+ <param index="0" name="property" type="StringName" />
+ <description>
+ Returns [code]true[/code] if preset has specified property.
+ </description>
+ </method>
+ <method name="has_export_file">
+ <return type="bool" />
+ <param index="0" name="path" type="String" />
+ <description>
+ Returns [code]true[/code] if specified file is exported.
+ </description>
+ </method>
+ <method name="is_dedicated_server" qualifiers="const">
+ <return type="bool" />
+ <description>
+ Returns [code]true[/code] if dedicated server export mode is selected in the export dialog.
+ </description>
+ </method>
+ <method name="is_runnable" qualifiers="const">
+ <return type="bool" />
+ <description>
+ Returns [code]true[/code] if "Runnable" toggle is enabled in the export dialog.
+ </description>
+ </method>
+ </methods>
+ <constants>
+ <constant name="EXPORT_ALL_RESOURCES" value="0" enum="ExportFilter">
+ </constant>
+ <constant name="EXPORT_SELECTED_SCENES" value="1" enum="ExportFilter">
+ </constant>
+ <constant name="EXPORT_SELECTED_RESOURCES" value="2" enum="ExportFilter">
+ </constant>
+ <constant name="EXCLUDE_SELECTED_RESOURCES" value="3" enum="ExportFilter">
+ </constant>
+ <constant name="EXPORT_CUSTOMIZED" value="4" enum="ExportFilter">
+ </constant>
+ <constant name="MODE_FILE_NOT_CUSTOMIZED" value="0" enum="FileExportMode">
+ </constant>
+ <constant name="MODE_FILE_STRIP" value="1" enum="FileExportMode">
+ </constant>
+ <constant name="MODE_FILE_KEEP" value="2" enum="FileExportMode">
+ </constant>
+ <constant name="MODE_FILE_REMOVE" value="3" enum="FileExportMode">
+ </constant>
+ <constant name="MODE_SCRIPT_TEXT" value="0" enum="ScriptExportMode">
+ </constant>
+ <constant name="MODE_SCRIPT_BINARY_TOKENS" value="1" enum="ScriptExportMode">
+ </constant>
+ <constant name="MODE_SCRIPT_BINARY_TOKENS_COMPRESSED" value="2" enum="ScriptExportMode">
+ </constant>
+ </constants>
+</class>
diff --git a/doc/classes/EditorInterface.xml b/doc/classes/EditorInterface.xml
index 178be439c3..795c5c1c2f 100644
--- a/doc/classes/EditorInterface.xml
+++ b/doc/classes/EditorInterface.xml
@@ -117,6 +117,12 @@
[b]Note:[/b] When creating custom editor UI, prefer accessing theme items directly from your GUI nodes using the [code]get_theme_*[/code] methods.
</description>
</method>
+ <method name="get_editor_undo_redo" qualifiers="const">
+ <return type="EditorUndoRedoManager" />
+ <description>
+ Returns the editor's [EditorUndoRedoManager].
+ </description>
+ </method>
<method name="get_editor_viewport_2d" qualifiers="const">
<return type="SubViewport" />
<description>
@@ -299,8 +305,9 @@
<return type="void" />
<param index="0" name="callback" type="Callable" />
<param index="1" name="valid_types" type="StringName[]" default="[]" />
+ <param index="2" name="current_value" type="Node" default="null" />
<description>
- Pops up an editor dialog for selecting a [Node] from the edited scene. The [param callback] must take a single argument of type [NodePath]. It is called on the selected [NodePath] or the empty path [code]^""[/code] if the dialog is canceled. If [param valid_types] is provided, the dialog will only show Nodes that match one of the listed Node types.
+ Pops up an editor dialog for selecting a [Node] from the edited scene. The [param callback] must take a single argument of type [NodePath]. It is called on the selected [NodePath] or the empty path [code]^""[/code] if the dialog is canceled. If [param valid_types] is provided, the dialog will only show Nodes that match one of the listed Node types. If [param current_value] is provided, the Node will be automatically selected in the tree, if it exists.
[b]Example:[/b] Display the node selection dialog as soon as this node is added to the tree for the first time:
[codeblock]
func _ready():
@@ -320,8 +327,9 @@
<param index="0" name="object" type="Object" />
<param index="1" name="callback" type="Callable" />
<param index="2" name="type_filter" type="PackedInt32Array" default="PackedInt32Array()" />
+ <param index="3" name="current_value" type="String" default="&quot;&quot;" />
<description>
- Pops up an editor dialog for selecting properties from [param object]. The [param callback] must take a single argument of type [NodePath]. It is called on the selected property path (see [method NodePath.get_as_property_path]) or the empty path [code]^""[/code] if the dialog is canceled. If [param type_filter] is provided, the dialog will only show properties that match one of the listed [enum Variant.Type] values.
+ Pops up an editor dialog for selecting properties from [param object]. The [param callback] must take a single argument of type [NodePath]. It is called on the selected property path (see [method NodePath.get_as_property_path]) or the empty path [code]^""[/code] if the dialog is canceled. If [param type_filter] is provided, the dialog will only show properties that match one of the listed [enum Variant.Type] values. If [param current_value] is provided, the property will be selected automatically in the property list, if it exists.
[codeblock]
func _ready():
if Engine.is_editor_hint():
diff --git a/doc/classes/EditorPlugin.xml b/doc/classes/EditorPlugin.xml
index ed3233b1ae..b3191e5378 100644
--- a/doc/classes/EditorPlugin.xml
+++ b/doc/classes/EditorPlugin.xml
@@ -401,6 +401,15 @@
Adds a script at [param path] to the Autoload list as [param name].
</description>
</method>
+ <method name="add_context_menu_plugin">
+ <return type="void" />
+ <param index="0" name="slot" type="int" enum="EditorPlugin.ContextMenuSlot" />
+ <param index="1" name="plugin" type="EditorContextMenuPlugin" />
+ <description>
+ Adds a plugin to the context menu. [param slot] is the position in the context menu where the plugin will be added.
+ Context menus are supported for three commonly used areas: the file system, scene tree, and editor script list panel.
+ </description>
+ </method>
<method name="add_control_to_bottom_panel">
<return type="Button" />
<param index="0" name="control" type="Control" />
@@ -455,6 +464,13 @@
Adds a [Script] as debugger plugin to the Debugger. The script must extend [EditorDebuggerPlugin].
</description>
</method>
+ <method name="add_export_platform">
+ <return type="void" />
+ <param index="0" name="platform" type="EditorExportPlatform" />
+ <description>
+ Registers a new [EditorExportPlatform]. Export platforms provides functionality of exporting to the specific platform.
+ </description>
+ </method>
<method name="add_export_plugin">
<return type="void" />
<param index="0" name="plugin" type="EditorExportPlugin" />
@@ -617,6 +633,14 @@
Removes an Autoload [param name] from the list.
</description>
</method>
+ <method name="remove_context_menu_plugin">
+ <return type="void" />
+ <param index="0" name="slot" type="int" enum="EditorPlugin.ContextMenuSlot" />
+ <param index="1" name="plugin" type="EditorContextMenuPlugin" />
+ <description>
+ Removes a context menu plugin from the specified slot.
+ </description>
+ </method>
<method name="remove_control_from_bottom_panel">
<return type="void" />
<param index="0" name="control" type="Control" />
@@ -653,6 +677,13 @@
Removes the debugger plugin with given script from the Debugger.
</description>
</method>
+ <method name="remove_export_platform">
+ <return type="void" />
+ <param index="0" name="platform" type="EditorExportPlatform" />
+ <description>
+ Removes an export platform registered by [method add_export_platform].
+ </description>
+ </method>
<method name="remove_export_plugin">
<return type="void" />
<param index="0" name="plugin" type="EditorExportPlugin" />
@@ -860,5 +891,17 @@
<constant name="AFTER_GUI_INPUT_CUSTOM" value="2" enum="AfterGUIInput">
Pass the [InputEvent] to other editor plugins except the main [Node3D] one. This can be used to prevent node selection changes and work with sub-gizmos instead.
</constant>
+ <constant name="CONTEXT_SLOT_SCENE_TREE" value="0" enum="ContextMenuSlot">
+ Context menu slot for the SceneTree.
+ </constant>
+ <constant name="CONTEXT_SLOT_FILESYSTEM" value="1" enum="ContextMenuSlot">
+ Context menu slot for the FileSystem.
+ </constant>
+ <constant name="CONTEXT_SLOT_SCRIPT_EDITOR" value="2" enum="ContextMenuSlot">
+ Context menu slot for the ScriptEditor file list.
+ </constant>
+ <constant name="CONTEXT_SUBMENU_SLOT_FILESYSTEM_CREATE" value="3" enum="ContextMenuSlot">
+ Context menu slot for the FileSystem create submenu.
+ </constant>
</constants>
</class>
diff --git a/doc/classes/EditorResourcePreviewGenerator.xml b/doc/classes/EditorResourcePreviewGenerator.xml
index fcfdbb5c44..9c9b6d11b2 100644
--- a/doc/classes/EditorResourcePreviewGenerator.xml
+++ b/doc/classes/EditorResourcePreviewGenerator.xml
@@ -23,7 +23,7 @@
<param index="2" name="metadata" type="Dictionary" />
<description>
Generate a preview from a given resource with the specified size. This must always be implemented.
- Returning an empty texture is an OK way to fail and let another generator take care.
+ Returning [code]null[/code] is an OK way to fail and let another generator take care.
Care must be taken because this function is always called from a thread (not the main thread).
[param metadata] dictionary can be modified to store file-specific metadata that can be used in [method EditorResourceTooltipPlugin._make_tooltip_for_path] (like image size, sample length etc.).
</description>
@@ -35,7 +35,7 @@
<param index="2" name="metadata" type="Dictionary" />
<description>
Generate a preview directly from a path with the specified size. Implementing this is optional, as default code will load and call [method _generate].
- Returning an empty texture is an OK way to fail and let another generator take care.
+ Returning [code]null[/code] is an OK way to fail and let another generator take care.
Care must be taken because this function is always called from a thread (not the main thread).
[param metadata] dictionary can be modified to store file-specific metadata that can be used in [method EditorResourceTooltipPlugin._make_tooltip_for_path] (like image size, sample length etc.).
</description>
diff --git a/doc/classes/EditorSettings.xml b/doc/classes/EditorSettings.xml
index ef87fb5edd..1de63b4a39 100644
--- a/doc/classes/EditorSettings.xml
+++ b/doc/classes/EditorSettings.xml
@@ -194,6 +194,9 @@
The maximum number of script functions that can be displayed per frame in the profiler. If there are more script functions called in a given profiler frame, these functions will be discarded from the profiling results entirely.
[b]Note:[/b] This setting is only read when the profiler is first started, so changing it during profiling will have no effect.
</member>
+ <member name="debugger/profiler_target_fps" type="int" setter="" getter="">
+ The target frame rate shown in the visual profiler graph, in frames per second.
+ </member>
<member name="debugger/remote_inspect_refresh_interval" type="float" setter="" getter="">
The refresh interval for the remote inspector's properties (in seconds). Lower values are more reactive, but may cause stuttering while the project is running from the editor and the [b]Remote[/b] scene tree is selected in the Scene tree dock.
</member>
@@ -204,8 +207,11 @@
If [code]true[/code], displays folders in the FileSystem dock's bottom pane when split mode is enabled. If [code]false[/code], only files will be displayed in the bottom pane. Split mode can be toggled by pressing the icon next to the [code]res://[/code] folder path.
[b]Note:[/b] This setting has no effect when split mode is disabled (which is the default).
</member>
+ <member name="docks/filesystem/other_file_extensions" type="String" setter="" getter="">
+ A comma separated list of unsupported file extensions to show in the FileSystem dock, e.g. [code]"ico,icns"[/code].
+ </member>
<member name="docks/filesystem/textfile_extensions" type="String" setter="" getter="">
- List of file extensions to consider as editable text files in the FileSystem dock (by double-clicking on the files).
+ A comma separated list of file extensions to consider as editable text files in the FileSystem dock (by double-clicking on the files), e.g. [code]"txt,md,cfg,ini,log,json,yml,yaml,toml,xml"[/code].
</member>
<member name="docks/filesystem/thumbnail_size" type="int" setter="" getter="">
The thumbnail size to use in the FileSystem dock (in pixels). See also [member filesystem/file_dialog/thumbnail_size].
@@ -324,7 +330,6 @@
<member name="editors/3d/navigation/emulate_3_button_mouse" type="bool" setter="" getter="">
If [code]true[/code], enables 3-button mouse emulation mode. This is useful on laptops when using a trackpad.
When 3-button mouse emulation mode is enabled, the pan, zoom and orbit modifiers can always be used in the 3D editor viewport, even when not holding down any mouse button.
- [b]Note:[/b] No matter the orbit modifier configured in [member editors/3d/navigation/orbit_modifier], [kbd]Alt[/kbd] will always remain usable for orbiting in this mode to improve usability with graphics tablets.
</member>
<member name="editors/3d/navigation/emulate_numpad" type="bool" setter="" getter="">
If [code]true[/code], allows using the top row [kbd]0[/kbd]-[kbd]9[/kbd] keys to function as their equivalent numpad keys for 3D editor navigation. This should be enabled on keyboards that have no numeric keypad available.
@@ -336,28 +341,25 @@
If [code]true[/code], invert the vertical mouse axis when panning, orbiting, or using freelook mode in the 3D editor.
</member>
<member name="editors/3d/navigation/navigation_scheme" type="int" setter="" getter="">
- The navigation scheme to use in the 3D editor. Changing this setting will affect the mouse buttons that must be held down to perform certain operations in the 3D editor viewport.
- - [b]Godot[/b] Middle mouse button to orbit, [kbd]Shift + Middle mouse button[/kbd] to pan. [kbd]Mouse wheel[/kbd] to zoom.
- - [b]Maya:[/b] [kbd]Alt + Left mouse button[/kbd] to orbit. [kbd]Middle mouse button[/kbd] to pan, [kbd]Shift + Middle mouse button[/kbd] to pan 10 times faster. [kbd]Mouse wheel[/kbd] to zoom.
+ The navigation scheme preset to use in the 3D editor. Changing this setting will affect the mouse button and modifier controls used to navigate the 3D editor viewport.
+ All schemes can use [kbd]Mouse wheel[/kbd] to zoom.
+ - [b]Godot:[/b] [kbd]Middle mouse button[/kbd] to orbit. [kbd]Shift + Middle mouse button[/kbd] to pan. [kbd]Ctrl + Shift + Middle mouse button[/kbd] to zoom.
+ - [b]Maya:[/b] [kbd]Alt + Left mouse button[/kbd] to orbit. [kbd]Middle mouse button[/kbd] to pan, [kbd]Shift + Middle mouse button[/kbd] to pan 10 times faster. [kbd]Alt + Right mouse button[/kbd] to zoom.
- [b]Modo:[/b] [kbd]Alt + Left mouse button[/kbd] to orbit. [kbd]Alt + Shift + Left mouse button[/kbd] to pan. [kbd]Ctrl + Alt + Left mouse button[/kbd] to zoom.
- See also [member editors/3d/freelook/freelook_navigation_scheme].
+ See also [member editors/3d/navigation/orbit_mouse_button], [member editors/3d/navigation/pan_mouse_button], [member editors/3d/navigation/zoom_mouse_button], and [member editors/3d/freelook/freelook_navigation_scheme].
[b]Note:[/b] On certain window managers on Linux, the [kbd]Alt[/kbd] key will be intercepted by the window manager when clicking a mouse button at the same time. This means Godot will not see the modifier key as being pressed.
</member>
- <member name="editors/3d/navigation/orbit_modifier" type="int" setter="" getter="">
- The modifier key that must be held to orbit in the 3D editor.
- [b]Note:[/b] If [member editors/3d/navigation/emulate_3_button_mouse] is [code]true[/code], [kbd]Alt[/kbd] will always remain usable for orbiting to improve usability with graphics tablets.
- [b]Note:[/b] On certain window managers on Linux, the [kbd]Alt[/kbd] key will be intercepted by the window manager when clicking a mouse button at the same time. This means Godot will not see the modifier key as being pressed.
+ <member name="editors/3d/navigation/orbit_mouse_button" type="int" setter="" getter="">
+ The mouse button that needs to be held down to orbit in the 3D editor viewport.
</member>
- <member name="editors/3d/navigation/pan_modifier" type="int" setter="" getter="">
- The modifier key that must be held to pan in the 3D editor.
- [b]Note:[/b] On certain window managers on Linux, the [kbd]Alt[/kbd] key will be intercepted by the window manager when clicking a mouse button at the same time. This means Godot will not see the modifier key as being pressed.
+ <member name="editors/3d/navigation/pan_mouse_button" type="int" setter="" getter="">
+ The mouse button that needs to be held down to pan in the 3D editor viewport.
</member>
<member name="editors/3d/navigation/warped_mouse_panning" type="bool" setter="" getter="">
If [code]true[/code], warps the mouse around the 3D viewport while panning in the 3D editor. This makes it possible to pan over a large area without having to exit panning and adjust the mouse cursor.
</member>
- <member name="editors/3d/navigation/zoom_modifier" type="int" setter="" getter="">
- The modifier key that must be held to zoom in the 3D editor.
- [b]Note:[/b] On certain window managers on Linux, the [kbd]Alt[/kbd] key will be intercepted by the window manager when clicking a mouse button at the same time. This means Godot will not see the modifier key as being pressed.
+ <member name="editors/3d/navigation/zoom_mouse_button" type="int" setter="" getter="">
+ The mouse button that needs to be held down to zoom in the 3D editor viewport.
</member>
<member name="editors/3d/navigation/zoom_style" type="int" setter="" getter="">
The mouse cursor movement direction to use when zooming by moving the mouse. This does not affect zooming with the mouse wheel.
@@ -967,7 +969,18 @@
If [code]true[/code], on Linux/BSD, the editor will check for Wayland first instead of X11 (if available).
</member>
<member name="run/window_placement/android_window" type="int" setter="" getter="">
- The Android window to display the project on when starting the project from the editor.
+ Specifies how the Play window is launched relative to the Android editor.
+ - [b]Auto (based on screen size)[/b] (default) will automatically choose how to launch the Play window based on the device and screen metrics. Defaults to [b]Same as Editor[/b] on phones and [b]Side-by-side with Editor[/b] on tablets.
+ - [b]Same as Editor[/b] will launch the Play window in the same window as the Editor.
+ - [b]Side-by-side with Editor[/b] will launch the Play window side-by-side with the Editor window.
+ - [b]Launch in PiP mode[/b] will launch the Play window directly in picture-in-picture (PiP) mode if PiP mode is supported and enabled. When maximized, the Play window will occupy the same window as the Editor.
+ [b]Note:[/b] Only available in the Android editor.
+ </member>
+ <member name="run/window_placement/play_window_pip_mode" type="int" setter="" getter="">
+ Specifies the picture-in-picture (PiP) mode for the Play window.
+ - [b]Disabled:[/b] PiP is disabled for the Play window.
+ - [b]Enabled:[/b] If the device supports it, PiP is always enabled for the Play window. The Play window will contain a button to enter PiP mode.
+ - [b]Enabled when Play window is same as Editor[/b] (default for Android editor): If the device supports it, PiP is enabled when the Play window is the same as the Editor. The Play window will contain a button to enter PiP mode.
[b]Note:[/b] Only available in the Android editor.
</member>
<member name="run/window_placement/rect" type="int" setter="" getter="">
diff --git a/doc/classes/EditorUndoRedoManager.xml b/doc/classes/EditorUndoRedoManager.xml
index 5ac0d790a2..0f8c69a1bb 100644
--- a/doc/classes/EditorUndoRedoManager.xml
+++ b/doc/classes/EditorUndoRedoManager.xml
@@ -68,6 +68,21 @@
Register a reference for "undo" that will be erased if the "undo" history is lost. This is useful mostly for nodes removed with the "do" call (not the "undo" call!).
</description>
</method>
+ <method name="clear_history">
+ <return type="void" />
+ <param index="0" name="id" type="int" default="-99" />
+ <param index="1" name="increase_version" type="bool" default="true" />
+ <description>
+ Clears the given undo history. You can clear history for a specific scene, global history, or for all scenes at once if [param id] is [constant INVALID_HISTORY].
+ If [param increase_version] is [code]true[/code], the undo history version will be increased, marking it as unsaved. Useful for operations that modify the scene, but don't support undo.
+ [codeblock]
+ var scene_root = EditorInterface.get_edited_scene_root()
+ var undo_redo = EditorInterface.get_editor_undo_redo()
+ undo_redo.clear_history(undo_redo.get_object_history_id(scene_root))
+ [/codeblock]
+ [b]Note:[/b] If you want to mark an edited scene as unsaved without clearing its history, use [method EditorInterface.mark_scene_as_unsaved] instead.
+ </description>
+ </method>
<method name="commit_action">
<return type="void" />
<param index="0" name="execute" type="bool" default="true" />
diff --git a/doc/classes/GDExtensionManager.xml b/doc/classes/GDExtensionManager.xml
index 211bc023c0..97d2d08752 100644
--- a/doc/classes/GDExtensionManager.xml
+++ b/doc/classes/GDExtensionManager.xml
@@ -56,6 +56,20 @@
</method>
</methods>
<signals>
+ <signal name="extension_loaded">
+ <param index="0" name="extension" type="GDExtension" />
+ <description>
+ Emitted after the editor has finished loading a new extension.
+ [b]Note:[/b] This signal is only emitted in editor builds.
+ </description>
+ </signal>
+ <signal name="extension_unloading">
+ <param index="0" name="extension" type="GDExtension" />
+ <description>
+ Emitted before the editor starts unloading an extension.
+ [b]Note:[/b] This signal is only emitted in editor builds.
+ </description>
+ </signal>
<signal name="extensions_reloaded">
<description>
Emitted after the editor has finished reloading one or more extensions.
diff --git a/doc/classes/ImporterMesh.xml b/doc/classes/ImporterMesh.xml
index eeabcd6e12..28ee5710d9 100644
--- a/doc/classes/ImporterMesh.xml
+++ b/doc/classes/ImporterMesh.xml
@@ -191,8 +191,4 @@
</description>
</method>
</methods>
- <members>
- <member name="_data" type="Dictionary" setter="_set_data" getter="_get_data" default="{ &quot;surfaces&quot;: [] }">
- </member>
- </members>
</class>
diff --git a/doc/classes/JSON.xml b/doc/classes/JSON.xml
index 8a19aa39bf..fe5fdfa89a 100644
--- a/doc/classes/JSON.xml
+++ b/doc/classes/JSON.xml
@@ -37,6 +37,16 @@
<tutorials>
</tutorials>
<methods>
+ <method name="from_native" qualifiers="static">
+ <return type="Variant" />
+ <param index="0" name="variant" type="Variant" />
+ <param index="1" name="allow_classes" type="bool" default="false" />
+ <param index="2" name="allow_scripts" type="bool" default="false" />
+ <description>
+ Converts a native engine type to a JSON-compliant dictionary.
+ By default, classes and scripts are ignored for security reasons, unless [param allow_classes] or [param allow_scripts] are specified.
+ </description>
+ </method>
<method name="get_error_line" qualifiers="const">
<return type="int" />
<description>
@@ -123,6 +133,16 @@
[/codeblock]
</description>
</method>
+ <method name="to_native" qualifiers="static">
+ <return type="Variant" />
+ <param index="0" name="json" type="Variant" />
+ <param index="1" name="allow_classes" type="bool" default="false" />
+ <param index="2" name="allow_scripts" type="bool" default="false" />
+ <description>
+ Converts a JSON-compliant dictionary that was created with [method from_native] back to native engine types.
+ By default, classes and scripts are ignored for security reasons, unless [param allow_classes] or [param allow_scripts] are specified.
+ </description>
+ </method>
</methods>
<members>
<member name="data" type="Variant" setter="set_data" getter="get_data" default="null">
diff --git a/doc/classes/JavaClass.xml b/doc/classes/JavaClass.xml
index ecfcaa8781..9a6c30df10 100644
--- a/doc/classes/JavaClass.xml
+++ b/doc/classes/JavaClass.xml
@@ -1,13 +1,33 @@
<?xml version="1.0" encoding="UTF-8" ?>
<class name="JavaClass" inherits="RefCounted" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../class.xsd">
<brief_description>
- Represents an object from the Java Native Interface.
+ Represents a class from the Java Native Interface.
</brief_description>
<description>
- Represents an object from the Java Native Interface. It is returned from [method JavaClassWrapper.wrap].
- [b]Note:[/b] This class only works on Android. For any other build, this class does nothing.
+ Represents a class from the Java Native Interface. It is returned from [method JavaClassWrapper.wrap].
+ [b]Note:[/b] This class only works on Android. On any other platform, this class does nothing.
[b]Note:[/b] This class is not to be confused with [JavaScriptObject].
</description>
<tutorials>
</tutorials>
+ <methods>
+ <method name="get_java_class_name" qualifiers="const">
+ <return type="String" />
+ <description>
+ Returns the Java class name.
+ </description>
+ </method>
+ <method name="get_java_method_list" qualifiers="const">
+ <return type="Dictionary[]" />
+ <description>
+ Returns the object's Java methods and their signatures as an [Array] of dictionaries, in the same format as [method Object.get_method_list].
+ </description>
+ </method>
+ <method name="get_java_parent_class" qualifiers="const">
+ <return type="JavaClass" />
+ <description>
+ Returns a [JavaClass] representing the Java parent class of this class.
+ </description>
+ </method>
+ </methods>
</class>
diff --git a/doc/classes/JavaClassWrapper.xml b/doc/classes/JavaClassWrapper.xml
index 01c3392b04..b43e149e9f 100644
--- a/doc/classes/JavaClassWrapper.xml
+++ b/doc/classes/JavaClassWrapper.xml
@@ -6,6 +6,15 @@
<description>
The JavaClassWrapper singleton provides a way for the Godot application to send and receive data through the [url=https://developer.android.com/training/articles/perf-jni]Java Native Interface[/url] (JNI).
[b]Note:[/b] This singleton is only available in Android builds.
+ [codeblock]
+ var LocalDateTime = JavaClassWrapper.wrap("java.time.LocalDateTime")
+ var DateTimeFormatter = JavaClassWrapper.wrap("java.time.format.DateTimeFormatter")
+
+ var datetime = LocalDateTime.now()
+ var formatter = DateTimeFormatter.ofPattern("dd-MM-yyyy HH:mm:ss")
+
+ print(datetime.format(formatter))
+ [/codeblock]
</description>
<tutorials>
</tutorials>
diff --git a/doc/classes/JavaObject.xml b/doc/classes/JavaObject.xml
new file mode 100644
index 0000000000..f38070e7d9
--- /dev/null
+++ b/doc/classes/JavaObject.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<class name="JavaObject" inherits="RefCounted" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../class.xsd">
+ <brief_description>
+ Represents an object from the Java Native Interface.
+ </brief_description>
+ <description>
+ Represents an object from the Java Native Interface. It can be returned from Java methods called on [JavaClass] or other [JavaObject]s. See [JavaClassWrapper] for an example.
+ [b]Note:[/b] This class only works on Android. On any other platform, this class does nothing.
+ [b]Note:[/b] This class is not to be confused with [JavaScriptObject].
+ </description>
+ <tutorials>
+ </tutorials>
+ <methods>
+ <method name="get_java_class" qualifiers="const">
+ <return type="JavaClass" />
+ <description>
+ Returns the [JavaClass] that this object is an instance of.
+ </description>
+ </method>
+ </methods>
+</class>
diff --git a/doc/classes/Label.xml b/doc/classes/Label.xml
index 8acd05cbd1..e6eba30ab7 100644
--- a/doc/classes/Label.xml
+++ b/doc/classes/Label.xml
@@ -59,7 +59,7 @@
Controls the text's horizontal alignment. Supports left, center, right, and fill, or justify. Set it to one of the [enum HorizontalAlignment] constants.
</member>
<member name="justification_flags" type="int" setter="set_justification_flags" getter="get_justification_flags" enum="TextServer.JustificationFlag" is_bitfield="true" default="163">
- Line fill alignment rules. For more info see [enum TextServer.JustificationFlag].
+ Line fill alignment rules. See [enum TextServer.JustificationFlag] for more information.
</member>
<member name="label_settings" type="LabelSettings" setter="set_label_settings" getter="get_label_settings">
A [LabelSettings] resource that can be shared between multiple [Label] nodes. Takes priority over theme properties.
diff --git a/doc/classes/Label3D.xml b/doc/classes/Label3D.xml
index 4c70897452..ff26c5490d 100644
--- a/doc/classes/Label3D.xml
+++ b/doc/classes/Label3D.xml
@@ -73,7 +73,7 @@
Controls the text's horizontal alignment. Supports left, center, right, and fill, or justify. Set it to one of the [enum HorizontalAlignment] constants.
</member>
<member name="justification_flags" type="int" setter="set_justification_flags" getter="get_justification_flags" enum="TextServer.JustificationFlag" is_bitfield="true" default="163">
- Line fill alignment rules. For more info see [enum TextServer.JustificationFlag].
+ Line fill alignment rules. See [enum TextServer.JustificationFlag] for more information.
</member>
<member name="language" type="String" setter="set_language" getter="get_language" default="&quot;&quot;">
Language code used for line-breaking and text shaping algorithms, if left empty current locale is used instead.
diff --git a/doc/classes/MenuBar.xml b/doc/classes/MenuBar.xml
index 9e4287331c..bcf63f0610 100644
--- a/doc/classes/MenuBar.xml
+++ b/doc/classes/MenuBar.xml
@@ -1,10 +1,10 @@
<?xml version="1.0" encoding="UTF-8" ?>
<class name="MenuBar" inherits="Control" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../class.xsd">
<brief_description>
- A horizontal menu bar that creates a [MenuButton] for each [PopupMenu] child.
+ A horizontal menu bar that creates a menu for each [PopupMenu] child.
</brief_description>
<description>
- A horizontal menu bar that creates a [MenuButton] for each [PopupMenu] child. New items are created by adding [PopupMenu]s to this node.
+ A horizontal menu bar that creates a menu for each [PopupMenu] child. New items are created by adding [PopupMenu]s to this node.
</description>
<tutorials>
</tutorials>
@@ -105,9 +105,11 @@
</member>
<member name="prefer_global_menu" type="bool" setter="set_prefer_global_menu" getter="is_prefer_global_menu" default="true">
If [code]true[/code], [MenuBar] will use system global menu when supported.
+ [b]Note:[/b] If [code]true[/code] and global menu is supported, this node is not displayed, has zero size, and all its child nodes except [PopupMenu]s are inaccessible.
+ [b]Note:[/b] This property overrides the value of the [member PopupMenu.prefer_native_menu] property of the child nodes.
</member>
<member name="start_index" type="int" setter="set_start_index" getter="get_start_index" default="-1">
- Position in the global menu to insert first [MenuBar] item at.
+ Position order in the global menu to insert [MenuBar] items at. All menu items in the [MenuBar] are always inserted as a continuous range. Menus with lower [member start_index] are inserted first. Menus with [member start_index] equal to [code]-1[/code] are inserted last.
</member>
<member name="switch_on_hover" type="bool" setter="set_switch_on_hover" getter="is_switch_on_hover" default="true">
If [code]true[/code], when the cursor hovers above menu item, it will close the current [PopupMenu] and open the other one.
diff --git a/doc/classes/MeshInstance3D.xml b/doc/classes/MeshInstance3D.xml
index abbb4c4eeb..d8e2c43566 100644
--- a/doc/classes/MeshInstance3D.xml
+++ b/doc/classes/MeshInstance3D.xml
@@ -21,6 +21,14 @@
[b]Performance:[/b] [Mesh] data needs to be received from the GPU, stalling the [RenderingServer] in the process.
</description>
</method>
+ <method name="bake_mesh_from_current_skeleton_pose">
+ <return type="ArrayMesh" />
+ <param index="0" name="existing" type="ArrayMesh" default="null" />
+ <description>
+ Takes a snapshot of the current animated skeleton pose of the skinned mesh and bakes it to the provided [param existing] mesh. If no [param existing] mesh is provided a new [ArrayMesh] is created, baked, and returned. Requires a skeleton with a registered skin to work. Blendshapes are ignored. Mesh surface materials are not copied.
+ [b]Performance:[/b] [Mesh] data needs to be retrieved from the GPU, stalling the [RenderingServer] in the process.
+ </description>
+ </method>
<method name="create_convex_collision">
<return type="void" />
<param index="0" name="clean" type="bool" default="true" />
diff --git a/doc/classes/OS.xml b/doc/classes/OS.xml
index 9675f5af50..777950c075 100644
--- a/doc/classes/OS.xml
+++ b/doc/classes/OS.xml
@@ -132,8 +132,10 @@
<return type="Dictionary" />
<param index="0" name="path" type="String" />
<param index="1" name="arguments" type="PackedStringArray" />
+ <param index="2" name="blocking" type="bool" default="true" />
<description>
Creates a new process that runs independently of Godot with redirected IO. It will not terminate when Godot terminates. The path specified in [param path] must exist and be an executable file or macOS [code].app[/code] bundle. The path is resolved based on the current platform. The [param arguments] are used in the given order and separated by a space.
+ If [param blocking] is [code]false[/code], created pipes work in non-blocking mode, i.e. read and write operations will return immediately. Use [method FileAccess.get_error] to check if the last read/write operation was successful.
If the process cannot be created, this method returns an empty [Dictionary]. Otherwise, this method returns a [Dictionary] with the following keys:
- [code]"stdio"[/code] - [FileAccess] to access the process stdin and stdout pipes (read/write).
- [code]"stderr"[/code] - [FileAccess] to access the process stderr pipe (read only).
diff --git a/doc/classes/Object.xml b/doc/classes/Object.xml
index 0cfa3a5d4a..a331c05e47 100644
--- a/doc/classes/Object.xml
+++ b/doc/classes/Object.xml
@@ -192,6 +192,55 @@
[b]Note:[/b] If [method _init] is defined with [i]required[/i] parameters, the Object with script may only be created directly. If any other means (such as [method PackedScene.instantiate] or [method Node.duplicate]) are used, the script's initialization will fail.
</description>
</method>
+ <method name="_iter_get" qualifiers="virtual">
+ <return type="Variant" />
+ <param index="0" name="iter" type="Variant" />
+ <description>
+ Returns the current iterable value. [param iter] stores the iteration state, but unlike [method _iter_init] and [method _iter_next] the state is supposed to be read-only, so there is no [Array] wrapper.
+ </description>
+ </method>
+ <method name="_iter_init" qualifiers="virtual">
+ <return type="bool" />
+ <param index="0" name="iter" type="Array" />
+ <description>
+ Initializes the iterator. [param iter] stores the iteration state. Since GDScript does not support passing arguments by reference, a single-element array is used as a wrapper. Returns [code]true[/code] so long as the iterator has not reached the end.
+ Example:
+ [codeblock]
+ class MyRange:
+ var _from
+ var _to
+
+ func _init(from, to):
+ assert(from &lt;= to)
+ _from = from
+ _to = to
+
+ func _iter_init(iter):
+ iter[0] = _from
+ return iter[0] &lt; _to
+
+ func _iter_next(iter):
+ iter[0] += 1
+ return iter[0] &lt; _to
+
+ func _iter_get(iter):
+ return iter
+
+ func _ready():
+ var my_range = MyRange.new(2, 5)
+ for x in my_range:
+ print(x) # Prints 2, 3, 4.
+ [/codeblock]
+ [b]Note:[/b] Alternatively, you can ignore [param iter] and use the object's state instead, see [url=$DOCS_URL/tutorials/scripting/gdscript/gdscript_advanced.html#custom-iterators]online docs[/url] for an example. Note that in this case you will not be able to reuse the same iterator instance in nested loops. Also, make sure you reset the iterator state in this method if you want to reuse the same instance multiple times.
+ </description>
+ </method>
+ <method name="_iter_next" qualifiers="virtual">
+ <return type="bool" />
+ <param index="0" name="iter" type="Array" />
+ <description>
+ Moves the iterator to the next iteration. [param iter] stores the iteration state. Since GDScript does not support passing arguments by reference, a single-element array is used as a wrapper. Returns [code]true[/code] so long as the iterator has not reached the end.
+ </description>
+ </method>
<method name="_notification" qualifiers="virtual">
<return type="void" />
<param index="0" name="what" type="int" />
diff --git a/doc/classes/PackedScene.xml b/doc/classes/PackedScene.xml
index 26d8fa8d5f..415e468e21 100644
--- a/doc/classes/PackedScene.xml
+++ b/doc/classes/PackedScene.xml
@@ -103,12 +103,6 @@
</description>
</method>
</methods>
- <members>
- <member name="_bundled" type="Dictionary" setter="_set_bundled_scene" getter="_get_bundled_scene" default="{ &quot;conn_count&quot;: 0, &quot;conns&quot;: PackedInt32Array(), &quot;editable_instances&quot;: [], &quot;names&quot;: PackedStringArray(), &quot;node_count&quot;: 0, &quot;node_paths&quot;: [], &quot;nodes&quot;: PackedInt32Array(), &quot;variants&quot;: [], &quot;version&quot;: 3 }">
- A dictionary representation of the scene contents.
- Available keys include "names" and "variants" for resources, "node_count", "nodes", "node_paths" for nodes, "editable_instances" for paths to overridden nodes, "conn_count" and "conns" for signal connections, and "version" for the format style of the PackedScene.
- </member>
- </members>
<constants>
<constant name="GEN_EDIT_STATE_DISABLED" value="0" enum="GenEditState">
If passed to [method instantiate], blocks edits to the scene state.
diff --git a/doc/classes/ParticleProcessMaterial.xml b/doc/classes/ParticleProcessMaterial.xml
index 1502690b45..28c60194c8 100644
--- a/doc/classes/ParticleProcessMaterial.xml
+++ b/doc/classes/ParticleProcessMaterial.xml
@@ -207,6 +207,10 @@
<member name="emission_ring_axis" type="Vector3" setter="set_emission_ring_axis" getter="get_emission_ring_axis">
The axis of the ring when using the emitter [constant EMISSION_SHAPE_RING].
</member>
+ <member name="emission_ring_cone_angle" type="float" setter="set_emission_ring_cone_angle" getter="get_emission_ring_cone_angle">
+ The angle of the cone when using the emitter [constant EMISSION_SHAPE_RING]. The default angle of 90 degrees results in a ring, while an angle of 0 degrees results in a cone. Intermediate values will result in a ring where one end is larger than the other.
+ [b]Note:[/b] Depending on [member emission_ring_height], the angle may be clamped if the ring's end is reached to form a perfect cone.
+ </member>
<member name="emission_ring_height" type="float" setter="set_emission_ring_height" getter="get_emission_ring_height">
The height of the ring when using the emitter [constant EMISSION_SHAPE_RING].
</member>
diff --git a/doc/classes/PathFollow2D.xml b/doc/classes/PathFollow2D.xml
index 61db34fb02..7444bfc23a 100644
--- a/doc/classes/PathFollow2D.xml
+++ b/doc/classes/PathFollow2D.xml
@@ -26,6 +26,7 @@
</member>
<member name="progress_ratio" type="float" setter="set_progress_ratio" getter="get_progress_ratio" default="0.0">
The distance along the path as a number in the range 0.0 (for the first vertex) to 1.0 (for the last). This is just another way of expressing the progress within the path, as the offset supplied is multiplied internally by the path's length.
+ It can be set or get only if the [PathFollow2D] is the child of a [Path2D] which is part of the scene tree, and that this [Path2D] has a [Curve2D] with a non-zero length. Otherwise, trying to set this field will print an error, and getting this field will return [code]0.0[/code].
</member>
<member name="rotates" type="bool" setter="set_rotates" getter="is_rotating" default="true">
If [code]true[/code], this node rotates to follow the path, with the +X direction facing forward on the path.
diff --git a/doc/classes/PathFollow3D.xml b/doc/classes/PathFollow3D.xml
index b505c6ea8b..bcd04d51ca 100644
--- a/doc/classes/PathFollow3D.xml
+++ b/doc/classes/PathFollow3D.xml
@@ -36,6 +36,7 @@
</member>
<member name="progress_ratio" type="float" setter="set_progress_ratio" getter="get_progress_ratio" default="0.0">
The distance from the first vertex, considering 0.0 as the first vertex and 1.0 as the last. This is just another way of expressing the progress within the path, as the progress supplied is multiplied internally by the path's length.
+ It can be set or get only if the [PathFollow3D] is the child of a [Path3D] which is part of the scene tree, and that this [Path3D] has a [Curve3D] with a non-zero length. Otherwise, trying to set this field will print an error, and getting this field will return [code]0.0[/code].
</member>
<member name="rotation_mode" type="int" setter="set_rotation_mode" getter="get_rotation_mode" enum="PathFollow3D.RotationMode" default="3">
Allows or forbids rotation on one or more axes, depending on the [enum RotationMode] constants being used.
diff --git a/doc/classes/PolygonPathFinder.xml b/doc/classes/PolygonPathFinder.xml
index f37a8a05e4..b70633883c 100644
--- a/doc/classes/PolygonPathFinder.xml
+++ b/doc/classes/PolygonPathFinder.xml
@@ -42,6 +42,30 @@
<return type="bool" />
<param index="0" name="point" type="Vector2" />
<description>
+ Returns [code]true[/code] if [param point] falls inside the polygon area.
+ [codeblocks]
+ [gdscript]
+ var polygon_path_finder = PolygonPathFinder.new()
+ var points = [Vector2(0.0, 0.0), Vector2(1.0, 0.0), Vector2(0.0, 1.0)]
+ var connections = [0, 1, 1, 2, 2, 0]
+ polygon_path_finder.setup(points, connections)
+ print(polygon_path_finder.is_point_inside(Vector2(0.2, 0.2))) # Prints true
+ print(polygon_path_finder.is_point_inside(Vector2(1.0, 1.0))) # Prints false
+ [/gdscript]
+ [csharp]
+ var polygonPathFinder = new PolygonPathFinder();
+ var points = new Vector2[]
+ {
+ new Vector2(0.0f, 0.0f),
+ new Vector2(1.0f, 0.0f),
+ new Vector2(0.0f, 1.0f)
+ };
+ var connections = new int[] { 0, 1, 1, 2, 2, 0 };
+ polygonPathFinder.Setup(points, connections);
+ GD.Print(polygonPathFinder.IsPointInside(new Vector2(0.2f, 0.2f))); // Prints true
+ GD.Print(polygonPathFinder.IsPointInside(new Vector2(1.0f, 1.0f))); // Prints false
+ [/csharp]
+ [/codeblocks]
</description>
</method>
<method name="set_point_penalty">
@@ -56,6 +80,27 @@
<param index="0" name="points" type="PackedVector2Array" />
<param index="1" name="connections" type="PackedInt32Array" />
<description>
+ Sets up [PolygonPathFinder] with an array of points that define the vertices of the polygon, and an array of indices that determine the edges of the polygon.
+ The length of [param connections] must be even, returns an error if odd.
+ [codeblocks]
+ [gdscript]
+ var polygon_path_finder = PolygonPathFinder.new()
+ var points = [Vector2(0.0, 0.0), Vector2(1.0, 0.0), Vector2(0.0, 1.0)]
+ var connections = [0, 1, 1, 2, 2, 0]
+ polygon_path_finder.setup(points, connections)
+ [/gdscript]
+ [csharp]
+ var polygonPathFinder = new PolygonPathFinder();
+ var points = new Vector2[]
+ {
+ new Vector2(0.0f, 0.0f),
+ new Vector2(1.0f, 0.0f),
+ new Vector2(0.0f, 1.0f)
+ };
+ var connections = new int[] { 0, 1, 1, 2, 2, 0 };
+ polygonPathFinder.Setup(points, connections);
+ [/csharp]
+ [/codeblocks]
</description>
</method>
</methods>
diff --git a/doc/classes/PortableCompressedTexture2D.xml b/doc/classes/PortableCompressedTexture2D.xml
index 3fc2aa2ab9..b85fe1de5c 100644
--- a/doc/classes/PortableCompressedTexture2D.xml
+++ b/doc/classes/PortableCompressedTexture2D.xml
@@ -52,8 +52,6 @@
</method>
</methods>
<members>
- <member name="_data" type="PackedByteArray" setter="_set_data" getter="_get_data" default="PackedByteArray()">
- </member>
<member name="keep_compressed_buffer" type="bool" setter="set_keep_compressed_buffer" getter="is_keeping_compressed_buffer" default="false">
When running on the editor, this class will keep the source compressed data in memory. Otherwise, the source compressed data is lost after loading and the resource can't be re saved.
This flag allows to keep the compressed data in memory if you intend it to persist after loading.
diff --git a/doc/classes/ProjectSettings.xml b/doc/classes/ProjectSettings.xml
index 30cbbbf799..497070fa81 100644
--- a/doc/classes/ProjectSettings.xml
+++ b/doc/classes/ProjectSettings.xml
@@ -528,6 +528,9 @@
<member name="debug/gdscript/warnings/integer_division" type="int" setter="" getter="" default="1">
When set to [code]warn[/code] or [code]error[/code], produces a warning or an error respectively when dividing an integer by another integer (the decimal part will be discarded).
</member>
+ <member name="debug/gdscript/warnings/missing_tool" type="int" setter="" getter="" default="1">
+ When set to [code]warn[/code] or [code]error[/code], produces a warning or an error respectively when the base class script has the [code]@tool[/code] annotation, but the current class script does not have it.
+ </member>
<member name="debug/gdscript/warnings/narrowing_conversion" type="int" setter="" getter="" default="1">
When set to [code]warn[/code] or [code]error[/code], produces a warning or an error respectively when passing a floating-point value to a function that expects an integer (it will be converted and lose precision).
</member>
@@ -2906,7 +2909,9 @@
The default compression factor for lossless WebP. Decompression speed is mostly unaffected by the compression factor. Supported values are 0 to 100.
</member>
<member name="rendering/viewport/hdr_2d" type="bool" setter="" getter="" default="false">
- If [code]true[/code], enables [member Viewport.use_hdr_2d] on the root viewport. This allows 2D rendering to take advantage of effects requiring high dynamic range (e.g. 2D glow).
+ If [code]true[/code], enables [member Viewport.use_hdr_2d] on the root viewport. 2D rendering will use an high dynamic range (HDR) format framebuffer matching the bit depth of the 3D framebuffer. When using the Forward+ renderer this will be an [code]RGBA16[/code] framebuffer, while when using the Mobile renderer it will be an [code]RGB10_A2[/code] framebuffer. Additionally, 2D rendering will take place in linear color space and will be converted to sRGB space immediately before blitting to the screen. Practically speaking, this means that the end result of the Viewport will not be clamped into the [code]0-1[/code] range and can be used in 3D rendering without color space adjustments. This allows 2D rendering to take advantage of effects requiring high dynamic range (e.g. 2D glow) as well as substantially improves the appearance of effects requiring highly detailed gradients.
+ [b]Note:[/b] This setting will have no effect when using the GL Compatibility renderer as the GL Compatibility renderer always renders in low dynamic range for performance reasons.
+ [b]Note:[/b] This property is only read when the project starts. To toggle HDR 2D at runtime, set [member Viewport.use_hdr_2d] on the root [Viewport].
</member>
<member name="rendering/viewport/transparent_background" type="bool" setter="" getter="" default="false">
If [code]true[/code], enables [member Viewport.transparent_bg] on the root viewport. This allows per-pixel transparency to be effective after also enabling [member display/window/size/transparent] and [member display/window/per_pixel_transparency/allowed].
diff --git a/doc/classes/PropertyTweener.xml b/doc/classes/PropertyTweener.xml
index b7aa6947d9..76cf4cbfeb 100644
--- a/doc/classes/PropertyTweener.xml
+++ b/doc/classes/PropertyTweener.xml
@@ -5,6 +5,7 @@
</brief_description>
<description>
[PropertyTweener] is used to interpolate a property in an object. See [method Tween.tween_property] for more usage information.
+ The tweener will finish automatically if the target object is freed.
[b]Note:[/b] [method Tween.tween_property] is the only correct way to create [PropertyTweener]. Any [PropertyTweener] created manually will not function correctly.
</description>
<tutorials>
diff --git a/doc/classes/RenderingServer.xml b/doc/classes/RenderingServer.xml
index c81d5d4fab..cea9a4cec4 100644
--- a/doc/classes/RenderingServer.xml
+++ b/doc/classes/RenderingServer.xml
@@ -329,6 +329,7 @@
<param index="4" name="texture" type="RID" default="RID()" />
<description>
Draws a 2D polygon on the [CanvasItem] pointed to by the [param item] [RID]. If you need more flexibility (such as being able to use bones), use [method canvas_item_add_triangle_array] instead. See also [method CanvasItem.draw_polygon].
+ [b]Note:[/b] If you frequently redraw the same polygon with a large number of vertices, consider pre-calculating the triangulation with [method Geometry2D.triangulate_polygon] and using [method CanvasItem.draw_mesh], [method CanvasItem.draw_multimesh], or [method canvas_item_add_triangle_array].
</description>
</method>
<method name="canvas_item_add_polyline">
@@ -412,6 +413,14 @@
[b]Note:[/b] [param count] is unused and can be left unspecified.
</description>
</method>
+ <method name="canvas_item_attach_skeleton">
+ <return type="void" />
+ <param index="0" name="item" type="RID" />
+ <param index="1" name="skeleton" type="RID" />
+ <description>
+ Attaches a skeleton to the [CanvasItem]. Removes the previous skeleton.
+ </description>
+ </method>
<method name="canvas_item_clear">
<return type="void" />
<param index="0" name="item" type="RID" />
diff --git a/doc/classes/ResourceImporterWAV.xml b/doc/classes/ResourceImporterWAV.xml
index 8f118ace03..3caa66d262 100644
--- a/doc/classes/ResourceImporterWAV.xml
+++ b/doc/classes/ResourceImporterWAV.xml
@@ -4,17 +4,18 @@
Imports a WAV audio file for playback.
</brief_description>
<description>
- WAV is an uncompressed format, which can provide higher quality compared to Ogg Vorbis and MP3. It also has the lowest CPU cost to decode. This means high numbers of WAV sounds can be played at the same time, even on low-end deviceS.
+ WAV is an uncompressed format, which can provide higher quality compared to Ogg Vorbis and MP3. It also has the lowest CPU cost to decode. This means high numbers of WAV sounds can be played at the same time, even on low-end devices.
+ By default, Godot imports WAV files using the lossy Quite OK Audio compression. You may change this by setting the [member compress/mode] property.
</description>
<tutorials>
<link title="Importing audio samples">$DOCS_URL/tutorials/assets_pipeline/importing_audio_samples.html</link>
</tutorials>
<members>
- <member name="compress/mode" type="int" setter="" getter="" default="0">
+ <member name="compress/mode" type="int" setter="" getter="" default="2">
The compression mode to use on import.
- [b]Disabled:[/b] Imports audio data without any compression. This results in the highest possible quality.
- [b]RAM (Ima-ADPCM):[/b] Performs fast lossy compression on import. Low CPU cost, but quality is noticeably decreased compared to Ogg Vorbis or even MP3.
- [b]QOA ([url=https://qoaformat.org/]Quite OK Audio[/url]):[/b] Performs lossy compression on import. CPU cost is slightly higher than IMA-ADPCM, but quality is much higher.
+ - [b]PCM (Uncompressed):[/b] Imports audio data without any form of compression, preserving the highest possible quality. It has the lowest CPU cost, but the highest memory usage.
+ - [b]IMA ADPCM:[/b] Applies fast, lossy compression during import, noticeably decreasing the quality, but with low CPU cost and memory usage. Does not support seeking and only Forward loop mode is supported.
+ - [b][url=https://qoaformat.org/]Quite OK Audio[/url]:[/b] Also applies lossy compression on import, having a slightly higher CPU cost compared to IMA ADPCM, but much higher quality and the lowest memory usage.
</member>
<member name="edit/loop_begin" type="int" setter="" getter="" default="0">
The begin loop point to use when [member edit/loop_mode] is [b]Forward[/b], [b]Ping-Pong[/b], or [b]Backward[/b]. This is set in samples after the beginning of the audio file.
@@ -23,11 +24,12 @@
The end loop point to use when [member edit/loop_mode] is [b]Forward[/b], [b]Ping-Pong[/b], or [b]Backward[/b]. This is set in samples after the beginning of the audio file. A value of [code]-1[/code] uses the end of the audio file as the end loop point.
</member>
<member name="edit/loop_mode" type="int" setter="" getter="" default="0">
- Controls how audio should loop. This is automatically read from the WAV metadata on import.
- [b]Disabled:[/b] Don't loop audio, even if metadata indicates the file should be played back looping.
- [b]Forward:[/b] Standard audio looping.
- [b]Ping-Pong:[/b] Play audio forward until it's done playing, then play it backward and repeat. This is similar to mirrored texture repeat, but for audio.
- [b]Backward:[/b] Play audio in reverse and loop back to the end when done playing.
+ Controls how audio should loop.
+ - [b]Detect From WAV:[/b] Uses loop information from the WAV metadata.
+ - [b]Disabled:[/b] Don't loop audio, even if the metadata indicates the file playback should loop.
+ - [b]Forward:[/b] Standard audio looping. Plays the audio forward from the beginning to [member edit/loop_end], then returns to [member edit/loop_begin] and repeats.
+ - [b]Ping-Pong:[/b] Plays the audio forward until [member edit/loop_end], then backwards to [member edit/loop_begin], repeating this cycle.
+ - [b]Backward:[/b] Plays the audio backwards from [member edit/loop_end] to [member edit/loop_begin], then repeats.
[b]Note:[/b] In [AudioStreamPlayer], the [signal AudioStreamPlayer.finished] signal won't be emitted for looping audio when it reaches the end of the audio file, as the audio will keep playing indefinitely.
</member>
<member name="edit/normalize" type="bool" setter="" getter="" default="false">
diff --git a/doc/classes/ResourceLoader.xml b/doc/classes/ResourceLoader.xml
index cb0db46595..56c3208fc3 100644
--- a/doc/classes/ResourceLoader.xml
+++ b/doc/classes/ResourceLoader.xml
@@ -31,6 +31,14 @@
[b]Note:[/b] If you use [method Resource.take_over_path], this method will return [code]true[/code] for the taken path even if the resource wasn't saved (i.e. exists only in resource cache).
</description>
</method>
+ <method name="get_cached_ref">
+ <return type="Resource" />
+ <param index="0" name="path" type="String" />
+ <description>
+ Returns the cached resource reference for the given [param path].
+ [b]Note:[/b] If the resource is not cached, the returned [Resource] will be invalid.
+ </description>
+ </method>
<method name="get_dependencies">
<return type="PackedStringArray" />
<param index="0" name="path" type="String" />
diff --git a/doc/classes/ResourceUID.xml b/doc/classes/ResourceUID.xml
index 8b990b132a..24853d462d 100644
--- a/doc/classes/ResourceUID.xml
+++ b/doc/classes/ResourceUID.xml
@@ -4,7 +4,7 @@
A singleton that manages the unique identifiers of all resources within a project.
</brief_description>
<description>
- Resource UIDs (Unique IDentifiers) allow the engine to keep references between resources intact, even if files can renamed or moved. They can be accessed with [code]uid://[/code].
+ Resource UIDs (Unique IDentifiers) allow the engine to keep references between resources intact, even if files are renamed or moved. They can be accessed with [code]uid://[/code].
[ResourceUID] keeps track of all registered resource UIDs in a project, generates new UIDs, and converts between their string and integer representations.
</description>
<tutorials>
diff --git a/doc/classes/RichTextLabel.xml b/doc/classes/RichTextLabel.xml
index 9a772835a6..4a2cbbc3d8 100644
--- a/doc/classes/RichTextLabel.xml
+++ b/doc/classes/RichTextLabel.xml
@@ -631,6 +631,12 @@
<member name="hint_underlined" type="bool" setter="set_hint_underline" getter="is_hint_underlined" default="true">
If [code]true[/code], the label underlines hint tags such as [code skip-lint][hint=description]{text}[/hint][/code].
</member>
+ <member name="horizontal_alignment" type="int" setter="set_horizontal_alignment" getter="get_horizontal_alignment" enum="HorizontalAlignment" default="0">
+ Controls the text's horizontal alignment. Supports left, center, right, and fill, or justify. Set it to one of the [enum HorizontalAlignment] constants.
+ </member>
+ <member name="justification_flags" type="int" setter="set_justification_flags" getter="get_justification_flags" enum="TextServer.JustificationFlag" is_bitfield="true" default="163">
+ Line fill alignment rules. See [enum TextServer.JustificationFlag] for more information.
+ </member>
<member name="language" type="String" setter="set_language" getter="get_language" default="&quot;&quot;">
Language code used for line-breaking and text shaping algorithms, if left empty current locale is used instead.
</member>
@@ -662,6 +668,9 @@
<member name="tab_size" type="int" setter="set_tab_size" getter="get_tab_size" default="4">
The number of spaces associated with a single tab length. Does not affect [code]\t[/code] in text tags, only indent tags.
</member>
+ <member name="tab_stops" type="PackedFloat32Array" setter="set_tab_stops" getter="get_tab_stops" default="PackedFloat32Array()">
+ Aligns text to the given tab-stops.
+ </member>
<member name="text" type="String" setter="set_text" getter="get_text" default="&quot;&quot;">
The label's text in BBCode format. Is not representative of manual modifications to the internal tag stack. Erases changes made by other methods when edited.
[b]Note:[/b] If [member bbcode_enabled] is [code]true[/code], it is unadvised to use the [code]+=[/code] operator with [member text] (e.g. [code]text += "some string"[/code]) as it replaces the whole text and can cause slowdowns. It will also erase all BBCode that was added to stack using [code]push_*[/code] methods. Use [method append_text] for adding text instead, unless you absolutely need to close a tag that was opened in an earlier method call.
diff --git a/doc/classes/ScriptEditor.xml b/doc/classes/ScriptEditor.xml
index 50adecccf5..5cf077c266 100644
--- a/doc/classes/ScriptEditor.xml
+++ b/doc/classes/ScriptEditor.xml
@@ -10,6 +10,12 @@
<tutorials>
</tutorials>
<methods>
+ <method name="get_breakpoints">
+ <return type="PackedStringArray" />
+ <description>
+ Returns array of breakpoints.
+ </description>
+ </method>
<method name="get_current_editor" qualifiers="const">
<return type="ScriptEditorBase" />
<description>
diff --git a/doc/classes/SpinBox.xml b/doc/classes/SpinBox.xml
index 3fb30a81b8..6da6a301fe 100644
--- a/doc/classes/SpinBox.xml
+++ b/doc/classes/SpinBox.xml
@@ -98,12 +98,12 @@
Vertical separation between the up and down buttons.
</theme_item>
<theme_item name="buttons_width" data_type="constant" type="int" default="16">
- Width of the up and down buttons. If smaller than any icon set on the buttons, the respective icon may overlap neighboring elements, unless [theme_item set_min_buttons_width_from_icons] is different than [code]0[/code].
+ Width of the up and down buttons. If smaller than any icon set on the buttons, the respective icon may overlap neighboring elements. If smaller than [code]0[/code], the width is automatically adjusted from the icon size.
</theme_item>
<theme_item name="field_and_buttons_separation" data_type="constant" type="int" default="2">
Width of the horizontal separation between the text input field ([LineEdit]) and the buttons.
</theme_item>
- <theme_item name="set_min_buttons_width_from_icons" data_type="constant" type="int" default="1">
+ <theme_item name="set_min_buttons_width_from_icons" data_type="constant" type="int" default="1" deprecated="This property exists only for compatibility with older themes. Setting [theme_item buttons_width] to a negative value has the same effect.">
If not [code]0[/code], the minimum button width corresponds to the widest of all icons set on those buttons, even if [theme_item buttons_width] is smaller.
</theme_item>
<theme_item name="down" data_type="icon" type="Texture2D">
diff --git a/doc/classes/SpriteFrames.xml b/doc/classes/SpriteFrames.xml
index fd8c15c560..b891f4adcd 100644
--- a/doc/classes/SpriteFrames.xml
+++ b/doc/classes/SpriteFrames.xml
@@ -39,6 +39,14 @@
Removes all animations. An empty [code]default[/code] animation will be created.
</description>
</method>
+ <method name="duplicate_animation">
+ <return type="void" />
+ <param index="0" name="anim_from" type="StringName" />
+ <param index="1" name="anim_to" type="StringName" />
+ <description>
+ Duplicates the animation [param anim_from] to a new animation named [param anim_to]. Fails if [param anim_to] already exists, or if [param anim_from] does not exist.
+ </description>
+ </method>
<method name="get_animation_loop" qualifiers="const">
<return type="bool" />
<param index="0" name="anim" type="StringName" />
diff --git a/doc/classes/TextMesh.xml b/doc/classes/TextMesh.xml
index 9e705311c5..898d19aed3 100644
--- a/doc/classes/TextMesh.xml
+++ b/doc/classes/TextMesh.xml
@@ -31,7 +31,7 @@
Controls the text's horizontal alignment. Supports left, center, right, and fill, or justify. Set it to one of the [enum HorizontalAlignment] constants.
</member>
<member name="justification_flags" type="int" setter="set_justification_flags" getter="get_justification_flags" enum="TextServer.JustificationFlag" is_bitfield="true" default="163">
- Line fill alignment rules. For more info see [enum TextServer.JustificationFlag].
+ Line fill alignment rules. See [enum TextServer.JustificationFlag] for more information.
</member>
<member name="language" type="String" setter="set_language" getter="get_language" default="&quot;&quot;">
Language code used for text shaping algorithms, if left empty current locale is used instead.
diff --git a/doc/classes/TextParagraph.xml b/doc/classes/TextParagraph.xml
index c6511a2b8e..46197f19b8 100644
--- a/doc/classes/TextParagraph.xml
+++ b/doc/classes/TextParagraph.xml
@@ -278,7 +278,7 @@
Ellipsis character used for text clipping.
</member>
<member name="justification_flags" type="int" setter="set_justification_flags" getter="get_justification_flags" enum="TextServer.JustificationFlag" is_bitfield="true" default="163">
- Line fill alignment rules. For more info see [enum TextServer.JustificationFlag].
+ Line fill alignment rules. See [enum TextServer.JustificationFlag] for more information.
</member>
<member name="max_lines_visible" type="int" setter="set_max_lines_visible" getter="get_max_lines_visible" default="-1">
Limits the lines of text shown.
diff --git a/doc/classes/TileData.xml b/doc/classes/TileData.xml
index 91df90580c..f9505124ef 100644
--- a/doc/classes/TileData.xml
+++ b/doc/classes/TileData.xml
@@ -16,6 +16,13 @@
Adds a collision polygon to the tile on the given TileSet physics layer.
</description>
</method>
+ <method name="add_occluder_polygon">
+ <return type="void" />
+ <param index="0" name="layer_id" type="int" />
+ <description>
+ Adds an occlusion polygon to the tile on the TileSet occlusion layer with index [param layer_id].
+ </description>
+ </method>
<method name="get_collision_polygon_one_way_margin" qualifiers="const">
<return type="float" />
<param index="0" name="layer_id" type="int" />
@@ -78,7 +85,7 @@
[param flip_h], [param flip_v], and [param transpose] allow transforming the returned polygon.
</description>
</method>
- <method name="get_occluder" qualifiers="const">
+ <method name="get_occluder" qualifiers="const" deprecated="Use [method get_occluder_polygon] instead.">
<return type="OccluderPolygon2D" />
<param index="0" name="layer_id" type="int" />
<param index="1" name="flip_h" type="bool" default="false" />
@@ -89,6 +96,25 @@
[param flip_h], [param flip_v], and [param transpose] allow transforming the returned polygon.
</description>
</method>
+ <method name="get_occluder_polygon" qualifiers="const">
+ <return type="OccluderPolygon2D" />
+ <param index="0" name="layer_id" type="int" />
+ <param index="1" name="polygon_index" type="int" />
+ <param index="2" name="flip_h" type="bool" default="false" />
+ <param index="3" name="flip_v" type="bool" default="false" />
+ <param index="4" name="transpose" type="bool" default="false" />
+ <description>
+ Returns the occluder polygon at index [param polygon_index] from the TileSet occlusion layer with index [param layer_id].
+ The [param flip_h], [param flip_v], and [param transpose] parameters can be [code]true[/code] to transform the returned polygon.
+ </description>
+ </method>
+ <method name="get_occluder_polygons_count" qualifiers="const">
+ <return type="int" />
+ <param index="0" name="layer_id" type="int" />
+ <description>
+ Returns the number of occluder polygons of the tile in the TileSet occlusion layer with index [param layer_id].
+ </description>
+ </method>
<method name="get_terrain_peering_bit" qualifiers="const">
<return type="int" />
<param index="0" name="peering_bit" type="int" enum="TileSet.CellNeighbor" />
@@ -119,6 +145,14 @@
Removes the polygon at index [param polygon_index] for TileSet physics layer with index [param layer_id].
</description>
</method>
+ <method name="remove_occluder_polygon">
+ <return type="void" />
+ <param index="0" name="layer_id" type="int" />
+ <param index="1" name="polygon_index" type="int" />
+ <description>
+ Removes the polygon at index [param polygon_index] for TileSet occlusion layer with index [param layer_id].
+ </description>
+ </method>
<method name="set_collision_polygon_one_way">
<return type="void" />
<param index="0" name="layer_id" type="int" />
@@ -134,7 +168,7 @@
<param index="1" name="polygon_index" type="int" />
<param index="2" name="one_way_margin" type="float" />
<description>
- Enables/disables one-way collisions on the polygon at index [param polygon_index] for TileSet physics layer with index [param layer_id].
+ Sets the one-way margin (for one-way platforms) of the polygon at index [param polygon_index] for TileSet physics layer with index [param layer_id].
</description>
</method>
<method name="set_collision_polygon_points">
@@ -194,7 +228,7 @@
Sets the navigation polygon for the TileSet navigation layer with index [param layer_id].
</description>
</method>
- <method name="set_occluder">
+ <method name="set_occluder" deprecated="Use [method set_occluder_polygon] instead.">
<return type="void" />
<param index="0" name="layer_id" type="int" />
<param index="1" name="occluder_polygon" type="OccluderPolygon2D" />
@@ -202,6 +236,23 @@
Sets the occluder for the TileSet occlusion layer with index [param layer_id].
</description>
</method>
+ <method name="set_occluder_polygon">
+ <return type="void" />
+ <param index="0" name="layer_id" type="int" />
+ <param index="1" name="polygon_index" type="int" />
+ <param index="2" name="polygon" type="OccluderPolygon2D" />
+ <description>
+ Sets the occluder for polygon with index [param polygon_index] in the TileSet occlusion layer with index [param layer_id].
+ </description>
+ </method>
+ <method name="set_occluder_polygons_count">
+ <return type="void" />
+ <param index="0" name="layer_id" type="int" />
+ <param index="1" name="polygons_count" type="int" />
+ <description>
+ Sets the occluder polygon count in the TileSet occlusion layer with index [param layer_id].
+ </description>
+ </method>
<method name="set_terrain_peering_bit">
<return type="void" />
<param index="0" name="peering_bit" type="int" enum="TileSet.CellNeighbor" />
diff --git a/doc/classes/TileMapLayer.xml b/doc/classes/TileMapLayer.xml
index bead1c32c0..135f85de69 100644
--- a/doc/classes/TileMapLayer.xml
+++ b/doc/classes/TileMapLayer.xml
@@ -284,6 +284,9 @@
<member name="navigation_visibility_mode" type="int" setter="set_navigation_visibility_mode" getter="get_navigation_visibility_mode" enum="TileMapLayer.DebugVisibilityMode" default="0">
Show or hide the [TileMapLayer]'s navigation meshes. If set to [constant DEBUG_VISIBILITY_MODE_DEFAULT], this depends on the show navigation debug settings.
</member>
+ <member name="occlusion_enabled" type="bool" setter="set_occlusion_enabled" getter="is_occlusion_enabled" default="true">
+ Enable or disable light occlusion.
+ </member>
<member name="rendering_quadrant_size" type="int" setter="set_rendering_quadrant_size" getter="get_rendering_quadrant_size" default="16">
The [TileMapLayer]'s quadrant size. A quadrant is a group of tiles to be drawn together on a single canvas item, for optimization purposes. [member rendering_quadrant_size] defines the length of a square's side, in the map's coordinate system, that forms the quadrant. Thus, the default quadrant size groups together [code]16 * 16 = 256[/code] tiles.
The quadrant size does not apply on a Y-sorted [TileMapLayer], as tiles are grouped by Y position instead in that case.
diff --git a/doc/classes/TreeItem.xml b/doc/classes/TreeItem.xml
index 78a703c213..bd46c54990 100644
--- a/doc/classes/TreeItem.xml
+++ b/doc/classes/TreeItem.xml
@@ -19,7 +19,7 @@
<param index="3" name="disabled" type="bool" default="false" />
<param index="4" name="tooltip_text" type="String" default="&quot;&quot;" />
<description>
- Adds a button with [Texture2D] [param button] at column [param column]. The [param id] is used to identify the button in the according [signal Tree.button_clicked] signal and can be different from the buttons index. If not specified, the next available index is used, which may be retrieved by calling [method get_button_count] immediately before this method. Optionally, the button can be [param disabled] and have a [param tooltip_text].
+ Adds a button with [Texture2D] [param button] to the end of the cell at column [param column]. The [param id] is used to identify the button in the according [signal Tree.button_clicked] signal and can be different from the buttons index. If not specified, the next available index is used, which may be retrieved by calling [method get_button_count] immediately before this method. Optionally, the button can be [param disabled] and have a [param tooltip_text].
</description>
</method>
<method name="add_child">
@@ -643,7 +643,7 @@
<param index="0" name="column" type="int" />
<param index="1" name="texture" type="Texture2D" />
<description>
- Sets the given cell's icon [Texture2D]. The cell has to be in [constant CELL_MODE_ICON] mode.
+ Sets the given cell's icon [Texture2D]. If the cell is in [constant CELL_MODE_ICON] mode, the icon is displayed in the center of the cell. Otherwise, the icon is displayed before the cell's text. [constant CELL_MODE_RANGE] does not display an icon.
</description>
</method>
<method name="set_icon_max_width">
@@ -811,17 +811,17 @@
</members>
<constants>
<constant name="CELL_MODE_STRING" value="0" enum="TreeCellMode">
- Cell shows a string label. When editable, the text can be edited using a [LineEdit], or a [TextEdit] popup if [method set_edit_multiline] is used.
+ Cell shows a string label, optionally with an icon. When editable, the text can be edited using a [LineEdit], or a [TextEdit] popup if [method set_edit_multiline] is used.
</constant>
<constant name="CELL_MODE_CHECK" value="1" enum="TreeCellMode">
- Cell shows a checkbox, optionally with text. The checkbox can be pressed, released, or indeterminate (via [method set_indeterminate]). The checkbox can't be clicked unless the cell is editable.
+ Cell shows a checkbox, optionally with text and an icon. The checkbox can be pressed, released, or indeterminate (via [method set_indeterminate]). The checkbox can't be clicked unless the cell is editable.
</constant>
<constant name="CELL_MODE_RANGE" value="2" enum="TreeCellMode">
Cell shows a numeric range. When editable, it can be edited using a range slider. Use [method set_range] to set the value and [method set_range_config] to configure the range.
This cell can also be used in a text dropdown mode when you assign a text with [method set_text]. Separate options with a comma, e.g. [code]"Option1,Option2,Option3"[/code].
</constant>
<constant name="CELL_MODE_ICON" value="3" enum="TreeCellMode">
- Cell shows an icon. It can't be edited nor display text.
+ Cell shows an icon. It can't be edited nor display text. The icon is always centered within the cell.
</constant>
<constant name="CELL_MODE_CUSTOM" value="4" enum="TreeCellMode">
Cell shows as a clickable button. It will display an arrow similar to [OptionButton], but doesn't feature a dropdown (for that you can use [constant CELL_MODE_RANGE]). Clicking the button emits the [signal Tree.item_edited] signal. The button is flat by default, you can use [method set_custom_as_button] to display it with a [StyleBox].
diff --git a/doc/classes/Tweener.xml b/doc/classes/Tweener.xml
index 65148e875d..88f5f9978c 100644
--- a/doc/classes/Tweener.xml
+++ b/doc/classes/Tweener.xml
@@ -11,7 +11,7 @@
<signals>
<signal name="finished">
<description>
- Emitted when the [Tweener] has just finished its job.
+ Emitted when the [Tweener] has just finished its job or became invalid (e.g. due to a freed object).
</description>
</signal>
</signals>
diff --git a/doc/classes/VehicleWheel3D.xml b/doc/classes/VehicleWheel3D.xml
index 92b2297bf4..7b4952580c 100644
--- a/doc/classes/VehicleWheel3D.xml
+++ b/doc/classes/VehicleWheel3D.xml
@@ -18,6 +18,18 @@
Returns [code]null[/code] if the wheel is not in contact with a surface, or the contact body is not a [PhysicsBody3D].
</description>
</method>
+ <method name="get_contact_normal" qualifiers="const">
+ <return type="Vector3" />
+ <description>
+ Returns the normal of the suspension's collision in world space if the wheel is in contact. If the wheel isn't in contact with anything, returns a vector pointing directly along the suspension axis toward the vehicle in world space.
+ </description>
+ </method>
+ <method name="get_contact_point" qualifiers="const">
+ <return type="Vector3" />
+ <description>
+ Returns the point of the suspension's collision in world space if the wheel is in contact. If the wheel isn't in contact with anything, returns the maximum point of the wheel's ray cast in world space, which is defined by [code]wheel_rest_length + wheel_radius[/code].
+ </description>
+ </method>
<method name="get_rpm" qualifiers="const">
<return type="float" />
<description>
diff --git a/doc/classes/Viewport.xml b/doc/classes/Viewport.xml
index b24f26a764..350fd65197 100644
--- a/doc/classes/Viewport.xml
+++ b/doc/classes/Viewport.xml
@@ -117,6 +117,12 @@
Returns the visible rectangle in global screen coordinates.
</description>
</method>
+ <method name="gui_cancel_drag">
+ <return type="void" />
+ <description>
+ Cancels the drag operation that was previously started through [method Control._get_drag_data] or forced with [method Control.force_drag].
+ </description>
+ </method>
<method name="gui_get_drag_data" qualifiers="const">
<return type="Variant" />
<description>
diff --git a/doc/tools/make_rst.py b/doc/tools/make_rst.py
index 761a7f8f4a..101660881b 100755
--- a/doc/tools/make_rst.py
+++ b/doc/tools/make_rst.py
@@ -1311,9 +1311,6 @@ def make_rst_class(class_def: ClassDef, state: State, dry_run: bool, output_dir:
if property_def.text is not None and property_def.text.strip() != "":
f.write(f"{format_text_block(property_def.text.strip(), property_def, state)}\n\n")
- if property_def.type_name.type_name in PACKED_ARRAY_TYPES:
- tmp = f"[b]Note:[/b] The returned array is [i]copied[/i] and any changes to it will not update the original property value. See [{property_def.type_name.type_name}] for more details."
- f.write(f"{format_text_block(tmp, property_def, state)}\n\n")
elif property_def.deprecated is None and property_def.experimental is None:
f.write(".. container:: contribute\n\n\t")
f.write(
@@ -1323,6 +1320,11 @@ def make_rst_class(class_def: ClassDef, state: State, dry_run: bool, output_dir:
+ "\n\n"
)
+ # Add copy note to built-in properties returning `Packed*Array`.
+ if property_def.type_name.type_name in PACKED_ARRAY_TYPES:
+ copy_note = f"[b]Note:[/b] The returned array is [i]copied[/i] and any changes to it will not update the original property value. See [{property_def.type_name.type_name}] for more details."
+ f.write(f"{format_text_block(copy_note, property_def, state)}\n\n")
+
index += 1
# Constructor, Method, Operator descriptions
@@ -1507,24 +1509,23 @@ def make_type(klass: str, state: State) -> str:
if klass.find("*") != -1: # Pointer, ignore
return f"``{klass}``"
- link_type = klass
- is_array = False
+ def resolve_type(link_type: str) -> str:
+ if link_type in state.classes:
+ return f":ref:`{link_type}<class_{link_type}>`"
+ else:
+ print_error(f'{state.current_class}.xml: Unresolved type "{link_type}".', state)
+ return f"``{link_type}``"
- if link_type.endswith("[]"): # Typed array, strip [] to link to contained type.
- link_type = link_type[:-2]
- is_array = True
+ if klass.endswith("[]"): # Typed array, strip [] to link to contained type.
+ return f":ref:`Array<class_Array>`\\[{resolve_type(klass[:-len('[]')])}\\]"
- if link_type in state.classes:
- type_rst = f":ref:`{link_type}<class_{link_type}>`"
- if is_array:
- type_rst = f":ref:`Array<class_Array>`\\[{type_rst}\\]"
- return type_rst
+ if klass.startswith("Dictionary["): # Typed dictionary, split elements to link contained types.
+ parts = klass[len("Dictionary[") : -len("]")].partition(", ")
+ key = parts[0]
+ value = parts[2]
+ return f":ref:`Dictionary<class_Dictionary>`\\[{resolve_type(key)}, {resolve_type(value)}\\]"
- print_error(f'{state.current_class}.xml: Unresolved type "{link_type}".', state)
- type_rst = f"``{link_type}``"
- if is_array:
- type_rst = f":ref:`Array<class_Array>`\\[{type_rst}\\]"
- return type_rst
+ return resolve_type(klass)
def make_enum(t: str, is_bitfield: bool, state: State) -> str:
diff --git a/drivers/gles3/rasterizer_canvas_gles3.cpp b/drivers/gles3/rasterizer_canvas_gles3.cpp
index b9206f310e..df7aba265b 100644
--- a/drivers/gles3/rasterizer_canvas_gles3.cpp
+++ b/drivers/gles3/rasterizer_canvas_gles3.cpp
@@ -468,7 +468,7 @@ void RasterizerCanvasGLES3::canvas_render_items(RID p_to_render_target, Item *p_
update_skeletons = false;
}
// Canvas group begins here, render until before this item
- _render_items(p_to_render_target, item_count, canvas_transform_inverse, p_light_list, r_sdf_used, false, r_render_info);
+ _render_items(p_to_render_target, item_count, canvas_transform_inverse, p_light_list, r_sdf_used, false, r_render_info, material_screen_texture_mipmaps_cached);
item_count = 0;
if (ci->canvas_group_owner->canvas_group->mode != RS::CANVAS_GROUP_MODE_TRANSPARENT) {
@@ -499,7 +499,7 @@ void RasterizerCanvasGLES3::canvas_render_items(RID p_to_render_target, Item *p_
mesh_storage->update_mesh_instances();
update_skeletons = false;
}
- _render_items(p_to_render_target, item_count, canvas_transform_inverse, p_light_list, r_sdf_used, true, r_render_info);
+ _render_items(p_to_render_target, item_count, canvas_transform_inverse, p_light_list, r_sdf_used, true, r_render_info, material_screen_texture_mipmaps_cached);
item_count = 0;
if (ci->canvas_group->blur_mipmaps) {
@@ -523,7 +523,7 @@ void RasterizerCanvasGLES3::canvas_render_items(RID p_to_render_target, Item *p_
}
//render anything pending, including clearing if no items
- _render_items(p_to_render_target, item_count, canvas_transform_inverse, p_light_list, r_sdf_used, false, r_render_info);
+ _render_items(p_to_render_target, item_count, canvas_transform_inverse, p_light_list, r_sdf_used, false, r_render_info, material_screen_texture_mipmaps_cached);
item_count = 0;
texture_storage->render_target_copy_to_back_buffer(p_to_render_target, back_buffer_rect, backbuffer_gen_mipmaps);
@@ -553,7 +553,7 @@ void RasterizerCanvasGLES3::canvas_render_items(RID p_to_render_target, Item *p_
mesh_storage->update_mesh_instances();
update_skeletons = false;
}
- _render_items(p_to_render_target, item_count, canvas_transform_inverse, p_light_list, r_sdf_used, canvas_group_owner != nullptr, r_render_info);
+ _render_items(p_to_render_target, item_count, canvas_transform_inverse, p_light_list, r_sdf_used, canvas_group_owner != nullptr, r_render_info, material_screen_texture_mipmaps_cached);
//then reset
item_count = 0;
}
@@ -573,10 +573,10 @@ void RasterizerCanvasGLES3::canvas_render_items(RID p_to_render_target, Item *p_
state.current_instance_buffer_index = 0;
}
-void RasterizerCanvasGLES3::_render_items(RID p_to_render_target, int p_item_count, const Transform2D &p_canvas_transform_inverse, Light *p_lights, bool &r_sdf_used, bool p_to_backbuffer, RenderingMethod::RenderInfo *r_render_info) {
+void RasterizerCanvasGLES3::_render_items(RID p_to_render_target, int p_item_count, const Transform2D &p_canvas_transform_inverse, Light *p_lights, bool &r_sdf_used, bool p_to_backbuffer, RenderingMethod::RenderInfo *r_render_info, bool p_backbuffer_has_mipmaps) {
GLES3::MaterialStorage *material_storage = GLES3::MaterialStorage::get_singleton();
- canvas_begin(p_to_render_target, p_to_backbuffer);
+ canvas_begin(p_to_render_target, p_to_backbuffer, p_backbuffer_has_mipmaps);
if (p_item_count <= 0) {
// Nothing to draw, just call canvas_begin() to clear the render target and return.
@@ -2169,7 +2169,7 @@ bool RasterizerCanvasGLES3::free(RID p_rid) {
void RasterizerCanvasGLES3::update() {
}
-void RasterizerCanvasGLES3::canvas_begin(RID p_to_render_target, bool p_to_backbuffer) {
+void RasterizerCanvasGLES3::canvas_begin(RID p_to_render_target, bool p_to_backbuffer, bool p_backbuffer_has_mipmaps) {
GLES3::TextureStorage *texture_storage = GLES3::TextureStorage::get_singleton();
GLES3::Config *config = GLES3::Config::get_singleton();
@@ -2184,6 +2184,7 @@ void RasterizerCanvasGLES3::canvas_begin(RID p_to_render_target, bool p_to_backb
glBindFramebuffer(GL_FRAMEBUFFER, render_target->fbo);
glActiveTexture(GL_TEXTURE0 + config->max_texture_image_units - 4);
glBindTexture(GL_TEXTURE_2D, render_target->backbuffer);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, p_backbuffer_has_mipmaps ? render_target->mipmap_count - 1 : 0);
}
if (render_target->is_transparent || p_to_backbuffer) {
diff --git a/drivers/gles3/rasterizer_canvas_gles3.h b/drivers/gles3/rasterizer_canvas_gles3.h
index 027f717eb7..9c0d0abccb 100644
--- a/drivers/gles3/rasterizer_canvas_gles3.h
+++ b/drivers/gles3/rasterizer_canvas_gles3.h
@@ -335,7 +335,7 @@ public:
typedef void Texture;
- void canvas_begin(RID p_to_render_target, bool p_to_backbuffer);
+ void canvas_begin(RID p_to_render_target, bool p_to_backbuffer, bool p_backbuffer_has_mipmaps);
//virtual void draw_window_margins(int *black_margin, RID *black_image) override;
void draw_lens_distortion_rect(const Rect2 &p_rect, float p_k1, float p_k2, const Vector2 &p_eye_center, float p_oversample);
@@ -361,7 +361,7 @@ public:
void _prepare_canvas_texture(RID p_texture, RS::CanvasItemTextureFilter p_base_filter, RS::CanvasItemTextureRepeat p_base_repeat, uint32_t &r_index, Size2 &r_texpixel_size);
void canvas_render_items(RID p_to_render_target, Item *p_item_list, const Color &p_modulate, Light *p_light_list, Light *p_directional_list, const Transform2D &p_canvas_transform, RS::CanvasItemTextureFilter p_default_filter, RS::CanvasItemTextureRepeat p_default_repeat, bool p_snap_2d_vertices_to_pixel, bool &r_sdf_used, RenderingMethod::RenderInfo *r_render_info = nullptr) override;
- void _render_items(RID p_to_render_target, int p_item_count, const Transform2D &p_canvas_transform_inverse, Light *p_lights, bool &r_sdf_used, bool p_to_backbuffer = false, RenderingMethod::RenderInfo *r_render_info = nullptr);
+ void _render_items(RID p_to_render_target, int p_item_count, const Transform2D &p_canvas_transform_inverse, Light *p_lights, bool &r_sdf_used, bool p_to_backbuffer = false, RenderingMethod::RenderInfo *r_render_info = nullptr, bool p_backbuffer_has_mipmaps = false);
void _record_item_commands(const Item *p_item, RID p_render_target, const Transform2D &p_canvas_transform_inverse, Item *&current_clip, GLES3::CanvasShaderData::BlendMode p_blend_mode, Light *p_lights, uint32_t &r_index, bool &r_break_batch, bool &r_sdf_used, const Point2 &p_repeat_offset);
void _render_batch(Light *p_lights, uint32_t p_index, RenderingMethod::RenderInfo *r_render_info = nullptr);
bool _bind_material(GLES3::CanvasMaterialData *p_material_data, CanvasShaderGLES3::ShaderVariant p_variant, uint64_t p_specialization);
diff --git a/drivers/gles3/rasterizer_gles3.h b/drivers/gles3/rasterizer_gles3.h
index 80a4a792bb..92c96519d2 100644
--- a/drivers/gles3/rasterizer_gles3.h
+++ b/drivers/gles3/rasterizer_gles3.h
@@ -113,6 +113,7 @@ public:
static void make_current(bool p_gles_over_gl) {
gles_over_gl = p_gles_over_gl;
+ OS::get_singleton()->set_gles_over_gl(gles_over_gl);
_create_func = _create_current;
low_end = true;
}
diff --git a/drivers/gles3/rasterizer_scene_gles3.cpp b/drivers/gles3/rasterizer_scene_gles3.cpp
index 3ed8042f3f..8b6d3e3268 100644
--- a/drivers/gles3/rasterizer_scene_gles3.cpp
+++ b/drivers/gles3/rasterizer_scene_gles3.cpp
@@ -912,7 +912,7 @@ void RasterizerSceneGLES3::_update_sky_radiance(RID p_env, const Projection &p_p
RS::SkyMode sky_mode = sky->mode;
if (sky_mode == RS::SKY_MODE_AUTOMATIC) {
- if (shader_data->uses_time || shader_data->uses_position) {
+ if ((shader_data->uses_time || shader_data->uses_position) && sky->radiance_size == 256) {
update_single_frame = true;
sky_mode = RS::SKY_MODE_REALTIME;
} else if (shader_data->uses_light || shader_data->ubo_size > 0) {
diff --git a/drivers/gles3/shaders/scene.glsl b/drivers/gles3/shaders/scene.glsl
index 6143ce2167..393ba014c6 100644
--- a/drivers/gles3/shaders/scene.glsl
+++ b/drivers/gles3/shaders/scene.glsl
@@ -1803,22 +1803,22 @@ void main() {
#ifdef LIGHTMAP_BICUBIC_FILTER
vec3 lm_light_l0 = textureArray_bicubic(lightmap_textures, uvw + vec3(0.0, 0.0, 0.0), lightmap_texture_size).rgb;
- vec3 lm_light_l1n1 = textureArray_bicubic(lightmap_textures, uvw + vec3(0.0, 0.0, 1.0), lightmap_texture_size).rgb;
- vec3 lm_light_l1_0 = textureArray_bicubic(lightmap_textures, uvw + vec3(0.0, 0.0, 2.0), lightmap_texture_size).rgb;
- vec3 lm_light_l1p1 = textureArray_bicubic(lightmap_textures, uvw + vec3(0.0, 0.0, 3.0), lightmap_texture_size).rgb;
+ vec3 lm_light_l1n1 = (textureArray_bicubic(lightmap_textures, uvw + vec3(0.0, 0.0, 1.0), lightmap_texture_size).rgb - vec3(0.5)) * 2.0;
+ vec3 lm_light_l1_0 = (textureArray_bicubic(lightmap_textures, uvw + vec3(0.0, 0.0, 2.0), lightmap_texture_size).rgb - vec3(0.5)) * 2.0;
+ vec3 lm_light_l1p1 = (textureArray_bicubic(lightmap_textures, uvw + vec3(0.0, 0.0, 3.0), lightmap_texture_size).rgb - vec3(0.5)) * 2.0;
#else
vec3 lm_light_l0 = textureLod(lightmap_textures, uvw + vec3(0.0, 0.0, 0.0), 0.0).rgb;
- vec3 lm_light_l1n1 = textureLod(lightmap_textures, uvw + vec3(0.0, 0.0, 1.0), 0.0).rgb;
- vec3 lm_light_l1_0 = textureLod(lightmap_textures, uvw + vec3(0.0, 0.0, 2.0), 0.0).rgb;
- vec3 lm_light_l1p1 = textureLod(lightmap_textures, uvw + vec3(0.0, 0.0, 3.0), 0.0).rgb;
+ vec3 lm_light_l1n1 = (textureLod(lightmap_textures, uvw + vec3(0.0, 0.0, 1.0), 0.0).rgb - vec3(0.5)) * 2.0;
+ vec3 lm_light_l1_0 = (textureLod(lightmap_textures, uvw + vec3(0.0, 0.0, 2.0), 0.0).rgb - vec3(0.5)) * 2.0;
+ vec3 lm_light_l1p1 = (textureLod(lightmap_textures, uvw + vec3(0.0, 0.0, 3.0), 0.0).rgb - vec3(0.5)) * 2.0;
#endif
vec3 n = normalize(lightmap_normal_xform * normal);
ambient_light += lm_light_l0 * lightmap_exposure_normalization;
- ambient_light += lm_light_l1n1 * n.y * lightmap_exposure_normalization;
- ambient_light += lm_light_l1_0 * n.z * lightmap_exposure_normalization;
- ambient_light += lm_light_l1p1 * n.x * lightmap_exposure_normalization;
+ ambient_light += lm_light_l1n1 * n.y * (lm_light_l0 * lightmap_exposure_normalization * 4.0);
+ ambient_light += lm_light_l1_0 * n.z * (lm_light_l0 * lightmap_exposure_normalization * 4.0);
+ ambient_light += lm_light_l1p1 * n.x * (lm_light_l0 * lightmap_exposure_normalization * 4.0);
#else
#ifdef LIGHTMAP_BICUBIC_FILTER
ambient_light += textureArray_bicubic(lightmap_textures, uvw, lightmap_texture_size).rgb * lightmap_exposure_normalization;
diff --git a/drivers/gles3/shaders/stdlib_inc.glsl b/drivers/gles3/shaders/stdlib_inc.glsl
index 029084c34c..f88c218506 100644
--- a/drivers/gles3/shaders/stdlib_inc.glsl
+++ b/drivers/gles3/shaders/stdlib_inc.glsl
@@ -9,19 +9,17 @@
// Floating point pack/unpack functions are part of the GLSL ES 300 specification used by web and mobile.
uint float2half(uint f) {
- uint e = f & uint(0x7f800000);
- if (e <= uint(0x38000000)) {
- return uint(0);
- } else {
- return ((f >> uint(16)) & uint(0x8000)) |
- (((e - uint(0x38000000)) >> uint(13)) & uint(0x7c00)) |
- ((f >> uint(13)) & uint(0x03ff));
- }
+ uint b = f + uint(0x00001000);
+ uint e = (b & uint(0x7F800000)) >> 23;
+ uint m = b & uint(0x007FFFFF);
+ return (b & uint(0x80000000)) >> uint(16) | uint(e > uint(112)) * ((((e - uint(112)) << uint(10)) & uint(0x7C00)) | m >> uint(13)) | (uint(e < uint(113)) & uint(e > uint(101))) * ((((uint(0x007FF000) + m) >> (uint(125) - e)) + uint(1)) >> uint(1)) | uint(e > uint(143)) * uint(0x7FFF);
}
uint half2float(uint h) {
- uint h_e = h & uint(0x7c00);
- return ((h & uint(0x8000)) << uint(16)) | uint((h_e >> uint(10)) != uint(0)) * (((h_e + uint(0x1c000)) << uint(13)) | ((h & uint(0x03ff)) << uint(13)));
+ uint e = (h & uint(0x7C00)) >> uint(10);
+ uint m = (h & uint(0x03FF)) << uint(13);
+ uint v = m >> uint(23);
+ return (h & uint(0x8000)) << uint(16) | uint(e != uint(0)) * ((e + uint(112)) << uint(23) | m) | (uint(e == uint(0)) & uint(m != uint(0))) * ((v - uint(37)) << uint(23) | ((m << (uint(150) - v)) & uint(0x007FE000)));
}
uint godot_packHalf2x16(vec2 v) {
diff --git a/drivers/gles3/storage/config.cpp b/drivers/gles3/storage/config.cpp
index 2b3c19dbb8..d6472c44c1 100644
--- a/drivers/gles3/storage/config.cpp
+++ b/drivers/gles3/storage/config.cpp
@@ -121,7 +121,7 @@ Config::Config() {
#ifdef WEB_ENABLED
msaa_supported = (msaa_max_samples > 0);
#else
- msaa_supported = extensions.has("GL_EXT_framebuffer_multisample");
+ msaa_supported = true;
#endif
#ifndef IOS_ENABLED
#ifdef WEB_ENABLED
diff --git a/drivers/gles3/storage/light_storage.h b/drivers/gles3/storage/light_storage.h
index 81e7220439..ed00dd235f 100644
--- a/drivers/gles3/storage/light_storage.h
+++ b/drivers/gles3/storage/light_storage.h
@@ -203,7 +203,7 @@ struct LightmapInstance {
class LightStorage : public RendererLightStorage {
public:
- enum ShadowAtlastQuadrant {
+ enum ShadowAtlastQuadrant : uint32_t {
QUADRANT_SHIFT = 27,
OMNI_LIGHT_FLAG = 1 << 26,
SHADOW_INDEX_MASK = OMNI_LIGHT_FLAG - 1,
diff --git a/drivers/gles3/storage/mesh_storage.cpp b/drivers/gles3/storage/mesh_storage.cpp
index b55a2e0a8a..de82d74aff 100644
--- a/drivers/gles3/storage/mesh_storage.cpp
+++ b/drivers/gles3/storage/mesh_storage.cpp
@@ -743,6 +743,7 @@ String MeshStorage::mesh_get_path(RID p_mesh) const {
}
void MeshStorage::mesh_set_shadow_mesh(RID p_mesh, RID p_shadow_mesh) {
+ ERR_FAIL_COND_MSG(p_mesh == p_shadow_mesh, "Cannot set a mesh as its own shadow mesh.");
Mesh *mesh = mesh_owner.get_or_null(p_mesh);
ERR_FAIL_NULL(mesh);
diff --git a/drivers/gles3/storage/texture_storage.cpp b/drivers/gles3/storage/texture_storage.cpp
index 57fe96fb6f..36393dde86 100644
--- a/drivers/gles3/storage/texture_storage.cpp
+++ b/drivers/gles3/storage/texture_storage.cpp
@@ -1389,8 +1389,22 @@ void TextureStorage::texture_debug_usage(List<RS::TextureInfo> *r_info) {
tinfo.format = t->format;
tinfo.width = t->alloc_width;
tinfo.height = t->alloc_height;
- tinfo.depth = t->depth;
tinfo.bytes = t->total_data_size;
+
+ switch (t->type) {
+ case Texture::TYPE_3D:
+ tinfo.depth = t->depth;
+ break;
+
+ case Texture::TYPE_LAYERED:
+ tinfo.depth = t->layers;
+ break;
+
+ default:
+ tinfo.depth = 0;
+ break;
+ }
+
r_info->push_back(tinfo);
}
}
@@ -1521,7 +1535,11 @@ void TextureStorage::_texture_set_data(RID p_texture, const Ref<Image> &p_image,
h = MAX(1, h >> 1);
}
- texture->total_data_size = tsize;
+ if (texture->target == GL_TEXTURE_CUBE_MAP || texture->target == GL_TEXTURE_2D_ARRAY) {
+ texture->total_data_size = tsize * texture->layers;
+ } else {
+ texture->total_data_size = tsize;
+ }
texture->stored_cube_sides |= (1 << p_layer);
diff --git a/drivers/metal/metal_objects.h b/drivers/metal/metal_objects.h
index f0a3e85f88..97f33bb1e8 100644
--- a/drivers/metal/metal_objects.h
+++ b/drivers/metal/metal_objects.h
@@ -227,6 +227,7 @@ public:
id<MTLRenderCommandEncoder> encoder = nil;
id<MTLBuffer> __unsafe_unretained index_buffer = nil; // Buffer is owned by RDD.
MTLIndexType index_type = MTLIndexTypeUInt16;
+ uint32_t index_offset = 0;
LocalVector<id<MTLBuffer> __unsafe_unretained> vertex_buffers;
LocalVector<NSUInteger> vertex_offsets;
// clang-format off
diff --git a/drivers/metal/metal_objects.mm b/drivers/metal/metal_objects.mm
index c66b683e23..abdcccf00c 100644
--- a/drivers/metal/metal_objects.mm
+++ b/drivers/metal/metal_objects.mm
@@ -717,6 +717,7 @@ void MDCommandBuffer::render_bind_index_buffer(RDD::BufferID p_buffer, RDD::Inde
render.index_buffer = rid::get(p_buffer);
render.index_type = p_format == RDD::IndexBufferFormat::INDEX_BUFFER_FORMAT_UINT16 ? MTLIndexTypeUInt16 : MTLIndexTypeUInt32;
+ render.index_offset = p_offset;
}
void MDCommandBuffer::render_draw_indexed(uint32_t p_index_count,
@@ -729,13 +730,16 @@ void MDCommandBuffer::render_draw_indexed(uint32_t p_index_count,
id<MTLRenderCommandEncoder> enc = render.encoder;
+ uint32_t index_offset = render.index_offset;
+ index_offset += p_first_index * (render.index_type == MTLIndexTypeUInt16 ? sizeof(uint16_t) : sizeof(uint32_t));
+
[enc drawIndexedPrimitives:render.pipeline->raster_state.render_primitive
indexCount:p_index_count
indexType:render.index_type
indexBuffer:render.index_buffer
- indexBufferOffset:p_vertex_offset
+ indexBufferOffset:index_offset
instanceCount:p_instance_count
- baseVertex:p_first_index
+ baseVertex:p_vertex_offset
baseInstance:p_first_instance];
}
diff --git a/drivers/metal/pixel_formats.mm b/drivers/metal/pixel_formats.mm
index ac737b3f0a..36edbab99a 100644
--- a/drivers/metal/pixel_formats.mm
+++ b/drivers/metal/pixel_formats.mm
@@ -1200,7 +1200,7 @@ void PixelFormats::modifyMTLFormatCapabilities(id<MTLDevice> p_device) {
// Disable for iOS simulator last.
#if TARGET_OS_SIMULATOR
- if (![mtlDevice supportsFamily:MTLGPUFamilyApple5]) {
+ if (![p_device supportsFamily:MTLGPUFamilyApple5]) {
disableAllMTLPixFmtCaps(R8Unorm_sRGB);
disableAllMTLPixFmtCaps(RG8Unorm_sRGB);
disableAllMTLPixFmtCaps(B5G6R5Unorm);
diff --git a/drivers/metal/rendering_context_driver_metal.h b/drivers/metal/rendering_context_driver_metal.h
index 0363ab111a..7e0b09186d 100644
--- a/drivers/metal/rendering_context_driver_metal.h
+++ b/drivers/metal/rendering_context_driver_metal.h
@@ -33,22 +33,36 @@
#ifdef METAL_ENABLED
-#import "rendering_device_driver_metal.h"
-
#import "servers/rendering/rendering_context_driver.h"
+#import "servers/rendering/rendering_device_driver.h"
#import <CoreGraphics/CGGeometry.h>
+
+#ifdef __OBJC__
+#import "metal_objects.h"
+
#import <Metal/Metal.h>
#import <QuartzCore/CALayer.h>
@class CAMetalLayer;
@protocol CAMetalDrawable;
+#else
+typedef enum MTLPixelFormat {
+ MTLPixelFormatBGRA8Unorm = 80,
+} MTLPixelFormat;
+class MDCommandBuffer;
+#endif
+
class PixelFormats;
class MDResourceCache;
class API_AVAILABLE(macos(11.0), ios(14.0)) RenderingContextDriverMetal : public RenderingContextDriver {
protected:
- id<MTLDevice> metal_device = nil;
+#ifdef __OBJC__
+ id<MTLDevice> metal_device = nullptr;
+#else
+ void *metal_device = nullptr;
+#endif
Device device; // There is only one device on Apple Silicon.
public:
@@ -73,12 +87,20 @@ public:
// Platform-specific data for the Windows embedded in this driver.
struct WindowPlatformData {
+#ifdef __OBJC__
CAMetalLayer *__unsafe_unretained layer;
+#else
+ void *layer;
+#endif
};
- class Surface {
+ class API_AVAILABLE(macos(11.0), ios(14.0)) Surface {
protected:
+#ifdef __OBJC__
id<MTLDevice> device;
+#else
+ void *device;
+#endif
public:
uint32_t width = 0;
@@ -86,8 +108,15 @@ public:
DisplayServer::VSyncMode vsync_mode = DisplayServer::VSYNC_ENABLED;
bool needs_resize = false;
- Surface(id<MTLDevice> p_device) :
- device(p_device) {}
+ Surface(
+#ifdef __OBJC__
+ id<MTLDevice> p_device
+#else
+ void *p_device
+#endif
+ ) :
+ device(p_device) {
+ }
virtual ~Surface() = default;
MTLPixelFormat get_pixel_format() const { return MTLPixelFormatBGRA8Unorm; }
@@ -96,104 +125,14 @@ public:
virtual void present(MDCommandBuffer *p_cmd_buffer) = 0;
};
- class SurfaceLayer : public Surface {
- CAMetalLayer *__unsafe_unretained layer = nil;
- LocalVector<MDFrameBuffer> frame_buffers;
- LocalVector<id<MTLDrawable>> drawables;
- uint32_t rear = -1;
- uint32_t front = 0;
- uint32_t count = 0;
-
- public:
- SurfaceLayer(CAMetalLayer *p_layer, id<MTLDevice> p_device) :
- Surface(p_device), layer(p_layer) {
- layer.allowsNextDrawableTimeout = YES;
- layer.framebufferOnly = YES;
- layer.opaque = OS::get_singleton()->is_layered_allowed() ? NO : YES;
- layer.pixelFormat = get_pixel_format();
- layer.device = p_device;
- }
-
- ~SurfaceLayer() override {
- layer = nil;
- }
-
- Error resize(uint32_t p_desired_framebuffer_count) override final {
- if (width == 0 || height == 0) {
- // Very likely the window is minimized, don't create a swap chain.
- return ERR_SKIP;
- }
-
- CGSize drawableSize = CGSizeMake(width, height);
- CGSize current = layer.drawableSize;
- if (!CGSizeEqualToSize(current, drawableSize)) {
- layer.drawableSize = drawableSize;
- }
-
- // Metal supports a maximum of 3 drawables.
- p_desired_framebuffer_count = MIN(3U, p_desired_framebuffer_count);
- layer.maximumDrawableCount = p_desired_framebuffer_count;
-
-#if TARGET_OS_OSX
- // Display sync is only supported on macOS.
- switch (vsync_mode) {
- case DisplayServer::VSYNC_MAILBOX:
- case DisplayServer::VSYNC_ADAPTIVE:
- case DisplayServer::VSYNC_ENABLED:
- layer.displaySyncEnabled = YES;
- break;
- case DisplayServer::VSYNC_DISABLED:
- layer.displaySyncEnabled = NO;
- break;
- }
+#ifdef __OBJC__
+ id<MTLDevice>
+#else
+ void *
#endif
- drawables.resize(p_desired_framebuffer_count);
- frame_buffers.resize(p_desired_framebuffer_count);
- for (uint32_t i = 0; i < p_desired_framebuffer_count; i++) {
- // Reserve space for the drawable texture.
- frame_buffers[i].textures.resize(1);
- }
-
- return OK;
- }
-
- RDD::FramebufferID acquire_next_frame_buffer() override final {
- if (count == frame_buffers.size()) {
- return RDD::FramebufferID();
- }
-
- rear = (rear + 1) % frame_buffers.size();
- count++;
-
- MDFrameBuffer &frame_buffer = frame_buffers[rear];
- frame_buffer.size = Size2i(width, height);
-
- id<CAMetalDrawable> drawable = layer.nextDrawable;
- ERR_FAIL_NULL_V_MSG(drawable, RDD::FramebufferID(), "no drawable available");
- drawables[rear] = drawable;
- frame_buffer.textures.write[0] = drawable.texture;
-
- return RDD::FramebufferID(&frame_buffer);
- }
-
- void present(MDCommandBuffer *p_cmd_buffer) override final {
- if (count == 0) {
- return;
- }
-
- // Release texture and drawable.
- frame_buffers[front].textures.write[0] = nil;
- id<MTLDrawable> drawable = drawables[front];
- drawables[front] = nil;
-
- count--;
- front = (front + 1) % frame_buffers.size();
-
- [p_cmd_buffer->get_command_buffer() presentDrawable:drawable];
- }
- };
-
- id<MTLDevice> get_metal_device() const { return metal_device; }
+ get_metal_device() const {
+ return metal_device;
+ }
#pragma mark - Initialization
diff --git a/drivers/metal/rendering_context_driver_metal.mm b/drivers/metal/rendering_context_driver_metal.mm
index b257d7142a..b97b586352 100644
--- a/drivers/metal/rendering_context_driver_metal.mm
+++ b/drivers/metal/rendering_context_driver_metal.mm
@@ -30,6 +30,8 @@
#import "rendering_context_driver_metal.h"
+#import "rendering_device_driver_metal.h"
+
@protocol MTLDeviceEx <MTLDevice>
#if TARGET_OS_OSX && __MAC_OS_X_VERSION_MAX_ALLOWED < 130300
- (void)setShouldMaximizeConcurrentCompilation:(BOOL)v;
@@ -77,6 +79,103 @@ void RenderingContextDriverMetal::driver_free(RenderingDeviceDriver *p_driver) {
memdelete(p_driver);
}
+class API_AVAILABLE(macos(11.0), ios(14.0)) SurfaceLayer : public RenderingContextDriverMetal::Surface {
+ CAMetalLayer *__unsafe_unretained layer = nil;
+ LocalVector<MDFrameBuffer> frame_buffers;
+ LocalVector<id<MTLDrawable>> drawables;
+ uint32_t rear = -1;
+ uint32_t front = 0;
+ uint32_t count = 0;
+
+public:
+ SurfaceLayer(CAMetalLayer *p_layer, id<MTLDevice> p_device) :
+ Surface(p_device), layer(p_layer) {
+ layer.allowsNextDrawableTimeout = YES;
+ layer.framebufferOnly = YES;
+ layer.opaque = OS::get_singleton()->is_layered_allowed() ? NO : YES;
+ layer.pixelFormat = get_pixel_format();
+ layer.device = p_device;
+ }
+
+ ~SurfaceLayer() override {
+ layer = nil;
+ }
+
+ Error resize(uint32_t p_desired_framebuffer_count) override final {
+ if (width == 0 || height == 0) {
+ // Very likely the window is minimized, don't create a swap chain.
+ return ERR_SKIP;
+ }
+
+ CGSize drawableSize = CGSizeMake(width, height);
+ CGSize current = layer.drawableSize;
+ if (!CGSizeEqualToSize(current, drawableSize)) {
+ layer.drawableSize = drawableSize;
+ }
+
+ // Metal supports a maximum of 3 drawables.
+ p_desired_framebuffer_count = MIN(3U, p_desired_framebuffer_count);
+ layer.maximumDrawableCount = p_desired_framebuffer_count;
+
+#if TARGET_OS_OSX
+ // Display sync is only supported on macOS.
+ switch (vsync_mode) {
+ case DisplayServer::VSYNC_MAILBOX:
+ case DisplayServer::VSYNC_ADAPTIVE:
+ case DisplayServer::VSYNC_ENABLED:
+ layer.displaySyncEnabled = YES;
+ break;
+ case DisplayServer::VSYNC_DISABLED:
+ layer.displaySyncEnabled = NO;
+ break;
+ }
+#endif
+ drawables.resize(p_desired_framebuffer_count);
+ frame_buffers.resize(p_desired_framebuffer_count);
+ for (uint32_t i = 0; i < p_desired_framebuffer_count; i++) {
+ // Reserve space for the drawable texture.
+ frame_buffers[i].textures.resize(1);
+ }
+
+ return OK;
+ }
+
+ RDD::FramebufferID acquire_next_frame_buffer() override final {
+ if (count == frame_buffers.size()) {
+ return RDD::FramebufferID();
+ }
+
+ rear = (rear + 1) % frame_buffers.size();
+ count++;
+
+ MDFrameBuffer &frame_buffer = frame_buffers[rear];
+ frame_buffer.size = Size2i(width, height);
+
+ id<CAMetalDrawable> drawable = layer.nextDrawable;
+ ERR_FAIL_NULL_V_MSG(drawable, RDD::FramebufferID(), "no drawable available");
+ drawables[rear] = drawable;
+ frame_buffer.textures.write[0] = drawable.texture;
+
+ return RDD::FramebufferID(&frame_buffer);
+ }
+
+ void present(MDCommandBuffer *p_cmd_buffer) override final {
+ if (count == 0) {
+ return;
+ }
+
+ // Release texture and drawable.
+ frame_buffers[front].textures.write[0] = nil;
+ id<MTLDrawable> drawable = drawables[front];
+ drawables[front] = nil;
+
+ count--;
+ front = (front + 1) % frame_buffers.size();
+
+ [p_cmd_buffer->get_command_buffer() presentDrawable:drawable];
+ }
+};
+
RenderingContextDriver::SurfaceID RenderingContextDriverMetal::surface_create(const void *p_platform_data) {
const WindowPlatformData *wpd = (const WindowPlatformData *)(p_platform_data);
Surface *surface = memnew(SurfaceLayer(wpd->layer, metal_device));
diff --git a/drivers/metal/rendering_device_driver_metal.mm b/drivers/metal/rendering_device_driver_metal.mm
index 0d88a14fbc..9d691a0d23 100644
--- a/drivers/metal/rendering_device_driver_metal.mm
+++ b/drivers/metal/rendering_device_driver_metal.mm
@@ -3112,7 +3112,7 @@ RenderingDeviceDriverMetal::Result<id<MTLFunction>> RenderingDeviceDriverMetal::
NSArray<MTLFunctionConstant *> *constants = function.functionConstantsDictionary.allValues;
bool is_sorted = true;
for (uint32_t i = 1; i < constants.count; i++) {
- if (constants[i - 1].index < constants[i].index) {
+ if (constants[i - 1].index > constants[i].index) {
is_sorted = false;
break;
}
diff --git a/drivers/unix/dir_access_unix.cpp b/drivers/unix/dir_access_unix.cpp
index a4208829b7..489181a025 100644
--- a/drivers/unix/dir_access_unix.cpp
+++ b/drivers/unix/dir_access_unix.cpp
@@ -397,12 +397,18 @@ Error DirAccessUnix::rename(String p_path, String p_new_path) {
}
p_path = fix_path(p_path);
+ if (p_path.ends_with("/")) {
+ p_path = p_path.left(-1);
+ }
if (p_new_path.is_relative_path()) {
p_new_path = get_current_dir().path_join(p_new_path);
}
p_new_path = fix_path(p_new_path);
+ if (p_new_path.ends_with("/")) {
+ p_new_path = p_new_path.left(-1);
+ }
return ::rename(p_path.utf8().get_data(), p_new_path.utf8().get_data()) == 0 ? OK : FAILED;
}
diff --git a/drivers/unix/file_access_unix.cpp b/drivers/unix/file_access_unix.cpp
index ea8b42b2e4..32f2d7dd79 100644
--- a/drivers/unix/file_access_unix.cpp
+++ b/drivers/unix/file_access_unix.cpp
@@ -218,67 +218,13 @@ bool FileAccessUnix::eof_reached() const {
return last_error == ERR_FILE_EOF;
}
-uint8_t FileAccessUnix::get_8() const {
- ERR_FAIL_NULL_V_MSG(f, 0, "File must be opened before use.");
- uint8_t b;
- if (fread(&b, 1, 1, f) == 0) {
- check_errors();
- b = '\0';
- }
- return b;
-}
-
-uint16_t FileAccessUnix::get_16() const {
- ERR_FAIL_NULL_V_MSG(f, 0, "File must be opened before use.");
-
- uint16_t b = 0;
- if (fread(&b, 1, 2, f) != 2) {
- check_errors();
- }
-
- if (big_endian) {
- b = BSWAP16(b);
- }
-
- return b;
-}
-
-uint32_t FileAccessUnix::get_32() const {
- ERR_FAIL_NULL_V_MSG(f, 0, "File must be opened before use.");
-
- uint32_t b = 0;
- if (fread(&b, 1, 4, f) != 4) {
- check_errors();
- }
-
- if (big_endian) {
- b = BSWAP32(b);
- }
-
- return b;
-}
-
-uint64_t FileAccessUnix::get_64() const {
- ERR_FAIL_NULL_V_MSG(f, 0, "File must be opened before use.");
-
- uint64_t b = 0;
- if (fread(&b, 1, 8, f) != 8) {
- check_errors();
- }
-
- if (big_endian) {
- b = BSWAP64(b);
- }
-
- return b;
-}
-
uint64_t FileAccessUnix::get_buffer(uint8_t *p_dst, uint64_t p_length) const {
- ERR_FAIL_COND_V(!p_dst && p_length > 0, -1);
ERR_FAIL_NULL_V_MSG(f, -1, "File must be opened before use.");
+ ERR_FAIL_COND_V(!p_dst && p_length > 0, -1);
uint64_t read = fread(p_dst, 1, p_length, f);
check_errors();
+
return read;
}
@@ -308,41 +254,6 @@ void FileAccessUnix::flush() {
fflush(f);
}
-void FileAccessUnix::store_8(uint8_t p_dest) {
- ERR_FAIL_NULL_MSG(f, "File must be opened before use.");
- ERR_FAIL_COND(fwrite(&p_dest, 1, 1, f) != 1);
-}
-
-void FileAccessUnix::store_16(uint16_t p_dest) {
- ERR_FAIL_NULL_MSG(f, "File must be opened before use.");
-
- if (big_endian) {
- p_dest = BSWAP16(p_dest);
- }
-
- ERR_FAIL_COND(fwrite(&p_dest, 1, 2, f) != 2);
-}
-
-void FileAccessUnix::store_32(uint32_t p_dest) {
- ERR_FAIL_NULL_MSG(f, "File must be opened before use.");
-
- if (big_endian) {
- p_dest = BSWAP32(p_dest);
- }
-
- ERR_FAIL_COND(fwrite(&p_dest, 1, 4, f) != 4);
-}
-
-void FileAccessUnix::store_64(uint64_t p_dest) {
- ERR_FAIL_NULL_MSG(f, "File must be opened before use.");
-
- if (big_endian) {
- p_dest = BSWAP64(p_dest);
- }
-
- ERR_FAIL_COND(fwrite(&p_dest, 1, 8, f) != 8);
-}
-
void FileAccessUnix::store_buffer(const uint8_t *p_src, uint64_t p_length) {
ERR_FAIL_NULL_MSG(f, "File must be opened before use.");
ERR_FAIL_COND(!p_src && p_length > 0);
diff --git a/drivers/unix/file_access_unix.h b/drivers/unix/file_access_unix.h
index c0286dbff3..76f629f7c2 100644
--- a/drivers/unix/file_access_unix.h
+++ b/drivers/unix/file_access_unix.h
@@ -67,20 +67,12 @@ public:
virtual bool eof_reached() const override; ///< reading passed EOF
- virtual uint8_t get_8() const override; ///< get a byte
- virtual uint16_t get_16() const override;
- virtual uint32_t get_32() const override;
- virtual uint64_t get_64() const override;
virtual uint64_t get_buffer(uint8_t *p_dst, uint64_t p_length) const override;
virtual Error get_error() const override; ///< get last error
virtual Error resize(int64_t p_length) override;
virtual void flush() override;
- virtual void store_8(uint8_t p_dest) override; ///< store a byte
- virtual void store_16(uint16_t p_dest) override;
- virtual void store_32(uint32_t p_dest) override;
- virtual void store_64(uint64_t p_dest) override;
virtual void store_buffer(const uint8_t *p_src, uint64_t p_length) override; ///< store an array of bytes
virtual bool file_exists(const String &p_path) override; ///< return true if a file exists
diff --git a/drivers/unix/file_access_unix_pipe.cpp b/drivers/unix/file_access_unix_pipe.cpp
index 5d9a27ad05..0a78429dec 100644
--- a/drivers/unix/file_access_unix_pipe.cpp
+++ b/drivers/unix/file_access_unix_pipe.cpp
@@ -41,7 +41,7 @@
#include <sys/types.h>
#include <unistd.h>
-Error FileAccessUnixPipe::open_existing(int p_rfd, int p_wfd) {
+Error FileAccessUnixPipe::open_existing(int p_rfd, int p_wfd, bool p_blocking) {
// Open pipe using handles created by pipe(fd) call in the OS.execute_with_pipe.
_close();
@@ -51,6 +51,11 @@ Error FileAccessUnixPipe::open_existing(int p_rfd, int p_wfd) {
fd[0] = p_rfd;
fd[1] = p_wfd;
+ if (!p_blocking) {
+ fcntl(fd[0], F_SETFL, fcntl(fd[0], F_GETFL) | O_NONBLOCK);
+ fcntl(fd[1], F_SETFL, fcntl(fd[1], F_GETFL) | O_NONBLOCK);
+ }
+
last_error = OK;
return OK;
}
@@ -74,7 +79,7 @@ Error FileAccessUnixPipe::open_internal(const String &p_path, int p_mode_flags)
ERR_FAIL_COND_V_MSG(!S_ISFIFO(st.st_mode), ERR_ALREADY_IN_USE, "Pipe name is already used by file.");
}
- int f = ::open(path.utf8().get_data(), O_RDWR | O_CLOEXEC);
+ int f = ::open(path.utf8().get_data(), O_RDWR | O_CLOEXEC | O_NONBLOCK);
if (f < 0) {
switch (errno) {
case ENOENT: {
@@ -125,25 +130,15 @@ String FileAccessUnixPipe::get_path_absolute() const {
return path_src;
}
-uint8_t FileAccessUnixPipe::get_8() const {
- ERR_FAIL_COND_V_MSG(fd[0] < 0, 0, "Pipe must be opened before use.");
-
- uint8_t b;
- if (::read(fd[0], &b, 1) == 0) {
- last_error = ERR_FILE_CANT_READ;
- b = '\0';
- } else {
- last_error = OK;
- }
- return b;
-}
-
uint64_t FileAccessUnixPipe::get_buffer(uint8_t *p_dst, uint64_t p_length) const {
- ERR_FAIL_COND_V(!p_dst && p_length > 0, -1);
ERR_FAIL_COND_V_MSG(fd[0] < 0, -1, "Pipe must be opened before use.");
+ ERR_FAIL_COND_V(!p_dst && p_length > 0, -1);
- uint64_t read = ::read(fd[0], p_dst, p_length);
- if (read == p_length) {
+ ssize_t read = ::read(fd[0], p_dst, p_length);
+ if (read == -1) {
+ last_error = ERR_FILE_CANT_READ;
+ read = 0;
+ } else if (read != (ssize_t)p_length) {
last_error = ERR_FILE_CANT_READ;
} else {
last_error = OK;
@@ -155,18 +150,10 @@ Error FileAccessUnixPipe::get_error() const {
return last_error;
}
-void FileAccessUnixPipe::store_8(uint8_t p_src) {
- ERR_FAIL_COND_MSG(fd[1] < 0, "Pipe must be opened before use.");
- if (::write(fd[1], &p_src, 1) != 1) {
- last_error = ERR_FILE_CANT_WRITE;
- } else {
- last_error = OK;
- }
-}
-
void FileAccessUnixPipe::store_buffer(const uint8_t *p_src, uint64_t p_length) {
ERR_FAIL_COND_MSG(fd[1] < 0, "Pipe must be opened before use.");
ERR_FAIL_COND(!p_src && p_length > 0);
+
if (::write(fd[1], p_src, p_length) != (ssize_t)p_length) {
last_error = ERR_FILE_CANT_WRITE;
} else {
diff --git a/drivers/unix/file_access_unix_pipe.h b/drivers/unix/file_access_unix_pipe.h
index 8e7988791b..1a4199f239 100644
--- a/drivers/unix/file_access_unix_pipe.h
+++ b/drivers/unix/file_access_unix_pipe.h
@@ -50,7 +50,7 @@ class FileAccessUnixPipe : public FileAccess {
void _close();
public:
- Error open_existing(int p_rfd, int p_wfd);
+ Error open_existing(int p_rfd, int p_wfd, bool p_blocking);
virtual Error open_internal(const String &p_path, int p_mode_flags) override; ///< open a file
virtual bool is_open() const override; ///< true when file is open
@@ -65,14 +65,12 @@ public:
virtual bool eof_reached() const override { return false; }
- virtual uint8_t get_8() const override; ///< get a byte
virtual uint64_t get_buffer(uint8_t *p_dst, uint64_t p_length) const override;
virtual Error get_error() const override; ///< get last error
virtual Error resize(int64_t p_length) override { return ERR_UNAVAILABLE; }
virtual void flush() override {}
- virtual void store_8(uint8_t p_src) override; ///< store a byte
virtual void store_buffer(const uint8_t *p_src, uint64_t p_length) override; ///< store an array of bytes
virtual bool file_exists(const String &p_path) override { return false; }
diff --git a/drivers/unix/os_unix.cpp b/drivers/unix/os_unix.cpp
index ce2553456d..8a9b130068 100644
--- a/drivers/unix/os_unix.cpp
+++ b/drivers/unix/os_unix.cpp
@@ -493,7 +493,7 @@ Dictionary OS_Unix::get_memory_info() const {
return meminfo;
}
-Dictionary OS_Unix::execute_with_pipe(const String &p_path, const List<String> &p_arguments) {
+Dictionary OS_Unix::execute_with_pipe(const String &p_path, const List<String> &p_arguments, bool p_blocking) {
#define CLEAN_PIPES \
if (pipe_in[0] >= 0) { \
::close(pipe_in[0]); \
@@ -578,11 +578,11 @@ Dictionary OS_Unix::execute_with_pipe(const String &p_path, const List<String> &
Ref<FileAccessUnixPipe> main_pipe;
main_pipe.instantiate();
- main_pipe->open_existing(pipe_out[0], pipe_in[1]);
+ main_pipe->open_existing(pipe_out[0], pipe_in[1], p_blocking);
Ref<FileAccessUnixPipe> err_pipe;
err_pipe.instantiate();
- err_pipe->open_existing(pipe_err[0], 0);
+ err_pipe->open_existing(pipe_err[0], 0, p_blocking);
ProcessInfo pi;
process_map_mutex.lock();
diff --git a/drivers/unix/os_unix.h b/drivers/unix/os_unix.h
index df269a59d3..3add5df055 100644
--- a/drivers/unix/os_unix.h
+++ b/drivers/unix/os_unix.h
@@ -83,7 +83,7 @@ public:
virtual Dictionary get_memory_info() const override;
virtual Error execute(const String &p_path, const List<String> &p_arguments, String *r_pipe = nullptr, int *r_exitcode = nullptr, bool read_stderr = false, Mutex *p_pipe_mutex = nullptr, bool p_open_console = false) override;
- virtual Dictionary execute_with_pipe(const String &p_path, const List<String> &p_arguments) override;
+ virtual Dictionary execute_with_pipe(const String &p_path, const List<String> &p_arguments, bool p_blocking = true) override;
virtual Error create_process(const String &p_path, const List<String> &p_arguments, ProcessID *r_child_id = nullptr, bool p_open_console = false) override;
virtual Error kill(const ProcessID &p_pid) override;
virtual int get_process_id() const override;
diff --git a/drivers/vulkan/rendering_device_driver_vulkan.cpp b/drivers/vulkan/rendering_device_driver_vulkan.cpp
index 2ba353868b..4ea46e8214 100644
--- a/drivers/vulkan/rendering_device_driver_vulkan.cpp
+++ b/drivers/vulkan/rendering_device_driver_vulkan.cpp
@@ -497,6 +497,7 @@ Error RenderingDeviceDriverVulkan::_initialize_device_extensions() {
_register_requested_device_extension(VK_KHR_MAINTENANCE_2_EXTENSION_NAME, false);
_register_requested_device_extension(VK_EXT_PIPELINE_CREATION_CACHE_CONTROL_EXTENSION_NAME, false);
_register_requested_device_extension(VK_EXT_SUBGROUP_SIZE_CONTROL_EXTENSION_NAME, false);
+ _register_requested_device_extension(VK_EXT_ASTC_DECODE_MODE_EXTENSION_NAME, false);
if (Engine::get_singleton()->is_generate_spirv_debug_info_enabled()) {
_register_requested_device_extension(VK_KHR_SHADER_NON_SEMANTIC_INFO_EXTENSION_NAME, true);
@@ -1705,6 +1706,16 @@ RDD::TextureID RenderingDeviceDriverVulkan::texture_create(const TextureFormat &
image_view_create_info.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
}
+ VkImageViewASTCDecodeModeEXT decode_mode;
+ if (enabled_device_extension_names.has(VK_EXT_ASTC_DECODE_MODE_EXTENSION_NAME)) {
+ if (image_view_create_info.format >= VK_FORMAT_ASTC_4x4_UNORM_BLOCK && image_view_create_info.format <= VK_FORMAT_ASTC_12x12_SRGB_BLOCK) {
+ decode_mode.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_ASTC_DECODE_MODE_EXT;
+ decode_mode.pNext = nullptr;
+ decode_mode.decodeMode = VK_FORMAT_R8G8B8A8_UNORM;
+ image_view_create_info.pNext = &decode_mode;
+ }
+ }
+
VkImageView vk_image_view = VK_NULL_HANDLE;
err = vkCreateImageView(vk_device, &image_view_create_info, VKC::get_allocation_callbacks(VK_OBJECT_TYPE_IMAGE_VIEW), &vk_image_view);
if (err) {
diff --git a/drivers/windows/dir_access_windows.cpp b/drivers/windows/dir_access_windows.cpp
index 63ba6a6c96..f7632842ed 100644
--- a/drivers/windows/dir_access_windows.cpp
+++ b/drivers/windows/dir_access_windows.cpp
@@ -35,6 +35,7 @@
#include "core/config/project_settings.h"
#include "core/os/memory.h"
+#include "core/os/os.h"
#include "core/string/print_string.h"
#include <stdio.h>
@@ -69,9 +70,19 @@ struct DirAccessWindowsPrivate {
};
String DirAccessWindows::fix_path(const String &p_path) const {
- String r_path = DirAccess::fix_path(p_path);
- if (r_path.is_absolute_path() && !r_path.is_network_share_path() && r_path.length() > MAX_PATH) {
- r_path = "\\\\?\\" + r_path.replace("/", "\\");
+ String r_path = DirAccess::fix_path(p_path.trim_prefix(R"(\\?\)").replace("\\", "/"));
+ if (r_path.ends_with(":")) {
+ r_path += "/";
+ }
+ if (r_path.is_relative_path()) {
+ r_path = current_dir.trim_prefix(R"(\\?\)").replace("\\", "/").path_join(r_path);
+ } else if (r_path == ".") {
+ r_path = current_dir.trim_prefix(R"(\\?\)").replace("\\", "/");
+ }
+ r_path = r_path.simplify_path();
+ r_path = r_path.replace("/", "\\");
+ if (!r_path.is_network_share_path() && !r_path.begins_with(R"(\\?\)")) {
+ r_path = R"(\\?\)" + r_path;
}
return r_path;
}
@@ -140,28 +151,33 @@ String DirAccessWindows::get_drive(int p_drive) {
Error DirAccessWindows::change_dir(String p_dir) {
GLOBAL_LOCK_FUNCTION
- p_dir = fix_path(p_dir);
+ String dir = fix_path(p_dir);
- WCHAR real_current_dir_name[2048];
- GetCurrentDirectoryW(2048, real_current_dir_name);
- String prev_dir = String::utf16((const char16_t *)real_current_dir_name);
+ Char16String real_current_dir_name;
+ size_t str_len = GetCurrentDirectoryW(0, nullptr);
+ real_current_dir_name.resize(str_len + 1);
+ GetCurrentDirectoryW(real_current_dir_name.size(), (LPWSTR)real_current_dir_name.ptrw());
+ String prev_dir = String::utf16((const char16_t *)real_current_dir_name.get_data());
SetCurrentDirectoryW((LPCWSTR)(current_dir.utf16().get_data()));
- bool worked = (SetCurrentDirectoryW((LPCWSTR)(p_dir.utf16().get_data())) != 0);
+ bool worked = (SetCurrentDirectoryW((LPCWSTR)(dir.utf16().get_data())) != 0);
String base = _get_root_path();
if (!base.is_empty()) {
- GetCurrentDirectoryW(2048, real_current_dir_name);
- String new_dir = String::utf16((const char16_t *)real_current_dir_name).replace("\\", "/");
+ str_len = GetCurrentDirectoryW(0, nullptr);
+ real_current_dir_name.resize(str_len + 1);
+ GetCurrentDirectoryW(real_current_dir_name.size(), (LPWSTR)real_current_dir_name.ptrw());
+ String new_dir = String::utf16((const char16_t *)real_current_dir_name.get_data()).trim_prefix(R"(\\?\)").replace("\\", "/");
if (!new_dir.begins_with(base)) {
worked = false;
}
}
if (worked) {
- GetCurrentDirectoryW(2048, real_current_dir_name);
- current_dir = String::utf16((const char16_t *)real_current_dir_name);
- current_dir = current_dir.replace("\\", "/");
+ str_len = GetCurrentDirectoryW(0, nullptr);
+ real_current_dir_name.resize(str_len + 1);
+ GetCurrentDirectoryW(real_current_dir_name.size(), (LPWSTR)real_current_dir_name.ptrw());
+ current_dir = String::utf16((const char16_t *)real_current_dir_name.get_data());
}
SetCurrentDirectoryW((LPCWSTR)(prev_dir.utf16().get_data()));
@@ -172,12 +188,6 @@ Error DirAccessWindows::change_dir(String p_dir) {
Error DirAccessWindows::make_dir(String p_dir) {
GLOBAL_LOCK_FUNCTION
- p_dir = fix_path(p_dir);
- if (p_dir.is_relative_path()) {
- p_dir = current_dir.path_join(p_dir);
- p_dir = fix_path(p_dir);
- }
-
if (FileAccessWindows::is_path_invalid(p_dir)) {
#ifdef DEBUG_ENABLED
WARN_PRINT("The path :" + p_dir + " is a reserved Windows system pipe, so it can't be used for creating directories.");
@@ -185,12 +195,12 @@ Error DirAccessWindows::make_dir(String p_dir) {
return ERR_INVALID_PARAMETER;
}
- p_dir = p_dir.simplify_path().replace("/", "\\");
+ String dir = fix_path(p_dir);
bool success;
int err;
- success = CreateDirectoryW((LPCWSTR)(p_dir.utf16().get_data()), nullptr);
+ success = CreateDirectoryW((LPCWSTR)(dir.utf16().get_data()), nullptr);
err = GetLastError();
if (success) {
@@ -205,9 +215,10 @@ Error DirAccessWindows::make_dir(String p_dir) {
}
String DirAccessWindows::get_current_dir(bool p_include_drive) const {
+ String cdir = current_dir.trim_prefix(R"(\\?\)").replace("\\", "/");
String base = _get_root_path();
if (!base.is_empty()) {
- String bd = current_dir.replace("\\", "/").replace_first(base, "");
+ String bd = cdir.replace_first(base, "");
if (bd.begins_with("/")) {
return _get_root_string() + bd.substr(1, bd.length());
} else {
@@ -216,30 +227,25 @@ String DirAccessWindows::get_current_dir(bool p_include_drive) const {
}
if (p_include_drive) {
- return current_dir;
+ return cdir;
} else {
if (_get_root_string().is_empty()) {
- int pos = current_dir.find(":");
+ int pos = cdir.find(":");
if (pos != -1) {
- return current_dir.substr(pos + 1);
+ return cdir.substr(pos + 1);
}
}
- return current_dir;
+ return cdir;
}
}
bool DirAccessWindows::file_exists(String p_file) {
GLOBAL_LOCK_FUNCTION
- if (!p_file.is_absolute_path()) {
- p_file = get_current_dir().path_join(p_file);
- }
-
- p_file = fix_path(p_file);
+ String file = fix_path(p_file);
DWORD fileAttr;
-
- fileAttr = GetFileAttributesW((LPCWSTR)(p_file.utf16().get_data()));
+ fileAttr = GetFileAttributesW((LPCWSTR)(file.utf16().get_data()));
if (INVALID_FILE_ATTRIBUTES == fileAttr) {
return false;
}
@@ -250,14 +256,10 @@ bool DirAccessWindows::file_exists(String p_file) {
bool DirAccessWindows::dir_exists(String p_dir) {
GLOBAL_LOCK_FUNCTION
- if (p_dir.is_relative_path()) {
- p_dir = get_current_dir().path_join(p_dir);
- }
-
- p_dir = fix_path(p_dir);
+ String dir = fix_path(p_dir);
DWORD fileAttr;
- fileAttr = GetFileAttributesW((LPCWSTR)(p_dir.utf16().get_data()));
+ fileAttr = GetFileAttributesW((LPCWSTR)(dir.utf16().get_data()));
if (INVALID_FILE_ATTRIBUTES == fileAttr) {
return false;
}
@@ -265,66 +267,63 @@ bool DirAccessWindows::dir_exists(String p_dir) {
}
Error DirAccessWindows::rename(String p_path, String p_new_path) {
- if (p_path.is_relative_path()) {
- p_path = get_current_dir().path_join(p_path);
- }
-
- p_path = fix_path(p_path);
-
- if (p_new_path.is_relative_path()) {
- p_new_path = get_current_dir().path_join(p_new_path);
- }
-
- p_new_path = fix_path(p_new_path);
+ String path = fix_path(p_path);
+ String new_path = fix_path(p_new_path);
// If we're only changing file name case we need to do a little juggling
- if (p_path.to_lower() == p_new_path.to_lower()) {
- if (dir_exists(p_path)) {
+ if (path.to_lower() == new_path.to_lower()) {
+ if (dir_exists(path)) {
// The path is a dir; just rename
- return ::_wrename((LPCWSTR)(p_path.utf16().get_data()), (LPCWSTR)(p_new_path.utf16().get_data())) == 0 ? OK : FAILED;
+ return MoveFileW((LPCWSTR)(path.utf16().get_data()), (LPCWSTR)(new_path.utf16().get_data())) != 0 ? OK : FAILED;
}
// The path is a file; juggle
- WCHAR tmpfile[MAX_PATH];
-
- if (!GetTempFileNameW((LPCWSTR)(fix_path(get_current_dir()).utf16().get_data()), nullptr, 0, tmpfile)) {
- return FAILED;
+ // Note: do not use GetTempFileNameW, it's not long path aware!
+ Char16String tmpfile_utf16;
+ uint64_t id = OS::get_singleton()->get_ticks_usec();
+ while (true) {
+ tmpfile_utf16 = (path + itos(id++) + ".tmp").utf16();
+ HANDLE handle = CreateFileW((LPCWSTR)tmpfile_utf16.get_data(), GENERIC_WRITE, 0, NULL, CREATE_NEW, FILE_ATTRIBUTE_NORMAL, 0);
+ if (handle != INVALID_HANDLE_VALUE) {
+ CloseHandle(handle);
+ break;
+ }
+ if (GetLastError() != ERROR_FILE_EXISTS && GetLastError() != ERROR_SHARING_VIOLATION) {
+ return FAILED;
+ }
}
- if (!::ReplaceFileW(tmpfile, (LPCWSTR)(p_path.utf16().get_data()), nullptr, 0, nullptr, nullptr)) {
- DeleteFileW(tmpfile);
+ if (!::ReplaceFileW((LPCWSTR)tmpfile_utf16.get_data(), (LPCWSTR)(path.utf16().get_data()), nullptr, 0, nullptr, nullptr)) {
+ DeleteFileW((LPCWSTR)tmpfile_utf16.get_data());
return FAILED;
}
- return ::_wrename(tmpfile, (LPCWSTR)(p_new_path.utf16().get_data())) == 0 ? OK : FAILED;
+ return MoveFileW((LPCWSTR)tmpfile_utf16.get_data(), (LPCWSTR)(new_path.utf16().get_data())) != 0 ? OK : FAILED;
} else {
- if (file_exists(p_new_path)) {
- if (remove(p_new_path) != OK) {
+ if (file_exists(new_path)) {
+ if (remove(new_path) != OK) {
return FAILED;
}
}
- return ::_wrename((LPCWSTR)(p_path.utf16().get_data()), (LPCWSTR)(p_new_path.utf16().get_data())) == 0 ? OK : FAILED;
+ return MoveFileW((LPCWSTR)(path.utf16().get_data()), (LPCWSTR)(new_path.utf16().get_data())) != 0 ? OK : FAILED;
}
}
Error DirAccessWindows::remove(String p_path) {
- if (p_path.is_relative_path()) {
- p_path = get_current_dir().path_join(p_path);
- }
-
- p_path = fix_path(p_path);
+ String path = fix_path(p_path);
+ const Char16String &path_utf16 = path.utf16();
DWORD fileAttr;
- fileAttr = GetFileAttributesW((LPCWSTR)(p_path.utf16().get_data()));
+ fileAttr = GetFileAttributesW((LPCWSTR)(path_utf16.get_data()));
if (INVALID_FILE_ATTRIBUTES == fileAttr) {
return FAILED;
}
if ((fileAttr & FILE_ATTRIBUTE_DIRECTORY)) {
- return ::_wrmdir((LPCWSTR)(p_path.utf16().get_data())) == 0 ? OK : FAILED;
+ return RemoveDirectoryW((LPCWSTR)(path_utf16.get_data())) != 0 ? OK : FAILED;
} else {
- return ::_wunlink((LPCWSTR)(p_path.utf16().get_data())) == 0 ? OK : FAILED;
+ return DeleteFileW((LPCWSTR)(path_utf16.get_data())) != 0 ? OK : FAILED;
}
}
@@ -339,16 +338,16 @@ uint64_t DirAccessWindows::get_space_left() {
}
String DirAccessWindows::get_filesystem_type() const {
- String path = fix_path(const_cast<DirAccessWindows *>(this)->get_current_dir());
-
- int unit_end = path.find(":");
- ERR_FAIL_COND_V(unit_end == -1, String());
- String unit = path.substr(0, unit_end + 1) + "\\";
+ String path = current_dir.trim_prefix(R"(\\?\)");
if (path.is_network_share_path()) {
return "Network Share";
}
+ int unit_end = path.find(":");
+ ERR_FAIL_COND_V(unit_end == -1, String());
+ String unit = path.substr(0, unit_end + 1) + "\\";
+
WCHAR szVolumeName[100];
WCHAR szFileSystemName[10];
DWORD dwSerialNumber = 0;
@@ -370,11 +369,7 @@ String DirAccessWindows::get_filesystem_type() const {
}
bool DirAccessWindows::is_case_sensitive(const String &p_path) const {
- String f = p_path;
- if (!f.is_absolute_path()) {
- f = get_current_dir().path_join(f);
- }
- f = fix_path(f);
+ String f = fix_path(p_path);
HANDLE h_file = ::CreateFileW((LPCWSTR)(f.utf16().get_data()), 0,
FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
@@ -397,12 +392,7 @@ bool DirAccessWindows::is_case_sensitive(const String &p_path) const {
}
bool DirAccessWindows::is_link(String p_file) {
- String f = p_file;
-
- if (!f.is_absolute_path()) {
- f = get_current_dir().path_join(f);
- }
- f = fix_path(f);
+ String f = fix_path(p_file);
DWORD attr = GetFileAttributesW((LPCWSTR)(f.utf16().get_data()));
if (attr == INVALID_FILE_ATTRIBUTES) {
@@ -413,12 +403,7 @@ bool DirAccessWindows::is_link(String p_file) {
}
String DirAccessWindows::read_link(String p_file) {
- String f = p_file;
-
- if (!f.is_absolute_path()) {
- f = get_current_dir().path_join(f);
- }
- f = fix_path(f);
+ String f = fix_path(p_file);
HANDLE hfile = CreateFileW((LPCWSTR)(f.utf16().get_data()), GENERIC_READ, FILE_SHARE_READ, nullptr, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, nullptr);
if (hfile == INVALID_HANDLE_VALUE) {
@@ -434,22 +419,18 @@ String DirAccessWindows::read_link(String p_file) {
GetFinalPathNameByHandleW(hfile, (LPWSTR)cs.ptrw(), ret, VOLUME_NAME_DOS | FILE_NAME_NORMALIZED);
CloseHandle(hfile);
- return String::utf16((const char16_t *)cs.ptr(), ret).trim_prefix(R"(\\?\)");
+ return String::utf16((const char16_t *)cs.ptr(), ret).trim_prefix(R"(\\?\)").replace("\\", "/");
}
Error DirAccessWindows::create_link(String p_source, String p_target) {
- if (p_target.is_relative_path()) {
- p_target = get_current_dir().path_join(p_target);
- }
+ String source = fix_path(p_source);
+ String target = fix_path(p_target);
- p_source = fix_path(p_source);
- p_target = fix_path(p_target);
-
- DWORD file_attr = GetFileAttributesW((LPCWSTR)(p_source.utf16().get_data()));
+ DWORD file_attr = GetFileAttributesW((LPCWSTR)(source.utf16().get_data()));
bool is_dir = (file_attr & FILE_ATTRIBUTE_DIRECTORY);
DWORD flags = ((is_dir) ? SYMBOLIC_LINK_FLAG_DIRECTORY : 0) | SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE;
- if (CreateSymbolicLinkW((LPCWSTR)p_target.utf16().get_data(), (LPCWSTR)p_source.utf16().get_data(), flags) != 0) {
+ if (CreateSymbolicLinkW((LPCWSTR)target.utf16().get_data(), (LPCWSTR)source.utf16().get_data(), flags) != 0) {
return OK;
} else {
return FAILED;
@@ -459,7 +440,12 @@ Error DirAccessWindows::create_link(String p_source, String p_target) {
DirAccessWindows::DirAccessWindows() {
p = memnew(DirAccessWindowsPrivate);
p->h = INVALID_HANDLE_VALUE;
- current_dir = ".";
+
+ Char16String real_current_dir_name;
+ size_t str_len = GetCurrentDirectoryW(0, nullptr);
+ real_current_dir_name.resize(str_len + 1);
+ GetCurrentDirectoryW(real_current_dir_name.size(), (LPWSTR)real_current_dir_name.ptrw());
+ current_dir = String::utf16((const char16_t *)real_current_dir_name.get_data());
DWORD mask = GetLogicalDrives();
diff --git a/drivers/windows/file_access_windows.cpp b/drivers/windows/file_access_windows.cpp
index 9885d9d7ee..0243d863f8 100644
--- a/drivers/windows/file_access_windows.cpp
+++ b/drivers/windows/file_access_windows.cpp
@@ -73,8 +73,18 @@ bool FileAccessWindows::is_path_invalid(const String &p_path) {
String FileAccessWindows::fix_path(const String &p_path) const {
String r_path = FileAccess::fix_path(p_path);
- if (r_path.is_absolute_path() && !r_path.is_network_share_path() && r_path.length() > MAX_PATH) {
- r_path = "\\\\?\\" + r_path.replace("/", "\\");
+
+ if (r_path.is_relative_path()) {
+ Char16String current_dir_name;
+ size_t str_len = GetCurrentDirectoryW(0, nullptr);
+ current_dir_name.resize(str_len + 1);
+ GetCurrentDirectoryW(current_dir_name.size(), (LPWSTR)current_dir_name.ptrw());
+ r_path = String::utf16((const char16_t *)current_dir_name.get_data()).trim_prefix(R"(\\?\)").replace("\\", "/").path_join(r_path);
+ }
+ r_path = r_path.simplify_path();
+ r_path = r_path.replace("/", "\\");
+ if (!r_path.is_network_share_path() && !r_path.begins_with(R"(\\?\)")) {
+ r_path = R"(\\?\)" + r_path;
}
return r_path;
}
@@ -108,9 +118,6 @@ Error FileAccessWindows::open_internal(const String &p_path, int p_mode_flags) {
return ERR_INVALID_PARAMETER;
}
- /* Pretty much every implementation that uses fopen as primary
- backend supports utf8 encoding. */
-
struct _stat st;
if (_wstat((LPCWSTR)(path.utf16().get_data()), &st) == 0) {
if (!S_ISREG(st.st_mode)) {
@@ -125,7 +132,7 @@ Error FileAccessWindows::open_internal(const String &p_path, int p_mode_flags) {
// platforms), we only check for relative paths, or paths in res:// or user://,
// other paths aren't likely to be portable anyway.
if (p_mode_flags == READ && (p_path.is_relative_path() || get_access_type() != ACCESS_FILESYSTEM)) {
- String base_path = path;
+ String base_path = p_path;
String working_path;
String proper_path;
@@ -144,23 +151,17 @@ Error FileAccessWindows::open_internal(const String &p_path, int p_mode_flags) {
}
proper_path = "user://";
}
+ working_path = fix_path(working_path);
WIN32_FIND_DATAW d;
- Vector<String> parts = base_path.split("/");
+ Vector<String> parts = base_path.simplify_path().split("/");
bool mismatch = false;
for (const String &part : parts) {
- working_path = working_path.path_join(part);
-
- // Skip if relative.
- if (part == "." || part == "..") {
- proper_path = proper_path.path_join(part);
- continue;
- }
+ working_path = working_path + "\\" + part;
HANDLE fnd = FindFirstFileW((LPCWSTR)(working_path.utf16().get_data()), &d);
-
if (fnd == INVALID_HANDLE_VALUE) {
mismatch = false;
break;
@@ -186,12 +187,22 @@ Error FileAccessWindows::open_internal(const String &p_path, int p_mode_flags) {
if (is_backup_save_enabled() && p_mode_flags == WRITE) {
save_path = path;
// Create a temporary file in the same directory as the target file.
- WCHAR tmpFileName[MAX_PATH];
- if (GetTempFileNameW((LPCWSTR)(path.get_base_dir().utf16().get_data()), (LPCWSTR)(path.get_file().utf16().get_data()), 0, tmpFileName) == 0) {
- last_error = ERR_FILE_CANT_OPEN;
- return last_error;
+ // Note: do not use GetTempFileNameW, it's not long path aware!
+ String tmpfile;
+ uint64_t id = OS::get_singleton()->get_ticks_usec();
+ while (true) {
+ tmpfile = path + itos(id++) + ".tmp";
+ HANDLE handle = CreateFileW((LPCWSTR)tmpfile.utf16().get_data(), GENERIC_WRITE, 0, NULL, CREATE_NEW, FILE_ATTRIBUTE_NORMAL, 0);
+ if (handle != INVALID_HANDLE_VALUE) {
+ CloseHandle(handle);
+ break;
+ }
+ if (GetLastError() != ERROR_FILE_EXISTS && GetLastError() != ERROR_SHARING_VIOLATION) {
+ last_error = ERR_FILE_CANT_WRITE;
+ return FAILED;
+ }
}
- path = tmpFileName;
+ path = tmpfile;
}
f = _wfsopen((LPCWSTR)(path.utf16().get_data()), mode_string, is_backup_save_enabled() ? _SH_SECURE : _SH_DENYNO);
@@ -235,7 +246,7 @@ void FileAccessWindows::_close() {
} else {
// Either the target exists and is locked (temporarily, hopefully)
// or it doesn't exist; let's assume the latter before re-trying.
- rename_error = _wrename((LPCWSTR)(path_utf16.get_data()), (LPCWSTR)(save_path_utf16.get_data())) != 0;
+ rename_error = MoveFileW((LPCWSTR)(path_utf16.get_data()), (LPCWSTR)(save_path_utf16.get_data())) == 0;
}
if (!rename_error) {
@@ -262,7 +273,7 @@ String FileAccessWindows::get_path() const {
}
String FileAccessWindows::get_path_absolute() const {
- return path;
+ return path.trim_prefix(R"(\\?\)").replace("\\", "/");
}
bool FileAccessWindows::is_open() const {
@@ -312,93 +323,9 @@ bool FileAccessWindows::eof_reached() const {
return last_error == ERR_FILE_EOF;
}
-uint8_t FileAccessWindows::get_8() const {
- ERR_FAIL_NULL_V(f, 0);
-
- if (flags == READ_WRITE || flags == WRITE_READ) {
- if (prev_op == WRITE) {
- fflush(f);
- }
- prev_op = READ;
- }
- uint8_t b;
- if (fread(&b, 1, 1, f) == 0) {
- check_errors();
- b = '\0';
- }
-
- return b;
-}
-
-uint16_t FileAccessWindows::get_16() const {
- ERR_FAIL_NULL_V(f, 0);
-
- if (flags == READ_WRITE || flags == WRITE_READ) {
- if (prev_op == WRITE) {
- fflush(f);
- }
- prev_op = READ;
- }
-
- uint16_t b = 0;
- if (fread(&b, 1, 2, f) != 2) {
- check_errors();
- }
-
- if (big_endian) {
- b = BSWAP16(b);
- }
-
- return b;
-}
-
-uint32_t FileAccessWindows::get_32() const {
- ERR_FAIL_NULL_V(f, 0);
-
- if (flags == READ_WRITE || flags == WRITE_READ) {
- if (prev_op == WRITE) {
- fflush(f);
- }
- prev_op = READ;
- }
-
- uint32_t b = 0;
- if (fread(&b, 1, 4, f) != 4) {
- check_errors();
- }
-
- if (big_endian) {
- b = BSWAP32(b);
- }
-
- return b;
-}
-
-uint64_t FileAccessWindows::get_64() const {
- ERR_FAIL_NULL_V(f, 0);
-
- if (flags == READ_WRITE || flags == WRITE_READ) {
- if (prev_op == WRITE) {
- fflush(f);
- }
- prev_op = READ;
- }
-
- uint64_t b = 0;
- if (fread(&b, 1, 8, f) != 8) {
- check_errors();
- }
-
- if (big_endian) {
- b = BSWAP64(b);
- }
-
- return b;
-}
-
uint64_t FileAccessWindows::get_buffer(uint8_t *p_dst, uint64_t p_length) const {
- ERR_FAIL_COND_V(!p_dst && p_length > 0, -1);
ERR_FAIL_NULL_V(f, -1);
+ ERR_FAIL_COND_V(!p_dst && p_length > 0, -1);
if (flags == READ_WRITE || flags == WRITE_READ) {
if (prev_op == WRITE) {
@@ -406,8 +333,10 @@ uint64_t FileAccessWindows::get_buffer(uint8_t *p_dst, uint64_t p_length) const
}
prev_op = READ;
}
+
uint64_t read = fread(p_dst, 1, p_length, f);
check_errors();
+
return read;
}
@@ -442,77 +371,6 @@ void FileAccessWindows::flush() {
}
}
-void FileAccessWindows::store_8(uint8_t p_dest) {
- ERR_FAIL_NULL(f);
-
- if (flags == READ_WRITE || flags == WRITE_READ) {
- if (prev_op == READ) {
- if (last_error != ERR_FILE_EOF) {
- fseek(f, 0, SEEK_CUR);
- }
- }
- prev_op = WRITE;
- }
- fwrite(&p_dest, 1, 1, f);
-}
-
-void FileAccessWindows::store_16(uint16_t p_dest) {
- ERR_FAIL_NULL(f);
-
- if (flags == READ_WRITE || flags == WRITE_READ) {
- if (prev_op == READ) {
- if (last_error != ERR_FILE_EOF) {
- fseek(f, 0, SEEK_CUR);
- }
- }
- prev_op = WRITE;
- }
-
- if (big_endian) {
- p_dest = BSWAP16(p_dest);
- }
-
- fwrite(&p_dest, 1, 2, f);
-}
-
-void FileAccessWindows::store_32(uint32_t p_dest) {
- ERR_FAIL_NULL(f);
-
- if (flags == READ_WRITE || flags == WRITE_READ) {
- if (prev_op == READ) {
- if (last_error != ERR_FILE_EOF) {
- fseek(f, 0, SEEK_CUR);
- }
- }
- prev_op = WRITE;
- }
-
- if (big_endian) {
- p_dest = BSWAP32(p_dest);
- }
-
- fwrite(&p_dest, 1, 4, f);
-}
-
-void FileAccessWindows::store_64(uint64_t p_dest) {
- ERR_FAIL_NULL(f);
-
- if (flags == READ_WRITE || flags == WRITE_READ) {
- if (prev_op == READ) {
- if (last_error != ERR_FILE_EOF) {
- fseek(f, 0, SEEK_CUR);
- }
- }
- prev_op = WRITE;
- }
-
- if (big_endian) {
- p_dest = BSWAP64(p_dest);
- }
-
- fwrite(&p_dest, 1, 8, f);
-}
-
void FileAccessWindows::store_buffer(const uint8_t *p_src, uint64_t p_length) {
ERR_FAIL_NULL(f);
ERR_FAIL_COND(!p_src && p_length > 0);
@@ -525,6 +383,7 @@ void FileAccessWindows::store_buffer(const uint8_t *p_src, uint64_t p_length) {
}
prev_op = WRITE;
}
+
ERR_FAIL_COND(fwrite(p_src, 1, p_length, f) != (size_t)p_length);
}
@@ -549,7 +408,7 @@ uint64_t FileAccessWindows::_get_modified_time(const String &p_file) {
}
String file = fix_path(p_file);
- if (file.ends_with("/") && file != "/") {
+ if (file.ends_with("\\") && file != "\\") {
file = file.substr(0, file.length() - 1);
}
@@ -582,14 +441,15 @@ bool FileAccessWindows::_get_hidden_attribute(const String &p_file) {
Error FileAccessWindows::_set_hidden_attribute(const String &p_file, bool p_hidden) {
String file = fix_path(p_file);
+ const Char16String &file_utf16 = file.utf16();
- DWORD attrib = GetFileAttributesW((LPCWSTR)file.utf16().get_data());
+ DWORD attrib = GetFileAttributesW((LPCWSTR)file_utf16.get_data());
ERR_FAIL_COND_V_MSG(attrib == INVALID_FILE_ATTRIBUTES, FAILED, "Failed to get attributes for: " + p_file);
BOOL ok;
if (p_hidden) {
- ok = SetFileAttributesW((LPCWSTR)file.utf16().get_data(), attrib | FILE_ATTRIBUTE_HIDDEN);
+ ok = SetFileAttributesW((LPCWSTR)file_utf16.get_data(), attrib | FILE_ATTRIBUTE_HIDDEN);
} else {
- ok = SetFileAttributesW((LPCWSTR)file.utf16().get_data(), attrib & ~FILE_ATTRIBUTE_HIDDEN);
+ ok = SetFileAttributesW((LPCWSTR)file_utf16.get_data(), attrib & ~FILE_ATTRIBUTE_HIDDEN);
}
ERR_FAIL_COND_V_MSG(!ok, FAILED, "Failed to set attributes for: " + p_file);
@@ -606,14 +466,15 @@ bool FileAccessWindows::_get_read_only_attribute(const String &p_file) {
Error FileAccessWindows::_set_read_only_attribute(const String &p_file, bool p_ro) {
String file = fix_path(p_file);
+ const Char16String &file_utf16 = file.utf16();
- DWORD attrib = GetFileAttributesW((LPCWSTR)file.utf16().get_data());
+ DWORD attrib = GetFileAttributesW((LPCWSTR)file_utf16.get_data());
ERR_FAIL_COND_V_MSG(attrib == INVALID_FILE_ATTRIBUTES, FAILED, "Failed to get attributes for: " + p_file);
BOOL ok;
if (p_ro) {
- ok = SetFileAttributesW((LPCWSTR)file.utf16().get_data(), attrib | FILE_ATTRIBUTE_READONLY);
+ ok = SetFileAttributesW((LPCWSTR)file_utf16.get_data(), attrib | FILE_ATTRIBUTE_READONLY);
} else {
- ok = SetFileAttributesW((LPCWSTR)file.utf16().get_data(), attrib & ~FILE_ATTRIBUTE_READONLY);
+ ok = SetFileAttributesW((LPCWSTR)file_utf16.get_data(), attrib & ~FILE_ATTRIBUTE_READONLY);
}
ERR_FAIL_COND_V_MSG(!ok, FAILED, "Failed to set attributes for: " + p_file);
diff --git a/drivers/windows/file_access_windows.h b/drivers/windows/file_access_windows.h
index a25bbcfb3a..f458ff9c6c 100644
--- a/drivers/windows/file_access_windows.h
+++ b/drivers/windows/file_access_windows.h
@@ -69,20 +69,12 @@ public:
virtual bool eof_reached() const override; ///< reading passed EOF
- virtual uint8_t get_8() const override; ///< get a byte
- virtual uint16_t get_16() const override;
- virtual uint32_t get_32() const override;
- virtual uint64_t get_64() const override;
virtual uint64_t get_buffer(uint8_t *p_dst, uint64_t p_length) const override;
virtual Error get_error() const override; ///< get last error
virtual Error resize(int64_t p_length) override;
virtual void flush() override;
- virtual void store_8(uint8_t p_dest) override; ///< store a byte
- virtual void store_16(uint16_t p_dest) override;
- virtual void store_32(uint32_t p_dest) override;
- virtual void store_64(uint64_t p_dest) override;
virtual void store_buffer(const uint8_t *p_src, uint64_t p_length) override; ///< store an array of bytes
virtual bool file_exists(const String &p_name) override; ///< return true if a file exists
diff --git a/drivers/windows/file_access_windows_pipe.cpp b/drivers/windows/file_access_windows_pipe.cpp
index 7902c8e1d8..9bf0f4d852 100644
--- a/drivers/windows/file_access_windows_pipe.cpp
+++ b/drivers/windows/file_access_windows_pipe.cpp
@@ -35,7 +35,7 @@
#include "core/os/os.h"
#include "core/string/print_string.h"
-Error FileAccessWindowsPipe::open_existing(HANDLE p_rfd, HANDLE p_wfd) {
+Error FileAccessWindowsPipe::open_existing(HANDLE p_rfd, HANDLE p_wfd, bool p_blocking) {
// Open pipe using handles created by CreatePipe(rfd, wfd, NULL, 4096) call in the OS.execute_with_pipe.
_close();
@@ -44,6 +44,12 @@ Error FileAccessWindowsPipe::open_existing(HANDLE p_rfd, HANDLE p_wfd) {
fd[0] = p_rfd;
fd[1] = p_wfd;
+ if (!p_blocking) {
+ DWORD mode = PIPE_READMODE_BYTE | PIPE_NOWAIT;
+ SetNamedPipeHandleState(fd[0], &mode, nullptr, nullptr);
+ SetNamedPipeHandleState(fd[1], &mode, nullptr, nullptr);
+ }
+
last_error = OK;
return OK;
}
@@ -58,7 +64,7 @@ Error FileAccessWindowsPipe::open_internal(const String &p_path, int p_mode_flag
HANDLE h = CreateFileW((LPCWSTR)path.utf16().get_data(), GENERIC_READ | GENERIC_WRITE, 0, nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
if (h == INVALID_HANDLE_VALUE) {
- h = CreateNamedPipeW((LPCWSTR)path.utf16().get_data(), PIPE_ACCESS_DUPLEX, PIPE_TYPE_BYTE | PIPE_READMODE_BYTE | PIPE_WAIT, 1, 4096, 4096, 0, nullptr);
+ h = CreateNamedPipeW((LPCWSTR)path.utf16().get_data(), PIPE_ACCESS_DUPLEX, PIPE_TYPE_BYTE | PIPE_READMODE_BYTE | PIPE_NOWAIT, 1, 4096, 4096, 0, nullptr);
if (h == INVALID_HANDLE_VALUE) {
last_error = ERR_FILE_CANT_OPEN;
return last_error;
@@ -96,24 +102,11 @@ String FileAccessWindowsPipe::get_path_absolute() const {
return path_src;
}
-uint8_t FileAccessWindowsPipe::get_8() const {
- ERR_FAIL_COND_V_MSG(fd[0] == 0, 0, "Pipe must be opened before use.");
-
- uint8_t b;
- if (!ReadFile(fd[0], &b, 1, nullptr, nullptr)) {
- last_error = ERR_FILE_CANT_READ;
- b = '\0';
- } else {
- last_error = OK;
- }
- return b;
-}
-
uint64_t FileAccessWindowsPipe::get_buffer(uint8_t *p_dst, uint64_t p_length) const {
- ERR_FAIL_COND_V(!p_dst && p_length > 0, -1);
ERR_FAIL_COND_V_MSG(fd[0] == 0, -1, "Pipe must be opened before use.");
+ ERR_FAIL_COND_V(!p_dst && p_length > 0, -1);
- DWORD read = -1;
+ DWORD read = 0;
if (!ReadFile(fd[0], p_dst, p_length, &read, nullptr) || read != p_length) {
last_error = ERR_FILE_CANT_READ;
} else {
@@ -126,15 +119,6 @@ Error FileAccessWindowsPipe::get_error() const {
return last_error;
}
-void FileAccessWindowsPipe::store_8(uint8_t p_src) {
- ERR_FAIL_COND_MSG(fd[1] == 0, "Pipe must be opened before use.");
- if (!WriteFile(fd[1], &p_src, 1, nullptr, nullptr)) {
- last_error = ERR_FILE_CANT_WRITE;
- } else {
- last_error = OK;
- }
-}
-
void FileAccessWindowsPipe::store_buffer(const uint8_t *p_src, uint64_t p_length) {
ERR_FAIL_COND_MSG(fd[1] == 0, "Pipe must be opened before use.");
ERR_FAIL_COND(!p_src && p_length > 0);
diff --git a/drivers/windows/file_access_windows_pipe.h b/drivers/windows/file_access_windows_pipe.h
index b885ef78e6..1eb3c6ef2f 100644
--- a/drivers/windows/file_access_windows_pipe.h
+++ b/drivers/windows/file_access_windows_pipe.h
@@ -49,7 +49,7 @@ class FileAccessWindowsPipe : public FileAccess {
void _close();
public:
- Error open_existing(HANDLE p_rfd, HANDLE p_wfd);
+ Error open_existing(HANDLE p_rfd, HANDLE p_wfd, bool p_blocking);
virtual Error open_internal(const String &p_path, int p_mode_flags) override; ///< open a file
virtual bool is_open() const override; ///< true when file is open
@@ -64,14 +64,12 @@ public:
virtual bool eof_reached() const override { return false; }
- virtual uint8_t get_8() const override; ///< get a byte
virtual uint64_t get_buffer(uint8_t *p_dst, uint64_t p_length) const override;
virtual Error get_error() const override; ///< get last error
virtual Error resize(int64_t p_length) override { return ERR_UNAVAILABLE; }
virtual void flush() override {}
- virtual void store_8(uint8_t p_src) override; ///< store a byte
virtual void store_buffer(const uint8_t *p_src, uint64_t p_length) override; ///< store an array of bytes
virtual bool file_exists(const String &p_name) override { return false; }
diff --git a/editor/action_map_editor.cpp b/editor/action_map_editor.cpp
index 6b237366fd..16423fb111 100644
--- a/editor/action_map_editor.cpp
+++ b/editor/action_map_editor.cpp
@@ -584,7 +584,7 @@ ActionMapEditor::ActionMapEditor() {
show_builtin_actions_checkbutton = memnew(CheckButton);
show_builtin_actions_checkbutton->set_text(TTR("Show Built-in Actions"));
- show_builtin_actions_checkbutton->connect("toggled", callable_mp(this, &ActionMapEditor::set_show_builtin_actions));
+ show_builtin_actions_checkbutton->connect(SceneStringName(toggled), callable_mp(this, &ActionMapEditor::set_show_builtin_actions));
add_hbox->add_child(show_builtin_actions_checkbutton);
show_builtin_actions = EditorSettings::get_singleton()->get_project_metadata("project_settings", "show_builtin_actions", false);
diff --git a/editor/animation_bezier_editor.cpp b/editor/animation_bezier_editor.cpp
index 5196857240..24fcfd3930 100644
--- a/editor/animation_bezier_editor.cpp
+++ b/editor/animation_bezier_editor.cpp
@@ -174,7 +174,7 @@ void AnimationBezierTrackEdit::_draw_track(int p_track, const Color &p_color) {
}
if (lines.size() >= 2) {
- draw_multiline(lines, p_color, Math::round(EDSCALE));
+ draw_multiline(lines, p_color, Math::round(EDSCALE), true);
}
}
}
@@ -208,7 +208,7 @@ void AnimationBezierTrackEdit::_draw_line_clipped(const Vector2 &p_from, const V
from = from.lerp(to, c);
}
- draw_line(from, to, p_color, Math::round(EDSCALE));
+ draw_line(from, to, p_color, Math::round(EDSCALE), true);
}
void AnimationBezierTrackEdit::_notification(int p_what) {
@@ -719,7 +719,7 @@ void AnimationBezierTrackEdit::set_root(Node *p_root) {
void AnimationBezierTrackEdit::set_filtered(bool p_filtered) {
is_filtered = p_filtered;
- if (animation == nullptr) {
+ if (animation.is_null()) {
return;
}
String base_path = animation->track_get_path(selected_track);
@@ -1660,7 +1660,7 @@ void AnimationBezierTrackEdit::_menu_selected(int p_index) {
switch (p_index) {
case MENU_KEY_INSERT: {
if (animation->get_track_count() > 0) {
- if (editor->snap->is_pressed() && editor->step->get_value() != 0) {
+ if (editor->snap_keys->is_pressed() && editor->step->get_value() != 0) {
time = editor->snap_time(time);
}
while (animation->track_find_key(selected_track, time, Animation::FIND_MODE_APPROX) != -1) {
@@ -1736,7 +1736,7 @@ void AnimationBezierTrackEdit::duplicate_selected_keys(real_t p_ofs, bool p_ofs_
real_t insert_pos = p_ofs_valid ? p_ofs : timeline->get_play_position();
if (p_ofs_valid) {
- if (editor->snap->is_pressed() && editor->step->get_value() != 0) {
+ if (editor->snap_keys->is_pressed() && editor->step->get_value() != 0) {
insert_pos = editor->snap_time(insert_pos);
}
}
@@ -1859,7 +1859,7 @@ void AnimationBezierTrackEdit::paste_keys(real_t p_ofs, bool p_ofs_valid) {
float insert_pos = p_ofs_valid ? p_ofs : timeline->get_play_position();
if (p_ofs_valid) {
- if (editor->snap->is_pressed() && editor->step->get_value() != 0) {
+ if (editor->snap_keys->is_pressed() && editor->step->get_value() != 0) {
insert_pos = editor->snap_time(insert_pos);
}
}
diff --git a/editor/animation_track_editor.cpp b/editor/animation_track_editor.cpp
index f4403780c2..95ba301282 100644
--- a/editor/animation_track_editor.cpp
+++ b/editor/animation_track_editor.cpp
@@ -41,6 +41,7 @@
#include "editor/gui/editor_spin_slider.h"
#include "editor/gui/scene_tree_editor.h"
#include "editor/inspector_dock.h"
+#include "editor/multi_node_edit.h"
#include "editor/plugins/animation_player_editor_plugin.h"
#include "editor/themes/editor_scale.h"
#include "scene/3d/mesh_instance_3d.h"
@@ -120,12 +121,18 @@ bool AnimationTrackKeyEdit::_set(const StringName &p_name, const Variant &p_valu
float val = p_value;
float prev_val = animation->track_get_key_transition(track, key);
setting = true;
+
EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
undo_redo->create_action(TTR("Animation Change Transition"), UndoRedo::MERGE_ENDS);
undo_redo->add_do_method(animation.ptr(), "track_set_key_transition", track, key, val);
undo_redo->add_undo_method(animation.ptr(), "track_set_key_transition", track, key, prev_val);
undo_redo->add_do_method(this, "_update_obj", animation);
undo_redo->add_undo_method(this, "_update_obj", animation);
+ AnimationPlayerEditor *ape = AnimationPlayerEditor::get_singleton();
+ if (ape) {
+ undo_redo->add_do_method(ape, "_animation_update_key_frame");
+ undo_redo->add_undo_method(ape, "_animation_update_key_frame");
+ }
undo_redo->commit_action();
setting = false;
@@ -177,12 +184,18 @@ bool AnimationTrackKeyEdit::_set(const StringName &p_name, const Variant &p_valu
}
setting = true;
+
undo_redo->create_action(TTR("Animation Change Keyframe Value"), UndoRedo::MERGE_ENDS);
Variant prev = animation->track_get_key_value(track, key);
undo_redo->add_do_method(animation.ptr(), "track_set_key_value", track, key, value);
undo_redo->add_undo_method(animation.ptr(), "track_set_key_value", track, key, prev);
undo_redo->add_do_method(this, "_update_obj", animation);
undo_redo->add_undo_method(this, "_update_obj", animation);
+ AnimationPlayerEditor *ape = AnimationPlayerEditor::get_singleton();
+ if (ape) {
+ undo_redo->add_do_method(ape, "_animation_update_key_frame");
+ undo_redo->add_undo_method(ape, "_animation_update_key_frame");
+ }
undo_redo->commit_action();
setting = false;
@@ -1540,14 +1553,18 @@ void AnimationTimelineEdit::_notification(int p_what) {
max_digit_width = MAX(digit_width, max_digit_width);
}
const int max_sc = int(Math::ceil(zoomw / scale));
- const int max_sc_width = String::num(max_sc).length() * max_digit_width;
+ const int max_sc_width = String::num(max_sc).length() * Math::ceil(max_digit_width);
+
+ const int min_margin = MAX(text_secondary_margin, text_primary_margin);
while (!step_found) {
int min = max_sc_width;
if (decimals > 0) {
- min += period_width + max_digit_width * decimals;
+ min += Math::ceil(period_width + max_digit_width * decimals);
}
+ min += (min_margin * 2);
+
static const int _multp[3] = { 1, 2, 5 };
for (int i = 0; i < 3; i++) {
step = (_multp[i] * dec);
@@ -1603,10 +1620,11 @@ void AnimationTimelineEdit::_notification(int p_what) {
int sc = int(Math::floor(pos * SC_ADJ));
int prev_sc = int(Math::floor(prev * SC_ADJ));
- bool sub = (sc % SC_ADJ);
if ((sc / step) != (prev_sc / step) || (prev_sc < 0 && sc >= 0)) {
int scd = sc < 0 ? prev_sc : sc;
+ bool sub = (((scd - (scd % step)) % (dec * 10)) != 0);
+
int line_margin = sub ? v_line_secondary_margin : v_line_primary_margin;
int line_width = sub ? v_line_secondary_width : v_line_primary_width;
Color line_color = sub ? v_line_secondary_color : v_line_primary_color;
@@ -3281,6 +3299,11 @@ void AnimationTrackEdit::_menu_selected(int p_index) {
undo_redo->create_action(TTR("Change Animation Update Mode"));
undo_redo->add_do_method(animation.ptr(), "value_track_set_update_mode", track, update_mode);
undo_redo->add_undo_method(animation.ptr(), "value_track_set_update_mode", track, animation->value_track_get_update_mode(track));
+ AnimationPlayerEditor *ape = AnimationPlayerEditor::get_singleton();
+ if (ape) {
+ undo_redo->add_do_method(ape, "_animation_update_key_frame");
+ undo_redo->add_undo_method(ape, "_animation_update_key_frame");
+ }
undo_redo->commit_action();
queue_redraw();
@@ -3295,6 +3318,11 @@ void AnimationTrackEdit::_menu_selected(int p_index) {
undo_redo->create_action(TTR("Change Animation Interpolation Mode"));
undo_redo->add_do_method(animation.ptr(), "track_set_interpolation_type", track, interp_mode);
undo_redo->add_undo_method(animation.ptr(), "track_set_interpolation_type", track, animation->track_get_interpolation_type(track));
+ AnimationPlayerEditor *ape = AnimationPlayerEditor::get_singleton();
+ if (ape) {
+ undo_redo->add_do_method(ape, "_animation_update_key_frame");
+ undo_redo->add_undo_method(ape, "_animation_update_key_frame");
+ }
undo_redo->commit_action();
queue_redraw();
} break;
@@ -3305,6 +3333,11 @@ void AnimationTrackEdit::_menu_selected(int p_index) {
undo_redo->create_action(TTR("Change Animation Loop Mode"));
undo_redo->add_do_method(animation.ptr(), "track_set_interpolation_loop_wrap", track, loop_wrap);
undo_redo->add_undo_method(animation.ptr(), "track_set_interpolation_loop_wrap", track, animation->track_get_interpolation_loop_wrap(track));
+ AnimationPlayerEditor *ape = AnimationPlayerEditor::get_singleton();
+ if (ape) {
+ undo_redo->add_do_method(ape, "_animation_update_key_frame");
+ undo_redo->add_undo_method(ape, "_animation_update_key_frame");
+ }
undo_redo->commit_action();
queue_redraw();
@@ -3582,7 +3615,8 @@ void AnimationTrackEditor::set_animation(const Ref<Animation> &p_anim, bool p_re
_update_step_spinbox();
step->set_block_signals(false);
step->set_read_only(false);
- snap->set_disabled(false);
+ snap_keys->set_disabled(false);
+ snap_timeline->set_disabled(false);
snap_mode->set_disabled(false);
auto_fit->set_disabled(false);
auto_fit_bezier->set_disabled(false);
@@ -3603,7 +3637,8 @@ void AnimationTrackEditor::set_animation(const Ref<Animation> &p_anim, bool p_re
step->set_value(0);
step->set_block_signals(false);
step->set_read_only(true);
- snap->set_disabled(true);
+ snap_keys->set_disabled(true);
+ snap_timeline->set_disabled(true);
snap_mode->set_disabled(true);
bezier_edit_icon->set_disabled(true);
auto_fit->set_disabled(true);
@@ -3661,7 +3696,7 @@ void AnimationTrackEditor::update_keying() {
EditorSelectionHistory *editor_history = EditorNode::get_singleton()->get_editor_selection_history();
if (is_visible_in_tree() && animation.is_valid() && editor_history->get_path_size() > 0) {
Object *obj = ObjectDB::get_instance(editor_history->get_path_object(0));
- keying_enabled = Object::cast_to<Node>(obj) != nullptr;
+ keying_enabled = Object::cast_to<Node>(obj) != nullptr || Object::cast_to<MultiNodeEdit>(obj) != nullptr;
}
if (keying_enabled == keying) {
@@ -4078,19 +4113,20 @@ void AnimationTrackEditor::_insert_animation_key(NodePath p_path, const Variant
_query_insert(id);
}
-void AnimationTrackEditor::insert_node_value_key(Node *p_node, const String &p_property, const Variant &p_value, bool p_only_if_exists) {
+void AnimationTrackEditor::insert_node_value_key(Node *p_node, const String &p_property, bool p_only_if_exists, bool p_advance) {
ERR_FAIL_NULL(root);
// Let's build a node path.
- Node *node = p_node;
- String path = root->get_path_to(node, true);
+ String path = root->get_path_to(p_node, true);
+
+ Variant value = p_node->get(p_property);
- if (Object::cast_to<AnimationPlayer>(node) && p_property == "current_animation") {
- if (node == AnimationPlayerEditor::get_singleton()->get_player()) {
+ if (Object::cast_to<AnimationPlayer>(p_node) && p_property == "current_animation") {
+ if (p_node == AnimationPlayerEditor::get_singleton()->get_player()) {
EditorNode::get_singleton()->show_warning(TTR("AnimationPlayer can't animate itself, only other players."));
return;
}
- _insert_animation_key(path, p_value);
+ _insert_animation_key(path, value);
return;
}
@@ -4118,26 +4154,26 @@ void AnimationTrackEditor::insert_node_value_key(Node *p_node, const String &p_p
InsertData id;
id.path = np;
id.track_idx = i;
- id.value = p_value;
+ id.value = value;
id.type = Animation::TYPE_VALUE;
// TRANSLATORS: This describes the target of new animation track, will be inserted into another string.
id.query = vformat(TTR("property '%s'"), p_property);
- id.advance = false;
+ id.advance = p_advance;
// Dialog insert.
_query_insert(id);
inserted = true;
} else if (animation->track_get_type(i) == Animation::TYPE_BEZIER) {
- Variant value;
+ Variant actual_value;
String track_path = animation->track_get_path(i);
if (track_path == np) {
- value = p_value; // All good.
+ actual_value = value; // All good.
} else {
int sep = track_path.rfind(":");
if (sep != -1) {
String base_path = track_path.substr(0, sep);
if (base_path == np) {
String value_name = track_path.substr(sep + 1);
- value = p_value.get(value_name);
+ actual_value = value.get(value_name);
} else {
continue;
}
@@ -4149,10 +4185,10 @@ void AnimationTrackEditor::insert_node_value_key(Node *p_node, const String &p_p
InsertData id;
id.path = animation->track_get_path(i);
id.track_idx = i;
- id.value = value;
+ id.value = actual_value;
id.type = Animation::TYPE_BEZIER;
id.query = vformat(TTR("property '%s'"), p_property);
- id.advance = false;
+ id.advance = p_advance;
// Dialog insert.
_query_insert(id);
inserted = true;
@@ -4165,105 +4201,41 @@ void AnimationTrackEditor::insert_node_value_key(Node *p_node, const String &p_p
InsertData id;
id.path = np;
id.track_idx = -1;
- id.value = p_value;
+ id.value = value;
id.type = Animation::TYPE_VALUE;
id.query = vformat(TTR("property '%s'"), p_property);
- id.advance = false;
+ id.advance = p_advance;
// Dialog insert.
_query_insert(id);
}
-void AnimationTrackEditor::insert_value_key(const String &p_property, const Variant &p_value, bool p_advance) {
+void AnimationTrackEditor::insert_value_key(const String &p_property, bool p_advance) {
EditorSelectionHistory *history = EditorNode::get_singleton()->get_editor_selection_history();
ERR_FAIL_NULL(root);
ERR_FAIL_COND(history->get_path_size() == 0);
Object *obj = ObjectDB::get_instance(history->get_path_object(0));
- ERR_FAIL_NULL(Object::cast_to<Node>(obj));
-
- // Let's build a node path.
- Node *node = Object::cast_to<Node>(obj);
- String path = root->get_path_to(node, true);
-
- if (Object::cast_to<AnimationPlayer>(node) && p_property == "current_animation") {
- if (node == AnimationPlayerEditor::get_singleton()->get_player()) {
- EditorNode::get_singleton()->show_warning(TTR("AnimationPlayer can't animate itself, only other players."));
- return;
- }
- _insert_animation_key(path, p_value);
- return;
- }
-
- for (int i = 1; i < history->get_path_size(); i++) {
- String prop = history->get_path_property(i);
- ERR_FAIL_COND(prop.is_empty());
- path += ":" + prop;
- }
-
- path += ":" + p_property;
-
- NodePath np = path;
-
- // Locate track.
-
- bool inserted = false;
- make_insert_queue();
- for (int i = 0; i < animation->get_track_count(); i++) {
- if (animation->track_get_type(i) == Animation::TYPE_VALUE) {
- if (animation->track_get_path(i) != np) {
- continue;
- }
+ Ref<MultiNodeEdit> multi_node_edit(obj);
+ if (multi_node_edit.is_valid()) {
+ Node *edited_scene = EditorNode::get_singleton()->get_edited_scene();
+ ERR_FAIL_NULL(edited_scene);
- InsertData id;
- id.path = np;
- id.track_idx = i;
- id.value = p_value;
- id.type = Animation::TYPE_VALUE;
- id.query = vformat(TTR("property '%s'"), p_property);
- id.advance = p_advance;
- // Dialog insert.
- _query_insert(id);
- inserted = true;
- } else if (animation->track_get_type(i) == Animation::TYPE_BEZIER) {
- Variant value;
- if (animation->track_get_path(i) == np) {
- value = p_value; // All good.
- } else {
- String tpath = animation->track_get_path(i);
- int index = tpath.rfind(":");
- if (NodePath(tpath.substr(0, index + 1)) == np) {
- String subindex = tpath.substr(index + 1, tpath.length() - index);
- value = p_value.get(subindex);
- } else {
- continue;
- }
- }
+ make_insert_queue();
- InsertData id;
- id.path = animation->track_get_path(i);
- id.track_idx = i;
- id.value = value;
- id.type = Animation::TYPE_BEZIER;
- id.query = vformat(TTR("property '%s'"), p_property);
- id.advance = p_advance;
- // Dialog insert.
- _query_insert(id);
- inserted = true;
+ for (int i = 0; i < multi_node_edit->get_node_count(); ++i) {
+ Node *node = edited_scene->get_node(multi_node_edit->get_node(i));
+ insert_node_value_key(node, p_property, false, p_advance);
}
- }
- commit_insert_queue();
- if (!inserted) {
- InsertData id;
- id.path = np;
- id.track_idx = -1;
- id.value = p_value;
- id.type = Animation::TYPE_VALUE;
- id.query = vformat(TTR("property '%s'"), p_property);
- id.advance = p_advance;
- // Dialog insert.
- _query_insert(id);
+ commit_insert_queue();
+ } else {
+ Node *node = Object::cast_to<Node>(obj);
+ ERR_FAIL_NULL(node);
+
+ make_insert_queue();
+ insert_node_value_key(node, p_property, false, p_advance);
+ commit_insert_queue();
}
}
@@ -4585,8 +4557,16 @@ bool AnimationTrackEditor::is_key_clipboard_active() const {
return key_clipboard.keys.size();
}
-bool AnimationTrackEditor::is_snap_enabled() const {
- return snap->is_pressed() ^ Input::get_singleton()->is_key_pressed(Key::CMD_OR_CTRL);
+bool AnimationTrackEditor::is_snap_timeline_enabled() const {
+ return snap_timeline->is_pressed() ^ Input::get_singleton()->is_key_pressed(Key::CMD_OR_CTRL);
+}
+
+bool AnimationTrackEditor::is_snap_keys_enabled() const {
+ return snap_keys->is_pressed() ^ Input::get_singleton()->is_key_pressed(Key::CMD_OR_CTRL);
+}
+
+bool AnimationTrackEditor::is_bezier_editor_active() const {
+ return bezier_edit->is_visible();
}
bool AnimationTrackEditor::can_add_reset_key() const {
@@ -4922,7 +4902,8 @@ void AnimationTrackEditor::_notification(int p_what) {
case NOTIFICATION_THEME_CHANGED: {
zoom_icon->set_texture(get_editor_theme_icon(SNAME("Zoom")));
bezier_edit_icon->set_icon(get_editor_theme_icon(SNAME("EditBezier")));
- snap->set_icon(get_editor_theme_icon(SNAME("Snap")));
+ snap_timeline->set_icon(get_editor_theme_icon(SNAME("SnapTimeline")));
+ snap_keys->set_icon(get_editor_theme_icon(SNAME("SnapKeys")));
view_group->set_icon(get_editor_theme_icon(view_group->is_pressed() ? SNAME("AnimationTrackList") : SNAME("AnimationTrackGroup")));
selected_filter->set_icon(get_editor_theme_icon(SNAME("AnimationFilter")));
imported_anim_warning->set_icon(get_editor_theme_icon(SNAME("NodeWarning")));
@@ -5243,7 +5224,7 @@ int AnimationTrackEditor::_get_track_selected() {
void AnimationTrackEditor::_insert_key_from_track(float p_ofs, int p_track) {
ERR_FAIL_INDEX(p_track, animation->get_track_count());
- if (snap->is_pressed() && step->get_value() != 0) {
+ if (snap_keys->is_pressed() && step->get_value() != 0) {
p_ofs = snap_time(p_ofs);
}
while (animation->track_find_key(p_track, p_ofs, Animation::FIND_MODE_APPROX) != -1) { // Make sure insertion point is valid.
@@ -5650,6 +5631,14 @@ void AnimationTrackEditor::_move_selection_commit() {
moving_selection = false;
undo_redo->add_do_method(this, "_redraw_tracks");
undo_redo->add_undo_method(this, "_redraw_tracks");
+
+ // Update key frame.
+ AnimationPlayerEditor *ape = AnimationPlayerEditor::get_singleton();
+ if (ape) {
+ undo_redo->add_do_method(ape, "_animation_update_key_frame");
+ undo_redo->add_undo_method(ape, "_animation_update_key_frame");
+ }
+
undo_redo->commit_action();
}
@@ -5858,7 +5847,7 @@ void AnimationTrackEditor::_anim_duplicate_keys(float p_ofs, bool p_ofs_valid, i
float insert_pos = p_ofs_valid ? p_ofs : timeline->get_play_position();
if (p_ofs_valid) {
- if (snap->is_pressed() && step->get_value() != 0) {
+ if (snap_keys->is_pressed() && step->get_value() != 0) {
insert_pos = snap_time(insert_pos);
}
}
@@ -6006,7 +5995,7 @@ void AnimationTrackEditor::_anim_paste_keys(float p_ofs, bool p_ofs_valid, int p
float insert_pos = p_ofs_valid ? p_ofs : timeline->get_play_position();
if (p_ofs_valid) {
- if (snap->is_pressed() && step->get_value() != 0) {
+ if (snap_keys->is_pressed() && step->get_value() != 0) {
insert_pos = snap_time(insert_pos);
}
}
@@ -6887,8 +6876,8 @@ void AnimationTrackEditor::_edit_menu_pressed(int p_option) {
_redraw_tracks();
_update_key_edit();
EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
- undo_redo->clear_history(true, undo_redo->get_history_id_for_object(animation.ptr()));
- undo_redo->clear_history(true, undo_redo->get_history_id_for_object(this));
+ undo_redo->clear_history(undo_redo->get_history_id_for_object(animation.ptr()));
+ undo_redo->clear_history(undo_redo->get_history_id_for_object(this));
} break;
case EDIT_CLEAN_UP_ANIMATION: {
@@ -7030,8 +7019,8 @@ void AnimationTrackEditor::_cleanup_animation(Ref<Animation> p_animation) {
}
EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
- undo_redo->clear_history(true, undo_redo->get_history_id_for_object(animation.ptr()));
- undo_redo->clear_history(true, undo_redo->get_history_id_for_object(this));
+ undo_redo->clear_history(undo_redo->get_history_id_for_object(animation.ptr()));
+ undo_redo->clear_history(undo_redo->get_history_id_for_object(this));
_update_tracks();
}
@@ -7079,12 +7068,15 @@ void AnimationTrackEditor::_update_snap_unit() {
if (timeline->is_using_fps()) {
snap_unit = 1.0 / step->get_value();
} else {
- snap_unit = 1.0 / Math::round(1.0 / step->get_value()); // Follow the snap behavior of the timeline editor.
+ double integer;
+ double fraction = Math::modf(step->get_value(), &integer);
+ fraction = 1.0 / Math::round(1.0 / fraction);
+ snap_unit = integer + fraction;
}
}
float AnimationTrackEditor::snap_time(float p_value, bool p_relative) {
- if (is_snap_enabled()) {
+ if (is_snap_keys_enabled()) {
if (Input::get_singleton()->is_key_pressed(Key::SHIFT)) {
// Use more precise snapping when holding Shift.
snap_unit *= 0.25;
@@ -7338,13 +7330,21 @@ AnimationTrackEditor::AnimationTrackEditor() {
bottom_hb->add_child(view_group);
bottom_hb->add_child(memnew(VSeparator));
- snap = memnew(Button);
- snap->set_flat(true);
- snap->set_text(TTR("Snap:") + " ");
- bottom_hb->add_child(snap);
- snap->set_disabled(true);
- snap->set_toggle_mode(true);
- snap->set_pressed(true);
+ snap_timeline = memnew(Button);
+ snap_timeline->set_flat(true);
+ bottom_hb->add_child(snap_timeline);
+ snap_timeline->set_disabled(true);
+ snap_timeline->set_toggle_mode(true);
+ snap_timeline->set_pressed(false);
+ snap_timeline->set_tooltip_text(TTR("Apply snapping to timeline cursor."));
+
+ snap_keys = memnew(Button);
+ snap_keys->set_flat(true);
+ bottom_hb->add_child(snap_keys);
+ snap_keys->set_disabled(true);
+ snap_keys->set_toggle_mode(true);
+ snap_keys->set_pressed(true);
+ snap_keys->set_tooltip_text(TTR("Apply snapping to selected key(s)."));
step = memnew(EditorSpinSlider);
step->set_min(0);
@@ -7713,6 +7713,8 @@ void AnimationTrackKeyEditEditor::_time_edit_exited() {
undo_redo->add_do_method(ate, "_select_at_anim", animation, track, new_time);
undo_redo->add_undo_method(ate, "_select_at_anim", animation, track, key_data_cache.time);
}
+ undo_redo->add_do_method(ape, "_animation_update_key_frame");
+ undo_redo->add_undo_method(ape, "_animation_update_key_frame");
}
undo_redo->commit_action();
diff --git a/editor/animation_track_editor.h b/editor/animation_track_editor.h
index 59ee6535ac..8a263d7d20 100644
--- a/editor/animation_track_editor.h
+++ b/editor/animation_track_editor.h
@@ -408,7 +408,8 @@ class AnimationTrackEditor : public VBoxContainer {
HSlider *zoom = nullptr;
EditorSpinSlider *step = nullptr;
TextureRect *zoom_icon = nullptr;
- Button *snap = nullptr;
+ Button *snap_keys = nullptr;
+ Button *snap_timeline = nullptr;
Button *bezier_edit_icon = nullptr;
OptionButton *snap_mode = nullptr;
Button *auto_fit = nullptr;
@@ -713,8 +714,8 @@ public:
void cleanup();
void set_anim_pos(float p_pos);
- void insert_node_value_key(Node *p_node, const String &p_property, const Variant &p_value, bool p_only_if_exists = false);
- void insert_value_key(const String &p_property, const Variant &p_value, bool p_advance);
+ void insert_node_value_key(Node *p_node, const String &p_property, bool p_only_if_exists = false, bool p_advance = false);
+ void insert_value_key(const String &p_property, bool p_advance);
void insert_transform_key(Node3D *p_node, const String &p_sub, const Animation::TrackType p_type, const Variant &p_value);
bool has_track(Node3D *p_node, const String &p_sub, const Animation::TrackType p_type);
void make_insert_queue();
@@ -728,7 +729,9 @@ public:
bool is_selection_active() const;
bool is_key_clipboard_active() const;
bool is_moving_selection() const;
- bool is_snap_enabled() const;
+ bool is_snap_timeline_enabled() const;
+ bool is_snap_keys_enabled() const;
+ bool is_bezier_editor_active() const;
bool can_add_reset_key() const;
float get_moving_selection_offset() const;
float snap_time(float p_value, bool p_relative = false);
diff --git a/editor/code_editor.cpp b/editor/code_editor.cpp
index 180c25c34f..dd8aa523c4 100644
--- a/editor/code_editor.cpp
+++ b/editor/code_editor.cpp
@@ -755,13 +755,13 @@ FindReplaceBar::FindReplaceBar() {
hbc_option_search->add_child(case_sensitive);
case_sensitive->set_text(TTR("Match Case"));
case_sensitive->set_focus_mode(FOCUS_NONE);
- case_sensitive->connect("toggled", callable_mp(this, &FindReplaceBar::_search_options_changed));
+ case_sensitive->connect(SceneStringName(toggled), callable_mp(this, &FindReplaceBar::_search_options_changed));
whole_words = memnew(CheckBox);
hbc_option_search->add_child(whole_words);
whole_words->set_text(TTR("Whole Words"));
whole_words->set_focus_mode(FOCUS_NONE);
- whole_words->connect("toggled", callable_mp(this, &FindReplaceBar::_search_options_changed));
+ whole_words->connect(SceneStringName(toggled), callable_mp(this, &FindReplaceBar::_search_options_changed));
// Replace toolbar
replace_text = memnew(LineEdit);
@@ -786,7 +786,7 @@ FindReplaceBar::FindReplaceBar() {
hbc_option_replace->add_child(selection_only);
selection_only->set_text(TTR("Selection Only"));
selection_only->set_focus_mode(FOCUS_NONE);
- selection_only->connect("toggled", callable_mp(this, &FindReplaceBar::_search_options_changed));
+ selection_only->connect(SceneStringName(toggled), callable_mp(this, &FindReplaceBar::_search_options_changed));
hide_button = memnew(TextureButton);
add_child(hide_button);
@@ -1548,7 +1548,8 @@ void CodeTextEditor::_set_show_warnings_panel(bool p_show) {
}
void CodeTextEditor::_toggle_scripts_pressed() {
- ScriptEditor::get_singleton()->toggle_scripts_panel();
+ ERR_FAIL_NULL(toggle_scripts_list);
+ toggle_scripts_list->set_visible(!toggle_scripts_list->is_visible());
update_toggle_scripts_button();
}
@@ -1723,16 +1724,18 @@ void CodeTextEditor::set_code_complete_func(CodeTextEditorCodeCompleteFunc p_cod
code_complete_ud = p_ud;
}
+void CodeTextEditor::set_toggle_list_control(Control *p_control) {
+ toggle_scripts_list = p_control;
+}
+
void CodeTextEditor::show_toggle_scripts_button() {
toggle_scripts_button->show();
}
void CodeTextEditor::update_toggle_scripts_button() {
- if (is_layout_rtl()) {
- toggle_scripts_button->set_icon(get_editor_theme_icon(ScriptEditor::get_singleton()->is_scripts_panel_toggled() ? SNAME("Forward") : SNAME("Back")));
- } else {
- toggle_scripts_button->set_icon(get_editor_theme_icon(ScriptEditor::get_singleton()->is_scripts_panel_toggled() ? SNAME("Back") : SNAME("Forward")));
- }
+ ERR_FAIL_NULL(toggle_scripts_list);
+ bool forward = toggle_scripts_list->is_visible() == is_layout_rtl();
+ toggle_scripts_button->set_icon(get_editor_theme_icon(forward ? SNAME("Forward") : SNAME("Back")));
toggle_scripts_button->set_tooltip_text(vformat("%s (%s)", TTR("Toggle Scripts Panel"), ED_GET_SHORTCUT("script_editor/toggle_scripts_panel")->get_as_text()));
}
diff --git a/editor/code_editor.h b/editor/code_editor.h
index d209d5c7a2..e56405a4b2 100644
--- a/editor/code_editor.h
+++ b/editor/code_editor.h
@@ -161,6 +161,7 @@ class CodeTextEditor : public VBoxContainer {
HBoxContainer *status_bar = nullptr;
Button *toggle_scripts_button = nullptr;
+ Control *toggle_scripts_list = nullptr;
Button *error_button = nullptr;
Button *warning_button = nullptr;
@@ -285,6 +286,7 @@ public:
void validate_script();
+ void set_toggle_list_control(Control *p_control);
void show_toggle_scripts_button();
void update_toggle_scripts_button();
diff --git a/editor/connections_dialog.cpp b/editor/connections_dialog.cpp
index eb0ab1174b..5273b61f37 100644
--- a/editor/connections_dialog.cpp
+++ b/editor/connections_dialog.cpp
@@ -486,11 +486,6 @@ void ConnectDialog::_notification(int p_what) {
type_list->set_item_icon(i, get_editor_theme_icon(type_name));
}
- Ref<StyleBox> style = get_theme_stylebox(CoreStringName(normal), "LineEdit")->duplicate();
- if (style.is_valid()) {
- style->set_content_margin(SIDE_TOP, style->get_content_margin(SIDE_TOP) + 1.0);
- from_signal->add_theme_style_override(CoreStringName(normal), style);
- }
method_search->set_right_icon(get_editor_theme_icon("Search"));
open_method_tree->set_icon(get_editor_theme_icon("Edit"));
} break;
@@ -576,6 +571,22 @@ String ConnectDialog::get_signature(const MethodInfo &p_method, PackedStringArra
type_name = "Array";
}
break;
+ case Variant::DICTIONARY:
+ type_name = "Dictionary";
+ if (pi.hint == PROPERTY_HINT_DICTIONARY_TYPE && !pi.hint_string.is_empty()) {
+ String key_hint = pi.hint_string.get_slice(";", 0);
+ String value_hint = pi.hint_string.get_slice(";", 1);
+ if (key_hint.is_empty() || key_hint.begins_with("res://")) {
+ key_hint = "Variant";
+ }
+ if (value_hint.is_empty() || value_hint.begins_with("res://")) {
+ value_hint = "Variant";
+ }
+ if (key_hint != "Variant" || value_hint != "Variant") {
+ type_name += "[" + key_hint + ", " + value_hint + "]";
+ }
+ }
+ break;
case Variant::OBJECT:
if (pi.class_name != StringName()) {
type_name = pi.class_name;
diff --git a/editor/debugger/editor_debugger_inspector.cpp b/editor/debugger/editor_debugger_inspector.cpp
index 816bd6b72c..cb5e4375a6 100644
--- a/editor/debugger/editor_debugger_inspector.cpp
+++ b/editor/debugger/editor_debugger_inspector.cpp
@@ -151,7 +151,7 @@ ObjectID EditorDebuggerInspector::add_object(const Array &p_arr) {
Variant &var = property.second;
if (pinfo.type == Variant::OBJECT) {
- if (var.get_type() == Variant::STRING) {
+ if (var.is_string()) {
String path = var;
if (path.contains("::")) {
// built-in resource
diff --git a/editor/debugger/editor_debugger_node.cpp b/editor/debugger/editor_debugger_node.cpp
index f01301384a..b4265f9fc0 100644
--- a/editor/debugger/editor_debugger_node.cpp
+++ b/editor/debugger/editor_debugger_node.cpp
@@ -303,7 +303,7 @@ void EditorDebuggerNode::stop(bool p_force) {
});
_break_state_changed();
breakpoints.clear();
- EditorUndoRedoManager::get_singleton()->clear_history(false, EditorUndoRedoManager::REMOTE_HISTORY);
+ EditorUndoRedoManager::get_singleton()->clear_history(EditorUndoRedoManager::REMOTE_HISTORY, false);
set_process(false);
}
diff --git a/editor/debugger/editor_debugger_tree.cpp b/editor/debugger/editor_debugger_tree.cpp
index f8ffce0c83..c4d7899b2d 100644
--- a/editor/debugger/editor_debugger_tree.cpp
+++ b/editor/debugger/editor_debugger_tree.cpp
@@ -124,6 +124,7 @@ void EditorDebuggerTree::_scene_tree_rmb_selected(const Vector2 &p_position, Mou
item_menu->clear();
item_menu->add_icon_item(get_editor_theme_icon(SNAME("CreateNewSceneFrom")), TTR("Save Branch as Scene"), ITEM_MENU_SAVE_REMOTE_NODE);
item_menu->add_icon_item(get_editor_theme_icon(SNAME("CopyNodePath")), TTR("Copy Node Path"), ITEM_MENU_COPY_NODE_PATH);
+ item_menu->add_icon_item(get_editor_theme_icon(SNAME("Collapse")), TTR("Expand/Collapse Branch"), ITEM_MENU_EXPAND_COLLAPSE);
item_menu->set_position(get_screen_position() + get_local_mouse_position());
item_menu->reset_size();
item_menu->popup();
@@ -359,6 +360,21 @@ void EditorDebuggerTree::_item_menu_id_pressed(int p_option) {
}
DisplayServer::get_singleton()->clipboard_set(text);
} break;
+ case ITEM_MENU_EXPAND_COLLAPSE: {
+ TreeItem *s_item = get_selected();
+
+ if (!s_item) {
+ s_item = get_root();
+ if (!s_item) {
+ break;
+ }
+ }
+
+ bool collapsed = s_item->is_any_collapsed();
+ s_item->set_collapsed_recursive(!collapsed);
+
+ ensure_cursor_is_visible();
+ }
}
}
diff --git a/editor/debugger/editor_debugger_tree.h b/editor/debugger/editor_debugger_tree.h
index dbffb0f219..705df17baf 100644
--- a/editor/debugger/editor_debugger_tree.h
+++ b/editor/debugger/editor_debugger_tree.h
@@ -43,6 +43,7 @@ private:
enum ItemMenu {
ITEM_MENU_SAVE_REMOTE_NODE,
ITEM_MENU_COPY_NODE_PATH,
+ ITEM_MENU_EXPAND_COLLAPSE,
};
ObjectID inspected_object_id;
diff --git a/editor/debugger/editor_visual_profiler.cpp b/editor/debugger/editor_visual_profiler.cpp
index 17977fcb29..56c9db44cd 100644
--- a/editor/debugger/editor_visual_profiler.cpp
+++ b/editor/debugger/editor_visual_profiler.cpp
@@ -103,6 +103,8 @@ void EditorVisualProfiler::clear() {
variables->clear();
//activate->set_pressed(false);
+ graph_limit = 1000.0f / CLAMP(int(EDITOR_GET("debugger/profiler_target_fps")), 1, 1000);
+
updating_frame = true;
cursor_metric_edit->set_min(0);
cursor_metric_edit->set_max(0);
@@ -812,6 +814,8 @@ EditorVisualProfiler::EditorVisualProfiler() {
int metric_size = CLAMP(int(EDITOR_GET("debugger/profiler_frame_history_size")), 60, 10000);
frame_metrics.resize(metric_size);
+ graph_limit = 1000.0f / CLAMP(int(EDITOR_GET("debugger/profiler_target_fps")), 1, 1000);
+
frame_delay = memnew(Timer);
frame_delay->set_wait_time(0.1);
frame_delay->set_one_shot(true);
diff --git a/editor/doc_tools.cpp b/editor/doc_tools.cpp
index bf5b717c19..79e0c7ebd1 100644
--- a/editor/doc_tools.cpp
+++ b/editor/doc_tools.cpp
@@ -277,6 +277,10 @@ static void merge_theme_properties(Vector<DocData::ThemeItemDoc> &p_to, const Ve
// Check found entry on name and data type.
if (to.name == from.name && to.data_type == from.data_type) {
to.description = from.description;
+ to.is_deprecated = from.is_deprecated;
+ to.deprecated_message = from.deprecated_message;
+ to.is_experimental = from.is_experimental;
+ to.experimental_message = from.experimental_message;
to.keywords = from.keywords;
}
}
@@ -571,6 +575,8 @@ void DocTools::generate(BitField<GenerateFlags> p_flags) {
prop.type = retinfo.class_name;
} else if (retinfo.type == Variant::ARRAY && retinfo.hint == PROPERTY_HINT_ARRAY_TYPE) {
prop.type = retinfo.hint_string + "[]";
+ } else if (retinfo.type == Variant::DICTIONARY && retinfo.hint == PROPERTY_HINT_DICTIONARY_TYPE) {
+ prop.type = "Dictionary[" + retinfo.hint_string.replace(";", ", ") + "]";
} else if (retinfo.hint == PROPERTY_HINT_RESOURCE_TYPE) {
prop.type = retinfo.hint_string;
} else if (retinfo.type == Variant::NIL && retinfo.usage & PROPERTY_USAGE_NIL_IS_VARIANT) {
@@ -1420,6 +1426,14 @@ Error DocTools::_load(Ref<XMLParser> parser) {
prop2.type = parser->get_named_attribute_value("type");
ERR_FAIL_COND_V(!parser->has_attribute("data_type"), ERR_FILE_CORRUPT);
prop2.data_type = parser->get_named_attribute_value("data_type");
+ if (parser->has_attribute("deprecated")) {
+ prop2.is_deprecated = true;
+ prop2.deprecated_message = parser->get_named_attribute_value("deprecated");
+ }
+ if (parser->has_attribute("experimental")) {
+ prop2.is_experimental = true;
+ prop2.experimental_message = parser->get_named_attribute_value("experimental");
+ }
if (parser->has_attribute("keywords")) {
prop2.keywords = parser->get_named_attribute_value("keywords");
}
@@ -1738,6 +1752,12 @@ Error DocTools::save_classes(const String &p_default_path, const HashMap<String,
if (!ti.default_value.is_empty()) {
additional_attributes += String(" default=\"") + ti.default_value.xml_escape(true) + "\"";
}
+ if (ti.is_deprecated) {
+ additional_attributes += " deprecated=\"" + ti.deprecated_message.xml_escape(true) + "\"";
+ }
+ if (ti.is_experimental) {
+ additional_attributes += " experimental=\"" + ti.experimental_message.xml_escape(true) + "\"";
+ }
if (!ti.keywords.is_empty()) {
additional_attributes += String(" keywords=\"") + ti.keywords.xml_escape(true) + "\"";
}
diff --git a/editor/editor_asset_installer.cpp b/editor/editor_asset_installer.cpp
index b415c72c15..1e44a9bdc9 100644
--- a/editor/editor_asset_installer.cpp
+++ b/editor/editor_asset_installer.cpp
@@ -685,7 +685,7 @@ EditorAssetInstaller::EditorAssetInstaller() {
show_source_files_button->set_toggle_mode(true);
show_source_files_button->set_tooltip_text(TTR("Open the list of the asset contents and select which files to install."));
remapping_tools->add_child(show_source_files_button);
- show_source_files_button->connect("toggled", callable_mp(this, &EditorAssetInstaller::_toggle_source_tree).bind(false));
+ show_source_files_button->connect(SceneStringName(toggled), callable_mp(this, &EditorAssetInstaller::_toggle_source_tree).bind(false));
Button *target_dir_button = memnew(Button);
target_dir_button->set_text(TTR("Change Install Folder"));
@@ -698,7 +698,7 @@ EditorAssetInstaller::EditorAssetInstaller() {
skip_toplevel_check = memnew(CheckBox);
skip_toplevel_check->set_text(TTR("Ignore asset root"));
skip_toplevel_check->set_tooltip_text(TTR("Ignore the root directory when extracting files."));
- skip_toplevel_check->connect("toggled", callable_mp(this, &EditorAssetInstaller::_set_skip_toplevel));
+ skip_toplevel_check->connect(SceneStringName(toggled), callable_mp(this, &EditorAssetInstaller::_set_skip_toplevel));
remapping_tools->add_child(skip_toplevel_check);
remapping_tools->add_spacer();
diff --git a/editor/editor_audio_buses.cpp b/editor/editor_audio_buses.cpp
index 3b337997e0..c076c99cd3 100644
--- a/editor/editor_audio_buses.cpp
+++ b/editor/editor_audio_buses.cpp
@@ -1262,7 +1262,7 @@ void EditorAudioBuses::_load_default_layout() {
file->set_text(String(TTR("Layout:")) + " " + layout_path.get_file());
AudioServer::get_singleton()->set_bus_layout(state);
_rebuild_buses();
- EditorUndoRedoManager::get_singleton()->clear_history(true, EditorUndoRedoManager::GLOBAL_HISTORY);
+ EditorUndoRedoManager::get_singleton()->clear_history(EditorUndoRedoManager::GLOBAL_HISTORY);
callable_mp(this, &EditorAudioBuses::_select_layout).call_deferred();
}
@@ -1278,7 +1278,7 @@ void EditorAudioBuses::_file_dialog_callback(const String &p_string) {
file->set_text(String(TTR("Layout:")) + " " + p_string.get_file());
AudioServer::get_singleton()->set_bus_layout(state);
_rebuild_buses();
- EditorUndoRedoManager::get_singleton()->clear_history(true, EditorUndoRedoManager::GLOBAL_HISTORY);
+ EditorUndoRedoManager::get_singleton()->clear_history(EditorUndoRedoManager::GLOBAL_HISTORY);
callable_mp(this, &EditorAudioBuses::_select_layout).call_deferred();
} else if (file_dialog->get_file_mode() == EditorFileDialog::FILE_MODE_SAVE_FILE) {
@@ -1298,7 +1298,7 @@ void EditorAudioBuses::_file_dialog_callback(const String &p_string) {
edited_path = p_string;
file->set_text(String(TTR("Layout:")) + " " + p_string.get_file());
_rebuild_buses();
- EditorUndoRedoManager::get_singleton()->clear_history(true, EditorUndoRedoManager::GLOBAL_HISTORY);
+ EditorUndoRedoManager::get_singleton()->clear_history(EditorUndoRedoManager::GLOBAL_HISTORY);
callable_mp(this, &EditorAudioBuses::_select_layout).call_deferred();
}
}
@@ -1397,7 +1397,7 @@ void EditorAudioBuses::open_layout(const String &p_path) {
file->set_text(p_path.get_file());
AudioServer::get_singleton()->set_bus_layout(state);
_rebuild_buses();
- EditorUndoRedoManager::get_singleton()->clear_history(true, EditorUndoRedoManager::GLOBAL_HISTORY);
+ EditorUndoRedoManager::get_singleton()->clear_history(EditorUndoRedoManager::GLOBAL_HISTORY);
callable_mp(this, &EditorAudioBuses::_select_layout).call_deferred();
}
diff --git a/editor/editor_build_profile.cpp b/editor/editor_build_profile.cpp
index f55fbe03d8..9c38d55337 100644
--- a/editor/editor_build_profile.cpp
+++ b/editor/editor_build_profile.cpp
@@ -73,7 +73,7 @@ const bool EditorBuildProfile::build_option_disabled_by_default[BUILD_OPTION_MAX
false, // TEXT_SERVER_COMPLEX
false, // DYNAMIC_FONTS
false, // WOFF2_FONTS
- false, // GRPAHITE_FONTS
+ false, // GRAPHITE_FONTS
false, // MSDFGEN
};
@@ -91,7 +91,7 @@ const bool EditorBuildProfile::build_option_disable_values[BUILD_OPTION_MAX] = {
false, // TEXT_SERVER_COMPLEX
false, // DYNAMIC_FONTS
false, // WOFF2_FONTS
- false, // GRPAHITE_FONTS
+ false, // GRAPHITE_FONTS
false, // MSDFGEN
};
@@ -108,7 +108,7 @@ const EditorBuildProfile::BuildOptionCategory EditorBuildProfile::build_option_c
BUILD_OPTION_CATEGORY_TEXT_SERVER, // TEXT_SERVER_COMPLEX
BUILD_OPTION_CATEGORY_TEXT_SERVER, // DYNAMIC_FONTS
BUILD_OPTION_CATEGORY_TEXT_SERVER, // WOFF2_FONTS
- BUILD_OPTION_CATEGORY_TEXT_SERVER, // GRPAHITE_FONTS
+ BUILD_OPTION_CATEGORY_TEXT_SERVER, // GRAPHITE_FONTS
BUILD_OPTION_CATEGORY_TEXT_SERVER, // MSDFGEN
};
@@ -345,7 +345,7 @@ void EditorBuildProfile::_bind_methods() {
BIND_ENUM_CONSTANT(BUILD_OPTION_TEXT_SERVER_ADVANCED);
BIND_ENUM_CONSTANT(BUILD_OPTION_DYNAMIC_FONTS);
BIND_ENUM_CONSTANT(BUILD_OPTION_WOFF2_FONTS);
- BIND_ENUM_CONSTANT(BUILD_OPTION_GRPAHITE_FONTS);
+ BIND_ENUM_CONSTANT(BUILD_OPTION_GRAPHITE_FONTS);
BIND_ENUM_CONSTANT(BUILD_OPTION_MSDFGEN);
BIND_ENUM_CONSTANT(BUILD_OPTION_MAX);
@@ -649,7 +649,7 @@ void EditorBuildProfileManager::_class_list_item_selected() {
}
Variant md = item->get_metadata(0);
- if (md.get_type() == Variant::STRING || md.get_type() == Variant::STRING_NAME) {
+ if (md.is_string()) {
description_bit->parse_symbol("class|" + md.operator String() + "|");
} else if (md.get_type() == Variant::INT) {
String build_option_description = EditorBuildProfile::get_build_option_description(EditorBuildProfile::BuildOption((int)md));
@@ -670,7 +670,7 @@ void EditorBuildProfileManager::_class_list_item_edited() {
bool checked = item->is_checked(0);
Variant md = item->get_metadata(0);
- if (md.get_type() == Variant::STRING || md.get_type() == Variant::STRING_NAME) {
+ if (md.is_string()) {
String class_selected = md;
edited->set_disable_class(class_selected, !checked);
_update_edited_profile();
@@ -691,7 +691,7 @@ void EditorBuildProfileManager::_class_list_item_collapsed(Object *p_item) {
}
Variant md = item->get_metadata(0);
- if (md.get_type() != Variant::STRING && md.get_type() != Variant::STRING_NAME) {
+ if (!md.is_string()) {
return;
}
@@ -706,7 +706,7 @@ void EditorBuildProfileManager::_update_edited_profile() {
if (class_list->get_selected()) {
Variant md = class_list->get_selected()->get_metadata(0);
- if (md.get_type() == Variant::STRING || md.get_type() == Variant::STRING_NAME) {
+ if (md.is_string()) {
class_selected = md;
} else if (md.get_type() == Variant::INT) {
build_option_selected = md;
diff --git a/editor/editor_build_profile.h b/editor/editor_build_profile.h
index a947365c7f..f34ab040d4 100644
--- a/editor/editor_build_profile.h
+++ b/editor/editor_build_profile.h
@@ -57,7 +57,7 @@ public:
BUILD_OPTION_TEXT_SERVER_ADVANCED,
BUILD_OPTION_DYNAMIC_FONTS,
BUILD_OPTION_WOFF2_FONTS,
- BUILD_OPTION_GRPAHITE_FONTS,
+ BUILD_OPTION_GRAPHITE_FONTS,
BUILD_OPTION_MSDFGEN,
BUILD_OPTION_MAX,
};
diff --git a/editor/editor_data.cpp b/editor/editor_data.cpp
index 80c4c49c87..e5caa6a352 100644
--- a/editor/editor_data.cpp
+++ b/editor/editor_data.cpp
@@ -36,11 +36,14 @@
#include "core/io/image_loader.h"
#include "core/io/resource_loader.h"
#include "editor/editor_node.h"
+#include "editor/editor_string_names.h"
#include "editor/editor_undo_redo_manager.h"
#include "editor/multi_node_edit.h"
+#include "editor/plugins/editor_context_menu_plugin.h"
#include "editor/plugins/editor_plugin.h"
#include "editor/plugins/script_editor_plugin.h"
#include "editor/themes/editor_scale.h"
+#include "scene/gui/popup_menu.h"
#include "scene/resources/packed_scene.h"
void EditorSelectionHistory::cleanup_history() {
@@ -519,6 +522,138 @@ EditorPlugin *EditorData::get_extension_editor_plugin(const StringName &p_class_
return plugin == nullptr ? nullptr : *plugin;
}
+void EditorData::add_context_menu_plugin(ContextMenuSlot p_slot, const Ref<EditorContextMenuPlugin> &p_plugin) {
+ ContextMenu cm;
+ cm.p_slot = p_slot;
+ cm.plugin = p_plugin;
+ p_plugin->start_idx = context_menu_plugins.size() * EditorContextMenuPlugin::MAX_ITEMS;
+ context_menu_plugins.push_back(cm);
+}
+
+void EditorData::remove_context_menu_plugin(ContextMenuSlot p_slot, const Ref<EditorContextMenuPlugin> &p_plugin) {
+ for (int i = context_menu_plugins.size() - 1; i > -1; i--) {
+ if (context_menu_plugins[i].p_slot == p_slot && context_menu_plugins[i].plugin == p_plugin) {
+ context_menu_plugins.remove_at(i);
+ }
+ }
+}
+
+int EditorData::match_context_menu_shortcut(ContextMenuSlot p_slot, const Ref<InputEvent> &p_event) {
+ for (ContextMenu &cm : context_menu_plugins) {
+ if (cm.p_slot != p_slot) {
+ continue;
+ }
+ HashMap<Ref<Shortcut>, Callable> &cms = cm.plugin->context_menu_shortcuts;
+ int shortcut_idx = 0;
+ for (KeyValue<Ref<Shortcut>, Callable> &E : cms) {
+ const Ref<Shortcut> &p_shortcut = E.key;
+ if (p_shortcut->matches_event(p_event)) {
+ return EditorData::CONTEXT_MENU_ITEM_ID_BASE + cm.plugin->start_idx + shortcut_idx;
+ }
+ shortcut_idx++;
+ }
+ }
+ return 0;
+}
+
+void EditorData::add_options_from_plugins(PopupMenu *p_popup, ContextMenuSlot p_slot, const Vector<String> &p_paths) {
+ bool add_separator = false;
+
+ for (ContextMenu &cm : context_menu_plugins) {
+ if (cm.p_slot != p_slot) {
+ continue;
+ }
+ cm.plugin->clear_context_menu_items();
+ cm.plugin->add_options(p_paths);
+ HashMap<String, EditorContextMenuPlugin::ContextMenuItem> &items = cm.plugin->context_menu_items;
+ if (items.size() > 0 && !add_separator) {
+ add_separator = true;
+ p_popup->add_separator();
+ }
+ for (KeyValue<String, EditorContextMenuPlugin::ContextMenuItem> &E : items) {
+ EditorContextMenuPlugin::ContextMenuItem &item = E.value;
+
+ if (item.icon.is_valid()) {
+ p_popup->add_icon_item(item.icon, item.item_name, item.idx);
+ const int icon_size = p_popup->get_theme_constant(SNAME("class_icon_size"), EditorStringName(Editor));
+ p_popup->set_item_icon_max_width(-1, icon_size);
+ } else {
+ p_popup->add_item(item.item_name, item.idx);
+ }
+ if (item.shortcut.is_valid()) {
+ p_popup->set_item_shortcut(-1, item.shortcut, true);
+ }
+ }
+ }
+}
+
+template <typename T>
+void EditorData::invoke_plugin_callback(ContextMenuSlot p_slot, int p_option, const T &p_arg) {
+ Variant arg = p_arg;
+ Variant *argptr = &arg;
+
+ for (int i = 0; i < context_menu_plugins.size(); i++) {
+ if (context_menu_plugins[i].p_slot != p_slot || context_menu_plugins[i].plugin.is_null()) {
+ continue;
+ }
+ Ref<EditorContextMenuPlugin> plugin = context_menu_plugins[i].plugin;
+
+ // Shortcut callback.
+ int shortcut_idx = 0;
+ int shortcut_base_idx = EditorData::CONTEXT_MENU_ITEM_ID_BASE + plugin->start_idx;
+ for (KeyValue<Ref<Shortcut>, Callable> &E : plugin->context_menu_shortcuts) {
+ if (shortcut_base_idx + shortcut_idx == p_option) {
+ const Callable &callable = E.value;
+ Callable::CallError ce;
+ Variant result;
+ callable.callp((const Variant **)&argptr, 1, result, ce);
+ }
+ shortcut_idx++;
+ }
+ if (p_option < shortcut_base_idx + shortcut_idx) {
+ return;
+ }
+
+ HashMap<String, EditorContextMenuPlugin::ContextMenuItem> &items = plugin->context_menu_items;
+ for (KeyValue<String, EditorContextMenuPlugin::ContextMenuItem> &E : items) {
+ EditorContextMenuPlugin::ContextMenuItem &item = E.value;
+
+ if (p_option != item.idx || !item.callable.is_valid()) {
+ continue;
+ }
+
+ Callable::CallError ce;
+ Variant result;
+ item.callable.callp((const Variant **)&argptr, 1, result, ce);
+
+ if (ce.error != Callable::CallError::CALL_OK) {
+ String err = Variant::get_callable_error_text(item.callable, nullptr, 0, ce);
+ ERR_PRINT("Error calling function from context menu: " + err);
+ }
+ }
+ }
+ // Invoke submenu items.
+ if (p_slot == CONTEXT_SLOT_FILESYSTEM) {
+ invoke_plugin_callback(CONTEXT_SUBMENU_SLOT_FILESYSTEM_CREATE, p_option, p_arg);
+ }
+}
+
+void EditorData::filesystem_options_pressed(ContextMenuSlot p_slot, int p_option, const Vector<String> &p_selected) {
+ invoke_plugin_callback(p_slot, p_option, p_selected);
+}
+
+void EditorData::scene_tree_options_pressed(ContextMenuSlot p_slot, int p_option, const List<Node *> &p_selected) {
+ TypedArray<Node> nodes;
+ for (Node *selected : p_selected) {
+ nodes.append(selected);
+ }
+ invoke_plugin_callback(p_slot, p_option, nodes);
+}
+
+void EditorData::script_editor_options_pressed(ContextMenuSlot p_slot, int p_option, const Ref<Resource> &p_script) {
+ invoke_plugin_callback(p_slot, p_option, p_script);
+}
+
void EditorData::add_custom_type(const String &p_type, const String &p_inherits, const Ref<Script> &p_script, const Ref<Texture2D> &p_icon) {
ERR_FAIL_COND_MSG(p_script.is_null(), "It's not a reference to a valid Script object.");
CustomType ct;
diff --git a/editor/editor_data.h b/editor/editor_data.h
index 524c93807b..754d44c479 100644
--- a/editor/editor_data.h
+++ b/editor/editor_data.h
@@ -37,6 +37,8 @@
class ConfigFile;
class EditorPlugin;
class EditorUndoRedoManager;
+class EditorContextMenuPlugin;
+class PopupMenu;
/**
* Stores the history of objects which have been selected for editing in the Editor & the Inspector.
@@ -123,6 +125,22 @@ public:
uint64_t last_checked_version = 0;
};
+ enum ContextMenuSlot {
+ CONTEXT_SLOT_SCENE_TREE,
+ CONTEXT_SLOT_FILESYSTEM,
+ CONTEXT_SLOT_SCRIPT_EDITOR,
+ CONTEXT_SUBMENU_SLOT_FILESYSTEM_CREATE,
+ };
+
+ inline static constexpr int CONTEXT_MENU_ITEM_ID_BASE = 1000;
+
+ struct ContextMenu {
+ int p_slot;
+ Ref<EditorContextMenuPlugin> plugin;
+ };
+
+ Vector<ContextMenu> context_menu_plugins;
+
private:
Vector<EditorPlugin *> editor_plugins;
HashMap<StringName, EditorPlugin *> extension_editor_plugins;
@@ -177,6 +195,18 @@ public:
bool has_extension_editor_plugin(const StringName &p_class_name);
EditorPlugin *get_extension_editor_plugin(const StringName &p_class_name);
+ // Context menu plugin.
+ void add_context_menu_plugin(ContextMenuSlot p_slot, const Ref<EditorContextMenuPlugin> &p_plugin);
+ void remove_context_menu_plugin(ContextMenuSlot p_slot, const Ref<EditorContextMenuPlugin> &p_plugin);
+ int match_context_menu_shortcut(ContextMenuSlot p_slot, const Ref<InputEvent> &p_event);
+
+ void add_options_from_plugins(PopupMenu *p_popup, ContextMenuSlot p_slot, const Vector<String> &p_paths);
+ void filesystem_options_pressed(ContextMenuSlot p_slot, int p_option, const Vector<String> &p_selected);
+ void scene_tree_options_pressed(ContextMenuSlot p_slot, int p_option, const List<Node *> &p_selected);
+ void script_editor_options_pressed(ContextMenuSlot p_slot, int p_option, const Ref<Resource> &p_script);
+ template <typename T>
+ void invoke_plugin_callback(ContextMenuSlot p_slot, int p_option, const T &p_arg);
+
void add_undo_redo_inspector_hook_callback(Callable p_callable); // Callbacks should have this signature: void (Object* undo_redo, Object *modified_object, String property, Variant new_value)
void remove_undo_redo_inspector_hook_callback(Callable p_callable);
const Vector<Callable> get_undo_redo_inspector_hook_callback();
diff --git a/editor/editor_feature_profile.cpp b/editor/editor_feature_profile.cpp
index 37cd74d2ac..44fc9e3702 100644
--- a/editor/editor_feature_profile.cpp
+++ b/editor/editor_feature_profile.cpp
@@ -558,7 +558,7 @@ void EditorFeatureProfileManager::_class_list_item_selected() {
}
Variant md = item->get_metadata(0);
- if (md.get_type() == Variant::STRING || md.get_type() == Variant::STRING_NAME) {
+ if (md.is_string()) {
description_bit->parse_symbol("class|" + md.operator String() + "|");
} else if (md.get_type() == Variant::INT) {
String feature_description = EditorFeatureProfile::get_feature_description(EditorFeatureProfile::Feature((int)md));
@@ -643,7 +643,7 @@ void EditorFeatureProfileManager::_class_list_item_edited() {
bool checked = item->is_checked(0);
Variant md = item->get_metadata(0);
- if (md.get_type() == Variant::STRING || md.get_type() == Variant::STRING_NAME) {
+ if (md.is_string()) {
String class_selected = md;
edited->set_disable_class(class_selected, !checked);
_save_and_update();
@@ -666,7 +666,7 @@ void EditorFeatureProfileManager::_class_list_item_collapsed(Object *p_item) {
}
Variant md = item->get_metadata(0);
- if (md.get_type() != Variant::STRING && md.get_type() != Variant::STRING_NAME) {
+ if (!md.is_string()) {
return;
}
@@ -686,7 +686,7 @@ void EditorFeatureProfileManager::_property_item_edited() {
}
Variant md = class_item->get_metadata(0);
- if (md.get_type() != Variant::STRING && md.get_type() != Variant::STRING_NAME) {
+ if (!md.is_string()) {
return;
}
@@ -699,7 +699,7 @@ void EditorFeatureProfileManager::_property_item_edited() {
bool checked = item->is_checked(0);
md = item->get_metadata(0);
- if (md.get_type() == Variant::STRING || md.get_type() == Variant::STRING_NAME) {
+ if (md.is_string()) {
String property_selected = md;
edited->set_disable_class_property(class_name, property_selected, !checked);
_save_and_update();
@@ -732,7 +732,7 @@ void EditorFeatureProfileManager::_update_selected_profile() {
if (class_list->get_selected()) {
Variant md = class_list->get_selected()->get_metadata(0);
- if (md.get_type() == Variant::STRING || md.get_type() == Variant::STRING_NAME) {
+ if (md.is_string()) {
class_selected = md;
} else if (md.get_type() == Variant::INT) {
feature_selected = md;
diff --git a/editor/editor_file_system.cpp b/editor/editor_file_system.cpp
index 22ff7a4509..b1b64b5d60 100644
--- a/editor/editor_file_system.cpp
+++ b/editor/editor_file_system.cpp
@@ -33,8 +33,6 @@
#include "core/config/project_settings.h"
#include "core/extension/gdextension_manager.h"
#include "core/io/file_access.h"
-#include "core/io/resource_importer.h"
-#include "core/io/resource_loader.h"
#include "core/io/resource_saver.h"
#include "core/object/worker_thread_pool.h"
#include "core/os/os.h"
@@ -95,25 +93,35 @@ String EditorFileSystemDirectory::get_file(int p_idx) const {
}
String EditorFileSystemDirectory::get_path() const {
- String p;
- const EditorFileSystemDirectory *d = this;
- while (d->parent) {
- p = d->name.path_join(p);
- d = d->parent;
+ int parents = 0;
+ const EditorFileSystemDirectory *efd = this;
+ // Determine the level of nesting.
+ while (efd->parent) {
+ parents++;
+ efd = efd->parent;
}
- return "res://" + p;
-}
+ if (parents == 0) {
+ return "res://";
+ }
-String EditorFileSystemDirectory::get_file_path(int p_idx) const {
- String file = get_file(p_idx);
- const EditorFileSystemDirectory *d = this;
- while (d->parent) {
- file = d->name.path_join(file);
- d = d->parent;
+ // Using PackedStringArray, because the path is built in reverse order.
+ PackedStringArray path_bits;
+ // Allocate an array based on nesting. It will store path bits.
+ path_bits.resize(parents + 2); // Last String is empty, so paths end with /.
+ String *path_write = path_bits.ptrw();
+ path_write[0] = "res:/";
+
+ efd = this;
+ for (int i = parents; i > 0; i--) {
+ path_write[i] = efd->name;
+ efd = efd->parent;
}
+ return String("/").join(path_bits);
+}
- return "res://" + file;
+String EditorFileSystemDirectory::get_file_path(int p_idx) const {
+ return get_path().path_join(get_file(p_idx));
}
Vector<String> EditorFileSystemDirectory::get_file_deps(int p_idx) const {
@@ -233,18 +241,27 @@ void EditorFileSystem::_first_scan_filesystem() {
first_scan_root_dir = memnew(ScannedDirectory);
first_scan_root_dir->full_path = "res://";
HashSet<String> existing_class_names;
+ HashSet<String> extensions;
ep.step(TTR("Scanning file structure..."), 0, true);
nb_files_total = _scan_new_dir(first_scan_root_dir, d);
// This loads the global class names from the scripts and ensures that even if the
// global_script_class_cache.cfg was missing or invalid, the global class names are valid in ScriptServer.
+ // At the same time, to prevent looping multiple times in all files, it looks for extensions.
ep.step(TTR("Loading global class names..."), 1, true);
- _first_scan_process_scripts(first_scan_root_dir, existing_class_names);
+ _first_scan_process_scripts(first_scan_root_dir, existing_class_names, extensions);
// Removing invalid global class to prevent having invalid paths in ScriptServer.
_remove_invalid_global_class_names(existing_class_names);
+ // Processing extensions to add new extensions or remove invalid ones.
+ // Important to do it in the first scan so custom types, new class names, custom importers, etc...
+ // from extensions are ready to go before plugins, autoloads and resources validation/importation.
+ // At this point, a restart of the editor should not be needed so we don't use the return value.
+ ep.step(TTR("Verifying GDExtensions..."), 2, true);
+ GDExtensionManager::get_singleton()->ensure_extensions_loaded(extensions);
+
// Now that all the global class names should be loaded, create autoloads and plugins.
// This is done after loading the global class names because autoloads and plugins can use
// global class names.
@@ -257,9 +274,9 @@ void EditorFileSystem::_first_scan_filesystem() {
ep.step(TTR("Starting file scan..."), 5, true);
}
-void EditorFileSystem::_first_scan_process_scripts(const ScannedDirectory *p_scan_dir, HashSet<String> &p_existing_class_names) {
+void EditorFileSystem::_first_scan_process_scripts(const ScannedDirectory *p_scan_dir, HashSet<String> &p_existing_class_names, HashSet<String> &p_extensions) {
for (ScannedDirectory *scan_sub_dir : p_scan_dir->subdirs) {
- _first_scan_process_scripts(scan_sub_dir, p_existing_class_names);
+ _first_scan_process_scripts(scan_sub_dir, p_existing_class_names, p_extensions);
}
for (const String &scan_file : p_scan_dir->files) {
@@ -275,6 +292,8 @@ void EditorFileSystem::_first_scan_process_scripts(const ScannedDirectory *p_sca
if (!script_class_name.is_empty()) {
p_existing_class_names.insert(script_class_name);
}
+ } else if (type == SNAME("GDExtension")) {
+ p_extensions.insert(path);
}
}
}
@@ -391,11 +410,13 @@ void EditorFileSystem::_scan_filesystem() {
new_filesystem->parent = nullptr;
ScannedDirectory *sd;
+ HashSet<String> *processed_files = nullptr;
// On the first scan, the first_scan_root_dir is created in _first_scan_filesystem.
if (first_scan) {
sd = first_scan_root_dir;
// Will be updated on scan.
ResourceUID::get_singleton()->clear();
+ processed_files = memnew(HashSet<String>());
} else {
Ref<DirAccess> d = DirAccess::create(DirAccess::ACCESS_RESOURCES);
sd = memnew(ScannedDirectory);
@@ -403,14 +424,18 @@ void EditorFileSystem::_scan_filesystem() {
nb_files_total = _scan_new_dir(sd, d);
}
- _process_file_system(sd, new_filesystem, sp);
+ _process_file_system(sd, new_filesystem, sp, processed_files);
+ if (first_scan) {
+ _process_removed_files(*processed_files);
+ }
dep_update_list.clear();
file_cache.clear(); //clear caches, no longer needed
if (first_scan) {
memdelete(first_scan_root_dir);
first_scan_root_dir = nullptr;
+ memdelete(processed_files);
} else {
//on the first scan this is done from the main thread after re-importing
_save_filesystem_cache();
@@ -882,6 +907,7 @@ void EditorFileSystem::scan() {
// Set first_scan to false before the signals so the function doing_first_scan can return false
// in editor_node to start the export if needed.
first_scan = false;
+ ResourceImporter::load_on_startup = nullptr;
emit_signal(SNAME("filesystem_changed"));
emit_signal(SNAME("sources_changed"), sources_changed.size() > 0);
} else {
@@ -970,7 +996,7 @@ int EditorFileSystem::_scan_new_dir(ScannedDirectory *p_dir, Ref<DirAccess> &da)
return nb_files_total_scan;
}
-void EditorFileSystem::_process_file_system(const ScannedDirectory *p_scan_dir, EditorFileSystemDirectory *p_dir, ScanProgress &p_progress) {
+void EditorFileSystem::_process_file_system(const ScannedDirectory *p_scan_dir, EditorFileSystemDirectory *p_dir, ScanProgress &p_progress, HashSet<String> *r_processed_files) {
p_dir->modified_time = FileAccess::get_modified_time(p_scan_dir->full_path);
for (ScannedDirectory *scan_sub_dir : p_scan_dir->subdirs) {
@@ -978,7 +1004,7 @@ void EditorFileSystem::_process_file_system(const ScannedDirectory *p_scan_dir,
sub_dir->parent = p_dir;
sub_dir->name = scan_sub_dir->name;
p_dir->subdirs.push_back(sub_dir);
- _process_file_system(scan_sub_dir, sub_dir, p_progress);
+ _process_file_system(scan_sub_dir, sub_dir, p_progress, r_processed_files);
}
for (const String &scan_file : p_scan_dir->files) {
@@ -994,6 +1020,10 @@ void EditorFileSystem::_process_file_system(const ScannedDirectory *p_scan_dir,
fi->file = scan_file;
p_dir->files.push_back(fi);
+ if (r_processed_files) {
+ r_processed_files->insert(path);
+ }
+
FileCache *fc = file_cache.getptr(path);
uint64_t mt = FileAccess::get_modified_time(path);
@@ -1045,7 +1075,7 @@ void EditorFileSystem::_process_file_system(const ScannedDirectory *p_scan_dir,
fi->script_class_name = _get_global_script_class(fi->type, path, &fi->script_class_extends, &fi->script_class_icon_path);
fi->modified_time = 0;
fi->import_modified_time = 0;
- fi->import_valid = fi->type == "TextFile" ? true : ResourceLoader::is_import_valid(path);
+ fi->import_valid = (fi->type == "TextFile" || fi->type == "OtherFile") ? true : ResourceLoader::is_import_valid(path);
ItemAction ia;
ia.action = ItemAction::ACTION_FILE_TEST_REIMPORT;
@@ -1088,6 +1118,9 @@ void EditorFileSystem::_process_file_system(const ScannedDirectory *p_scan_dir,
if (fi->type == "" && textfile_extensions.has(ext)) {
fi->type = "TextFile";
}
+ if (fi->type == "" && other_file_extensions.has(ext)) {
+ fi->type = "OtherFile";
+ }
fi->uid = ResourceLoader::get_resource_uid(path);
fi->script_class_name = _get_global_script_class(fi->type, path, &fi->script_class_extends, &fi->script_class_icon_path);
fi->deps = _get_dependencies(path);
@@ -1119,6 +1152,20 @@ void EditorFileSystem::_process_file_system(const ScannedDirectory *p_scan_dir,
}
}
+void EditorFileSystem::_process_removed_files(const HashSet<String> &p_processed_files) {
+ for (const KeyValue<String, EditorFileSystem::FileCache> &kv : file_cache) {
+ if (!p_processed_files.has(kv.key)) {
+ if (ClassDB::is_parent_class(kv.value.type, SNAME("Script"))) {
+ // A script has been removed from disk since the last startup. The documentation needs to be updated.
+ // There's no need to add the path in update_script_paths since that is exclusively for updating global class names,
+ // which is handled in _first_scan_filesystem before the full scan to ensure plugins and autoloads can be created.
+ MutexLock update_script_lock(update_script_mutex);
+ update_script_paths_documentation.insert(kv.key);
+ }
+ }
+ }
+}
+
void EditorFileSystem::_scan_fs_changes(EditorFileSystemDirectory *p_dir, ScanProgress &p_progress) {
uint64_t current_mtime = FileAccess::get_modified_time(p_dir->get_path());
@@ -1186,7 +1233,7 @@ void EditorFileSystem::_scan_fs_changes(EditorFileSystemDirectory *p_dir, ScanPr
int nb_files_dir = _scan_new_dir(&sd, d);
p_progress.hi += nb_files_dir;
diff_nb_files += nb_files_dir;
- _process_file_system(&sd, efd, p_progress);
+ _process_file_system(&sd, efd, p_progress, nullptr);
ItemAction ia;
ia.action = ItemAction::ACTION_DIR_ADD;
@@ -1219,8 +1266,11 @@ void EditorFileSystem::_scan_fs_changes(EditorFileSystemDirectory *p_dir, ScanPr
if (fi->type == "" && textfile_extensions.has(ext)) {
fi->type = "TextFile";
}
+ if (fi->type == "" && other_file_extensions.has(ext)) {
+ fi->type = "OtherFile";
+ }
fi->script_class_name = _get_global_script_class(fi->type, path, &fi->script_class_extends, &fi->script_class_icon_path);
- fi->import_valid = fi->type == "TextFile" ? true : ResourceLoader::is_import_valid(path);
+ fi->import_valid = (fi->type == "TextFile" || fi->type == "OtherFile") ? true : ResourceLoader::is_import_valid(path);
fi->import_group_file = ResourceLoader::get_import_group_file(path);
{
@@ -1492,6 +1542,7 @@ void EditorFileSystem::_notification(int p_what) {
// Set first_scan to false before the signals so the function doing_first_scan can return false
// in editor_node to start the export if needed.
first_scan = false;
+ ResourceImporter::load_on_startup = nullptr;
if (changed) {
emit_signal(SNAME("filesystem_changed"));
}
@@ -1514,6 +1565,7 @@ void EditorFileSystem::_notification(int p_what) {
// Set first_scan to false before the signals so the function doing_first_scan can return false
// in editor_node to start the export if needed.
first_scan = false;
+ ResourceImporter::load_on_startup = nullptr;
emit_signal(SNAME("filesystem_changed"));
emit_signal(SNAME("sources_changed"), sources_changed.size() > 0);
}
@@ -1845,25 +1897,31 @@ void EditorFileSystem::_update_script_classes() {
return;
}
- update_script_mutex.lock();
+ {
+ MutexLock update_script_lock(update_script_mutex);
- EditorProgress *ep = nullptr;
- if (update_script_paths.size() > 1) {
- ep = memnew(EditorProgress("update_scripts_classes", TTR("Registering global classes..."), update_script_paths.size()));
- }
+ EditorProgress *ep = nullptr;
+ if (update_script_paths.size() > 1) {
+ if (MessageQueue::get_singleton()->is_flushing()) {
+ // Use background progress when message queue is flushing.
+ ep = memnew(EditorProgress("update_scripts_classes", TTR("Registering global classes..."), update_script_paths.size(), false, true));
+ } else {
+ ep = memnew(EditorProgress("update_scripts_classes", TTR("Registering global classes..."), update_script_paths.size()));
+ }
+ }
- int step_count = 0;
- for (const KeyValue<String, ScriptInfo> &E : update_script_paths) {
- _register_global_class_script(E.key, E.key, E.value.type, E.value.script_class_name, E.value.script_class_extends, E.value.script_class_icon_path);
- if (ep) {
- ep->step(E.value.script_class_name, step_count++, false);
+ int step_count = 0;
+ for (const KeyValue<String, ScriptInfo> &E : update_script_paths) {
+ _register_global_class_script(E.key, E.key, E.value.type, E.value.script_class_name, E.value.script_class_extends, E.value.script_class_icon_path);
+ if (ep) {
+ ep->step(E.value.script_class_name, step_count++, false);
+ }
}
- }
- memdelete_notnull(ep);
+ memdelete_notnull(ep);
- update_script_paths.clear();
- update_script_mutex.unlock();
+ update_script_paths.clear();
+ }
ScriptServer::save_global_classes();
EditorNode::get_editor_data().script_class_save_icon_paths();
@@ -1884,11 +1942,16 @@ void EditorFileSystem::_update_script_documentation() {
return;
}
- update_script_mutex.lock();
+ MutexLock update_script_lock(update_script_mutex);
EditorProgress *ep = nullptr;
if (update_script_paths_documentation.size() > 1) {
- ep = memnew(EditorProgress("update_script_paths_documentation", TTR("Updating scripts documentation"), update_script_paths_documentation.size()));
+ if (MessageQueue::get_singleton()->is_flushing()) {
+ // Use background progress when message queue is flushing.
+ ep = memnew(EditorProgress("update_script_paths_documentation", TTR("Updating scripts documentation"), update_script_paths_documentation.size(), false, true));
+ } else {
+ ep = memnew(EditorProgress("update_script_paths_documentation", TTR("Updating scripts documentation"), update_script_paths_documentation.size()));
+ }
}
int step_count = 0;
@@ -1923,7 +1986,6 @@ void EditorFileSystem::_update_script_documentation() {
memdelete_notnull(ep);
update_script_paths_documentation.clear();
- update_script_mutex.unlock();
}
void EditorFileSystem::_process_update_pending() {
@@ -1935,7 +1997,7 @@ void EditorFileSystem::_process_update_pending() {
}
void EditorFileSystem::_queue_update_script_class(const String &p_path, const String &p_type, const String &p_script_class_name, const String &p_script_class_extends, const String &p_script_class_icon_path) {
- update_script_mutex.lock();
+ MutexLock update_script_lock(update_script_mutex);
ScriptInfo si;
si.type = p_type;
@@ -1945,8 +2007,6 @@ void EditorFileSystem::_queue_update_script_class(const String &p_path, const St
update_script_paths.insert(p_path, si);
update_script_paths_documentation.insert(p_path);
-
- update_script_mutex.unlock();
}
void EditorFileSystem::_update_scene_groups() {
@@ -1960,31 +2020,32 @@ void EditorFileSystem::_update_scene_groups() {
}
int step_count = 0;
- update_scene_mutex.lock();
- for (const String &path : update_scene_paths) {
- ProjectSettings::get_singleton()->remove_scene_groups_cache(path);
+ {
+ MutexLock update_scene_lock(update_scene_mutex);
+ for (const String &path : update_scene_paths) {
+ ProjectSettings::get_singleton()->remove_scene_groups_cache(path);
- int index = -1;
- EditorFileSystemDirectory *efd = find_file(path, &index);
+ int index = -1;
+ EditorFileSystemDirectory *efd = find_file(path, &index);
- if (!efd || index < 0) {
- // The file was removed.
- continue;
- }
+ if (!efd || index < 0) {
+ // The file was removed.
+ continue;
+ }
- const HashSet<StringName> scene_groups = PackedScene::get_scene_groups(path);
- if (!scene_groups.is_empty()) {
- ProjectSettings::get_singleton()->add_scene_groups_cache(path, scene_groups);
- }
+ const HashSet<StringName> scene_groups = PackedScene::get_scene_groups(path);
+ if (!scene_groups.is_empty()) {
+ ProjectSettings::get_singleton()->add_scene_groups_cache(path, scene_groups);
+ }
- if (ep) {
- ep->step(efd->files[index]->file, step_count++);
+ if (ep) {
+ ep->step(efd->files[index]->file, step_count++, false);
+ }
}
- }
- memdelete_notnull(ep);
- update_scene_paths.clear();
- update_scene_mutex.unlock();
+ memdelete_notnull(ep);
+ update_scene_paths.clear();
+ }
ProjectSettings::get_singleton()->save_scene_groups_cache();
}
@@ -1999,9 +2060,8 @@ void EditorFileSystem::_update_pending_scene_groups() {
}
void EditorFileSystem::_queue_update_scene_groups(const String &p_path) {
- update_scene_mutex.lock();
+ MutexLock update_scene_lock(update_scene_mutex);
update_scene_paths.insert(p_path);
- update_scene_mutex.unlock();
}
void EditorFileSystem::_get_all_scenes(EditorFileSystemDirectory *p_dir, HashSet<String> &r_list) {
@@ -2064,6 +2124,9 @@ void EditorFileSystem::update_files(const Vector<String> &p_script_paths) {
if (type.is_empty() && textfile_extensions.has(file.get_extension())) {
type = "TextFile";
}
+ if (type.is_empty() && other_file_extensions.has(file.get_extension())) {
+ type = "OtherFile";
+ }
String script_class = ResourceLoader::get_resource_script_class(file);
ResourceUID::ID uid = ResourceLoader::get_resource_uid(file);
@@ -2083,7 +2146,7 @@ void EditorFileSystem::update_files(const Vector<String> &p_script_paths) {
EditorFileSystemDirectory::FileInfo *fi = memnew(EditorFileSystemDirectory::FileInfo);
fi->file = file_name;
fi->import_modified_time = 0;
- fi->import_valid = type == "TextFile" ? true : ResourceLoader::is_import_valid(file);
+ fi->import_valid = (type == "TextFile" || type == "OtherFile") ? true : ResourceLoader::is_import_valid(file);
if (idx == fs->files.size()) {
fs->files.push_back(fi);
@@ -2107,7 +2170,7 @@ void EditorFileSystem::update_files(const Vector<String> &p_script_paths) {
fs->files[cpos]->import_group_file = ResourceLoader::get_import_group_file(file);
fs->files[cpos]->modified_time = FileAccess::get_modified_time(file);
fs->files[cpos]->deps = _get_dependencies(file);
- fs->files[cpos]->import_valid = type == "TextFile" ? true : ResourceLoader::is_import_valid(file);
+ fs->files[cpos]->import_valid = (type == "TextFile" || type == "OtherFile") ? true : ResourceLoader::is_import_valid(file);
if (uid != ResourceUID::INVALID_ID) {
if (ResourceUID::get_singleton()->has_id(uid)) {
@@ -2365,6 +2428,9 @@ Error EditorFileSystem::_reimport_group(const String &p_group_file, const Vector
if (fs->files[cpos]->type == "" && textfile_extensions.has(file.get_extension())) {
fs->files[cpos]->type = "TextFile";
}
+ if (fs->files[cpos]->type == "" && other_file_extensions.has(file.get_extension())) {
+ fs->files[cpos]->type = "OtherFile";
+ }
fs->files[cpos]->import_valid = err == OK;
if (ResourceUID::get_singleton()->has_id(uid)) {
@@ -2391,14 +2457,16 @@ Error EditorFileSystem::_reimport_group(const String &p_group_file, const Vector
return err;
}
-Error EditorFileSystem::_reimport_file(const String &p_file, const HashMap<StringName, Variant> &p_custom_options, const String &p_custom_importer, Variant *p_generator_parameters) {
+Error EditorFileSystem::_reimport_file(const String &p_file, const HashMap<StringName, Variant> &p_custom_options, const String &p_custom_importer, Variant *p_generator_parameters, bool p_update_file_system) {
print_verbose(vformat("EditorFileSystem: Importing file: %s", p_file));
uint64_t start_time = OS::get_singleton()->get_ticks_msec();
EditorFileSystemDirectory *fs = nullptr;
int cpos = -1;
- bool found = _find_file(p_file, &fs, cpos);
- ERR_FAIL_COND_V_MSG(!found, ERR_FILE_NOT_FOUND, "Can't find file '" + p_file + "'.");
+ if (p_update_file_system) {
+ bool found = _find_file(p_file, &fs, cpos);
+ ERR_FAIL_COND_V_MSG(!found, ERR_FILE_NOT_FOUND, "Can't find file '" + p_file + "'.");
+ }
//try to obtain existing params
@@ -2452,11 +2520,13 @@ Error EditorFileSystem::_reimport_file(const String &p_file, const HashMap<Strin
if (importer_name == "keep" || importer_name == "skip") {
//keep files, do nothing.
- fs->files[cpos]->modified_time = FileAccess::get_modified_time(p_file);
- fs->files[cpos]->import_modified_time = FileAccess::get_modified_time(p_file + ".import");
- fs->files[cpos]->deps.clear();
- fs->files[cpos]->type = "";
- fs->files[cpos]->import_valid = false;
+ if (p_update_file_system) {
+ fs->files[cpos]->modified_time = FileAccess::get_modified_time(p_file);
+ fs->files[cpos]->import_modified_time = FileAccess::get_modified_time(p_file + ".import");
+ fs->files[cpos]->deps.clear();
+ fs->files[cpos]->type = "";
+ fs->files[cpos]->import_valid = false;
+ }
EditorResourcePreview::get_singleton()->check_for_invalidation(p_file);
return OK;
}
@@ -2616,16 +2686,18 @@ Error EditorFileSystem::_reimport_file(const String &p_file, const HashMap<Strin
}
}
- // Update cpos, newly created files could've changed the index of the reimported p_file.
- _find_file(p_file, &fs, cpos);
+ if (p_update_file_system) {
+ // Update cpos, newly created files could've changed the index of the reimported p_file.
+ _find_file(p_file, &fs, cpos);
- // Update modified times, to avoid reimport.
- fs->files[cpos]->modified_time = FileAccess::get_modified_time(p_file);
- fs->files[cpos]->import_modified_time = FileAccess::get_modified_time(p_file + ".import");
- fs->files[cpos]->deps = _get_dependencies(p_file);
- fs->files[cpos]->type = importer->get_resource_type();
- fs->files[cpos]->uid = uid;
- fs->files[cpos]->import_valid = fs->files[cpos]->type == "TextFile" ? true : ResourceLoader::is_import_valid(p_file);
+ // Update modified times, to avoid reimport.
+ fs->files[cpos]->modified_time = FileAccess::get_modified_time(p_file);
+ fs->files[cpos]->import_modified_time = FileAccess::get_modified_time(p_file + ".import");
+ fs->files[cpos]->deps = _get_dependencies(p_file);
+ fs->files[cpos]->type = importer->get_resource_type();
+ fs->files[cpos]->uid = uid;
+ fs->files[cpos]->import_valid = fs->files[cpos]->type == "TextFile" ? true : ResourceLoader::is_import_valid(p_file);
+ }
if (ResourceUID::get_singleton()->has_id(uid)) {
ResourceUID::get_singleton()->set_id(uid, p_file);
@@ -2696,6 +2768,16 @@ void EditorFileSystem::reimport_files(const Vector<String> &p_files) {
EditorProgress *ep = memnew(EditorProgress("reimport", TTR("(Re)Importing Assets"), p_files.size()));
+ // The method reimport_files runs on the main thread, and if VSync is enabled
+ // or Update Continuously is disabled, Main::Iteration takes longer each frame.
+ // Each EditorProgress::step can trigger a redraw, and when there are many files to import,
+ // this could lead to a slow import process, especially when the editor is unfocused.
+ // Temporarily disabling VSync and low_processor_usage_mode while reimporting fixes this.
+ const bool old_low_processor_usage_mode = OS::get_singleton()->is_in_low_processor_usage_mode();
+ const DisplayServer::VSyncMode old_vsync_mode = DisplayServer::get_singleton()->window_get_vsync_mode(DisplayServer::MAIN_WINDOW_ID);
+ OS::get_singleton()->set_low_processor_usage_mode(false);
+ DisplayServer::get_singleton()->window_set_vsync_mode(DisplayServer::VSyncMode::VSYNC_DISABLED);
+
Vector<ImportFile> reimport_files;
HashSet<String> groups_to_reimport;
@@ -2826,6 +2908,7 @@ void EditorFileSystem::reimport_files(const Vector<String> &p_files) {
}
}
}
+ ep->step(TTR("Finalizing Asset Import..."), p_files.size());
ResourceUID::get_singleton()->update_cache(); // After reimporting, update the cache.
_save_filesystem_cache();
@@ -2833,6 +2916,11 @@ void EditorFileSystem::reimport_files(const Vector<String> &p_files) {
memdelete_notnull(ep);
_process_update_pending();
+
+ // Revert to previous values to restore editor settings for VSync and Update Continuously.
+ OS::get_singleton()->set_low_processor_usage_mode(old_low_processor_usage_mode);
+ DisplayServer::get_singleton()->window_set_vsync_mode(old_vsync_mode);
+
importing = false;
ep = memnew(EditorProgress("reimport", TTR("(Re)Importing Assets"), p_files.size()));
@@ -2869,6 +2957,33 @@ Error EditorFileSystem::_resource_import(const String &p_path) {
return OK;
}
+Ref<Resource> EditorFileSystem::_load_resource_on_startup(ResourceFormatImporter *p_importer, const String &p_path, Error *r_error, bool p_use_sub_threads, float *r_progress, ResourceFormatLoader::CacheMode p_cache_mode) {
+ ERR_FAIL_NULL_V(p_importer, Ref<Resource>());
+
+ if (!FileAccess::exists(p_path)) {
+ ERR_FAIL_V_MSG(Ref<Resource>(), vformat("Failed loading resource: %s. The file doesn't seem to exist.", p_path));
+ }
+
+ Ref<Resource> res;
+ bool can_retry = true;
+ bool retry = true;
+ while (retry) {
+ retry = false;
+
+ res = p_importer->load_internal(p_path, r_error, p_use_sub_threads, r_progress, p_cache_mode, can_retry);
+
+ if (res.is_null() && can_retry) {
+ can_retry = false;
+ Error err = singleton->_reimport_file(p_path, HashMap<StringName, Variant>(), "", nullptr, false);
+ if (err == OK) {
+ retry = true;
+ }
+ }
+ }
+
+ return res;
+}
+
bool EditorFileSystem::_should_skip_directory(const String &p_path) {
String project_data_path = ProjectSettings::get_singleton()->get_project_data_path();
if (p_path == project_data_path || p_path.begins_with(project_data_path + "/")) {
@@ -2990,57 +3105,7 @@ bool EditorFileSystem::_scan_extensions() {
_scan_extensions_dir(d, extensions);
- //verify against loaded extensions
-
- Vector<String> extensions_added;
- Vector<String> extensions_removed;
-
- for (const String &E : extensions) {
- if (!GDExtensionManager::get_singleton()->is_extension_loaded(E)) {
- extensions_added.push_back(E);
- }
- }
-
- Vector<String> loaded_extensions = GDExtensionManager::get_singleton()->get_loaded_extensions();
- for (int i = 0; i < loaded_extensions.size(); i++) {
- if (!extensions.has(loaded_extensions[i])) {
- // The extension may not have a .gdextension file.
- if (!FileAccess::exists(loaded_extensions[i])) {
- extensions_removed.push_back(loaded_extensions[i]);
- }
- }
- }
-
- String extension_list_config_file = GDExtension::get_extension_list_config_file();
- if (extensions.size()) {
- if (extensions_added.size() || extensions_removed.size()) { //extensions were added or removed
- Ref<FileAccess> f = FileAccess::open(extension_list_config_file, FileAccess::WRITE);
- for (const String &E : extensions) {
- f->store_line(E);
- }
- }
- } else {
- if (loaded_extensions.size() || FileAccess::exists(extension_list_config_file)) { //extensions were removed
- Ref<DirAccess> da = DirAccess::create(DirAccess::ACCESS_RESOURCES);
- da->remove(extension_list_config_file);
- }
- }
-
- bool needs_restart = false;
- for (int i = 0; i < extensions_added.size(); i++) {
- GDExtensionManager::LoadStatus st = GDExtensionManager::get_singleton()->load_extension(extensions_added[i]);
- if (st == GDExtensionManager::LOAD_STATUS_NEEDS_RESTART) {
- needs_restart = true;
- }
- }
- for (int i = 0; i < extensions_removed.size(); i++) {
- GDExtensionManager::LoadStatus st = GDExtensionManager::get_singleton()->unload_extension(extensions_removed[i]);
- if (st == GDExtensionManager::LOAD_STATUS_NEEDS_RESTART) {
- needs_restart = true;
- }
- }
-
- return needs_restart;
+ return GDExtensionManager::get_singleton()->ensure_extensions_loaded(extensions);
}
void EditorFileSystem::_bind_methods() {
@@ -3066,6 +3131,7 @@ void EditorFileSystem::_update_extensions() {
valid_extensions.clear();
import_extensions.clear();
textfile_extensions.clear();
+ other_file_extensions.clear();
List<String> extensionsl;
ResourceLoader::get_recognized_extensions_for_type("", &extensionsl);
@@ -3081,6 +3147,14 @@ void EditorFileSystem::_update_extensions() {
valid_extensions.insert(E);
textfile_extensions.insert(E);
}
+ const Vector<String> other_file_ext = ((String)(EDITOR_GET("docks/filesystem/other_file_extensions"))).split(",", false);
+ for (const String &E : other_file_ext) {
+ if (valid_extensions.has(E)) {
+ continue;
+ }
+ valid_extensions.insert(E);
+ other_file_extensions.insert(E);
+ }
extensionsl.clear();
ResourceFormatImporter::get_singleton()->get_recognized_extensions(&extensionsl);
@@ -3116,6 +3190,10 @@ EditorFileSystem::EditorFileSystem() {
scan_total = 0;
ResourceSaver::set_get_resource_id_for_path(_resource_saver_get_resource_id_for_path);
+
+ // Set the callback method that the ResourceFormatImporter will use
+ // if resources are loaded during the first scan.
+ ResourceImporter::load_on_startup = _load_resource_on_startup;
}
EditorFileSystem::~EditorFileSystem() {
diff --git a/editor/editor_file_system.h b/editor/editor_file_system.h
index 1bc24416eb..9adab1ed24 100644
--- a/editor/editor_file_system.h
+++ b/editor/editor_file_system.h
@@ -32,6 +32,8 @@
#define EDITOR_FILE_SYSTEM_H
#include "core/io/dir_access.h"
+#include "core/io/resource_importer.h"
+#include "core/io/resource_loader.h"
#include "core/os/thread.h"
#include "core/os/thread_safe.h"
#include "core/templates/hash_set.h"
@@ -187,7 +189,7 @@ class EditorFileSystem : public Node {
void _scan_filesystem();
void _first_scan_filesystem();
- void _first_scan_process_scripts(const ScannedDirectory *p_scan_dir, HashSet<String> &p_existing_class_names);
+ void _first_scan_process_scripts(const ScannedDirectory *p_scan_dir, HashSet<String> &p_existing_class_names, HashSet<String> &p_extensions);
HashSet<String> late_update_files;
@@ -233,11 +235,12 @@ class EditorFileSystem : public Node {
int _insert_actions_delete_files_directory(EditorFileSystemDirectory *p_dir);
HashSet<String> textfile_extensions;
+ HashSet<String> other_file_extensions;
HashSet<String> valid_extensions;
HashSet<String> import_extensions;
int _scan_new_dir(ScannedDirectory *p_dir, Ref<DirAccess> &da);
- void _process_file_system(const ScannedDirectory *p_scan_dir, EditorFileSystemDirectory *p_dir, ScanProgress &p_progress);
+ void _process_file_system(const ScannedDirectory *p_scan_dir, EditorFileSystemDirectory *p_dir, ScanProgress &p_progress, HashSet<String> *p_processed_files);
Thread thread_sources;
bool scanning_changes = false;
@@ -252,7 +255,7 @@ class EditorFileSystem : public Node {
void _update_extensions();
- Error _reimport_file(const String &p_file, const HashMap<StringName, Variant> &p_custom_options = HashMap<StringName, Variant>(), const String &p_custom_importer = String(), Variant *generator_parameters = nullptr);
+ Error _reimport_file(const String &p_file, const HashMap<StringName, Variant> &p_custom_options = HashMap<StringName, Variant>(), const String &p_custom_importer = String(), Variant *generator_parameters = nullptr, bool p_update_file_system = true);
Error _reimport_group(const String &p_group_file, const Vector<String> &p_files);
bool _test_for_reimport(const String &p_path, bool p_only_imported_files);
@@ -285,6 +288,7 @@ class EditorFileSystem : public Node {
void _update_script_classes();
void _update_script_documentation();
void _process_update_pending();
+ void _process_removed_files(const HashSet<String> &p_processed_files);
Mutex update_scene_mutex;
HashSet<String> update_scene_paths;
@@ -296,6 +300,7 @@ class EditorFileSystem : public Node {
String _get_global_script_class(const String &p_type, const String &p_path, String *r_extends, String *r_icon_path) const;
static Error _resource_import(const String &p_path);
+ static Ref<Resource> _load_resource_on_startup(ResourceFormatImporter *p_importer, const String &p_path, Error *r_error, bool p_use_sub_threads, float *r_progress, ResourceFormatLoader::CacheMode p_cache_mode);
bool using_fat32_or_exfat; // Workaround for projects in FAT32 or exFAT filesystem (pendrives, most of the time)
diff --git a/editor/editor_help.cpp b/editor/editor_help.cpp
index 9b0c05d910..fe758bf99b 100644
--- a/editor/editor_help.cpp
+++ b/editor/editor_help.cpp
@@ -376,10 +376,10 @@ static void _add_type_to_rt(const String &p_type, const String &p_enum, bool p_i
}
p_rt->push_color(type_color);
- bool add_array = false;
+ bool add_typed_container = false;
if (can_ref) {
if (link_t.ends_with("[]")) {
- add_array = true;
+ add_typed_container = true;
link_t = link_t.trim_suffix("[]");
display_t = display_t.trim_suffix("[]");
@@ -387,6 +387,22 @@ static void _add_type_to_rt(const String &p_type, const String &p_enum, bool p_i
p_rt->add_text("Array");
p_rt->pop(); // meta
p_rt->add_text("[");
+ } else if (link_t.begins_with("Dictionary[")) {
+ add_typed_container = true;
+ link_t = link_t.trim_prefix("Dictionary[").trim_suffix("]");
+ display_t = display_t.trim_prefix("Dictionary[").trim_suffix("]");
+
+ p_rt->push_meta("#Dictionary", RichTextLabel::META_UNDERLINE_ON_HOVER); // class
+ p_rt->add_text("Dictionary");
+ p_rt->pop(); // meta
+ p_rt->add_text("[");
+ p_rt->push_meta("#" + link_t.get_slice(", ", 0), RichTextLabel::META_UNDERLINE_ON_HOVER); // class
+ p_rt->add_text(_contextualize_class_specifier(display_t.get_slice(", ", 0), p_class));
+ p_rt->pop(); // meta
+ p_rt->add_text(", ");
+
+ link_t = link_t.get_slice(", ", 1);
+ display_t = _contextualize_class_specifier(display_t.get_slice(", ", 1), p_class);
} else if (is_bitfield) {
p_rt->push_color(Color(type_color, 0.5));
p_rt->push_hint(TTR("This value is an integer composed as a bitmask of the following flags."));
@@ -405,7 +421,7 @@ static void _add_type_to_rt(const String &p_type, const String &p_enum, bool p_i
p_rt->add_text(display_t);
if (can_ref) {
p_rt->pop(); // meta
- if (add_array) {
+ if (add_typed_container) {
p_rt->add_text("]");
} else if (is_bitfield) {
p_rt->push_color(Color(type_color, 0.5));
@@ -1456,10 +1472,31 @@ void EditorHelp::_update_doc() {
_push_normal_font();
class_desc->push_color(theme_cache.comment_color);
+ bool has_prev_text = false;
+
+ if (theme_item.is_deprecated) {
+ has_prev_text = true;
+ DEPRECATED_DOC_MSG(HANDLE_DOC(theme_item.deprecated_message), TTR("This theme property may be changed or removed in future versions."));
+ }
+
+ if (theme_item.is_experimental) {
+ if (has_prev_text) {
+ class_desc->add_newline();
+ class_desc->add_newline();
+ }
+ has_prev_text = true;
+ EXPERIMENTAL_DOC_MSG(HANDLE_DOC(theme_item.experimental_message), TTR("This theme property may be changed or removed in future versions."));
+ }
+
const String descr = HANDLE_DOC(theme_item.description);
if (!descr.is_empty()) {
+ if (has_prev_text) {
+ class_desc->add_newline();
+ class_desc->add_newline();
+ }
+ has_prev_text = true;
_add_text(descr);
- } else {
+ } else if (!has_prev_text) {
class_desc->add_image(get_editor_theme_icon(SNAME("Error")));
class_desc->add_text(" ");
class_desc->push_color(theme_cache.comment_color);
@@ -2220,12 +2257,6 @@ void EditorHelp::_update_doc() {
}
has_prev_text = true;
_add_text(descr);
- // Add copy note to built-in properties returning Packed*Array.
- if (!cd.is_script_doc && packed_array_types.has(prop.type)) {
- class_desc->add_newline();
- class_desc->add_newline();
- _add_text(vformat(TTR("[b]Note:[/b] The returned array is [i]copied[/i] and any changes to it will not update the original property value. See [%s] for more details."), prop.type));
- }
} else if (!has_prev_text) {
class_desc->add_image(get_editor_theme_icon(SNAME("Error")));
class_desc->add_text(" ");
@@ -2238,6 +2269,13 @@ void EditorHelp::_update_doc() {
class_desc->pop(); // color
}
+ // Add copy note to built-in properties returning `Packed*Array`.
+ if (!cd.is_script_doc && packed_array_types.has(prop.type)) {
+ class_desc->add_newline();
+ class_desc->add_newline();
+ _add_text(vformat(TTR("[b]Note:[/b] The returned array is [i]copied[/i] and any changes to it will not update the original property value. See [%s] for more details."), prop.type));
+ }
+
class_desc->pop(); // color
_pop_normal_font();
class_desc->pop(); // indent
@@ -3380,6 +3418,20 @@ EditorHelpBit::HelpData EditorHelpBit::_get_theme_item_help_data(const StringNam
for (const DocData::ThemeItemDoc &theme_item : E->value.theme_properties) {
HelpData current;
current.description = HANDLE_DOC(theme_item.description);
+ if (theme_item.is_deprecated) {
+ if (theme_item.deprecated_message.is_empty()) {
+ current.deprecated_message = TTR("This theme property may be changed or removed in future versions.");
+ } else {
+ current.deprecated_message = HANDLE_DOC(theme_item.deprecated_message);
+ }
+ }
+ if (theme_item.is_experimental) {
+ if (theme_item.experimental_message.is_empty()) {
+ current.experimental_message = TTR("This theme property may be changed or removed in future versions.");
+ } else {
+ current.experimental_message = HANDLE_DOC(theme_item.experimental_message);
+ }
+ }
if (theme_item.name == p_theme_item_name) {
result = current;
diff --git a/editor/editor_help_search.cpp b/editor/editor_help_search.cpp
index ff5bc6ba87..987c7f9c31 100644
--- a/editor/editor_help_search.cpp
+++ b/editor/editor_help_search.cpp
@@ -134,18 +134,38 @@ void EditorHelpSearch::_native_action_cb(const String &p_item_string) {
}
void EditorHelpSearch::_update_results() {
- String term = search_box->get_text();
+ const String term = search_box->get_text().strip_edges();
int search_flags = filter_combo->get_selected_id();
- if (case_sensitive_button->is_pressed()) {
- search_flags |= SEARCH_CASE_SENSITIVE;
- }
- if (hierarchy_button->is_pressed()) {
- search_flags |= SEARCH_SHOW_HIERARCHY;
- }
- search = Ref<Runner>(memnew(Runner(results_tree, results_tree, &tree_cache, term, search_flags)));
- set_process(true);
+ // Process separately if term is not short, or is "@" for annotations.
+ if (term.length() > 1 || term == "@") {
+ case_sensitive_button->set_disabled(false);
+ hierarchy_button->set_disabled(false);
+
+ if (case_sensitive_button->is_pressed()) {
+ search_flags |= SEARCH_CASE_SENSITIVE;
+ }
+ if (hierarchy_button->is_pressed()) {
+ search_flags |= SEARCH_SHOW_HIERARCHY;
+ }
+
+ search = Ref<Runner>(memnew(Runner(results_tree, results_tree, &tree_cache, term, search_flags)));
+
+ // Clear old search flags to force rebuild on short term.
+ old_search_flags = 0;
+ set_process(true);
+ } else {
+ // Disable hierarchy and case sensitive options, not used for short searches.
+ case_sensitive_button->set_disabled(true);
+ hierarchy_button->set_disabled(true);
+
+ // Always show hierarchy for short searches.
+ search = Ref<Runner>(memnew(Runner(results_tree, results_tree, &tree_cache, term, search_flags | SEARCH_SHOW_HIERARCHY)));
+
+ old_search_flags = search_flags;
+ set_process(true);
+ }
}
void EditorHelpSearch::_search_box_gui_input(const Ref<InputEvent> &p_event) {
@@ -205,6 +225,8 @@ void EditorHelpSearch::_notification(int p_what) {
case NOTIFICATION_VISIBILITY_CHANGED: {
if (!is_visible()) {
tree_cache.clear();
+ results_tree->get_vscroll_bar()->set_value(0);
+ search = Ref<Runner>();
callable_mp(results_tree, &Tree::clear).call_deferred(); // Wait for the Tree's mouse event propagation.
get_ok_button()->set_disabled(true);
EditorSettings::get_singleton()->set_project_metadata("dialog_bounds", "search_help", Rect2(get_position(), get_size()));
@@ -278,6 +300,7 @@ void EditorHelpSearch::popup_dialog(const String &p_term) {
popup_centered_ratio(0.5F);
}
+ old_search_flags = 0;
if (p_term.is_empty()) {
search_box->clear();
} else {
@@ -401,6 +424,237 @@ bool EditorHelpSearch::Runner::_is_class_disabled_by_feature_profile(const Strin
return false;
}
+bool EditorHelpSearch::Runner::_fill() {
+ bool phase_done = false;
+ switch (phase) {
+ case PHASE_MATCH_CLASSES_INIT:
+ phase_done = _phase_fill_classes_init();
+ break;
+ case PHASE_MATCH_CLASSES:
+ phase_done = _phase_fill_classes();
+ break;
+ case PHASE_CLASS_ITEMS_INIT:
+ case PHASE_CLASS_ITEMS:
+ phase_done = true;
+ break;
+ case PHASE_MEMBER_ITEMS_INIT:
+ phase_done = _phase_fill_member_items_init();
+ break;
+ case PHASE_MEMBER_ITEMS:
+ phase_done = _phase_fill_member_items();
+ break;
+ case PHASE_SELECT_MATCH:
+ phase_done = _phase_select_match();
+ break;
+ case PHASE_MAX:
+ return true;
+ default:
+ WARN_PRINT("Invalid or unhandled phase in EditorHelpSearch::Runner, aborting search.");
+ return true;
+ }
+
+ if (phase_done) {
+ phase++;
+ }
+ return false;
+}
+
+bool EditorHelpSearch::Runner::_phase_fill_classes_init() {
+ // Initialize fill.
+ iterator_stack.clear();
+ matched_classes.clear();
+ matched_item = nullptr;
+ match_highest_score = 0;
+
+ // Initialize stack of iterators to fill, in reverse.
+ iterator_stack.push_back(EditorHelp::get_doc_data()->inheriting[""].back());
+
+ return true;
+}
+
+bool EditorHelpSearch::Runner::_phase_fill_classes() {
+ if (iterator_stack.is_empty()) {
+ return true;
+ }
+
+ if (iterator_stack[iterator_stack.size() - 1]) {
+ DocData::ClassDoc *class_doc = EditorHelp::get_doc_data()->class_list.getptr(iterator_stack[iterator_stack.size() - 1]->get());
+
+ // Decrement stack.
+ iterator_stack[iterator_stack.size() - 1] = iterator_stack[iterator_stack.size() - 1]->prev();
+
+ // Drop last element of stack if empty.
+ if (!iterator_stack[iterator_stack.size() - 1]) {
+ iterator_stack.resize(iterator_stack.size() - 1);
+ }
+
+ if (!class_doc || class_doc->name.is_empty()) {
+ return false;
+ }
+
+ // If class matches the flags, add it to the matched stack.
+ const bool class_matched =
+ (search_flags & SEARCH_CLASSES) ||
+ ((search_flags & SEARCH_CONSTRUCTORS) && !class_doc->constructors.is_empty()) ||
+ ((search_flags & SEARCH_METHODS) && !class_doc->methods.is_empty()) ||
+ ((search_flags & SEARCH_OPERATORS) && !class_doc->operators.is_empty()) ||
+ ((search_flags & SEARCH_SIGNALS) && !class_doc->signals.is_empty()) ||
+ ((search_flags & SEARCH_CONSTANTS) && !class_doc->constants.is_empty()) ||
+ ((search_flags & SEARCH_PROPERTIES) && !class_doc->properties.is_empty()) ||
+ ((search_flags & SEARCH_THEME_ITEMS) && !class_doc->theme_properties.is_empty()) ||
+ ((search_flags & SEARCH_ANNOTATIONS) && !class_doc->annotations.is_empty());
+
+ if (class_matched) {
+ if (term.is_empty() || class_doc->name.containsn(term)) {
+ matched_classes.push_back(Pair<DocData::ClassDoc *, String>(class_doc, String()));
+ } else if (String keyword = _match_keywords(term, class_doc->keywords); !keyword.is_empty()) {
+ matched_classes.push_back(Pair<DocData::ClassDoc *, String>(class_doc, keyword));
+ }
+ }
+
+ // Add inheriting classes, in reverse.
+ if (class_doc && EditorHelp::get_doc_data()->inheriting.has(class_doc->name)) {
+ iterator_stack.push_back(EditorHelp::get_doc_data()->inheriting[class_doc->name].back());
+ }
+
+ return false;
+ }
+
+ // Drop last element of stack if empty.
+ if (!iterator_stack[iterator_stack.size() - 1]) {
+ iterator_stack.resize(iterator_stack.size() - 1);
+ }
+
+ return iterator_stack.is_empty();
+}
+
+bool EditorHelpSearch::Runner::_phase_fill_member_items_init() {
+ // Prepare tree.
+ class_items.clear();
+ _populate_cache();
+
+ return true;
+}
+
+TreeItem *EditorHelpSearch::Runner::_create_category_item(TreeItem *p_parent, const String &p_class, const StringName &p_icon, const String &p_metatype, const String &p_text) {
+ const String item_meta = "class_" + p_metatype + ":" + p_class;
+
+ TreeItem *item = nullptr;
+ if (_find_or_create_item(p_parent, item_meta, item)) {
+ item->set_icon(0, ui_service->get_editor_theme_icon(p_icon));
+ item->set_text(0, p_text);
+ item->set_metadata(0, item_meta);
+ }
+ item->set_collapsed(true);
+
+ return item;
+}
+
+bool EditorHelpSearch::Runner::_phase_fill_member_items() {
+ if (matched_classes.is_empty()) {
+ return true;
+ }
+
+ // Pop working item from stack.
+ Pair<DocData::ClassDoc *, String> match = matched_classes[matched_classes.size() - 1];
+ DocData::ClassDoc *class_doc = match.first;
+ const String &keyword = match.second;
+ matched_classes.resize(matched_classes.size() - 1);
+
+ if (class_doc) {
+ TreeItem *item = _create_class_hierarchy(class_doc, keyword, !(search_flags & SEARCH_CLASSES));
+
+ // If the class has no inheriting classes, fold its item.
+ item->set_collapsed(!item->get_first_child());
+
+ if (search_flags & SEARCH_CLASSES) {
+ item->clear_custom_color(0);
+ item->clear_custom_color(1);
+ } else {
+ item->set_custom_color(0, disabled_color);
+ item->set_custom_color(1, disabled_color);
+ }
+
+ // Create common header if required.
+ const bool search_all = (search_flags & SEARCH_ALL) == SEARCH_ALL;
+
+ if ((search_flags & SEARCH_CONSTRUCTORS) && !class_doc->constructors.is_empty()) {
+ TreeItem *parent_item = item;
+ if (search_all) {
+ parent_item = _create_category_item(parent_item, class_doc->name, SNAME("MemberConstructor"), TTRC("Constructors"), "constructors");
+ }
+ for (const DocData::MethodDoc &constructor_doc : class_doc->constructors) {
+ _create_constructor_item(parent_item, class_doc, &constructor_doc);
+ }
+ }
+ if ((search_flags & SEARCH_METHODS) && !class_doc->methods.is_empty()) {
+ TreeItem *parent_item = item;
+ if (search_all) {
+ parent_item = _create_category_item(parent_item, class_doc->name, SNAME("MemberMethod"), TTRC("Methods"), "methods");
+ }
+ for (const DocData::MethodDoc &method_doc : class_doc->methods) {
+ _create_method_item(parent_item, class_doc, &method_doc);
+ }
+ }
+ if ((search_flags & SEARCH_OPERATORS) && !class_doc->operators.is_empty()) {
+ TreeItem *parent_item = item;
+ if (search_all) {
+ parent_item = _create_category_item(parent_item, class_doc->name, SNAME("MemberOperator"), TTRC("Operators"), "operators");
+ }
+ for (const DocData::MethodDoc &operator_doc : class_doc->operators) {
+ _create_operator_item(parent_item, class_doc, &operator_doc);
+ }
+ }
+ if ((search_flags & SEARCH_SIGNALS) && !class_doc->signals.is_empty()) {
+ TreeItem *parent_item = item;
+ if (search_all) {
+ parent_item = _create_category_item(parent_item, class_doc->name, SNAME("MemberSignal"), TTRC("Signals"), "signals");
+ }
+ for (const DocData::MethodDoc &signal_doc : class_doc->signals) {
+ _create_signal_item(parent_item, class_doc, &signal_doc);
+ }
+ }
+ if ((search_flags & SEARCH_CONSTANTS) && !class_doc->constants.is_empty()) {
+ TreeItem *parent_item = item;
+ if (search_all) {
+ parent_item = _create_category_item(parent_item, class_doc->name, SNAME("MemberConstant"), TTRC("Constants"), "constants");
+ }
+ for (const DocData::ConstantDoc &constant_doc : class_doc->constants) {
+ _create_constant_item(parent_item, class_doc, &constant_doc);
+ }
+ }
+ if ((search_flags & SEARCH_PROPERTIES) && !class_doc->properties.is_empty()) {
+ TreeItem *parent_item = item;
+ if (search_all) {
+ parent_item = _create_category_item(parent_item, class_doc->name, SNAME("MemberProperty"), TTRC("Prtoperties"), "propertiess");
+ }
+ for (const DocData::PropertyDoc &property_doc : class_doc->properties) {
+ _create_property_item(parent_item, class_doc, &property_doc);
+ }
+ }
+ if ((search_flags & SEARCH_THEME_ITEMS) && !class_doc->theme_properties.is_empty()) {
+ TreeItem *parent_item = item;
+ if (search_all) {
+ parent_item = _create_category_item(parent_item, class_doc->name, SNAME("MemberTheme"), TTRC("Theme Properties"), "theme_items");
+ }
+ for (const DocData::ThemeItemDoc &theme_property_doc : class_doc->theme_properties) {
+ _create_theme_property_item(parent_item, class_doc, &theme_property_doc);
+ }
+ }
+ if ((search_flags & SEARCH_ANNOTATIONS) && !class_doc->annotations.is_empty()) {
+ TreeItem *parent_item = item;
+ if (search_all) {
+ parent_item = _create_category_item(parent_item, class_doc->name, SNAME("MemberAnnotation"), TTRC("Annotations"), "annotations");
+ }
+ for (const DocData::MethodDoc &annotation_doc : class_doc->annotations) {
+ _create_annotation_item(parent_item, class_doc, &annotation_doc);
+ }
+ }
+ }
+
+ return matched_classes.is_empty();
+}
+
bool EditorHelpSearch::Runner::_slice() {
bool phase_done = false;
switch (phase) {
@@ -430,7 +684,7 @@ bool EditorHelpSearch::Runner::_slice() {
default:
WARN_PRINT("Invalid or unhandled phase in EditorHelpSearch::Runner, aborting search.");
return true;
- };
+ }
if (phase_done) {
phase++;
@@ -450,9 +704,11 @@ bool EditorHelpSearch::Runner::_phase_match_classes_init() {
matched_item = nullptr;
match_highest_score = 0;
- terms = term.split_spaces();
- if (terms.is_empty()) {
- terms.append(term);
+ if (!term.is_empty()) {
+ terms = term.split_spaces();
+ if (terms.is_empty()) {
+ terms.append(term);
+ }
}
return true;
@@ -480,78 +736,71 @@ bool EditorHelpSearch::Runner::_phase_match_classes() {
// Match class name.
if (search_flags & SEARCH_CLASSES) {
- // If the search term is empty, add any classes which are not script docs or which don't start with
- // a double-quotation. This will ensure that only C++ classes and explicitly named classes will
- // be added.
- match.name = (term.is_empty() && (!class_doc->is_script_doc || class_doc->name[0] != '\"')) || _match_string(term, class_doc->name);
+ match.name = _match_string(term, class_doc->name);
match.keyword = _match_keywords(term, class_doc->keywords);
}
- // Match members only if the term is long enough, to avoid slow performance from building a large tree.
- // Make an exception for annotations, since there are not that many of them.
- if (term.length() > 1 || term == "@") {
- if (search_flags & SEARCH_CONSTRUCTORS) {
- _match_method_name_and_push_back(class_doc->constructors, &match.constructors);
- }
- if (search_flags & SEARCH_METHODS) {
- _match_method_name_and_push_back(class_doc->methods, &match.methods);
- }
- if (search_flags & SEARCH_OPERATORS) {
- _match_method_name_and_push_back(class_doc->operators, &match.operators);
- }
- if (search_flags & SEARCH_SIGNALS) {
- for (int i = 0; i < class_doc->signals.size(); i++) {
- MemberMatch<DocData::MethodDoc> signal;
- signal.name = _all_terms_in_name(class_doc->signals[i].name);
- signal.keyword = _match_keywords_in_all_terms(class_doc->signals[i].keywords);
- if (signal.name || !signal.keyword.is_empty()) {
- signal.doc = const_cast<DocData::MethodDoc *>(&class_doc->signals[i]);
- match.signals.push_back(signal);
- }
+ if (search_flags & SEARCH_CONSTRUCTORS) {
+ _match_method_name_and_push_back(class_doc->constructors, &match.constructors);
+ }
+ if (search_flags & SEARCH_METHODS) {
+ _match_method_name_and_push_back(class_doc->methods, &match.methods);
+ }
+ if (search_flags & SEARCH_OPERATORS) {
+ _match_method_name_and_push_back(class_doc->operators, &match.operators);
+ }
+ if (search_flags & SEARCH_SIGNALS) {
+ for (const DocData::MethodDoc &signal_doc : class_doc->signals) {
+ MemberMatch<DocData::MethodDoc> signal;
+ signal.name = _all_terms_in_name(signal_doc.name);
+ signal.keyword = _match_keywords_in_all_terms(signal_doc.keywords);
+ if (signal.name || !signal.keyword.is_empty()) {
+ signal.doc = &signal_doc;
+ match.signals.push_back(signal);
}
}
- if (search_flags & SEARCH_CONSTANTS) {
- for (int i = 0; i < class_doc->constants.size(); i++) {
- MemberMatch<DocData::ConstantDoc> constant;
- constant.name = _all_terms_in_name(class_doc->constants[i].name);
- constant.keyword = _match_keywords_in_all_terms(class_doc->constants[i].keywords);
- if (constant.name || !constant.keyword.is_empty()) {
- constant.doc = const_cast<DocData::ConstantDoc *>(&class_doc->constants[i]);
- match.constants.push_back(constant);
- }
+ }
+ if (search_flags & SEARCH_CONSTANTS) {
+ for (const DocData::ConstantDoc &constant_doc : class_doc->constants) {
+ MemberMatch<DocData::ConstantDoc> constant;
+ constant.name = _all_terms_in_name(constant_doc.name);
+ constant.keyword = _match_keywords_in_all_terms(constant_doc.keywords);
+ if (constant.name || !constant.keyword.is_empty()) {
+ constant.doc = &constant_doc;
+ match.constants.push_back(constant);
}
}
- if (search_flags & SEARCH_PROPERTIES) {
- for (int i = 0; i < class_doc->properties.size(); i++) {
- MemberMatch<DocData::PropertyDoc> property;
- property.name = _all_terms_in_name(class_doc->properties[i].name);
- property.keyword = _match_keywords_in_all_terms(class_doc->properties[i].keywords);
- if (property.name || !property.keyword.is_empty()) {
- property.doc = const_cast<DocData::PropertyDoc *>(&class_doc->properties[i]);
- match.properties.push_back(property);
- }
+ }
+ if (search_flags & SEARCH_PROPERTIES) {
+ for (const DocData::PropertyDoc &property_doc : class_doc->properties) {
+ MemberMatch<DocData::PropertyDoc> property;
+ property.name = _all_terms_in_name(property_doc.name);
+ property.keyword = _match_keywords_in_all_terms(property_doc.keywords);
+ if (property.name || !property.keyword.is_empty()) {
+ property.doc = &property_doc;
+ match.properties.push_back(property);
}
}
- if (search_flags & SEARCH_THEME_ITEMS) {
- for (int i = 0; i < class_doc->theme_properties.size(); i++) {
- MemberMatch<DocData::ThemeItemDoc> theme_property;
- theme_property.name = _all_terms_in_name(class_doc->theme_properties[i].name);
- theme_property.keyword = _match_keywords_in_all_terms(class_doc->theme_properties[i].keywords);
- if (theme_property.name || !theme_property.keyword.is_empty()) {
- theme_property.doc = const_cast<DocData::ThemeItemDoc *>(&class_doc->theme_properties[i]);
- match.theme_properties.push_back(theme_property);
- }
+ }
+ if (search_flags & SEARCH_THEME_ITEMS) {
+ for (const DocData::ThemeItemDoc &theme_property_doc : class_doc->theme_properties) {
+ MemberMatch<DocData::ThemeItemDoc> theme_property;
+ theme_property.name = _all_terms_in_name(theme_property_doc.name);
+ theme_property.keyword = _match_keywords_in_all_terms(theme_property_doc.keywords);
+ if (theme_property.name || !theme_property.keyword.is_empty()) {
+ theme_property.doc = &theme_property_doc;
+ match.theme_properties.push_back(theme_property);
}
}
- if (search_flags & SEARCH_ANNOTATIONS) {
- for (int i = 0; i < class_doc->annotations.size(); i++) {
- MemberMatch<DocData::MethodDoc> annotation;
- annotation.name = _all_terms_in_name(class_doc->annotations[i].name);
- annotation.keyword = _match_keywords_in_all_terms(class_doc->annotations[i].keywords);
- if (annotation.name || !annotation.keyword.is_empty()) {
- annotation.doc = const_cast<DocData::MethodDoc *>(&class_doc->annotations[i]);
- match.annotations.push_back(annotation);
- }
+ }
+ if (search_flags & SEARCH_ANNOTATIONS) {
+ for (const DocData::MethodDoc &annotation_doc : class_doc->annotations) {
+ MemberMatch<DocData::MethodDoc> annotation;
+ annotation.name = _all_terms_in_name(annotation_doc.name);
+ annotation.keyword = _match_keywords_in_all_terms(annotation_doc.keywords);
+ if (annotation.name || !annotation.keyword.is_empty()) {
+ annotation.doc = &annotation_doc;
+ match.annotations.push_back(annotation);
}
}
}
@@ -564,9 +813,11 @@ bool EditorHelpSearch::Runner::_phase_match_classes() {
}
if (!iterator_stack.is_empty()) {
+ // Iterate on stack.
if (iterator_stack[iterator_stack.size() - 1]) {
iterator_stack[iterator_stack.size() - 1] = iterator_stack[iterator_stack.size() - 1]->next();
}
+ // Drop last element of stack.
if (!iterator_stack[iterator_stack.size() - 1]) {
iterator_stack.resize(iterator_stack.size() - 1);
}
@@ -661,36 +912,32 @@ bool EditorHelpSearch::Runner::_phase_member_items() {
return false;
}
+ // Pick appropriate parent item if showing hierarchy, otherwise pick root.
TreeItem *parent_item = (search_flags & SEARCH_SHOW_HIERARCHY) ? class_items[match.doc->name] : root_item;
- bool constructor_created = false;
- for (int i = 0; i < match.methods.size(); i++) {
- String text = match.methods[i].doc->name;
- if (!constructor_created) {
- if (match.doc->name == match.methods[i].doc->name) {
- text += " " + TTR("(constructors)");
- constructor_created = true;
- }
- } else {
- if (match.doc->name == match.methods[i].doc->name) {
- continue;
- }
- }
- _create_method_item(parent_item, match.doc, text, match.methods[i]);
+
+ for (const MemberMatch<DocData::MethodDoc> &constructor_item : match.constructors) {
+ _create_constructor_item(parent_item, match.doc, constructor_item);
}
- for (int i = 0; i < match.signals.size(); i++) {
- _create_signal_item(parent_item, match.doc, match.signals[i]);
+ for (const MemberMatch<DocData::MethodDoc> &method_item : match.methods) {
+ _create_method_item(parent_item, match.doc, method_item);
}
- for (int i = 0; i < match.constants.size(); i++) {
- _create_constant_item(parent_item, match.doc, match.constants[i]);
+ for (const MemberMatch<DocData::MethodDoc> &operator_item : match.operators) {
+ _create_operator_item(parent_item, match.doc, operator_item);
}
- for (int i = 0; i < match.properties.size(); i++) {
- _create_property_item(parent_item, match.doc, match.properties[i]);
+ for (const MemberMatch<DocData::MethodDoc> &signal_item : match.signals) {
+ _create_signal_item(parent_item, match.doc, signal_item);
}
- for (int i = 0; i < match.theme_properties.size(); i++) {
- _create_theme_property_item(parent_item, match.doc, match.theme_properties[i]);
+ for (const MemberMatch<DocData::ConstantDoc> &constant_item : match.constants) {
+ _create_constant_item(parent_item, match.doc, constant_item);
}
- for (int i = 0; i < match.annotations.size(); i++) {
- _create_annotation_item(parent_item, match.doc, match.annotations[i]);
+ for (const MemberMatch<DocData::PropertyDoc> &property_item : match.properties) {
+ _create_property_item(parent_item, match.doc, property_item);
+ }
+ for (const MemberMatch<DocData::ThemeItemDoc> &theme_property_item : match.theme_properties) {
+ _create_theme_property_item(parent_item, match.doc, theme_property_item);
+ }
+ for (const MemberMatch<DocData::MethodDoc> &annotation_item : match.annotations) {
+ _create_annotation_item(parent_item, match.doc, annotation_item);
}
++iterator_match;
@@ -704,7 +951,7 @@ bool EditorHelpSearch::Runner::_phase_select_match() {
return true;
}
-void EditorHelpSearch::Runner::_match_method_name_and_push_back(Vector<DocData::MethodDoc> &p_methods, Vector<MemberMatch<DocData::MethodDoc>> *r_match_methods) {
+void EditorHelpSearch::Runner::_match_method_name_and_push_back(Vector<DocData::MethodDoc> &p_methods, LocalVector<MemberMatch<DocData::MethodDoc>> *r_match_methods) {
// Constructors, Methods, Operators...
for (int i = 0; i < p_methods.size(); i++) {
String method_name = (search_flags & SEARCH_CASE_SENSITIVE) ? p_methods[i].name : p_methods[i].name.to_lower();
@@ -765,12 +1012,12 @@ void EditorHelpSearch::Runner::_match_item(TreeItem *p_item, const String &p_tex
return;
}
- float inverse_length = 1.f / float(p_text.length());
+ float inverse_length = 1.0f / float(p_text.length());
// Favor types where search term is a substring close to the start of the type.
float w = 0.5f;
int pos = p_text.findn(term);
- float score = (pos > -1) ? 1.0f - w * MIN(1, 3 * pos * inverse_length) : MAX(0.f, .9f - w);
+ float score = (pos > -1) ? 1.0f - w * MIN(1, 3 * pos * inverse_length) : MAX(0.0f, 0.9f - w);
// Favor shorter items: they resemble the search term more.
w = 0.1f;
@@ -781,7 +1028,8 @@ void EditorHelpSearch::Runner::_match_item(TreeItem *p_item, const String &p_tex
score *= 0.9f;
}
- if (match_highest_score == 0 || score > match_highest_score) {
+ // Replace current match if term is short as we are searching in reverse.
+ if (match_highest_score == 0 || score > match_highest_score || (score == match_highest_score && term.length() == 1)) {
matched_item = p_item;
match_highest_score = score;
}
@@ -820,6 +1068,29 @@ String EditorHelpSearch::Runner::_build_keywords_tooltip(const String &p_keyword
return tooltip.left(-2);
}
+TreeItem *EditorHelpSearch::Runner::_create_class_hierarchy(const DocData::ClassDoc *p_class_doc, const String &p_matching_keyword, bool p_gray) {
+ if (p_class_doc->name.is_empty()) {
+ return nullptr;
+ }
+ if (TreeItem **found = class_items.getptr(p_class_doc->name)) {
+ return *found;
+ }
+
+ // Ensure parent nodes are created first.
+ TreeItem *parent_item = root_item;
+ if (!p_class_doc->inherits.is_empty()) {
+ if (class_items.has(p_class_doc->inherits)) {
+ parent_item = class_items[p_class_doc->inherits];
+ } else if (const DocData::ClassDoc *found = EditorHelp::get_doc_data()->class_list.getptr(p_class_doc->inherits)) {
+ parent_item = _create_class_hierarchy(found, String(), true);
+ }
+ }
+
+ TreeItem *class_item = _create_class_item(parent_item, p_class_doc, p_gray, p_matching_keyword);
+ class_items[p_class_doc->name] = class_item;
+ return class_item;
+}
+
TreeItem *EditorHelpSearch::Runner::_create_class_hierarchy(const ClassMatch &p_match) {
if (p_match.doc->name.is_empty()) {
return nullptr;
@@ -887,6 +1158,8 @@ TreeItem *EditorHelpSearch::Runner::_create_class_item(TreeItem *p_parent, const
item->add_button(0, warning_icon, 0, false, TTR("This class is marked as experimental."));
}
}
+ // Cached item might be collapsed.
+ item->set_collapsed(false);
if (p_gray) {
item->set_custom_color(0, disabled_color);
@@ -902,7 +1175,9 @@ TreeItem *EditorHelpSearch::Runner::_create_class_item(TreeItem *p_parent, const
item->set_text(0, p_doc->name + " - " + TTR(vformat("Matches the \"%s\" keyword.", p_matching_keyword)));
}
- _match_item(item, p_doc->name);
+ if (!term.is_empty()) {
+ _match_item(item, p_doc->name);
+ }
for (const String &keyword : p_doc->keywords.split(",")) {
_match_item(item, keyword.strip_edges(), true);
}
@@ -910,44 +1185,73 @@ TreeItem *EditorHelpSearch::Runner::_create_class_item(TreeItem *p_parent, const
return item;
}
-TreeItem *EditorHelpSearch::Runner::_create_method_item(TreeItem *p_parent, const DocData::ClassDoc *p_class_doc, const String &p_text, const MemberMatch<DocData::MethodDoc> &p_match) {
+TreeItem *EditorHelpSearch::Runner::_create_constructor_item(TreeItem *p_parent, const DocData::ClassDoc *p_class_doc, const MemberMatch<DocData::MethodDoc> &p_match) {
+ String tooltip = p_class_doc->name + "(";
+ String text = p_class_doc->name + "(";
+ for (int i = 0; i < p_match.doc->arguments.size(); i++) {
+ const DocData::ArgumentDoc &arg = p_match.doc->arguments[i];
+ tooltip += arg.type + " " + arg.name;
+ text += arg.type;
+ if (!arg.default_value.is_empty()) {
+ tooltip += " = " + arg.default_value;
+ }
+ if (i < p_match.doc->arguments.size() - 1) {
+ tooltip += ", ";
+ text += ", ";
+ }
+ }
+ tooltip += ")";
+ tooltip += _build_keywords_tooltip(p_match.doc->keywords);
+ text += ")";
+ return _create_member_item(p_parent, p_class_doc->name, SNAME("MemberConstructor"), p_match.doc->name, text, TTRC("Constructor"), "method", tooltip, p_match.doc->keywords, p_match.doc->is_deprecated, p_match.doc->is_experimental, p_match.name ? String() : p_match.keyword);
+}
+
+TreeItem *EditorHelpSearch::Runner::_create_method_item(TreeItem *p_parent, const DocData::ClassDoc *p_class_doc, const MemberMatch<DocData::MethodDoc> &p_match) {
String tooltip = _build_method_tooltip(p_class_doc, p_match.doc);
- return _create_member_item(p_parent, p_class_doc->name, "MemberMethod", p_match.doc->name, p_text, TTRC("Method"), "method", tooltip, p_match.doc->keywords, p_match.doc->is_deprecated, p_match.doc->is_experimental, p_match.name ? String() : p_match.keyword);
+ return _create_member_item(p_parent, p_class_doc->name, SNAME("MemberMethod"), p_match.doc->name, p_match.doc->name, TTRC("Method"), "method", tooltip, p_match.doc->keywords, p_match.doc->is_deprecated, p_match.doc->is_experimental, p_match.name ? String() : p_match.keyword);
+}
+
+TreeItem *EditorHelpSearch::Runner::_create_operator_item(TreeItem *p_parent, const DocData::ClassDoc *p_class_doc, const MemberMatch<DocData::MethodDoc> &p_match) {
+ String tooltip = _build_method_tooltip(p_class_doc, p_match.doc);
+ String text = p_match.doc->name;
+ if (!p_match.doc->arguments.is_empty()) {
+ text += "(" + p_match.doc->arguments[0].type + ")";
+ }
+ return _create_member_item(p_parent, p_class_doc->name, SNAME("MemberOperator"), p_match.doc->name, text, TTRC("Operator"), "method", tooltip, p_match.doc->keywords, p_match.doc->is_deprecated, p_match.doc->is_experimental, p_match.name ? String() : p_match.keyword);
}
TreeItem *EditorHelpSearch::Runner::_create_signal_item(TreeItem *p_parent, const DocData::ClassDoc *p_class_doc, const MemberMatch<DocData::MethodDoc> &p_match) {
String tooltip = _build_method_tooltip(p_class_doc, p_match.doc);
- return _create_member_item(p_parent, p_class_doc->name, "MemberSignal", p_match.doc->name, p_match.doc->name, TTRC("Signal"), "signal", tooltip, p_match.doc->keywords, p_match.doc->is_deprecated, p_match.doc->is_experimental, p_match.name ? String() : p_match.keyword);
+ return _create_member_item(p_parent, p_class_doc->name, SNAME("MemberSignal"), p_match.doc->name, p_match.doc->name, TTRC("Signal"), "signal", tooltip, p_match.doc->keywords, p_match.doc->is_deprecated, p_match.doc->is_experimental, p_match.name ? String() : p_match.keyword);
}
TreeItem *EditorHelpSearch::Runner::_create_annotation_item(TreeItem *p_parent, const DocData::ClassDoc *p_class_doc, const MemberMatch<DocData::MethodDoc> &p_match) {
String tooltip = _build_method_tooltip(p_class_doc, p_match.doc);
// Hide the redundant leading @ symbol.
String text = p_match.doc->name.substr(1);
- return _create_member_item(p_parent, p_class_doc->name, "MemberAnnotation", p_match.doc->name, text, TTRC("Annotation"), "annotation", tooltip, p_match.doc->keywords, p_match.doc->is_deprecated, p_match.doc->is_experimental, p_match.name ? String() : p_match.keyword);
+ return _create_member_item(p_parent, p_class_doc->name, SNAME("MemberAnnotation"), p_match.doc->name, text, TTRC("Annotation"), "annotation", tooltip, p_match.doc->keywords, p_match.doc->is_deprecated, p_match.doc->is_experimental, p_match.name ? String() : p_match.keyword);
}
TreeItem *EditorHelpSearch::Runner::_create_constant_item(TreeItem *p_parent, const DocData::ClassDoc *p_class_doc, const MemberMatch<DocData::ConstantDoc> &p_match) {
String tooltip = p_class_doc->name + "." + p_match.doc->name;
tooltip += _build_keywords_tooltip(p_match.doc->keywords);
- return _create_member_item(p_parent, p_class_doc->name, "MemberConstant", p_match.doc->name, p_match.doc->name, TTRC("Constant"), "constant", tooltip, p_match.doc->keywords, p_match.doc->is_deprecated, p_match.doc->is_experimental, p_match.name ? String() : p_match.keyword);
+ return _create_member_item(p_parent, p_class_doc->name, SNAME("MemberConstant"), p_match.doc->name, p_match.doc->name, TTRC("Constant"), "constant", tooltip, p_match.doc->keywords, p_match.doc->is_deprecated, p_match.doc->is_experimental, p_match.name ? String() : p_match.keyword);
}
TreeItem *EditorHelpSearch::Runner::_create_property_item(TreeItem *p_parent, const DocData::ClassDoc *p_class_doc, const MemberMatch<DocData::PropertyDoc> &p_match) {
String tooltip = p_match.doc->type + " " + p_class_doc->name + "." + p_match.doc->name;
tooltip += "\n " + p_class_doc->name + "." + p_match.doc->setter + "(value) setter";
tooltip += "\n " + p_class_doc->name + "." + p_match.doc->getter + "() getter";
- tooltip += _build_keywords_tooltip(p_match.doc->keywords);
- return _create_member_item(p_parent, p_class_doc->name, "MemberProperty", p_match.doc->name, p_match.doc->name, TTRC("Property"), "property", tooltip, p_match.doc->keywords, p_match.doc->is_deprecated, p_match.doc->is_experimental, p_match.name ? String() : p_match.keyword);
+ return _create_member_item(p_parent, p_class_doc->name, SNAME("MemberProperty"), p_match.doc->name, p_match.doc->name, TTRC("Property"), "property", tooltip, p_match.doc->keywords, p_match.doc->is_deprecated, p_match.doc->is_experimental, p_match.name ? String() : p_match.keyword);
}
TreeItem *EditorHelpSearch::Runner::_create_theme_property_item(TreeItem *p_parent, const DocData::ClassDoc *p_class_doc, const MemberMatch<DocData::ThemeItemDoc> &p_match) {
String tooltip = p_match.doc->type + " " + p_class_doc->name + "." + p_match.doc->name;
tooltip += _build_keywords_tooltip(p_match.doc->keywords);
- return _create_member_item(p_parent, p_class_doc->name, "MemberTheme", p_match.doc->name, p_match.doc->name, TTRC("Theme Property"), "theme_item", p_match.doc->keywords, tooltip, false, false, p_match.name ? String() : p_match.keyword);
+ return _create_member_item(p_parent, p_class_doc->name, SNAME("MemberTheme"), p_match.doc->name, p_match.doc->name, TTRC("Theme Property"), "theme_item", p_match.doc->keywords, tooltip, p_match.doc->is_deprecated, p_match.doc->is_experimental, p_match.name ? String() : p_match.keyword);
}
-TreeItem *EditorHelpSearch::Runner::_create_member_item(TreeItem *p_parent, const String &p_class_name, const String &p_icon, const String &p_name, const String &p_text, const String &p_type, const String &p_metatype, const String &p_tooltip, const String &p_keywords, bool p_is_deprecated, bool p_is_experimental, const String &p_matching_keyword) {
+TreeItem *EditorHelpSearch::Runner::_create_member_item(TreeItem *p_parent, const String &p_class_name, const StringName &p_icon, const String &p_name, const String &p_text, const String &p_type, const String &p_metatype, const String &p_tooltip, const String &p_keywords, bool p_is_deprecated, bool p_is_experimental, const String &p_matching_keyword) {
const String item_meta = "class_" + p_metatype + ":" + p_class_name + ":" + p_name;
TreeItem *item = nullptr;
@@ -978,7 +1282,10 @@ TreeItem *EditorHelpSearch::Runner::_create_member_item(TreeItem *p_parent, cons
}
item->set_text(0, text);
- _match_item(item, p_name);
+ // Don't match member items for short searches.
+ if (term.length() > 1 || term == "@") {
+ _match_item(item, p_name);
+ }
for (const String &keyword : p_keywords.split(",")) {
_match_item(item, keyword.strip_edges(), true);
}
@@ -989,9 +1296,17 @@ TreeItem *EditorHelpSearch::Runner::_create_member_item(TreeItem *p_parent, cons
bool EditorHelpSearch::Runner::work(uint64_t slot) {
// Return true when the search has been completed, otherwise false.
const uint64_t until = OS::get_singleton()->get_ticks_usec() + slot;
- while (!_slice()) {
- if (OS::get_singleton()->get_ticks_usec() > until) {
- return false;
+ if (term.length() > 1 || term == "@") {
+ while (!_slice()) {
+ if (OS::get_singleton()->get_ticks_usec() > until) {
+ return false;
+ }
+ }
+ } else {
+ while (!_fill()) {
+ if (OS::get_singleton()->get_ticks_usec() > until) {
+ return false;
+ }
}
}
return true;
@@ -1001,7 +1316,7 @@ EditorHelpSearch::Runner::Runner(Control *p_icon_service, Tree *p_results_tree,
ui_service(p_icon_service),
results_tree(p_results_tree),
tree_cache(p_tree_cache),
- term((p_search_flags & SEARCH_CASE_SENSITIVE) == 0 ? p_term.strip_edges().to_lower() : p_term.strip_edges()),
+ term((p_search_flags & SEARCH_CASE_SENSITIVE) == 0 ? p_term.to_lower() : p_term),
search_flags(p_search_flags),
disabled_color(ui_service->get_theme_color(SNAME("font_disabled_color"), EditorStringName(Editor))) {
}
diff --git a/editor/editor_help_search.h b/editor/editor_help_search.h
index 58061dae4c..b8b3c26b41 100644
--- a/editor/editor_help_search.h
+++ b/editor/editor_help_search.h
@@ -63,6 +63,7 @@ class EditorHelpSearch : public ConfirmationDialog {
Tree *results_tree = nullptr;
bool old_search = false;
String old_term;
+ int old_search_flags = 0;
class Runner;
Ref<Runner> search;
@@ -119,26 +120,30 @@ class EditorHelpSearch::Runner : public RefCounted {
template <typename T>
struct MemberMatch {
- T *doc = nullptr;
+ const T *doc = nullptr;
bool name = false;
String keyword;
+
+ MemberMatch() {}
+ MemberMatch(const T *p_doc) :
+ doc(p_doc) {}
};
struct ClassMatch {
- DocData::ClassDoc *doc = nullptr;
+ const DocData::ClassDoc *doc = nullptr;
bool name = false;
String keyword;
- Vector<MemberMatch<DocData::MethodDoc>> constructors;
- Vector<MemberMatch<DocData::MethodDoc>> methods;
- Vector<MemberMatch<DocData::MethodDoc>> operators;
- Vector<MemberMatch<DocData::MethodDoc>> signals;
- Vector<MemberMatch<DocData::ConstantDoc>> constants;
- Vector<MemberMatch<DocData::PropertyDoc>> properties;
- Vector<MemberMatch<DocData::ThemeItemDoc>> theme_properties;
- Vector<MemberMatch<DocData::MethodDoc>> annotations;
+ LocalVector<MemberMatch<DocData::MethodDoc>> constructors;
+ LocalVector<MemberMatch<DocData::MethodDoc>> methods;
+ LocalVector<MemberMatch<DocData::MethodDoc>> operators;
+ LocalVector<MemberMatch<DocData::MethodDoc>> signals;
+ LocalVector<MemberMatch<DocData::ConstantDoc>> constants;
+ LocalVector<MemberMatch<DocData::PropertyDoc>> properties;
+ LocalVector<MemberMatch<DocData::ThemeItemDoc>> theme_properties;
+ LocalVector<MemberMatch<DocData::MethodDoc>> annotations;
bool required() {
- return name || !keyword.is_empty() || methods.size() || signals.size() || constants.size() || properties.size() || theme_properties.size() || annotations.size();
+ return name || !keyword.is_empty() || !constructors.is_empty() || !methods.is_empty() || !operators.is_empty() || !signals.is_empty() || !constants.is_empty() || !properties.is_empty() || !theme_properties.is_empty() || !annotations.is_empty();
}
};
@@ -155,6 +160,7 @@ class EditorHelpSearch::Runner : public RefCounted {
LocalVector<RBSet<String, NaturalNoCaseComparator>::Element *> iterator_stack;
HashMap<String, ClassMatch> matches;
HashMap<String, ClassMatch>::Iterator iterator_match;
+ LocalVector<Pair<DocData::ClassDoc *, String>> matched_classes;
TreeItem *root_item = nullptr;
HashMap<String, TreeItem *> class_items;
TreeItem *matched_item = nullptr;
@@ -165,6 +171,12 @@ class EditorHelpSearch::Runner : public RefCounted {
void _populate_cache();
bool _find_or_create_item(TreeItem *p_parent, const String &p_item_meta, TreeItem *&r_item);
+ bool _fill();
+ bool _phase_fill_classes_init();
+ bool _phase_fill_classes();
+ bool _phase_fill_member_items_init();
+ bool _phase_fill_member_items();
+
bool _slice();
bool _phase_match_classes_init();
bool _phase_match_classes();
@@ -177,21 +189,25 @@ class EditorHelpSearch::Runner : public RefCounted {
String _build_method_tooltip(const DocData::ClassDoc *p_class_doc, const DocData::MethodDoc *p_doc) const;
String _build_keywords_tooltip(const String &p_keywords) const;
- void _match_method_name_and_push_back(Vector<DocData::MethodDoc> &p_methods, Vector<MemberMatch<DocData::MethodDoc>> *r_match_methods);
+ void _match_method_name_and_push_back(Vector<DocData::MethodDoc> &p_methods, LocalVector<MemberMatch<DocData::MethodDoc>> *r_match_methods);
bool _all_terms_in_name(const String &p_name) const;
String _match_keywords_in_all_terms(const String &p_keywords) const;
bool _match_string(const String &p_term, const String &p_string) const;
String _match_keywords(const String &p_term, const String &p_keywords) const;
void _match_item(TreeItem *p_item, const String &p_text, bool p_is_keywords = false);
TreeItem *_create_class_hierarchy(const ClassMatch &p_match);
+ TreeItem *_create_class_hierarchy(const DocData::ClassDoc *p_class_doc, const String &p_matching_keyword, bool p_gray);
TreeItem *_create_class_item(TreeItem *p_parent, const DocData::ClassDoc *p_doc, bool p_gray, const String &p_matching_keyword);
- TreeItem *_create_method_item(TreeItem *p_parent, const DocData::ClassDoc *p_class_doc, const String &p_text, const MemberMatch<DocData::MethodDoc> &p_match);
+ TreeItem *_create_category_item(TreeItem *p_parent, const String &p_class, const StringName &p_icon, const String &p_metatype, const String &p_type);
+ TreeItem *_create_method_item(TreeItem *p_parent, const DocData::ClassDoc *p_class_doc, const MemberMatch<DocData::MethodDoc> &p_match);
+ TreeItem *_create_constructor_item(TreeItem *p_parent, const DocData::ClassDoc *p_class_doc, const MemberMatch<DocData::MethodDoc> &p_match);
+ TreeItem *_create_operator_item(TreeItem *p_parent, const DocData::ClassDoc *p_class_doc, const MemberMatch<DocData::MethodDoc> &p_match);
TreeItem *_create_signal_item(TreeItem *p_parent, const DocData::ClassDoc *p_class_doc, const MemberMatch<DocData::MethodDoc> &p_match);
TreeItem *_create_annotation_item(TreeItem *p_parent, const DocData::ClassDoc *p_class_doc, const MemberMatch<DocData::MethodDoc> &p_match);
TreeItem *_create_constant_item(TreeItem *p_parent, const DocData::ClassDoc *p_class_doc, const MemberMatch<DocData::ConstantDoc> &p_match);
TreeItem *_create_property_item(TreeItem *p_parent, const DocData::ClassDoc *p_class_doc, const MemberMatch<DocData::PropertyDoc> &p_match);
TreeItem *_create_theme_property_item(TreeItem *p_parent, const DocData::ClassDoc *p_class_doc, const MemberMatch<DocData::ThemeItemDoc> &p_match);
- TreeItem *_create_member_item(TreeItem *p_parent, const String &p_class_name, const String &p_icon, const String &p_name, const String &p_text, const String &p_type, const String &p_metatype, const String &p_tooltip, const String &p_keywords, bool p_is_deprecated, bool p_is_experimental, const String &p_matching_keyword);
+ TreeItem *_create_member_item(TreeItem *p_parent, const String &p_class_name, const StringName &p_icon, const String &p_name, const String &p_text, const String &p_type, const String &p_metatype, const String &p_tooltip, const String &p_keywords, bool p_is_deprecated, bool p_is_experimental, const String &p_matching_keyword);
public:
bool work(uint64_t slot = 100000);
diff --git a/editor/editor_inspector.cpp b/editor/editor_inspector.cpp
index 1e5acce032..a1cae374aa 100644
--- a/editor/editor_inspector.cpp
+++ b/editor/editor_inspector.cpp
@@ -1500,12 +1500,6 @@ void EditorInspectorSection::_notification(int p_what) {
draw_string(font, text_offset, label, text_align, available, font_size, font_color, TextServer::JUSTIFICATION_KASHIDA | TextServer::JUSTIFICATION_CONSTRAIN_ELLIPSIS);
}
- // Draw dropping highlight.
- if (dropping && !vbox->is_visible_in_tree()) {
- Color accent_color = get_theme_color(SNAME("accent_color"), EditorStringName(Editor));
- draw_rect(Rect2(Point2(), get_size()), accent_color, false);
- }
-
// Draw section indentation.
if (section_indent_style.is_valid() && section_indent > 0) {
Rect2 indent_rect = Rect2(Vector2(), Vector2(indent_depth * section_indent_size, get_size().height));
@@ -1527,14 +1521,14 @@ void EditorInspectorSection::_notification(int p_what) {
} break;
case NOTIFICATION_MOUSE_ENTER: {
- if (dropping || dropping_for_unfold) {
+ if (dropping_for_unfold) {
dropping_unfold_timer->start();
}
queue_redraw();
} break;
case NOTIFICATION_MOUSE_EXIT: {
- if (dropping || dropping_for_unfold) {
+ if (dropping_for_unfold) {
dropping_unfold_timer->stop();
}
queue_redraw();
diff --git a/editor/editor_inspector.h b/editor/editor_inspector.h
index 29ee234883..fda1443000 100644
--- a/editor/editor_inspector.h
+++ b/editor/editor_inspector.h
@@ -310,7 +310,6 @@ class EditorInspectorSection : public Container {
int level = 1;
Timer *dropping_unfold_timer = nullptr;
- bool dropping = false;
bool dropping_for_unfold = false;
HashSet<StringName> revertable_properties;
diff --git a/editor/editor_interface.compat.inc b/editor/editor_interface.compat.inc
new file mode 100644
index 0000000000..f5b35931fe
--- /dev/null
+++ b/editor/editor_interface.compat.inc
@@ -0,0 +1,48 @@
+/**************************************************************************/
+/* editor_interface.compat.inc */
+/**************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/**************************************************************************/
+/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/**************************************************************************/
+
+#include "editor_interface.h"
+
+#ifndef DISABLE_DEPRECATED
+
+void EditorInterface::_popup_node_selector_bind_compat_94323(const Callable &p_callback, const TypedArray<StringName> &p_valid_types) {
+ popup_node_selector(p_callback, p_valid_types, nullptr);
+}
+
+void EditorInterface::_popup_property_selector_bind_compat_94323(Object *p_object, const Callable &p_callback, const PackedInt32Array &p_type_filter) {
+ popup_property_selector(p_object, p_callback, p_type_filter, String());
+}
+
+void EditorInterface::_bind_compatibility_methods() {
+ ClassDB::bind_compatibility_method(D_METHOD("popup_node_selector", "callback", "valid_types"), &EditorInterface::_popup_node_selector_bind_compat_94323, DEFVAL(TypedArray<StringName>()));
+ ClassDB::bind_compatibility_method(D_METHOD("popup_property_selector", "object", "callback", "type_filter"), &EditorInterface::_popup_property_selector_bind_compat_94323, DEFVAL(PackedInt32Array()));
+}
+
+#endif
diff --git a/editor/editor_interface.cpp b/editor/editor_interface.cpp
index 46113ab2cb..86b66ef410 100644
--- a/editor/editor_interface.cpp
+++ b/editor/editor_interface.cpp
@@ -29,6 +29,7 @@
/**************************************************************************/
#include "editor_interface.h"
+#include "editor_interface.compat.inc"
#include "editor/editor_command_palette.h"
#include "editor/editor_feature_profile.h"
@@ -86,6 +87,10 @@ Ref<EditorSettings> EditorInterface::get_editor_settings() const {
return EditorSettings::get_singleton();
}
+EditorUndoRedoManager *EditorInterface::get_editor_undo_redo() const {
+ return EditorUndoRedoManager::get_singleton();
+}
+
TypedArray<Texture2D> EditorInterface::_make_mesh_previews(const TypedArray<Mesh> &p_meshes, int p_preview_size) {
Vector<Ref<Mesh>> meshes;
@@ -272,7 +277,7 @@ void EditorInterface::set_current_feature_profile(const String &p_profile_name)
// Editor dialogs.
-void EditorInterface::popup_node_selector(const Callable &p_callback, const TypedArray<StringName> &p_valid_types) {
+void EditorInterface::popup_node_selector(const Callable &p_callback, const TypedArray<StringName> &p_valid_types, Node *p_current_value) {
// TODO: Should reuse dialog instance instead of creating a fresh one, but need to rework set_valid_types first.
if (node_selector) {
node_selector->disconnect(SNAME("selected"), callable_mp(this, &EditorInterface::_node_selected).bind(p_callback));
@@ -292,7 +297,7 @@ void EditorInterface::popup_node_selector(const Callable &p_callback, const Type
get_base_control()->add_child(node_selector);
- node_selector->popup_scenetree_dialog();
+ node_selector->popup_scenetree_dialog(p_current_value);
const Callable selected_callback = callable_mp(this, &EditorInterface::_node_selected).bind(p_callback);
node_selector->connect(SNAME("selected"), selected_callback, CONNECT_DEFERRED);
@@ -301,7 +306,7 @@ void EditorInterface::popup_node_selector(const Callable &p_callback, const Type
node_selector->connect(SNAME("canceled"), canceled_callback, CONNECT_DEFERRED);
}
-void EditorInterface::popup_property_selector(Object *p_object, const Callable &p_callback, const PackedInt32Array &p_type_filter) {
+void EditorInterface::popup_property_selector(Object *p_object, const Callable &p_callback, const PackedInt32Array &p_type_filter, const String &p_current_value) {
// TODO: Should reuse dialog instance instead of creating a fresh one, but need to rework set_type_filter first.
if (property_selector) {
property_selector->disconnect(SNAME("selected"), callable_mp(this, &EditorInterface::_property_selected).bind(p_callback));
@@ -321,7 +326,7 @@ void EditorInterface::popup_property_selector(Object *p_object, const Callable &
get_base_control()->add_child(property_selector);
- property_selector->select_property_from_instance(p_object);
+ property_selector->select_property_from_instance(p_object, p_current_value);
const Callable selected_callback = callable_mp(this, &EditorInterface::_property_selected).bind(p_callback);
property_selector->connect(SNAME("selected"), selected_callback, CONNECT_DEFERRED);
@@ -525,6 +530,7 @@ void EditorInterface::_bind_methods() {
ClassDB::bind_method(D_METHOD("get_resource_previewer"), &EditorInterface::get_resource_previewer);
ClassDB::bind_method(D_METHOD("get_selection"), &EditorInterface::get_selection);
ClassDB::bind_method(D_METHOD("get_editor_settings"), &EditorInterface::get_editor_settings);
+ ClassDB::bind_method(D_METHOD("get_editor_undo_redo"), &EditorInterface::get_editor_undo_redo);
ClassDB::bind_method(D_METHOD("make_mesh_previews", "meshes", "preview_size"), &EditorInterface::_make_mesh_previews);
@@ -559,8 +565,8 @@ void EditorInterface::_bind_methods() {
// Editor dialogs.
- ClassDB::bind_method(D_METHOD("popup_node_selector", "callback", "valid_types"), &EditorInterface::popup_node_selector, DEFVAL(TypedArray<StringName>()));
- ClassDB::bind_method(D_METHOD("popup_property_selector", "object", "callback", "type_filter"), &EditorInterface::popup_property_selector, DEFVAL(PackedInt32Array()));
+ ClassDB::bind_method(D_METHOD("popup_node_selector", "callback", "valid_types", "current_value"), &EditorInterface::popup_node_selector, DEFVAL(TypedArray<StringName>()), DEFVAL(Variant()));
+ ClassDB::bind_method(D_METHOD("popup_property_selector", "object", "callback", "type_filter", "current_value"), &EditorInterface::popup_property_selector, DEFVAL(PackedInt32Array()), DEFVAL(String()));
// Editor docks.
diff --git a/editor/editor_interface.h b/editor/editor_interface.h
index 3ef4325780..20d66d71f5 100644
--- a/editor/editor_interface.h
+++ b/editor/editor_interface.h
@@ -45,6 +45,7 @@ class EditorPlugin;
class EditorResourcePreview;
class EditorSelection;
class EditorSettings;
+class EditorUndoRedoManager;
class FileSystemDock;
class Mesh;
class Node;
@@ -80,6 +81,13 @@ class EditorInterface : public Object {
protected:
static void _bind_methods();
+#ifndef DISABLE_DEPRECATED
+ void _popup_node_selector_bind_compat_94323(const Callable &p_callback, const TypedArray<StringName> &p_valid_types = TypedArray<StringName>());
+ void _popup_property_selector_bind_compat_94323(Object *p_object, const Callable &p_callback, const PackedInt32Array &p_type_filter = PackedInt32Array());
+
+ static void _bind_compatibility_methods();
+#endif
+
public:
static EditorInterface *get_singleton() { return singleton; }
@@ -93,6 +101,7 @@ public:
EditorResourcePreview *get_resource_previewer() const;
EditorSelection *get_selection() const;
Ref<EditorSettings> get_editor_settings() const;
+ EditorUndoRedoManager *get_editor_undo_redo() const;
Vector<Ref<Texture2D>> make_mesh_previews(const Vector<Ref<Mesh>> &p_meshes, Vector<Transform3D> *p_transforms, int p_preview_size);
@@ -126,9 +135,9 @@ public:
// Editor dialogs.
- void popup_node_selector(const Callable &p_callback, const TypedArray<StringName> &p_valid_types = TypedArray<StringName>());
+ void popup_node_selector(const Callable &p_callback, const TypedArray<StringName> &p_valid_types = TypedArray<StringName>(), Node *p_current_value = nullptr);
// Must use Vector<int> because exposing Vector<Variant::Type> is not supported.
- void popup_property_selector(Object *p_object, const Callable &p_callback, const PackedInt32Array &p_type_filter = PackedInt32Array());
+ void popup_property_selector(Object *p_object, const Callable &p_callback, const PackedInt32Array &p_type_filter = PackedInt32Array(), const String &p_current_value = String());
// Editor docks.
diff --git a/editor/editor_locale_dialog.cpp b/editor/editor_locale_dialog.cpp
index 83f1c70c69..c36792c9e3 100644
--- a/editor/editor_locale_dialog.cpp
+++ b/editor/editor_locale_dialog.cpp
@@ -408,7 +408,7 @@ EditorLocaleDialog::EditorLocaleDialog() {
edit_filters->set_text(TTR("Edit Filters"));
edit_filters->set_toggle_mode(true);
edit_filters->set_pressed(false);
- edit_filters->connect("toggled", callable_mp(this, &EditorLocaleDialog::_edit_filters));
+ edit_filters->connect(SceneStringName(toggled), callable_mp(this, &EditorLocaleDialog::_edit_filters));
hb_filter->add_child(edit_filters);
}
{
@@ -416,7 +416,7 @@ EditorLocaleDialog::EditorLocaleDialog() {
advanced->set_text(TTR("Advanced"));
advanced->set_toggle_mode(true);
advanced->set_pressed(false);
- advanced->connect("toggled", callable_mp(this, &EditorLocaleDialog::_toggle_advanced));
+ advanced->connect(SceneStringName(toggled), callable_mp(this, &EditorLocaleDialog::_toggle_advanced));
hb_filter->add_child(advanced);
}
vb->add_child(hb_filter);
diff --git a/editor/editor_log.cpp b/editor/editor_log.cpp
index 2c30cb8265..aec374929e 100644
--- a/editor/editor_log.cpp
+++ b/editor/editor_log.cpp
@@ -510,7 +510,7 @@ EditorLog::EditorLog() {
collapse_button->set_tooltip_text(TTR("Collapse duplicate messages into one log entry. Shows number of occurrences."));
collapse_button->set_toggle_mode(true);
collapse_button->set_pressed(false);
- collapse_button->connect("toggled", callable_mp(this, &EditorLog::_set_collapse));
+ collapse_button->connect(SceneStringName(toggled), callable_mp(this, &EditorLog::_set_collapse));
hb_tools2->add_child(collapse_button);
// Show Search.
@@ -521,7 +521,7 @@ EditorLog::EditorLog() {
show_search_button->set_pressed(true);
show_search_button->set_shortcut(ED_SHORTCUT("editor/open_search", TTR("Focus Search/Filter Bar"), KeyModifierMask::CMD_OR_CTRL | Key::F));
show_search_button->set_shortcut_context(this);
- show_search_button->connect("toggled", callable_mp(this, &EditorLog::_set_search_visible));
+ show_search_button->connect(SceneStringName(toggled), callable_mp(this, &EditorLog::_set_search_visible));
hb_tools2->add_child(show_search_button);
// Message Type Filters.
diff --git a/editor/editor_log.h b/editor/editor_log.h
index 9c652e912a..899b4a9ac4 100644
--- a/editor/editor_log.h
+++ b/editor/editor_log.h
@@ -102,7 +102,7 @@ private:
toggle_button->add_theme_color_override("icon_color_pressed", Color(1, 1, 1, 1));
toggle_button->set_focus_mode(FOCUS_NONE);
// When toggled call the callback and pass the MessageType this button is for.
- toggle_button->connect("toggled", p_toggled_callback.bind(type));
+ toggle_button->connect(SceneStringName(toggled), p_toggled_callback.bind(type));
}
int get_message_count() {
diff --git a/editor/editor_node.cpp b/editor/editor_node.cpp
index b2504e1670..2b8d6e5ad1 100644
--- a/editor/editor_node.cpp
+++ b/editor/editor_node.cpp
@@ -175,7 +175,7 @@ static const String REMOVE_ANDROID_BUILD_TEMPLATE_MESSAGE = "The Android build t
static const String INSTALL_ANDROID_BUILD_TEMPLATE_MESSAGE = "This will set up your project for gradle Android builds by installing the source template to \"%s\".\nNote that in order to make gradle builds instead of using pre-built APKs, the \"Use Gradle Build\" option should be enabled in the Android export preset.";
bool EditorProgress::step(const String &p_state, int p_step, bool p_force_refresh) {
- if (Thread::is_main_thread()) {
+ if (!force_background && Thread::is_main_thread()) {
return EditorNode::progress_task_step(task, p_state, p_step, p_force_refresh);
} else {
EditorNode::progress_task_step_bg(task, p_step);
@@ -183,17 +183,18 @@ bool EditorProgress::step(const String &p_state, int p_step, bool p_force_refres
}
}
-EditorProgress::EditorProgress(const String &p_task, const String &p_label, int p_amount, bool p_can_cancel) {
- if (Thread::is_main_thread()) {
+EditorProgress::EditorProgress(const String &p_task, const String &p_label, int p_amount, bool p_can_cancel, bool p_force_background) {
+ if (!p_force_background && Thread::is_main_thread()) {
EditorNode::progress_add_task(p_task, p_label, p_amount, p_can_cancel);
} else {
EditorNode::progress_add_task_bg(p_task, p_label, p_amount);
}
task = p_task;
+ force_background = p_force_background;
}
EditorProgress::~EditorProgress() {
- if (Thread::is_main_thread()) {
+ if (!force_background && Thread::is_main_thread()) {
EditorNode::progress_end_task(task);
} else {
EditorNode::progress_end_task_bg(task);
@@ -481,6 +482,9 @@ void EditorNode::_gdextensions_reloaded() {
// In case the developer is inspecting an object that will be changed by the reload.
InspectorDock::get_inspector_singleton()->update_tree();
+ // Reload script editor to revalidate GDScript if classes are added or removed.
+ ScriptEditor::get_singleton()->reload_scripts(true);
+
// Regenerate documentation.
EditorHelp::generate_doc();
}
@@ -821,6 +825,7 @@ void EditorNode::_notification(int p_what) {
if (EditorSettings::get_singleton()->check_changed_settings_in_group("docks/filesystem")) {
HashSet<String> updated_textfile_extensions;
+ HashSet<String> updated_other_file_extensions;
bool extensions_match = true;
const Vector<String> textfile_ext = ((String)(EDITOR_GET("docks/filesystem/textfile_extensions"))).split(",", false);
for (const String &E : textfile_ext) {
@@ -829,9 +834,17 @@ void EditorNode::_notification(int p_what) {
extensions_match = false;
}
}
+ const Vector<String> other_file_ext = ((String)(EDITOR_GET("docks/filesystem/other_file_extensions"))).split(",", false);
+ for (const String &E : other_file_ext) {
+ updated_other_file_extensions.insert(E);
+ if (extensions_match && !other_file_extensions.has(E)) {
+ extensions_match = false;
+ }
+ }
- if (!extensions_match || updated_textfile_extensions.size() < textfile_extensions.size()) {
+ if (!extensions_match || updated_textfile_extensions.size() < textfile_extensions.size() || updated_other_file_extensions.size() < other_file_extensions.size()) {
textfile_extensions = updated_textfile_extensions;
+ other_file_extensions = updated_other_file_extensions;
EditorFileSystem::get_singleton()->scan();
}
}
@@ -1322,6 +1335,9 @@ Error EditorNode::load_resource(const String &p_resource, bool p_ignore_broken_d
res = ResourceLoader::load(p_resource, "", ResourceFormatLoader::CACHE_MODE_REUSE, &err);
} else if (textfile_extensions.has(p_resource.get_extension())) {
res = ScriptEditor::get_singleton()->open_file(p_resource);
+ } else if (other_file_extensions.has(p_resource.get_extension())) {
+ OS::get_singleton()->shell_open(ProjectSettings::get_singleton()->globalize_path(p_resource));
+ return OK;
}
ERR_FAIL_COND_V(!res.is_valid(), ERR_CANT_OPEN);
@@ -2961,7 +2977,7 @@ void EditorNode::_menu_option_confirm(int p_option, bool p_confirmed) {
ERR_PRINT("Failed to load scene");
}
editor_data.move_edited_scene_to_index(cur_idx);
- EditorUndoRedoManager::get_singleton()->clear_history(false, editor_data.get_current_edited_scene_history_id());
+ EditorUndoRedoManager::get_singleton()->clear_history(editor_data.get_current_edited_scene_history_id(), false);
scene_tabs->set_current_tab(cur_idx);
} break;
@@ -3963,7 +3979,7 @@ void EditorNode::_set_current_scene_nocheck(int p_idx) {
editor_folding.load_scene_folding(editor_data.get_edited_scene_root(p_idx), editor_data.get_scene_path(p_idx));
}
- EditorUndoRedoManager::get_singleton()->clear_history(false, editor_data.get_scene_history_id(p_idx));
+ EditorUndoRedoManager::get_singleton()->clear_history(editor_data.get_scene_history_id(p_idx), false);
}
Dictionary state = editor_data.restore_edited_scene_state(editor_selection, &editor_history);
@@ -4073,7 +4089,7 @@ Error EditorNode::load_scene(const String &p_scene, bool p_ignore_broken_deps, b
_set_current_scene(idx);
}
} else {
- EditorUndoRedoManager::get_singleton()->clear_history(false, editor_data.get_current_edited_scene_history_id());
+ EditorUndoRedoManager::get_singleton()->clear_history(editor_data.get_current_edited_scene_history_id(), false);
}
dependency_errors.clear();
@@ -4388,6 +4404,21 @@ bool EditorNode::is_additional_node_in_scene(Node *p_edited_scene, Node *p_reimp
return true;
}
+void EditorNode::get_scene_editor_data_for_node(Node *p_root, Node *p_node, HashMap<NodePath, SceneEditorDataEntry> &p_table) {
+ SceneEditorDataEntry new_entry;
+ new_entry.is_display_folded = p_node->is_displayed_folded();
+
+ if (p_root != p_node) {
+ new_entry.is_editable = p_root->is_editable_instance(p_node);
+ }
+
+ p_table.insert(p_root->get_path_to(p_node), new_entry);
+
+ for (int i = 0; i < p_node->get_child_count(); i++) {
+ get_scene_editor_data_for_node(p_root, p_node->get_child(i), p_table);
+ }
+}
+
void EditorNode::get_preload_scene_modification_table(
Node *p_edited_scene,
Node *p_reimported_root,
@@ -4494,7 +4525,7 @@ void EditorNode::get_preload_scene_modification_table(
void EditorNode::get_preload_modifications_reference_to_nodes(
Node *p_root,
Node *p_node,
- List<Node *> &p_excluded_nodes,
+ HashSet<Node *> &p_excluded_nodes,
List<Node *> &p_instance_list_with_children,
HashMap<NodePath, ModificationNodeEntry> &p_modification_table) {
if (!p_excluded_nodes.find(p_node)) {
@@ -4949,7 +4980,9 @@ bool EditorNode::is_object_of_custom_type(const Object *p_object, const StringNa
}
void EditorNode::progress_add_task(const String &p_task, const String &p_label, int p_steps, bool p_can_cancel) {
- if (singleton->cmdline_export_mode) {
+ if (!singleton) {
+ return;
+ } else if (singleton->cmdline_export_mode) {
print_line(p_task + ": begin: " + p_label + " steps: " + itos(p_steps));
} else if (singleton->progress_dialog) {
singleton->progress_dialog->add_task(p_task, p_label, p_steps, p_can_cancel);
@@ -4957,7 +4990,9 @@ void EditorNode::progress_add_task(const String &p_task, const String &p_label,
}
bool EditorNode::progress_task_step(const String &p_task, const String &p_state, int p_step, bool p_force_refresh) {
- if (singleton->cmdline_export_mode) {
+ if (!singleton) {
+ return false;
+ } else if (singleton->cmdline_export_mode) {
print_line("\t" + p_task + ": step " + itos(p_step) + ": " + p_state);
return false;
} else if (singleton->progress_dialog) {
@@ -4968,7 +5003,9 @@ bool EditorNode::progress_task_step(const String &p_task, const String &p_state,
}
void EditorNode::progress_end_task(const String &p_task) {
- if (singleton->cmdline_export_mode) {
+ if (!singleton) {
+ return;
+ } else if (singleton->cmdline_export_mode) {
print_line(p_task + ": end");
} else if (singleton->progress_dialog) {
singleton->progress_dialog->end_task(p_task);
@@ -5007,8 +5044,8 @@ String EditorNode::_get_system_info() const {
#ifdef LINUXBSD_ENABLED
const String display_server = OS::get_singleton()->get_environment("XDG_SESSION_TYPE").capitalize().replace(" ", ""); // `replace` is necessary, because `capitalize` introduces a whitespace between "x" and "11".
#endif // LINUXBSD_ENABLED
- String driver_name = GLOBAL_GET("rendering/rendering_device/driver");
- String rendering_method = GLOBAL_GET("rendering/renderer/rendering_method");
+ String driver_name = OS::get_singleton()->get_current_rendering_driver_name().to_lower();
+ String rendering_method = OS::get_singleton()->get_current_rendering_method().to_lower();
const String rendering_device_name = RenderingServer::get_singleton()->get_video_adapter_name();
@@ -5044,12 +5081,21 @@ String EditorNode::_get_system_info() const {
rendering_method = "Mobile";
} else if (rendering_method == "gl_compatibility") {
rendering_method = "Compatibility";
- driver_name = GLOBAL_GET("rendering/gl_compatibility/driver");
}
if (driver_name == "vulkan") {
driver_name = "Vulkan";
- } else if (driver_name.begins_with("opengl3")) {
- driver_name = "GLES3";
+ } else if (driver_name == "d3d12") {
+ driver_name = "Direct3D 12";
+ } else if (driver_name == "opengl3_angle") {
+ driver_name = "OpenGL ES 3/ANGLE";
+ } else if (driver_name == "opengl3_es") {
+ driver_name = "OpenGL ES 3";
+ } else if (driver_name == "opengl3") {
+ if (OS::get_singleton()->get_gles_over_gl()) {
+ driver_name = "OpenGL 3";
+ } else {
+ driver_name = "OpenGL ES 3";
+ }
} else if (driver_name == "metal") {
driver_name = "Metal";
}
@@ -5221,8 +5267,8 @@ void EditorNode::_copy_warning(const String &p_str) {
}
void EditorNode::_save_editor_layout() {
- if (waiting_for_first_scan) {
- return; // Scanning, do not touch docks.
+ if (!load_editor_layout_done) {
+ return;
}
Ref<ConfigFile> config;
config.instantiate();
@@ -5278,22 +5324,22 @@ void EditorNode::_load_editor_layout() {
if (overridden_default_layout >= 0) {
_layout_menu_option(overridden_default_layout);
}
- return;
- }
-
- ep.step(TTR("Loading docks..."), 1, true);
- editor_dock_manager->load_docks_from_config(config, "docks");
+ } else {
+ ep.step(TTR("Loading docks..."), 1, true);
+ editor_dock_manager->load_docks_from_config(config, "docks");
- ep.step(TTR("Reopening scenes..."), 2, true);
- _load_open_scenes_from_config(config);
+ ep.step(TTR("Reopening scenes..."), 2, true);
+ _load_open_scenes_from_config(config);
- ep.step(TTR("Loading central editor layout..."), 3, true);
- _load_central_editor_layout_from_config(config);
+ ep.step(TTR("Loading central editor layout..."), 3, true);
+ _load_central_editor_layout_from_config(config);
- ep.step(TTR("Loading plugin window layout..."), 4, true);
- editor_data.set_plugin_window_layout(config);
+ ep.step(TTR("Loading plugin window layout..."), 4, true);
+ editor_data.set_plugin_window_layout(config);
- ep.step(TTR("Editor layout ready."), 5, true);
+ ep.step(TTR("Editor layout ready."), 5, true);
+ }
+ load_editor_layout_done = true;
}
void EditorNode::_save_central_editor_layout_to_config(Ref<ConfigFile> p_config_file) {
@@ -5928,7 +5974,7 @@ void EditorNode::reload_scene(const String &p_path) {
bool is_unsaved = EditorUndoRedoManager::get_singleton()->is_history_unsaved(current_history_id);
// Scene is not open, so at it might be instantiated. We'll refresh the whole scene later.
- EditorUndoRedoManager::get_singleton()->clear_history(false, current_history_id);
+ EditorUndoRedoManager::get_singleton()->clear_history(current_history_id, false);
if (is_unsaved) {
EditorUndoRedoManager::get_singleton()->set_history_as_unsaved(current_history_id);
}
@@ -5947,18 +5993,20 @@ void EditorNode::reload_scene(const String &p_path) {
// Adjust index so tab is back a the previous position.
editor_data.move_edited_scene_to_index(scene_idx);
- EditorUndoRedoManager::get_singleton()->clear_history(false, editor_data.get_scene_history_id(scene_idx));
+ EditorUndoRedoManager::get_singleton()->clear_history(editor_data.get_scene_history_id(scene_idx), false);
// Recover the tab.
scene_tabs->set_current_tab(current_tab);
}
-void EditorNode::find_all_instances_inheriting_path_in_node(Node *p_root, Node *p_node, const String &p_instance_path, List<Node *> &p_instance_list) {
+void EditorNode::find_all_instances_inheriting_path_in_node(Node *p_root, Node *p_node, const String &p_instance_path, HashSet<Node *> &p_instance_list) {
String scene_file_path = p_node->get_scene_file_path();
- // This is going to get messy...
+ bool valid_instance_found = false;
+
+ // Attempt to find all the instances matching path we're going to reload.
if (p_node->get_scene_file_path() == p_instance_path) {
- p_instance_list.push_back(p_node);
+ valid_instance_found = true;
} else {
Node *current_node = p_node;
@@ -5966,7 +6014,7 @@ void EditorNode::find_all_instances_inheriting_path_in_node(Node *p_root, Node *
while (inherited_state.is_valid()) {
String inherited_path = inherited_state->get_path();
if (inherited_path == p_instance_path) {
- p_instance_list.push_back(p_node);
+ valid_instance_found = true;
break;
}
@@ -5974,6 +6022,19 @@ void EditorNode::find_all_instances_inheriting_path_in_node(Node *p_root, Node *
}
}
+ // Instead of adding this instance directly, if its not owned by the scene, walk its ancestors
+ // and find the first node still owned by the scene. This is what we will reloading instead.
+ if (valid_instance_found) {
+ Node *current_node = p_node;
+ while (true) {
+ if (current_node->get_owner() == p_root || current_node->get_owner() == nullptr) {
+ p_instance_list.insert(current_node);
+ break;
+ }
+ current_node = current_node->get_parent();
+ }
+ }
+
for (int i = 0; i < p_node->get_child_count(); i++) {
find_all_instances_inheriting_path_in_node(p_root, p_node->get_child(i), p_instance_path, p_instance_list);
}
@@ -5992,20 +6053,20 @@ void EditorNode::preload_reimporting_with_path_in_edited_scenes(const List<Strin
Node *edited_scene_root = editor_data.get_edited_scene_root(current_scene_idx);
if (edited_scene_root) {
- SceneModificationsEntry scene_motifications;
+ SceneModificationsEntry scene_modifications;
for (const String &instance_path : p_scenes) {
if (editor_data.get_scene_path(current_scene_idx) == instance_path) {
continue;
}
- List<Node *> instance_list;
- find_all_instances_inheriting_path_in_node(edited_scene_root, edited_scene_root, instance_path, instance_list);
- if (instance_list.size() > 0) {
+ HashSet<Node *> instances_to_reimport;
+ find_all_instances_inheriting_path_in_node(edited_scene_root, edited_scene_root, instance_path, instances_to_reimport);
+ if (instances_to_reimport.size() > 0) {
editor_data.set_edited_scene(current_scene_idx);
List<Node *> instance_list_with_children;
- for (Node *original_node : instance_list) {
+ for (Node *original_node : instances_to_reimport) {
InstanceModificationsEntry instance_modifications;
// Fetching all the modified properties of the nodes reimported scene.
@@ -6013,19 +6074,19 @@ void EditorNode::preload_reimporting_with_path_in_edited_scenes(const List<Strin
instance_modifications.original_node = original_node;
instance_modifications.instance_path = instance_path;
- scene_motifications.instance_list.push_back(instance_modifications);
+ scene_modifications.instance_list.push_back(instance_modifications);
instance_list_with_children.push_back(original_node);
get_children_nodes(original_node, instance_list_with_children);
}
// Search the scene to find nodes that references the nodes will be recreated.
- get_preload_modifications_reference_to_nodes(edited_scene_root, edited_scene_root, instance_list, instance_list_with_children, scene_motifications.other_instances_modifications);
+ get_preload_modifications_reference_to_nodes(edited_scene_root, edited_scene_root, instances_to_reimport, instance_list_with_children, scene_modifications.other_instances_modifications);
}
}
- if (scene_motifications.instance_list.size() > 0) {
- scenes_modification_table[current_scene_idx] = scene_motifications;
+ if (scene_modifications.instance_list.size() > 0) {
+ scenes_modification_table[current_scene_idx] = scene_modifications;
}
}
}
@@ -6092,7 +6153,7 @@ void EditorNode::reload_instances_with_path_in_edited_scenes() {
bool is_unsaved = EditorUndoRedoManager::get_singleton()->is_history_unsaved(current_history_id);
// Clear the history for this affected tab.
- EditorUndoRedoManager::get_singleton()->clear_history(false, current_history_id);
+ EditorUndoRedoManager::get_singleton()->clear_history(current_history_id, false);
// Update the version
editor_data.is_scene_changed(current_scene_idx);
@@ -6135,7 +6196,7 @@ void EditorNode::reload_instances_with_path_in_edited_scenes() {
base_packed_scene = current_packed_scene;
}
if (!local_scene_cache.find(path)) {
- current_packed_scene = ResourceLoader::load(path, "", ResourceFormatLoader::CACHE_MODE_REPLACE_DEEP, &err);
+ current_packed_scene = ResourceLoader::load(path, "", ResourceFormatLoader::CACHE_MODE_REPLACE, &err);
local_scene_cache[path] = current_packed_scene;
} else {
current_packed_scene = local_scene_cache[path];
@@ -6148,10 +6209,10 @@ void EditorNode::reload_instances_with_path_in_edited_scenes() {
// Instantiate early so that caches cleared on load in SceneState can be rebuilt early.
Node *instantiated_node = nullptr;
- // If we are in a inherit scene, it's easier to create a new base scene and
+ // If we are in a inherited scene, it's easier to create a new base scene and
// grab the node from there.
// When scene_path_to_node is '.' and we have scene_inherited_state, it's because
- // it's a muli-level inheritance scene. We should use
+ // it's a multi-level inheritance scene. We should use
NodePath scene_path_to_node = current_edited_scene->get_path_to(original_node);
Ref<SceneState> scene_state = current_edited_scene->get_scene_inherited_state();
if (scene_path_to_node != "." && scene_state.is_valid() && scene_state->get_path() != instance_modifications.instance_path && scene_state->find_node_by_path(scene_path_to_node) >= 0) {
@@ -6175,9 +6236,9 @@ void EditorNode::reload_instances_with_path_in_edited_scenes() {
if (!instantiated_node) {
// If no base scene was found to create the node, we will use the reimported packed scene directly.
- // But, when the current edited scene is the reimported scene, it's because it's a inherited scene
- // of the reimported scene. In that case, we will not instantiate current_packed_scene, because
- // we would reinstanciate ourself. Using the base scene is better.
+ // But, when the current edited scene is the reimported scene, it's because it's an inherited scene
+ // derived from the reimported scene. In that case, we will not instantiate current_packed_scene, because
+ // we would reinstantiate ourself. Using the base scene is better.
if (current_edited_scene == original_node) {
if (base_packed_scene.is_valid()) {
instantiated_node = base_packed_scene->instantiate(PackedScene::GEN_EDIT_STATE_INSTANCE);
@@ -6233,6 +6294,17 @@ void EditorNode::reload_instances_with_path_in_edited_scenes() {
// crash when reimporting scenes with animations when "Editable children" was enabled.
replace_history_reimported_nodes(original_node, instantiated_node, original_node);
+ // Reset the editable instance state.
+ HashMap<NodePath, SceneEditorDataEntry> scene_editor_data_table;
+ Node *owner = original_node->get_owner();
+ if (!owner) {
+ owner = original_node;
+ }
+
+ get_scene_editor_data_for_node(owner, original_node, scene_editor_data_table);
+
+ bool original_node_scene_instance_load_placeholder = original_node->get_scene_instance_load_placeholder();
+
// Delete all the remaining node children.
while (original_node->get_child_count()) {
Node *child = original_node->get_child(0);
@@ -6241,16 +6313,6 @@ void EditorNode::reload_instances_with_path_in_edited_scenes() {
child->queue_free();
}
- // Reset the editable instance state.
- bool is_editable = true;
- Node *owner = original_node->get_owner();
- if (owner) {
- is_editable = owner->is_editable_instance(original_node);
- }
-
- bool original_node_is_displayed_folded = original_node->is_displayed_folded();
- bool original_node_scene_instance_load_placeholder = original_node->get_scene_instance_load_placeholder();
-
// Update the name to match
instantiated_node->set_name(original_node->get_name());
@@ -6281,19 +6343,9 @@ void EditorNode::reload_instances_with_path_in_edited_scenes() {
// Mark the old node for deletion.
original_node->queue_free();
- // Restore the folded and placeholder state from the original node.
- instantiated_node->set_display_folded(original_node_is_displayed_folded);
+ // Restore the placeholder state from the original node.
instantiated_node->set_scene_instance_load_placeholder(original_node_scene_instance_load_placeholder);
- if (owner) {
- Ref<SceneState> ss_inst = owner->get_scene_instance_state();
- if (ss_inst.is_valid()) {
- ss_inst->update_instance_resource(instance_modifications.instance_path, current_packed_scene);
- }
-
- owner->set_editable_instance(instantiated_node, is_editable);
- }
-
// Attempt to re-add all the additional nodes.
for (AdditiveNodeEntry additive_node_entry : instance_modifications.addition_list) {
Node *parent_node = instantiated_node->get_node_or_null(additive_node_entry.parent);
@@ -6325,6 +6377,17 @@ void EditorNode::reload_instances_with_path_in_edited_scenes() {
}
}
+ // Restore the scene's editable instance and folded states.
+ for (HashMap<NodePath, SceneEditorDataEntry>::Iterator I = scene_editor_data_table.begin(); I; ++I) {
+ Node *node = owner->get_node_or_null(I->key);
+ if (node) {
+ if (owner != node) {
+ owner->set_editable_instance(node, I->value.is_editable);
+ }
+ node->set_display_folded(I->value.is_display_folded);
+ }
+ }
+
// Restore the selection.
if (selected_node_paths.size()) {
for (NodePath selected_node_path : selected_node_paths) {
@@ -6888,10 +6951,10 @@ EditorNode::EditorNode() {
import_shader_file.instantiate();
ResourceFormatImporter::get_singleton()->add_importer(import_shader_file);
- Ref<ResourceImporterScene> import_scene = memnew(ResourceImporterScene(false, true));
+ Ref<ResourceImporterScene> import_scene = memnew(ResourceImporterScene("PackedScene", true));
ResourceFormatImporter::get_singleton()->add_importer(import_scene);
- Ref<ResourceImporterScene> import_animation = memnew(ResourceImporterScene(true, true));
+ Ref<ResourceImporterScene> import_animation = memnew(ResourceImporterScene("AnimationLibrary", true));
ResourceFormatImporter::get_singleton()->add_importer(import_animation);
{
@@ -6961,6 +7024,10 @@ EditorNode::EditorNode() {
for (const String &E : textfile_ext) {
textfile_extensions.insert(E);
}
+ const Vector<String> other_file_ext = ((String)(EDITOR_GET("docks/filesystem/other_file_extensions"))).split(",", false);
+ for (const String &E : other_file_ext) {
+ other_file_extensions.insert(E);
+ }
resource_preview = memnew(EditorResourcePreview);
add_child(resource_preview);
@@ -7141,6 +7208,7 @@ EditorNode::EditorNode() {
}
main_menu = memnew(MenuBar);
+ main_menu->set_mouse_filter(Control::MOUSE_FILTER_STOP);
title_bar->add_child(main_menu);
main_menu->set_theme_type_variation("MainMenuBar");
main_menu->set_start_index(0); // Main menu, add to the start of global menu.
@@ -7323,6 +7391,7 @@ EditorNode::EditorNode() {
}
main_editor_button_hb = memnew(HBoxContainer);
+ main_editor_button_hb->set_mouse_filter(Control::MOUSE_FILTER_STOP);
title_bar->add_child(main_editor_button_hb);
// Options are added and handled by DebuggerEditorPlugin.
@@ -7358,11 +7427,9 @@ EditorNode::EditorNode() {
settings_menu->set_item_tooltip(-1, TTR("Screenshots are stored in the user data folder (\"user://\")."));
-#ifndef ANDROID_ENABLED
ED_SHORTCUT_AND_COMMAND("editor/fullscreen_mode", TTR("Toggle Fullscreen"), KeyModifierMask::SHIFT | Key::F11);
ED_SHORTCUT_OVERRIDE("editor/fullscreen_mode", "macos", KeyModifierMask::META | KeyModifierMask::CTRL | Key::F);
settings_menu->add_shortcut(ED_GET_SHORTCUT("editor/fullscreen_mode"), SETTINGS_TOGGLE_FULLSCREEN);
-#endif
settings_menu->add_separator();
#ifndef ANDROID_ENABLED
@@ -7419,11 +7486,13 @@ EditorNode::EditorNode() {
title_bar->add_child(right_spacer);
project_run_bar = memnew(EditorRunBar);
+ project_run_bar->set_mouse_filter(Control::MOUSE_FILTER_STOP);
title_bar->add_child(project_run_bar);
project_run_bar->connect("play_pressed", callable_mp(this, &EditorNode::_project_run_started));
project_run_bar->connect("stop_pressed", callable_mp(this, &EditorNode::_project_run_stopped));
HBoxContainer *right_menu_hb = memnew(HBoxContainer);
+ right_menu_hb->set_mouse_filter(Control::MOUSE_FILTER_STOP);
title_bar->add_child(right_menu_hb);
renderer = memnew(OptionButton);
@@ -7897,6 +7966,9 @@ EditorNode::EditorNode() {
ED_SHORTCUT_AND_COMMAND("editor/editor_next", TTR("Open the next Editor"));
ED_SHORTCUT_AND_COMMAND("editor/editor_prev", TTR("Open the previous Editor"));
+ // Apply setting presets in case the editor_settings file is missing values.
+ EditorSettingsDialog::update_navigation_preset();
+
screenshot_timer = memnew(Timer);
screenshot_timer->set_one_shot(true);
screenshot_timer->set_wait_time(settings_menu->get_submenu_popup_delay() + 0.1f);
diff --git a/editor/editor_node.h b/editor/editor_node.h
index bdf9b26a7a..f1b50cf3fa 100644
--- a/editor/editor_node.h
+++ b/editor/editor_node.h
@@ -124,9 +124,10 @@ class WindowWrapper;
struct EditorProgress {
String task;
+ bool force_background = false;
bool step(const String &p_state, int p_step = -1, bool p_force_refresh = true);
- EditorProgress(const String &p_task, const String &p_label, int p_amount, bool p_can_cancel = false);
+ EditorProgress(const String &p_task, const String &p_label, int p_amount, bool p_can_cancel = false, bool p_force_background = false);
~EditorProgress();
};
@@ -461,6 +462,7 @@ private:
bool requested_first_scan = false;
bool waiting_for_first_scan = true;
+ bool load_editor_layout_done = false;
int current_menu_option = 0;
@@ -487,6 +489,7 @@ private:
String import_reload_fn;
HashSet<String> textfile_extensions;
+ HashSet<String> other_file_extensions;
HashSet<FileDialog *> file_dialogs;
HashSet<EditorFileDialog *> editor_file_dialogs;
@@ -849,12 +852,19 @@ public:
HashMap<NodePath, ModificationNodeEntry> other_instances_modifications;
};
+ struct SceneEditorDataEntry {
+ bool is_editable;
+ bool is_display_folded;
+ };
+
HashMap<int, SceneModificationsEntry> scenes_modification_table;
List<String> scenes_reimported;
List<String> resources_reimported;
void update_node_from_node_modification_entry(Node *p_node, ModificationNodeEntry &p_node_modification);
+ void get_scene_editor_data_for_node(Node *p_root, Node *p_node, HashMap<NodePath, SceneEditorDataEntry> &p_table);
+
void get_preload_scene_modification_table(
Node *p_edited_scene,
Node *p_reimported_root,
@@ -863,7 +873,7 @@ public:
void get_preload_modifications_reference_to_nodes(
Node *p_root,
Node *p_node,
- List<Node *> &p_excluded_nodes,
+ HashSet<Node *> &p_excluded_nodes,
List<Node *> &p_instance_list_with_children,
HashMap<NodePath, ModificationNodeEntry> &p_modification_table);
void get_children_nodes(Node *p_node, List<Node *> &p_nodes);
@@ -924,7 +934,7 @@ public:
void reload_scene(const String &p_path);
- void find_all_instances_inheriting_path_in_node(Node *p_root, Node *p_node, const String &p_instance_path, List<Node *> &p_instance_list);
+ void find_all_instances_inheriting_path_in_node(Node *p_root, Node *p_node, const String &p_instance_path, HashSet<Node *> &p_instance_list);
void preload_reimporting_with_path_in_edited_scenes(const List<String> &p_scenes);
void reload_instances_with_path_in_edited_scenes();
diff --git a/editor/editor_properties.cpp b/editor/editor_properties.cpp
index 19a4165041..123d903220 100644
--- a/editor/editor_properties.cpp
+++ b/editor/editor_properties.cpp
@@ -3771,7 +3771,7 @@ EditorProperty *EditorInspectorDefaultPlugin::get_editor_for_property(Object *p_
return editor;
} else {
EditorPropertyDictionary *editor = memnew(EditorPropertyDictionary);
- editor->setup(p_hint);
+ editor->setup(p_hint, p_hint_text);
return editor;
}
} break;
diff --git a/editor/editor_properties_array_dict.cpp b/editor/editor_properties_array_dict.cpp
index d58d0520cc..f03eef4d4d 100644
--- a/editor/editor_properties_array_dict.cpp
+++ b/editor/editor_properties_array_dict.cpp
@@ -644,6 +644,8 @@ void EditorPropertyArray::_notification(int p_what) {
case NOTIFICATION_THEME_CHANGED:
case NOTIFICATION_ENTER_TREE: {
change_type->clear();
+ change_type->add_icon_item(get_editor_theme_icon(SNAME("Remove")), TTR("Remove Item"), Variant::VARIANT_MAX);
+ change_type->add_separator();
for (int i = 0; i < Variant::VARIANT_MAX; i++) {
if (i == Variant::CALLABLE || i == Variant::SIGNAL || i == Variant::RID) {
// These types can't be constructed or serialized properly, so skip them.
@@ -653,8 +655,6 @@ void EditorPropertyArray::_notification(int p_what) {
String type = Variant::get_type_name(Variant::Type(i));
change_type->add_icon_item(get_editor_theme_icon(type), type, i);
}
- change_type->add_separator();
- change_type->add_icon_item(get_editor_theme_icon(SNAME("Remove")), TTR("Remove Item"), Variant::VARIANT_MAX);
if (button_add_item) {
button_add_item->set_icon(get_editor_theme_icon(SNAME("Add")));
@@ -863,6 +863,26 @@ EditorPropertyArray::EditorPropertyArray() {
///////////////////// DICTIONARY ///////////////////////////
+void EditorPropertyDictionary::initialize_dictionary(Variant &p_dictionary) {
+ if (key_subtype != Variant::NIL || value_subtype != Variant::NIL) {
+ Dictionary dict;
+ StringName key_subtype_class;
+ Ref<Script> key_subtype_script;
+ if (key_subtype == Variant::OBJECT && !key_subtype_hint_string.is_empty() && ClassDB::class_exists(key_subtype_hint_string)) {
+ key_subtype_class = key_subtype_hint_string;
+ }
+ StringName value_subtype_class;
+ Ref<Script> value_subtype_script;
+ if (value_subtype == Variant::OBJECT && !value_subtype_hint_string.is_empty() && ClassDB::class_exists(value_subtype_hint_string)) {
+ value_subtype_class = value_subtype_hint_string;
+ }
+ dict.set_typed(key_subtype, key_subtype_class, key_subtype_script, value_subtype, value_subtype_class, value_subtype_script);
+ p_dictionary = dict;
+ } else {
+ VariantInternal::initialize(&p_dictionary, Variant::DICTIONARY);
+ }
+}
+
void EditorPropertyDictionary::_property_changed(const String &p_property, Variant p_value, const String &p_name, bool p_changing) {
if (p_value.get_type() == Variant::OBJECT && p_value.is_null()) {
p_value = Variant(); // `EditorResourcePicker` resets to `Ref<Resource>()`. See GH-82716.
@@ -914,16 +934,29 @@ void EditorPropertyDictionary::_create_new_property_slot(int p_idx) {
EditorProperty *prop = memnew(EditorPropertyNil);
hbox->add_child(prop);
- Button *edit_btn = memnew(Button);
- edit_btn->set_icon(get_editor_theme_icon(SNAME("Edit")));
- edit_btn->set_disabled(is_read_only());
- edit_btn->connect(SceneStringName(pressed), callable_mp(this, &EditorPropertyDictionary::_change_type).bind(edit_btn, slots.size()));
- hbox->add_child(edit_btn);
+ bool use_key = p_idx == EditorPropertyDictionaryObject::NEW_KEY_INDEX;
+ bool is_untyped_dict = (use_key ? key_subtype : value_subtype) == Variant::NIL;
+
+ if (is_untyped_dict) {
+ Button *edit_btn = memnew(Button);
+ edit_btn->set_icon(get_editor_theme_icon(SNAME("Edit")));
+ edit_btn->set_disabled(is_read_only());
+ edit_btn->connect(SceneStringName(pressed), callable_mp(this, &EditorPropertyDictionary::_change_type).bind(edit_btn, slots.size()));
+ hbox->add_child(edit_btn);
+ } else if (p_idx >= 0) {
+ Button *remove_btn = memnew(Button);
+ remove_btn->set_icon(get_editor_theme_icon(SNAME("Remove")));
+ remove_btn->set_disabled(is_read_only());
+ remove_btn->connect(SceneStringName(pressed), callable_mp(this, &EditorPropertyDictionary::_remove_pressed).bind(slots.size()));
+ hbox->add_child(remove_btn);
+ }
+
if (add_panel) {
add_panel->get_child(0)->add_child(hbox);
} else {
property_vbox->add_child(hbox);
}
+
Slot slot;
slot.prop = prop;
slot.object = object;
@@ -969,15 +1002,70 @@ void EditorPropertyDictionary::_change_type_menu(int p_index) {
}
}
-void EditorPropertyDictionary::setup(PropertyHint p_hint) {
- property_hint = p_hint;
+void EditorPropertyDictionary::setup(PropertyHint p_hint, const String &p_hint_string) {
+ PackedStringArray types = p_hint_string.split(";");
+ if (types.size() > 0 && !types[0].is_empty()) {
+ String key = types[0];
+ int hint_key_subtype_separator = key.find(":");
+ if (hint_key_subtype_separator >= 0) {
+ String key_subtype_string = key.substr(0, hint_key_subtype_separator);
+ int slash_pos = key_subtype_string.find("/");
+ if (slash_pos >= 0) {
+ key_subtype_hint = PropertyHint(key_subtype_string.substr(slash_pos + 1, key_subtype_string.size() - slash_pos - 1).to_int());
+ key_subtype_string = key_subtype_string.substr(0, slash_pos);
+ }
+
+ key_subtype_hint_string = key.substr(hint_key_subtype_separator + 1, key.size() - hint_key_subtype_separator - 1);
+ key_subtype = Variant::Type(key_subtype_string.to_int());
+
+ Variant new_key = object->get_new_item_key();
+ VariantInternal::initialize(&new_key, key_subtype);
+ object->set_new_item_key(new_key);
+ }
+ }
+ if (types.size() > 1 && !types[1].is_empty()) {
+ String value = types[1];
+ int hint_value_subtype_separator = value.find(":");
+ if (hint_value_subtype_separator >= 0) {
+ String value_subtype_string = value.substr(0, hint_value_subtype_separator);
+ int slash_pos = value_subtype_string.find("/");
+ if (slash_pos >= 0) {
+ value_subtype_hint = PropertyHint(value_subtype_string.substr(slash_pos + 1, value_subtype_string.size() - slash_pos - 1).to_int());
+ value_subtype_string = value_subtype_string.substr(0, slash_pos);
+ }
+
+ value_subtype_hint_string = value.substr(hint_value_subtype_separator + 1, value.size() - hint_value_subtype_separator - 1);
+ value_subtype = Variant::Type(value_subtype_string.to_int());
+
+ Variant new_value = object->get_new_item_value();
+ VariantInternal::initialize(&new_value, value_subtype);
+ object->set_new_item_value(new_value);
+ }
+ }
}
void EditorPropertyDictionary::update_property() {
Variant updated_val = get_edited_property_value();
+ String dict_type_name = "Dictionary";
+ if (key_subtype != Variant::NIL || value_subtype != Variant::NIL) {
+ String key_subtype_name = "Variant";
+ if (key_subtype == Variant::OBJECT && (key_subtype_hint == PROPERTY_HINT_RESOURCE_TYPE || key_subtype_hint == PROPERTY_HINT_NODE_TYPE)) {
+ key_subtype_name = key_subtype_hint_string;
+ } else if (key_subtype != Variant::NIL) {
+ key_subtype_name = Variant::get_type_name(key_subtype);
+ }
+ String value_subtype_name = "Variant";
+ if (value_subtype == Variant::OBJECT && (value_subtype_hint == PROPERTY_HINT_RESOURCE_TYPE || value_subtype_hint == PROPERTY_HINT_NODE_TYPE)) {
+ value_subtype_name = value_subtype_hint_string;
+ } else if (value_subtype != Variant::NIL) {
+ value_subtype_name = Variant::get_type_name(value_subtype);
+ }
+ dict_type_name += vformat("[%s, %s]", key_subtype_name, value_subtype_name);
+ }
+
if (updated_val.get_type() != Variant::DICTIONARY) {
- edit->set_text(TTR("Dictionary (Nil)")); // This provides symmetry with the array property.
+ edit->set_text(vformat(TTR("(Nil) %s"), dict_type_name)); // This provides symmetry with the array property.
edit->set_pressed(false);
if (container) {
set_bottom_editor(nullptr);
@@ -993,7 +1081,7 @@ void EditorPropertyDictionary::update_property() {
Dictionary dict = updated_val;
object->set_dict(updated_val);
- edit->set_text(vformat(TTR("Dictionary (size %d)"), dict.size()));
+ edit->set_text(vformat(TTR("%s (size %d)"), dict_type_name, dict.size()));
bool unfolded = get_edited_object()->editor_is_section_unfolded(get_edited_property());
if (edit->is_pressed() != unfolded) {
@@ -1074,7 +1162,9 @@ void EditorPropertyDictionary::update_property() {
editor->setup("Object");
new_prop = editor;
} else {
- new_prop = EditorInspector::instantiate_property_editor(this, value_type, "", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NONE);
+ bool use_key = slot.index == EditorPropertyDictionaryObject::NEW_KEY_INDEX;
+ new_prop = EditorInspector::instantiate_property_editor(this, value_type, "", use_key ? key_subtype_hint : value_subtype_hint,
+ use_key ? key_subtype_hint_string : value_subtype_hint_string, PROPERTY_USAGE_NONE);
}
new_prop->set_selectable(false);
new_prop->set_use_folding(is_using_folding());
@@ -1108,6 +1198,13 @@ void EditorPropertyDictionary::update_property() {
}
}
+void EditorPropertyDictionary::_remove_pressed(int p_slot_index) {
+ Dictionary dict = object->get_dict().duplicate();
+ dict.erase(dict.get_key_at_index(p_slot_index));
+
+ emit_changed(get_edited_property(), dict);
+}
+
void EditorPropertyDictionary::_object_id_selected(const StringName &p_property, ObjectID p_id) {
emit_signal(SNAME("object_id_selected"), p_property, p_id);
}
@@ -1117,6 +1214,8 @@ void EditorPropertyDictionary::_notification(int p_what) {
case NOTIFICATION_THEME_CHANGED:
case NOTIFICATION_ENTER_TREE: {
change_type->clear();
+ change_type->add_icon_item(get_editor_theme_icon(SNAME("Remove")), TTR("Remove Item"), Variant::VARIANT_MAX);
+ change_type->add_separator();
for (int i = 0; i < Variant::VARIANT_MAX; i++) {
if (i == Variant::CALLABLE || i == Variant::SIGNAL || i == Variant::RID) {
// These types can't be constructed or serialized properly, so skip them.
@@ -1126,8 +1225,6 @@ void EditorPropertyDictionary::_notification(int p_what) {
String type = Variant::get_type_name(Variant::Type(i));
change_type->add_icon_item(get_editor_theme_icon(type), type, i);
}
- change_type->add_separator();
- change_type->add_icon_item(get_editor_theme_icon(SNAME("Remove")), TTR("Remove Item"), Variant::VARIANT_MAX);
if (button_add_item) {
button_add_item->set_icon(get_editor_theme_icon(SNAME("Add")));
@@ -1140,7 +1237,7 @@ void EditorPropertyDictionary::_notification(int p_what) {
void EditorPropertyDictionary::_edit_pressed() {
Variant prop_val = get_edited_property_value();
if (prop_val.get_type() == Variant::NIL && edit->is_pressed()) {
- VariantInternal::initialize(&prop_val, Variant::DICTIONARY);
+ initialize_dictionary(prop_val);
emit_changed(get_edited_property(), prop_val);
}
@@ -1187,6 +1284,14 @@ EditorPropertyDictionary::EditorPropertyDictionary() {
change_type->connect(SceneStringName(id_pressed), callable_mp(this, &EditorPropertyDictionary::_change_type_menu));
changing_type_index = -1;
has_borders = true;
+
+ key_subtype = Variant::NIL;
+ key_subtype_hint = PROPERTY_HINT_NONE;
+ key_subtype_hint_string = "";
+
+ value_subtype = Variant::NIL;
+ value_subtype_hint = PROPERTY_HINT_NONE;
+ value_subtype_hint_string = "";
}
///////////////////// LOCALIZABLE STRING ///////////////////////////
diff --git a/editor/editor_properties_array_dict.h b/editor/editor_properties_array_dict.h
index 024c04956f..84c3f975be 100644
--- a/editor/editor_properties_array_dict.h
+++ b/editor/editor_properties_array_dict.h
@@ -219,7 +219,6 @@ class EditorPropertyDictionary : public EditorProperty {
EditorSpinSlider *size_sliderv = nullptr;
Button *button_add_item = nullptr;
EditorPaginator *paginator = nullptr;
- PropertyHint property_hint;
LocalVector<Slot> slots;
void _create_new_property_slot(int p_idx);
@@ -231,12 +230,21 @@ class EditorPropertyDictionary : public EditorProperty {
void _add_key_value();
void _object_id_selected(const StringName &p_property, ObjectID p_id);
+ void _remove_pressed(int p_slot_index);
+
+ Variant::Type key_subtype;
+ PropertyHint key_subtype_hint;
+ String key_subtype_hint_string;
+ Variant::Type value_subtype;
+ PropertyHint value_subtype_hint;
+ String value_subtype_hint_string;
+ void initialize_dictionary(Variant &p_dictionary);
protected:
void _notification(int p_what);
public:
- void setup(PropertyHint p_hint);
+ void setup(PropertyHint p_hint, const String &p_hint_string = "");
virtual void update_property() override;
virtual bool is_colored(ColorationMode p_mode) override;
EditorPropertyDictionary();
diff --git a/editor/editor_properties_vector.cpp b/editor/editor_properties_vector.cpp
index 365cbc6ec8..9ff8bf674d 100644
--- a/editor/editor_properties_vector.cpp
+++ b/editor/editor_properties_vector.cpp
@@ -130,9 +130,11 @@ void EditorPropertyVectorN::_notification(int p_what) {
switch (p_what) {
case NOTIFICATION_READY: {
if (linked->is_visible()) {
- const String key = vformat("%s:%s", get_edited_object()->get_class(), get_edited_property());
- linked->set_pressed_no_signal(EditorSettings::get_singleton()->get_project_metadata("linked_properties", key, true));
- _update_ratio();
+ if (get_edited_object()) {
+ const String key = vformat("%s:%s", get_edited_object()->get_class(), get_edited_property());
+ linked->set_pressed_no_signal(EditorSettings::get_singleton()->get_project_metadata("linked_properties", key, true));
+ _update_ratio();
+ }
}
} break;
@@ -236,7 +238,7 @@ EditorPropertyVectorN::EditorPropertyVectorN(Variant::Type p_type, bool p_force_
linked->set_stretch_mode(TextureButton::STRETCH_KEEP_CENTERED);
linked->set_tooltip_text(TTR("Lock/Unlock Component Ratio"));
linked->connect(SceneStringName(pressed), callable_mp(this, &EditorPropertyVectorN::_update_ratio));
- linked->connect(SNAME("toggled"), callable_mp(this, &EditorPropertyVectorN::_store_link));
+ linked->connect(SceneStringName(toggled), callable_mp(this, &EditorPropertyVectorN::_store_link));
hb->add_child(linked);
add_child(hb);
diff --git a/editor/editor_resource_picker.cpp b/editor/editor_resource_picker.cpp
index 8935b9ad8a..f20dd992bb 100644
--- a/editor/editor_resource_picker.cpp
+++ b/editor/editor_resource_picker.cpp
@@ -175,6 +175,13 @@ void EditorResourcePicker::_file_quick_selected() {
_file_selected(quick_open->get_selected());
}
+void EditorResourcePicker::_resource_saved(Object *p_resource) {
+ if (edited_resource.is_valid() && p_resource == edited_resource.ptr()) {
+ emit_signal(SNAME("resource_changed"), edited_resource);
+ _update_resource();
+ }
+}
+
void EditorResourcePicker::_update_menu() {
_update_menu_items();
@@ -408,6 +415,10 @@ void EditorResourcePicker::_edit_menu_cbk(int p_which) {
if (edited_resource.is_null()) {
return;
}
+ Callable resource_saved = callable_mp(this, &EditorResourcePicker::_resource_saved);
+ if (!EditorNode::get_singleton()->is_connected("resource_saved", resource_saved)) {
+ EditorNode::get_singleton()->connect("resource_saved", resource_saved);
+ }
EditorNode::get_singleton()->save_resource_as(edited_resource);
} break;
@@ -833,6 +844,13 @@ void EditorResourcePicker::_notification(int p_what) {
assign_button->queue_redraw();
}
} break;
+
+ case NOTIFICATION_EXIT_TREE: {
+ Callable resource_saved = callable_mp(this, &EditorResourcePicker::_resource_saved);
+ if (EditorNode::get_singleton()->is_connected("resource_saved", resource_saved)) {
+ EditorNode::get_singleton()->disconnect("resource_saved", resource_saved);
+ }
+ } break;
}
}
diff --git a/editor/editor_resource_picker.h b/editor/editor_resource_picker.h
index 28229e6b37..05e392da2c 100644
--- a/editor/editor_resource_picker.h
+++ b/editor/editor_resource_picker.h
@@ -91,6 +91,8 @@ class EditorResourcePicker : public HBoxContainer {
void _file_quick_selected();
void _file_selected(const String &p_path);
+ void _resource_saved(Object *p_resource);
+
void _update_menu();
void _update_menu_items();
void _edit_menu_cbk(int p_which);
diff --git a/editor/editor_resource_preview.cpp b/editor/editor_resource_preview.cpp
index 71865f7e8c..956fdc5cfa 100644
--- a/editor/editor_resource_preview.cpp
+++ b/editor/editor_resource_preview.cpp
@@ -221,7 +221,9 @@ void EditorResourcePreview::_generate_preview(Ref<ImageTexture> &r_texture, Ref<
r_small_texture->set_image(small_image);
}
- break;
+ if (generated.is_valid()) {
+ break;
+ }
}
if (!p_item.resource.is_valid()) {
diff --git a/editor/editor_run_native.cpp b/editor/editor_run_native.cpp
index 5d378820ae..e0e1ef6d19 100644
--- a/editor/editor_run_native.cpp
+++ b/editor/editor_run_native.cpp
@@ -141,7 +141,7 @@ Error EditorRunNative::start_run_native(int p_id) {
emit_signal(SNAME("native_run"), preset);
- int flags = 0;
+ BitField<EditorExportPlatform::DebugFlags> flags = 0;
bool deploy_debug_remote = is_deploy_debug_remote_enabled();
bool deploy_dumb = EditorSettings::get_singleton()->get_project_metadata("debug_options", "run_file_server", false);
@@ -149,16 +149,16 @@ Error EditorRunNative::start_run_native(int p_id) {
bool debug_navigation = EditorSettings::get_singleton()->get_project_metadata("debug_options", "run_debug_navigation", false);
if (deploy_debug_remote) {
- flags |= EditorExportPlatform::DEBUG_FLAG_REMOTE_DEBUG;
+ flags.set_flag(EditorExportPlatform::DEBUG_FLAG_REMOTE_DEBUG);
}
if (deploy_dumb) {
- flags |= EditorExportPlatform::DEBUG_FLAG_DUMB_CLIENT;
+ flags.set_flag(EditorExportPlatform::DEBUG_FLAG_DUMB_CLIENT);
}
if (debug_collisions) {
- flags |= EditorExportPlatform::DEBUG_FLAG_VIEW_COLLISIONS;
+ flags.set_flag(EditorExportPlatform::DEBUG_FLAG_VIEW_COLLISIONS);
}
if (debug_navigation) {
- flags |= EditorExportPlatform::DEBUG_FLAG_VIEW_NAVIGATION;
+ flags.set_flag(EditorExportPlatform::DEBUG_FLAG_VIEW_NAVIGATION);
}
eep->clear_messages();
diff --git a/editor/editor_settings.cpp b/editor/editor_settings.cpp
index fe2fad0cb6..b1a91fa3dd 100644
--- a/editor/editor_settings.cpp
+++ b/editor/editor_settings.cpp
@@ -593,6 +593,7 @@ void EditorSettings::_load_defaults(Ref<ConfigFile> p_extra_config) {
EDITOR_SETTING(Variant::INT, PROPERTY_HINT_RANGE, "docks/filesystem/thumbnail_size", 64, "32,128,16")
_initial_set("docks/filesystem/always_show_folders", true);
_initial_set("docks/filesystem/textfile_extensions", "txt,md,cfg,ini,log,json,yml,yaml,toml,xml");
+ _initial_set("docks/filesystem/other_file_extensions", "ico,icns");
// Property editor
EDITOR_SETTING(Variant::FLOAT, PROPERTY_HINT_RANGE, "docks/property_editor/auto_refresh_interval", 0.2, "0.01,1,0.001"); // Update 5 times per second by default.
@@ -731,14 +732,14 @@ void EditorSettings::_load_defaults(Ref<ConfigFile> p_extra_config) {
// 3D: Navigation
_initial_set("editors/3d/navigation/invert_x_axis", false);
_initial_set("editors/3d/navigation/invert_y_axis", false);
- EDITOR_SETTING(Variant::INT, PROPERTY_HINT_ENUM, "editors/3d/navigation/navigation_scheme", 0, "Godot,Maya,Modo")
+ EDITOR_SETTING(Variant::INT, PROPERTY_HINT_ENUM, "editors/3d/navigation/navigation_scheme", 0, "Godot,Maya,Modo,Custom")
+ EDITOR_SETTING(Variant::INT, PROPERTY_HINT_ENUM, "editors/3d/navigation/orbit_mouse_button", 1, "Left Mouse,Middle Mouse,Right Mouse")
+ EDITOR_SETTING(Variant::INT, PROPERTY_HINT_ENUM, "editors/3d/navigation/pan_mouse_button", 1, "Left Mouse,Middle Mouse,Right Mouse")
+ EDITOR_SETTING(Variant::INT, PROPERTY_HINT_ENUM, "editors/3d/navigation/zoom_mouse_button", 1, "Left Mouse,Middle Mouse,Right Mouse")
EDITOR_SETTING(Variant::INT, PROPERTY_HINT_ENUM, "editors/3d/navigation/zoom_style", 0, "Vertical,Horizontal")
_initial_set("editors/3d/navigation/emulate_numpad", false);
_initial_set("editors/3d/navigation/emulate_3_button_mouse", false);
- EDITOR_SETTING(Variant::INT, PROPERTY_HINT_ENUM, "editors/3d/navigation/orbit_modifier", 0, "None,Shift,Alt,Meta,Ctrl")
- EDITOR_SETTING(Variant::INT, PROPERTY_HINT_ENUM, "editors/3d/navigation/pan_modifier", 1, "None,Shift,Alt,Meta,Ctrl")
- EDITOR_SETTING(Variant::INT, PROPERTY_HINT_ENUM, "editors/3d/navigation/zoom_modifier", 4, "None,Shift,Alt,Meta,Ctrl")
_initial_set("editors/3d/navigation/warped_mouse_panning", true);
// 3D: Navigation feel
@@ -824,9 +825,15 @@ void EditorSettings::_load_defaults(Ref<ConfigFile> p_extra_config) {
EDITOR_SETTING(Variant::INT, PROPERTY_HINT_ENUM, "run/window_placement/screen", -5, screen_hints)
#endif
// Should match the ANDROID_WINDOW_* constants in 'platform/android/java/editor/src/main/java/org/godotengine/editor/GodotEditor.kt'
- String android_window_hints = "Auto (based on screen size):0,Same as Editor:1,Side-by-side with Editor:2";
+ String android_window_hints = "Auto (based on screen size):0,Same as Editor:1,Side-by-side with Editor:2,Launch in PiP mode:3";
EDITOR_SETTING(Variant::INT, PROPERTY_HINT_ENUM, "run/window_placement/android_window", 0, android_window_hints)
+ int default_play_window_pip_mode = 0;
+#ifdef ANDROID_ENABLED
+ default_play_window_pip_mode = 2;
+#endif
+ EDITOR_SETTING(Variant::INT, PROPERTY_HINT_ENUM, "run/window_placement/play_window_pip_mode", default_play_window_pip_mode, "Disabled:0,Enabled:1,Enabled when Play window is same as Editor:2")
+
// Auto save
_initial_set("run/auto_save/save_before_running", true);
@@ -865,6 +872,7 @@ void EditorSettings::_load_defaults(Ref<ConfigFile> p_extra_config) {
EDITOR_SETTING(Variant::BOOL, PROPERTY_HINT_NONE, "debugger/auto_switch_to_remote_scene_tree", false, "")
EDITOR_SETTING(Variant::INT, PROPERTY_HINT_RANGE, "debugger/profiler_frame_history_size", 3600, "60,10000,1")
EDITOR_SETTING(Variant::INT, PROPERTY_HINT_RANGE, "debugger/profiler_frame_max_functions", 64, "16,512,1")
+ EDITOR_SETTING(Variant::INT, PROPERTY_HINT_RANGE, "debugger/profiler_target_fps", 60, "1,1000,1")
EDITOR_SETTING(Variant::FLOAT, PROPERTY_HINT_RANGE, "debugger/remote_scene_tree_refresh_interval", 1.0, "0.1,10,0.01,or_greater")
EDITOR_SETTING(Variant::FLOAT, PROPERTY_HINT_RANGE, "debugger/remote_inspect_refresh_interval", 0.2, "0.02,10,0.01,or_greater")
EDITOR_SETTING(Variant::BOOL, PROPERTY_HINT_NONE, "debugger/profile_native_calls", false, "")
diff --git a/editor/editor_settings_dialog.cpp b/editor/editor_settings_dialog.cpp
index a5c492da10..7e4dec86e9 100644
--- a/editor/editor_settings_dialog.cpp
+++ b/editor/editor_settings_dialog.cpp
@@ -42,6 +42,7 @@
#include "editor/editor_undo_redo_manager.h"
#include "editor/event_listener_line_edit.h"
#include "editor/input_event_configuration_dialog.h"
+#include "editor/plugins/node_3d_editor_plugin.h"
#include "editor/themes/editor_scale.h"
#include "editor/themes/editor_theme_manager.h"
#include "scene/gui/panel_container.h"
@@ -74,7 +75,82 @@ void EditorSettingsDialog::_settings_property_edited(const String &p_name) {
EditorSettings::get_singleton()->set_manually("text_editor/theme/color_theme", "Custom");
} else if (full_name.begins_with("editors/visual_editors/connection_colors") || full_name.begins_with("editors/visual_editors/category_colors")) {
EditorSettings::get_singleton()->set_manually("editors/visual_editors/color_theme", "Custom");
- }
+ } else if (full_name == "editors/3d/navigation/orbit_mouse_button" || full_name == "editors/3d/navigation/pan_mouse_button" || full_name == "editors/3d/navigation/zoom_mouse_button") {
+ EditorSettings::get_singleton()->set_manually("editors/3d/navigation/navigation_scheme", (int)Node3DEditorViewport::NAVIGATION_CUSTOM);
+ } else if (full_name == "editors/3d/navigation/navigation_scheme") {
+ update_navigation_preset();
+ }
+}
+
+void EditorSettingsDialog::update_navigation_preset() {
+ Node3DEditorViewport::NavigationScheme nav_scheme = (Node3DEditorViewport::NavigationScheme)EDITOR_GET("editors/3d/navigation/navigation_scheme").operator int();
+ Node3DEditorViewport::ViewportNavMouseButton set_orbit_mouse_button = Node3DEditorViewport::NAVIGATION_LEFT_MOUSE;
+ Node3DEditorViewport::ViewportNavMouseButton set_pan_mouse_button = Node3DEditorViewport::NAVIGATION_LEFT_MOUSE;
+ Node3DEditorViewport::ViewportNavMouseButton set_zoom_mouse_button = Node3DEditorViewport::NAVIGATION_LEFT_MOUSE;
+ Ref<InputEventKey> orbit_mod_key_1;
+ Ref<InputEventKey> orbit_mod_key_2;
+ Ref<InputEventKey> pan_mod_key_1;
+ Ref<InputEventKey> pan_mod_key_2;
+ Ref<InputEventKey> zoom_mod_key_1;
+ Ref<InputEventKey> zoom_mod_key_2;
+ bool set_preset = false;
+
+ if (nav_scheme == Node3DEditorViewport::NAVIGATION_GODOT) {
+ set_preset = true;
+ set_orbit_mouse_button = Node3DEditorViewport::NAVIGATION_MIDDLE_MOUSE;
+ set_pan_mouse_button = Node3DEditorViewport::NAVIGATION_MIDDLE_MOUSE;
+ set_zoom_mouse_button = Node3DEditorViewport::NAVIGATION_MIDDLE_MOUSE;
+ orbit_mod_key_1 = InputEventKey::create_reference(Key::NONE);
+ orbit_mod_key_2 = InputEventKey::create_reference(Key::NONE);
+ pan_mod_key_1 = InputEventKey::create_reference(Key::SHIFT);
+ pan_mod_key_2 = InputEventKey::create_reference(Key::NONE);
+ zoom_mod_key_1 = InputEventKey::create_reference(Key::SHIFT);
+ zoom_mod_key_2 = InputEventKey::create_reference(Key::CTRL);
+ } else if (nav_scheme == Node3DEditorViewport::NAVIGATION_MAYA) {
+ set_preset = true;
+ set_orbit_mouse_button = Node3DEditorViewport::NAVIGATION_LEFT_MOUSE;
+ set_pan_mouse_button = Node3DEditorViewport::NAVIGATION_MIDDLE_MOUSE;
+ set_zoom_mouse_button = Node3DEditorViewport::NAVIGATION_RIGHT_MOUSE;
+ orbit_mod_key_1 = InputEventKey::create_reference(Key::ALT);
+ orbit_mod_key_2 = InputEventKey::create_reference(Key::NONE);
+ pan_mod_key_1 = InputEventKey::create_reference(Key::NONE);
+ pan_mod_key_2 = InputEventKey::create_reference(Key::NONE);
+ zoom_mod_key_1 = InputEventKey::create_reference(Key::ALT);
+ zoom_mod_key_2 = InputEventKey::create_reference(Key::NONE);
+ } else if (nav_scheme == Node3DEditorViewport::NAVIGATION_MODO) {
+ set_preset = true;
+ set_orbit_mouse_button = Node3DEditorViewport::NAVIGATION_LEFT_MOUSE;
+ set_pan_mouse_button = Node3DEditorViewport::NAVIGATION_LEFT_MOUSE;
+ set_zoom_mouse_button = Node3DEditorViewport::NAVIGATION_LEFT_MOUSE;
+ orbit_mod_key_1 = InputEventKey::create_reference(Key::ALT);
+ orbit_mod_key_2 = InputEventKey::create_reference(Key::NONE);
+ pan_mod_key_1 = InputEventKey::create_reference(Key::SHIFT);
+ pan_mod_key_2 = InputEventKey::create_reference(Key::ALT);
+ zoom_mod_key_1 = InputEventKey::create_reference(Key::ALT);
+ zoom_mod_key_2 = InputEventKey::create_reference(Key::CTRL);
+ }
+ // Set settings to the desired preset values.
+ if (set_preset) {
+ EditorSettings::get_singleton()->set_manually("editors/3d/navigation/orbit_mouse_button", (int)set_orbit_mouse_button);
+ EditorSettings::get_singleton()->set_manually("editors/3d/navigation/pan_mouse_button", (int)set_pan_mouse_button);
+ EditorSettings::get_singleton()->set_manually("editors/3d/navigation/zoom_mouse_button", (int)set_zoom_mouse_button);
+ _set_shortcut_input("spatial_editor/viewport_orbit_modifier_1", orbit_mod_key_1);
+ _set_shortcut_input("spatial_editor/viewport_orbit_modifier_2", orbit_mod_key_2);
+ _set_shortcut_input("spatial_editor/viewport_pan_modifier_1", pan_mod_key_1);
+ _set_shortcut_input("spatial_editor/viewport_pan_modifier_2", pan_mod_key_2);
+ _set_shortcut_input("spatial_editor/viewport_zoom_modifier_1", zoom_mod_key_1);
+ _set_shortcut_input("spatial_editor/viewport_zoom_modifier_2", zoom_mod_key_2);
+ }
+}
+
+void EditorSettingsDialog::_set_shortcut_input(const String &p_name, Ref<InputEventKey> &p_event) {
+ Array sc_events;
+ if (p_event->get_keycode() != Key::NONE) {
+ sc_events.push_back((Variant)p_event);
+ }
+
+ Ref<Shortcut> sc = EditorSettings::get_singleton()->get_shortcut(p_name);
+ sc->set_events(sc_events);
}
void EditorSettingsDialog::_settings_save() {
@@ -97,6 +173,8 @@ void EditorSettingsDialog::popup_edit_settings() {
EditorSettings::get_singleton()->list_text_editor_themes(); // make sure we have an up to date list of themes
+ _update_dynamic_property_hints();
+
inspector->edit(EditorSettings::get_singleton());
inspector->get_inspector()->update_tree();
@@ -160,6 +238,12 @@ void EditorSettingsDialog::_notification(int p_what) {
_update_shortcuts();
}
+ if (EditorSettings::get_singleton()->check_changed_settings_in_group("editors/3d/navigation")) {
+ // Shortcuts may have changed, so dynamic hint values must update.
+ _update_dynamic_property_hints();
+ inspector->get_inspector()->update_tree();
+ }
+
if (EditorSettings::get_singleton()->check_changed_settings_in_group("interface/editor/localize_settings")) {
inspector->update_category_list();
}
@@ -256,6 +340,13 @@ void EditorSettingsDialog::_update_shortcut_events(const String &p_path, const A
undo_redo->add_do_method(this, "_settings_changed");
undo_redo->add_undo_method(this, "_settings_changed");
undo_redo->commit_action();
+
+ bool path_is_orbit_mod = p_path == "spatial_editor/viewport_orbit_modifier_1" || p_path == "spatial_editor/viewport_orbit_modifier_2";
+ bool path_is_pan_mod = p_path == "spatial_editor/viewport_pan_modifier_1" || p_path == "spatial_editor/viewport_pan_modifier_2";
+ bool path_is_zoom_mod = p_path == "spatial_editor/viewport_zoom_modifier_1" || p_path == "spatial_editor/viewport_zoom_modifier_2";
+ if (path_is_orbit_mod || path_is_pan_mod || path_is_zoom_mod) {
+ EditorSettings::get_singleton()->set_manually("editors/3d/navigation/navigation_scheme", (int)Node3DEditorViewport::NAVIGATION_CUSTOM);
+ }
}
Array EditorSettingsDialog::_event_list_to_array_helper(const List<Ref<InputEvent>> &p_events) {
@@ -661,6 +752,40 @@ void EditorSettingsDialog::drop_data_fw(const Point2 &p_point, const Variant &p_
void EditorSettingsDialog::_tabs_tab_changed(int p_tab) {
_focus_current_search_box();
+
+ // When tab has switched, shortcuts may have changed.
+ _update_dynamic_property_hints();
+ inspector->get_inspector()->update_tree();
+}
+
+void EditorSettingsDialog::_update_dynamic_property_hints() {
+ // Calling add_property_hint overrides the existing hint.
+ EditorSettings *settings = EditorSettings::get_singleton();
+ settings->add_property_hint(_create_mouse_shortcut_property_info("editors/3d/navigation/orbit_mouse_button", "spatial_editor/viewport_orbit_modifier_1", "spatial_editor/viewport_orbit_modifier_2"));
+ settings->add_property_hint(_create_mouse_shortcut_property_info("editors/3d/navigation/pan_mouse_button", "spatial_editor/viewport_pan_modifier_1", "spatial_editor/viewport_pan_modifier_2"));
+ settings->add_property_hint(_create_mouse_shortcut_property_info("editors/3d/navigation/zoom_mouse_button", "spatial_editor/viewport_zoom_modifier_1", "spatial_editor/viewport_zoom_modifier_2"));
+}
+
+PropertyInfo EditorSettingsDialog::_create_mouse_shortcut_property_info(const String &p_property_name, const String &p_shortcut_1_name, const String &p_shortcut_2_name) {
+ String hint_string;
+ hint_string += _get_shortcut_button_string(p_shortcut_1_name) + _get_shortcut_button_string(p_shortcut_2_name);
+ hint_string += "Left Mouse,";
+ hint_string += _get_shortcut_button_string(p_shortcut_1_name) + _get_shortcut_button_string(p_shortcut_2_name);
+ hint_string += "Middle Mouse,";
+ hint_string += _get_shortcut_button_string(p_shortcut_1_name) + _get_shortcut_button_string(p_shortcut_2_name);
+ hint_string += "Right Mouse";
+
+ return PropertyInfo(Variant::INT, p_property_name, PROPERTY_HINT_ENUM, hint_string);
+}
+
+String EditorSettingsDialog::_get_shortcut_button_string(const String &p_shortcut_name) {
+ String button_string;
+ Ref<Shortcut> shortcut_ref = EditorSettings::get_singleton()->get_shortcut(p_shortcut_name);
+ Array events = shortcut_ref->get_events();
+ for (Ref<InputEvent> input_event : events) {
+ button_string += input_event->as_text() + " + ";
+ }
+ return button_string;
}
void EditorSettingsDialog::_focus_current_search_box() {
diff --git a/editor/editor_settings_dialog.h b/editor/editor_settings_dialog.h
index cab8fe9da1..c213f737e2 100644
--- a/editor/editor_settings_dialog.h
+++ b/editor/editor_settings_dialog.h
@@ -100,6 +100,10 @@ class EditorSettingsDialog : public AcceptDialog {
void _tabs_tab_changed(int p_tab);
void _focus_current_search_box();
+ void _update_dynamic_property_hints();
+ PropertyInfo _create_mouse_shortcut_property_info(const String &p_property_name, const String &p_shortcut_1_name, const String &p_shortcut_2_name);
+ String _get_shortcut_button_string(const String &p_shortcut_name);
+
void _filter_shortcuts(const String &p_filter);
void _filter_shortcuts_by_event(const Ref<InputEvent> &p_event);
bool _should_display_shortcut(const String &p_name, const Array &p_events) const;
@@ -107,6 +111,7 @@ class EditorSettingsDialog : public AcceptDialog {
void _update_shortcuts();
void _shortcut_button_pressed(Object *p_item, int p_column, int p_idx, MouseButton p_button = MouseButton::LEFT);
void _shortcut_cell_double_clicked();
+ static void _set_shortcut_input(const String &p_name, Ref<InputEventKey> &p_event);
static void _undo_redo_callback(void *p_self, const String &p_name);
@@ -124,6 +129,7 @@ protected:
public:
void popup_edit_settings();
+ static void update_navigation_preset();
EditorSettingsDialog();
~EditorSettingsDialog();
diff --git a/editor/editor_undo_redo_manager.cpp b/editor/editor_undo_redo_manager.cpp
index 55bc198dfb..2e96ae82fc 100644
--- a/editor/editor_undo_redo_manager.cpp
+++ b/editor/editor_undo_redo_manager.cpp
@@ -177,7 +177,7 @@ void EditorUndoRedoManager::_add_do_method(const Variant **p_args, int p_argcoun
return;
}
- if (p_args[1]->get_type() != Variant::STRING_NAME && p_args[1]->get_type() != Variant::STRING) {
+ if (!p_args[1]->is_string()) {
r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT;
r_error.argument = 1;
r_error.expected = Variant::STRING_NAME;
@@ -206,7 +206,7 @@ void EditorUndoRedoManager::_add_undo_method(const Variant **p_args, int p_argco
return;
}
- if (p_args[1]->get_type() != Variant::STRING_NAME && p_args[1]->get_type() != Variant::STRING) {
+ if (!p_args[1]->is_string()) {
r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT;
r_error.argument = 1;
r_error.expected = Variant::STRING_NAME;
@@ -390,7 +390,7 @@ bool EditorUndoRedoManager::has_history(int p_idx) const {
return history_map.has(p_idx);
}
-void EditorUndoRedoManager::clear_history(bool p_increase_version, int p_idx) {
+void EditorUndoRedoManager::clear_history(int p_idx, bool p_increase_version) {
if (p_idx != INVALID_HISTORY) {
History &history = get_or_create_history(p_idx);
history.undo_redo->clear_history(p_increase_version);
@@ -507,6 +507,7 @@ void EditorUndoRedoManager::_bind_methods() {
ClassDB::bind_method(D_METHOD("get_object_history_id", "object"), &EditorUndoRedoManager::get_history_id_for_object);
ClassDB::bind_method(D_METHOD("get_history_undo_redo", "id"), &EditorUndoRedoManager::get_history_undo_redo);
+ ClassDB::bind_method(D_METHOD("clear_history", "id", "increase_version"), &EditorUndoRedoManager::clear_history, DEFVAL(INVALID_HISTORY), DEFVAL(true));
ADD_SIGNAL(MethodInfo("history_changed"));
ADD_SIGNAL(MethodInfo("version_changed"));
diff --git a/editor/editor_undo_redo_manager.h b/editor/editor_undo_redo_manager.h
index 219d5e0702..54475c3c6c 100644
--- a/editor/editor_undo_redo_manager.h
+++ b/editor/editor_undo_redo_manager.h
@@ -125,7 +125,7 @@ public:
bool undo_history(int p_id);
bool redo();
bool redo_history(int p_id);
- void clear_history(bool p_increase_version = true, int p_idx = INVALID_HISTORY);
+ void clear_history(int p_idx = INVALID_HISTORY, bool p_increase_version = true);
void set_history_as_saved(int p_idx);
void set_history_as_unsaved(int p_idx);
diff --git a/editor/export/editor_export.cpp b/editor/export/editor_export.cpp
index 72ab186036..975a601ae1 100644
--- a/editor/export/editor_export.cpp
+++ b/editor/export/editor_export.cpp
@@ -124,7 +124,17 @@ void EditorExport::_bind_methods() {
void EditorExport::add_export_platform(const Ref<EditorExportPlatform> &p_platform) {
export_platforms.push_back(p_platform);
+
should_update_presets = true;
+ should_reload_presets = true;
+}
+
+void EditorExport::remove_export_platform(const Ref<EditorExportPlatform> &p_platform) {
+ export_platforms.erase(p_platform);
+ p_platform->cleanup();
+
+ should_update_presets = true;
+ should_reload_presets = true;
}
int EditorExport::get_export_platform_count() {
@@ -244,7 +254,7 @@ void EditorExport::load_config() {
if (!preset.is_valid()) {
index++;
- ERR_CONTINUE(!preset.is_valid());
+ continue; // Unknown platform, skip without error (platform might be loaded later).
}
preset->set_name(config->get_value(section, "name"));
@@ -343,6 +353,12 @@ void EditorExport::load_config() {
void EditorExport::update_export_presets() {
HashMap<StringName, List<EditorExportPlatform::ExportOption>> platform_options;
+ if (should_reload_presets) {
+ should_reload_presets = false;
+ export_presets.clear();
+ load_config();
+ }
+
for (int i = 0; i < export_platforms.size(); i++) {
Ref<EditorExportPlatform> platform = export_platforms[i];
diff --git a/editor/export/editor_export.h b/editor/export/editor_export.h
index f8cb90dc39..ebb2038f53 100644
--- a/editor/export/editor_export.h
+++ b/editor/export/editor_export.h
@@ -47,6 +47,7 @@ class EditorExport : public Node {
Timer *save_timer = nullptr;
bool block_save = false;
bool should_update_presets = false;
+ bool should_reload_presets = false;
static EditorExport *singleton;
@@ -66,6 +67,7 @@ public:
void add_export_platform(const Ref<EditorExportPlatform> &p_platform);
int get_export_platform_count();
Ref<EditorExportPlatform> get_export_platform(int p_idx);
+ void remove_export_platform(const Ref<EditorExportPlatform> &p_platform);
void add_export_preset(const Ref<EditorExportPreset> &p_preset, int p_at_pos = -1);
int get_export_preset_count() const;
diff --git a/editor/export/editor_export_platform.cpp b/editor/export/editor_export_platform.cpp
index 8b31eda3bc..7ad589a58d 100644
--- a/editor/export/editor_export_platform.cpp
+++ b/editor/export/editor_export_platform.cpp
@@ -71,7 +71,7 @@ bool EditorExportPlatform::fill_log_messages(RichTextLabel *p_log, Error p_err)
p_log->add_text(" ");
p_log->add_text(get_name());
p_log->add_text(" - ");
- if (p_err == OK) {
+ if (p_err == OK && get_worst_message_type() < EditorExportPlatform::EXPORT_MESSAGE_ERROR) {
if (get_worst_message_type() >= EditorExportPlatform::EXPORT_MESSAGE_WARNING) {
p_log->add_image(p_log->get_editor_theme_icon(SNAME("StatusWarning")), 16 * EDSCALE, 16 * EDSCALE, Color(1.0, 1.0, 1.0), INLINE_ALIGNMENT_CENTER);
p_log->add_text(" ");
@@ -167,58 +167,6 @@ bool EditorExportPlatform::fill_log_messages(RichTextLabel *p_log, Error p_err)
return has_messages;
}
-void EditorExportPlatform::gen_debug_flags(Vector<String> &r_flags, int p_flags) {
- String host = EDITOR_GET("network/debug/remote_host");
- int remote_port = (int)EDITOR_GET("network/debug/remote_port");
-
- if (EditorSettings::get_singleton()->has_setting("export/android/use_wifi_for_remote_debug") && EDITOR_GET("export/android/use_wifi_for_remote_debug")) {
- host = EDITOR_GET("export/android/wifi_remote_debug_host");
- } else if (p_flags & DEBUG_FLAG_REMOTE_DEBUG_LOCALHOST) {
- host = "localhost";
- }
-
- if (p_flags & DEBUG_FLAG_DUMB_CLIENT) {
- int port = EDITOR_GET("filesystem/file_server/port");
- String passwd = EDITOR_GET("filesystem/file_server/password");
- r_flags.push_back("--remote-fs");
- r_flags.push_back(host + ":" + itos(port));
- if (!passwd.is_empty()) {
- r_flags.push_back("--remote-fs-password");
- r_flags.push_back(passwd);
- }
- }
-
- if (p_flags & DEBUG_FLAG_REMOTE_DEBUG) {
- r_flags.push_back("--remote-debug");
-
- r_flags.push_back(get_debug_protocol() + host + ":" + String::num(remote_port));
-
- List<String> breakpoints;
- ScriptEditor::get_singleton()->get_breakpoints(&breakpoints);
-
- if (breakpoints.size()) {
- r_flags.push_back("--breakpoints");
- String bpoints;
- for (const List<String>::Element *E = breakpoints.front(); E; E = E->next()) {
- bpoints += E->get().replace(" ", "%20");
- if (E->next()) {
- bpoints += ",";
- }
- }
-
- r_flags.push_back(bpoints);
- }
- }
-
- if (p_flags & DEBUG_FLAG_VIEW_COLLISIONS) {
- r_flags.push_back("--debug-collisions");
- }
-
- if (p_flags & DEBUG_FLAG_VIEW_NAVIGATION) {
- r_flags.push_back("--debug-navigation");
- }
-}
-
Error EditorExportPlatform::_save_pack_file(void *p_userdata, const String &p_path, const Vector<uint8_t> &p_data, int p_file, int p_total, const Vector<String> &p_enc_in_filters, const Vector<String> &p_enc_ex_filters, const Vector<uint8_t> &p_key) {
ERR_FAIL_COND_V_MSG(p_total < 1, ERR_PARAMETER_RANGE_ERROR, "Must select at least one file to export.");
@@ -530,7 +478,7 @@ HashSet<String> EditorExportPlatform::get_features(const Ref<EditorExportPreset>
return result;
}
-EditorExportPlatform::ExportNotifier::ExportNotifier(EditorExportPlatform &p_platform, const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, int p_flags) {
+EditorExportPlatform::ExportNotifier::ExportNotifier(EditorExportPlatform &p_platform, const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, BitField<EditorExportPlatform::DebugFlags> p_flags) {
HashSet<String> features = p_platform.get_features(p_preset, p_debug);
Vector<Ref<EditorExportPlugin>> export_plugins = EditorExport::get_singleton()->get_export_plugins();
//initial export plugin callback
@@ -919,6 +867,55 @@ Vector<String> EditorExportPlatform::get_forced_export_files() {
return files;
}
+Error EditorExportPlatform::_script_save_file(void *p_userdata, const String &p_path, const Vector<uint8_t> &p_data, int p_file, int p_total, const Vector<String> &p_enc_in_filters, const Vector<String> &p_enc_ex_filters, const Vector<uint8_t> &p_key) {
+ Callable cb = ((ScriptCallbackData *)p_userdata)->file_cb;
+ ERR_FAIL_COND_V(!cb.is_valid(), FAILED);
+
+ Variant path = p_path;
+ Variant data = p_data;
+ Variant file = p_file;
+ Variant total = p_total;
+ Variant enc_in = p_enc_in_filters;
+ Variant enc_ex = p_enc_ex_filters;
+ Variant enc_key = p_key;
+
+ Variant ret;
+ Callable::CallError ce;
+ const Variant *args[7] = { &path, &data, &file, &total, &enc_in, &enc_ex, &enc_key };
+
+ cb.callp(args, 7, ret, ce);
+ ERR_FAIL_COND_V_MSG(ce.error != Callable::CallError::CALL_OK, FAILED, vformat("Failed to execute file save callback: %s.", Variant::get_callable_error_text(cb, args, 7, ce)));
+
+ return (Error)ret.operator int();
+}
+
+Error EditorExportPlatform::_script_add_shared_object(void *p_userdata, const SharedObject &p_so) {
+ Callable cb = ((ScriptCallbackData *)p_userdata)->so_cb;
+ if (!cb.is_valid()) {
+ return OK; // Optional.
+ }
+
+ Variant path = p_so.path;
+ Variant tags = p_so.tags;
+ Variant target = p_so.target;
+
+ Variant ret;
+ Callable::CallError ce;
+ const Variant *args[3] = { &path, &tags, &target };
+
+ cb.callp(args, 3, ret, ce);
+ ERR_FAIL_COND_V_MSG(ce.error != Callable::CallError::CALL_OK, FAILED, vformat("Failed to execute shared object save callback: %s.", Variant::get_callable_error_text(cb, args, 3, ce)));
+
+ return (Error)ret.operator int();
+}
+
+Error EditorExportPlatform::_export_project_files(const Ref<EditorExportPreset> &p_preset, bool p_debug, const Callable &p_save_func, const Callable &p_so_func) {
+ ScriptCallbackData data;
+ data.file_cb = p_save_func;
+ data.so_cb = p_so_func;
+ return export_project_files(p_preset, p_debug, _script_save_file, &data, _script_add_shared_object);
+}
+
Error EditorExportPlatform::export_project_files(const Ref<EditorExportPreset> &p_preset, bool p_debug, EditorExportSaveFunction p_func, void *p_udata, EditorExportSaveSharedObject p_so_func) {
//figure out paths of files that will be exported
HashSet<String> paths;
@@ -1425,7 +1422,7 @@ Error EditorExportPlatform::export_project_files(const Ref<EditorExportPreset> &
return p_func(p_udata, "res://" + config_file, data, idx, total, enc_in_filters, enc_ex_filters, key);
}
-Error EditorExportPlatform::_add_shared_object(void *p_userdata, const SharedObject &p_so) {
+Error EditorExportPlatform::_pack_add_shared_object(void *p_userdata, const SharedObject &p_so) {
PackData *pack_data = (PackData *)p_userdata;
if (pack_data->so_files) {
pack_data->so_files->push_back(p_so);
@@ -1434,6 +1431,15 @@ Error EditorExportPlatform::_add_shared_object(void *p_userdata, const SharedObj
return OK;
}
+Error EditorExportPlatform::_zip_add_shared_object(void *p_userdata, const SharedObject &p_so) {
+ ZipData *zip_data = (ZipData *)p_userdata;
+ if (zip_data->so_files) {
+ zip_data->so_files->push_back(p_so);
+ }
+
+ return OK;
+}
+
void EditorExportPlatform::zip_folder_recursive(zipFile &p_zip, const String &p_root_path, const String &p_folder, const String &p_pkg_name) {
String dir = p_folder.is_empty() ? p_root_path : p_root_path.path_join(p_folder);
@@ -1551,6 +1557,54 @@ void EditorExportPlatform::zip_folder_recursive(zipFile &p_zip, const String &p_
da->list_dir_end();
}
+Dictionary EditorExportPlatform::_save_pack(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, bool p_embed) {
+ Vector<SharedObject> so_files;
+ int64_t embedded_start = 0;
+ int64_t embedded_size = 0;
+ Error err_code = save_pack(p_preset, p_debug, p_path, &so_files, p_embed, &embedded_start, &embedded_size);
+
+ Dictionary ret;
+ ret["result"] = err_code;
+ if (err_code == OK) {
+ Array arr;
+ for (const SharedObject &E : so_files) {
+ Dictionary so;
+ so["path"] = E.path;
+ so["tags"] = E.tags;
+ so["target_folder"] = E.target;
+ arr.push_back(so);
+ }
+ ret["so_files"] = arr;
+ if (p_embed) {
+ ret["embedded_start"] = embedded_start;
+ ret["embedded_size"] = embedded_size;
+ }
+ }
+
+ return ret;
+}
+
+Dictionary EditorExportPlatform::_save_zip(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path) {
+ Vector<SharedObject> so_files;
+ Error err_code = save_zip(p_preset, p_debug, p_path, &so_files);
+
+ Dictionary ret;
+ ret["result"] = err_code;
+ if (err_code == OK) {
+ Array arr;
+ for (const SharedObject &E : so_files) {
+ Dictionary so;
+ so["path"] = E.path;
+ so["tags"] = E.tags;
+ so["target_folder"] = E.target;
+ arr.push_back(so);
+ }
+ ret["so_files"] = arr;
+ }
+
+ return ret;
+}
+
Error EditorExportPlatform::save_pack(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, Vector<SharedObject> *p_so_files, bool p_embed, int64_t *r_embedded_start, int64_t *r_embedded_size) {
EditorProgress ep("savepack", TTR("Packing"), 102, true);
@@ -1570,7 +1624,7 @@ Error EditorExportPlatform::save_pack(const Ref<EditorExportPreset> &p_preset, b
pd.f = ftmp;
pd.so_files = p_so_files;
- Error err = export_project_files(p_preset, p_debug, _save_pack_file, &pd, _add_shared_object);
+ Error err = export_project_files(p_preset, p_debug, _save_pack_file, &pd, _pack_add_shared_object);
// Close temp file.
pd.f.unref();
@@ -1777,7 +1831,7 @@ Error EditorExportPlatform::save_pack(const Ref<EditorExportPreset> &p_preset, b
return OK;
}
-Error EditorExportPlatform::save_zip(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path) {
+Error EditorExportPlatform::save_zip(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, Vector<SharedObject> *p_so_files) {
EditorProgress ep("savezip", TTR("Packing"), 102, true);
Ref<FileAccess> io_fa;
@@ -1787,8 +1841,9 @@ Error EditorExportPlatform::save_zip(const Ref<EditorExportPreset> &p_preset, bo
ZipData zd;
zd.ep = &ep;
zd.zip = zip;
+ zd.so_files = p_so_files;
- Error err = export_project_files(p_preset, p_debug, _save_zip_file, &zd);
+ Error err = export_project_files(p_preset, p_debug, _save_zip_file, &zd, _zip_add_shared_object);
if (err != OK && err != ERR_SKIP) {
add_message(EXPORT_MESSAGE_ERROR, TTR("Save ZIP"), TTR("Failed to export project files."));
}
@@ -1798,45 +1853,48 @@ Error EditorExportPlatform::save_zip(const Ref<EditorExportPreset> &p_preset, bo
return OK;
}
-Error EditorExportPlatform::export_pack(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, int p_flags) {
+Error EditorExportPlatform::export_pack(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, BitField<EditorExportPlatform::DebugFlags> p_flags) {
ExportNotifier notifier(*this, p_preset, p_debug, p_path, p_flags);
return save_pack(p_preset, p_debug, p_path);
}
-Error EditorExportPlatform::export_zip(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, int p_flags) {
+Error EditorExportPlatform::export_zip(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, BitField<EditorExportPlatform::DebugFlags> p_flags) {
ExportNotifier notifier(*this, p_preset, p_debug, p_path, p_flags);
return save_zip(p_preset, p_debug, p_path);
}
-void EditorExportPlatform::gen_export_flags(Vector<String> &r_flags, int p_flags) {
+Vector<String> EditorExportPlatform::gen_export_flags(BitField<EditorExportPlatform::DebugFlags> p_flags) {
+ Vector<String> ret;
String host = EDITOR_GET("network/debug/remote_host");
int remote_port = (int)EDITOR_GET("network/debug/remote_port");
- if (p_flags & DEBUG_FLAG_REMOTE_DEBUG_LOCALHOST) {
+ if (get_name() == "Android" && EditorSettings::get_singleton()->has_setting("export/android/use_wifi_for_remote_debug") && EDITOR_GET("export/android/use_wifi_for_remote_debug")) {
+ host = EDITOR_GET("export/android/wifi_remote_debug_host");
+ } else if (p_flags.has_flag(DEBUG_FLAG_REMOTE_DEBUG_LOCALHOST)) {
host = "localhost";
}
- if (p_flags & DEBUG_FLAG_DUMB_CLIENT) {
+ if (p_flags.has_flag(DEBUG_FLAG_DUMB_CLIENT)) {
int port = EDITOR_GET("filesystem/file_server/port");
String passwd = EDITOR_GET("filesystem/file_server/password");
- r_flags.push_back("--remote-fs");
- r_flags.push_back(host + ":" + itos(port));
+ ret.push_back("--remote-fs");
+ ret.push_back(host + ":" + itos(port));
if (!passwd.is_empty()) {
- r_flags.push_back("--remote-fs-password");
- r_flags.push_back(passwd);
+ ret.push_back("--remote-fs-password");
+ ret.push_back(passwd);
}
}
- if (p_flags & DEBUG_FLAG_REMOTE_DEBUG) {
- r_flags.push_back("--remote-debug");
+ if (p_flags.has_flag(DEBUG_FLAG_REMOTE_DEBUG)) {
+ ret.push_back("--remote-debug");
- r_flags.push_back(get_debug_protocol() + host + ":" + String::num(remote_port));
+ ret.push_back(get_debug_protocol() + host + ":" + String::num(remote_port));
List<String> breakpoints;
ScriptEditor::get_singleton()->get_breakpoints(&breakpoints);
if (breakpoints.size()) {
- r_flags.push_back("--breakpoints");
+ ret.push_back("--breakpoints");
String bpoints;
for (List<String>::Element *E = breakpoints.front(); E; E = E->next()) {
bpoints += E->get().replace(" ", "%20");
@@ -1845,17 +1903,18 @@ void EditorExportPlatform::gen_export_flags(Vector<String> &r_flags, int p_flags
}
}
- r_flags.push_back(bpoints);
+ ret.push_back(bpoints);
}
}
- if (p_flags & DEBUG_FLAG_VIEW_COLLISIONS) {
- r_flags.push_back("--debug-collisions");
+ if (p_flags.has_flag(DEBUG_FLAG_VIEW_COLLISIONS)) {
+ ret.push_back("--debug-collisions");
}
- if (p_flags & DEBUG_FLAG_VIEW_NAVIGATION) {
- r_flags.push_back("--debug-navigation");
+ if (p_flags.has_flag(DEBUG_FLAG_VIEW_NAVIGATION)) {
+ ret.push_back("--debug-navigation");
}
+ return ret;
}
bool EditorExportPlatform::can_export(const Ref<EditorExportPreset> &p_preset, String &r_error, bool &r_missing_templates, bool p_debug) const {
@@ -2035,8 +2094,61 @@ Error EditorExportPlatform::ssh_push_to_remote(const String &p_host, const Strin
return OK;
}
+Array EditorExportPlatform::get_current_presets() const {
+ Array ret;
+ for (int i = 0; i < EditorExport::get_singleton()->get_export_preset_count(); i++) {
+ Ref<EditorExportPreset> ep = EditorExport::get_singleton()->get_export_preset(i);
+ if (ep->get_platform() == this) {
+ ret.push_back(ep);
+ }
+ }
+ return ret;
+}
+
void EditorExportPlatform::_bind_methods() {
ClassDB::bind_method(D_METHOD("get_os_name"), &EditorExportPlatform::get_os_name);
+
+ ClassDB::bind_method(D_METHOD("create_preset"), &EditorExportPlatform::create_preset);
+
+ ClassDB::bind_method(D_METHOD("find_export_template", "template_file_name"), &EditorExportPlatform::_find_export_template);
+ ClassDB::bind_method(D_METHOD("get_current_presets"), &EditorExportPlatform::get_current_presets);
+
+ ClassDB::bind_method(D_METHOD("save_pack", "preset", "debug", "path", "embed"), &EditorExportPlatform::_save_pack, DEFVAL(false));
+ ClassDB::bind_method(D_METHOD("save_zip", "preset", "debug", "path"), &EditorExportPlatform::_save_zip);
+
+ ClassDB::bind_method(D_METHOD("gen_export_flags", "flags"), &EditorExportPlatform::gen_export_flags);
+
+ ClassDB::bind_method(D_METHOD("export_project_files", "preset", "debug", "save_cb", "shared_cb"), &EditorExportPlatform::_export_project_files, DEFVAL(Callable()));
+
+ ClassDB::bind_method(D_METHOD("export_project", "preset", "debug", "path", "flags"), &EditorExportPlatform::export_project, DEFVAL(0));
+ ClassDB::bind_method(D_METHOD("export_pack", "preset", "debug", "path", "flags"), &EditorExportPlatform::export_pack, DEFVAL(0));
+ ClassDB::bind_method(D_METHOD("export_zip", "preset", "debug", "path", "flags"), &EditorExportPlatform::export_zip, DEFVAL(0));
+
+ ClassDB::bind_method(D_METHOD("clear_messages"), &EditorExportPlatform::clear_messages);
+ ClassDB::bind_method(D_METHOD("add_message", "type", "category", "message"), &EditorExportPlatform::add_message);
+ ClassDB::bind_method(D_METHOD("get_message_count"), &EditorExportPlatform::get_message_count);
+
+ ClassDB::bind_method(D_METHOD("get_message_type", "index"), &EditorExportPlatform::_get_message_type);
+ ClassDB::bind_method(D_METHOD("get_message_category", "index"), &EditorExportPlatform::_get_message_category);
+ ClassDB::bind_method(D_METHOD("get_message_text", "index"), &EditorExportPlatform::_get_message_text);
+ ClassDB::bind_method(D_METHOD("get_worst_message_type"), &EditorExportPlatform::get_worst_message_type);
+
+ ClassDB::bind_method(D_METHOD("ssh_run_on_remote", "host", "port", "ssh_arg", "cmd_args", "output", "port_fwd"), &EditorExportPlatform::_ssh_run_on_remote, DEFVAL(Array()), DEFVAL(-1));
+ ClassDB::bind_method(D_METHOD("ssh_run_on_remote_no_wait", "host", "port", "ssh_args", "cmd_args", "port_fwd"), &EditorExportPlatform::_ssh_run_on_remote_no_wait, DEFVAL(-1));
+ ClassDB::bind_method(D_METHOD("ssh_push_to_remote", "host", "port", "scp_args", "src_file", "dst_file"), &EditorExportPlatform::ssh_push_to_remote);
+
+ ClassDB::bind_static_method("EditorExportPlatform", D_METHOD("get_forced_export_files"), &EditorExportPlatform::get_forced_export_files);
+
+ BIND_ENUM_CONSTANT(EXPORT_MESSAGE_NONE);
+ BIND_ENUM_CONSTANT(EXPORT_MESSAGE_INFO);
+ BIND_ENUM_CONSTANT(EXPORT_MESSAGE_WARNING);
+ BIND_ENUM_CONSTANT(EXPORT_MESSAGE_ERROR);
+
+ BIND_BITFIELD_FLAG(DEBUG_FLAG_DUMB_CLIENT);
+ BIND_BITFIELD_FLAG(DEBUG_FLAG_REMOTE_DEBUG);
+ BIND_BITFIELD_FLAG(DEBUG_FLAG_REMOTE_DEBUG_LOCALHOST);
+ BIND_BITFIELD_FLAG(DEBUG_FLAG_VIEW_COLLISIONS);
+ BIND_BITFIELD_FLAG(DEBUG_FLAG_VIEW_NAVIGATION);
}
EditorExportPlatform::EditorExportPlatform() {
diff --git a/editor/export/editor_export_platform.h b/editor/export/editor_export_platform.h
index 3fd75ff67f..a800bb95e6 100644
--- a/editor/export/editor_export_platform.h
+++ b/editor/export/editor_export_platform.h
@@ -56,6 +56,14 @@ public:
typedef Error (*EditorExportSaveFunction)(void *p_userdata, const String &p_path, const Vector<uint8_t> &p_data, int p_file, int p_total, const Vector<String> &p_enc_in_filters, const Vector<String> &p_enc_ex_filters, const Vector<uint8_t> &p_key);
typedef Error (*EditorExportSaveSharedObject)(void *p_userdata, const SharedObject &p_so);
+ enum DebugFlags {
+ DEBUG_FLAG_DUMB_CLIENT = 1,
+ DEBUG_FLAG_REMOTE_DEBUG = 2,
+ DEBUG_FLAG_REMOTE_DEBUG_LOCALHOST = 4,
+ DEBUG_FLAG_VIEW_COLLISIONS = 8,
+ DEBUG_FLAG_VIEW_NAVIGATION = 16,
+ };
+
enum ExportMessageType {
EXPORT_MESSAGE_NONE,
EXPORT_MESSAGE_INFO,
@@ -92,6 +100,7 @@ private:
struct ZipData {
void *zip = nullptr;
EditorProgress *ep = nullptr;
+ Vector<SharedObject> *so_files = nullptr;
};
Vector<ExportMessage> messages;
@@ -101,13 +110,22 @@ private:
void _export_find_dependencies(const String &p_path, HashSet<String> &p_paths);
static Error _save_pack_file(void *p_userdata, const String &p_path, const Vector<uint8_t> &p_data, int p_file, int p_total, const Vector<String> &p_enc_in_filters, const Vector<String> &p_enc_ex_filters, const Vector<uint8_t> &p_key);
+ static Error _pack_add_shared_object(void *p_userdata, const SharedObject &p_so);
+
static Error _save_zip_file(void *p_userdata, const String &p_path, const Vector<uint8_t> &p_data, int p_file, int p_total, const Vector<String> &p_enc_in_filters, const Vector<String> &p_enc_ex_filters, const Vector<uint8_t> &p_key);
+ static Error _zip_add_shared_object(void *p_userdata, const SharedObject &p_so);
+
+ struct ScriptCallbackData {
+ Callable file_cb;
+ Callable so_cb;
+ };
+
+ static Error _script_save_file(void *p_userdata, const String &p_path, const Vector<uint8_t> &p_data, int p_file, int p_total, const Vector<String> &p_enc_in_filters, const Vector<String> &p_enc_ex_filters, const Vector<uint8_t> &p_key);
+ static Error _script_add_shared_object(void *p_userdata, const SharedObject &p_so);
void _edit_files_with_filter(Ref<DirAccess> &da, const Vector<String> &p_filters, HashSet<String> &r_list, bool exclude);
void _edit_filter_list(HashSet<String> &r_list, const String &p_filter, bool exclude);
- static Error _add_shared_object(void *p_userdata, const SharedObject &p_so);
-
struct FileExportCache {
uint64_t source_modified_time = 0;
String source_md5;
@@ -126,19 +144,46 @@ private:
protected:
struct ExportNotifier {
- ExportNotifier(EditorExportPlatform &p_platform, const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, int p_flags);
+ ExportNotifier(EditorExportPlatform &p_platform, const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, BitField<EditorExportPlatform::DebugFlags> p_flags);
~ExportNotifier();
};
HashSet<String> get_features(const Ref<EditorExportPreset> &p_preset, bool p_debug) const;
- bool exists_export_template(const String &template_file_name, String *err) const;
- String find_export_template(const String &template_file_name, String *err = nullptr) const;
- void gen_export_flags(Vector<String> &r_flags, int p_flags);
- void gen_debug_flags(Vector<String> &r_flags, int p_flags);
+ Dictionary _find_export_template(const String &p_template_file_name) const {
+ Dictionary ret;
+ String err;
+
+ String path = find_export_template(p_template_file_name, &err);
+ ret["result"] = (err.is_empty() && !path.is_empty()) ? OK : FAILED;
+ ret["path"] = path;
+ ret["error_string"] = err;
+
+ return ret;
+ }
+
+ bool exists_export_template(const String &p_template_file_name, String *r_err) const;
+ String find_export_template(const String &p_template_file_name, String *r_err = nullptr) const;
+ Vector<String> gen_export_flags(BitField<EditorExportPlatform::DebugFlags> p_flags);
virtual void zip_folder_recursive(zipFile &p_zip, const String &p_root_path, const String &p_folder, const String &p_pkg_name);
+ Error _ssh_run_on_remote(const String &p_host, const String &p_port, const Vector<String> &p_ssh_args, const String &p_cmd_args, Array r_output = Array(), int p_port_fwd = -1) const {
+ String pipe;
+ Error err = ssh_run_on_remote(p_host, p_port, p_ssh_args, p_cmd_args, &pipe, p_port_fwd);
+ r_output.push_back(pipe);
+ return err;
+ }
+ OS::ProcessID _ssh_run_on_remote_no_wait(const String &p_host, const String &p_port, const Vector<String> &p_ssh_args, const String &p_cmd_args, int p_port_fwd = -1) const {
+ OS::ProcessID pid = 0;
+ Error err = ssh_run_on_remote_no_wait(p_host, p_port, p_ssh_args, p_cmd_args, &pid, p_port_fwd);
+ if (err != OK) {
+ return -1;
+ } else {
+ return pid;
+ }
+ }
+
Error ssh_run_on_remote(const String &p_host, const String &p_port, const Vector<String> &p_ssh_args, const String &p_cmd_args, String *r_out = nullptr, int p_port_fwd = -1) const;
Error ssh_run_on_remote_no_wait(const String &p_host, const String &p_port, const Vector<String> &p_ssh_args, const String &p_cmd_args, OS::ProcessID *r_pid = nullptr, int p_port_fwd = -1) const;
Error ssh_push_to_remote(const String &p_host, const String &p_port, const Vector<String> &p_scp_args, const String &p_src_file, const String &p_dst_file) const;
@@ -195,6 +240,21 @@ public:
return messages[p_index];
}
+ virtual ExportMessageType _get_message_type(int p_index) const {
+ ERR_FAIL_INDEX_V(p_index, messages.size(), EXPORT_MESSAGE_NONE);
+ return messages[p_index].msg_type;
+ }
+
+ virtual String _get_message_category(int p_index) const {
+ ERR_FAIL_INDEX_V(p_index, messages.size(), String());
+ return messages[p_index].category;
+ }
+
+ virtual String _get_message_text(int p_index) const {
+ ERR_FAIL_INDEX_V(p_index, messages.size(), String());
+ return messages[p_index].text;
+ }
+
virtual ExportMessageType get_worst_message_type() const {
ExportMessageType worst_type = EXPORT_MESSAGE_NONE;
for (int i = 0; i < messages.size(); i++) {
@@ -216,10 +276,16 @@ public:
virtual String get_name() const = 0;
virtual Ref<Texture2D> get_logo() const = 0;
+ Array get_current_presets() const;
+
+ Error _export_project_files(const Ref<EditorExportPreset> &p_preset, bool p_debug, const Callable &p_save_func, const Callable &p_so_func);
Error export_project_files(const Ref<EditorExportPreset> &p_preset, bool p_debug, EditorExportSaveFunction p_func, void *p_udata, EditorExportSaveSharedObject p_so_func = nullptr);
+ Dictionary _save_pack(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, bool p_embed = false);
+ Dictionary _save_zip(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path);
+
Error save_pack(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, Vector<SharedObject> *p_so_files = nullptr, bool p_embed = false, int64_t *r_embedded_start = nullptr, int64_t *r_embedded_size = nullptr);
- Error save_zip(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path);
+ Error save_zip(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, Vector<SharedObject> *p_so_files = nullptr);
virtual bool poll_export() { return false; }
virtual int get_options_count() const { return 0; }
@@ -229,31 +295,26 @@ public:
virtual String get_option_tooltip(int p_device) const { return ""; }
virtual String get_device_architecture(int p_device) const { return ""; }
- enum DebugFlags {
- DEBUG_FLAG_DUMB_CLIENT = 1,
- DEBUG_FLAG_REMOTE_DEBUG = 2,
- DEBUG_FLAG_REMOTE_DEBUG_LOCALHOST = 4,
- DEBUG_FLAG_VIEW_COLLISIONS = 8,
- DEBUG_FLAG_VIEW_NAVIGATION = 16,
- };
-
virtual void cleanup() {}
- virtual Error run(const Ref<EditorExportPreset> &p_preset, int p_device, int p_debug_flags) { return OK; }
+ virtual Error run(const Ref<EditorExportPreset> &p_preset, int p_device, BitField<EditorExportPlatform::DebugFlags> p_debug_flags) { return OK; }
virtual Ref<Texture2D> get_run_icon() const { return get_logo(); }
- bool can_export(const Ref<EditorExportPreset> &p_preset, String &r_error, bool &r_missing_templates, bool p_debug = false) const;
+ virtual bool can_export(const Ref<EditorExportPreset> &p_preset, String &r_error, bool &r_missing_templates, bool p_debug = false) const;
virtual bool has_valid_export_configuration(const Ref<EditorExportPreset> &p_preset, String &r_error, bool &r_missing_templates, bool p_debug = false) const = 0;
virtual bool has_valid_project_configuration(const Ref<EditorExportPreset> &p_preset, String &r_error) const = 0;
virtual List<String> get_binary_extensions(const Ref<EditorExportPreset> &p_preset) const = 0;
- virtual Error export_project(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, int p_flags = 0) = 0;
- virtual Error export_pack(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, int p_flags = 0);
- virtual Error export_zip(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, int p_flags = 0);
+ virtual Error export_project(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, BitField<EditorExportPlatform::DebugFlags> p_flags = 0) = 0;
+ virtual Error export_pack(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, BitField<EditorExportPlatform::DebugFlags> p_flags = 0);
+ virtual Error export_zip(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, BitField<EditorExportPlatform::DebugFlags> p_flags = 0);
virtual void get_platform_features(List<String> *r_features) const = 0;
- virtual void resolve_platform_feature_priorities(const Ref<EditorExportPreset> &p_preset, HashSet<String> &p_features) = 0;
+ virtual void resolve_platform_feature_priorities(const Ref<EditorExportPreset> &p_preset, HashSet<String> &p_features){};
virtual String get_debug_protocol() const { return "tcp://"; }
EditorExportPlatform();
};
+VARIANT_ENUM_CAST(EditorExportPlatform::ExportMessageType)
+VARIANT_BITFIELD_CAST(EditorExportPlatform::DebugFlags);
+
#endif // EDITOR_EXPORT_PLATFORM_H
diff --git a/editor/export/editor_export_platform_extension.cpp b/editor/export/editor_export_platform_extension.cpp
new file mode 100644
index 0000000000..808a2076e2
--- /dev/null
+++ b/editor/export/editor_export_platform_extension.cpp
@@ -0,0 +1,317 @@
+/**************************************************************************/
+/* editor_export_platform_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 "editor_export_platform_extension.h"
+
+void EditorExportPlatformExtension::_bind_methods() {
+ ClassDB::bind_method(D_METHOD("set_config_error", "error_text"), &EditorExportPlatformExtension::set_config_error);
+ ClassDB::bind_method(D_METHOD("get_config_error"), &EditorExportPlatformExtension::get_config_error);
+
+ ClassDB::bind_method(D_METHOD("set_config_missing_templates", "missing_templates"), &EditorExportPlatformExtension::set_config_missing_templates);
+ ClassDB::bind_method(D_METHOD("get_config_missing_templates"), &EditorExportPlatformExtension::get_config_missing_templates);
+
+ GDVIRTUAL_BIND(_get_preset_features, "preset");
+ GDVIRTUAL_BIND(_is_executable, "path");
+ GDVIRTUAL_BIND(_get_export_options);
+ GDVIRTUAL_BIND(_should_update_export_options);
+ GDVIRTUAL_BIND(_get_export_option_visibility, "preset", "option");
+ GDVIRTUAL_BIND(_get_export_option_warning, "preset", "option");
+
+ GDVIRTUAL_BIND(_get_os_name);
+ GDVIRTUAL_BIND(_get_name);
+ GDVIRTUAL_BIND(_get_logo);
+
+ GDVIRTUAL_BIND(_poll_export);
+ GDVIRTUAL_BIND(_get_options_count);
+ GDVIRTUAL_BIND(_get_options_tooltip);
+
+ GDVIRTUAL_BIND(_get_option_icon, "device");
+ GDVIRTUAL_BIND(_get_option_label, "device");
+ GDVIRTUAL_BIND(_get_option_tooltip, "device");
+ GDVIRTUAL_BIND(_get_device_architecture, "device");
+
+ GDVIRTUAL_BIND(_cleanup);
+
+ GDVIRTUAL_BIND(_run, "preset", "device", "debug_flags");
+ GDVIRTUAL_BIND(_get_run_icon);
+
+ GDVIRTUAL_BIND(_can_export, "preset", "debug");
+ GDVIRTUAL_BIND(_has_valid_export_configuration, "preset", "debug");
+ GDVIRTUAL_BIND(_has_valid_project_configuration, "preset");
+
+ GDVIRTUAL_BIND(_get_binary_extensions, "preset");
+
+ GDVIRTUAL_BIND(_export_project, "preset", "debug", "path", "flags");
+ GDVIRTUAL_BIND(_export_pack, "preset", "debug", "path", "flags");
+ GDVIRTUAL_BIND(_export_zip, "preset", "debug", "path", "flags");
+
+ GDVIRTUAL_BIND(_get_platform_features);
+
+ GDVIRTUAL_BIND(_get_debug_protocol);
+}
+
+void EditorExportPlatformExtension::get_preset_features(const Ref<EditorExportPreset> &p_preset, List<String> *r_features) const {
+ Vector<String> ret;
+ if (GDVIRTUAL_REQUIRED_CALL(_get_preset_features, p_preset, ret) && r_features) {
+ for (const String &E : ret) {
+ r_features->push_back(E);
+ }
+ }
+}
+
+bool EditorExportPlatformExtension::is_executable(const String &p_path) const {
+ bool ret = false;
+ GDVIRTUAL_CALL(_is_executable, p_path, ret);
+ return ret;
+}
+
+void EditorExportPlatformExtension::get_export_options(List<ExportOption> *r_options) const {
+ TypedArray<Dictionary> ret;
+ if (GDVIRTUAL_CALL(_get_export_options, ret) && r_options) {
+ for (const Variant &var : ret) {
+ const Dictionary &d = var;
+ ERR_CONTINUE(!d.has("name"));
+ ERR_CONTINUE(!d.has("type"));
+
+ PropertyInfo pinfo = PropertyInfo::from_dict(d);
+ ERR_CONTINUE(pinfo.name.is_empty() && (pinfo.usage & PROPERTY_USAGE_STORAGE));
+ ERR_CONTINUE(pinfo.type < 0 || pinfo.type >= Variant::VARIANT_MAX);
+
+ Variant default_value;
+ if (d.has("default_value")) {
+ default_value = d["default_value"];
+ }
+ bool update_visibility = false;
+ if (d.has("update_visibility")) {
+ update_visibility = d["update_visibility"];
+ }
+ bool required = false;
+ if (d.has("required")) {
+ required = d["required"];
+ }
+
+ r_options->push_back(ExportOption(pinfo, default_value, update_visibility, required));
+ }
+ }
+}
+
+bool EditorExportPlatformExtension::should_update_export_options() {
+ bool ret = false;
+ GDVIRTUAL_CALL(_should_update_export_options, ret);
+ return ret;
+}
+
+bool EditorExportPlatformExtension::get_export_option_visibility(const EditorExportPreset *p_preset, const String &p_option) const {
+ bool ret = true;
+ GDVIRTUAL_CALL(_get_export_option_visibility, Ref<EditorExportPreset>(p_preset), p_option, ret);
+ return ret;
+}
+
+String EditorExportPlatformExtension::get_export_option_warning(const EditorExportPreset *p_preset, const StringName &p_name) const {
+ String ret;
+ GDVIRTUAL_CALL(_get_export_option_warning, Ref<EditorExportPreset>(p_preset), p_name, ret);
+ return ret;
+}
+
+String EditorExportPlatformExtension::get_os_name() const {
+ String ret;
+ GDVIRTUAL_REQUIRED_CALL(_get_os_name, ret);
+ return ret;
+}
+
+String EditorExportPlatformExtension::get_name() const {
+ String ret;
+ GDVIRTUAL_REQUIRED_CALL(_get_name, ret);
+ return ret;
+}
+
+Ref<Texture2D> EditorExportPlatformExtension::get_logo() const {
+ Ref<Texture2D> ret;
+ GDVIRTUAL_REQUIRED_CALL(_get_logo, ret);
+ return ret;
+}
+
+bool EditorExportPlatformExtension::poll_export() {
+ bool ret = false;
+ GDVIRTUAL_CALL(_poll_export, ret);
+ return ret;
+}
+
+int EditorExportPlatformExtension::get_options_count() const {
+ int ret = 0;
+ GDVIRTUAL_CALL(_get_options_count, ret);
+ return ret;
+}
+
+String EditorExportPlatformExtension::get_options_tooltip() const {
+ String ret;
+ GDVIRTUAL_CALL(_get_options_tooltip, ret);
+ return ret;
+}
+
+Ref<ImageTexture> EditorExportPlatformExtension::get_option_icon(int p_index) const {
+ Ref<ImageTexture> ret;
+ if (GDVIRTUAL_CALL(_get_option_icon, p_index, ret)) {
+ return ret;
+ }
+ return EditorExportPlatform::get_option_icon(p_index);
+}
+
+String EditorExportPlatformExtension::get_option_label(int p_device) const {
+ String ret;
+ GDVIRTUAL_CALL(_get_option_label, p_device, ret);
+ return ret;
+}
+
+String EditorExportPlatformExtension::get_option_tooltip(int p_device) const {
+ String ret;
+ GDVIRTUAL_CALL(_get_option_tooltip, p_device, ret);
+ return ret;
+}
+
+String EditorExportPlatformExtension::get_device_architecture(int p_device) const {
+ String ret;
+ GDVIRTUAL_CALL(_get_device_architecture, p_device, ret);
+ return ret;
+}
+
+void EditorExportPlatformExtension::cleanup() {
+ GDVIRTUAL_CALL(_cleanup);
+}
+
+Error EditorExportPlatformExtension::run(const Ref<EditorExportPreset> &p_preset, int p_device, BitField<EditorExportPlatform::DebugFlags> p_debug_flags) {
+ Error ret = OK;
+ GDVIRTUAL_CALL(_run, p_preset, p_device, p_debug_flags, ret);
+ return ret;
+}
+
+Ref<Texture2D> EditorExportPlatformExtension::get_run_icon() const {
+ Ref<Texture2D> ret;
+ if (GDVIRTUAL_CALL(_get_run_icon, ret)) {
+ return ret;
+ }
+ return EditorExportPlatform::get_run_icon();
+}
+
+bool EditorExportPlatformExtension::can_export(const Ref<EditorExportPreset> &p_preset, String &r_error, bool &r_missing_templates, bool p_debug) const {
+ bool ret = false;
+ config_error = r_error;
+ config_missing_templates = r_missing_templates;
+ if (GDVIRTUAL_CALL(_can_export, p_preset, p_debug, ret)) {
+ r_error = config_error;
+ r_missing_templates = config_missing_templates;
+ return ret;
+ }
+ return EditorExportPlatform::can_export(p_preset, r_error, r_missing_templates, p_debug);
+}
+
+bool EditorExportPlatformExtension::has_valid_export_configuration(const Ref<EditorExportPreset> &p_preset, String &r_error, bool &r_missing_templates, bool p_debug) const {
+ bool ret = false;
+ config_error = r_error;
+ config_missing_templates = r_missing_templates;
+ if (GDVIRTUAL_REQUIRED_CALL(_has_valid_export_configuration, p_preset, p_debug, ret)) {
+ r_error = config_error;
+ r_missing_templates = config_missing_templates;
+ }
+ return ret;
+}
+
+bool EditorExportPlatformExtension::has_valid_project_configuration(const Ref<EditorExportPreset> &p_preset, String &r_error) const {
+ bool ret = false;
+ config_error = r_error;
+ if (GDVIRTUAL_REQUIRED_CALL(_has_valid_project_configuration, p_preset, ret)) {
+ r_error = config_error;
+ }
+ return ret;
+}
+
+List<String> EditorExportPlatformExtension::get_binary_extensions(const Ref<EditorExportPreset> &p_preset) const {
+ List<String> ret_list;
+ Vector<String> ret;
+ if (GDVIRTUAL_REQUIRED_CALL(_get_binary_extensions, p_preset, ret)) {
+ for (const String &E : ret) {
+ ret_list.push_back(E);
+ }
+ }
+ return ret_list;
+}
+
+Error EditorExportPlatformExtension::export_project(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, BitField<EditorExportPlatform::DebugFlags> p_flags) {
+ ExportNotifier notifier(*this, p_preset, p_debug, p_path, p_flags);
+
+ Error ret = FAILED;
+ GDVIRTUAL_REQUIRED_CALL(_export_project, p_preset, p_debug, p_path, p_flags, ret);
+ return ret;
+}
+
+Error EditorExportPlatformExtension::export_pack(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, BitField<EditorExportPlatform::DebugFlags> p_flags) {
+ ExportNotifier notifier(*this, p_preset, p_debug, p_path, p_flags);
+
+ Error ret = FAILED;
+ if (GDVIRTUAL_CALL(_export_pack, p_preset, p_debug, p_path, p_flags, ret)) {
+ return ret;
+ }
+ return save_pack(p_preset, p_debug, p_path);
+}
+
+Error EditorExportPlatformExtension::export_zip(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, BitField<EditorExportPlatform::DebugFlags> p_flags) {
+ ExportNotifier notifier(*this, p_preset, p_debug, p_path, p_flags);
+
+ Error ret = FAILED;
+ if (GDVIRTUAL_CALL(_export_zip, p_preset, p_debug, p_path, p_flags, ret)) {
+ return ret;
+ }
+ return save_zip(p_preset, p_debug, p_path);
+}
+
+void EditorExportPlatformExtension::get_platform_features(List<String> *r_features) const {
+ Vector<String> ret;
+ if (GDVIRTUAL_REQUIRED_CALL(_get_platform_features, ret) && r_features) {
+ for (const String &E : ret) {
+ r_features->push_back(E);
+ }
+ }
+}
+
+String EditorExportPlatformExtension::get_debug_protocol() const {
+ String ret;
+ if (GDVIRTUAL_CALL(_get_debug_protocol, ret)) {
+ return ret;
+ }
+ return EditorExportPlatform::get_debug_protocol();
+}
+
+EditorExportPlatformExtension::EditorExportPlatformExtension() {
+ //NOP
+}
+
+EditorExportPlatformExtension::~EditorExportPlatformExtension() {
+ //NOP
+}
diff --git a/editor/export/editor_export_platform_extension.h b/editor/export/editor_export_platform_extension.h
new file mode 100644
index 0000000000..6391e65ac1
--- /dev/null
+++ b/editor/export/editor_export_platform_extension.h
@@ -0,0 +1,149 @@
+/**************************************************************************/
+/* editor_export_platform_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 EDITOR_EXPORT_PLATFORM_EXTENSION_H
+#define EDITOR_EXPORT_PLATFORM_EXTENSION_H
+
+#include "editor_export_platform.h"
+#include "editor_export_preset.h"
+
+class EditorExportPlatformExtension : public EditorExportPlatform {
+ GDCLASS(EditorExportPlatformExtension, EditorExportPlatform);
+
+ mutable String config_error;
+ mutable bool config_missing_templates = false;
+
+protected:
+ static void _bind_methods();
+
+public:
+ virtual void get_preset_features(const Ref<EditorExportPreset> &p_preset, List<String> *r_features) const override;
+ GDVIRTUAL1RC(Vector<String>, _get_preset_features, Ref<EditorExportPreset>);
+
+ virtual bool is_executable(const String &p_path) const override;
+ GDVIRTUAL1RC(bool, _is_executable, const String &);
+
+ virtual void get_export_options(List<ExportOption> *r_options) const override;
+ GDVIRTUAL0RC(TypedArray<Dictionary>, _get_export_options);
+
+ virtual bool should_update_export_options() override;
+ GDVIRTUAL0R(bool, _should_update_export_options);
+
+ virtual bool get_export_option_visibility(const EditorExportPreset *p_preset, const String &p_option) const override;
+ GDVIRTUAL2RC(bool, _get_export_option_visibility, Ref<EditorExportPreset>, const String &);
+
+ virtual String get_export_option_warning(const EditorExportPreset *p_preset, const StringName &p_name) const override;
+ GDVIRTUAL2RC(String, _get_export_option_warning, Ref<EditorExportPreset>, const StringName &);
+
+ virtual String get_os_name() const override;
+ GDVIRTUAL0RC(String, _get_os_name);
+
+ virtual String get_name() const override;
+ GDVIRTUAL0RC(String, _get_name);
+
+ virtual Ref<Texture2D> get_logo() const override;
+ GDVIRTUAL0RC(Ref<Texture2D>, _get_logo);
+
+ virtual bool poll_export() override;
+ GDVIRTUAL0R(bool, _poll_export);
+
+ virtual int get_options_count() const override;
+ GDVIRTUAL0RC(int, _get_options_count);
+
+ virtual String get_options_tooltip() const override;
+ GDVIRTUAL0RC(String, _get_options_tooltip);
+
+ virtual Ref<ImageTexture> get_option_icon(int p_index) const override;
+ GDVIRTUAL1RC(Ref<ImageTexture>, _get_option_icon, int);
+
+ virtual String get_option_label(int p_device) const override;
+ GDVIRTUAL1RC(String, _get_option_label, int);
+
+ virtual String get_option_tooltip(int p_device) const override;
+ GDVIRTUAL1RC(String, _get_option_tooltip, int);
+
+ virtual String get_device_architecture(int p_device) const override;
+ GDVIRTUAL1RC(String, _get_device_architecture, int);
+
+ virtual void cleanup() override;
+ GDVIRTUAL0(_cleanup);
+
+ virtual Error run(const Ref<EditorExportPreset> &p_preset, int p_device, BitField<EditorExportPlatform::DebugFlags> p_debug_flags) override;
+ GDVIRTUAL3R(Error, _run, Ref<EditorExportPreset>, int, BitField<EditorExportPlatform::DebugFlags>);
+
+ virtual Ref<Texture2D> get_run_icon() const override;
+ GDVIRTUAL0RC(Ref<Texture2D>, _get_run_icon);
+
+ void set_config_error(const String &p_error) const {
+ config_error = p_error;
+ }
+ String get_config_error() const {
+ return config_error;
+ }
+
+ void set_config_missing_templates(bool p_missing_templates) const {
+ config_missing_templates = p_missing_templates;
+ }
+ bool get_config_missing_templates() const {
+ return config_missing_templates;
+ }
+
+ virtual bool can_export(const Ref<EditorExportPreset> &p_preset, String &r_error, bool &r_missing_templates, bool p_debug = false) const override;
+ GDVIRTUAL2RC(bool, _can_export, Ref<EditorExportPreset>, bool);
+
+ virtual bool has_valid_export_configuration(const Ref<EditorExportPreset> &p_preset, String &r_error, bool &r_missing_templates, bool p_debug = false) const override;
+ GDVIRTUAL2RC(bool, _has_valid_export_configuration, Ref<EditorExportPreset>, bool);
+
+ virtual bool has_valid_project_configuration(const Ref<EditorExportPreset> &p_preset, String &r_error) const override;
+ GDVIRTUAL1RC(bool, _has_valid_project_configuration, Ref<EditorExportPreset>);
+
+ virtual List<String> get_binary_extensions(const Ref<EditorExportPreset> &p_preset) const override;
+ GDVIRTUAL1RC(Vector<String>, _get_binary_extensions, Ref<EditorExportPreset>);
+
+ virtual Error export_project(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, BitField<EditorExportPlatform::DebugFlags> p_flags = 0) override;
+ GDVIRTUAL4R(Error, _export_project, Ref<EditorExportPreset>, bool, const String &, BitField<EditorExportPlatform::DebugFlags>);
+
+ virtual Error export_pack(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, BitField<EditorExportPlatform::DebugFlags> p_flags = 0) override;
+ GDVIRTUAL4R(Error, _export_pack, Ref<EditorExportPreset>, bool, const String &, BitField<EditorExportPlatform::DebugFlags>);
+
+ virtual Error export_zip(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, BitField<EditorExportPlatform::DebugFlags> p_flags = 0) override;
+ GDVIRTUAL4R(Error, _export_zip, Ref<EditorExportPreset>, bool, const String &, BitField<EditorExportPlatform::DebugFlags>);
+
+ virtual void get_platform_features(List<String> *r_features) const override;
+ GDVIRTUAL0RC(Vector<String>, _get_platform_features);
+
+ virtual String get_debug_protocol() const override;
+ GDVIRTUAL0RC(String, _get_debug_protocol);
+
+ EditorExportPlatformExtension();
+ ~EditorExportPlatformExtension();
+};
+
+#endif // EDITOR_EXPORT_PLATFORM_EXTENSION_H
diff --git a/editor/export/editor_export_platform_pc.cpp b/editor/export/editor_export_platform_pc.cpp
index cdaf18b346..24d89b7f34 100644
--- a/editor/export/editor_export_platform_pc.cpp
+++ b/editor/export/editor_export_platform_pc.cpp
@@ -115,7 +115,7 @@ bool EditorExportPlatformPC::has_valid_project_configuration(const Ref<EditorExp
return true;
}
-Error EditorExportPlatformPC::export_project(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, int p_flags) {
+Error EditorExportPlatformPC::export_project(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, BitField<EditorExportPlatform::DebugFlags> p_flags) {
ExportNotifier notifier(*this, p_preset, p_debug, p_path, p_flags);
Error err = prepare_template(p_preset, p_debug, p_path, p_flags);
@@ -129,7 +129,7 @@ Error EditorExportPlatformPC::export_project(const Ref<EditorExportPreset> &p_pr
return err;
}
-Error EditorExportPlatformPC::prepare_template(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, int p_flags) {
+Error EditorExportPlatformPC::prepare_template(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, BitField<EditorExportPlatform::DebugFlags> p_flags) {
if (!DirAccess::exists(p_path.get_base_dir())) {
add_message(EXPORT_MESSAGE_ERROR, TTR("Prepare Template"), TTR("The given export path doesn't exist."));
return ERR_FILE_BAD_PATH;
@@ -182,7 +182,7 @@ Error EditorExportPlatformPC::prepare_template(const Ref<EditorExportPreset> &p_
return err;
}
-Error EditorExportPlatformPC::export_project_data(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, int p_flags) {
+Error EditorExportPlatformPC::export_project_data(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, BitField<EditorExportPlatform::DebugFlags> p_flags) {
String pck_path;
if (p_preset->get("binary_format/embed_pck")) {
pck_path = p_path;
diff --git a/editor/export/editor_export_platform_pc.h b/editor/export/editor_export_platform_pc.h
index 53574c2333..668ddaf47e 100644
--- a/editor/export/editor_export_platform_pc.h
+++ b/editor/export/editor_export_platform_pc.h
@@ -54,13 +54,13 @@ public:
virtual bool has_valid_export_configuration(const Ref<EditorExportPreset> &p_preset, String &r_error, bool &r_missing_templates, bool p_debug = false) const override;
virtual bool has_valid_project_configuration(const Ref<EditorExportPreset> &p_preset, String &r_error) const override;
- virtual Error export_project(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, int p_flags = 0) override;
+ virtual Error export_project(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, BitField<EditorExportPlatform::DebugFlags> p_flags = 0) override;
virtual Error sign_shared_object(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path);
virtual String get_template_file_name(const String &p_target, const String &p_arch) const = 0;
- virtual Error prepare_template(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, int p_flags);
- virtual Error modify_template(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, int p_flags) { return OK; };
- virtual Error export_project_data(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, int p_flags);
+ virtual Error prepare_template(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, BitField<EditorExportPlatform::DebugFlags> p_flags);
+ virtual Error modify_template(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, BitField<EditorExportPlatform::DebugFlags> p_flags) { return OK; }
+ virtual Error export_project_data(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, BitField<EditorExportPlatform::DebugFlags> p_flags);
void set_name(const String &p_name);
void set_os_name(const String &p_name);
diff --git a/editor/export/editor_export_plugin.cpp b/editor/export/editor_export_plugin.cpp
index 28d0750d5a..6bb21d7fd4 100644
--- a/editor/export/editor_export_plugin.cpp
+++ b/editor/export/editor_export_plugin.cpp
@@ -48,6 +48,14 @@ Ref<EditorExportPreset> EditorExportPlugin::get_export_preset() const {
return export_preset;
}
+Ref<EditorExportPlatform> EditorExportPlugin::get_export_platform() const {
+ if (export_preset.is_valid()) {
+ return export_preset->get_platform();
+ } else {
+ return Ref<EditorExportPlatform>();
+ }
+}
+
void EditorExportPlugin::add_file(const String &p_path, const Vector<uint8_t> &p_file, bool p_remap) {
ExtraFile ef;
ef.data = p_file;
@@ -132,7 +140,7 @@ Vector<String> EditorExportPlugin::get_ios_project_static_libs() const {
}
Variant EditorExportPlugin::get_option(const StringName &p_name) const {
- ERR_FAIL_NULL_V(export_preset, Variant());
+ ERR_FAIL_COND_V(export_preset.is_null(), Variant());
return export_preset->get(p_name);
}
@@ -321,6 +329,9 @@ void EditorExportPlugin::_bind_methods() {
ClassDB::bind_method(D_METHOD("skip"), &EditorExportPlugin::skip);
ClassDB::bind_method(D_METHOD("get_option", "name"), &EditorExportPlugin::get_option);
+ ClassDB::bind_method(D_METHOD("get_export_preset"), &EditorExportPlugin::get_export_preset);
+ ClassDB::bind_method(D_METHOD("get_export_platform"), &EditorExportPlugin::get_export_platform);
+
GDVIRTUAL_BIND(_export_file, "path", "type", "features");
GDVIRTUAL_BIND(_export_begin, "features", "is_debug", "path", "flags");
GDVIRTUAL_BIND(_export_end);
diff --git a/editor/export/editor_export_plugin.h b/editor/export/editor_export_plugin.h
index 56eea85010..7a355614c7 100644
--- a/editor/export/editor_export_plugin.h
+++ b/editor/export/editor_export_plugin.h
@@ -91,6 +91,7 @@ class EditorExportPlugin : public RefCounted {
protected:
void set_export_preset(const Ref<EditorExportPreset> &p_preset);
Ref<EditorExportPreset> get_export_preset() const;
+ Ref<EditorExportPlatform> get_export_platform() const;
void add_file(const String &p_path, const Vector<uint8_t> &p_file, bool p_remap);
void add_shared_object(const String &p_path, const Vector<String> &tags, const String &p_target = String());
diff --git a/editor/export/editor_export_preset.cpp b/editor/export/editor_export_preset.cpp
index e2e3e9d154..9f805666d0 100644
--- a/editor/export/editor_export_preset.cpp
+++ b/editor/export/editor_export_preset.cpp
@@ -62,6 +62,48 @@ bool EditorExportPreset::_get(const StringName &p_name, Variant &r_ret) const {
void EditorExportPreset::_bind_methods() {
ClassDB::bind_method(D_METHOD("_get_property_warning", "name"), &EditorExportPreset::_get_property_warning);
+
+ ClassDB::bind_method(D_METHOD("has", "property"), &EditorExportPreset::has);
+
+ ClassDB::bind_method(D_METHOD("get_files_to_export"), &EditorExportPreset::get_files_to_export);
+ ClassDB::bind_method(D_METHOD("get_customized_files"), &EditorExportPreset::get_customized_files);
+ ClassDB::bind_method(D_METHOD("get_customized_files_count"), &EditorExportPreset::get_customized_files_count);
+ ClassDB::bind_method(D_METHOD("has_export_file", "path"), &EditorExportPreset::has_export_file);
+ ClassDB::bind_method(D_METHOD("get_file_export_mode", "path", "default"), &EditorExportPreset::get_file_export_mode, DEFVAL(MODE_FILE_NOT_CUSTOMIZED));
+
+ ClassDB::bind_method(D_METHOD("get_preset_name"), &EditorExportPreset::get_name);
+ ClassDB::bind_method(D_METHOD("is_runnable"), &EditorExportPreset::is_runnable);
+ ClassDB::bind_method(D_METHOD("are_advanced_options_enabled"), &EditorExportPreset::are_advanced_options_enabled);
+ ClassDB::bind_method(D_METHOD("is_dedicated_server"), &EditorExportPreset::is_dedicated_server);
+ ClassDB::bind_method(D_METHOD("get_export_filter"), &EditorExportPreset::get_export_filter);
+ ClassDB::bind_method(D_METHOD("get_include_filter"), &EditorExportPreset::get_include_filter);
+ ClassDB::bind_method(D_METHOD("get_exclude_filter"), &EditorExportPreset::get_exclude_filter);
+ ClassDB::bind_method(D_METHOD("get_custom_features"), &EditorExportPreset::get_custom_features);
+ ClassDB::bind_method(D_METHOD("get_export_path"), &EditorExportPreset::get_export_path);
+ ClassDB::bind_method(D_METHOD("get_encryption_in_filter"), &EditorExportPreset::get_enc_in_filter);
+ ClassDB::bind_method(D_METHOD("get_encryption_ex_filter"), &EditorExportPreset::get_enc_ex_filter);
+ ClassDB::bind_method(D_METHOD("get_encrypt_pck"), &EditorExportPreset::get_enc_pck);
+ ClassDB::bind_method(D_METHOD("get_encrypt_directory"), &EditorExportPreset::get_enc_directory);
+ ClassDB::bind_method(D_METHOD("get_encryption_key"), &EditorExportPreset::get_script_encryption_key);
+ ClassDB::bind_method(D_METHOD("get_script_export_mode"), &EditorExportPreset::get_script_export_mode);
+
+ ClassDB::bind_method(D_METHOD("get_or_env", "name", "env_var"), &EditorExportPreset::_get_or_env);
+ ClassDB::bind_method(D_METHOD("get_version", "name", "windows_version"), &EditorExportPreset::get_version);
+
+ BIND_ENUM_CONSTANT(EXPORT_ALL_RESOURCES);
+ BIND_ENUM_CONSTANT(EXPORT_SELECTED_SCENES);
+ BIND_ENUM_CONSTANT(EXPORT_SELECTED_RESOURCES);
+ BIND_ENUM_CONSTANT(EXCLUDE_SELECTED_RESOURCES);
+ BIND_ENUM_CONSTANT(EXPORT_CUSTOMIZED);
+
+ BIND_ENUM_CONSTANT(MODE_FILE_NOT_CUSTOMIZED);
+ BIND_ENUM_CONSTANT(MODE_FILE_STRIP);
+ BIND_ENUM_CONSTANT(MODE_FILE_KEEP);
+ BIND_ENUM_CONSTANT(MODE_FILE_REMOVE);
+
+ BIND_ENUM_CONSTANT(MODE_SCRIPT_TEXT);
+ BIND_ENUM_CONSTANT(MODE_SCRIPT_BINARY_TOKENS);
+ BIND_ENUM_CONSTANT(MODE_SCRIPT_BINARY_TOKENS_COMPRESSED);
}
String EditorExportPreset::_get_property_warning(const StringName &p_name) const {
diff --git a/editor/export/editor_export_preset.h b/editor/export/editor_export_preset.h
index c6a8808af1..f220477461 100644
--- a/editor/export/editor_export_preset.h
+++ b/editor/export/editor_export_preset.h
@@ -168,6 +168,9 @@ public:
void set_script_export_mode(int p_mode);
int get_script_export_mode() const;
+ Variant _get_or_env(const StringName &p_name, const String &p_env_var) const {
+ return get_or_env(p_name, p_env_var);
+ }
Variant get_or_env(const StringName &p_name, const String &p_env_var, bool *r_valid = nullptr) const;
// Return the preset's version number, or fall back to the
@@ -183,4 +186,8 @@ public:
EditorExportPreset();
};
+VARIANT_ENUM_CAST(EditorExportPreset::ExportFilter);
+VARIANT_ENUM_CAST(EditorExportPreset::FileExportMode);
+VARIANT_ENUM_CAST(EditorExportPreset::ScriptExportMode);
+
#endif // EDITOR_EXPORT_PRESET_H
diff --git a/editor/export/export_template_manager.cpp b/editor/export/export_template_manager.cpp
index 9dfbe515d2..5360b6fb60 100644
--- a/editor/export/export_template_manager.cpp
+++ b/editor/export/export_template_manager.cpp
@@ -47,6 +47,32 @@
#include "scene/gui/tree.h"
#include "scene/main/http_request.h"
+enum DownloadsAvailability {
+ DOWNLOADS_AVAILABLE,
+ DOWNLOADS_NOT_AVAILABLE_IN_OFFLINE_MODE,
+ DOWNLOADS_NOT_AVAILABLE_FOR_DEV_BUILDS,
+};
+
+static DownloadsAvailability _get_downloads_availability() {
+ const int network_mode = EDITOR_GET("network/connection/network_mode");
+ if (network_mode == EditorSettings::NETWORK_OFFLINE) {
+ return DOWNLOADS_NOT_AVAILABLE_IN_OFFLINE_MODE;
+ }
+
+ // Downloadable export templates are only available for stable and official alpha/beta/RC builds
+ // (which always have a number following their status, e.g. "alpha1").
+ // Therefore, don't display download-related features when using a development version
+ // (whose builds aren't numbered).
+ if (String(VERSION_STATUS) == String("dev") ||
+ String(VERSION_STATUS) == String("alpha") ||
+ String(VERSION_STATUS) == String("beta") ||
+ String(VERSION_STATUS) == String("rc")) {
+ return DOWNLOADS_NOT_AVAILABLE_FOR_DEV_BUILDS;
+ }
+
+ return DOWNLOADS_AVAILABLE;
+}
+
void ExportTemplateManager::_update_template_status() {
// Fetch installed templates from the file system.
Ref<DirAccess> da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM);
@@ -545,7 +571,7 @@ bool ExportTemplateManager::_install_file_selected(const String &p_file, bool p_
unzClose(pkg);
_update_template_status();
- EditorSettings::get_singleton()->set_meta("export_template_download_directory", p_file.get_base_dir());
+ EditorSettings::get_singleton()->set("_export_template_download_directory", p_file.get_base_dir());
return true;
}
@@ -641,9 +667,55 @@ void ExportTemplateManager::_open_template_folder(const String &p_version) {
void ExportTemplateManager::popup_manager() {
_update_template_status();
- if (downloads_available && !is_downloading_templates) {
- _refresh_mirrors();
+
+ switch (_get_downloads_availability()) {
+ case DOWNLOADS_AVAILABLE: {
+ current_missing_label->set_text(TTR("Export templates are missing. Download them or install from a file."));
+
+ mirrors_list->clear();
+ mirrors_list->add_item(TTR("Best available mirror"), 0);
+ mirrors_list->set_disabled(false);
+ mirrors_list->set_tooltip_text("");
+
+ mirror_options_button->set_disabled(false);
+
+ download_current_button->set_disabled(false);
+ download_current_button->set_tooltip_text("");
+
+ if (!is_downloading_templates) {
+ _refresh_mirrors();
+ }
+ } break;
+
+ case DOWNLOADS_NOT_AVAILABLE_IN_OFFLINE_MODE: {
+ current_missing_label->set_text(TTR("Export templates are missing. Install them from a file."));
+
+ mirrors_list->clear();
+ mirrors_list->add_item(TTR("Not available in offline mode"), 0);
+ mirrors_list->set_disabled(true);
+ mirrors_list->set_tooltip_text(TTR("Template downloading is disabled in offline mode."));
+
+ mirror_options_button->set_disabled(true);
+
+ download_current_button->set_disabled(true);
+ download_current_button->set_tooltip_text(TTR("Template downloading is disabled in offline mode."));
+ } break;
+
+ case DOWNLOADS_NOT_AVAILABLE_FOR_DEV_BUILDS: {
+ current_missing_label->set_text(TTR("Export templates are missing. Install them from a file."));
+
+ mirrors_list->clear();
+ mirrors_list->add_item(TTR("No templates for development builds"), 0);
+ mirrors_list->set_disabled(true);
+ mirrors_list->set_tooltip_text(TTR("Official export templates aren't available for development builds."));
+
+ mirror_options_button->set_disabled(true);
+
+ download_current_button->set_disabled(true);
+ download_current_button->set_tooltip_text(TTR("Official export templates aren't available for development builds."));
+ } break;
}
+
popup_centered(Size2(720, 280) * EDSCALE);
}
@@ -866,16 +938,6 @@ ExportTemplateManager::ExportTemplateManager() {
set_hide_on_ok(false);
set_ok_button_text(TTR("Close"));
- // Downloadable export templates are only available for stable and official alpha/beta/RC builds
- // (which always have a number following their status, e.g. "alpha1").
- // Therefore, don't display download-related features when using a development version
- // (whose builds aren't numbered).
- downloads_available =
- String(VERSION_STATUS) != String("dev") &&
- String(VERSION_STATUS) != String("alpha") &&
- String(VERSION_STATUS) != String("beta") &&
- String(VERSION_STATUS) != String("rc");
-
VBoxContainer *main_vb = memnew(VBoxContainer);
add_child(main_vb);
@@ -898,11 +960,6 @@ ExportTemplateManager::ExportTemplateManager() {
current_missing_label->set_h_size_flags(Control::SIZE_EXPAND_FILL);
current_missing_label->set_horizontal_alignment(HORIZONTAL_ALIGNMENT_RIGHT);
- if (downloads_available) {
- current_missing_label->set_text(TTR("Export templates are missing. Download them or install from a file."));
- } else {
- current_missing_label->set_text(TTR("Export templates are missing. Install them from a file."));
- }
current_hb->add_child(current_missing_label);
// Status: Current version is installed.
@@ -957,12 +1014,6 @@ ExportTemplateManager::ExportTemplateManager() {
mirrors_list = memnew(OptionButton);
mirrors_list->set_custom_minimum_size(Size2(280, 0) * EDSCALE);
- if (downloads_available) {
- mirrors_list->add_item(TTR("Best available mirror"), 0);
- } else {
- mirrors_list->add_item(TTR("(no templates for development builds)"), 0);
- mirrors_list->set_disabled(true);
- }
download_install_hb->add_child(mirrors_list);
request_mirrors = memnew(HTTPRequest);
@@ -972,24 +1023,17 @@ ExportTemplateManager::ExportTemplateManager() {
mirror_options_button = memnew(MenuButton);
mirror_options_button->get_popup()->add_item(TTR("Open in Web Browser"), VISIT_WEB_MIRROR);
mirror_options_button->get_popup()->add_item(TTR("Copy Mirror URL"), COPY_MIRROR_URL);
- mirror_options_button->set_disabled(!downloads_available);
download_install_hb->add_child(mirror_options_button);
mirror_options_button->get_popup()->connect(SceneStringName(id_pressed), callable_mp(this, &ExportTemplateManager::_mirror_options_button_cbk));
download_install_hb->add_spacer();
- Button *download_current_button = memnew(Button);
+ download_current_button = memnew(Button);
download_current_button->set_text(TTR("Download and Install"));
download_current_button->set_tooltip_text(TTR("Download and install templates for the current version from the best possible mirror."));
download_install_hb->add_child(download_current_button);
download_current_button->connect(SceneStringName(pressed), callable_mp(this, &ExportTemplateManager::_download_current));
- // Update downloads buttons to prevent unsupported downloads.
- if (!downloads_available) {
- download_current_button->set_disabled(true);
- download_current_button->set_tooltip_text(TTR("Official export templates aren't available for development builds."));
- }
-
HBoxContainer *install_file_hb = memnew(HBoxContainer);
install_file_hb->set_alignment(BoxContainer::ALIGNMENT_END);
install_options_vb->add_child(install_file_hb);
@@ -1058,7 +1102,7 @@ ExportTemplateManager::ExportTemplateManager() {
install_file_dialog->set_title(TTR("Select Template File"));
install_file_dialog->set_access(FileDialog::ACCESS_FILESYSTEM);
install_file_dialog->set_file_mode(FileDialog::FILE_MODE_OPEN_FILE);
- install_file_dialog->set_current_dir(EditorSettings::get_singleton()->get_meta("export_template_download_directory", ""));
+ install_file_dialog->set_current_dir(EDITOR_DEF("_export_template_download_directory", ""));
install_file_dialog->add_filter("*.tpz", TTR("Godot Export Templates"));
install_file_dialog->connect("file_selected", callable_mp(this, &ExportTemplateManager::_install_file_selected).bind(false));
add_child(install_file_dialog);
diff --git a/editor/export/export_template_manager.h b/editor/export/export_template_manager.h
index 5227e43a6e..ccfe568d32 100644
--- a/editor/export/export_template_manager.h
+++ b/editor/export/export_template_manager.h
@@ -46,7 +46,6 @@ class ExportTemplateManager : public AcceptDialog {
GDCLASS(ExportTemplateManager, AcceptDialog);
bool current_version_exists = false;
- bool downloads_available = true;
bool mirrors_available = false;
bool is_refreshing_mirrors = false;
bool is_downloading_templates = false;
@@ -74,6 +73,7 @@ class ExportTemplateManager : public AcceptDialog {
Label *download_progress_label = nullptr;
HTTPRequest *download_templates = nullptr;
Button *install_file_button = nullptr;
+ Button *download_current_button = nullptr;
HTTPRequest *request_mirrors = nullptr;
enum TemplatesAction {
diff --git a/editor/export/project_export.cpp b/editor/export/project_export.cpp
index 351afa3810..3ad8ab0b19 100644
--- a/editor/export/project_export.cpp
+++ b/editor/export/project_export.cpp
@@ -903,7 +903,7 @@ bool ProjectExportDialog::_fill_tree(EditorFileSystemDirectory *p_dir, TreeItem
if (p_export_filter == EditorExportPreset::EXPORT_SELECTED_SCENES && type != "PackedScene") {
continue;
}
- if (type == "TextFile") {
+ if (type == "TextFile" || type == "OtherFile") {
continue;
}
@@ -1410,12 +1410,12 @@ ProjectExportDialog::ProjectExportDialog() {
sec_scroll_container->add_child(sec_vb);
enc_pck = memnew(CheckButton);
- enc_pck->connect("toggled", callable_mp(this, &ProjectExportDialog::_enc_pck_changed));
+ enc_pck->connect(SceneStringName(toggled), callable_mp(this, &ProjectExportDialog::_enc_pck_changed));
enc_pck->set_text(TTR("Encrypt Exported PCK"));
sec_vb->add_child(enc_pck);
enc_directory = memnew(CheckButton);
- enc_directory->connect("toggled", callable_mp(this, &ProjectExportDialog::_enc_directory_changed));
+ enc_directory->connect(SceneStringName(toggled), callable_mp(this, &ProjectExportDialog::_enc_directory_changed));
enc_directory->set_text(TTR("Encrypt Index (File Names and Info)"));
sec_vb->add_child(enc_directory);
diff --git a/editor/filesystem_dock.cpp b/editor/filesystem_dock.cpp
index a9de8e3bc5..f7e81b329c 100644
--- a/editor/filesystem_dock.cpp
+++ b/editor/filesystem_dock.cpp
@@ -49,6 +49,7 @@
#include "editor/gui/editor_scene_tabs.h"
#include "editor/import/3d/scene_import_settings.h"
#include "editor/import_dock.h"
+#include "editor/plugins/editor_context_menu_plugin.h"
#include "editor/plugins/editor_resource_tooltip_plugins.h"
#include "editor/scene_create_dialog.h"
#include "editor/scene_tree_dock.h"
@@ -270,7 +271,7 @@ bool FileSystemDock::_create_tree(TreeItem *p_parent, EditorFileSystemDirectory
List<FileInfo> file_list;
for (int i = 0; i < p_dir->get_file_count(); i++) {
String file_type = p_dir->get_file_type(i);
- if (file_type != "TextFile" && _is_file_type_disabled_by_feature_profile(file_type)) {
+ if (file_type != "TextFile" && file_type != "OtherFile" && _is_file_type_disabled_by_feature_profile(file_type)) {
// If type is disabled, file won't be displayed.
continue;
}
@@ -1216,7 +1217,7 @@ void FileSystemDock::_select_file(const String &p_path, bool p_select_in_favorit
}
if (is_imported) {
- SceneImportSettingsDialog::get_singleton()->open_settings(p_path, resource_type == "AnimationLibrary");
+ SceneImportSettingsDialog::get_singleton()->open_settings(p_path, resource_type);
} else if (resource_type == "PackedScene") {
EditorNode::get_singleton()->open_request(fpath);
} else {
@@ -2556,6 +2557,9 @@ void FileSystemDock::_file_option(int p_option, const Vector<String> &p_selected
String dir = ProjectSettings::get_singleton()->globalize_path(fpath);
ScriptEditor::get_singleton()->open_text_file_create_dialog(dir);
} break;
+ default:
+ EditorNode::get_editor_data().filesystem_options_pressed(EditorData::CONTEXT_SLOT_FILESYSTEM, p_option, p_selected);
+ break;
}
}
@@ -3178,6 +3182,8 @@ void FileSystemDock::_file_and_folders_fill_popup(PopupMenu *p_popup, const Vect
new_menu->add_icon_item(get_editor_theme_icon(SNAME("Script")), TTR("Script..."), FILE_NEW_SCRIPT);
new_menu->add_icon_item(get_editor_theme_icon(SNAME("Object")), TTR("Resource..."), FILE_NEW_RESOURCE);
new_menu->add_icon_item(get_editor_theme_icon(SNAME("TextFile")), TTR("TextFile..."), FILE_NEW_TEXTFILE);
+
+ EditorNode::get_editor_data().add_options_from_plugins(new_menu, EditorData::CONTEXT_SUBMENU_SLOT_FILESYSTEM_CREATE, p_paths);
p_popup->add_separator();
}
@@ -3317,6 +3323,8 @@ void FileSystemDock::_file_and_folders_fill_popup(PopupMenu *p_popup, const Vect
current_path = fpath;
}
+
+ EditorNode::get_editor_data().add_options_from_plugins(p_popup, EditorData::CONTEXT_SLOT_FILESYSTEM, p_paths);
}
void FileSystemDock::_tree_rmb_select(const Vector2 &p_pos, MouseButton p_button) {
@@ -3410,6 +3418,11 @@ void FileSystemDock::_file_list_empty_clicked(const Vector2 &p_pos, MouseButton
current_path = current_path_line_edit->get_text();
+ // Favorites isn't a directory so don't show menu.
+ if (current_path == "Favorites") {
+ return;
+ }
+
file_list_popup->clear();
file_list_popup->reset_size();
@@ -3546,6 +3559,10 @@ void FileSystemDock::_tree_gui_input(Ref<InputEvent> p_event) {
} else if (ED_IS_SHORTCUT("editor/open_search", p_event)) {
focus_on_filter();
} else {
+ int match_option = EditorNode::get_editor_data().match_context_menu_shortcut(EditorData::CONTEXT_SLOT_FILESYSTEM, p_event);
+ if (match_option) {
+ _tree_rmb_option(match_option);
+ }
return;
}
diff --git a/editor/groups_editor.cpp b/editor/groups_editor.cpp
index a5f7e8556c..ce2dbe7cb1 100644
--- a/editor/groups_editor.cpp
+++ b/editor/groups_editor.cpp
@@ -648,7 +648,7 @@ void GroupsEditor::_show_add_group_dialog() {
add_group_description->set_editable(false);
gc->add_child(add_group_description);
- global_group_button->connect("toggled", callable_mp(add_group_description, &LineEdit::set_editable));
+ global_group_button->connect(SceneStringName(toggled), callable_mp(add_group_description, &LineEdit::set_editable));
add_group_dialog->register_text_enter(add_group_name);
add_group_dialog->register_text_enter(add_group_description);
diff --git a/editor/gui/editor_bottom_panel.cpp b/editor/gui/editor_bottom_panel.cpp
index 3cb95a3926..4b2fd9cb2f 100644
--- a/editor/gui/editor_bottom_panel.cpp
+++ b/editor/gui/editor_bottom_panel.cpp
@@ -158,7 +158,7 @@ void EditorBottomPanel::load_layout_from_config(Ref<ConfigFile> p_config_file, c
Button *EditorBottomPanel::add_item(String p_text, Control *p_item, const Ref<Shortcut> &p_shortcut, bool p_at_front) {
Button *tb = memnew(Button);
tb->set_theme_type_variation("BottomPanelButton");
- tb->connect("toggled", callable_mp(this, &EditorBottomPanel::_switch_by_control).bind(p_item));
+ tb->connect(SceneStringName(toggled), callable_mp(this, &EditorBottomPanel::_switch_by_control).bind(p_item));
tb->set_drag_forwarding(Callable(), callable_mp(this, &EditorBottomPanel::_button_drag_hover).bind(tb, p_item), Callable());
tb->set_text(p_text);
tb->set_shortcut(p_shortcut);
@@ -295,5 +295,5 @@ EditorBottomPanel::EditorBottomPanel() {
expand_button->set_theme_type_variation("FlatMenuButton");
expand_button->set_toggle_mode(true);
expand_button->set_shortcut(ED_SHORTCUT_AND_COMMAND("editor/bottom_panel_expand", TTR("Expand Bottom Panel"), KeyModifierMask::SHIFT | Key::F12));
- expand_button->connect("toggled", callable_mp(this, &EditorBottomPanel::_expand_button_toggled));
+ expand_button->connect(SceneStringName(toggled), callable_mp(this, &EditorBottomPanel::_expand_button_toggled));
}
diff --git a/editor/gui/editor_file_dialog.cpp b/editor/gui/editor_file_dialog.cpp
index afc6d58d63..b6aa3c2215 100644
--- a/editor/gui/editor_file_dialog.cpp
+++ b/editor/gui/editor_file_dialog.cpp
@@ -115,6 +115,8 @@ void EditorFileDialog::_native_dialog_cb(bool p_ok, const Vector<String> &p_file
file_name = ProjectSettings::get_singleton()->localize_path(file_name);
}
}
+ selected_options = p_selected_options;
+
String f = files[0];
if (mode == FILE_MODE_OPEN_FILES) {
emit_signal(SNAME("files_selected"), files);
@@ -146,7 +148,6 @@ void EditorFileDialog::_native_dialog_cb(bool p_ok, const Vector<String> &p_file
}
file->set_text(f);
dir->set_text(f.get_base_dir());
- selected_options = p_selected_options;
filter->select(p_filter);
}
@@ -1779,7 +1780,7 @@ void EditorFileDialog::_update_option_controls() {
CheckBox *cb = memnew(CheckBox);
cb->set_pressed(opt.default_idx);
grid_options->add_child(cb);
- cb->connect("toggled", callable_mp(this, &EditorFileDialog::_option_changed_checkbox_toggled).bind(opt.name));
+ cb->connect(SceneStringName(toggled), callable_mp(this, &EditorFileDialog::_option_changed_checkbox_toggled).bind(opt.name));
selected_options[opt.name] = (bool)opt.default_idx;
} else {
OptionButton *ob = memnew(OptionButton);
@@ -2146,7 +2147,7 @@ EditorFileDialog::EditorFileDialog() {
show_hidden->set_toggle_mode(true);
show_hidden->set_pressed(is_showing_hidden_files());
show_hidden->set_tooltip_text(TTR("Toggle the visibility of hidden files."));
- show_hidden->connect("toggled", callable_mp(this, &EditorFileDialog::set_show_hidden_files));
+ show_hidden->connect(SceneStringName(toggled), callable_mp(this, &EditorFileDialog::set_show_hidden_files));
pathhb->add_child(show_hidden);
pathhb->add_child(memnew(VSeparator));
diff --git a/editor/gui/editor_object_selector.cpp b/editor/gui/editor_object_selector.cpp
index bfd7e18de1..b73cd3b65c 100644
--- a/editor/gui/editor_object_selector.cpp
+++ b/editor/gui/editor_object_selector.cpp
@@ -103,7 +103,6 @@ void EditorObjectSelector::_show_popup() {
sub_objects_menu->set_position(gp);
sub_objects_menu->set_size(Size2(size.width, 1));
- sub_objects_menu->take_mouse_focus();
sub_objects_menu->popup();
}
diff --git a/editor/gui/editor_run_bar.cpp b/editor/gui/editor_run_bar.cpp
index 4cc2d1145e..9050ee0cd4 100644
--- a/editor/gui/editor_run_bar.cpp
+++ b/editor/gui/editor_run_bar.cpp
@@ -445,7 +445,7 @@ EditorRunBar::EditorRunBar() {
write_movie_button->set_pressed(false);
write_movie_button->set_focus_mode(Control::FOCUS_NONE);
write_movie_button->set_tooltip_text(TTR("Enable Movie Maker mode.\nThe project will run at stable FPS and the visual and audio output will be recorded to a video file."));
- write_movie_button->connect("toggled", callable_mp(this, &EditorRunBar::_write_movie_toggled));
+ write_movie_button->connect(SceneStringName(toggled), callable_mp(this, &EditorRunBar::_write_movie_toggled));
quick_run = memnew(EditorQuickOpen);
add_child(quick_run);
diff --git a/editor/gui/editor_spin_slider.cpp b/editor/gui/editor_spin_slider.cpp
index 5372d33b4c..d49d597084 100644
--- a/editor/gui/editor_spin_slider.cpp
+++ b/editor/gui/editor_spin_slider.cpp
@@ -185,10 +185,6 @@ void EditorSpinSlider::_grabber_gui_input(const Ref<InputEvent> &p_event) {
Ref<InputEventMouseButton> mb = p_event;
- if (is_read_only()) {
- return;
- }
-
if (grabbing_grabber) {
if (mb.is_valid()) {
if (mb->get_button_index() == MouseButton::WHEEL_UP) {
@@ -241,7 +237,7 @@ void EditorSpinSlider::_grabber_gui_input(const Ref<InputEvent> &p_event) {
void EditorSpinSlider::_value_input_gui_input(const Ref<InputEvent> &p_event) {
Ref<InputEventKey> k = p_event;
- if (k.is_valid() && k->is_pressed() && !is_read_only()) {
+ if (k.is_valid() && k->is_pressed() && !read_only) {
Key code = k->get_keycode();
switch (code) {
@@ -315,7 +311,7 @@ void EditorSpinSlider::_draw_spin_slider() {
bool rtl = is_layout_rtl();
Vector2 size = get_size();
- Ref<StyleBox> sb = get_theme_stylebox(is_read_only() ? SNAME("read_only") : CoreStringName(normal), SNAME("LineEdit"));
+ Ref<StyleBox> sb = get_theme_stylebox(read_only ? SNAME("read_only") : CoreStringName(normal), SNAME("LineEdit"));
if (!flat) {
draw_style_box(sb, Rect2(Vector2(), size));
}
@@ -327,14 +323,14 @@ void EditorSpinSlider::_draw_spin_slider() {
int label_width = font->get_string_size(label, HORIZONTAL_ALIGNMENT_LEFT, -1, font_size).width;
int number_width = size.width - sb->get_minimum_size().width - label_width - sep;
- Ref<Texture2D> updown = get_theme_icon(is_read_only() ? SNAME("updown_disabled") : SNAME("updown"), SNAME("SpinBox"));
+ Ref<Texture2D> updown = get_theme_icon(read_only ? SNAME("updown_disabled") : SNAME("updown"), SNAME("SpinBox"));
String numstr = get_text_value();
int vofs = (size.height - font->get_height(font_size)) / 2 + font->get_ascent(font_size);
- Color fc = get_theme_color(is_read_only() ? SNAME("font_uneditable_color") : SceneStringName(font_color), SNAME("LineEdit"));
- Color lc = get_theme_color(is_read_only() ? SNAME("read_only_label_color") : SNAME("label_color"));
+ Color fc = get_theme_color(read_only ? SNAME("font_uneditable_color") : SceneStringName(font_color), SNAME("LineEdit"));
+ Color lc = get_theme_color(read_only ? SNAME("read_only_label_color") : SNAME("label_color"));
if (flat && !label.is_empty()) {
Ref<StyleBox> label_bg = get_theme_stylebox(SNAME("label_bg"), SNAME("EditorSpinSlider"));
@@ -384,7 +380,7 @@ void EditorSpinSlider::_draw_spin_slider() {
if (!hide_slider) {
if (get_step() == 1) {
- Ref<Texture2D> updown2 = is_read_only() ? theme_cache.updown_disabled_icon : theme_cache.updown_icon;
+ Ref<Texture2D> updown2 = read_only ? theme_cache.updown_disabled_icon : theme_cache.updown_icon;
int updown_vofs = (size.height - updown2->get_height()) / 2;
if (rtl) {
updown_offset = sb->get_margin(SIDE_LEFT);
@@ -604,7 +600,7 @@ void EditorSpinSlider::_value_focus_exited() {
return;
}
- if (is_read_only()) {
+ if (read_only) {
// Spin slider has become read only while it was being edited.
return;
}
@@ -665,6 +661,10 @@ bool EditorSpinSlider::is_grabbing() const {
}
void EditorSpinSlider::_focus_entered() {
+ if (read_only) {
+ return;
+ }
+
_ensure_input_popup();
value_input->set_text(get_text_value());
value_input_popup->set_size(get_size());
diff --git a/editor/gui/scene_tree_editor.cpp b/editor/gui/scene_tree_editor.cpp
index 69ab9810a0..a7a14de527 100644
--- a/editor/gui/scene_tree_editor.cpp
+++ b/editor/gui/scene_tree_editor.cpp
@@ -174,15 +174,40 @@ void SceneTreeEditor::_cell_button_pressed(Object *p_item, int p_column, int p_i
EditorDockManager::get_singleton()->focus_dock(NodeDock::get_singleton());
NodeDock::get_singleton()->show_groups();
} else if (p_id == BUTTON_UNIQUE) {
- undo_redo->create_action(TTR("Disable Scene Unique Name"));
- undo_redo->add_do_method(n, "set_unique_name_in_owner", false);
- undo_redo->add_undo_method(n, "set_unique_name_in_owner", true);
- undo_redo->add_do_method(this, "_update_tree");
- undo_redo->add_undo_method(this, "_update_tree");
- undo_redo->commit_action();
+ bool ask_before_revoking_unique_name = EDITOR_GET("docks/scene_tree/ask_before_revoking_unique_name");
+ revoke_node = n;
+ if (ask_before_revoking_unique_name) {
+ String msg = vformat(TTR("Revoke unique name for node \"%s\"?"), n->get_name());
+ ask_before_revoke_checkbox->set_pressed(false);
+ revoke_dialog_label->set_text(msg);
+ revoke_dialog->reset_size();
+ revoke_dialog->popup_centered();
+ } else {
+ _revoke_unique_name();
+ }
}
}
+void SceneTreeEditor::_update_ask_before_revoking_unique_name() {
+ if (ask_before_revoke_checkbox->is_pressed()) {
+ EditorSettings::get_singleton()->set("docks/scene_tree/ask_before_revoking_unique_name", false);
+ ask_before_revoke_checkbox->set_pressed(false);
+ }
+
+ _revoke_unique_name();
+}
+
+void SceneTreeEditor::_revoke_unique_name() {
+ EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
+
+ undo_redo->create_action(TTR("Disable Scene Unique Name"));
+ undo_redo->add_do_method(revoke_node, "set_unique_name_in_owner", false);
+ undo_redo->add_undo_method(revoke_node, "set_unique_name_in_owner", true);
+ undo_redo->add_do_method(this, "_update_tree");
+ undo_redo->add_undo_method(this, "_update_tree");
+ undo_redo->commit_action();
+}
+
void SceneTreeEditor::_toggle_visible(Node *p_node) {
if (p_node->has_method("is_visible") && p_node->has_method("set_visible")) {
bool v = bool(p_node->call("is_visible"));
@@ -491,10 +516,14 @@ void SceneTreeEditor::_update_node_tooltip(Node *p_node, TreeItem *p_item) {
String tooltip = p_node->get_name();
if (p_node == get_scene_node() && p_node->get_scene_inherited_state().is_valid()) {
- p_item->add_button(0, get_editor_theme_icon(SNAME("InstanceOptions")), BUTTON_SUBSCENE, false, TTR("Open in Editor"));
+ if (p_item->get_button_by_id(0, BUTTON_SUBSCENE) == -1) {
+ p_item->add_button(0, get_editor_theme_icon(SNAME("InstanceOptions")), BUTTON_SUBSCENE, false, TTR("Open in Editor"));
+ }
tooltip += String("\n" + TTR("Inherits:") + " " + p_node->get_scene_inherited_state()->get_path());
} else if (p_node != get_scene_node() && !p_node->get_scene_file_path().is_empty() && can_open_instance) {
- p_item->add_button(0, get_editor_theme_icon(SNAME("InstanceOptions")), BUTTON_SUBSCENE, false, TTR("Open in Editor"));
+ if (p_item->get_button_by_id(0, BUTTON_SUBSCENE) == -1) {
+ p_item->add_button(0, get_editor_theme_icon(SNAME("InstanceOptions")), BUTTON_SUBSCENE, false, TTR("Open in Editor"));
+ }
tooltip += String("\n" + TTR("Instance:") + " " + p_node->get_scene_file_path());
}
@@ -1620,6 +1649,18 @@ SceneTreeEditor::SceneTreeEditor(bool p_label, bool p_can_rename, bool p_can_ope
update_node_tooltip_delay->set_one_shot(true);
add_child(update_node_tooltip_delay);
+ revoke_dialog = memnew(ConfirmationDialog);
+ revoke_dialog->set_ok_button_text(TTR("Revoke"));
+ add_child(revoke_dialog);
+ revoke_dialog->connect(SceneStringName(confirmed), callable_mp(this, &SceneTreeEditor::_update_ask_before_revoking_unique_name));
+ VBoxContainer *vb = memnew(VBoxContainer);
+ revoke_dialog->add_child(vb);
+ revoke_dialog_label = memnew(Label);
+ vb->add_child(revoke_dialog_label);
+ ask_before_revoke_checkbox = memnew(CheckBox(TTR("Don't Ask Again")));
+ ask_before_revoke_checkbox->set_tooltip_text(TTR("This dialog can also be enabled/disabled in the Editor Settings: Docks > Scene Tree > Ask Before Revoking Unique Name."));
+ vb->add_child(ask_before_revoke_checkbox);
+
script_types = memnew(List<StringName>);
ClassDB::get_inheriters_from_class("Script", script_types);
}
@@ -1769,7 +1810,7 @@ SceneTreeDialog::SceneTreeDialog() {
// Add 'Show All' button to HBoxContainer next to the filter, visible only when valid_types is defined.
show_all_nodes = memnew(CheckButton);
show_all_nodes->set_text(TTR("Show All"));
- show_all_nodes->connect("toggled", callable_mp(this, &SceneTreeDialog::_show_all_nodes_changed));
+ show_all_nodes->connect(SceneStringName(toggled), callable_mp(this, &SceneTreeDialog::_show_all_nodes_changed));
show_all_nodes->set_h_size_flags(Control::SIZE_SHRINK_BEGIN);
show_all_nodes->hide();
filter_hbc->add_child(show_all_nodes);
diff --git a/editor/gui/scene_tree_editor.h b/editor/gui/scene_tree_editor.h
index b4d9644f16..9633993b8b 100644
--- a/editor/gui/scene_tree_editor.h
+++ b/editor/gui/scene_tree_editor.h
@@ -31,6 +31,7 @@
#ifndef SCENE_TREE_EDITOR_H
#define SCENE_TREE_EDITOR_H
+#include "scene/gui/check_box.h"
#include "scene/gui/check_button.h"
#include "scene/gui/dialogs.h"
#include "scene/gui/tree.h"
@@ -68,6 +69,11 @@ class SceneTreeEditor : public Control {
AcceptDialog *error = nullptr;
AcceptDialog *warning = nullptr;
+ ConfirmationDialog *revoke_dialog = nullptr;
+ Label *revoke_dialog_label = nullptr;
+ CheckBox *ask_before_revoke_checkbox = nullptr;
+ Node *revoke_node = nullptr;
+
bool auto_expand_selected = true;
bool connect_to_script_mode = false;
bool connecting_signal = false;
@@ -144,6 +150,9 @@ class SceneTreeEditor : public Control {
Vector<StringName> valid_types;
+ void _update_ask_before_revoking_unique_name();
+ void _revoke_unique_name();
+
public:
// Public for use with callable_mp.
void _update_tree(bool p_scroll_to_selected = false);
diff --git a/editor/history_dock.cpp b/editor/history_dock.cpp
index 5a64fba788..1a0971a15c 100644
--- a/editor/history_dock.cpp
+++ b/editor/history_dock.cpp
@@ -245,8 +245,8 @@ HistoryDock::HistoryDock() {
current_scene_checkbox->set_text(TTR("Scene"));
current_scene_checkbox->set_h_size_flags(SIZE_EXPAND_FILL);
current_scene_checkbox->set_clip_text(true);
- current_scene_checkbox->connect("toggled", callable_mp(this, &HistoryDock::refresh_history).unbind(1));
- current_scene_checkbox->connect("toggled", callable_mp(this, &HistoryDock::save_options).unbind(1));
+ current_scene_checkbox->connect(SceneStringName(toggled), callable_mp(this, &HistoryDock::refresh_history).unbind(1));
+ current_scene_checkbox->connect(SceneStringName(toggled), callable_mp(this, &HistoryDock::save_options).unbind(1));
global_history_checkbox = memnew(CheckBox);
mode_hb->add_child(global_history_checkbox);
@@ -255,8 +255,8 @@ HistoryDock::HistoryDock() {
global_history_checkbox->set_text(TTR("Global"));
global_history_checkbox->set_h_size_flags(SIZE_EXPAND_FILL);
global_history_checkbox->set_clip_text(true);
- global_history_checkbox->connect("toggled", callable_mp(this, &HistoryDock::refresh_history).unbind(1));
- global_history_checkbox->connect("toggled", callable_mp(this, &HistoryDock::save_options).unbind(1));
+ global_history_checkbox->connect(SceneStringName(toggled), callable_mp(this, &HistoryDock::refresh_history).unbind(1));
+ global_history_checkbox->connect(SceneStringName(toggled), callable_mp(this, &HistoryDock::save_options).unbind(1));
action_list = memnew(ItemList);
action_list->set_auto_translate_mode(AUTO_TRANSLATE_MODE_DISABLED);
diff --git a/editor/icons/MemberConstructor.svg b/editor/icons/MemberConstructor.svg
new file mode 100644
index 0000000000..0e61768739
--- /dev/null
+++ b/editor/icons/MemberConstructor.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><path fill="#e0e0e0" d="M9 6c-1.33 2.67-1.33 4.33 0 7h2c-1.33-2.67-1.33-4.33 0-7zm4 0c1.33 2.67 1.33 4.33 0 7h2c1.33-2.67 1.33-4.33 0-7zM1 11a1 1 0 0 0 0 2 1 1 0 0 0 0-2zm6 2v-2H6a1 1 0 0 1 0-2h1V7H6a3 3 0 0 0 0 6z"/></svg> \ No newline at end of file
diff --git a/editor/icons/MemberOperator.svg b/editor/icons/MemberOperator.svg
new file mode 100644
index 0000000000..a00d990e26
--- /dev/null
+++ b/editor/icons/MemberOperator.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><path fill="#e0e0e0" d="M9 6c-1.33 2.67-1.33 4.33 0 7h2c-1.33-2.67-1.33-4.33 0-7zm4 0c1.33 2.67 1.33 4.33 0 7h2c1.33-2.67 1.33-4.33 0-7zM1 11a1 1 0 0 0 0 2 1 1 0 0 0 0-2zm4-4a3 3 0 0 0 0 6 3 3 0 0 0 0-6zm0 2a1 1 0 0 1 0 2 1 1 0 0 1 0-2z"/></svg> \ No newline at end of file
diff --git a/editor/icons/SnapKeys.svg b/editor/icons/SnapKeys.svg
new file mode 100644
index 0000000000..813781b801
--- /dev/null
+++ b/editor/icons/SnapKeys.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><path fill="#e0e0e0" d="m 7,13 v 2 h 2 v -2 z m 6,0 v 2 h 2 v -2 z"/><path fill="#fff" fill-opacity=".686" d="m 7,13 h 2 v -2 a 2,2 0 0 1 4,0 v 2 h 2 v -2 a 4,4 0 0 0 -8,0 z"/><path fill="#fff" d="M 5,1 1,5 5,9 9,5 Z"/></svg> \ No newline at end of file
diff --git a/editor/icons/SnapTimeline.svg b/editor/icons/SnapTimeline.svg
new file mode 100644
index 0000000000..b558545f5a
--- /dev/null
+++ b/editor/icons/SnapTimeline.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><path fill="#e0e0e0" d="m 7,13 v 2 h 2 v -2 z m 6,0 v 2 h 2 v -2 z"/><path fill="#fff" fill-opacity=".686" d="m 7,13 h 2 v -2 a 2,2 0 0 1 4,0 v 2 h 2 v -2 a 4,4 0 0 0 -8,0 z"/><path fill="#fff" d="m 1,2 3,3 v 9 H 6 V 5 L 9,2 Z"/></svg> \ No newline at end of file
diff --git a/editor/icons/editor_icons_builders.py b/editor/icons/editor_icons_builders.py
index 5cc67ca3ad..d3e8953483 100644
--- a/editor/icons/editor_icons_builders.py
+++ b/editor/icons/editor_icons_builders.py
@@ -3,6 +3,8 @@
import os
from io import StringIO
+from methods import to_raw_cstring
+
# See also `scene/theme/icons/default_theme_icons_builders.py`.
def make_editor_icons_action(target, source, env):
@@ -10,21 +12,9 @@ def make_editor_icons_action(target, source, env):
svg_icons = source
with StringIO() as icons_string, StringIO() as s:
- for f in svg_icons:
- fname = str(f)
-
- icons_string.write('\t"')
-
- with open(fname, "rb") as svgf:
- b = svgf.read(1)
- while len(b) == 1:
- icons_string.write("\\" + str(hex(ord(b)))[1:])
- b = svgf.read(1)
-
- icons_string.write('"')
- if fname != svg_icons[-1]:
- icons_string.write(",")
- icons_string.write("\n")
+ for svg in svg_icons:
+ with open(str(svg), "r") as svgf:
+ icons_string.write("\t%s,\n" % to_raw_cstring(svgf.read()))
s.write("/* THIS FILE IS GENERATED DO NOT EDIT */\n")
s.write("#ifndef _EDITOR_ICONS_H\n")
diff --git a/editor/import/3d/post_import_plugin_skeleton_rest_fixer.cpp b/editor/import/3d/post_import_plugin_skeleton_rest_fixer.cpp
index b69d38afa0..64bec0532b 100644
--- a/editor/import/3d/post_import_plugin_skeleton_rest_fixer.cpp
+++ b/editor/import/3d/post_import_plugin_skeleton_rest_fixer.cpp
@@ -48,13 +48,13 @@ void PostImportPluginSkeletonRestFixer::get_internal_import_options(InternalImpo
// TODO: PostImportPlugin need to be implemented such as validate_option(PropertyInfo &property, const Dictionary &p_options).
// get_internal_option_visibility() is not sufficient because it can only retrieve options implemented in the core and can only read option values.
// r_options->push_back(ResourceImporter::ImportOption(PropertyInfo(Variant::ARRAY, "retarget/rest_fixer/filter", PROPERTY_HINT_ARRAY_TYPE, vformat("%s/%s:%s", Variant::STRING_NAME, PROPERTY_HINT_ENUM, "Hips,Spine,Chest")), Array()));
- r_options->push_back(ResourceImporter::ImportOption(PropertyInfo(Variant::ARRAY, "retarget/rest_fixer/fix_silhouette/filter", PROPERTY_HINT_ARRAY_TYPE, "StringName"), Array()));
+ r_options->push_back(ResourceImporter::ImportOption(PropertyInfo(Variant::ARRAY, "retarget/rest_fixer/fix_silhouette/filter", PROPERTY_HINT_ARRAY_TYPE, vformat("%s:", Variant::STRING_NAME)), Array()));
r_options->push_back(ResourceImporter::ImportOption(PropertyInfo(Variant::FLOAT, "retarget/rest_fixer/fix_silhouette/threshold"), 15));
r_options->push_back(ResourceImporter::ImportOption(PropertyInfo(Variant::FLOAT, "retarget/rest_fixer/fix_silhouette/base_height_adjustment", PROPERTY_HINT_RANGE, "-1,1,0.01"), 0.0));
}
}
-Variant PostImportPluginSkeletonRestFixer::get_internal_option_visibility(InternalImportCategory p_category, bool p_for_animation, const String &p_option, const HashMap<StringName, Variant> &p_options) const {
+Variant PostImportPluginSkeletonRestFixer::get_internal_option_visibility(InternalImportCategory p_category, const String &p_scene_import_type, const String &p_option, const HashMap<StringName, Variant> &p_options) const {
if (p_category == INTERNAL_IMPORT_CATEGORY_SKELETON_3D_NODE) {
if (p_option.begins_with("retarget/rest_fixer/fix_silhouette/")) {
if (!bool(p_options["retarget/rest_fixer/fix_silhouette/enable"])) {
diff --git a/editor/import/3d/post_import_plugin_skeleton_rest_fixer.h b/editor/import/3d/post_import_plugin_skeleton_rest_fixer.h
index 1750ed1233..9fec76be48 100644
--- a/editor/import/3d/post_import_plugin_skeleton_rest_fixer.h
+++ b/editor/import/3d/post_import_plugin_skeleton_rest_fixer.h
@@ -38,7 +38,7 @@ class PostImportPluginSkeletonRestFixer : public EditorScenePostImportPlugin {
public:
virtual void get_internal_import_options(InternalImportCategory p_category, List<ResourceImporter::ImportOption> *r_options) override;
- virtual Variant get_internal_option_visibility(InternalImportCategory p_category, bool p_for_animation, const String &p_option, const HashMap<StringName, Variant> &p_options) const override;
+ virtual Variant get_internal_option_visibility(InternalImportCategory p_category, const String &p_scene_import_type, const String &p_option, const HashMap<StringName, Variant> &p_options) const override;
virtual void internal_process(InternalImportCategory p_category, Node *p_base_scene, Node *p_node, Ref<Resource> p_resource, const Dictionary &p_options) override;
PostImportPluginSkeletonRestFixer();
diff --git a/editor/import/3d/resource_importer_scene.cpp b/editor/import/3d/resource_importer_scene.cpp
index d0d7142c01..5c28213ca7 100644
--- a/editor/import/3d/resource_importer_scene.cpp
+++ b/editor/import/3d/resource_importer_scene.cpp
@@ -96,9 +96,10 @@ void EditorSceneFormatImporter::get_import_options(const String &p_path, List<Re
GDVIRTUAL_CALL(_get_import_options, p_path);
}
-Variant EditorSceneFormatImporter::get_option_visibility(const String &p_path, bool p_for_animation, const String &p_option, const HashMap<StringName, Variant> &p_options) {
+Variant EditorSceneFormatImporter::get_option_visibility(const String &p_path, const String &p_scene_import_type, const String &p_option, const HashMap<StringName, Variant> &p_options) {
Variant ret;
- GDVIRTUAL_CALL(_get_option_visibility, p_path, p_for_animation, p_option, ret);
+ // For compatibility with the old API, pass the import type as a boolean.
+ GDVIRTUAL_CALL(_get_option_visibility, p_path, p_scene_import_type == "AnimationLibrary", p_option, ret);
return ret;
}
@@ -172,13 +173,16 @@ void EditorScenePostImportPlugin::get_internal_import_options(InternalImportCate
GDVIRTUAL_CALL(_get_internal_import_options, p_category);
current_option_list = nullptr;
}
-Variant EditorScenePostImportPlugin::get_internal_option_visibility(InternalImportCategory p_category, bool p_for_animation, const String &p_option, const HashMap<StringName, Variant> &p_options) const {
+
+Variant EditorScenePostImportPlugin::get_internal_option_visibility(InternalImportCategory p_category, const String &p_scene_import_type, const String &p_option, const HashMap<StringName, Variant> &p_options) const {
current_options = &p_options;
Variant ret;
- GDVIRTUAL_CALL(_get_internal_option_visibility, p_category, p_for_animation, p_option, ret);
+ // For compatibility with the old API, pass the import type as a boolean.
+ GDVIRTUAL_CALL(_get_internal_option_visibility, p_category, p_scene_import_type == "AnimationLibrary", p_option, ret);
current_options = nullptr;
return ret;
}
+
Variant EditorScenePostImportPlugin::get_internal_option_update_view_required(InternalImportCategory p_category, const String &p_option, const HashMap<StringName, Variant> &p_options) const {
current_options = &p_options;
Variant ret;
@@ -198,10 +202,10 @@ void EditorScenePostImportPlugin::get_import_options(const String &p_path, List<
GDVIRTUAL_CALL(_get_import_options, p_path);
current_option_list = nullptr;
}
-Variant EditorScenePostImportPlugin::get_option_visibility(const String &p_path, bool p_for_animation, const String &p_option, const HashMap<StringName, Variant> &p_options) const {
+Variant EditorScenePostImportPlugin::get_option_visibility(const String &p_path, const String &p_scene_import_type, const String &p_option, const HashMap<StringName, Variant> &p_options) const {
current_options = &p_options;
Variant ret;
- GDVIRTUAL_CALL(_get_option_visibility, p_path, p_for_animation, p_option, ret);
+ GDVIRTUAL_CALL(_get_option_visibility, p_path, p_scene_import_type == "AnimationLibrary", p_option, ret);
current_options = nullptr;
return ret;
}
@@ -245,11 +249,22 @@ void EditorScenePostImportPlugin::_bind_methods() {
/////////////////////////////////////////////////////////
String ResourceImporterScene::get_importer_name() const {
- return animation_importer ? "animation_library" : "scene";
+ // For compatibility with 4.2 and earlier we need to keep the "scene" and "animation_library" names.
+ // However this is arbitrary so for new import types we can use any string.
+ if (_scene_import_type == "PackedScene") {
+ return "scene";
+ } else if (_scene_import_type == "AnimationLibrary") {
+ return "animation_library";
+ }
+ return _scene_import_type;
}
String ResourceImporterScene::get_visible_name() const {
- return animation_importer ? "Animation Library" : "Scene";
+ // This is displayed on the UI. Friendly names here are nice but not vital, so fall back to the type.
+ if (_scene_import_type == "PackedScene") {
+ return "Scene";
+ }
+ return _scene_import_type.capitalize();
}
void ResourceImporterScene::get_recognized_extensions(List<String> *p_extensions) const {
@@ -257,11 +272,14 @@ void ResourceImporterScene::get_recognized_extensions(List<String> *p_extensions
}
String ResourceImporterScene::get_save_extension() const {
- return animation_importer ? "res" : "scn";
+ if (_scene_import_type == "PackedScene") {
+ return "scn";
+ }
+ return "res";
}
String ResourceImporterScene::get_resource_type() const {
- return animation_importer ? "AnimationLibrary" : "PackedScene";
+ return _scene_import_type;
}
int ResourceImporterScene::get_format_version() const {
@@ -269,27 +287,28 @@ int ResourceImporterScene::get_format_version() const {
}
bool ResourceImporterScene::get_option_visibility(const String &p_path, const String &p_option, const HashMap<StringName, Variant> &p_options) const {
- if (animation_importer) {
+ if (_scene_import_type == "PackedScene") {
+ if (p_option.begins_with("animation/")) {
+ if (p_option != "animation/import" && !bool(p_options["animation/import"])) {
+ return false;
+ }
+ }
+ } else if (_scene_import_type == "AnimationLibrary") {
if (p_option == "animation/import") { // Option ignored, animation always imported.
return false;
}
- } else if (p_option.begins_with("animation/")) {
- if (p_option != "animation/import" && !bool(p_options["animation/import"])) {
- return false;
+ if (p_option == "nodes/root_type" || p_option == "nodes/root_name" || p_option.begins_with("meshes/") || p_option.begins_with("skins/")) {
+ return false; // Nothing to do here for animations.
}
}
- if (animation_importer && (p_option == "nodes/root_type" || p_option == "nodes/root_name" || p_option.begins_with("meshes/") || p_option.begins_with("skins/"))) {
- return false; // Nothing to do here for animations.
- }
-
if (p_option == "meshes/lightmap_texel_size" && int(p_options["meshes/light_baking"]) != 2) {
// Only display the lightmap texel size import option when using the Static Lightmaps light baking mode.
return false;
}
for (int i = 0; i < post_importer_plugins.size(); i++) {
- Variant ret = post_importer_plugins.write[i]->get_option_visibility(p_path, animation_importer, p_option, p_options);
+ Variant ret = post_importer_plugins.write[i]->get_option_visibility(p_path, _scene_import_type, p_option, p_options);
if (ret.get_type() == Variant::BOOL) {
if (!ret) {
return false;
@@ -298,7 +317,7 @@ bool ResourceImporterScene::get_option_visibility(const String &p_path, const St
}
for (Ref<EditorSceneFormatImporter> importer : scene_importers) {
- Variant ret = importer->get_option_visibility(p_path, animation_importer, p_option, p_options);
+ Variant ret = importer->get_option_visibility(p_path, _scene_import_type, p_option, p_options);
if (ret.get_type() == Variant::BOOL) {
if (!ret) {
return false;
@@ -427,7 +446,7 @@ static String _fixstr(const String &p_what, const String &p_str) {
}
static void _pre_gen_shape_list(Ref<ImporterMesh> &mesh, Vector<Ref<Shape3D>> &r_shape_list, bool p_convex) {
- ERR_FAIL_NULL_MSG(mesh, "Cannot generate shape list with null mesh value.");
+ ERR_FAIL_COND_MSG(mesh.is_null(), "Cannot generate shape list with null mesh value.");
if (!p_convex) {
Ref<ConcavePolygonShape3D> shape = mesh->create_trimesh_shape();
r_shape_list.push_back(shape);
@@ -630,6 +649,9 @@ Node *ResourceImporterScene::_pre_fix_node(Node *p_node, Node *p_root, HashMap<R
String name = p_node->get_name();
NodePath original_path = p_root->get_path_to(p_node); // Used to detect renames due to import hints.
+ Ref<Resource> original_meta = memnew(Resource); // Create temp resource to hold original meta
+ original_meta->merge_meta_from(p_node);
+
bool isroot = p_node == p_root;
if (!isroot && _teststr(name, "noimp")) {
@@ -1003,6 +1025,8 @@ Node *ResourceImporterScene::_pre_fix_node(Node *p_node, Node *p_root, HashMap<R
print_verbose(vformat("Fix: Renamed %s to %s", original_path, new_path));
r_node_renames.push_back({ original_path, p_node });
}
+ // If we created new node instead, merge meta values from the original node.
+ p_node->merge_meta_from(*original_meta);
}
return p_node;
@@ -2283,7 +2307,7 @@ bool ResourceImporterScene::get_internal_option_visibility(InternalImportCategor
}
for (int i = 0; i < post_importer_plugins.size(); i++) {
- Variant ret = post_importer_plugins.write[i]->get_internal_option_visibility(EditorScenePostImportPlugin::InternalImportCategory(p_category), animation_importer, p_option, p_options);
+ Variant ret = post_importer_plugins.write[i]->get_internal_option_visibility(EditorScenePostImportPlugin::InternalImportCategory(p_category), _scene_import_type, p_option, p_options);
if (ret.get_type() == Variant::BOOL) {
return ret;
}
@@ -2433,6 +2457,8 @@ Node *ResourceImporterScene::_generate_meshes(Node *p_node, const Dictionary &p_
mesh_node->set_transform(src_mesh_node->get_transform());
mesh_node->set_skin(src_mesh_node->get_skin());
mesh_node->set_skeleton_path(src_mesh_node->get_skeleton_path());
+ mesh_node->merge_meta_from(src_mesh_node);
+
if (src_mesh_node->get_mesh().is_valid()) {
Ref<ArrayMesh> mesh;
if (!src_mesh_node->get_mesh()->has_mesh()) {
@@ -2580,6 +2606,7 @@ Node *ResourceImporterScene::_generate_meshes(Node *p_node, const Dictionary &p_
for (int i = 0; i < mesh->get_surface_count(); i++) {
mesh_node->set_surface_override_material(i, src_mesh_node->get_surface_material(i));
}
+ mesh->merge_meta_from(*src_mesh_node->get_mesh());
}
}
@@ -2877,13 +2904,11 @@ Error ResourceImporterScene::import(const String &p_source_file, const String &p
int import_flags = 0;
- if (animation_importer) {
+ if (_scene_import_type == "AnimationLibrary") {
import_flags |= EditorSceneFormatImporter::IMPORT_ANIMATION;
import_flags |= EditorSceneFormatImporter::IMPORT_DISCARD_MESHES_AND_MATERIALS;
- } else {
- if (bool(p_options["animation/import"])) {
- import_flags |= EditorSceneFormatImporter::IMPORT_ANIMATION;
- }
+ } else if (bool(p_options["animation/import"])) {
+ import_flags |= EditorSceneFormatImporter::IMPORT_ANIMATION;
}
if (bool(p_options["skins/use_named_skins"])) {
@@ -3079,6 +3104,19 @@ Error ResourceImporterScene::import(const String &p_source_file, const String &p
}
}
+ // Apply RESET animation before serializing.
+ if (_scene_import_type == "PackedScene") {
+ int scene_child_count = scene->get_child_count();
+ for (int i = 0; i < scene_child_count; i++) {
+ AnimationPlayer *ap = Object::cast_to<AnimationPlayer>(scene->get_child(i));
+ if (ap) {
+ if (ap->can_apply_reset()) {
+ ap->apply_reset();
+ }
+ }
+ }
+ }
+
if (post_import_script.is_valid()) {
post_import_script->init(p_source_file);
scene = post_import_script->post_import(scene);
@@ -3097,11 +3135,11 @@ Error ResourceImporterScene::import(const String &p_source_file, const String &p
progress.step(TTR("Saving..."), 104);
int flags = 0;
- if (EDITOR_GET("filesystem/on_save/compress_binary_resources")) {
+ if (EditorSettings::get_singleton() && EDITOR_GET("filesystem/on_save/compress_binary_resources")) {
flags |= ResourceSaver::FLAG_COMPRESS;
}
- if (animation_importer) {
+ if (_scene_import_type == "AnimationLibrary") {
Ref<AnimationLibrary> library;
for (int i = 0; i < scene->get_child_count(); i++) {
AnimationPlayer *ap = Object::cast_to<AnimationPlayer>(scene->get_child(i));
@@ -3122,13 +3160,14 @@ Error ResourceImporterScene::import(const String &p_source_file, const String &p
print_verbose("Saving animation to: " + p_save_path + ".res");
err = ResourceSaver::save(library, p_save_path + ".res", flags); //do not take over, let the changed files reload themselves
ERR_FAIL_COND_V_MSG(err != OK, err, "Cannot save animation to file '" + p_save_path + ".res'.");
-
- } else {
+ } else if (_scene_import_type == "PackedScene") {
Ref<PackedScene> packer = memnew(PackedScene);
packer->pack(scene);
print_verbose("Saving scene to: " + p_save_path + ".scn");
err = ResourceSaver::save(packer, p_save_path + ".scn", flags); //do not take over, let the changed files reload themselves
ERR_FAIL_COND_V_MSG(err != OK, err, "Cannot save scene to file '" + p_save_path + ".scn'.");
+ } else {
+ ERR_FAIL_V_MSG(ERR_FILE_UNRECOGNIZED, "Unknown scene import type: " + _scene_import_type);
}
memdelete(scene);
@@ -3150,20 +3189,20 @@ bool ResourceImporterScene::has_advanced_options() const {
}
void ResourceImporterScene::show_advanced_options(const String &p_path) {
- SceneImportSettingsDialog::get_singleton()->open_settings(p_path, animation_importer);
+ SceneImportSettingsDialog::get_singleton()->open_settings(p_path, _scene_import_type);
}
-ResourceImporterScene::ResourceImporterScene(bool p_animation_import, bool p_singleton) {
+ResourceImporterScene::ResourceImporterScene(const String &p_scene_import_type, bool p_singleton) {
// This should only be set through the EditorNode.
if (p_singleton) {
- if (p_animation_import) {
+ if (p_scene_import_type == "AnimationLibrary") {
animation_singleton = this;
- } else {
+ } else if (p_scene_import_type == "PackedScene") {
scene_singleton = this;
}
}
- animation_importer = p_animation_import;
+ _scene_import_type = p_scene_import_type;
}
ResourceImporterScene::~ResourceImporterScene() {
diff --git a/editor/import/3d/resource_importer_scene.h b/editor/import/3d/resource_importer_scene.h
index f9124ad289..9759f328d7 100644
--- a/editor/import/3d/resource_importer_scene.h
+++ b/editor/import/3d/resource_importer_scene.h
@@ -44,10 +44,10 @@
#include "scene/resources/animation.h"
#include "scene/resources/mesh.h"
-class Material;
class AnimationPlayer;
-
class ImporterMesh;
+class Material;
+
class EditorSceneFormatImporter : public RefCounted {
GDCLASS(EditorSceneFormatImporter, RefCounted);
@@ -78,7 +78,7 @@ public:
virtual void get_extensions(List<String> *r_extensions) const;
virtual Node *import_scene(const String &p_path, uint32_t p_flags, const HashMap<StringName, Variant> &p_options, List<String> *r_missing_deps, Error *r_err = nullptr);
virtual void get_import_options(const String &p_path, List<ResourceImporter::ImportOption> *r_options);
- virtual Variant get_option_visibility(const String &p_path, bool p_for_animation, const String &p_option, const HashMap<StringName, Variant> &p_options);
+ virtual Variant get_option_visibility(const String &p_path, const String &p_scene_import_type, const String &p_option, const HashMap<StringName, Variant> &p_options);
virtual void handle_compatibility_options(HashMap<StringName, Variant> &p_import_params) const {}
EditorSceneFormatImporter() {}
@@ -140,13 +140,13 @@ public:
void add_import_option_advanced(Variant::Type p_type, const String &p_name, const Variant &p_default_value, PropertyHint p_hint = PROPERTY_HINT_NONE, const String &p_hint_string = String(), int p_usage_flags = PROPERTY_USAGE_DEFAULT);
virtual void get_internal_import_options(InternalImportCategory p_category, List<ResourceImporter::ImportOption> *r_options);
- virtual Variant get_internal_option_visibility(InternalImportCategory p_category, bool p_for_animation, const String &p_option, const HashMap<StringName, Variant> &p_options) const;
+ virtual Variant get_internal_option_visibility(InternalImportCategory p_category, const String &p_scene_import_type, const String &p_option, const HashMap<StringName, Variant> &p_options) const;
virtual Variant get_internal_option_update_view_required(InternalImportCategory p_category, const String &p_option, const HashMap<StringName, Variant> &p_options) const;
virtual void internal_process(InternalImportCategory p_category, Node *p_base_scene, Node *p_node, Ref<Resource> p_resource, const Dictionary &p_options);
virtual void get_import_options(const String &p_path, List<ResourceImporter::ImportOption> *r_options);
- virtual Variant get_option_visibility(const String &p_path, bool p_for_animation, const String &p_option, const HashMap<StringName, Variant> &p_options) const;
+ virtual Variant get_option_visibility(const String &p_path, const String &p_scene_import_type, const String &p_option, const HashMap<StringName, Variant> &p_options) const;
virtual void pre_process(Node *p_scene, const HashMap<StringName, Variant> &p_options);
virtual void post_process(Node *p_scene, const HashMap<StringName, Variant> &p_options);
@@ -237,7 +237,7 @@ class ResourceImporterScene : public ResourceImporter {
void _optimize_track_usage(AnimationPlayer *p_player, AnimationImportTracks *p_track_actions);
- bool animation_importer = false;
+ String _scene_import_type = "PackedScene";
public:
static ResourceImporterScene *get_scene_singleton() { return scene_singleton; }
@@ -253,6 +253,9 @@ public:
static void clean_up_importer_plugins();
+ String get_scene_import_type() const { return _scene_import_type; }
+ void set_scene_import_type(const String &p_type) { _scene_import_type = p_type; }
+
virtual String get_importer_name() const override;
virtual String get_visible_name() const override;
virtual void get_recognized_extensions(List<String> *p_extensions) const override;
@@ -303,7 +306,7 @@ public:
virtual bool can_import_threaded() const override { return false; }
- ResourceImporterScene(bool p_animation_import = false, bool p_singleton = false);
+ ResourceImporterScene(const String &p_scene_import_type = "PackedScene", bool p_singleton = false);
~ResourceImporterScene();
template <typename M>
diff --git a/editor/import/3d/scene_import_settings.cpp b/editor/import/3d/scene_import_settings.cpp
index 7cd5279b63..011d0135b4 100644
--- a/editor/import/3d/scene_import_settings.cpp
+++ b/editor/import/3d/scene_import_settings.cpp
@@ -37,6 +37,7 @@
#include "editor/editor_settings.h"
#include "editor/editor_string_names.h"
#include "editor/gui/editor_file_dialog.h"
+#include "editor/plugins/skeleton_3d_editor_plugin.h"
#include "editor/themes/editor_scale.h"
#include "scene/3d/importer_mesh_instance_3d.h"
#include "scene/animation/animation_player.h"
@@ -419,7 +420,9 @@ void SceneImportSettingsDialog::_fill_scene(Node *p_node, TreeItem *p_parent_ite
animation_player->connect(SceneStringName(animation_finished), callable_mp(this, &SceneImportSettingsDialog::_animation_finished));
} else if (Object::cast_to<Skeleton3D>(p_node)) {
category = ResourceImporterScene::INTERNAL_IMPORT_CATEGORY_SKELETON_3D_NODE;
- skeletons.push_back(Object::cast_to<Skeleton3D>(p_node));
+ Skeleton3D *skeleton = Object::cast_to<Skeleton3D>(p_node);
+ skeleton->connect(SceneStringName(tree_entered), callable_mp(this, &SceneImportSettingsDialog::_skeleton_tree_entered).bind(skeleton));
+ skeletons.push_back(skeleton);
} else {
category = ResourceImporterScene::INTERNAL_IMPORT_CATEGORY_NODE;
}
@@ -480,6 +483,31 @@ void SceneImportSettingsDialog::_fill_scene(Node *p_node, TreeItem *p_parent_ite
contents_aabb.merge_with(aabb);
}
}
+
+ Skeleton3D *skeleton = Object::cast_to<Skeleton3D>(p_node);
+ if (skeleton) {
+ Ref<ArrayMesh> bones_mesh = Skeleton3DGizmoPlugin::get_bones_mesh(skeleton, -1, true);
+
+ bones_mesh_preview->set_mesh(bones_mesh);
+
+ Transform3D accum_xform;
+ Node3D *base = skeleton;
+ while (base) {
+ accum_xform = base->get_transform() * accum_xform;
+ base = Object::cast_to<Node3D>(base->get_parent());
+ }
+
+ bones_mesh_preview->set_transform(accum_xform * skeleton->get_transform());
+
+ AABB aabb = accum_xform.xform(bones_mesh->get_aabb());
+
+ if (first_aabb) {
+ contents_aabb = aabb;
+ first_aabb = false;
+ } else {
+ contents_aabb.merge_with(aabb);
+ }
+ }
}
void SceneImportSettingsDialog::_update_scene() {
@@ -678,26 +706,26 @@ void SceneImportSettingsDialog::update_view() {
update_view_timer->start();
}
-void SceneImportSettingsDialog::open_settings(const String &p_path, bool p_for_animation) {
+void SceneImportSettingsDialog::open_settings(const String &p_path, const String &p_scene_import_type) {
if (scene) {
_cleanup();
memdelete(scene);
scene = nullptr;
}
- editing_animation = p_for_animation;
+ editing_animation = p_scene_import_type == "AnimationLibrary";
scene_import_settings_data->settings = nullptr;
scene_import_settings_data->path = p_path;
// Visibility.
- data_mode->set_tab_hidden(1, p_for_animation);
- data_mode->set_tab_hidden(2, p_for_animation);
- if (p_for_animation) {
+ data_mode->set_tab_hidden(1, editing_animation);
+ data_mode->set_tab_hidden(2, editing_animation);
+ if (editing_animation) {
data_mode->set_current_tab(0);
}
- action_menu->get_popup()->set_item_disabled(action_menu->get_popup()->get_item_id(ACTION_EXTRACT_MATERIALS), p_for_animation);
- action_menu->get_popup()->set_item_disabled(action_menu->get_popup()->get_item_id(ACTION_CHOOSE_MESH_SAVE_PATHS), p_for_animation);
+ action_menu->get_popup()->set_item_disabled(action_menu->get_popup()->get_item_id(ACTION_EXTRACT_MATERIALS), editing_animation);
+ action_menu->get_popup()->set_item_disabled(action_menu->get_popup()->get_item_id(ACTION_CHOOSE_MESH_SAVE_PATHS), editing_animation);
base_path = p_path;
@@ -771,7 +799,7 @@ void SceneImportSettingsDialog::open_settings(const String &p_path, bool p_for_a
// Start with the root item (Scene) selected.
scene_tree->get_root()->select(0);
- if (p_for_animation) {
+ if (editing_animation) {
set_title(vformat(TTR("Advanced Import Settings for AnimationLibrary '%s'"), base_path.get_file()));
} else {
set_title(vformat(TTR("Advanced Import Settings for Scene '%s'"), base_path.get_file()));
@@ -795,6 +823,7 @@ void SceneImportSettingsDialog::_select(Tree *p_from, const String &p_type, cons
selecting = true;
scene_import_settings_data->hide_options = false;
+ bones_mesh_preview->hide();
if (p_type == "Node") {
node_selected->hide(); // Always hide just in case.
mesh_preview->hide();
@@ -834,6 +863,7 @@ void SceneImportSettingsDialog::_select(Tree *p_from, const String &p_type, cons
scene_import_settings_data->category = ResourceImporterScene::INTERNAL_IMPORT_CATEGORY_ANIMATION_NODE;
} else if (Object::cast_to<Skeleton3D>(nd.node)) {
scene_import_settings_data->category = ResourceImporterScene::INTERNAL_IMPORT_CATEGORY_SKELETON_3D_NODE;
+ bones_mesh_preview->show();
} else {
scene_import_settings_data->category = ResourceImporterScene::INTERNAL_IMPORT_CATEGORY_NODE;
scene_import_settings_data->hide_options = editing_animation;
@@ -853,6 +883,8 @@ void SceneImportSettingsDialog::_select(Tree *p_from, const String &p_type, cons
scene_import_settings_data->settings = &ad.settings;
scene_import_settings_data->category = ResourceImporterScene::INTERNAL_IMPORT_CATEGORY_ANIMATION;
+
+ _animation_update_skeleton_visibility();
} else if (p_type == "Mesh") {
node_selected->hide();
if (Object::cast_to<Node3D>(scene)) {
@@ -1055,6 +1087,11 @@ void SceneImportSettingsDialog::_animation_slider_value_changed(double p_value)
animation_player->seek(p_value * animation_map[selected_id].animation->get_length(), true);
}
+void SceneImportSettingsDialog::_skeleton_tree_entered(Skeleton3D *p_skeleton) {
+ bones_mesh_preview->set_skeleton_path(p_skeleton->get_path());
+ bones_mesh_preview->set_skin(p_skeleton->register_skin(p_skeleton->create_skin_from_rest_transforms()));
+}
+
void SceneImportSettingsDialog::_animation_finished(const StringName &p_name) {
Animation::LoopMode loop_mode = animation_loop_mode;
@@ -1080,6 +1117,14 @@ void SceneImportSettingsDialog::_animation_finished(const StringName &p_name) {
}
}
+void SceneImportSettingsDialog::_animation_update_skeleton_visibility() {
+ if (animation_toggle_skeleton_visibility->is_pressed()) {
+ bones_mesh_preview->show();
+ } else {
+ bones_mesh_preview->hide();
+ }
+}
+
void SceneImportSettingsDialog::_material_tree_selected() {
if (selecting) {
return;
@@ -1282,6 +1327,8 @@ void SceneImportSettingsDialog::_notification(int p_what) {
light_1_switch->set_icon(theme_cache.light_1_icon);
light_2_switch->set_icon(theme_cache.light_2_icon);
light_rotate_switch->set_icon(theme_cache.rotate_icon);
+
+ animation_toggle_skeleton_visibility->set_icon(get_editor_theme_icon(SNAME("Skeleton3D")));
} break;
case NOTIFICATION_PROCESS: {
@@ -1661,6 +1708,7 @@ SceneImportSettingsDialog::SceneImportSettingsDialog() {
animation_hbox->add_child(animation_stop_button);
animation_stop_button->set_flat(true);
animation_stop_button->set_focus_mode(Control::FOCUS_NONE);
+ animation_stop_button->set_tooltip_text(TTR("Selected Animation Stop"));
animation_stop_button->connect(SceneStringName(pressed), callable_mp(this, &SceneImportSettingsDialog::_stop_current_animation));
animation_slider = memnew(HSlider);
@@ -1673,6 +1721,15 @@ SceneImportSettingsDialog::SceneImportSettingsDialog() {
animation_slider->set_focus_mode(Control::FOCUS_NONE);
animation_slider->connect(SceneStringName(value_changed), callable_mp(this, &SceneImportSettingsDialog::_animation_slider_value_changed));
+ animation_toggle_skeleton_visibility = memnew(Button);
+ animation_hbox->add_child(animation_toggle_skeleton_visibility);
+ animation_toggle_skeleton_visibility->set_toggle_mode(true);
+ animation_toggle_skeleton_visibility->set_flat(true);
+ animation_toggle_skeleton_visibility->set_focus_mode(Control::FOCUS_NONE);
+ animation_toggle_skeleton_visibility->set_tooltip_text(TTR("Toggle Animation Skeleton Visibility"));
+
+ animation_toggle_skeleton_visibility->connect(SceneStringName(pressed), callable_mp(this, &SceneImportSettingsDialog::_animation_update_skeleton_visibility));
+
base_viewport->set_use_own_world_3d(true);
HBoxContainer *viewport_hbox = memnew(HBoxContainer);
@@ -1800,6 +1857,13 @@ SceneImportSettingsDialog::SceneImportSettingsDialog() {
collider_mat->set_albedo(Color(0.5, 0.5, 1.0));
}
+ {
+ bones_mesh_preview = memnew(MeshInstance3D);
+ bones_mesh_preview->set_cast_shadows_setting(GeometryInstance3D::SHADOW_CASTING_SETTING_OFF);
+ bones_mesh_preview->set_skeleton_path(NodePath());
+ base_viewport->add_child(bones_mesh_preview);
+ }
+
inspector = memnew(EditorInspector);
inspector->set_custom_minimum_size(Size2(300 * EDSCALE, 0));
inspector->connect(SNAME("property_edited"), callable_mp(this, &SceneImportSettingsDialog::_inspector_property_edited));
diff --git a/editor/import/3d/scene_import_settings.h b/editor/import/3d/scene_import_settings.h
index c2a5151432..1088acf772 100644
--- a/editor/import/3d/scene_import_settings.h
+++ b/editor/import/3d/scene_import_settings.h
@@ -109,10 +109,12 @@ class SceneImportSettingsDialog : public ConfirmationDialog {
HSlider *animation_slider = nullptr;
Button *animation_play_button = nullptr;
Button *animation_stop_button = nullptr;
+ Button *animation_toggle_skeleton_visibility = nullptr;
Animation::LoopMode animation_loop_mode = Animation::LOOP_NONE;
bool animation_pingpong = false;
bool previous_import_as_skeleton = false;
bool previous_rest_as_reset = false;
+ MeshInstance3D *bones_mesh_preview = nullptr;
Ref<StandardMaterial3D> collider_mat;
@@ -187,9 +189,11 @@ class SceneImportSettingsDialog : public ConfirmationDialog {
void _reset_animation(const String &p_animation_name = "");
void _animation_slider_value_changed(double p_value);
void _animation_finished(const StringName &p_name);
+ void _animation_update_skeleton_visibility();
void _material_tree_selected();
void _mesh_tree_selected();
void _scene_tree_selected();
+ void _skeleton_tree_entered(Skeleton3D *p_skeleton);
void _cleanup();
void _on_light_1_switch_pressed();
void _on_light_2_switch_pressed();
@@ -243,7 +247,7 @@ public:
bool is_editing_animation() const { return editing_animation; }
void request_generate_collider();
void update_view();
- void open_settings(const String &p_path, bool p_for_animation = false);
+ void open_settings(const String &p_path, const String &p_scene_import_type = "PackedScene");
static SceneImportSettingsDialog *get_singleton();
Node *get_selected_node();
SceneImportSettingsDialog();
diff --git a/editor/import/audio_stream_import_settings.cpp b/editor/import/audio_stream_import_settings.cpp
index a53deefee9..9a0c62193c 100644
--- a/editor/import/audio_stream_import_settings.cpp
+++ b/editor/import/audio_stream_import_settings.cpp
@@ -537,7 +537,7 @@ AudioStreamImportSettingsDialog::AudioStreamImportSettingsDialog() {
loop = memnew(CheckBox);
loop->set_text(TTR("Enable"));
loop->set_tooltip_text(TTR("Enable looping."));
- loop->connect("toggled", callable_mp(this, &AudioStreamImportSettingsDialog::_settings_changed).unbind(1));
+ loop->connect(SceneStringName(toggled), callable_mp(this, &AudioStreamImportSettingsDialog::_settings_changed).unbind(1));
loop_hb->add_child(loop);
loop_hb->add_spacer();
loop_hb->add_child(memnew(Label(TTR("Offset:"))));
@@ -554,7 +554,7 @@ AudioStreamImportSettingsDialog::AudioStreamImportSettingsDialog() {
interactive_hb->add_theme_constant_override("separation", 4 * EDSCALE);
bpm_enabled = memnew(CheckBox);
bpm_enabled->set_text((TTR("BPM:")));
- bpm_enabled->connect("toggled", callable_mp(this, &AudioStreamImportSettingsDialog::_settings_changed).unbind(1));
+ bpm_enabled->connect(SceneStringName(toggled), callable_mp(this, &AudioStreamImportSettingsDialog::_settings_changed).unbind(1));
interactive_hb->add_child(bpm_enabled);
bpm_edit = memnew(SpinBox);
bpm_edit->set_max(400);
@@ -565,7 +565,7 @@ AudioStreamImportSettingsDialog::AudioStreamImportSettingsDialog() {
interactive_hb->add_spacer();
beats_enabled = memnew(CheckBox);
beats_enabled->set_text(TTR("Beat Count:"));
- beats_enabled->connect("toggled", callable_mp(this, &AudioStreamImportSettingsDialog::_settings_changed).unbind(1));
+ beats_enabled->connect(SceneStringName(toggled), callable_mp(this, &AudioStreamImportSettingsDialog::_settings_changed).unbind(1));
interactive_hb->add_child(beats_enabled);
beats_edit = memnew(SpinBox);
beats_edit->set_tooltip_text(TTR("Configure the amount of Beats used for music-aware looping. If zero, it will be autodetected from the length.\nIt is recommended to set this value (either manually or by clicking on a beat number in the preview) to ensure looping works properly."));
diff --git a/editor/import/dynamic_font_import_settings.cpp b/editor/import/dynamic_font_import_settings.cpp
index 590e3a9ede..c526ca0b0c 100644
--- a/editor/import/dynamic_font_import_settings.cpp
+++ b/editor/import/dynamic_font_import_settings.cpp
@@ -509,7 +509,7 @@ void DynamicFontImportSettingsDialog::_variation_add() {
Ref<DynamicFontImportSettingsData> import_variation_data;
import_variation_data.instantiate();
import_variation_data->owner = this;
- ERR_FAIL_NULL(import_variation_data);
+ ERR_FAIL_COND(import_variation_data.is_null());
for (List<ResourceImporter::ImportOption>::Element *E = options_variations.front(); E; E = E->next()) {
import_variation_data->defaults[E->get().option.name] = E->get().default_value;
@@ -529,7 +529,7 @@ void DynamicFontImportSettingsDialog::_variation_selected() {
TreeItem *vars_item = vars_list->get_selected();
if (vars_item) {
Ref<DynamicFontImportSettingsData> import_variation_data = vars_item->get_metadata(0);
- ERR_FAIL_NULL(import_variation_data);
+ ERR_FAIL_COND(import_variation_data.is_null());
inspector_vars->edit(import_variation_data.ptr());
import_variation_data->notify_property_list_changed();
@@ -588,14 +588,14 @@ void DynamicFontImportSettingsDialog::_variations_validate() {
}
for (TreeItem *vars_item_a = vars_list_root->get_first_child(); vars_item_a; vars_item_a = vars_item_a->get_next()) {
Ref<DynamicFontImportSettingsData> import_variation_data_a = vars_item_a->get_metadata(0);
- ERR_FAIL_NULL(import_variation_data_a);
+ ERR_FAIL_COND(import_variation_data_a.is_null());
for (TreeItem *vars_item_b = vars_list_root->get_first_child(); vars_item_b; vars_item_b = vars_item_b->get_next()) {
if (vars_item_b != vars_item_a) {
bool match = true;
for (const KeyValue<StringName, Variant> &E : import_variation_data_a->settings) {
Ref<DynamicFontImportSettingsData> import_variation_data_b = vars_item_b->get_metadata(0);
- ERR_FAIL_NULL(import_variation_data_b);
+ ERR_FAIL_COND(import_variation_data_b.is_null());
match = match && (import_variation_data_b->settings[E.key] == E.value);
}
if (match) {
@@ -956,7 +956,7 @@ void DynamicFontImportSettingsDialog::_re_import() {
Array configurations;
for (TreeItem *vars_item = vars_list_root->get_first_child(); vars_item; vars_item = vars_item->get_next()) {
Ref<DynamicFontImportSettingsData> import_variation_data = vars_item->get_metadata(0);
- ERR_FAIL_NULL(import_variation_data);
+ ERR_FAIL_COND(import_variation_data.is_null());
Dictionary preload_config;
preload_config["name"] = vars_item->get_text(0);
@@ -1107,7 +1107,7 @@ void DynamicFontImportSettingsDialog::open_settings(const String &p_path) {
inspector_general->edit(nullptr);
text_settings_data.instantiate();
- ERR_FAIL_NULL(text_settings_data);
+ ERR_FAIL_COND(text_settings_data.is_null());
text_settings_data->owner = this;
@@ -1137,7 +1137,7 @@ void DynamicFontImportSettingsDialog::open_settings(const String &p_path) {
Ref<ConfigFile> config;
config.instantiate();
- ERR_FAIL_NULL(config);
+ ERR_FAIL_COND(config.is_null());
Error err = config->load(p_path + ".import");
print_verbose("Loading import settings:");
@@ -1169,7 +1169,7 @@ void DynamicFontImportSettingsDialog::open_settings(const String &p_path) {
Ref<DynamicFontImportSettingsData> import_variation_data_custom;
import_variation_data_custom.instantiate();
- ERR_FAIL_NULL(import_variation_data_custom);
+ ERR_FAIL_COND(import_variation_data_custom.is_null());
import_variation_data_custom->owner = this;
for (List<ResourceImporter::ImportOption>::Element *F = options_variations.front(); F; F = F->next()) {
diff --git a/editor/import/resource_importer_layered_texture.cpp b/editor/import/resource_importer_layered_texture.cpp
index 212a4160bf..552815a6af 100644
--- a/editor/import/resource_importer_layered_texture.cpp
+++ b/editor/import/resource_importer_layered_texture.cpp
@@ -510,7 +510,7 @@ void ResourceImporterLayeredTexture::_check_compress_ctex(const String &p_source
}
bool can_compress_hdr = r_texture_import->hdr_compression > 0;
- ERR_FAIL_NULL(r_texture_import->image);
+ ERR_FAIL_COND(r_texture_import->image.is_null());
bool is_hdr = (r_texture_import->image->get_format() >= Image::FORMAT_RF && r_texture_import->image->get_format() <= Image::FORMAT_RGBE9995);
ERR_FAIL_NULL(r_texture_import->slices);
// Can compress hdr, but hdr with alpha is not compressible.
diff --git a/editor/import/resource_importer_wav.cpp b/editor/import/resource_importer_wav.cpp
index 6d3d474cee..77b3629b07 100644
--- a/editor/import/resource_importer_wav.cpp
+++ b/editor/import/resource_importer_wav.cpp
@@ -90,7 +90,8 @@ void ResourceImporterWAV::get_import_options(const String &p_path, List<ImportOp
r_options->push_back(ImportOption(PropertyInfo(Variant::INT, "edit/loop_mode", PROPERTY_HINT_ENUM, "Detect From WAV,Disabled,Forward,Ping-Pong,Backward", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_UPDATE_ALL_IF_MODIFIED), 0));
r_options->push_back(ImportOption(PropertyInfo(Variant::INT, "edit/loop_begin"), 0));
r_options->push_back(ImportOption(PropertyInfo(Variant::INT, "edit/loop_end"), -1));
- r_options->push_back(ImportOption(PropertyInfo(Variant::INT, "compress/mode", PROPERTY_HINT_ENUM, "Disabled,RAM (Ima-ADPCM),QOA (Quite OK Audio)"), 0));
+ // Quite OK Audio is lightweight enough and supports virtually every significant AudioStreamWAV feature.
+ r_options->push_back(ImportOption(PropertyInfo(Variant::INT, "compress/mode", PROPERTY_HINT_ENUM, "PCM (Uncompressed),IMA ADPCM,Quite OK Audio"), 2));
}
Error ResourceImporterWAV::import(const String &p_source_file, const String &p_save_path, const HashMap<StringName, Variant> &p_options, List<String> *r_platform_variants, List<String> *r_gen_files, Variant *r_metadata) {
@@ -517,16 +518,19 @@ Error ResourceImporterWAV::import(const String &p_source_file, const String &p_s
Vector<uint8_t> dst_data;
if (compression == 2) {
dst_format = AudioStreamWAV::FORMAT_QOA;
- qoa_desc desc = { 0, 0, 0, { { { 0 }, { 0 } } } };
+ qoa_desc desc = {};
uint32_t qoa_len = 0;
desc.samplerate = rate;
desc.samples = frames;
desc.channels = format_channels;
- void *encoded = qoa_encode((short *)pcm_data.ptrw(), &desc, &qoa_len);
- dst_data.resize(qoa_len);
- memcpy(dst_data.ptrw(), encoded, qoa_len);
+ void *encoded = qoa_encode((short *)pcm_data.ptr(), &desc, &qoa_len);
+ if (encoded) {
+ dst_data.resize(qoa_len);
+ memcpy(dst_data.ptrw(), encoded, qoa_len);
+ QOA_FREE(encoded);
+ }
} else {
dst_data = pcm_data;
}
diff --git a/editor/import_dock.cpp b/editor/import_dock.cpp
index a8f8e9ef11..16f4aeda95 100644
--- a/editor/import_dock.cpp
+++ b/editor/import_dock.cpp
@@ -171,7 +171,7 @@ void ImportDock::_add_keep_import_option(const String &p_importer_name) {
void ImportDock::_update_options(const String &p_path, const Ref<ConfigFile> &p_config) {
// Set the importer class to fetch the correct class in the XML class reference.
// This allows tooltips to display when hovering properties.
- if (params->importer != nullptr) {
+ if (params->importer.is_valid()) {
// Null check to avoid crashing if the "Keep File (exported as is)" mode is selected.
import_opts->set_object_class(params->importer->get_class_name());
}
diff --git a/editor/input_event_configuration_dialog.cpp b/editor/input_event_configuration_dialog.cpp
index dc839b02f6..c60197b96b 100644
--- a/editor/input_event_configuration_dialog.cpp
+++ b/editor/input_event_configuration_dialog.cpp
@@ -720,7 +720,7 @@ InputEventConfigurationDialog::InputEventConfigurationDialog() {
for (int i = 0; i < MOD_MAX; i++) {
String name = mods[i];
mod_checkboxes[i] = memnew(CheckBox);
- mod_checkboxes[i]->connect("toggled", callable_mp(this, &InputEventConfigurationDialog::_mod_toggled).bind(i));
+ mod_checkboxes[i]->connect(SceneStringName(toggled), callable_mp(this, &InputEventConfigurationDialog::_mod_toggled).bind(i));
mod_checkboxes[i]->set_text(name);
mod_checkboxes[i]->set_tooltip_text(TTR(mods_tip[i]));
mod_container->add_child(mod_checkboxes[i]);
@@ -729,7 +729,7 @@ InputEventConfigurationDialog::InputEventConfigurationDialog() {
mod_container->add_child(memnew(VSeparator));
autoremap_command_or_control_checkbox = memnew(CheckBox);
- autoremap_command_or_control_checkbox->connect("toggled", callable_mp(this, &InputEventConfigurationDialog::_autoremap_command_or_control_toggled));
+ autoremap_command_or_control_checkbox->connect(SceneStringName(toggled), callable_mp(this, &InputEventConfigurationDialog::_autoremap_command_or_control_toggled));
autoremap_command_or_control_checkbox->set_pressed(false);
autoremap_command_or_control_checkbox->set_text(TTR("Command / Control (auto)"));
autoremap_command_or_control_checkbox->set_tooltip_text(TTR("Automatically remaps between 'Meta' ('Command') and 'Control' depending on current platform."));
diff --git a/editor/inspector_dock.cpp b/editor/inspector_dock.cpp
index aa075c80c3..dc07403213 100644
--- a/editor/inspector_dock.cpp
+++ b/editor/inspector_dock.cpp
@@ -190,7 +190,7 @@ void InspectorDock::_menu_option_confirm(int p_option, bool p_confirmed) {
}
int history_id = EditorUndoRedoManager::get_singleton()->get_history_id_for_object(current);
- EditorUndoRedoManager::get_singleton()->clear_history(true, history_id);
+ EditorUndoRedoManager::get_singleton()->clear_history(history_id);
EditorNode::get_singleton()->edit_item(current, inspector);
}
diff --git a/editor/plugins/animation_blend_space_1d_editor.cpp b/editor/plugins/animation_blend_space_1d_editor.cpp
index 7d580e8de9..cbf8b27b32 100644
--- a/editor/plugins/animation_blend_space_1d_editor.cpp
+++ b/editor/plugins/animation_blend_space_1d_editor.cpp
@@ -713,7 +713,7 @@ AnimationNodeBlendSpace1DEditor::AnimationNodeBlendSpace1DEditor() {
top_hb->add_child(memnew(Label(TTR("Sync:"))));
sync = memnew(CheckBox);
top_hb->add_child(sync);
- sync->connect("toggled", callable_mp(this, &AnimationNodeBlendSpace1DEditor::_config_changed));
+ sync->connect(SceneStringName(toggled), callable_mp(this, &AnimationNodeBlendSpace1DEditor::_config_changed));
top_hb->add_child(memnew(VSeparator));
diff --git a/editor/plugins/animation_blend_space_2d_editor.cpp b/editor/plugins/animation_blend_space_2d_editor.cpp
index 55949df54f..934f26415a 100644
--- a/editor/plugins/animation_blend_space_2d_editor.cpp
+++ b/editor/plugins/animation_blend_space_2d_editor.cpp
@@ -961,7 +961,7 @@ AnimationNodeBlendSpace2DEditor::AnimationNodeBlendSpace2DEditor() {
top_hb->add_child(memnew(Label(TTR("Sync:"))));
sync = memnew(CheckBox);
top_hb->add_child(sync);
- sync->connect("toggled", callable_mp(this, &AnimationNodeBlendSpace2DEditor::_config_changed));
+ sync->connect(SceneStringName(toggled), callable_mp(this, &AnimationNodeBlendSpace2DEditor::_config_changed));
top_hb->add_child(memnew(VSeparator));
diff --git a/editor/plugins/animation_player_editor_plugin.cpp b/editor/plugins/animation_player_editor_plugin.cpp
index 660e4647a1..5cb558abbe 100644
--- a/editor/plugins/animation_player_editor_plugin.cpp
+++ b/editor/plugins/animation_player_editor_plugin.cpp
@@ -224,6 +224,69 @@ void AnimationPlayerEditor::_autoplay_pressed() {
}
}
+void AnimationPlayerEditor::_go_to_nearest_keyframe(bool p_backward) {
+ if (_get_current().is_empty()) {
+ return;
+ }
+
+ Ref<Animation> anim = player->get_animation(player->get_assigned_animation());
+
+ double current_time = player->get_current_animation_position();
+ // Offset the time to avoid finding the same keyframe with Animation::track_find_key().
+ double time_offset = MAX(CMP_EPSILON * 2, current_time * CMP_EPSILON * 2);
+ double current_time_offset = current_time + (p_backward ? -time_offset : time_offset);
+
+ float nearest_key_time = p_backward ? 0 : anim->get_length();
+ int track_count = anim->get_track_count();
+ bool bezier_active = track_editor->is_bezier_editor_active();
+
+ Node *root = get_tree()->get_edited_scene_root();
+ EditorSelection *selection = EditorNode::get_singleton()->get_editor_selection();
+
+ Vector<int> selected_tracks;
+ for (int i = 0; i < track_count; ++i) {
+ if (selection->is_selected(root->get_node_or_null(anim->track_get_path(i)))) {
+ selected_tracks.push_back(i);
+ }
+ }
+
+ // Find the nearest keyframe in selection if the scene has selected nodes
+ // or the nearest keyframe in the entire animation otherwise.
+ if (selected_tracks.size() > 0) {
+ for (int track : selected_tracks) {
+ if (bezier_active && anim->track_get_type(track) != Animation::TYPE_BEZIER) {
+ continue;
+ }
+ int key = anim->track_find_key(track, current_time_offset, Animation::FIND_MODE_NEAREST, false, !p_backward);
+ if (key == -1) {
+ continue;
+ }
+ double key_time = anim->track_get_key_time(track, key);
+ if ((p_backward && key_time > nearest_key_time) || (!p_backward && key_time < nearest_key_time)) {
+ nearest_key_time = key_time;
+ }
+ }
+ } else {
+ for (int track = 0; track < track_count; ++track) {
+ if (bezier_active && anim->track_get_type(track) != Animation::TYPE_BEZIER) {
+ continue;
+ }
+ int key = anim->track_find_key(track, current_time_offset, Animation::FIND_MODE_NEAREST, false, !p_backward);
+ if (key == -1) {
+ continue;
+ }
+ double key_time = anim->track_get_key_time(track, key);
+ if ((p_backward && key_time > nearest_key_time) || (!p_backward && key_time < nearest_key_time)) {
+ nearest_key_time = key_time;
+ }
+ }
+ }
+
+ player->seek_internal(nearest_key_time, true, true, true);
+ frame->set_value(nearest_key_time);
+ track_editor->set_anim_pos(nearest_key_time);
+}
+
void AnimationPlayerEditor::_play_pressed() {
String current = _get_current();
@@ -339,7 +402,17 @@ void AnimationPlayerEditor::_animation_selected(int p_which) {
track_editor->set_animation(anim, animation_is_readonly);
Node *root = player->get_node_or_null(player->get_root_node());
- if (root) {
+
+ // Player shouldn't access parent if it's the scene root.
+ if (!root || (player == get_tree()->get_edited_scene_root() && player->get_root_node() == SceneStringName(path_pp))) {
+ NodePath cached_root_path = player->get_path_to(get_cached_root_node());
+ if (player->get_node_or_null(cached_root_path) != nullptr) {
+ player->set_root_node(cached_root_path);
+ } else {
+ player->set_root_node(SceneStringName(path_pp)); // No other choice, preventing crash.
+ }
+ } else {
+ cached_root_node_id = root->get_instance_id(); // Caching as `track_editor` can lose track of player's root node.
track_editor->set_root(root);
}
}
@@ -474,17 +547,12 @@ void AnimationPlayerEditor::_select_anim_by_name(const String &p_anim) {
}
float AnimationPlayerEditor::_get_editor_step() const {
- // Returns the effective snapping value depending on snapping modifiers, or 0 if snapping is disabled.
- if (track_editor->is_snap_enabled()) {
- const String current = player->get_assigned_animation();
- const Ref<Animation> anim = player->get_animation(current);
- ERR_FAIL_COND_V(!anim.is_valid(), 0.0);
+ const String current = player->get_assigned_animation();
+ const Ref<Animation> anim = player->get_animation(current);
+ ERR_FAIL_COND_V(anim.is_null(), 0.0);
- // Use more precise snapping when holding Shift
- return Input::get_singleton()->is_key_pressed(Key::SHIFT) ? anim->get_step() * 0.25 : anim->get_step();
- }
-
- return 0.0f;
+ // Use more precise snapping when holding Shift
+ return Input::get_singleton()->is_key_pressed(Key::SHIFT) ? anim->get_step() * 0.25 : anim->get_step();
}
void AnimationPlayerEditor::_animation_name_edited() {
@@ -1290,7 +1358,7 @@ void AnimationPlayerEditor::_seek_value_changed(float p_value, bool p_timeline_o
anim = player->get_animation(current);
double pos = CLAMP((double)anim->get_length() * (p_value / frame->get_max()), 0, (double)anim->get_length());
- if (track_editor->is_snap_enabled()) {
+ if (track_editor->is_snap_timeline_enabled()) {
pos = Math::snapped(pos, _get_editor_step());
}
pos = CLAMP(pos, 0, (double)anim->get_length() - CMP_EPSILON2); // Hack: Avoid fposmod with LOOP_LINEAR.
@@ -1408,11 +1476,17 @@ void AnimationPlayerEditor::_animation_key_editor_seek(float p_pos, bool p_timel
}
updating = true;
- frame->set_value(Math::snapped(p_pos, _get_editor_step()));
+ frame->set_value(track_editor->is_snap_timeline_enabled() ? Math::snapped(p_pos, _get_editor_step()) : p_pos);
updating = false;
_seek_value_changed(p_pos, p_timeline_only);
}
+void AnimationPlayerEditor::_animation_update_key_frame() {
+ if (player) {
+ player->advance(0);
+ }
+}
+
void AnimationPlayerEditor::_animation_tool_menu(int p_option) {
String current = _get_current();
@@ -1505,30 +1579,28 @@ void AnimationPlayerEditor::shortcut_input(const Ref<InputEvent> &p_ev) {
ERR_FAIL_COND(p_ev.is_null());
Ref<InputEventKey> k = p_ev;
- if (is_visible_in_tree() && k.is_valid() && k->is_pressed() && !k->is_echo() && !k->is_alt_pressed() && !k->is_ctrl_pressed() && !k->is_meta_pressed()) {
- switch (k->get_keycode()) {
- case Key::A: {
- if (!k->is_shift_pressed()) {
- _play_bw_from_pressed();
- } else {
- _play_bw_pressed();
- }
- accept_event();
- } break;
- case Key::S: {
- _stop_pressed();
- accept_event();
- } break;
- case Key::D: {
- if (!k->is_shift_pressed()) {
- _play_from_pressed();
- } else {
- _play_pressed();
- }
- accept_event();
- } break;
- default:
- break;
+ if (is_visible_in_tree() && k.is_valid() && k->is_pressed() && !k->is_echo()) {
+ if (ED_IS_SHORTCUT("animation_editor/stop_animation", p_ev)) {
+ _stop_pressed();
+ accept_event();
+ } else if (ED_IS_SHORTCUT("animation_editor/play_animation", p_ev)) {
+ _play_from_pressed();
+ accept_event();
+ } else if (ED_IS_SHORTCUT("animation_editor/play_animation_backwards", p_ev)) {
+ _play_bw_from_pressed();
+ accept_event();
+ } else if (ED_IS_SHORTCUT("animation_editor/play_animation_from_start", p_ev)) {
+ _play_pressed();
+ accept_event();
+ } else if (ED_IS_SHORTCUT("animation_editor/play_animation_from_end", p_ev)) {
+ _play_bw_pressed();
+ accept_event();
+ } else if (ED_IS_SHORTCUT("animation_editor/go_to_next_keyframe", p_ev)) {
+ _go_to_nearest_keyframe(false);
+ accept_event();
+ } else if (ED_IS_SHORTCUT("animation_editor/go_to_previous_keyframe", p_ev)) {
+ _go_to_nearest_keyframe(true);
+ accept_event();
}
}
}
@@ -1824,6 +1896,10 @@ AnimationMixer *AnimationPlayerEditor::fetch_mixer_for_library() const {
return original_node;
}
+Node *AnimationPlayerEditor::get_cached_root_node() const {
+ return Object::cast_to<Node>(ObjectDB::get_instance(cached_root_node_id));
+}
+
bool AnimationPlayerEditor::_validate_tracks(const Ref<Animation> p_anim) {
bool is_valid = true;
if (!p_anim.is_valid()) {
@@ -1874,6 +1950,7 @@ bool AnimationPlayerEditor::_validate_tracks(const Ref<Animation> p_anim) {
void AnimationPlayerEditor::_bind_methods() {
// Needed for UndoRedo.
ClassDB::bind_method(D_METHOD("_animation_player_changed"), &AnimationPlayerEditor::_animation_player_changed);
+ ClassDB::bind_method(D_METHOD("_animation_update_key_frame"), &AnimationPlayerEditor::_animation_update_key_frame);
ClassDB::bind_method(D_METHOD("_start_onion_skinning"), &AnimationPlayerEditor::_start_onion_skinning);
ClassDB::bind_method(D_METHOD("_stop_onion_skinning"), &AnimationPlayerEditor::_stop_onion_skinning);
@@ -1902,27 +1979,27 @@ AnimationPlayerEditor::AnimationPlayerEditor(AnimationPlayerEditorPlugin *p_plug
play_bw_from = memnew(Button);
play_bw_from->set_theme_type_variation("FlatButton");
- play_bw_from->set_tooltip_text(TTR("Play selected animation backwards from current pos. (A)"));
+ play_bw_from->set_tooltip_text(TTR("Play Animation Backwards"));
hb->add_child(play_bw_from);
play_bw = memnew(Button);
play_bw->set_theme_type_variation("FlatButton");
- play_bw->set_tooltip_text(TTR("Play selected animation backwards from end. (Shift+A)"));
+ play_bw->set_tooltip_text(TTR("Play Animation Backwards from End"));
hb->add_child(play_bw);
stop = memnew(Button);
stop->set_theme_type_variation("FlatButton");
+ stop->set_tooltip_text(TTR("Pause/Stop Animation"));
hb->add_child(stop);
- stop->set_tooltip_text(TTR("Pause/stop animation playback. (S)"));
play = memnew(Button);
play->set_theme_type_variation("FlatButton");
- play->set_tooltip_text(TTR("Play selected animation from start. (Shift+D)"));
+ play->set_tooltip_text(TTR("Play Animation from Start"));
hb->add_child(play);
play_from = memnew(Button);
play_from->set_theme_type_variation("FlatButton");
- play_from->set_tooltip_text(TTR("Play selected animation from current pos. (D)"));
+ play_from->set_tooltip_text(TTR("Play Animation"));
hb->add_child(play_from);
frame = memnew(SpinBox);
@@ -2138,6 +2215,14 @@ void fragment() {
}
)");
RS::get_singleton()->material_set_shader(onion.capture.material->get_rid(), onion.capture.shader->get_rid());
+
+ ED_SHORTCUT("animation_editor/stop_animation", TTR("Pause/Stop Animation"), Key::S);
+ ED_SHORTCUT("animation_editor/play_animation", TTR("Play Animation"), Key::D);
+ ED_SHORTCUT("animation_editor/play_animation_backwards", TTR("Play Animation Backwards"), Key::A);
+ ED_SHORTCUT("animation_editor/play_animation_from_start", TTR("Play Animation from Start"), KeyModifierMask::SHIFT + Key::D);
+ ED_SHORTCUT("animation_editor/play_animation_from_end", TTR("Play Animation Backwards from End"), KeyModifierMask::SHIFT + Key::A);
+ ED_SHORTCUT("animation_editor/go_to_next_keyframe", TTR("Go to Next Keyframe"), KeyModifierMask::SHIFT + KeyModifierMask::ALT + Key::D);
+ ED_SHORTCUT("animation_editor/go_to_previous_keyframe", TTR("Go to Previous Keyframe"), KeyModifierMask::SHIFT + KeyModifierMask::ALT + Key::A);
}
AnimationPlayerEditor::~AnimationPlayerEditor() {
@@ -2165,7 +2250,7 @@ void AnimationPlayerEditorPlugin::_property_keyed(const String &p_keyed, const V
return;
}
te->_clear_selection();
- te->insert_value_key(p_keyed, p_value, p_advance);
+ te->insert_value_key(p_keyed, p_advance);
}
void AnimationPlayerEditorPlugin::_transform_key_request(Object *sp, const String &p_sub, const Transform3D &p_key) {
diff --git a/editor/plugins/animation_player_editor_plugin.h b/editor/plugins/animation_player_editor_plugin.h
index 4a3b1f37ab..e4ca6c17c3 100644
--- a/editor/plugins/animation_player_editor_plugin.h
+++ b/editor/plugins/animation_player_editor_plugin.h
@@ -52,6 +52,7 @@ class AnimationPlayerEditor : public VBoxContainer {
AnimationPlayerEditorPlugin *plugin = nullptr;
AnimationMixer *original_node = nullptr; // For pinned mark in SceneTree.
AnimationPlayer *player = nullptr; // For AnimationPlayerEditor, could be dummy.
+ ObjectID cached_root_node_id;
bool is_dummy = false;
enum {
@@ -178,6 +179,7 @@ class AnimationPlayerEditor : public VBoxContainer {
void _select_anim_by_name(const String &p_anim);
float _get_editor_step() const;
+ void _go_to_nearest_keyframe(bool p_backward);
void _play_pressed();
void _play_from_pressed();
void _play_bw_pressed();
@@ -216,6 +218,7 @@ class AnimationPlayerEditor : public VBoxContainer {
void _animation_key_editor_seek(float p_pos, bool p_timeline_only = false, bool p_update_position_only = false);
void _animation_key_editor_anim_len_changed(float p_len);
+ void _animation_update_key_frame();
virtual void shortcut_input(const Ref<InputEvent> &p_ev) override;
void _animation_tool_menu(int p_option);
@@ -251,6 +254,7 @@ public:
AnimationMixer *get_editing_node() const;
AnimationPlayer *get_player() const;
AnimationMixer *fetch_mixer_for_library() const;
+ Node *get_cached_root_node() const;
static AnimationPlayerEditor *get_singleton() { return singleton; }
diff --git a/editor/plugins/canvas_item_editor_plugin.cpp b/editor/plugins/canvas_item_editor_plugin.cpp
index 1afe2ddda7..4b7bf1674f 100644
--- a/editor/plugins/canvas_item_editor_plugin.cpp
+++ b/editor/plugins/canvas_item_editor_plugin.cpp
@@ -64,6 +64,7 @@
#include "scene/resources/style_box_texture.h"
#define RULER_WIDTH (15 * EDSCALE)
+#define DRAG_THRESHOLD (8 * EDSCALE)
constexpr real_t SCALE_HANDLE_DISTANCE = 25;
constexpr real_t MOVE_HANDLE_DISTANCE = 25;
@@ -2319,7 +2320,7 @@ bool CanvasItemEditor::_gui_input_select(const Ref<InputEvent> &p_event) {
Ref<InputEventMouseMotion> m = p_event;
Ref<InputEventKey> k = p_event;
- if (drag_type == DRAG_NONE) {
+ if (drag_type == DRAG_NONE || (drag_type == DRAG_BOX_SELECTION && b.is_valid() && !b->is_pressed())) {
if (b.is_valid() && b->is_pressed() &&
((b->get_button_index() == MouseButton::RIGHT && b->is_alt_pressed() && tool == TOOL_SELECT) ||
(b->get_button_index() == MouseButton::LEFT && tool == TOOL_LIST_SELECT))) {
@@ -2411,47 +2412,58 @@ bool CanvasItemEditor::_gui_input_select(const Ref<InputEvent> &p_event) {
return true;
}
- if (b.is_valid() && b->get_button_index() == MouseButton::LEFT && b->is_pressed() && !panner->is_panning() && (tool == TOOL_SELECT || tool == TOOL_MOVE || tool == TOOL_SCALE || tool == TOOL_ROTATE)) {
- // Single item selection
- Point2 click = transform.affine_inverse().xform(b->get_position());
+ Point2 click;
+ bool can_select = b.is_valid() && b->get_button_index() == MouseButton::LEFT && !panner->is_panning() && (tool == TOOL_SELECT || tool == TOOL_MOVE || tool == TOOL_SCALE || tool == TOOL_ROTATE);
+ if (can_select) {
+ click = transform.affine_inverse().xform(b->get_position());
+ // Allow selecting on release when performed very small box selection (necessary when Shift is pressed, see below).
+ can_select = b->is_pressed() || (drag_type == DRAG_BOX_SELECTION && click.distance_to(drag_from) <= DRAG_THRESHOLD);
+ }
+ if (can_select) {
+ // Single item selection.
Node *scene = EditorNode::get_singleton()->get_edited_scene();
if (!scene) {
return true;
}
- // Find the item to select
+ // Find the item to select.
CanvasItem *ci = nullptr;
Vector<_SelectResult> selection = Vector<_SelectResult>();
- // Retrieve the canvas items
+ // Retrieve the canvas items.
_get_canvas_items_at_pos(click, selection);
if (!selection.is_empty()) {
ci = selection[0].item;
}
- if (!ci) {
- // Start a box selection
+ // Shift also allows forcing box selection when item was clicked.
+ if (!ci || (b->is_shift_pressed() && b->is_pressed())) {
+ // Start a box selection.
if (!b->is_shift_pressed()) {
- // Clear the selection if not additive
+ // Clear the selection if not additive.
editor_selection->clear();
viewport->queue_redraw();
selected_from_canvas = true;
};
- drag_from = click;
- drag_type = DRAG_BOX_SELECTION;
- box_selecting_to = drag_from;
- return true;
+ if (b->is_pressed()) {
+ drag_from = click;
+ drag_type = DRAG_BOX_SELECTION;
+ box_selecting_to = drag_from;
+ return true;
+ }
} else {
bool still_selected = _select_click_on_item(ci, click, b->is_shift_pressed());
- // Start dragging
- if (still_selected && (tool == TOOL_SELECT || tool == TOOL_MOVE)) {
- // Drag the node(s) if requested
+ // Start dragging.
+ if (still_selected && (tool == TOOL_SELECT || tool == TOOL_MOVE) && b->is_pressed()) {
+ // Drag the node(s) if requested.
drag_start_origin = click;
drag_type = DRAG_QUEUED;
+ } else if (!b->is_pressed()) {
+ _reset_drag();
}
- // Select the item
+ // Select the item.
return true;
}
}
@@ -4108,7 +4120,8 @@ void CanvasItemEditor::_notification(int p_what) {
}
} break;
- case NOTIFICATION_APPLICATION_FOCUS_OUT: {
+ case NOTIFICATION_APPLICATION_FOCUS_OUT:
+ case NOTIFICATION_WM_WINDOW_FOCUS_OUT: {
if (drag_type != DRAG_NONE) {
_reset_drag();
viewport->queue_redraw();
@@ -4319,13 +4332,13 @@ void CanvasItemEditor::_insert_animation_keys(bool p_location, bool p_rotation,
Node2D *n2d = Object::cast_to<Node2D>(ci);
if (key_pos && p_location) {
- te->insert_node_value_key(n2d, "position", n2d->get_position(), p_on_existing);
+ te->insert_node_value_key(n2d, "position", p_on_existing);
}
if (key_rot && p_rotation) {
- te->insert_node_value_key(n2d, "rotation", n2d->get_rotation(), p_on_existing);
+ te->insert_node_value_key(n2d, "rotation", p_on_existing);
}
if (key_scale && p_scale) {
- te->insert_node_value_key(n2d, "scale", n2d->get_scale(), p_on_existing);
+ te->insert_node_value_key(n2d, "scale", p_on_existing);
}
if (n2d->has_meta("_edit_bone_") && n2d->get_parent_item()) {
@@ -4351,13 +4364,13 @@ void CanvasItemEditor::_insert_animation_keys(bool p_location, bool p_rotation,
if (has_chain && ik_chain.size()) {
for (Node2D *&F : ik_chain) {
if (key_pos) {
- te->insert_node_value_key(F, "position", F->get_position(), p_on_existing);
+ te->insert_node_value_key(F, "position", p_on_existing);
}
if (key_rot) {
- te->insert_node_value_key(F, "rotation", F->get_rotation(), p_on_existing);
+ te->insert_node_value_key(F, "rotation", p_on_existing);
}
if (key_scale) {
- te->insert_node_value_key(F, "scale", F->get_scale(), p_on_existing);
+ te->insert_node_value_key(F, "scale", p_on_existing);
}
}
}
@@ -4367,13 +4380,13 @@ void CanvasItemEditor::_insert_animation_keys(bool p_location, bool p_rotation,
Control *ctrl = Object::cast_to<Control>(ci);
if (key_pos) {
- te->insert_node_value_key(ctrl, "position", ctrl->get_position(), p_on_existing);
+ te->insert_node_value_key(ctrl, "position", p_on_existing);
}
if (key_rot) {
- te->insert_node_value_key(ctrl, "rotation", ctrl->get_rotation(), p_on_existing);
+ te->insert_node_value_key(ctrl, "rotation", p_on_existing);
}
if (key_scale) {
- te->insert_node_value_key(ctrl, "size", ctrl->get_size(), p_on_existing);
+ te->insert_node_value_key(ctrl, "size", p_on_existing);
}
}
}
@@ -5397,7 +5410,7 @@ CanvasItemEditor::CanvasItemEditor() {
smart_snap_button->set_theme_type_variation("FlatButton");
main_menu_hbox->add_child(smart_snap_button);
smart_snap_button->set_toggle_mode(true);
- smart_snap_button->connect("toggled", callable_mp(this, &CanvasItemEditor::_button_toggle_smart_snap));
+ smart_snap_button->connect(SceneStringName(toggled), callable_mp(this, &CanvasItemEditor::_button_toggle_smart_snap));
smart_snap_button->set_tooltip_text(TTR("Toggle smart snapping."));
smart_snap_button->set_shortcut(ED_SHORTCUT("canvas_item_editor/use_smart_snap", TTR("Use Smart Snap"), KeyModifierMask::SHIFT | Key::S));
smart_snap_button->set_shortcut_context(this);
@@ -5406,7 +5419,7 @@ CanvasItemEditor::CanvasItemEditor() {
grid_snap_button->set_theme_type_variation("FlatButton");
main_menu_hbox->add_child(grid_snap_button);
grid_snap_button->set_toggle_mode(true);
- grid_snap_button->connect("toggled", callable_mp(this, &CanvasItemEditor::_button_toggle_grid_snap));
+ grid_snap_button->connect(SceneStringName(toggled), callable_mp(this, &CanvasItemEditor::_button_toggle_grid_snap));
grid_snap_button->set_tooltip_text(TTR("Toggle grid snapping."));
grid_snap_button->set_shortcut(ED_SHORTCUT("canvas_item_editor/use_grid_snap", TTR("Use Grid Snap"), KeyModifierMask::SHIFT | Key::G));
grid_snap_button->set_shortcut_context(this);
@@ -5499,7 +5512,7 @@ CanvasItemEditor::CanvasItemEditor() {
override_camera_button = memnew(Button);
override_camera_button->set_theme_type_variation("FlatButton");
main_menu_hbox->add_child(override_camera_button);
- override_camera_button->connect("toggled", callable_mp(this, &CanvasItemEditor::_button_override_camera));
+ override_camera_button->connect(SceneStringName(toggled), callable_mp(this, &CanvasItemEditor::_button_override_camera));
override_camera_button->set_toggle_mode(true);
override_camera_button->set_disabled(true);
_update_override_camera_button(false);
diff --git a/editor/plugins/control_editor_plugin.cpp b/editor/plugins/control_editor_plugin.cpp
index df20395ac5..5c5f236ff3 100644
--- a/editor/plugins/control_editor_plugin.cpp
+++ b/editor/plugins/control_editor_plugin.cpp
@@ -1084,7 +1084,7 @@ ControlEditorToolbar::ControlEditorToolbar() {
anchor_mode_button->set_toggle_mode(true);
anchor_mode_button->set_tooltip_text(TTR("When active, moving Control nodes changes their anchors instead of their offsets."));
add_child(anchor_mode_button);
- anchor_mode_button->connect("toggled", callable_mp(this, &ControlEditorToolbar::_anchor_mode_toggled));
+ anchor_mode_button->connect(SceneStringName(toggled), callable_mp(this, &ControlEditorToolbar::_anchor_mode_toggled));
// Container tools.
containers_button = memnew(ControlEditorPopupButton);
diff --git a/editor/plugins/curve_editor_plugin.cpp b/editor/plugins/curve_editor_plugin.cpp
index 180de700b7..e518cf7815 100644
--- a/editor/plugins/curve_editor_plugin.cpp
+++ b/editor/plugins/curve_editor_plugin.cpp
@@ -1003,7 +1003,7 @@ CurveEditor::CurveEditor() {
snap_button->set_tooltip_text(TTR("Toggle Grid Snap"));
snap_button->set_toggle_mode(true);
toolbar->add_child(snap_button);
- snap_button->connect("toggled", callable_mp(this, &CurveEditor::_set_snap_enabled));
+ snap_button->connect(SceneStringName(toggled), callable_mp(this, &CurveEditor::_set_snap_enabled));
toolbar->add_child(memnew(VSeparator));
diff --git a/editor/plugins/editor_context_menu_plugin.cpp b/editor/plugins/editor_context_menu_plugin.cpp
new file mode 100644
index 0000000000..86d3c0a896
--- /dev/null
+++ b/editor/plugins/editor_context_menu_plugin.cpp
@@ -0,0 +1,65 @@
+/**************************************************************************/
+/* editor_context_menu_plugin.cpp */
+/**************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/**************************************************************************/
+/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/**************************************************************************/
+
+#include "editor_context_menu_plugin.h"
+
+#include "core/input/shortcut.h"
+#include "editor/editor_node.h"
+#include "scene/resources/texture.h"
+
+void EditorContextMenuPlugin::add_options(const Vector<String> &p_paths) {
+ GDVIRTUAL_CALL(_popup_menu, p_paths);
+}
+
+void EditorContextMenuPlugin::add_menu_shortcut(const Ref<Shortcut> &p_shortcut, const Callable &p_callable) {
+ context_menu_shortcuts.insert(p_shortcut, p_callable);
+}
+
+void EditorContextMenuPlugin::add_context_menu_item(const String &p_name, const Callable &p_callable, const Ref<Texture2D> &p_texture, const Ref<Shortcut> &p_shortcut) {
+ ERR_FAIL_COND_MSG(context_menu_items.has(p_name), "Context menu item already registered.");
+ ERR_FAIL_COND_MSG(context_menu_items.size() == MAX_ITEMS, "Maximum number of context menu items reached.");
+ ContextMenuItem item;
+ item.item_name = p_name;
+ item.callable = p_callable;
+ item.icon = p_texture;
+ item.shortcut = p_shortcut;
+ item.idx = EditorData::CONTEXT_MENU_ITEM_ID_BASE + start_idx + context_menu_shortcuts.size() + context_menu_items.size();
+ context_menu_items.insert(p_name, item);
+}
+
+void EditorContextMenuPlugin::clear_context_menu_items() {
+ context_menu_items.clear();
+}
+
+void EditorContextMenuPlugin::_bind_methods() {
+ ClassDB::bind_method(D_METHOD("add_menu_shortcut", "shortcut", "callback"), &EditorContextMenuPlugin::add_menu_shortcut);
+ ClassDB::bind_method(D_METHOD("add_context_menu_item", "name", "callback", "icon", "shortcut"), &EditorContextMenuPlugin::add_context_menu_item, DEFVAL(Ref<Texture2D>()), DEFVAL(Ref<Shortcut>()));
+ GDVIRTUAL_BIND(_popup_menu, "paths");
+}
diff --git a/editor/plugins/editor_context_menu_plugin.h b/editor/plugins/editor_context_menu_plugin.h
new file mode 100644
index 0000000000..db05e6c6c7
--- /dev/null
+++ b/editor/plugins/editor_context_menu_plugin.h
@@ -0,0 +1,70 @@
+/**************************************************************************/
+/* editor_context_menu_plugin.h */
+/**************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/**************************************************************************/
+/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/**************************************************************************/
+
+#ifndef EDITOR_CONTEXT_MENU_PLUGIN_H
+#define EDITOR_CONTEXT_MENU_PLUGIN_H
+
+#include "core/object/gdvirtual.gen.inc"
+#include "core/object/ref_counted.h"
+
+class Texture2D;
+class Shortcut;
+
+class EditorContextMenuPlugin : public RefCounted {
+ GDCLASS(EditorContextMenuPlugin, RefCounted);
+
+public:
+ int start_idx;
+
+ inline static constexpr int MAX_ITEMS = 100;
+
+ struct ContextMenuItem {
+ int idx = 0;
+ String item_name;
+ Callable callable;
+ Ref<Texture2D> icon;
+ Ref<Shortcut> shortcut;
+ };
+ HashMap<String, ContextMenuItem> context_menu_items;
+ HashMap<Ref<Shortcut>, Callable> context_menu_shortcuts;
+
+protected:
+ static void _bind_methods();
+
+ GDVIRTUAL1(_popup_menu, Vector<String>);
+
+public:
+ virtual void add_options(const Vector<String> &p_paths);
+ void add_menu_shortcut(const Ref<Shortcut> &p_shortcut, const Callable &p_callable);
+ void add_context_menu_item(const String &p_name, const Callable &p_callable, const Ref<Texture2D> &p_texture, const Ref<Shortcut> &p_shortcut);
+ void clear_context_menu_items();
+};
+
+#endif // EDITOR_CONTEXT_MENU_PLUGIN_H
diff --git a/editor/plugins/editor_plugin.cpp b/editor/plugins/editor_plugin.cpp
index d9f60e155d..0ee67e08ba 100644
--- a/editor/plugins/editor_plugin.cpp
+++ b/editor/plugins/editor_plugin.cpp
@@ -40,12 +40,14 @@
#include "editor/editor_translation_parser.h"
#include "editor/editor_undo_redo_manager.h"
#include "editor/export/editor_export.h"
+#include "editor/export/editor_export_platform.h"
#include "editor/gui/editor_bottom_panel.h"
#include "editor/gui/editor_title_bar.h"
#include "editor/import/3d/resource_importer_scene.h"
#include "editor/import/editor_import_plugin.h"
#include "editor/inspector_dock.h"
#include "editor/plugins/canvas_item_editor_plugin.h"
+#include "editor/plugins/editor_context_menu_plugin.h"
#include "editor/plugins/editor_debugger_plugin.h"
#include "editor/plugins/editor_resource_conversion_plugin.h"
#include "editor/plugins/node_3d_editor_plugin.h"
@@ -441,6 +443,16 @@ void EditorPlugin::remove_export_plugin(const Ref<EditorExportPlugin> &p_exporte
EditorExport::get_singleton()->remove_export_plugin(p_exporter);
}
+void EditorPlugin::add_export_platform(const Ref<EditorExportPlatform> &p_platform) {
+ ERR_FAIL_COND(p_platform.is_null());
+ EditorExport::get_singleton()->add_export_platform(p_platform);
+}
+
+void EditorPlugin::remove_export_platform(const Ref<EditorExportPlatform> &p_platform) {
+ ERR_FAIL_COND(p_platform.is_null());
+ EditorExport::get_singleton()->remove_export_platform(p_platform);
+}
+
void EditorPlugin::add_node_3d_gizmo_plugin(const Ref<EditorNode3DGizmoPlugin> &p_gizmo_plugin) {
ERR_FAIL_COND(!p_gizmo_plugin.is_valid());
Node3DEditor::get_singleton()->add_gizmo_plugin(p_gizmo_plugin);
@@ -479,6 +491,16 @@ void EditorPlugin::remove_scene_post_import_plugin(const Ref<EditorScenePostImpo
ResourceImporterScene::remove_post_importer_plugin(p_plugin);
}
+void EditorPlugin::add_context_menu_plugin(ContextMenuSlot p_slot, const Ref<EditorContextMenuPlugin> &p_plugin) {
+ ERR_FAIL_COND(p_plugin.is_null());
+ EditorNode::get_editor_data().add_context_menu_plugin(EditorData::ContextMenuSlot(p_slot), p_plugin);
+}
+
+void EditorPlugin::remove_context_menu_plugin(ContextMenuSlot p_slot, const Ref<EditorContextMenuPlugin> &p_plugin) {
+ ERR_FAIL_COND(p_plugin.is_null());
+ EditorNode::get_editor_data().remove_context_menu_plugin(EditorData::ContextMenuSlot(p_slot), p_plugin);
+}
+
int find(const PackedStringArray &a, const String &v) {
const String *r = a.ptr();
for (int j = 0; j < a.size(); ++j) {
@@ -608,6 +630,8 @@ void EditorPlugin::_bind_methods() {
ClassDB::bind_method(D_METHOD("remove_scene_post_import_plugin", "scene_import_plugin"), &EditorPlugin::remove_scene_post_import_plugin);
ClassDB::bind_method(D_METHOD("add_export_plugin", "plugin"), &EditorPlugin::add_export_plugin);
ClassDB::bind_method(D_METHOD("remove_export_plugin", "plugin"), &EditorPlugin::remove_export_plugin);
+ ClassDB::bind_method(D_METHOD("add_export_platform", "platform"), &EditorPlugin::add_export_platform);
+ ClassDB::bind_method(D_METHOD("remove_export_platform", "platform"), &EditorPlugin::remove_export_platform);
ClassDB::bind_method(D_METHOD("add_node_3d_gizmo_plugin", "plugin"), &EditorPlugin::add_node_3d_gizmo_plugin);
ClassDB::bind_method(D_METHOD("remove_node_3d_gizmo_plugin", "plugin"), &EditorPlugin::remove_node_3d_gizmo_plugin);
ClassDB::bind_method(D_METHOD("add_inspector_plugin", "plugin"), &EditorPlugin::add_inspector_plugin);
@@ -616,6 +640,8 @@ void EditorPlugin::_bind_methods() {
ClassDB::bind_method(D_METHOD("remove_resource_conversion_plugin", "plugin"), &EditorPlugin::remove_resource_conversion_plugin);
ClassDB::bind_method(D_METHOD("set_input_event_forwarding_always_enabled"), &EditorPlugin::set_input_event_forwarding_always_enabled);
ClassDB::bind_method(D_METHOD("set_force_draw_over_forwarding_enabled"), &EditorPlugin::set_force_draw_over_forwarding_enabled);
+ ClassDB::bind_method(D_METHOD("add_context_menu_plugin", "slot", "plugin"), &EditorPlugin::add_context_menu_plugin);
+ ClassDB::bind_method(D_METHOD("remove_context_menu_plugin", "slot", "plugin"), &EditorPlugin::remove_context_menu_plugin);
ClassDB::bind_method(D_METHOD("get_editor_interface"), &EditorPlugin::get_editor_interface);
ClassDB::bind_method(D_METHOD("get_script_create_dialog"), &EditorPlugin::get_script_create_dialog);
@@ -681,6 +707,11 @@ void EditorPlugin::_bind_methods() {
BIND_ENUM_CONSTANT(AFTER_GUI_INPUT_PASS);
BIND_ENUM_CONSTANT(AFTER_GUI_INPUT_STOP);
BIND_ENUM_CONSTANT(AFTER_GUI_INPUT_CUSTOM);
+
+ BIND_ENUM_CONSTANT(CONTEXT_SLOT_SCENE_TREE);
+ BIND_ENUM_CONSTANT(CONTEXT_SLOT_FILESYSTEM);
+ BIND_ENUM_CONSTANT(CONTEXT_SLOT_SCRIPT_EDITOR);
+ BIND_ENUM_CONSTANT(CONTEXT_SUBMENU_SLOT_FILESYSTEM_CREATE);
}
EditorUndoRedoManager *EditorPlugin::get_undo_redo() {
diff --git a/editor/plugins/editor_plugin.h b/editor/plugins/editor_plugin.h
index f6c4b35407..35f65f03cd 100644
--- a/editor/plugins/editor_plugin.h
+++ b/editor/plugins/editor_plugin.h
@@ -41,6 +41,7 @@ class PopupMenu;
class EditorDebuggerPlugin;
class EditorExport;
class EditorExportPlugin;
+class EditorExportPlatform;
class EditorImportPlugin;
class EditorInspectorPlugin;
class EditorInterface;
@@ -52,6 +53,7 @@ class EditorToolAddons;
class EditorTranslationParserPlugin;
class EditorUndoRedoManager;
class ScriptCreateDialog;
+class EditorContextMenuPlugin;
class EditorPlugin : public Node {
GDCLASS(EditorPlugin, Node);
@@ -101,6 +103,13 @@ public:
AFTER_GUI_INPUT_CUSTOM,
};
+ enum ContextMenuSlot {
+ CONTEXT_SLOT_SCENE_TREE,
+ CONTEXT_SLOT_FILESYSTEM,
+ CONTEXT_SLOT_SCRIPT_EDITOR,
+ CONTEXT_SUBMENU_SLOT_FILESYSTEM_CREATE,
+ };
+
protected:
void _notification(int p_what);
@@ -224,6 +233,9 @@ public:
void add_export_plugin(const Ref<EditorExportPlugin> &p_exporter);
void remove_export_plugin(const Ref<EditorExportPlugin> &p_exporter);
+ void add_export_platform(const Ref<EditorExportPlatform> &p_platform);
+ void remove_export_platform(const Ref<EditorExportPlatform> &p_platform);
+
void add_node_3d_gizmo_plugin(const Ref<EditorNode3DGizmoPlugin> &p_gizmo_plugin);
void remove_node_3d_gizmo_plugin(const Ref<EditorNode3DGizmoPlugin> &p_gizmo_plugin);
@@ -245,6 +257,9 @@ public:
void add_resource_conversion_plugin(const Ref<EditorResourceConversionPlugin> &p_plugin);
void remove_resource_conversion_plugin(const Ref<EditorResourceConversionPlugin> &p_plugin);
+ void add_context_menu_plugin(ContextMenuSlot p_slot, const Ref<EditorContextMenuPlugin> &p_plugin);
+ void remove_context_menu_plugin(ContextMenuSlot p_slot, const Ref<EditorContextMenuPlugin> &p_plugin);
+
void enable_plugin();
void disable_plugin();
@@ -255,6 +270,7 @@ public:
VARIANT_ENUM_CAST(EditorPlugin::CustomControlContainer);
VARIANT_ENUM_CAST(EditorPlugin::DockSlot);
VARIANT_ENUM_CAST(EditorPlugin::AfterGUIInput);
+VARIANT_ENUM_CAST(EditorPlugin::ContextMenuSlot);
typedef EditorPlugin *(*EditorPluginCreateFunc)();
diff --git a/editor/plugins/gizmos/joint_3d_gizmo_plugin.cpp b/editor/plugins/gizmos/joint_3d_gizmo_plugin.cpp
index ae24b4250e..c277ec8cd3 100644
--- a/editor/plugins/gizmos/joint_3d_gizmo_plugin.cpp
+++ b/editor/plugins/gizmos/joint_3d_gizmo_plugin.cpp
@@ -293,9 +293,15 @@ Joint3DGizmoPlugin::Joint3DGizmoPlugin() {
void Joint3DGizmoPlugin::incremental_update_gizmos() {
if (!current_gizmos.is_empty()) {
- update_idx++;
- update_idx = update_idx % current_gizmos.size();
- redraw(current_gizmos.get(update_idx));
+ HashSet<EditorNode3DGizmo *>::Iterator E = current_gizmos.find(last_drawn);
+ if (E) {
+ ++E;
+ }
+ if (!E) {
+ E = current_gizmos.begin();
+ }
+ redraw(*E);
+ last_drawn = *E;
}
}
diff --git a/editor/plugins/gizmos/joint_3d_gizmo_plugin.h b/editor/plugins/gizmos/joint_3d_gizmo_plugin.h
index 79fe40d1b2..25b08d71c6 100644
--- a/editor/plugins/gizmos/joint_3d_gizmo_plugin.h
+++ b/editor/plugins/gizmos/joint_3d_gizmo_plugin.h
@@ -37,7 +37,7 @@ class Joint3DGizmoPlugin : public EditorNode3DGizmoPlugin {
GDCLASS(Joint3DGizmoPlugin, EditorNode3DGizmoPlugin);
Timer *update_timer = nullptr;
- uint64_t update_idx = 0;
+ EditorNode3DGizmo *last_drawn = nullptr;
void incremental_update_gizmos();
diff --git a/editor/plugins/gradient_editor_plugin.cpp b/editor/plugins/gradient_editor_plugin.cpp
index 8bf5dad97f..1300394ca3 100644
--- a/editor/plugins/gradient_editor_plugin.cpp
+++ b/editor/plugins/gradient_editor_plugin.cpp
@@ -632,7 +632,7 @@ GradientEditor::GradientEditor() {
snap_button->set_tooltip_text(TTR("Toggle Grid Snap"));
snap_button->set_toggle_mode(true);
toolbar->add_child(snap_button);
- snap_button->connect("toggled", callable_mp(this, &GradientEditor::_set_snap_enabled));
+ snap_button->connect(SceneStringName(toggled), callable_mp(this, &GradientEditor::_set_snap_enabled));
snap_count_edit = memnew(EditorSpinSlider);
snap_count_edit->set_min(2);
diff --git a/editor/plugins/gradient_texture_2d_editor_plugin.cpp b/editor/plugins/gradient_texture_2d_editor_plugin.cpp
index 7e22e1209c..5bf1422780 100644
--- a/editor/plugins/gradient_texture_2d_editor_plugin.cpp
+++ b/editor/plugins/gradient_texture_2d_editor_plugin.cpp
@@ -290,7 +290,7 @@ GradientTexture2DEditor::GradientTexture2DEditor() {
snap_button->set_tooltip_text(TTR("Toggle Grid Snap"));
snap_button->set_toggle_mode(true);
toolbar->add_child(snap_button);
- snap_button->connect("toggled", callable_mp(this, &GradientTexture2DEditor::_set_snap_enabled));
+ snap_button->connect(SceneStringName(toggled), callable_mp(this, &GradientTexture2DEditor::_set_snap_enabled));
snap_count_edit = memnew(EditorSpinSlider);
snap_count_edit->set_min(2);
diff --git a/editor/plugins/node_3d_editor_gizmos.cpp b/editor/plugins/node_3d_editor_gizmos.cpp
index 67d5e44ce5..8aff3c9aec 100644
--- a/editor/plugins/node_3d_editor_gizmos.cpp
+++ b/editor/plugins/node_3d_editor_gizmos.cpp
@@ -431,12 +431,13 @@ void EditorNode3DGizmo::add_handles(const Vector<Vector3> &p_handles, const Ref<
colors.resize(p_handles.size());
Color *w = colors.ptrw();
for (int i = 0; i < p_handles.size(); i++) {
+ int id = p_ids.is_empty() ? i : p_ids[i];
+
Color col(1, 1, 1, 1);
- if (is_handle_highlighted(i, p_secondary)) {
+ if (is_handle_highlighted(id, p_secondary)) {
col = Color(0, 0, 1, 0.9);
}
- int id = p_ids.is_empty() ? i : p_ids[i];
if (!is_current_hover_gizmo || current_hover_handle != id || p_secondary != current_hover_handle_secondary) {
col.a = 0.8;
}
@@ -982,7 +983,7 @@ void EditorNode3DGizmoPlugin::create_handle_material(const String &p_name, bool
handle_material->set_shading_mode(StandardMaterial3D::SHADING_MODE_UNSHADED);
handle_material->set_flag(StandardMaterial3D::FLAG_USE_POINT_SIZE, true);
- Ref<Texture2D> handle_t = p_icon != nullptr ? p_icon : EditorNode::get_singleton()->get_editor_theme()->get_icon(SNAME("Editor3DHandle"), EditorStringName(EditorIcons));
+ Ref<Texture2D> handle_t = p_icon.is_valid() ? p_icon : EditorNode::get_singleton()->get_editor_theme()->get_icon(SNAME("Editor3DHandle"), EditorStringName(EditorIcons));
handle_material->set_point_size(handle_t->get_width());
handle_material->set_texture(StandardMaterial3D::TEXTURE_ALBEDO, handle_t);
handle_material->set_albedo(Color(1, 1, 1));
@@ -1060,7 +1061,7 @@ Ref<EditorNode3DGizmo> EditorNode3DGizmoPlugin::get_gizmo(Node3D *p_spatial) {
ref->set_node_3d(p_spatial);
ref->set_hidden(current_state == HIDDEN);
- current_gizmos.push_back(ref.ptr());
+ current_gizmos.insert(ref.ptr());
return ref;
}
diff --git a/editor/plugins/node_3d_editor_gizmos.h b/editor/plugins/node_3d_editor_gizmos.h
index c4b275032a..1916bc2058 100644
--- a/editor/plugins/node_3d_editor_gizmos.h
+++ b/editor/plugins/node_3d_editor_gizmos.h
@@ -157,7 +157,7 @@ public:
protected:
int current_state;
- List<EditorNode3DGizmo *> current_gizmos;
+ HashSet<EditorNode3DGizmo *> current_gizmos;
HashMap<String, Vector<Ref<StandardMaterial3D>>> materials;
static void _bind_methods();
diff --git a/editor/plugins/node_3d_editor_plugin.cpp b/editor/plugins/node_3d_editor_plugin.cpp
index d911e745db..44673e7224 100644
--- a/editor/plugins/node_3d_editor_plugin.cpp
+++ b/editor/plugins/node_3d_editor_plugin.cpp
@@ -770,7 +770,7 @@ void Node3DEditorViewport::_select_clicked(bool p_allow_locked) {
}
}
- if (p_allow_locked || !_is_node_locked(selected)) {
+ if (p_allow_locked || (selected != nullptr && !_is_node_locked(selected))) {
if (clicked_wants_append) {
if (editor_selection->is_selected(selected)) {
editor_selection->remove_node(selected);
@@ -1692,6 +1692,10 @@ void Node3DEditorViewport::_sinput(const Ref<InputEvent> &p_event) {
if (b.is_valid()) {
emit_signal(SNAME("clicked"), this);
+ ViewportNavMouseButton orbit_mouse_preference = (ViewportNavMouseButton)EDITOR_GET("editors/3d/navigation/orbit_mouse_button").operator int();
+ ViewportNavMouseButton pan_mouse_preference = (ViewportNavMouseButton)EDITOR_GET("editors/3d/navigation/pan_mouse_button").operator int();
+ ViewportNavMouseButton zoom_mouse_preference = (ViewportNavMouseButton)EDITOR_GET("editors/3d/navigation/zoom_mouse_button").operator int();
+
const real_t zoom_factor = 1 + (ZOOM_FREELOOK_MULTIPLIER - 1) * b->get_factor();
switch (b->get_button_index()) {
case MouseButton::WHEEL_UP: {
@@ -1709,8 +1713,6 @@ void Node3DEditorViewport::_sinput(const Ref<InputEvent> &p_event) {
}
} break;
case MouseButton::RIGHT: {
- NavigationScheme nav_scheme = (NavigationScheme)EDITOR_GET("editors/3d/navigation/navigation_scheme").operator int();
-
if (b->is_pressed() && _edit.gizmo.is_valid()) {
//restore
_edit.gizmo->commit_handle(_edit.gizmo_handle, _edit.gizmo_handle_secondary, _edit.gizmo_initial_value, true);
@@ -1718,11 +1720,15 @@ void Node3DEditorViewport::_sinput(const Ref<InputEvent> &p_event) {
}
if (_edit.mode == TRANSFORM_NONE && b->is_pressed()) {
- if (b->is_alt_pressed()) {
- if (nav_scheme == NAVIGATION_MAYA) {
- break;
- }
+ if (orbit_mouse_preference == NAVIGATION_RIGHT_MOUSE && _is_nav_modifier_pressed("spatial_editor/viewport_orbit_modifier_1") && _is_nav_modifier_pressed("spatial_editor/viewport_orbit_modifier_2")) {
+ break;
+ } else if (pan_mouse_preference == NAVIGATION_RIGHT_MOUSE && _is_nav_modifier_pressed("spatial_editor/viewport_pan_modifier_1") && _is_nav_modifier_pressed("spatial_editor/viewport_pan_modifier_2")) {
+ break;
+ } else if (zoom_mouse_preference == NAVIGATION_RIGHT_MOUSE && _is_nav_modifier_pressed("spatial_editor/viewport_zoom_modifier_1") && _is_nav_modifier_pressed("spatial_editor/viewport_zoom_modifier_2")) {
+ break;
+ }
+ if (b->is_alt_pressed()) {
_list_select(b);
return;
}
@@ -1753,6 +1759,14 @@ void Node3DEditorViewport::_sinput(const Ref<InputEvent> &p_event) {
} break;
case MouseButton::MIDDLE: {
if (b->is_pressed() && _edit.mode != TRANSFORM_NONE) {
+ if (orbit_mouse_preference == NAVIGATION_MIDDLE_MOUSE && _is_nav_modifier_pressed("spatial_editor/viewport_orbit_modifier_1") && _is_nav_modifier_pressed("spatial_editor/viewport_orbit_modifier_2")) {
+ break;
+ } else if (pan_mouse_preference == NAVIGATION_MIDDLE_MOUSE && _is_nav_modifier_pressed("spatial_editor/viewport_pan_modifier_1") && _is_nav_modifier_pressed("spatial_editor/viewport_pan_modifier_2")) {
+ break;
+ } else if (zoom_mouse_preference == NAVIGATION_MIDDLE_MOUSE && _is_nav_modifier_pressed("spatial_editor/viewport_zoom_modifier_1") && _is_nav_modifier_pressed("spatial_editor/viewport_zoom_modifier_2")) {
+ break;
+ }
+
switch (_edit.plane) {
case TRANSFORM_VIEW: {
_edit.plane = TRANSFORM_X_AXIS;
@@ -1791,8 +1805,11 @@ void Node3DEditorViewport::_sinput(const Ref<InputEvent> &p_event) {
commit_transform();
break; // just commit the edit, stop processing the event so we don't deselect the object
}
- NavigationScheme nav_scheme = (NavigationScheme)EDITOR_GET("editors/3d/navigation/navigation_scheme").operator int();
- if ((nav_scheme == NAVIGATION_MAYA || nav_scheme == NAVIGATION_MODO) && b->is_alt_pressed()) {
+ if (orbit_mouse_preference == NAVIGATION_LEFT_MOUSE && _is_nav_modifier_pressed("spatial_editor/viewport_orbit_modifier_1") && _is_nav_modifier_pressed("spatial_editor/viewport_orbit_modifier_2")) {
+ break;
+ } else if (pan_mouse_preference == NAVIGATION_LEFT_MOUSE && _is_nav_modifier_pressed("spatial_editor/viewport_pan_modifier_1") && _is_nav_modifier_pressed("spatial_editor/viewport_pan_modifier_2")) {
+ break;
+ } else if (zoom_mouse_preference == NAVIGATION_LEFT_MOUSE && _is_nav_modifier_pressed("spatial_editor/viewport_zoom_modifier_1") && _is_nav_modifier_pressed("spatial_editor/viewport_zoom_modifier_2")) {
break;
}
@@ -1811,7 +1828,9 @@ void Node3DEditorViewport::_sinput(const Ref<InputEvent> &p_event) {
{
int idx = view_menu->get_popup()->get_item_index(VIEW_GIZMOS);
+ int idx2 = view_menu->get_popup()->get_item_index(VIEW_TRANSFORM_GIZMO);
can_select_gizmos = can_select_gizmos && view_menu->get_popup()->is_item_checked(idx);
+ transform_gizmo_visible = view_menu->get_popup()->is_item_checked(idx2);
}
// Gizmo handles
@@ -1956,7 +1975,7 @@ void Node3DEditorViewport::_sinput(const Ref<InputEvent> &p_event) {
}
}
- if (_edit.mode != TRANSFORM_NONE) {
+ if (!_edit.instant && _edit.mode != TRANSFORM_NONE) {
Node3D *selected = spatial_editor->get_single_selected_node();
Node3DEditorSelectedItem *se = selected ? editor_selection->get_node_editor_data<Node3DEditorSelectedItem>(selected) : nullptr;
@@ -1988,6 +2007,24 @@ void Node3DEditorViewport::_sinput(const Ref<InputEvent> &p_event) {
}
}
+ ViewportNavMouseButton orbit_mouse_preference = (ViewportNavMouseButton)EDITOR_GET("editors/3d/navigation/orbit_mouse_button").operator int();
+ ViewportNavMouseButton pan_mouse_preference = (ViewportNavMouseButton)EDITOR_GET("editors/3d/navigation/pan_mouse_button").operator int();
+ ViewportNavMouseButton zoom_mouse_preference = (ViewportNavMouseButton)EDITOR_GET("editors/3d/navigation/zoom_mouse_button").operator int();
+ bool orbit_mod_pressed = _is_nav_modifier_pressed("spatial_editor/viewport_orbit_modifier_1") && _is_nav_modifier_pressed("spatial_editor/viewport_orbit_modifier_2");
+ bool pan_mod_pressed = _is_nav_modifier_pressed("spatial_editor/viewport_pan_modifier_1") && _is_nav_modifier_pressed("spatial_editor/viewport_pan_modifier_2");
+ bool zoom_mod_pressed = _is_nav_modifier_pressed("spatial_editor/viewport_zoom_modifier_1") && _is_nav_modifier_pressed("spatial_editor/viewport_zoom_modifier_2");
+ int orbit_mod_input_count = _get_shortcut_input_count("spatial_editor/viewport_orbit_modifier_1") + _get_shortcut_input_count("spatial_editor/viewport_orbit_modifier_2");
+ int pan_mod_input_count = _get_shortcut_input_count("spatial_editor/viewport_pan_modifier_1") + _get_shortcut_input_count("spatial_editor/viewport_pan_modifier_2");
+ int zoom_mod_input_count = _get_shortcut_input_count("spatial_editor/viewport_zoom_modifier_1") + _get_shortcut_input_count("spatial_editor/viewport_zoom_modifier_2");
+ bool orbit_not_empty = !_is_shortcut_empty("spatial_editor/viewport_zoom_modifier_1") || !_is_shortcut_empty("spatial_editor/viewport_zoom_modifier_2");
+ bool pan_not_empty = !_is_shortcut_empty("spatial_editor/viewport_pan_modifier_1") || !_is_shortcut_empty("spatial_editor/viewport_pan_modifier_2");
+ bool zoom_not_empty = !_is_shortcut_empty("spatial_editor/viewport_orbit_modifier_1") || !_is_shortcut_empty("spatial_editor/viewport_orbit_modifier_2");
+ Vector<ShortcutCheckSet> shortcut_check_sets;
+ shortcut_check_sets.push_back(ShortcutCheckSet(orbit_mod_pressed, orbit_not_empty, orbit_mod_input_count, orbit_mouse_preference, NAVIGATION_ORBIT));
+ shortcut_check_sets.push_back(ShortcutCheckSet(pan_mod_pressed, pan_not_empty, pan_mod_input_count, pan_mouse_preference, NAVIGATION_PAN));
+ shortcut_check_sets.push_back(ShortcutCheckSet(zoom_mod_pressed, zoom_not_empty, zoom_mod_input_count, zoom_mouse_preference, NAVIGATION_ZOOM));
+ shortcut_check_sets.sort_custom<ShortcutCheckSetComparator>();
+
Ref<InputEventMouseMotion> m = p_event;
// Instant transforms process mouse motion in input() to handle wrapping.
@@ -2032,7 +2069,6 @@ void Node3DEditorViewport::_sinput(const Ref<InputEvent> &p_event) {
_transform_gizmo_select(_edit.mouse_pos, true);
}
- NavigationScheme nav_scheme = (NavigationScheme)EDITOR_GET("editors/3d/navigation/navigation_scheme").operator int();
NavigationMode nav_mode = NAVIGATION_NONE;
if (_edit.gizmo.is_valid()) {
@@ -2042,14 +2078,9 @@ void Node3DEditorViewport::_sinput(const Ref<InputEvent> &p_event) {
set_message(n + ": " + String(v));
} else if (m->get_button_mask().has_flag(MouseButtonMask::LEFT)) {
- if (nav_scheme == NAVIGATION_MAYA && m->is_alt_pressed()) {
- nav_mode = NAVIGATION_ORBIT;
- } else if (nav_scheme == NAVIGATION_MODO && m->is_alt_pressed() && m->is_shift_pressed()) {
- nav_mode = NAVIGATION_PAN;
- } else if (nav_scheme == NAVIGATION_MODO && m->is_alt_pressed() && m->is_command_or_control_pressed()) {
- nav_mode = NAVIGATION_ZOOM;
- } else if (nav_scheme == NAVIGATION_MODO && m->is_alt_pressed()) {
- nav_mode = NAVIGATION_ORBIT;
+ NavigationMode change_nav_from_shortcut = _get_nav_mode_from_shortcut_check(NAVIGATION_LEFT_MOUSE, shortcut_check_sets, false);
+ if (change_nav_from_shortcut != NAVIGATION_NONE) {
+ nav_mode = change_nav_from_shortcut;
} else {
const bool movement_threshold_passed = _edit.original_mouse_pos.distance_to(_edit.mouse_pos) > 8 * EDSCALE;
@@ -2080,8 +2111,9 @@ void Node3DEditorViewport::_sinput(const Ref<InputEvent> &p_event) {
update_transform(_get_key_modifier(m) == Key::SHIFT);
}
} else if (m->get_button_mask().has_flag(MouseButtonMask::RIGHT) || freelook_active) {
- if (nav_scheme == NAVIGATION_MAYA && m->is_alt_pressed()) {
- nav_mode = NAVIGATION_ZOOM;
+ NavigationMode change_nav_from_shortcut = _get_nav_mode_from_shortcut_check(NAVIGATION_RIGHT_MOUSE, shortcut_check_sets, false);
+ if (m->get_button_mask().has_flag(MouseButtonMask::RIGHT) && change_nav_from_shortcut != NAVIGATION_NONE) {
+ nav_mode = change_nav_from_shortcut;
} else if (freelook_active) {
nav_mode = NAVIGATION_LOOK;
} else if (orthogonal) {
@@ -2089,34 +2121,16 @@ void Node3DEditorViewport::_sinput(const Ref<InputEvent> &p_event) {
}
} else if (m->get_button_mask().has_flag(MouseButtonMask::MIDDLE)) {
- const Key mod = _get_key_modifier(m);
- if (nav_scheme == NAVIGATION_GODOT) {
- if (mod == _get_key_modifier_setting("editors/3d/navigation/pan_modifier")) {
- nav_mode = NAVIGATION_PAN;
- } else if (mod == _get_key_modifier_setting("editors/3d/navigation/zoom_modifier")) {
- nav_mode = NAVIGATION_ZOOM;
- } else if (mod == Key::ALT || mod == _get_key_modifier_setting("editors/3d/navigation/orbit_modifier")) {
- // Always allow Alt as a modifier to better support graphic tablets.
- nav_mode = NAVIGATION_ORBIT;
- }
- } else if (nav_scheme == NAVIGATION_MAYA) {
- if (mod == _get_key_modifier_setting("editors/3d/navigation/pan_modifier")) {
- nav_mode = NAVIGATION_PAN;
- }
+ NavigationMode change_nav_from_shortcut = _get_nav_mode_from_shortcut_check(NAVIGATION_MIDDLE_MOUSE, shortcut_check_sets, false);
+ if (change_nav_from_shortcut != NAVIGATION_NONE) {
+ nav_mode = change_nav_from_shortcut;
}
+
} else if (EDITOR_GET("editors/3d/navigation/emulate_3_button_mouse")) {
// Handle trackpad (no external mouse) use case
- const Key mod = _get_key_modifier(m);
-
- if (mod != Key::NONE) {
- if (mod == _get_key_modifier_setting("editors/3d/navigation/pan_modifier")) {
- nav_mode = NAVIGATION_PAN;
- } else if (mod == _get_key_modifier_setting("editors/3d/navigation/zoom_modifier")) {
- nav_mode = NAVIGATION_ZOOM;
- } else if (mod == Key::ALT || mod == _get_key_modifier_setting("editors/3d/navigation/orbit_modifier")) {
- // Always allow Alt as a modifier to better support graphic tablets.
- nav_mode = NAVIGATION_ORBIT;
- }
+ NavigationMode change_nav_from_shortcut = _get_nav_mode_from_shortcut_check(NAVIGATION_LEFT_MOUSE, shortcut_check_sets, true);
+ if (change_nav_from_shortcut != NAVIGATION_NONE) {
+ nav_mode = change_nav_from_shortcut;
}
}
@@ -2157,25 +2171,11 @@ void Node3DEditorViewport::_sinput(const Ref<InputEvent> &p_event) {
Ref<InputEventPanGesture> pan_gesture = p_event;
if (pan_gesture.is_valid()) {
- NavigationScheme nav_scheme = (NavigationScheme)EDITOR_GET("editors/3d/navigation/navigation_scheme").operator int();
NavigationMode nav_mode = NAVIGATION_NONE;
- if (nav_scheme == NAVIGATION_GODOT) {
- const Key mod = _get_key_modifier(pan_gesture);
-
- if (mod == _get_key_modifier_setting("editors/3d/navigation/pan_modifier")) {
- nav_mode = NAVIGATION_PAN;
- } else if (mod == _get_key_modifier_setting("editors/3d/navigation/zoom_modifier")) {
- nav_mode = NAVIGATION_ZOOM;
- } else if (mod == Key::ALT || mod == _get_key_modifier_setting("editors/3d/navigation/orbit_modifier")) {
- // Always allow Alt as a modifier to better support graphic tablets.
- nav_mode = NAVIGATION_ORBIT;
- }
-
- } else if (nav_scheme == NAVIGATION_MAYA) {
- if (pan_gesture->is_alt_pressed()) {
- nav_mode = NAVIGATION_PAN;
- }
+ NavigationMode change_nav_from_shortcut = _get_nav_mode_from_shortcut_check(NAVIGATION_LEFT_MOUSE, shortcut_check_sets, true);
+ if (change_nav_from_shortcut != NAVIGATION_NONE) {
+ nav_mode = change_nav_from_shortcut;
}
switch (nav_mode) {
@@ -2399,15 +2399,30 @@ void Node3DEditorViewport::_sinput(const Ref<InputEvent> &p_event) {
if (ED_IS_SHORTCUT("spatial_editor/cancel_transform", p_event) && _edit.mode != TRANSFORM_NONE) {
cancel_transform();
}
- if (!is_freelook_active()) {
- if (ED_IS_SHORTCUT("spatial_editor/instant_translate", p_event)) {
- begin_transform(TRANSFORM_TRANSLATE, true);
+ if (!is_freelook_active() && !k->is_echo()) {
+ if (ED_IS_SHORTCUT("spatial_editor/instant_translate", p_event) && _edit.mode != TRANSFORM_TRANSLATE) {
+ if (_edit.mode == TRANSFORM_NONE) {
+ begin_transform(TRANSFORM_TRANSLATE, true);
+ } else if (_edit.instant) {
+ commit_transform();
+ begin_transform(TRANSFORM_TRANSLATE, true);
+ }
}
- if (ED_IS_SHORTCUT("spatial_editor/instant_rotate", p_event)) {
- begin_transform(TRANSFORM_ROTATE, true);
+ if (ED_IS_SHORTCUT("spatial_editor/instant_rotate", p_event) && _edit.mode != TRANSFORM_ROTATE) {
+ if (_edit.mode == TRANSFORM_NONE) {
+ begin_transform(TRANSFORM_ROTATE, true);
+ } else if (_edit.instant) {
+ commit_transform();
+ begin_transform(TRANSFORM_ROTATE, true);
+ }
}
- if (ED_IS_SHORTCUT("spatial_editor/instant_scale", p_event)) {
- begin_transform(TRANSFORM_SCALE, true);
+ if (ED_IS_SHORTCUT("spatial_editor/instant_scale", p_event) && _edit.mode != TRANSFORM_SCALE) {
+ if (_edit.mode == TRANSFORM_NONE) {
+ begin_transform(TRANSFORM_SCALE, true);
+ } else if (_edit.instant) {
+ commit_transform();
+ begin_transform(TRANSFORM_SCALE, true);
+ }
}
}
@@ -2445,6 +2460,32 @@ void Node3DEditorViewport::_sinput(const Ref<InputEvent> &p_event) {
}
}
+int Node3DEditorViewport::_get_shortcut_input_count(const String &p_name) {
+ Ref<Shortcut> check_shortcut = ED_GET_SHORTCUT(p_name);
+
+ ERR_FAIL_COND_V_MSG(check_shortcut.is_null(), 0, "The Shortcut was null, possible name mismatch.");
+
+ return check_shortcut->get_events().size();
+}
+
+Node3DEditorViewport::NavigationMode Node3DEditorViewport::_get_nav_mode_from_shortcut_check(ViewportNavMouseButton p_mouse_button, Vector<ShortcutCheckSet> p_shortcut_check_sets, bool p_use_not_empty) {
+ if (p_use_not_empty) {
+ for (const ShortcutCheckSet &shortcut_check_set : p_shortcut_check_sets) {
+ if (shortcut_check_set.mod_pressed && shortcut_check_set.shortcut_not_empty) {
+ return shortcut_check_set.result_nav_mode;
+ }
+ }
+ } else {
+ for (const ShortcutCheckSet &shortcut_check_set : p_shortcut_check_sets) {
+ if (shortcut_check_set.mouse_preference == p_mouse_button && shortcut_check_set.mod_pressed) {
+ return shortcut_check_set.result_nav_mode;
+ }
+ }
+ }
+
+ return NAVIGATION_NONE;
+}
+
void Node3DEditorViewport::_nav_pan(Ref<InputEventWithModifiers> p_event, const Vector2 &p_relative) {
const NavigationScheme nav_scheme = (NavigationScheme)EDITOR_GET("editors/3d/navigation/navigation_scheme").operator int();
@@ -2644,6 +2685,18 @@ void Node3DEditorViewport::scale_freelook_speed(real_t scale) {
surface->queue_redraw();
}
+bool Node3DEditorViewport::_is_nav_modifier_pressed(const String &p_name) {
+ return _is_shortcut_empty(p_name) || Input::get_singleton()->is_action_pressed(p_name);
+}
+
+bool Node3DEditorViewport::_is_shortcut_empty(const String &p_name) {
+ Ref<Shortcut> check_shortcut = ED_GET_SHORTCUT(p_name);
+
+ ERR_FAIL_COND_V_MSG(check_shortcut.is_null(), true, "The Shortcut was null, possible name mismatch.");
+
+ return check_shortcut->get_events().is_empty();
+}
+
Point2 Node3DEditorViewport::_get_warped_mouse_motion(const Ref<InputEventMouseMotion> &p_ev_mouse_motion) const {
Point2 relative;
if (bool(EDITOR_GET("editors/3d/navigation/warped_mouse_panning"))) {
@@ -3039,8 +3092,11 @@ void Node3DEditorViewport::_notification(int p_what) {
update_preview_node = false;
} break;
+ case NOTIFICATION_APPLICATION_FOCUS_OUT:
case NOTIFICATION_WM_WINDOW_FOCUS_OUT: {
set_freelook_active(false);
+ cursor.region_select = false;
+ surface->queue_redraw();
} break;
case NOTIFICATION_ENTER_TREE: {
@@ -3539,6 +3595,15 @@ void Node3DEditorViewport::_menu_option(int p_option) {
view_menu->get_popup()->set_item_checked(idx, current);
} break;
+ case VIEW_TRANSFORM_GIZMO: {
+ int idx = view_menu->get_popup()->get_item_index(VIEW_TRANSFORM_GIZMO);
+ bool current = view_menu->get_popup()->is_item_checked(idx);
+ current = !current;
+ transform_gizmo_visible = current;
+
+ spatial_editor->update_transform_gizmo();
+ view_menu->get_popup()->set_item_checked(idx, current);
+ } break;
case VIEW_HALF_RESOLUTION: {
int idx = view_menu->get_popup()->get_item_index(VIEW_HALF_RESOLUTION);
bool current = view_menu->get_popup()->is_item_checked(idx);
@@ -3682,10 +3747,10 @@ void Node3DEditorViewport::_set_auto_orthogonal() {
}
void Node3DEditorViewport::_preview_exited_scene() {
- preview_camera->disconnect("toggled", callable_mp(this, &Node3DEditorViewport::_toggle_camera_preview));
+ preview_camera->disconnect(SceneStringName(toggled), callable_mp(this, &Node3DEditorViewport::_toggle_camera_preview));
preview_camera->set_pressed(false);
_toggle_camera_preview(false);
- preview_camera->connect("toggled", callable_mp(this, &Node3DEditorViewport::_toggle_camera_preview));
+ preview_camera->connect(SceneStringName(toggled), callable_mp(this, &Node3DEditorViewport::_toggle_camera_preview));
view_menu->show();
}
@@ -3903,7 +3968,7 @@ void Node3DEditorViewport::update_transform_gizmo_view() {
return;
}
- bool show_gizmo = spatial_editor->is_gizmo_visible() && !_edit.instant;
+ bool show_gizmo = spatial_editor->is_gizmo_visible() && !_edit.instant && transform_gizmo_visible;
for (int i = 0; i < 3; i++) {
Transform3D axis_angle;
if (xform.basis.get_column(i).normalized().dot(xform.basis.get_column((i + 1) % 3).normalized()) < 1.0) {
@@ -3934,7 +3999,7 @@ void Node3DEditorViewport::update_transform_gizmo_view() {
xform.orthonormalize();
xform.basis.scale(scale);
RenderingServer::get_singleton()->instance_set_transform(rotate_gizmo_instance[3], xform);
- RenderingServer::get_singleton()->instance_set_visible(rotate_gizmo_instance[3], spatial_editor->is_gizmo_visible() && (spatial_editor->get_tool_mode() == Node3DEditor::TOOL_MODE_SELECT || spatial_editor->get_tool_mode() == Node3DEditor::TOOL_MODE_ROTATE));
+ RenderingServer::get_singleton()->instance_set_visible(rotate_gizmo_instance[3], spatial_editor->is_gizmo_visible() && transform_gizmo_visible && (spatial_editor->get_tool_mode() == Node3DEditor::TOOL_MODE_SELECT || spatial_editor->get_tool_mode() == Node3DEditor::TOOL_MODE_ROTATE));
}
void Node3DEditorViewport::set_state(const Dictionary &p_state) {
@@ -4011,6 +4076,14 @@ void Node3DEditorViewport::set_state(const Dictionary &p_state) {
_menu_option(VIEW_GIZMOS);
}
}
+ if (p_state.has("transform_gizmo")) {
+ bool transform_gizmo = p_state["transform_gizmo"];
+
+ int idx = view_menu->get_popup()->get_item_index(VIEW_TRANSFORM_GIZMO);
+ if (view_menu->get_popup()->is_item_checked(idx) != transform_gizmo) {
+ _menu_option(VIEW_TRANSFORM_GIZMO);
+ }
+ }
if (p_state.has("grid")) {
bool grid = p_state["grid"];
@@ -4049,8 +4122,8 @@ void Node3DEditorViewport::set_state(const Dictionary &p_state) {
view_menu->get_popup()->set_item_checked(idx, previewing_cinema);
}
- if (preview_camera->is_connected("toggled", callable_mp(this, &Node3DEditorViewport::_toggle_camera_preview))) {
- preview_camera->disconnect("toggled", callable_mp(this, &Node3DEditorViewport::_toggle_camera_preview));
+ if (preview_camera->is_connected(SceneStringName(toggled), callable_mp(this, &Node3DEditorViewport::_toggle_camera_preview))) {
+ preview_camera->disconnect(SceneStringName(toggled), callable_mp(this, &Node3DEditorViewport::_toggle_camera_preview));
}
if (p_state.has("previewing")) {
Node *pv = EditorNode::get_singleton()->get_edited_scene()->get_node(p_state["previewing"]);
@@ -4063,7 +4136,7 @@ void Node3DEditorViewport::set_state(const Dictionary &p_state) {
preview_camera->show();
}
}
- preview_camera->connect("toggled", callable_mp(this, &Node3DEditorViewport::_toggle_camera_preview));
+ preview_camera->connect(SceneStringName(toggled), callable_mp(this, &Node3DEditorViewport::_toggle_camera_preview));
}
Dictionary Node3DEditorViewport::get_state() const {
@@ -4097,6 +4170,7 @@ Dictionary Node3DEditorViewport::get_state() const {
d["listener"] = viewport->is_audio_listener_3d();
d["doppler"] = view_menu->get_popup()->is_item_checked(view_menu->get_popup()->get_item_index(VIEW_AUDIO_DOPPLER));
d["gizmos"] = view_menu->get_popup()->is_item_checked(view_menu->get_popup()->get_item_index(VIEW_GIZMOS));
+ d["transform_gizmo"] = view_menu->get_popup()->is_item_checked(view_menu->get_popup()->get_item_index(VIEW_TRANSFORM_GIZMO));
d["grid"] = view_menu->get_popup()->is_item_checked(view_menu->get_popup()->get_item_index(VIEW_GRID));
d["information"] = view_menu->get_popup()->is_item_checked(view_menu->get_popup()->get_item_index(VIEW_INFORMATION));
d["frame_time"] = view_menu->get_popup()->is_item_checked(view_menu->get_popup()->get_item_index(VIEW_FRAME_TIME));
@@ -4450,8 +4524,8 @@ bool Node3DEditorViewport::_create_instance(Node *p_parent, const String &p_path
Node *instantiated_scene = nullptr;
- if (mesh != nullptr || scene != nullptr) {
- if (mesh != nullptr) {
+ if (mesh.is_valid() || scene.is_valid()) {
+ if (mesh.is_valid()) {
MeshInstance3D *mesh_instance = memnew(MeshInstance3D);
mesh_instance->set_mesh(mesh);
@@ -4482,7 +4556,7 @@ bool Node3DEditorViewport::_create_instance(Node *p_parent, const String &p_path
}
}
- if (scene != nullptr) {
+ if (scene.is_valid()) {
instantiated_scene->set_scene_file_path(ProjectSettings::get_singleton()->localize_path(p_path));
}
@@ -5330,6 +5404,7 @@ Node3DEditorViewport::Node3DEditorViewport(Node3DEditor *p_spatial_editor, int p
view_menu->get_popup()->add_separator();
view_menu->get_popup()->add_check_shortcut(ED_SHORTCUT("spatial_editor/view_environment", TTR("View Environment")), VIEW_ENVIRONMENT);
view_menu->get_popup()->add_check_shortcut(ED_SHORTCUT("spatial_editor/view_gizmos", TTR("View Gizmos")), VIEW_GIZMOS);
+ view_menu->get_popup()->add_check_shortcut(ED_SHORTCUT("spatial_editor/view_transform_gizmo", TTR("View Transform Gizmo")), VIEW_TRANSFORM_GIZMO);
view_menu->get_popup()->add_check_shortcut(ED_SHORTCUT("spatial_editor/view_grid_lines", TTR("View Grid")), VIEW_GRID);
view_menu->get_popup()->add_check_shortcut(ED_SHORTCUT("spatial_editor/view_information", TTR("View Information")), VIEW_INFORMATION);
view_menu->get_popup()->add_check_shortcut(ED_SHORTCUT("spatial_editor/view_fps", TTR("View Frame Time")), VIEW_FRAME_TIME);
@@ -5340,6 +5415,7 @@ Node3DEditorViewport::Node3DEditorViewport(Node3DEditor *p_spatial_editor, int p
view_menu->get_popup()->add_check_shortcut(ED_SHORTCUT("spatial_editor/view_audio_listener", TTR("Audio Listener")), VIEW_AUDIO_LISTENER);
view_menu->get_popup()->add_check_shortcut(ED_SHORTCUT("spatial_editor/view_audio_doppler", TTR("Enable Doppler")), VIEW_AUDIO_DOPPLER);
view_menu->get_popup()->set_item_checked(view_menu->get_popup()->get_item_index(VIEW_GIZMOS), true);
+ view_menu->get_popup()->set_item_checked(view_menu->get_popup()->get_item_index(VIEW_TRANSFORM_GIZMO), true);
view_menu->get_popup()->set_item_checked(view_menu->get_popup()->get_item_index(VIEW_GRID), true);
view_menu->get_popup()->add_separator();
@@ -5374,6 +5450,14 @@ Node3DEditorViewport::Node3DEditorViewport(Node3DEditor *p_spatial_editor, int p
view_menu->get_popup()->set_item_tooltip(shadeless_idx, unsupported_tooltip);
}
+ // Registering with Key::NONE intentionally creates an empty Array.
+ register_shortcut_action("spatial_editor/viewport_orbit_modifier_1", TTR("Viewport Orbit Modifier 1"), Key::NONE);
+ register_shortcut_action("spatial_editor/viewport_orbit_modifier_2", TTR("Viewport Orbit Modifier 2"), Key::NONE);
+ register_shortcut_action("spatial_editor/viewport_pan_modifier_1", TTR("Viewport Pan Modifier 1"), Key::SHIFT);
+ register_shortcut_action("spatial_editor/viewport_pan_modifier_2", TTR("Viewport Pan Modifier 2"), Key::NONE);
+ register_shortcut_action("spatial_editor/viewport_zoom_modifier_1", TTR("Viewport Zoom Modifier 1"), Key::SHIFT);
+ register_shortcut_action("spatial_editor/viewport_zoom_modifier_2", TTR("Viewport Zoom Modifier 2"), Key::CTRL);
+
register_shortcut_action("spatial_editor/freelook_left", TTR("Freelook Left"), Key::A, true);
register_shortcut_action("spatial_editor/freelook_right", TTR("Freelook Right"), Key::D, true);
register_shortcut_action("spatial_editor/freelook_forward", TTR("Freelook Forward"), Key::W, true);
@@ -5396,11 +5480,12 @@ Node3DEditorViewport::Node3DEditorViewport(Node3DEditor *p_spatial_editor, int p
preview_camera = memnew(CheckBox);
preview_camera->set_text(TTR("Preview"));
- preview_camera->set_shortcut(ED_SHORTCUT("spatial_editor/toggle_camera_preview", TTR("Toggle Camera Preview"), KeyModifierMask::CMD_OR_CTRL | Key::P));
+ // Using Control even on macOS to avoid conflict with Quick Open shortcut.
+ preview_camera->set_shortcut(ED_SHORTCUT("spatial_editor/toggle_camera_preview", TTR("Toggle Camera Preview"), KeyModifierMask::CTRL | Key::P));
vbox->add_child(preview_camera);
preview_camera->set_h_size_flags(0);
preview_camera->hide();
- preview_camera->connect("toggled", callable_mp(this, &Node3DEditorViewport::_toggle_camera_preview));
+ preview_camera->connect(SceneStringName(toggled), callable_mp(this, &Node3DEditorViewport::_toggle_camera_preview));
previewing = nullptr;
gizmo_scale = 1.0;
@@ -7780,6 +7865,9 @@ void Node3DEditor::_sun_environ_settings_pressed() {
sun_environ_popup->set_position(pos - Vector2(sun_environ_popup->get_contents_minimum_size().width / 2, 0));
sun_environ_popup->reset_size();
sun_environ_popup->popup();
+ // Grabbing the focus is required for Shift modifier checking to be functional
+ // (when the Add sun/environment buttons are pressed).
+ sun_environ_popup->grab_focus();
}
void Node3DEditor::_add_sun_to_scene(bool p_already_added_environment) {
@@ -8615,7 +8703,7 @@ Node3DEditor::Node3DEditor() {
main_menu_hbox->add_child(tool_option_button[TOOL_OPT_LOCAL_COORDS]);
tool_option_button[TOOL_OPT_LOCAL_COORDS]->set_toggle_mode(true);
tool_option_button[TOOL_OPT_LOCAL_COORDS]->set_theme_type_variation("FlatButton");
- tool_option_button[TOOL_OPT_LOCAL_COORDS]->connect("toggled", callable_mp(this, &Node3DEditor::_menu_item_toggled).bind(MENU_TOOL_LOCAL_COORDS));
+ tool_option_button[TOOL_OPT_LOCAL_COORDS]->connect(SceneStringName(toggled), callable_mp(this, &Node3DEditor::_menu_item_toggled).bind(MENU_TOOL_LOCAL_COORDS));
tool_option_button[TOOL_OPT_LOCAL_COORDS]->set_shortcut(ED_SHORTCUT("spatial_editor/local_coords", TTR("Use Local Space"), Key::T));
tool_option_button[TOOL_OPT_LOCAL_COORDS]->set_shortcut_context(this);
@@ -8623,7 +8711,7 @@ Node3DEditor::Node3DEditor() {
main_menu_hbox->add_child(tool_option_button[TOOL_OPT_USE_SNAP]);
tool_option_button[TOOL_OPT_USE_SNAP]->set_toggle_mode(true);
tool_option_button[TOOL_OPT_USE_SNAP]->set_theme_type_variation("FlatButton");
- tool_option_button[TOOL_OPT_USE_SNAP]->connect("toggled", callable_mp(this, &Node3DEditor::_menu_item_toggled).bind(MENU_TOOL_USE_SNAP));
+ tool_option_button[TOOL_OPT_USE_SNAP]->connect(SceneStringName(toggled), callable_mp(this, &Node3DEditor::_menu_item_toggled).bind(MENU_TOOL_USE_SNAP));
tool_option_button[TOOL_OPT_USE_SNAP]->set_shortcut(ED_SHORTCUT("spatial_editor/snap", TTR("Use Snap"), Key::Y));
tool_option_button[TOOL_OPT_USE_SNAP]->set_shortcut_context(this);
@@ -8634,7 +8722,7 @@ Node3DEditor::Node3DEditor() {
tool_option_button[TOOL_OPT_OVERRIDE_CAMERA]->set_toggle_mode(true);
tool_option_button[TOOL_OPT_OVERRIDE_CAMERA]->set_theme_type_variation("FlatButton");
tool_option_button[TOOL_OPT_OVERRIDE_CAMERA]->set_disabled(true);
- tool_option_button[TOOL_OPT_OVERRIDE_CAMERA]->connect("toggled", callable_mp(this, &Node3DEditor::_menu_item_toggled).bind(MENU_TOOL_OVERRIDE_CAMERA));
+ tool_option_button[TOOL_OPT_OVERRIDE_CAMERA]->connect(SceneStringName(toggled), callable_mp(this, &Node3DEditor::_menu_item_toggled).bind(MENU_TOOL_OVERRIDE_CAMERA));
_update_camera_override_button(false);
main_menu_hbox->add_child(memnew(VSeparator));
@@ -9225,7 +9313,7 @@ struct _GizmoPluginNameComparator {
};
void Node3DEditor::add_gizmo_plugin(Ref<EditorNode3DGizmoPlugin> p_plugin) {
- ERR_FAIL_NULL(p_plugin.ptr());
+ ERR_FAIL_COND(p_plugin.is_null());
gizmo_plugins_by_priority.push_back(p_plugin);
gizmo_plugins_by_priority.sort_custom<_GizmoPluginPriorityComparator>();
diff --git a/editor/plugins/node_3d_editor_plugin.h b/editor/plugins/node_3d_editor_plugin.h
index 9e7d46c5e8..2cfe784ca6 100644
--- a/editor/plugins/node_3d_editor_plugin.h
+++ b/editor/plugins/node_3d_editor_plugin.h
@@ -125,6 +125,7 @@ class Node3DEditorViewport : public Control {
VIEW_AUDIO_LISTENER,
VIEW_AUDIO_DOPPLER,
VIEW_GIZMOS,
+ VIEW_TRANSFORM_GIZMO,
VIEW_GRID,
VIEW_INFORMATION,
VIEW_FRAME_TIME,
@@ -192,6 +193,7 @@ public:
NAVIGATION_GODOT,
NAVIGATION_MAYA,
NAVIGATION_MODO,
+ NAVIGATION_CUSTOM,
};
enum FreelookNavigationScheme {
@@ -200,6 +202,12 @@ public:
FREELOOK_FULLY_AXIS_LOCKED,
};
+ enum ViewportNavMouseButton {
+ NAVIGATION_LEFT_MOUSE,
+ NAVIGATION_MIDDLE_MOUSE,
+ NAVIGATION_RIGHT_MOUSE,
+ };
+
private:
double cpu_time_history[FRAME_TIME_HISTORY];
int cpu_time_history_index;
@@ -236,6 +244,7 @@ private:
bool orthogonal;
bool auto_orthogonal;
bool lock_rotation;
+ bool transform_gizmo_visible = true;
real_t gizmo_scale;
bool freelook_active;
@@ -294,6 +303,10 @@ private:
void _nav_orbit(Ref<InputEventWithModifiers> p_event, const Vector2 &p_relative);
void _nav_look(Ref<InputEventWithModifiers> p_event, const Vector2 &p_relative);
+ bool _is_shortcut_empty(const String &p_name);
+ bool _is_nav_modifier_pressed(const String &p_name);
+ int _get_shortcut_input_count(const String &p_name);
+
float get_znear() const;
float get_zfar() const;
float get_fov() const;
@@ -390,6 +403,28 @@ private:
void reset_fov();
void scale_cursor_distance(real_t scale);
+ struct ShortcutCheckSet {
+ bool mod_pressed = false;
+ bool shortcut_not_empty = true;
+ int input_count = 0;
+ ViewportNavMouseButton mouse_preference = NAVIGATION_LEFT_MOUSE;
+ NavigationMode result_nav_mode = NAVIGATION_NONE;
+
+ ShortcutCheckSet() {}
+
+ ShortcutCheckSet(bool p_mod_pressed, bool p_shortcut_not_empty, int p_input_count, const ViewportNavMouseButton &p_mouse_preference, const NavigationMode &p_result_nav_mode) :
+ mod_pressed(p_mod_pressed), shortcut_not_empty(p_shortcut_not_empty), input_count(p_input_count), mouse_preference(p_mouse_preference), result_nav_mode(p_result_nav_mode) {
+ }
+ };
+
+ struct ShortcutCheckSetComparator {
+ _FORCE_INLINE_ bool operator()(const ShortcutCheckSet &A, const ShortcutCheckSet &B) const {
+ return A.input_count > B.input_count;
+ }
+ };
+
+ NavigationMode _get_nav_mode_from_shortcut_check(ViewportNavMouseButton p_mouse_button, Vector<ShortcutCheckSet> p_shortcut_check_sets, bool p_use_not_empty);
+
void set_freelook_active(bool active_now);
void scale_freelook_speed(real_t scale);
diff --git a/editor/plugins/particle_process_material_editor_plugin.cpp b/editor/plugins/particle_process_material_editor_plugin.cpp
index 79c9c69584..67c9403aaf 100644
--- a/editor/plugins/particle_process_material_editor_plugin.cpp
+++ b/editor/plugins/particle_process_material_editor_plugin.cpp
@@ -438,7 +438,7 @@ ParticleProcessMaterialMinMaxPropertyEditor::ParticleProcessMaterialMinMaxProper
toggle_mode_button->set_toggle_mode(true);
toggle_mode_button->set_tooltip_text(TTR("Toggle between minimum/maximum and base value/spread modes."));
hb->add_child(toggle_mode_button);
- toggle_mode_button->connect(SNAME("toggled"), callable_mp(this, &ParticleProcessMaterialMinMaxPropertyEditor::_toggle_mode));
+ toggle_mode_button->connect(SceneStringName(toggled), callable_mp(this, &ParticleProcessMaterialMinMaxPropertyEditor::_toggle_mode));
set_bottom_editor(content_vb);
}
diff --git a/editor/plugins/path_2d_editor_plugin.cpp b/editor/plugins/path_2d_editor_plugin.cpp
index 5b23cf44d0..0455b266b4 100644
--- a/editor/plugins/path_2d_editor_plugin.cpp
+++ b/editor/plugins/path_2d_editor_plugin.cpp
@@ -42,7 +42,6 @@
void Path2DEditor::_notification(int p_what) {
switch (p_what) {
- case NOTIFICATION_ENTER_TREE:
case NOTIFICATION_THEME_CHANGED: {
curve_edit->set_icon(get_editor_theme_icon(SNAME("CurveEdit")));
curve_edit_curve->set_icon(get_editor_theme_icon(SNAME("CurveCurve")));
@@ -50,6 +49,8 @@ void Path2DEditor::_notification(int p_what) {
curve_del->set_icon(get_editor_theme_icon(SNAME("CurveDelete")));
curve_close->set_icon(get_editor_theme_icon(SNAME("CurveClose")));
curve_clear_points->set_icon(get_editor_theme_icon(SNAME("Clear")));
+
+ create_curve_button->set_icon(get_editor_theme_icon(SNAME("Curve2D")));
} break;
}
}
@@ -197,12 +198,7 @@ bool Path2DEditor::forward_gui_input(const Ref<InputEvent> &p_event) {
const Vector2 new_point = xform.affine_inverse().xform(gpoint2);
curve->add_point(new_point, Vector2(0, 0), Vector2(0, 0), insertion_point + 1);
- EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
- undo_redo->create_action(TTR("Split Curve"));
- undo_redo->add_do_method(curve.ptr(), "add_point", new_point, Vector2(0, 0), Vector2(0, 0), insertion_point + 1);
- undo_redo->add_undo_method(curve.ptr(), "remove_point", insertion_point + 1);
-
- action = ACTION_MOVING_NEW_POINT;
+ action = ACTION_MOVING_NEW_POINT_FROM_SPLIT;
action_point = insertion_point + 1;
moving_from = curve->get_point_position(action_point);
moving_screen_from = gpoint2;
@@ -245,6 +241,16 @@ bool Path2DEditor::forward_gui_input(const Ref<InputEvent> &p_event) {
undo_redo->commit_action(false);
} break;
+ case ACTION_MOVING_NEW_POINT_FROM_SPLIT: {
+ undo_redo->create_action(TTR("Split Curve"));
+ undo_redo->add_do_method(curve.ptr(), "add_point", Vector2(), Vector2(), Vector2(), action_point);
+ undo_redo->add_do_method(curve.ptr(), "set_point_position", action_point, cpoint);
+ undo_redo->add_undo_method(curve.ptr(), "remove_point", action_point);
+ undo_redo->add_do_method(canvas_item_editor, "update_viewport");
+ undo_redo->add_undo_method(canvas_item_editor, "update_viewport");
+ undo_redo->commit_action(false);
+ } break;
+
case ACTION_MOVING_IN: {
if (original_mouse_pos != gpoint) {
undo_redo->create_action(TTR("Move In-Control in Curve"));
@@ -295,7 +301,7 @@ bool Path2DEditor::forward_gui_input(const Ref<InputEvent> &p_event) {
Vector2 gpoint = mm->get_position();
Ref<Curve2D> curve = node->get_curve();
- if (curve == nullptr) {
+ if (curve.is_null()) {
return true;
}
if (curve->get_point_count() < 2) {
@@ -350,7 +356,8 @@ bool Path2DEditor::forward_gui_input(const Ref<InputEvent> &p_event) {
break;
case ACTION_MOVING_POINT:
- case ACTION_MOVING_NEW_POINT: {
+ case ACTION_MOVING_NEW_POINT:
+ case ACTION_MOVING_NEW_POINT_FROM_SPLIT: {
curve->set_point_position(action_point, cpoint);
} break;
@@ -444,6 +451,16 @@ void Path2DEditor::_node_visibility_changed() {
}
canvas_item_editor->update_viewport();
+ _update_toolbar();
+}
+
+void Path2DEditor::_update_toolbar() {
+ if (!node) {
+ return;
+ }
+ bool has_curve = node->get_curve().is_valid();
+ toolbar->set_visible(has_curve);
+ create_curve_button->set_visible(!has_curve);
}
void Path2DEditor::edit(Node *p_path2d) {
@@ -457,6 +474,7 @@ void Path2DEditor::edit(Node *p_path2d) {
if (p_path2d) {
node = Object::cast_to<Path2D>(p_path2d);
+ _update_toolbar();
if (!node->is_connected(SceneStringName(visibility_changed), callable_mp(this, &Path2DEditor::_node_visibility_changed))) {
node->connect(SceneStringName(visibility_changed), callable_mp(this, &Path2DEditor::_node_visibility_changed));
@@ -471,7 +489,7 @@ void Path2DEditor::edit(Node *p_path2d) {
}
void Path2DEditor::_bind_methods() {
- //ClassDB::bind_method(D_METHOD("_menu_option"),&Path2DEditor::_menu_option);
+ ClassDB::bind_method(D_METHOD("_update_toolbar"), &Path2DEditor::_update_toolbar);
ClassDB::bind_method(D_METHOD("_clear_curve_points"), &Path2DEditor::_clear_curve_points);
ClassDB::bind_method(D_METHOD("_restore_curve_points"), &Path2DEditor::_restore_curve_points);
}
@@ -573,6 +591,10 @@ void Path2DEditor::_cancel_current_action() {
curve->remove_point(curve->get_point_count() - 1);
} break;
+ case ACTION_MOVING_NEW_POINT_FROM_SPLIT: {
+ curve->remove_point(action_point);
+ } break;
+
case ACTION_MOVING_IN: {
curve->set_point_in(action_point, moving_from);
curve->set_point_out(action_point, mirror_handle_length ? -moving_from : (-moving_from.normalized() * orig_out_length));
@@ -591,6 +613,21 @@ void Path2DEditor::_cancel_current_action() {
action = ACTION_NONE;
}
+void Path2DEditor::_create_curve() {
+ ERR_FAIL_NULL(node);
+
+ Ref<Curve2D> new_curve;
+ new_curve.instantiate();
+
+ EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
+ undo_redo->create_action(TTR("Create Curve in Path2D"));
+ undo_redo->add_do_property(node, "curve", new_curve);
+ undo_redo->add_undo_property(node, "curve", Ref<Curve2D>());
+ undo_redo->add_do_method(this, "_update_toolbar");
+ undo_redo->add_undo_method(this, "_update_toolbar");
+ undo_redo->commit_action();
+}
+
void Path2DEditor::_confirm_clear_points() {
if (!node || node->get_curve().is_null()) {
return;
@@ -638,6 +675,8 @@ void Path2DEditor::_restore_curve_points(Path2D *p_path2d, const PackedVector2Ar
}
Path2DEditor::Path2DEditor() {
+ toolbar = memnew(HBoxContainer);
+
curve_edit = memnew(Button);
curve_edit->set_theme_type_variation("FlatButton");
curve_edit->set_toggle_mode(true);
@@ -645,7 +684,7 @@ Path2DEditor::Path2DEditor() {
curve_edit->set_focus_mode(Control::FOCUS_NONE);
curve_edit->set_tooltip_text(TTR("Select Points") + "\n" + TTR("Shift+Drag: Select Control Points") + "\n" + keycode_get_string((Key)KeyModifierMask::CMD_OR_CTRL) + TTR("Click: Add Point") + "\n" + TTR("Left Click: Split Segment (in curve)") + "\n" + TTR("Right Click: Delete Point"));
curve_edit->connect(SceneStringName(pressed), callable_mp(this, &Path2DEditor::_mode_selected).bind(MODE_EDIT));
- add_child(curve_edit);
+ toolbar->add_child(curve_edit);
curve_edit_curve = memnew(Button);
curve_edit_curve->set_theme_type_variation("FlatButton");
@@ -653,7 +692,7 @@ Path2DEditor::Path2DEditor() {
curve_edit_curve->set_focus_mode(Control::FOCUS_NONE);
curve_edit_curve->set_tooltip_text(TTR("Select Control Points (Shift+Drag)"));
curve_edit_curve->connect(SceneStringName(pressed), callable_mp(this, &Path2DEditor::_mode_selected).bind(MODE_EDIT_CURVE));
- add_child(curve_edit_curve);
+ toolbar->add_child(curve_edit_curve);
curve_create = memnew(Button);
curve_create->set_theme_type_variation("FlatButton");
@@ -661,7 +700,7 @@ Path2DEditor::Path2DEditor() {
curve_create->set_focus_mode(Control::FOCUS_NONE);
curve_create->set_tooltip_text(TTR("Add Point (in empty space)") + "\n" + TTR("Right Click: Delete Point"));
curve_create->connect(SceneStringName(pressed), callable_mp(this, &Path2DEditor::_mode_selected).bind(MODE_CREATE));
- add_child(curve_create);
+ toolbar->add_child(curve_create);
curve_del = memnew(Button);
curve_del->set_theme_type_variation("FlatButton");
@@ -669,42 +708,48 @@ Path2DEditor::Path2DEditor() {
curve_del->set_focus_mode(Control::FOCUS_NONE);
curve_del->set_tooltip_text(TTR("Delete Point"));
curve_del->connect(SceneStringName(pressed), callable_mp(this, &Path2DEditor::_mode_selected).bind(MODE_DELETE));
- add_child(curve_del);
+ toolbar->add_child(curve_del);
curve_close = memnew(Button);
curve_close->set_theme_type_variation("FlatButton");
curve_close->set_focus_mode(Control::FOCUS_NONE);
curve_close->set_tooltip_text(TTR("Close Curve"));
curve_close->connect(SceneStringName(pressed), callable_mp(this, &Path2DEditor::_mode_selected).bind(MODE_CLOSE));
- add_child(curve_close);
+ toolbar->add_child(curve_close);
curve_clear_points = memnew(Button);
curve_clear_points->set_theme_type_variation("FlatButton");
curve_clear_points->set_focus_mode(Control::FOCUS_NONE);
curve_clear_points->set_tooltip_text(TTR("Clear Points"));
curve_clear_points->connect(SceneStringName(pressed), callable_mp(this, &Path2DEditor::_confirm_clear_points));
- add_child(curve_clear_points);
+ toolbar->add_child(curve_clear_points);
clear_points_dialog = memnew(ConfirmationDialog);
clear_points_dialog->set_title(TTR("Please Confirm..."));
clear_points_dialog->set_text(TTR("Remove all curve points?"));
clear_points_dialog->connect(SceneStringName(confirmed), callable_mp(this, &Path2DEditor::_mode_selected).bind(MODE_CLEAR_POINTS));
- add_child(clear_points_dialog);
-
- PopupMenu *menu;
+ toolbar->add_child(clear_points_dialog);
handle_menu = memnew(MenuButton);
handle_menu->set_flat(false);
handle_menu->set_theme_type_variation("FlatMenuButton");
handle_menu->set_text(TTR("Options"));
- add_child(handle_menu);
+ toolbar->add_child(handle_menu);
- menu = handle_menu->get_popup();
+ PopupMenu *menu = handle_menu->get_popup();
menu->add_check_item(TTR("Mirror Handle Angles"));
menu->set_item_checked(HANDLE_OPTION_ANGLE, mirror_handle_angle);
menu->add_check_item(TTR("Mirror Handle Lengths"));
menu->set_item_checked(HANDLE_OPTION_LENGTH, mirror_handle_length);
menu->connect(SceneStringName(id_pressed), callable_mp(this, &Path2DEditor::_handle_option_pressed));
+
+ add_child(toolbar);
+
+ create_curve_button = memnew(Button);
+ create_curve_button->set_text(TTR("Create Curve"));
+ create_curve_button->hide();
+ add_child(create_curve_button);
+ create_curve_button->connect(SceneStringName(pressed), callable_mp(this, &Path2DEditor::_create_curve));
}
void Path2DEditorPlugin::edit(Object *p_object) {
diff --git a/editor/plugins/path_2d_editor_plugin.h b/editor/plugins/path_2d_editor_plugin.h
index a2857fddb7..f2f03f12f1 100644
--- a/editor/plugins/path_2d_editor_plugin.h
+++ b/editor/plugins/path_2d_editor_plugin.h
@@ -58,6 +58,7 @@ class Path2DEditor : public HBoxContainer {
};
Mode mode = MODE_EDIT;
+ HBoxContainer *toolbar = nullptr;
Button *curve_clear_points = nullptr;
Button *curve_close = nullptr;
Button *curve_create = nullptr;
@@ -66,6 +67,7 @@ class Path2DEditor : public HBoxContainer {
Button *curve_edit_curve = nullptr;
MenuButton *handle_menu = nullptr;
+ Button *create_curve_button = nullptr;
ConfirmationDialog *clear_points_dialog = nullptr;
bool mirror_handle_angle = true;
@@ -81,6 +83,7 @@ class Path2DEditor : public HBoxContainer {
ACTION_NONE,
ACTION_MOVING_POINT,
ACTION_MOVING_NEW_POINT,
+ ACTION_MOVING_NEW_POINT_FROM_SPLIT,
ACTION_MOVING_IN,
ACTION_MOVING_OUT,
};
@@ -99,7 +102,9 @@ class Path2DEditor : public HBoxContainer {
void _cancel_current_action();
void _node_visibility_changed();
+ void _update_toolbar();
+ void _create_curve();
void _confirm_clear_points();
void _clear_curve_points(Path2D *p_path2d);
void _restore_curve_points(Path2D *p_path2d, const PackedVector2Array &p_points);
diff --git a/editor/plugins/path_3d_editor_plugin.cpp b/editor/plugins/path_3d_editor_plugin.cpp
index 240206e124..7ba464f4e5 100644
--- a/editor/plugins/path_3d_editor_plugin.cpp
+++ b/editor/plugins/path_3d_editor_plugin.cpp
@@ -652,6 +652,7 @@ void Path3DEditorPlugin::edit(Object *p_object) {
if (path->get_curve().is_valid()) {
path->get_curve()->emit_signal(CoreStringName(changed));
}
+ _update_toolbar();
}
} else {
Path3D *pre = path;
@@ -732,6 +733,21 @@ void Path3DEditorPlugin::_handle_option_pressed(int p_option) {
}
}
+void Path3DEditorPlugin::_create_curve() {
+ ERR_FAIL_NULL(path);
+
+ Ref<Curve3D> new_curve;
+ new_curve.instantiate();
+
+ EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
+ undo_redo->create_action(TTR("Create Curve in Path3D"));
+ undo_redo->add_do_property(path, "curve", new_curve);
+ undo_redo->add_undo_property(path, "curve", Ref<Curve3D>());
+ undo_redo->add_do_method(this, "_update_toolbar");
+ undo_redo->add_undo_method(this, "_update_toolbar");
+ undo_redo->commit_action();
+}
+
void Path3DEditorPlugin::_confirm_clear_points() {
if (!path || path->get_curve().is_null() || path->get_curve()->get_point_count() == 0) {
return;
@@ -783,6 +799,17 @@ void Path3DEditorPlugin::_update_theme() {
curve_del->set_icon(EditorNode::get_singleton()->get_editor_theme()->get_icon(SNAME("CurveDelete"), EditorStringName(EditorIcons)));
curve_close->set_icon(EditorNode::get_singleton()->get_editor_theme()->get_icon(SNAME("CurveClose"), EditorStringName(EditorIcons)));
curve_clear_points->set_icon(EditorNode::get_singleton()->get_editor_theme()->get_icon(SNAME("Clear"), EditorStringName(EditorIcons)));
+
+ create_curve_button->set_icon(EditorNode::get_singleton()->get_editor_theme()->get_icon(SNAME("Curve3D"), EditorStringName(EditorIcons)));
+}
+
+void Path3DEditorPlugin::_update_toolbar() {
+ if (!path) {
+ return;
+ }
+ bool has_curve = path->get_curve().is_valid();
+ toolbar->set_visible(has_curve);
+ create_curve_button->set_visible(!has_curve);
}
void Path3DEditorPlugin::_notification(int p_what) {
@@ -808,6 +835,7 @@ void Path3DEditorPlugin::_notification(int p_what) {
}
void Path3DEditorPlugin::_bind_methods() {
+ ClassDB::bind_method(D_METHOD("_update_toolbar"), &Path3DEditorPlugin::_update_toolbar);
ClassDB::bind_method(D_METHOD("_clear_curve_points"), &Path3DEditorPlugin::_clear_curve_points);
ClassDB::bind_method(D_METHOD("_restore_curve_points"), &Path3DEditorPlugin::_restore_curve_points);
}
@@ -830,65 +858,74 @@ Path3DEditorPlugin::Path3DEditorPlugin() {
topmenu_bar->hide();
Node3DEditor::get_singleton()->add_control_to_menu_panel(topmenu_bar);
+ toolbar = memnew(HBoxContainer);
+ topmenu_bar->add_child(toolbar);
+
curve_edit = memnew(Button);
curve_edit->set_theme_type_variation("FlatButton");
curve_edit->set_toggle_mode(true);
curve_edit->set_focus_mode(Control::FOCUS_NONE);
curve_edit->set_tooltip_text(TTR("Select Points") + "\n" + TTR("Shift+Click: Select multiple Points") + "\n" + keycode_get_string((Key)KeyModifierMask::CMD_OR_CTRL) + TTR("Click: Add Point") + "\n" + TTR("Right Click: Delete Point"));
- topmenu_bar->add_child(curve_edit);
+ toolbar->add_child(curve_edit);
curve_edit_curve = memnew(Button);
curve_edit_curve->set_theme_type_variation("FlatButton");
curve_edit_curve->set_toggle_mode(true);
curve_edit_curve->set_focus_mode(Control::FOCUS_NONE);
curve_edit_curve->set_tooltip_text(TTR("Select Control Points") + "\n" + TTR("Shift+Click: Drag out Control Points"));
- topmenu_bar->add_child(curve_edit_curve);
+ toolbar->add_child(curve_edit_curve);
curve_edit_tilt = memnew(Button);
curve_edit_tilt->set_theme_type_variation("FlatButton");
curve_edit_tilt->set_toggle_mode(true);
curve_edit_tilt->set_focus_mode(Control::FOCUS_NONE);
curve_edit_tilt->set_tooltip_text(TTR("Select Tilt Handles"));
- topmenu_bar->add_child(curve_edit_tilt);
+ toolbar->add_child(curve_edit_tilt);
curve_create = memnew(Button);
curve_create->set_theme_type_variation("FlatButton");
curve_create->set_toggle_mode(true);
curve_create->set_focus_mode(Control::FOCUS_NONE);
curve_create->set_tooltip_text(TTR("Add Point (in empty space)") + "\n" + TTR("Split Segment (in curve)"));
- topmenu_bar->add_child(curve_create);
+ toolbar->add_child(curve_create);
curve_del = memnew(Button);
curve_del->set_theme_type_variation("FlatButton");
curve_del->set_toggle_mode(true);
curve_del->set_focus_mode(Control::FOCUS_NONE);
curve_del->set_tooltip_text(TTR("Delete Point"));
- topmenu_bar->add_child(curve_del);
+ toolbar->add_child(curve_del);
curve_close = memnew(Button);
curve_close->set_theme_type_variation("FlatButton");
curve_close->set_focus_mode(Control::FOCUS_NONE);
curve_close->set_tooltip_text(TTR("Close Curve"));
- topmenu_bar->add_child(curve_close);
+ toolbar->add_child(curve_close);
curve_clear_points = memnew(Button);
curve_clear_points->set_theme_type_variation("FlatButton");
curve_clear_points->set_focus_mode(Control::FOCUS_NONE);
curve_clear_points->set_tooltip_text(TTR("Clear Points"));
curve_clear_points->connect(SceneStringName(pressed), callable_mp(this, &Path3DEditorPlugin::_confirm_clear_points));
- topmenu_bar->add_child(curve_clear_points);
+ toolbar->add_child(curve_clear_points);
clear_points_dialog = memnew(ConfirmationDialog);
clear_points_dialog->set_title(TTR("Please Confirm..."));
clear_points_dialog->set_text(TTR("Remove all curve points?"));
clear_points_dialog->connect(SceneStringName(confirmed), callable_mp(this, &Path3DEditorPlugin::_clear_points));
- topmenu_bar->add_child(clear_points_dialog);
+ toolbar->add_child(clear_points_dialog);
handle_menu = memnew(MenuButton);
handle_menu->set_flat(false);
handle_menu->set_theme_type_variation("FlatMenuButton");
handle_menu->set_text(TTR("Options"));
- topmenu_bar->add_child(handle_menu);
+ toolbar->add_child(handle_menu);
+
+ create_curve_button = memnew(Button);
+ create_curve_button->set_text(TTR("Create Curve"));
+ create_curve_button->hide();
+ topmenu_bar->add_child(create_curve_button);
+ create_curve_button->connect(SceneStringName(pressed), callable_mp(this, &Path3DEditorPlugin::_create_curve));
PopupMenu *menu;
menu = handle_menu->get_popup();
diff --git a/editor/plugins/path_3d_editor_plugin.h b/editor/plugins/path_3d_editor_plugin.h
index ee73df1617..09a7a46803 100644
--- a/editor/plugins/path_3d_editor_plugin.h
+++ b/editor/plugins/path_3d_editor_plugin.h
@@ -113,6 +113,8 @@ class Path3DEditorPlugin : public EditorPlugin {
Ref<Path3DGizmoPlugin> path_3d_gizmo_plugin;
HBoxContainer *topmenu_bar = nullptr;
+
+ HBoxContainer *toolbar = nullptr;
Button *curve_create = nullptr;
Button *curve_edit = nullptr;
Button *curve_edit_curve = nullptr;
@@ -122,6 +124,7 @@ class Path3DEditorPlugin : public EditorPlugin {
Button *curve_clear_points = nullptr;
MenuButton *handle_menu = nullptr;
+ Button *create_curve_button = nullptr;
ConfirmationDialog *clear_points_dialog = nullptr;
float disk_size = 0.8;
@@ -138,14 +141,16 @@ class Path3DEditorPlugin : public EditorPlugin {
Path3D *path = nullptr;
void _update_theme();
+ void _update_toolbar();
void _mode_changed(int p_mode);
void _close_curve();
void _handle_option_pressed(int p_option);
bool handle_clicked = false;
- bool mirror_handle_angle;
- bool mirror_handle_length;
+ bool mirror_handle_angle = true;
+ bool mirror_handle_length = true;
+ void _create_curve();
void _confirm_clear_points();
void _clear_points();
void _clear_curve_points();
diff --git a/editor/plugins/physical_bone_3d_editor_plugin.cpp b/editor/plugins/physical_bone_3d_editor_plugin.cpp
index d7e9701452..c858fa8606 100644
--- a/editor/plugins/physical_bone_3d_editor_plugin.cpp
+++ b/editor/plugins/physical_bone_3d_editor_plugin.cpp
@@ -62,7 +62,7 @@ PhysicalBone3DEditor::PhysicalBone3DEditor() {
// when the editor theme updates.
button_transform_joint->set_icon(EditorNode::get_singleton()->get_editor_theme()->get_icon(SNAME("PhysicalBone3D"), EditorStringName(EditorIcons)));
button_transform_joint->set_toggle_mode(true);
- button_transform_joint->connect("toggled", callable_mp(this, &PhysicalBone3DEditor::_on_toggle_button_transform_joint));
+ button_transform_joint->connect(SceneStringName(toggled), callable_mp(this, &PhysicalBone3DEditor::_on_toggle_button_transform_joint));
hide();
}
diff --git a/editor/plugins/polygon_2d_editor_plugin.cpp b/editor/plugins/polygon_2d_editor_plugin.cpp
index 7c350f1e6d..b8a309bc60 100644
--- a/editor/plugins/polygon_2d_editor_plugin.cpp
+++ b/editor/plugins/polygon_2d_editor_plugin.cpp
@@ -1468,7 +1468,7 @@ Polygon2DEditor::Polygon2DEditor() {
b_snap_enable->set_toggle_mode(true);
b_snap_enable->set_pressed(use_snap);
b_snap_enable->set_tooltip_text(TTR("Enable Snap"));
- b_snap_enable->connect("toggled", callable_mp(this, &Polygon2DEditor::_set_use_snap));
+ b_snap_enable->connect(SceneStringName(toggled), callable_mp(this, &Polygon2DEditor::_set_use_snap));
b_snap_grid = memnew(Button);
b_snap_grid->set_theme_type_variation("FlatButton");
@@ -1478,11 +1478,11 @@ Polygon2DEditor::Polygon2DEditor() {
b_snap_grid->set_toggle_mode(true);
b_snap_grid->set_pressed(snap_show_grid);
b_snap_grid->set_tooltip_text(TTR("Show Grid"));
- b_snap_grid->connect("toggled", callable_mp(this, &Polygon2DEditor::_set_show_grid));
+ b_snap_grid->connect(SceneStringName(toggled), callable_mp(this, &Polygon2DEditor::_set_show_grid));
grid_settings = memnew(AcceptDialog);
grid_settings->set_title(TTR("Configure Grid:"));
- add_child(grid_settings);
+ uv_edit->add_child(grid_settings);
VBoxContainer *grid_settings_vb = memnew(VBoxContainer);
grid_settings->add_child(grid_settings_vb);
diff --git a/editor/plugins/script_editor_plugin.cpp b/editor/plugins/script_editor_plugin.cpp
index b27e768999..93c8ae5438 100644
--- a/editor/plugins/script_editor_plugin.cpp
+++ b/editor/plugins/script_editor_plugin.cpp
@@ -57,6 +57,7 @@
#include "editor/gui/editor_toaster.h"
#include "editor/inspector_dock.h"
#include "editor/node_dock.h"
+#include "editor/plugins/editor_context_menu_plugin.h"
#include "editor/plugins/shader_editor_plugin.h"
#include "editor/plugins/text_shader_editor.h"
#include "editor/themes/editor_scale.h"
@@ -510,7 +511,7 @@ void ScriptEditor::_set_execution(Ref<RefCounted> p_script, int p_line) {
continue;
}
- if ((scr != nullptr && se->get_edited_resource() == p_script) || se->get_edited_resource()->get_path() == scr->get_path()) {
+ if ((scr.is_valid() && se->get_edited_resource() == p_script) || se->get_edited_resource()->get_path() == scr->get_path()) {
se->set_executing_line(p_line);
}
}
@@ -526,7 +527,7 @@ void ScriptEditor::_clear_execution(Ref<RefCounted> p_script) {
continue;
}
- if ((scr != nullptr && se->get_edited_resource() == p_script) || se->get_edited_resource()->get_path() == scr->get_path()) {
+ if ((scr.is_valid() && se->get_edited_resource() == p_script) || se->get_edited_resource()->get_path() == scr->get_path()) {
se->clear_executing_line();
}
}
@@ -578,8 +579,8 @@ void ScriptEditor::_clear_breakpoints() {
script_editor_cache->get_sections(&cached_editors);
for (const String &E : cached_editors) {
Array breakpoints = _get_cached_breakpoints_for_script(E);
- for (int i = 0; i < breakpoints.size(); i++) {
- EditorDebuggerNode::get_singleton()->set_breakpoint(E, (int)breakpoints[i] + 1, false);
+ for (int breakpoint : breakpoints) {
+ EditorDebuggerNode::get_singleton()->set_breakpoint(E, (int)breakpoint + 1, false);
}
if (breakpoints.size() > 0) {
@@ -711,7 +712,7 @@ void ScriptEditor::_go_to_tab(int p_idx) {
}
Ref<Script> scr = Object::cast_to<ScriptEditorBase>(c)->get_edited_resource();
- if (scr != nullptr) {
+ if (scr.is_valid()) {
notify_script_changed(scr);
}
@@ -1017,7 +1018,7 @@ void ScriptEditor::_resave_scripts(const String &p_str) {
}
Ref<TextFile> text_file = scr;
- if (text_file != nullptr) {
+ if (text_file.is_valid()) {
se->apply_code();
_save_text_file(text_file, text_file->get_path());
break;
@@ -1079,8 +1080,12 @@ void ScriptEditor::_mark_built_in_scripts_as_saved(const String &p_parent_path)
}
Ref<Script> scr = edited_res;
- if (scr.is_valid() && scr->is_tool()) {
- scr->reload(true);
+ if (scr.is_valid()) {
+ trigger_live_script_reload(scr->get_path());
+
+ if (scr->is_tool()) {
+ scr->reload(true);
+ }
}
}
}
@@ -1224,7 +1229,7 @@ Ref<Script> ScriptEditor::_get_current_script() {
if (current) {
Ref<Script> scr = current->get_edited_resource();
- return scr != nullptr ? scr : nullptr;
+ return scr.is_valid() ? scr : nullptr;
} else {
return nullptr;
}
@@ -1394,6 +1399,16 @@ void ScriptEditor::_menu_option(int p_option) {
}
}
+ // Context menu options.
+ if (p_option >= EditorData::CONTEXT_MENU_ITEM_ID_BASE) {
+ Ref<Resource> resource;
+ if (current) {
+ resource = current->get_edited_resource();
+ }
+ EditorNode::get_editor_data().script_editor_options_pressed(EditorData::CONTEXT_SLOT_SCRIPT_EDITOR, p_option, resource);
+ return;
+ }
+
if (current) {
switch (p_option) {
case FILE_SAVE: {
@@ -1416,7 +1431,7 @@ void ScriptEditor::_menu_option(int p_option) {
Ref<TextFile> text_file = resource;
Ref<Script> scr = resource;
- if (text_file != nullptr) {
+ if (text_file.is_valid()) {
file_dialog->set_file_mode(EditorFileDialog::FILE_MODE_SAVE_FILE);
file_dialog->set_access(EditorFileDialog::ACCESS_FILESYSTEM);
file_dialog_option = FILE_SAVE_AS;
@@ -1445,7 +1460,7 @@ void ScriptEditor::_menu_option(int p_option) {
case FILE_TOOL_RELOAD_SOFT: {
Ref<Script> scr = current->get_edited_resource();
- if (scr == nullptr || scr.is_null()) {
+ if (scr.is_null()) {
EditorNode::get_singleton()->show_warning(TTR("Can't obtain the script for reloading."));
break;
}
@@ -1459,7 +1474,7 @@ void ScriptEditor::_menu_option(int p_option) {
case FILE_RUN: {
Ref<Script> scr = current->get_edited_resource();
- if (scr == nullptr || scr.is_null()) {
+ if (scr.is_null()) {
EditorToaster::get_singleton()->popup_str(TTR("Cannot run the edited file because it's not a script."), EditorToaster::SEVERITY_WARNING);
break;
}
@@ -1792,7 +1807,7 @@ void ScriptEditor::_close_builtin_scripts_from_scene(const String &p_scene) {
if (se) {
Ref<Script> scr = se->get_edited_resource();
- if (scr == nullptr || !scr.is_valid()) {
+ if (scr.is_null()) {
continue;
}
@@ -1816,6 +1831,48 @@ void ScriptEditor::notify_script_changed(const Ref<Script> &p_script) {
emit_signal(SNAME("editor_script_changed"), p_script);
}
+Vector<String> ScriptEditor::_get_breakpoints() {
+ Vector<String> ret;
+ HashSet<String> loaded_scripts;
+ for (int i = 0; i < tab_container->get_tab_count(); i++) {
+ ScriptEditorBase *se = Object::cast_to<ScriptEditorBase>(tab_container->get_tab_control(i));
+ if (!se) {
+ continue;
+ }
+
+ Ref<Script> scr = se->get_edited_resource();
+ if (scr.is_null()) {
+ continue;
+ }
+
+ String base = scr->get_path();
+ loaded_scripts.insert(base);
+ if (base.is_empty() || base.begins_with("local://")) {
+ continue;
+ }
+
+ PackedInt32Array bpoints = se->get_breakpoints();
+ for (int32_t bpoint : bpoints) {
+ ret.push_back(base + ":" + itos((int)bpoint + 1));
+ }
+ }
+
+ // Load breakpoints that are in closed scripts.
+ List<String> cached_editors;
+ script_editor_cache->get_sections(&cached_editors);
+ for (const String &E : cached_editors) {
+ if (loaded_scripts.has(E)) {
+ continue;
+ }
+
+ Array breakpoints = _get_cached_breakpoints_for_script(E);
+ for (int breakpoint : breakpoints) {
+ ret.push_back(E + ":" + itos((int)breakpoint + 1));
+ }
+ }
+ return ret;
+}
+
void ScriptEditor::get_breakpoints(List<String> *p_breakpoints) {
HashSet<String> loaded_scripts;
for (int i = 0; i < tab_container->get_tab_count(); i++) {
@@ -1825,19 +1882,19 @@ void ScriptEditor::get_breakpoints(List<String> *p_breakpoints) {
}
Ref<Script> scr = se->get_edited_resource();
- if (scr == nullptr) {
+ if (scr.is_null()) {
continue;
}
String base = scr->get_path();
loaded_scripts.insert(base);
- if (base.begins_with("local://") || base.is_empty()) {
+ if (base.is_empty() || base.begins_with("local://")) {
continue;
}
PackedInt32Array bpoints = se->get_breakpoints();
- for (int j = 0; j < bpoints.size(); j++) {
- p_breakpoints->push_back(base + ":" + itos((int)bpoints[j] + 1));
+ for (int32_t bpoint : bpoints) {
+ p_breakpoints->push_back(base + ":" + itos((int)bpoint + 1));
}
}
@@ -1850,8 +1907,8 @@ void ScriptEditor::get_breakpoints(List<String> *p_breakpoints) {
}
Array breakpoints = _get_cached_breakpoints_for_script(E);
- for (int i = 0; i < breakpoints.size(); i++) {
- p_breakpoints->push_back(E + ":" + itos((int)breakpoints[i] + 1));
+ for (int breakpoint : breakpoints) {
+ p_breakpoints->push_back(E + ":" + itos((int)breakpoint + 1));
}
}
}
@@ -2454,7 +2511,7 @@ bool ScriptEditor::edit(const Ref<Resource> &p_resource, int p_line, int p_col,
continue;
}
- if ((scr != nullptr && se->get_edited_resource() == p_resource) || se->get_edited_resource()->get_path() == p_resource->get_path()) {
+ if ((scr.is_valid() && se->get_edited_resource() == p_resource) || se->get_edited_resource()->get_path() == p_resource->get_path()) {
if (should_open) {
se->enable_editor(this);
@@ -2504,7 +2561,7 @@ bool ScriptEditor::edit(const Ref<Resource> &p_resource, int p_line, int p_col,
PackedStringArray languages = highlighter->_get_supported_languages();
// If script try language, else use extension.
- if (scr != nullptr) {
+ if (scr.is_valid()) {
if (languages.has(scr->get_language()->get_name())) {
se->set_syntax_highlighter(highlighter);
highlighter_set = true;
@@ -2614,7 +2671,7 @@ void ScriptEditor::save_current_script() {
Ref<TextFile> text_file = resource;
Ref<Script> scr = resource;
- if (text_file != nullptr) {
+ if (text_file.is_valid()) {
current->apply_code();
_save_text_file(text_file, text_file->get_path());
return;
@@ -2665,7 +2722,7 @@ void ScriptEditor::save_all_scripts() {
Ref<TextFile> text_file = edited_res;
Ref<Script> scr = edited_res;
- if (text_file != nullptr) {
+ if (text_file.is_valid()) {
_save_text_file(text_file, text_file->get_path());
continue;
}
@@ -2742,7 +2799,7 @@ void ScriptEditor::_reload_scripts(bool p_refresh_only) {
}
Ref<JSON> json = edited_res;
- if (json != nullptr) {
+ if (json.is_valid()) {
Ref<JSON> rel_json = ResourceLoader::load(json->get_path(), json->get_class(), ResourceFormatLoader::CACHE_MODE_IGNORE);
ERR_CONTINUE(!rel_json.is_valid());
json->parse(rel_json->get_parsed_text(), true);
@@ -2940,8 +2997,8 @@ void ScriptEditor::_files_moved(const String &p_old_file, const String &p_new_fi
// If Script, update breakpoints with debugger.
Array breakpoints = _get_cached_breakpoints_for_script(p_new_file);
- for (int i = 0; i < breakpoints.size(); i++) {
- int line = (int)breakpoints[i] + 1;
+ for (int breakpoint : breakpoints) {
+ int line = (int)breakpoint + 1;
EditorDebuggerNode::get_singleton()->set_breakpoint(p_old_file, line, false);
if (!p_new_file.begins_with("local://") && ResourceLoader::exists(p_new_file, "Script")) {
EditorDebuggerNode::get_singleton()->set_breakpoint(p_new_file, line, true);
@@ -2965,8 +3022,8 @@ void ScriptEditor::_file_removed(const String &p_removed_file) {
// Check closed.
if (script_editor_cache->has_section(p_removed_file)) {
Array breakpoints = _get_cached_breakpoints_for_script(p_removed_file);
- for (int i = 0; i < breakpoints.size(); i++) {
- EditorDebuggerNode::get_singleton()->set_breakpoint(p_removed_file, (int)breakpoints[i] + 1, false);
+ for (int breakpoint : breakpoints) {
+ EditorDebuggerNode::get_singleton()->set_breakpoint(p_removed_file, (int)breakpoint + 1, false);
}
script_editor_cache->erase_section(p_removed_file);
}
@@ -3258,6 +3315,11 @@ void ScriptEditor::shortcut_input(const Ref<InputEvent> &p_event) {
_menu_option(WINDOW_MOVE_DOWN);
accept_event();
}
+ // Context menu shortcuts.
+ int match_option = EditorNode::get_editor_data().match_context_menu_shortcut(EditorData::CONTEXT_SLOT_SCRIPT_EDITOR, p_event);
+ if (match_option) {
+ _menu_option(match_option);
+ }
}
void ScriptEditor::_script_list_clicked(int p_item, Vector2 p_local_mouse_pos, MouseButton p_mouse_button_index) {
@@ -3292,7 +3354,7 @@ void ScriptEditor::_make_script_list_context_menu() {
context_menu->add_separator();
if (se) {
Ref<Script> scr = se->get_edited_resource();
- if (scr != nullptr) {
+ if (scr.is_valid()) {
if (!scr.is_null() && scr->is_tool()) {
context_menu->add_shortcut(ED_GET_SHORTCUT("script_editor/reload_script_soft"), FILE_TOOL_RELOAD_SOFT);
context_menu->add_shortcut(ED_GET_SHORTCUT("script_editor/run_file"), FILE_RUN);
@@ -3316,6 +3378,17 @@ void ScriptEditor::_make_script_list_context_menu() {
context_menu->set_item_disabled(context_menu->get_item_index(WINDOW_MOVE_DOWN), tab_container->get_current_tab() >= tab_container->get_tab_count() - 1);
context_menu->set_item_disabled(context_menu->get_item_index(WINDOW_SORT), tab_container->get_tab_count() <= 1);
+ // Context menu plugin.
+ Vector<String> selected_paths;
+ if (se) {
+ Ref<Resource> scr = se->get_edited_resource();
+ if (scr.is_valid()) {
+ String path = scr->get_path();
+ selected_paths.push_back(path);
+ }
+ }
+ EditorNode::get_editor_data().add_options_from_plugins(context_menu, EditorData::CONTEXT_SLOT_SCRIPT_EDITOR, selected_paths);
+
context_menu->set_position(get_screen_position() + get_local_mouse_position());
context_menu->reset_size();
context_menu->popup();
@@ -3421,8 +3494,8 @@ void ScriptEditor::set_window_layout(Ref<ConfigFile> p_layout) {
}
Array breakpoints = _get_cached_breakpoints_for_script(E);
- for (int i = 0; i < breakpoints.size(); i++) {
- EditorDebuggerNode::get_singleton()->set_breakpoint(E, (int)breakpoints[i] + 1, true);
+ for (int breakpoint : breakpoints) {
+ EditorDebuggerNode::get_singleton()->set_breakpoint(E, (int)breakpoint + 1, true);
}
}
@@ -3639,7 +3712,7 @@ void ScriptEditor::_update_history_pos(int p_new_pos) {
seb->ensure_focus();
Ref<Script> scr = seb->get_edited_resource();
- if (scr != nullptr) {
+ if (scr.is_valid()) {
notify_script_changed(scr);
}
}
@@ -3678,7 +3751,7 @@ Vector<Ref<Script>> ScriptEditor::get_open_scripts() const {
}
Ref<Script> scr = se->get_edited_resource();
- if (scr != nullptr) {
+ if (scr.is_valid()) {
out_scripts.push_back(scr);
}
}
@@ -3968,6 +4041,7 @@ void ScriptEditor::_bind_methods() {
ClassDB::bind_method("_help_tab_goto", &ScriptEditor::_help_tab_goto);
ClassDB::bind_method("get_current_editor", &ScriptEditor::_get_current_editor);
ClassDB::bind_method("get_open_script_editors", &ScriptEditor::_get_open_script_editors);
+ ClassDB::bind_method("get_breakpoints", &ScriptEditor::_get_breakpoints);
ClassDB::bind_method(D_METHOD("register_syntax_highlighter", "syntax_highlighter"), &ScriptEditor::register_syntax_highlighter);
ClassDB::bind_method(D_METHOD("unregister_syntax_highlighter", "syntax_highlighter"), &ScriptEditor::unregister_syntax_highlighter);
@@ -4059,7 +4133,7 @@ ScriptEditor::ScriptEditor(WindowWrapper *p_wrapper) {
members_overview_alphabeta_sort_button->set_tooltip_text(TTR("Toggle alphabetical sorting of the method list."));
members_overview_alphabeta_sort_button->set_toggle_mode(true);
members_overview_alphabeta_sort_button->set_pressed(EDITOR_GET("text_editor/script_list/sort_members_outline_alphabetically"));
- members_overview_alphabeta_sort_button->connect("toggled", callable_mp(this, &ScriptEditor::_toggle_members_overview_alpha_sort));
+ members_overview_alphabeta_sort_button->connect(SceneStringName(toggled), callable_mp(this, &ScriptEditor::_toggle_members_overview_alpha_sort));
buttons_hbox->add_child(members_overview_alphabeta_sort_button);
@@ -4235,7 +4309,7 @@ ScriptEditor::ScriptEditor(WindowWrapper *p_wrapper) {
make_floating->connect("request_open_in_screen", callable_mp(window_wrapper, &WindowWrapper::enable_window_on_screen).bind(true));
if (!make_floating->is_disabled()) {
// Override default ScreenSelect tooltip if multi-window support is available.
- make_floating->set_tooltip_text(TTR("Make the script editor floating."));
+ make_floating->set_tooltip_text(TTR("Make the script editor floating.\nRight-click to open the screen selector."));
}
menu_hb->add_child(make_floating);
diff --git a/editor/plugins/script_editor_plugin.h b/editor/plugins/script_editor_plugin.h
index 5dbf5d6f38..e0fac5d0c6 100644
--- a/editor/plugins/script_editor_plugin.h
+++ b/editor/plugins/script_editor_plugin.h
@@ -55,7 +55,7 @@ class EditorSyntaxHighlighter : public SyntaxHighlighter {
GDCLASS(EditorSyntaxHighlighter, SyntaxHighlighter)
private:
- Ref<RefCounted> edited_resourse;
+ Ref<RefCounted> edited_resource;
protected:
static void _bind_methods();
@@ -67,8 +67,8 @@ public:
virtual String _get_name() const;
virtual PackedStringArray _get_supported_languages() const;
- void _set_edited_resource(const Ref<Resource> &p_res) { edited_resourse = p_res; }
- Ref<RefCounted> _get_edited_resource() { return edited_resourse; }
+ void _set_edited_resource(const Ref<Resource> &p_res) { edited_resource = p_res; }
+ Ref<RefCounted> _get_edited_resource() { return edited_resource; }
virtual Ref<EditorSyntaxHighlighter> _create() const;
};
@@ -539,6 +539,7 @@ public:
_FORCE_INLINE_ bool edit(const Ref<Resource> &p_resource, bool p_grab_focus = true) { return edit(p_resource, -1, 0, p_grab_focus); }
bool edit(const Ref<Resource> &p_resource, int p_line, int p_col, bool p_grab_focus = true);
+ Vector<String> _get_breakpoints();
void get_breakpoints(List<String> *p_breakpoints);
PackedStringArray get_unsaved_scripts() const;
diff --git a/editor/plugins/script_text_editor.cpp b/editor/plugins/script_text_editor.cpp
index eb39a27d02..7e8fba8b9e 100644
--- a/editor/plugins/script_text_editor.cpp
+++ b/editor/plugins/script_text_editor.cpp
@@ -1843,7 +1843,8 @@ void ScriptTextEditor::drop_data_fw(const Point2 &p_point, const Variant &p_data
const String &line = te->get_line(drop_at_line);
const bool is_empty_line = line_will_be_empty || line.is_empty() || te->get_first_non_whitespace_column(drop_at_line) == line.length();
- if (d.has("type") && String(d["type"]) == "resource") {
+ const String type = d.get("type", "");
+ if (type == "resource") {
Ref<Resource> resource = d["resource"];
if (resource.is_null()) {
return;
@@ -1868,11 +1869,11 @@ void ScriptTextEditor::drop_data_fw(const Point2 &p_point, const Variant &p_data
}
}
- if (d.has("type") && (String(d["type"]) == "files" || String(d["type"]) == "files_and_dirs")) {
- Array files = d["files"];
- for (int i = 0; i < files.size(); i++) {
- const String &path = String(files[i]);
+ if (type == "files" || type == "files_and_dirs") {
+ const PackedStringArray files = d["files"];
+ PackedStringArray parts;
+ for (const String &path : files) {
if (drop_modifier_pressed && ResourceLoader::exists(path)) {
Ref<Resource> resource = ResourceLoader::load(path);
if (resource.is_null()) {
@@ -1880,18 +1881,15 @@ void ScriptTextEditor::drop_data_fw(const Point2 &p_point, const Variant &p_data
resource.instantiate();
resource->set_path_cache(path);
}
- text_to_drop += _get_dropped_resource_line(resource, is_empty_line);
+ parts.append(_get_dropped_resource_line(resource, is_empty_line));
} else {
- text_to_drop += _quote_drop_data(path);
- }
-
- if (i < files.size() - 1) {
- text_to_drop += is_empty_line ? "\n" : ", ";
+ parts.append(_quote_drop_data(path));
}
}
+ text_to_drop = String(is_empty_line ? "\n" : ", ").join(parts);
}
- if (d.has("type") && String(d["type"]) == "nodes") {
+ if (type == "nodes") {
Node *scene_root = get_tree()->get_edited_scene_root();
if (!scene_root) {
EditorNode::get_singleton()->show_warning(TTR("Can't drop nodes without an open scene."));
@@ -1982,7 +1980,7 @@ void ScriptTextEditor::drop_data_fw(const Point2 &p_point, const Variant &p_data
}
}
- if (d.has("type") && String(d["type"]) == "obj_property") {
+ if (type == "obj_property") {
bool add_literal = EDITOR_GET("text_editor/completion/add_node_path_literals");
text_to_drop = add_literal ? "^" : "";
// It is unclear whether properties may contain single or double quotes.
@@ -2373,6 +2371,7 @@ void ScriptTextEditor::_enable_code_editor() {
ScriptTextEditor::ScriptTextEditor() {
code_editor = memnew(CodeTextEditor);
+ code_editor->set_toggle_list_control(ScriptEditor::get_singleton()->get_left_list_split());
code_editor->add_theme_constant_override("separation", 2);
code_editor->set_anchors_and_offsets_preset(Control::PRESET_FULL_RECT);
code_editor->set_code_complete_func(_code_complete_scripts, this);
@@ -2542,8 +2541,9 @@ void ScriptTextEditor::register_editor() {
ED_SHORTCUT_OVERRIDE("script_text_editor/toggle_breakpoint", "macos", KeyModifierMask::META | KeyModifierMask::SHIFT | Key::B);
ED_SHORTCUT("script_text_editor/remove_all_breakpoints", TTR("Remove All Breakpoints"), KeyModifierMask::CMD_OR_CTRL | KeyModifierMask::SHIFT | Key::F9);
- ED_SHORTCUT("script_text_editor/goto_next_breakpoint", TTR("Go to Next Breakpoint"), KeyModifierMask::CMD_OR_CTRL | Key::PERIOD);
- ED_SHORTCUT("script_text_editor/goto_previous_breakpoint", TTR("Go to Previous Breakpoint"), KeyModifierMask::CMD_OR_CTRL | Key::COMMA);
+ // Using Control for these shortcuts even on macOS because Command+Comma is taken for opening Editor Settings.
+ ED_SHORTCUT("script_text_editor/goto_next_breakpoint", TTR("Go to Next Breakpoint"), KeyModifierMask::CTRL | Key::PERIOD);
+ ED_SHORTCUT("script_text_editor/goto_previous_breakpoint", TTR("Go to Previous Breakpoint"), KeyModifierMask::CTRL | Key::COMMA);
ScriptEditor::register_create_script_editor_function(create_editor);
}
diff --git a/editor/plugins/shader_editor_plugin.cpp b/editor/plugins/shader_editor_plugin.cpp
index 04a392768a..597ef49004 100644
--- a/editor/plugins/shader_editor_plugin.cpp
+++ b/editor/plugins/shader_editor_plugin.cpp
@@ -149,7 +149,9 @@ void ShaderEditorPlugin::edit(Object *p_object) {
}
}
es.shader_inc = Ref<ShaderInclude>(si);
- es.shader_editor = memnew(TextShaderEditor);
+ TextShaderEditor *text_shader = memnew(TextShaderEditor);
+ text_shader->get_code_editor()->set_toggle_list_control(left_panel);
+ es.shader_editor = text_shader;
es.shader_editor->edit_shader_include(si);
shader_tabs->add_child(es.shader_editor);
} else {
@@ -166,7 +168,9 @@ void ShaderEditorPlugin::edit(Object *p_object) {
if (vs.is_valid()) {
es.shader_editor = memnew(VisualShaderEditor);
} else {
- es.shader_editor = memnew(TextShaderEditor);
+ TextShaderEditor *text_shader = memnew(TextShaderEditor);
+ text_shader->get_code_editor()->set_toggle_list_control(left_panel);
+ es.shader_editor = text_shader;
}
shader_tabs->add_child(es.shader_editor);
es.shader_editor->edit_shader(es.shader);
@@ -434,6 +438,10 @@ void ShaderEditorPlugin::_close_shader(int p_index) {
edited_shaders.remove_at(p_index);
_update_shader_list();
EditorUndoRedoManager::get_singleton()->clear_history(); // To prevent undo on deleted graphs.
+
+ if (shader_tabs->get_tab_count() == 0) {
+ left_panel->show(); // Make sure the panel is visible, because it can't be toggled without open shaders.
+ }
}
void ShaderEditorPlugin::_close_builtin_shaders_from_scene(const String &p_scene) {
@@ -768,10 +776,10 @@ ShaderEditorPlugin::ShaderEditorPlugin() {
Ref<Shortcut> make_floating_shortcut = ED_SHORTCUT_AND_COMMAND("shader_editor/make_floating", TTR("Make Floating"));
window_wrapper->set_wrapped_control(main_split, make_floating_shortcut);
- VBoxContainer *vb = memnew(VBoxContainer);
+ left_panel = memnew(VBoxContainer);
HBoxContainer *menu_hb = memnew(HBoxContainer);
- vb->add_child(menu_hb);
+ left_panel->add_child(menu_hb);
file_menu = memnew(MenuButton);
file_menu->set_text(TTR("File"));
file_menu->set_shortcut_context(main_split);
@@ -794,7 +802,7 @@ ShaderEditorPlugin::ShaderEditorPlugin() {
make_floating->connect("request_open_in_screen", callable_mp(window_wrapper, &WindowWrapper::enable_window_on_screen).bind(true));
if (!make_floating->is_disabled()) {
// Override default ScreenSelect tooltip if multi-window support is available.
- make_floating->set_tooltip_text(TTR("Make the shader editor floating."));
+ make_floating->set_tooltip_text(TTR("Make the shader editor floating.\nRight-click to open the screen selector."));
}
menu_hb->add_child(make_floating);
@@ -803,14 +811,14 @@ ShaderEditorPlugin::ShaderEditorPlugin() {
shader_list = memnew(ItemList);
shader_list->set_auto_translate_mode(AUTO_TRANSLATE_MODE_DISABLED);
shader_list->set_v_size_flags(Control::SIZE_EXPAND_FILL);
- vb->add_child(shader_list);
+ left_panel->add_child(shader_list);
shader_list->connect(SceneStringName(item_selected), callable_mp(this, &ShaderEditorPlugin::_shader_selected));
shader_list->connect("item_clicked", callable_mp(this, &ShaderEditorPlugin::_shader_list_clicked));
shader_list->set_allow_rmb_select(true);
SET_DRAG_FORWARDING_GCD(shader_list, ShaderEditorPlugin);
- main_split->add_child(vb);
- vb->set_custom_minimum_size(Size2(200, 300) * EDSCALE);
+ main_split->add_child(left_panel);
+ left_panel->set_custom_minimum_size(Size2(200, 300) * EDSCALE);
shader_tabs = memnew(TabContainer);
shader_tabs->set_tabs_visible(false);
@@ -823,7 +831,7 @@ ShaderEditorPlugin::ShaderEditorPlugin() {
button = EditorNode::get_bottom_panel()->add_item(TTR("Shader Editor"), window_wrapper, ED_SHORTCUT_AND_COMMAND("bottom_panels/toggle_shader_editor_bottom_panel", TTR("Toggle Shader Editor Bottom Panel"), KeyModifierMask::ALT | Key::S));
shader_create_dialog = memnew(ShaderCreateDialog);
- vb->add_child(shader_create_dialog);
+ main_split->add_child(shader_create_dialog);
shader_create_dialog->connect("shader_created", callable_mp(this, &ShaderEditorPlugin::_shader_created));
shader_create_dialog->connect("shader_include_created", callable_mp(this, &ShaderEditorPlugin::_shader_include_created));
}
diff --git a/editor/plugins/shader_editor_plugin.h b/editor/plugins/shader_editor_plugin.h
index 31be64a56c..43e6af79fa 100644
--- a/editor/plugins/shader_editor_plugin.h
+++ b/editor/plugins/shader_editor_plugin.h
@@ -40,6 +40,7 @@ class ShaderCreateDialog;
class ShaderEditor;
class TabContainer;
class TextShaderEditor;
+class VBoxContainer;
class VisualShaderEditor;
class WindowWrapper;
@@ -82,6 +83,7 @@ class ShaderEditorPlugin : public EditorPlugin {
};
HSplitContainer *main_split = nullptr;
+ VBoxContainer *left_panel = nullptr;
ItemList *shader_list = nullptr;
TabContainer *shader_tabs = nullptr;
diff --git a/editor/plugins/skeleton_3d_editor_plugin.cpp b/editor/plugins/skeleton_3d_editor_plugin.cpp
index b340dd976e..9fc88b3a6a 100644
--- a/editor/plugins/skeleton_3d_editor_plugin.cpp
+++ b/editor/plugins/skeleton_3d_editor_plugin.cpp
@@ -47,6 +47,7 @@
#include "scene/3d/physics/physics_body_3d.h"
#include "scene/gui/separator.h"
#include "scene/gui/texture_rect.h"
+#include "scene/property_utils.h"
#include "scene/resources/3d/capsule_shape_3d.h"
#include "scene/resources/skeleton_profile.h"
#include "scene/resources/surface_tool.h"
@@ -65,7 +66,7 @@ void BoneTransformEditor::create_editors() {
// Position property.
position_property = memnew(EditorPropertyVector3());
- position_property->setup(-10000, 10000, 0.001f, true);
+ position_property->setup(-10000, 10000, 0.001, true);
position_property->set_label("Position");
position_property->set_selectable(false);
position_property->connect("property_changed", callable_mp(this, &BoneTransformEditor::_value_changed));
@@ -74,7 +75,7 @@ void BoneTransformEditor::create_editors() {
// Rotation property.
rotation_property = memnew(EditorPropertyQuaternion());
- rotation_property->setup(-10000, 10000, 0.001f, true);
+ rotation_property->setup(-10000, 10000, 0.001, true);
rotation_property->set_label("Rotation");
rotation_property->set_selectable(false);
rotation_property->connect("property_changed", callable_mp(this, &BoneTransformEditor::_value_changed));
@@ -83,7 +84,7 @@ void BoneTransformEditor::create_editors() {
// Scale property.
scale_property = memnew(EditorPropertyVector3());
- scale_property->setup(-10000, 10000, 0.001f, true);
+ scale_property->setup(-10000, 10000, 0.001, true, true);
scale_property->set_label("Scale");
scale_property->set_selectable(false);
scale_property->connect("property_changed", callable_mp(this, &BoneTransformEditor::_value_changed));
@@ -97,7 +98,7 @@ void BoneTransformEditor::create_editors() {
// Transform/Matrix property.
rest_matrix = memnew(EditorPropertyTransform3D());
- rest_matrix->setup(-10000, 10000, 0.001f, true);
+ rest_matrix->setup(-10000, 10000, 0.001, true);
rest_matrix->set_label("Transform");
rest_matrix->set_selectable(false);
rest_section->get_vbox()->add_child(rest_matrix);
@@ -122,6 +123,13 @@ void BoneTransformEditor::_value_changed(const String &p_property, const Variant
undo_redo->create_action(TTR("Set Bone Transform"), UndoRedo::MERGE_ENDS);
undo_redo->add_undo_property(skeleton, p_property, skeleton->get(p_property));
undo_redo->add_do_property(skeleton, p_property, p_value);
+
+ Skeleton3DEditor *se = Skeleton3DEditor::get_singleton();
+ if (se) {
+ undo_redo->add_do_method(se, "update_joint_tree");
+ undo_redo->add_undo_method(se, "update_joint_tree");
+ }
+
undo_redo->commit_action();
}
}
@@ -189,26 +197,31 @@ void BoneTransformEditor::_update_properties() {
if (split[2] == "enabled") {
enabled_checkbox->set_read_only(E.usage & PROPERTY_USAGE_READ_ONLY);
enabled_checkbox->update_property();
+ enabled_checkbox->update_editor_property_status();
enabled_checkbox->queue_redraw();
}
if (split[2] == "position") {
position_property->set_read_only(E.usage & PROPERTY_USAGE_READ_ONLY);
position_property->update_property();
+ position_property->update_editor_property_status();
position_property->queue_redraw();
}
if (split[2] == "rotation") {
rotation_property->set_read_only(E.usage & PROPERTY_USAGE_READ_ONLY);
rotation_property->update_property();
+ rotation_property->update_editor_property_status();
rotation_property->queue_redraw();
}
if (split[2] == "scale") {
scale_property->set_read_only(E.usage & PROPERTY_USAGE_READ_ONLY);
scale_property->update_property();
+ scale_property->update_editor_property_status();
scale_property->queue_redraw();
}
if (split[2] == "rest") {
rest_matrix->set_read_only(E.usage & PROPERTY_USAGE_READ_ONLY);
rest_matrix->update_property();
+ rest_matrix->update_editor_property_status();
rest_matrix->queue_redraw();
}
}
@@ -232,6 +245,11 @@ void Skeleton3DEditor::set_bone_options_enabled(const bool p_bone_options_enable
skeleton_options->get_popup()->set_item_disabled(SKELETON_OPTION_SELECTED_POSES_TO_RESTS, !p_bone_options_enabled);
};
+void Skeleton3DEditor::_bind_methods() {
+ ClassDB::bind_method(D_METHOD("update_all"), &Skeleton3DEditor::update_all);
+ ClassDB::bind_method(D_METHOD("update_joint_tree"), &Skeleton3DEditor::update_joint_tree);
+}
+
void Skeleton3DEditor::_on_click_skeleton_option(int p_skeleton_option) {
if (!skeleton) {
return;
@@ -294,6 +312,10 @@ void Skeleton3DEditor::reset_pose(const bool p_all_bones) {
ur->add_undo_method(skeleton, "set_bone_pose_scale", selected_bone, skeleton->get_bone_pose_scale(selected_bone));
ur->add_do_method(skeleton, "reset_bone_pose", selected_bone);
}
+
+ ur->add_undo_method(this, "update_joint_tree");
+ ur->add_do_method(this, "update_joint_tree");
+
ur->commit_action();
}
@@ -357,6 +379,10 @@ void Skeleton3DEditor::pose_to_rest(const bool p_all_bones) {
ur->add_do_method(skeleton, "set_bone_rest", selected_bone, skeleton->get_bone_pose(selected_bone));
ur->add_undo_method(skeleton, "set_bone_rest", selected_bone, skeleton->get_bone_rest(selected_bone));
}
+
+ ur->add_undo_method(this, "update_joint_tree");
+ ur->add_do_method(this, "update_joint_tree");
+
ur->commit_action();
}
@@ -620,9 +646,12 @@ void Skeleton3DEditor::move_skeleton_bone(NodePath p_skeleton_path, int32_t p_se
}
ur->add_undo_method(skeleton_node, "set_bone_parent", p_selected_boneidx, skeleton_node->get_bone_parent(p_selected_boneidx));
ur->add_do_method(skeleton_node, "set_bone_parent", p_selected_boneidx, p_target_boneidx);
+
+ ur->add_undo_method(this, "update_joint_tree");
+ ur->add_do_method(this, "update_joint_tree");
+
skeleton_node->set_bone_parent(p_selected_boneidx, p_target_boneidx);
- update_joint_tree();
ur->commit_action();
}
@@ -655,6 +684,107 @@ void Skeleton3DEditor::_joint_tree_selection_changed() {
void Skeleton3DEditor::_joint_tree_rmb_select(const Vector2 &p_pos, MouseButton p_button) {
}
+void Skeleton3DEditor::_joint_tree_button_clicked(Object *p_item, int p_column, int p_id, MouseButton p_button) {
+ if (!skeleton) {
+ return;
+ }
+
+ TreeItem *tree_item = Object::cast_to<TreeItem>(p_item);
+ if (tree_item) {
+ String tree_item_metadata = tree_item->get_metadata(0);
+
+ String bone_enabled_property = tree_item_metadata + "/enabled";
+ String bone_parent_property = tree_item_metadata + "/parent";
+ String bone_name_property = tree_item_metadata + "/name";
+ String bone_position_property = tree_item_metadata + "/position";
+ String bone_rotation_property = tree_item_metadata + "/rotation";
+ String bone_scale_property = tree_item_metadata + "/scale";
+ String bone_rest_property = tree_item_metadata + "/rest";
+
+ Variant current_enabled = skeleton->get(bone_enabled_property);
+ Variant current_parent = skeleton->get(bone_parent_property);
+ Variant current_name = skeleton->get(bone_name_property);
+ Variant current_position = skeleton->get(bone_position_property);
+ Variant current_rotation = skeleton->get(bone_rotation_property);
+ Variant current_scale = skeleton->get(bone_scale_property);
+ Variant current_rest = skeleton->get(bone_rest_property);
+
+ EditorUndoRedoManager *ur = EditorUndoRedoManager::get_singleton();
+ ur->create_action(TTR("Revert Bone"));
+
+ bool can_revert_enabled = EditorPropertyRevert::can_property_revert(skeleton, bone_enabled_property, &current_enabled);
+ if (can_revert_enabled) {
+ bool is_valid = false;
+ Variant new_enabled = EditorPropertyRevert::get_property_revert_value(skeleton, bone_enabled_property, &is_valid);
+ if (is_valid) {
+ ur->add_undo_method(skeleton, "set", bone_enabled_property, current_enabled);
+ ur->add_do_method(skeleton, "set", bone_enabled_property, new_enabled);
+ }
+ }
+
+ bool can_revert_parent = EditorPropertyRevert::can_property_revert(skeleton, bone_parent_property, &current_parent);
+ if (can_revert_parent) {
+ bool is_valid = false;
+ Variant new_parent = EditorPropertyRevert::get_property_revert_value(skeleton, bone_parent_property, &is_valid);
+ if (is_valid) {
+ ur->add_undo_method(skeleton, "set", bone_parent_property, current_parent);
+ ur->add_do_method(skeleton, "set", bone_parent_property, new_parent);
+ }
+ }
+ bool can_revert_name = EditorPropertyRevert::can_property_revert(skeleton, bone_name_property, &current_name);
+ if (can_revert_name) {
+ bool is_valid = false;
+ Variant new_name = EditorPropertyRevert::get_property_revert_value(skeleton, bone_name_property, &is_valid);
+ if (is_valid) {
+ ur->add_undo_method(skeleton, "set", bone_name_property, current_name);
+ ur->add_do_method(skeleton, "set", bone_name_property, new_name);
+ }
+ }
+ bool can_revert_position = EditorPropertyRevert::can_property_revert(skeleton, bone_position_property, &current_position);
+ if (can_revert_position) {
+ bool is_valid = false;
+ Variant new_position = EditorPropertyRevert::get_property_revert_value(skeleton, bone_position_property, &is_valid);
+ if (is_valid) {
+ ur->add_undo_method(skeleton, "set", bone_position_property, current_position);
+ ur->add_do_method(skeleton, "set", bone_position_property, new_position);
+ }
+ }
+ bool can_revert_rotation = EditorPropertyRevert::can_property_revert(skeleton, bone_rotation_property, &current_rotation);
+ if (can_revert_rotation) {
+ bool is_valid = false;
+ Variant new_rotation = EditorPropertyRevert::get_property_revert_value(skeleton, bone_rotation_property, &is_valid);
+ if (is_valid) {
+ ur->add_undo_method(skeleton, "set", bone_rotation_property, current_rotation);
+ ur->add_do_method(skeleton, "set", bone_rotation_property, new_rotation);
+ }
+ }
+ bool can_revert_scale = EditorPropertyRevert::can_property_revert(skeleton, bone_scale_property, &current_scale);
+ if (can_revert_scale) {
+ bool is_valid = false;
+ Variant new_scale = EditorPropertyRevert::get_property_revert_value(skeleton, bone_scale_property, &is_valid);
+ if (is_valid) {
+ ur->add_undo_method(skeleton, "set", bone_scale_property, current_scale);
+ ur->add_do_method(skeleton, "set", bone_scale_property, new_scale);
+ }
+ }
+ bool can_revert_rest = EditorPropertyRevert::can_property_revert(skeleton, bone_rest_property, &current_rest);
+ if (can_revert_rest) {
+ bool is_valid = false;
+ Variant new_rest = EditorPropertyRevert::get_property_revert_value(skeleton, bone_rest_property, &is_valid);
+ if (is_valid) {
+ ur->add_undo_method(skeleton, "set", bone_rest_property, current_rest);
+ ur->add_do_method(skeleton, "set", bone_rest_property, new_rest);
+ }
+ }
+
+ ur->add_undo_method(this, "update_all");
+ ur->add_do_method(this, "update_all");
+
+ ur->commit_action();
+ }
+ return;
+}
+
void Skeleton3DEditor::_update_properties() {
if (pose_editor) {
pose_editor->_update_properties();
@@ -693,15 +823,52 @@ void Skeleton3DEditor::update_joint_tree() {
joint_item->set_selectable(0, true);
joint_item->set_metadata(0, "bones/" + itos(current_bone_idx));
+ String bone_enabled_property = "bones/" + itos(current_bone_idx) + "/enabled";
+ String bone_parent_property = "bones/" + itos(current_bone_idx) + "/parent";
+ String bone_name_property = "bones/" + itos(current_bone_idx) + "/name";
+ String bone_position_property = "bones/" + itos(current_bone_idx) + "/position";
+ String bone_rotation_property = "bones/" + itos(current_bone_idx) + "/rotation";
+ String bone_scale_property = "bones/" + itos(current_bone_idx) + "/scale";
+ String bone_rest_property = "bones/" + itos(current_bone_idx) + "/rest";
+
+ Variant current_enabled = skeleton->get(bone_enabled_property);
+ Variant current_parent = skeleton->get(bone_parent_property);
+ Variant current_name = skeleton->get(bone_name_property);
+ Variant current_position = skeleton->get(bone_position_property);
+ Variant current_rotation = skeleton->get(bone_rotation_property);
+ Variant current_scale = skeleton->get(bone_scale_property);
+ Variant current_rest = skeleton->get(bone_rest_property);
+
+ bool can_revert_enabled = EditorPropertyRevert::can_property_revert(skeleton, bone_enabled_property, &current_enabled);
+ bool can_revert_parent = EditorPropertyRevert::can_property_revert(skeleton, bone_parent_property, &current_parent);
+ bool can_revert_name = EditorPropertyRevert::can_property_revert(skeleton, bone_name_property, &current_name);
+ bool can_revert_position = EditorPropertyRevert::can_property_revert(skeleton, bone_position_property, &current_position);
+ bool can_revert_rotation = EditorPropertyRevert::can_property_revert(skeleton, bone_rotation_property, &current_rotation);
+ bool can_revert_scale = EditorPropertyRevert::can_property_revert(skeleton, bone_scale_property, &current_scale);
+ bool can_revert_rest = EditorPropertyRevert::can_property_revert(skeleton, bone_rest_property, &current_rest);
+
+ if (can_revert_enabled || can_revert_parent || can_revert_name || can_revert_position || can_revert_rotation || can_revert_scale || can_revert_rest) {
+ joint_item->add_button(0, get_editor_theme_icon(SNAME("ReloadSmall")), JOINT_BUTTON_REVERT, false, TTR("Revert"));
+ }
+
// Add the bone's children to the list of bones to be processed.
Vector<int> current_bone_child_bones = skeleton->get_bone_children(current_bone_idx);
int child_bone_size = current_bone_child_bones.size();
for (int i = 0; i < child_bone_size; i++) {
bones_to_process.push_back(current_bone_child_bones[i]);
}
+
+ if (current_bone_idx == selected_bone) {
+ joint_item->select(0);
+ }
}
}
+void Skeleton3DEditor::update_all() {
+ _update_properties();
+ update_joint_tree();
+}
+
void Skeleton3DEditor::create_editors() {
set_h_size_flags(SIZE_EXPAND_FILL);
set_focus_mode(FOCUS_ALL);
@@ -747,7 +914,7 @@ void Skeleton3DEditor::create_editors() {
edit_mode_button->set_toggle_mode(true);
edit_mode_button->set_focus_mode(FOCUS_NONE);
edit_mode_button->set_tooltip_text(TTR("Edit Mode\nShow buttons on joints."));
- edit_mode_button->connect("toggled", callable_mp(this, &Skeleton3DEditor::edit_mode_toggled));
+ edit_mode_button->connect(SceneStringName(toggled), callable_mp(this, &Skeleton3DEditor::edit_mode_toggled));
edit_mode = false;
@@ -840,6 +1007,7 @@ void Skeleton3DEditor::_notification(int p_what) {
joint_tree->connect(SceneStringName(item_selected), callable_mp(this, &Skeleton3DEditor::_joint_tree_selection_changed));
joint_tree->connect("item_mouse_selected", callable_mp(this, &Skeleton3DEditor::_joint_tree_rmb_select));
+ joint_tree->connect("button_clicked", callable_mp(this, &Skeleton3DEditor::_joint_tree_button_clicked));
#ifdef TOOLS_ENABLED
skeleton->connect(SceneStringName(pose_updated), callable_mp(this, &Skeleton3DEditor::_draw_gizmo));
skeleton->connect(SceneStringName(pose_updated), callable_mp(this, &Skeleton3DEditor::_update_properties));
@@ -1180,16 +1348,18 @@ int Skeleton3DEditor::get_selected_bone() const {
return selected_bone;
}
+Skeleton3DGizmoPlugin::SelectionMaterials Skeleton3DGizmoPlugin::selection_materials;
+
Skeleton3DGizmoPlugin::Skeleton3DGizmoPlugin() {
- unselected_mat = Ref<StandardMaterial3D>(memnew(StandardMaterial3D));
- unselected_mat->set_shading_mode(StandardMaterial3D::SHADING_MODE_UNSHADED);
- unselected_mat->set_transparency(StandardMaterial3D::TRANSPARENCY_ALPHA);
- unselected_mat->set_flag(StandardMaterial3D::FLAG_ALBEDO_FROM_VERTEX_COLOR, true);
- unselected_mat->set_flag(StandardMaterial3D::FLAG_SRGB_VERTEX_COLOR, true);
- unselected_mat->set_flag(StandardMaterial3D::FLAG_DISABLE_FOG, true);
-
- selected_mat = Ref<ShaderMaterial>(memnew(ShaderMaterial));
- selected_sh = Ref<Shader>(memnew(Shader));
+ selection_materials.unselected_mat.instantiate();
+ selection_materials.unselected_mat->set_shading_mode(StandardMaterial3D::SHADING_MODE_UNSHADED);
+ selection_materials.unselected_mat->set_transparency(StandardMaterial3D::TRANSPARENCY_ALPHA);
+ selection_materials.unselected_mat->set_flag(StandardMaterial3D::FLAG_ALBEDO_FROM_VERTEX_COLOR, true);
+ selection_materials.unselected_mat->set_flag(StandardMaterial3D::FLAG_SRGB_VERTEX_COLOR, true);
+ selection_materials.unselected_mat->set_flag(StandardMaterial3D::FLAG_DISABLE_FOG, true);
+
+ selection_materials.selected_mat.instantiate();
+ Ref<Shader> selected_sh = Ref<Shader>(memnew(Shader));
selected_sh->set_code(R"(
// Skeleton 3D gizmo bones shader.
@@ -1208,7 +1378,7 @@ void fragment() {
ALPHA = COLOR.a;
}
)");
- selected_mat->set_shader(selected_sh);
+ selection_materials.selected_mat->set_shader(selected_sh);
// Register properties in editor settings.
EDITOR_DEF_RST("editors/3d_gizmos/gizmo_colors/skeleton", Color(1, 0.8, 0.4));
@@ -1218,6 +1388,11 @@ void fragment() {
EditorSettings::get_singleton()->add_property_hint(PropertyInfo(Variant::INT, "editors/3d_gizmos/gizmo_settings/bone_shape", PROPERTY_HINT_ENUM, "Wire,Octahedron"));
}
+Skeleton3DGizmoPlugin::~Skeleton3DGizmoPlugin() {
+ selection_materials.unselected_mat.unref();
+ selection_materials.selected_mat.unref();
+}
+
bool Skeleton3DGizmoPlugin::has_gizmo(Node3D *p_spatial) {
return Object::cast_to<Skeleton3D>(p_spatial) != nullptr;
}
@@ -1337,6 +1512,10 @@ void Skeleton3DGizmoPlugin::commit_subgizmos(const EditorNode3DGizmo *p_gizmo, c
ur->add_undo_method(skeleton, "set_bone_pose_scale", p_ids[i], se->get_bone_original_scale());
}
}
+
+ ur->add_do_method(se, "update_joint_tree");
+ ur->add_undo_method(se, "update_joint_tree");
+
ur->commit_action();
}
@@ -1354,6 +1533,11 @@ void Skeleton3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) {
selected = se->get_selected_bone();
}
+ Ref<ArrayMesh> m = get_bones_mesh(skeleton, selected, p_gizmo->is_selected());
+ p_gizmo->add_mesh(m, Ref<Material>(), Transform3D(), skeleton->register_skin(skeleton->create_skin_from_rest_transforms()));
+}
+
+Ref<ArrayMesh> Skeleton3DGizmoPlugin::get_bones_mesh(Skeleton3D *p_skeleton, int p_selected, bool p_is_selected) {
Color bone_color = EDITOR_GET("editors/3d_gizmos/gizmo_colors/skeleton");
Color selected_bone_color = EDITOR_GET("editors/3d_gizmos/gizmo_colors/selected_bone");
real_t bone_axis_length = EDITOR_GET("editors/3d_gizmos/gizmo_settings/bone_axis_length");
@@ -1367,11 +1551,11 @@ void Skeleton3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) {
Ref<SurfaceTool> surface_tool(memnew(SurfaceTool));
surface_tool->begin(Mesh::PRIMITIVE_LINES);
- if (p_gizmo->is_selected()) {
- surface_tool->set_material(selected_mat);
+ if (p_is_selected) {
+ surface_tool->set_material(selection_materials.selected_mat);
} else {
- unselected_mat->set_albedo(bone_color);
- surface_tool->set_material(unselected_mat);
+ selection_materials.unselected_mat->set_albedo(bone_color);
+ surface_tool->set_material(selection_materials.unselected_mat);
}
LocalVector<int> bones;
@@ -1385,16 +1569,16 @@ void Skeleton3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) {
weights[0] = 1;
int current_bone_index = 0;
- Vector<int> bones_to_process = skeleton->get_parentless_bones();
+ Vector<int> bones_to_process = p_skeleton->get_parentless_bones();
while (bones_to_process.size() > current_bone_index) {
int current_bone_idx = bones_to_process[current_bone_index];
current_bone_index++;
- Color current_bone_color = (current_bone_idx == selected) ? selected_bone_color : bone_color;
+ Color current_bone_color = (current_bone_idx == p_selected) ? selected_bone_color : bone_color;
Vector<int> child_bones_vector;
- child_bones_vector = skeleton->get_bone_children(current_bone_idx);
+ child_bones_vector = p_skeleton->get_bone_children(current_bone_idx);
int child_bones_size = child_bones_vector.size();
for (int i = 0; i < child_bones_size; i++) {
@@ -1405,8 +1589,8 @@ void Skeleton3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) {
int child_bone_idx = child_bones_vector[i];
- Vector3 v0 = skeleton->get_bone_global_rest(current_bone_idx).origin;
- Vector3 v1 = skeleton->get_bone_global_rest(child_bone_idx).origin;
+ Vector3 v0 = p_skeleton->get_bone_global_rest(current_bone_idx).origin;
+ Vector3 v1 = p_skeleton->get_bone_global_rest(child_bone_idx).origin;
Vector3 d = (v1 - v0).normalized();
real_t dist = v0.distance_to(v1);
@@ -1414,7 +1598,7 @@ void Skeleton3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) {
int closest = -1;
real_t closest_d = 0.0;
for (int j = 0; j < 3; j++) {
- real_t dp = Math::abs(skeleton->get_bone_global_rest(current_bone_idx).basis[j].normalized().dot(d));
+ real_t dp = Math::abs(p_skeleton->get_bone_global_rest(current_bone_idx).basis[j].normalized().dot(d));
if (j == 0 || dp > closest_d) {
closest = j;
}
@@ -1441,7 +1625,7 @@ void Skeleton3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) {
for (int j = 0; j < 3; j++) {
Vector3 axis;
if (first == Vector3()) {
- axis = d.cross(d.cross(skeleton->get_bone_global_rest(current_bone_idx).basis[j])).normalized();
+ axis = d.cross(d.cross(p_skeleton->get_bone_global_rest(current_bone_idx).basis[j])).normalized();
first = axis;
} else {
axis = d.cross(first).normalized();
@@ -1496,7 +1680,7 @@ void Skeleton3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) {
surface_tool->add_vertex(v0);
surface_tool->set_bones(bones);
surface_tool->set_weights(weights);
- surface_tool->add_vertex(v0 + (skeleton->get_bone_global_rest(current_bone_idx).basis.inverse())[j].normalized() * dist * bone_axis_length);
+ surface_tool->add_vertex(v0 + (p_skeleton->get_bone_global_rest(current_bone_idx).basis.inverse())[j].normalized() * dist * bone_axis_length);
if (j == closest) {
continue;
@@ -1513,7 +1697,7 @@ void Skeleton3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) {
surface_tool->add_vertex(v1);
surface_tool->set_bones(bones);
surface_tool->set_weights(weights);
- surface_tool->add_vertex(v1 + (skeleton->get_bone_global_rest(child_bone_idx).basis.inverse())[j].normalized() * dist * bone_axis_length);
+ surface_tool->add_vertex(v1 + (p_skeleton->get_bone_global_rest(child_bone_idx).basis.inverse())[j].normalized() * dist * bone_axis_length);
if (j == closest) {
continue;
@@ -1526,6 +1710,5 @@ void Skeleton3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) {
}
}
- Ref<ArrayMesh> m = surface_tool->commit();
- p_gizmo->add_mesh(m, Ref<Material>(), Transform3D(), skeleton->register_skin(skeleton->create_skin_from_rest_transforms()));
+ return surface_tool->commit();
}
diff --git a/editor/plugins/skeleton_3d_editor_plugin.h b/editor/plugins/skeleton_3d_editor_plugin.h
index 79dc16ae2f..d4dee1f16f 100644
--- a/editor/plugins/skeleton_3d_editor_plugin.h
+++ b/editor/plugins/skeleton_3d_editor_plugin.h
@@ -96,6 +96,8 @@ public:
class Skeleton3DEditor : public VBoxContainer {
GDCLASS(Skeleton3DEditor, VBoxContainer);
+ static void _bind_methods();
+
friend class Skeleton3DEditorPlugin;
enum SkeletonOption {
@@ -116,6 +118,10 @@ class Skeleton3DEditor : public VBoxContainer {
Skeleton3D *skeleton = nullptr;
+ enum {
+ JOINT_BUTTON_REVERT = 0,
+ };
+
Tree *joint_tree = nullptr;
BoneTransformEditor *rest_editor = nullptr;
BoneTransformEditor *pose_editor = nullptr;
@@ -149,6 +155,7 @@ class Skeleton3DEditor : public VBoxContainer {
EditorFileDialog *file_export_lib = nullptr;
void update_joint_tree();
+ void update_all();
void create_editors();
@@ -189,6 +196,7 @@ class Skeleton3DEditor : public VBoxContainer {
void _joint_tree_selection_changed();
void _joint_tree_rmb_select(const Vector2 &p_pos, MouseButton p_button);
+ void _joint_tree_button_clicked(Object *p_item, int p_column, int p_id, MouseButton p_button);
void _update_properties();
void _subgizmo_selection_change();
@@ -252,11 +260,15 @@ public:
class Skeleton3DGizmoPlugin : public EditorNode3DGizmoPlugin {
GDCLASS(Skeleton3DGizmoPlugin, EditorNode3DGizmoPlugin);
- Ref<StandardMaterial3D> unselected_mat;
- Ref<ShaderMaterial> selected_mat;
- Ref<Shader> selected_sh;
+ struct SelectionMaterials {
+ Ref<StandardMaterial3D> unselected_mat;
+ Ref<ShaderMaterial> selected_mat;
+ };
+ static SelectionMaterials selection_materials;
public:
+ static Ref<ArrayMesh> get_bones_mesh(Skeleton3D *p_skeleton, int p_selected, bool p_is_selected);
+
bool has_gizmo(Node3D *p_spatial) override;
String get_gizmo_name() const override;
int get_priority() const override;
@@ -269,6 +281,7 @@ public:
void redraw(EditorNode3DGizmo *p_gizmo) override;
Skeleton3DGizmoPlugin();
+ ~Skeleton3DGizmoPlugin();
};
#endif // SKELETON_3D_EDITOR_PLUGIN_H
diff --git a/editor/plugins/sprite_frames_editor_plugin.cpp b/editor/plugins/sprite_frames_editor_plugin.cpp
index ff5aca6cb0..37d5b787eb 100644
--- a/editor/plugins/sprite_frames_editor_plugin.cpp
+++ b/editor/plugins/sprite_frames_editor_plugin.cpp
@@ -659,6 +659,7 @@ void SpriteFramesEditor::_notification(int p_what) {
zoom_reset->set_icon(get_editor_theme_icon(SNAME("ZoomReset")));
zoom_in->set_icon(get_editor_theme_icon(SNAME("ZoomMore")));
add_anim->set_icon(get_editor_theme_icon(SNAME("New")));
+ duplicate_anim->set_icon(get_editor_theme_icon(SNAME("Duplicate")));
delete_anim->set_icon(get_editor_theme_icon(SNAME("Remove")));
anim_search_box->set_right_icon(get_editor_theme_icon(SNAME("Search")));
split_sheet_zoom_out->set_icon(get_editor_theme_icon(SNAME("ZoomLess")));
@@ -1179,6 +1180,41 @@ void SpriteFramesEditor::_animation_add() {
animations->grab_focus();
}
+void SpriteFramesEditor::_animation_duplicate() {
+ if (updating) {
+ return;
+ }
+
+ if (!frames->has_animation(edited_anim)) {
+ return;
+ }
+
+ int counter = 1;
+ String new_name = edited_anim;
+ PackedStringArray name_component = new_name.rsplit("_", true, 1);
+ String base_name = name_component[0];
+ if (name_component.size() > 1 && name_component[1].is_valid_int() && name_component[1].to_int() >= 0) {
+ counter = name_component[1].to_int();
+ }
+ new_name = base_name + "_" + itos(counter);
+ while (frames->has_animation(new_name)) {
+ counter++;
+ new_name = base_name + "_" + itos(counter);
+ }
+
+ EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
+ undo_redo->create_action(TTR("Duplicate Animation"), UndoRedo::MERGE_DISABLE, EditorNode::get_singleton()->get_edited_scene());
+ undo_redo->add_do_method(frames.ptr(), "duplicate_animation", edited_anim, new_name);
+ undo_redo->add_undo_method(frames.ptr(), "remove_animation", new_name);
+ undo_redo->add_do_method(this, "_select_animation", new_name);
+ undo_redo->add_undo_method(this, "_select_animation", edited_anim);
+ undo_redo->add_do_method(this, "_update_library");
+ undo_redo->add_undo_method(this, "_update_library");
+ undo_redo->commit_action();
+
+ animations->grab_focus();
+}
+
void SpriteFramesEditor::_animation_remove() {
if (updating) {
return;
@@ -1541,6 +1577,7 @@ void SpriteFramesEditor::edit(Ref<SpriteFrames> p_frames) {
_zoom_reset();
add_anim->set_disabled(read_only);
+ duplicate_anim->set_disabled(read_only);
delete_anim->set_disabled(read_only);
anim_speed->set_editable(!read_only);
anim_loop->set_disabled(read_only);
@@ -1865,6 +1902,11 @@ SpriteFramesEditor::SpriteFramesEditor() {
hbc_animlist->add_child(add_anim);
add_anim->connect(SceneStringName(pressed), callable_mp(this, &SpriteFramesEditor::_animation_add));
+ duplicate_anim = memnew(Button);
+ duplicate_anim->set_flat(true);
+ hbc_animlist->add_child(duplicate_anim);
+ duplicate_anim->connect(SceneStringName(pressed), callable_mp(this, &SpriteFramesEditor::_animation_duplicate));
+
delete_anim = memnew(Button);
delete_anim->set_theme_type_variation("FlatButton");
hbc_animlist->add_child(delete_anim);
@@ -1918,6 +1960,8 @@ SpriteFramesEditor::SpriteFramesEditor() {
add_anim->set_shortcut_context(animations);
add_anim->set_shortcut(ED_SHORTCUT("sprite_frames/new_animation", TTR("Add Animation"), KeyModifierMask::CMD_OR_CTRL | Key::N));
+ duplicate_anim->set_shortcut_context(animations);
+ duplicate_anim->set_shortcut(ED_SHORTCUT("sprite_frames/duplicate_animation", TTR("Duplicate Animation"), KeyModifierMask::CMD_OR_CTRL | Key::D));
delete_anim->set_shortcut_context(animations);
delete_anim->set_shortcut(ED_SHORTCUT("sprite_frames/delete_animation", TTR("Delete Animation"), Key::KEY_DELETE));
diff --git a/editor/plugins/sprite_frames_editor_plugin.h b/editor/plugins/sprite_frames_editor_plugin.h
index d6345a3ac8..a85a6b2453 100644
--- a/editor/plugins/sprite_frames_editor_plugin.h
+++ b/editor/plugins/sprite_frames_editor_plugin.h
@@ -121,6 +121,7 @@ class SpriteFramesEditor : public HSplitContainer {
Vector<int> selection;
Button *add_anim = nullptr;
+ Button *duplicate_anim = nullptr;
Button *delete_anim = nullptr;
SpinBox *anim_speed = nullptr;
Button *anim_loop = nullptr;
@@ -210,6 +211,7 @@ class SpriteFramesEditor : public HSplitContainer {
void _animation_selected();
void _animation_name_edited();
void _animation_add();
+ void _animation_duplicate();
void _animation_remove();
void _animation_remove_confirmed();
void _animation_search_text_changed(const String &p_text);
diff --git a/editor/plugins/style_box_editor_plugin.cpp b/editor/plugins/style_box_editor_plugin.cpp
index 6ecbff3bb4..0b53c10fab 100644
--- a/editor/plugins/style_box_editor_plugin.cpp
+++ b/editor/plugins/style_box_editor_plugin.cpp
@@ -113,7 +113,7 @@ StyleBoxPreview::StyleBoxPreview() {
// This theme variation works better than the normal theme because there's no focus highlight.
grid_preview->set_theme_type_variation("PreviewLightButton");
grid_preview->set_toggle_mode(true);
- grid_preview->connect("toggled", callable_mp(this, &StyleBoxPreview::_grid_preview_toggled));
+ grid_preview->connect(SceneStringName(toggled), callable_mp(this, &StyleBoxPreview::_grid_preview_toggled));
grid_preview->set_pressed(grid_preview_enabled);
add_child(grid_preview);
}
diff --git a/editor/plugins/text_editor.cpp b/editor/plugins/text_editor.cpp
index cb1f6c1ec6..1262fe1ce8 100644
--- a/editor/plugins/text_editor.cpp
+++ b/editor/plugins/text_editor.cpp
@@ -35,6 +35,7 @@
#include "editor/editor_node.h"
#include "editor/editor_settings.h"
#include "scene/gui/menu_button.h"
+#include "scene/gui/split_container.h"
void TextEditor::add_syntax_highlighter(Ref<EditorSyntaxHighlighter> p_highlighter) {
ERR_FAIL_COND(p_highlighter.is_null());
@@ -103,12 +104,12 @@ void TextEditor::set_edited_resource(const Ref<Resource> &p_res) {
edited_res = p_res;
Ref<TextFile> text_file = edited_res;
- if (text_file != nullptr) {
+ if (text_file.is_valid()) {
code_editor->get_text_editor()->set_text(text_file->get_text());
}
Ref<JSON> json_file = edited_res;
- if (json_file != nullptr) {
+ if (json_file.is_valid()) {
code_editor->get_text_editor()->set_text(json_file->get_parsed_text());
}
@@ -168,12 +169,12 @@ void TextEditor::reload_text() {
int v = te->get_v_scroll();
Ref<TextFile> text_file = edited_res;
- if (text_file != nullptr) {
+ if (text_file.is_valid()) {
te->set_text(text_file->get_text());
}
Ref<JSON> json_file = edited_res;
- if (json_file != nullptr) {
+ if (json_file.is_valid()) {
te->set_text(json_file->get_parsed_text());
}
@@ -193,7 +194,7 @@ void TextEditor::_validate_script() {
emit_signal(SNAME("edited_script_changed"));
Ref<JSON> json_file = edited_res;
- if (json_file != nullptr) {
+ if (json_file.is_valid()) {
CodeEdit *te = code_editor->get_text_editor();
te->set_line_background_color(code_editor->get_error_pos().x, Color(0, 0, 0, 0));
@@ -244,12 +245,12 @@ void TextEditor::_bookmark_item_pressed(int p_idx) {
void TextEditor::apply_code() {
Ref<TextFile> text_file = edited_res;
- if (text_file != nullptr) {
+ if (text_file.is_valid()) {
text_file->set_text(code_editor->get_text_editor()->get_text());
}
Ref<JSON> json_file = edited_res;
- if (json_file != nullptr) {
+ if (json_file.is_valid()) {
json_file->parse(code_editor->get_text_editor()->get_text(), true);
}
code_editor->get_text_editor()->get_syntax_highlighter()->update_cache();
@@ -606,6 +607,7 @@ TextEditor::TextEditor() {
code_editor->set_anchors_and_offsets_preset(Control::PRESET_FULL_RECT);
code_editor->set_v_size_flags(Control::SIZE_EXPAND_FILL);
code_editor->show_toggle_scripts_button();
+ code_editor->set_toggle_list_control(ScriptEditor::get_singleton()->get_left_list_split());
update_settings();
diff --git a/editor/plugins/text_shader_editor.cpp b/editor/plugins/text_shader_editor.cpp
index 4d8f3298b6..0ff7aaa3fe 100644
--- a/editor/plugins/text_shader_editor.cpp
+++ b/editor/plugins/text_shader_editor.cpp
@@ -1256,4 +1256,5 @@ TextShaderEditor::TextShaderEditor() {
add_child(disk_changed);
_editor_settings_changed();
+ code_editor->show_toggle_scripts_button(); // TODO: Disabled for now, because it doesn't work properly.
}
diff --git a/editor/plugins/texture_editor_plugin.cpp b/editor/plugins/texture_editor_plugin.cpp
index a3c1405553..d93466b5ba 100644
--- a/editor/plugins/texture_editor_plugin.cpp
+++ b/editor/plugins/texture_editor_plugin.cpp
@@ -169,7 +169,7 @@ void EditorInspectorPluginTexture::parse_begin(Object *p_object) {
Ref<Image> image(Object::cast_to<Image>(p_object));
texture = ImageTexture::create_from_image(image);
- ERR_FAIL_NULL_MSG(texture, "Failed to create the texture from an invalid image.");
+ ERR_FAIL_COND_MSG(texture.is_null(), "Failed to create the texture from an invalid image.");
}
add_custom_control(memnew(TexturePreview(texture, true)));
diff --git a/editor/plugins/theme_editor_plugin.cpp b/editor/plugins/theme_editor_plugin.cpp
index 99635a2531..ea1756b65a 100644
--- a/editor/plugins/theme_editor_plugin.cpp
+++ b/editor/plugins/theme_editor_plugin.cpp
@@ -2174,8 +2174,26 @@ void ThemeTypeDialog::_add_type_filter_cbk(const String &p_value) {
_update_add_type_options(p_value);
}
+void ThemeTypeDialog::_type_filter_input(const Ref<InputEvent> &p_ie) {
+ Ref<InputEventKey> k = p_ie;
+ if (k.is_valid() && k->is_pressed()) {
+ switch (k->get_keycode()) {
+ case Key::UP:
+ case Key::DOWN:
+ case Key::PAGEUP:
+ case Key::PAGEDOWN: {
+ add_type_options->gui_input(k);
+ add_type_filter->accept_event();
+ } break;
+ default:
+ break;
+ }
+ }
+}
+
void ThemeTypeDialog::_add_type_options_cbk(int p_index) {
add_type_filter->set_text(add_type_options->get_item_text(p_index));
+ add_type_filter->set_caret_column(add_type_filter->get_text().length());
}
void ThemeTypeDialog::_add_type_dialog_entered(const String &p_value) {
@@ -2245,6 +2263,7 @@ ThemeTypeDialog::ThemeTypeDialog() {
add_type_vb->add_child(add_type_filter);
add_type_filter->connect(SceneStringName(text_changed), callable_mp(this, &ThemeTypeDialog::_add_type_filter_cbk));
add_type_filter->connect("text_submitted", callable_mp(this, &ThemeTypeDialog::_add_type_dialog_entered));
+ add_type_filter->connect(SceneStringName(gui_input), callable_mp(this, &ThemeTypeDialog::_type_filter_input));
Label *add_type_options_label = memnew(Label);
add_type_options_label->set_text(TTR("Available Node-based types:"));
diff --git a/editor/plugins/theme_editor_plugin.h b/editor/plugins/theme_editor_plugin.h
index ba8e3a30b7..ae92365c32 100644
--- a/editor/plugins/theme_editor_plugin.h
+++ b/editor/plugins/theme_editor_plugin.h
@@ -303,6 +303,7 @@ class ThemeTypeDialog : public ConfirmationDialog {
void _update_add_type_options(const String &p_filter = "");
void _add_type_filter_cbk(const String &p_value);
+ void _type_filter_input(const Ref<InputEvent> &p_ie);
void _add_type_options_cbk(int p_index);
void _add_type_dialog_entered(const String &p_value);
void _add_type_dialog_activated(int p_index);
diff --git a/editor/plugins/tiles/tile_data_editors.cpp b/editor/plugins/tiles/tile_data_editors.cpp
index af52243c41..79915012a8 100644
--- a/editor/plugins/tiles/tile_data_editors.cpp
+++ b/editor/plugins/tiles/tile_data_editors.cpp
@@ -917,7 +917,7 @@ GenericTilePolygonEditor::GenericTilePolygonEditor() {
button_expand->set_toggle_mode(true);
button_expand->set_pressed(false);
button_expand->set_tooltip_text(TTR("Expand editor"));
- button_expand->connect("toggled", callable_mp(this, &GenericTilePolygonEditor::_toggle_expand));
+ button_expand->connect(SceneStringName(toggled), callable_mp(this, &GenericTilePolygonEditor::_toggle_expand));
toolbar->add_child(button_expand);
toolbar->add_child(memnew(VSeparator));
@@ -1482,30 +1482,36 @@ void TileDataOcclusionShapeEditor::draw_over_tile(CanvasItem *p_canvas_item, Tra
debug_occlusion_color.push_back(color);
RenderingServer::get_singleton()->canvas_item_add_set_transform(p_canvas_item->get_canvas_item(), p_transform);
- Ref<OccluderPolygon2D> occluder = tile_data->get_occluder(occlusion_layer);
- if (occluder.is_valid() && occluder->get_polygon().size() >= 3) {
- p_canvas_item->draw_polygon(Variant(occluder->get_polygon()), debug_occlusion_color);
+ for (int i = 0; i < tile_data->get_occluder_polygons_count(occlusion_layer); i++) {
+ Ref<OccluderPolygon2D> occluder = tile_data->get_occluder_polygon(occlusion_layer, i);
+ if (occluder.is_valid() && occluder->get_polygon().size() >= 3) {
+ p_canvas_item->draw_polygon(Variant(occluder->get_polygon()), debug_occlusion_color);
+ }
}
RenderingServer::get_singleton()->canvas_item_add_set_transform(p_canvas_item->get_canvas_item(), Transform2D());
}
Variant TileDataOcclusionShapeEditor::_get_painted_value() {
- Ref<OccluderPolygon2D> occluder_polygon;
- if (polygon_editor->get_polygon_count() >= 1) {
+ Array polygons;
+ for (int i = 0; i < polygon_editor->get_polygon_count(); i++) {
+ Ref<OccluderPolygon2D> occluder_polygon;
occluder_polygon.instantiate();
- occluder_polygon->set_polygon(polygon_editor->get_polygon(0));
+ occluder_polygon->set_polygon(polygon_editor->get_polygon(i));
+ polygons.push_back(occluder_polygon);
}
- return occluder_polygon;
+ return polygons;
}
void TileDataOcclusionShapeEditor::_set_painted_value(TileSetAtlasSource *p_tile_set_atlas_source, Vector2 p_coords, int p_alternative_tile) {
TileData *tile_data = p_tile_set_atlas_source->get_tile_data(p_coords, p_alternative_tile);
ERR_FAIL_NULL(tile_data);
- Ref<OccluderPolygon2D> occluder_polygon = tile_data->get_occluder(occlusion_layer);
polygon_editor->clear_polygons();
- if (occluder_polygon.is_valid()) {
- polygon_editor->add_polygon(occluder_polygon->get_polygon());
+ for (int i = 0; i < tile_data->get_occluder_polygons_count(occlusion_layer); i++) {
+ Ref<OccluderPolygon2D> occluder_polygon = tile_data->get_occluder_polygon(occlusion_layer, i);
+ if (occluder_polygon.is_valid()) {
+ polygon_editor->add_polygon(occluder_polygon->get_polygon());
+ }
}
polygon_editor->set_background_tile(p_tile_set_atlas_source, p_coords, p_alternative_tile);
}
@@ -1513,8 +1519,13 @@ void TileDataOcclusionShapeEditor::_set_painted_value(TileSetAtlasSource *p_tile
void TileDataOcclusionShapeEditor::_set_value(TileSetAtlasSource *p_tile_set_atlas_source, Vector2 p_coords, int p_alternative_tile, const Variant &p_value) {
TileData *tile_data = p_tile_set_atlas_source->get_tile_data(p_coords, p_alternative_tile);
ERR_FAIL_NULL(tile_data);
- Ref<OccluderPolygon2D> occluder_polygon = p_value;
- tile_data->set_occluder(occlusion_layer, occluder_polygon);
+
+ Array polygons = p_value;
+ tile_data->set_occluder_polygons_count(occlusion_layer, polygons.size());
+ for (int i = 0; i < polygons.size(); i++) {
+ Ref<OccluderPolygon2D> occluder_polygon = polygons[i];
+ tile_data->set_occluder_polygon(occlusion_layer, i, occluder_polygon);
+ }
polygon_editor->set_background_tile(p_tile_set_atlas_source, p_coords, p_alternative_tile);
}
@@ -1522,7 +1533,11 @@ void TileDataOcclusionShapeEditor::_set_value(TileSetAtlasSource *p_tile_set_atl
Variant TileDataOcclusionShapeEditor::_get_value(TileSetAtlasSource *p_tile_set_atlas_source, Vector2 p_coords, int p_alternative_tile) {
TileData *tile_data = p_tile_set_atlas_source->get_tile_data(p_coords, p_alternative_tile);
ERR_FAIL_NULL_V(tile_data, Variant());
- return tile_data->get_occluder(occlusion_layer);
+ Array polygons;
+ for (int i = 0; i < tile_data->get_occluder_polygons_count(occlusion_layer); i++) {
+ polygons.push_back(tile_data->get_occluder_polygon(occlusion_layer, i));
+ }
+ return polygons;
}
void TileDataOcclusionShapeEditor::_setup_undo_redo_action(TileSetAtlasSource *p_tile_set_atlas_source, const HashMap<TileMapCell, Variant, TileMapCell> &p_previous_values, const Variant &p_new_value) {
@@ -1548,6 +1563,7 @@ void TileDataOcclusionShapeEditor::_notification(int p_what) {
TileDataOcclusionShapeEditor::TileDataOcclusionShapeEditor() {
polygon_editor = memnew(GenericTilePolygonEditor);
+ polygon_editor->set_multiple_polygon_mode(true);
add_child(polygon_editor);
}
diff --git a/editor/plugins/tiles/tile_map_layer_editor.cpp b/editor/plugins/tiles/tile_map_layer_editor.cpp
index 63a54372b5..7e6746dd3c 100644
--- a/editor/plugins/tiles/tile_map_layer_editor.cpp
+++ b/editor/plugins/tiles/tile_map_layer_editor.cpp
@@ -86,8 +86,7 @@ void TileMapLayerEditorTilesPlugin::_update_toolbar() {
transform_toolbar->show();
tools_settings_vsep_2->show();
random_tile_toggle->show();
- scatter_label->show();
- scatter_spinbox->show();
+ scatter_controls_container->set_visible(random_tile_toggle->is_pressed());
} else {
tools_settings_vsep->show();
picker_button->show();
@@ -96,8 +95,7 @@ void TileMapLayerEditorTilesPlugin::_update_toolbar() {
tools_settings_vsep_2->show();
bucket_contiguous_checkbox->show();
random_tile_toggle->show();
- scatter_label->show();
- scatter_spinbox->show();
+ scatter_controls_container->set_visible(random_tile_toggle->is_pressed());
}
}
@@ -2330,7 +2328,7 @@ TileMapLayerEditorTilesPlugin::TileMapLayerEditorTilesPlugin() {
random_tile_toggle->set_theme_type_variation("FlatButton");
random_tile_toggle->set_toggle_mode(true);
random_tile_toggle->set_tooltip_text(TTR("Place Random Tile"));
- random_tile_toggle->connect("toggled", callable_mp(this, &TileMapLayerEditorTilesPlugin::_on_random_tile_checkbox_toggled));
+ random_tile_toggle->connect(SceneStringName(toggled), callable_mp(this, &TileMapLayerEditorTilesPlugin::_on_random_tile_checkbox_toggled));
tools_settings->add_child(random_tile_toggle);
// Random tile scattering.
@@ -4486,7 +4484,7 @@ TileMapLayerEditor::TileMapLayerEditor() {
toggle_highlight_selected_layer_button->set_theme_type_variation("FlatButton");
toggle_highlight_selected_layer_button->set_toggle_mode(true);
toggle_highlight_selected_layer_button->set_pressed(true);
- toggle_highlight_selected_layer_button->connect("toggled", callable_mp(this, &TileMapLayerEditor::_highlight_selected_layer_button_toggled));
+ toggle_highlight_selected_layer_button->connect(SceneStringName(toggled), callable_mp(this, &TileMapLayerEditor::_highlight_selected_layer_button_toggled));
toggle_highlight_selected_layer_button->set_tooltip_text(TTR("Highlight Selected TileMap Layer"));
tile_map_toolbar->add_child(toggle_highlight_selected_layer_button);
@@ -4497,7 +4495,7 @@ TileMapLayerEditor::TileMapLayerEditor() {
toggle_grid_button->set_theme_type_variation("FlatButton");
toggle_grid_button->set_toggle_mode(true);
toggle_grid_button->set_tooltip_text(TTR("Toggle grid visibility."));
- toggle_grid_button->connect("toggled", callable_mp(this, &TileMapLayerEditor::_on_grid_toggled));
+ toggle_grid_button->connect(SceneStringName(toggled), callable_mp(this, &TileMapLayerEditor::_on_grid_toggled));
tile_map_toolbar->add_child(toggle_grid_button);
// Advanced settings menu button.
diff --git a/editor/plugins/tiles/tile_set_atlas_source_editor.cpp b/editor/plugins/tiles/tile_set_atlas_source_editor.cpp
index 224c4e434f..b1417b2878 100644
--- a/editor/plugins/tiles/tile_set_atlas_source_editor.cpp
+++ b/editor/plugins/tiles/tile_set_atlas_source_editor.cpp
@@ -2768,15 +2768,7 @@ void EditorPropertyTilePolygon::_add_focusable_children(Node *p_node) {
void EditorPropertyTilePolygon::_polygons_changed() {
if (String(count_property).is_empty()) {
- if (base_type == "OccluderPolygon2D") {
- // Single OccluderPolygon2D.
- Ref<OccluderPolygon2D> occluder;
- if (generic_tile_polygon_editor->get_polygon_count() >= 1) {
- occluder.instantiate();
- occluder->set_polygon(generic_tile_polygon_editor->get_polygon(0));
- }
- emit_changed(get_edited_property(), occluder);
- } else if (base_type == "NavigationPolygon") {
+ if (base_type == "NavigationPolygon") {
Ref<NavigationPolygon> navigation_polygon;
if (generic_tile_polygon_editor->get_polygon_count() >= 1) {
navigation_polygon.instantiate();
@@ -2798,19 +2790,24 @@ void EditorPropertyTilePolygon::_polygons_changed() {
emit_changed(get_edited_property(), navigation_polygon);
}
} else {
- if (base_type.is_empty()) {
- // Multiple array of vertices.
- Vector<String> changed_properties;
- Array values;
- int count = generic_tile_polygon_editor->get_polygon_count();
- changed_properties.push_back(count_property);
- values.push_back(count);
- for (int i = 0; i < count; i++) {
- changed_properties.push_back(vformat(element_pattern, i));
+ // Multiple array of vertices or OccluderPolygon2D.
+ Vector<String> changed_properties;
+ Array values;
+ int count = generic_tile_polygon_editor->get_polygon_count();
+ changed_properties.push_back(count_property);
+ values.push_back(count);
+ for (int i = 0; i < count; i++) {
+ changed_properties.push_back(vformat(element_pattern, i));
+ if (base_type.is_empty()) {
values.push_back(generic_tile_polygon_editor->get_polygon(i));
+ } else if (base_type == "OccluderPolygon2D") {
+ Ref<OccluderPolygon2D> occluder;
+ occluder.instantiate();
+ occluder->set_polygon(generic_tile_polygon_editor->get_polygon(i));
+ values.push_back(occluder);
}
- emit_signal(SNAME("multiple_properties_changed"), changed_properties, values, false);
}
+ emit_signal(SNAME("multiple_properties_changed"), changed_properties, values, false);
}
}
@@ -2834,15 +2831,8 @@ void EditorPropertyTilePolygon::update_property() {
generic_tile_polygon_editor->clear_polygons();
if (String(count_property).is_empty()) {
- if (base_type == "OccluderPolygon2D") {
- // Single OccluderPolygon2D.
- Ref<OccluderPolygon2D> occluder = get_edited_property_value();
- generic_tile_polygon_editor->clear_polygons();
- if (occluder.is_valid()) {
- generic_tile_polygon_editor->add_polygon(occluder->get_polygon());
- }
- } else if (base_type == "NavigationPolygon") {
- // Single OccluderPolygon2D.
+ if (base_type == "NavigationPolygon") {
+ // Single NavigationPolygon.
Ref<NavigationPolygon> navigation_polygon = get_edited_property_value();
generic_tile_polygon_editor->clear_polygons();
if (navigation_polygon.is_valid()) {
@@ -2859,6 +2849,15 @@ void EditorPropertyTilePolygon::update_property() {
for (int i = 0; i < count; i++) {
generic_tile_polygon_editor->add_polygon(get_edited_object()->get(vformat(element_pattern, i)));
}
+ } else if (base_type == "OccluderPolygon2D") {
+ // Multiple OccluderPolygon2D.
+ generic_tile_polygon_editor->clear_polygons();
+ for (int i = 0; i < count; i++) {
+ Ref<OccluderPolygon2D> occluder = get_edited_object()->get(vformat(element_pattern, i));
+ if (occluder.is_valid()) {
+ generic_tile_polygon_editor->add_polygon(occluder->get_polygon());
+ }
+ }
}
}
}
@@ -2899,16 +2898,30 @@ bool EditorInspectorPluginTileData::can_handle(Object *p_object) {
bool EditorInspectorPluginTileData::parse_property(Object *p_object, const Variant::Type p_type, const String &p_path, const PropertyHint p_hint, const String &p_hint_text, const BitField<PropertyUsageFlags> p_usage, const bool p_wide) {
Vector<String> components = String(p_path).split("/", true, 2);
- if (components.size() == 2 && components[0].begins_with("occlusion_layer_") && components[0].trim_prefix("occlusion_layer_").is_valid_int()) {
+ if (components.size() >= 2 && components[0].begins_with("occlusion_layer_") && components[0].trim_prefix("occlusion_layer_").is_valid_int()) {
// Occlusion layers.
int layer_index = components[0].trim_prefix("occlusion_layer_").to_int();
ERR_FAIL_COND_V(layer_index < 0, false);
- if (components[1] == "polygon") {
+ if (components[1] == "polygons_count") {
EditorPropertyTilePolygon *ep = memnew(EditorPropertyTilePolygon);
- ep->setup_single_mode(p_path, "OccluderPolygon2D");
- add_property_editor(p_path, ep);
+ ep->setup_multiple_mode(vformat("occlusion_layer_%d/polygons", layer_index), vformat("occlusion_layer_%d/polygons_count", layer_index), vformat("occlusion_layer_%d/polygon_%%d/polygon", layer_index), "OccluderPolygon2D");
+ Vector<String> properties;
+ properties.push_back(p_path);
+ int count = p_object->get(vformat("occlusion_layer_%d/polygons_count", layer_index));
+ for (int i = 0; i < count; i++) {
+ properties.push_back(vformat("occlusion_layer_%d/polygon_%d/polygon", layer_index, i));
+ }
+ add_property_editor_for_multiple_properties("Polygons", properties, ep);
return true;
}
+ // We keep the original editor for now, but here is the code that could be used if we need a custom editor for each polygon:
+ /*else if (components.size() == 3 && components[1].begins_with("polygon_") && components[1].trim_prefix("polygon_").is_valid_int()) {
+ int polygon_index = components[1].trim_prefix("polygon_").to_int();
+ ERR_FAIL_COND_V(polygon_index < 0, false);
+ if (components[2] == "polygon") {
+ return true;
+ }
+ }*/
} else if (components.size() >= 2 && components[0].begins_with("physics_layer_") && components[0].trim_prefix("physics_layer_").is_valid_int()) {
// Physics layers.
int layer_index = components[0].trim_prefix("physics_layer_").to_int();
diff --git a/editor/plugins/version_control_editor_plugin.cpp b/editor/plugins/version_control_editor_plugin.cpp
index 071be13692..1df84cb0a7 100644
--- a/editor/plugins/version_control_editor_plugin.cpp
+++ b/editor/plugins/version_control_editor_plugin.cpp
@@ -1006,7 +1006,7 @@ VersionControlEditorPlugin::VersionControlEditorPlugin() {
toggle_vcs_choice = memnew(CheckButton);
toggle_vcs_choice->set_h_size_flags(Control::SIZE_EXPAND_FILL);
toggle_vcs_choice->set_pressed_no_signal(false);
- toggle_vcs_choice->connect(SNAME("toggled"), callable_mp(this, &VersionControlEditorPlugin::_toggle_vcs_integration));
+ toggle_vcs_choice->connect(SceneStringName(toggled), callable_mp(this, &VersionControlEditorPlugin::_toggle_vcs_integration));
toggle_vcs_hbc->add_child(toggle_vcs_choice);
set_up_vbc->add_child(memnew(HSeparator));
diff --git a/editor/plugins/visual_shader_editor_plugin.cpp b/editor/plugins/visual_shader_editor_plugin.cpp
index aaa65a0fc4..bf8dab92f8 100644
--- a/editor/plugins/visual_shader_editor_plugin.cpp
+++ b/editor/plugins/visual_shader_editor_plugin.cpp
@@ -2201,7 +2201,7 @@ void VisualShaderEditor::_update_options_menu() {
if (input.is_valid()) {
input->set_shader_mode(visual_shader->get_mode());
input->set_shader_type(visual_shader->get_shader_type());
- if (!add_options[i].ops.is_empty() && add_options[i].ops[0].get_type() == Variant::STRING) {
+ if (!add_options[i].ops.is_empty() && add_options[i].ops[0].is_string()) {
input->set_input_name((String)add_options[i].ops[0]);
}
}
@@ -2995,8 +2995,8 @@ void VisualShaderEditor::_frame_title_popup_show(const Point2 &p_position, int p
}
frame_title_change_edit->set_text(node->get_title());
frame_title_change_popup->set_meta("id", p_node_id);
- frame_title_change_popup->popup();
frame_title_change_popup->set_position(p_position);
+ frame_title_change_popup->popup();
// Select current text.
frame_title_change_edit->grab_focus();
@@ -3281,7 +3281,7 @@ void VisualShaderEditor::_setup_node(VisualShaderNode *p_node, const Vector<Vari
VisualShaderNodeInput *input = Object::cast_to<VisualShaderNodeInput>(p_node);
if (input) {
- ERR_FAIL_COND(p_ops[0].get_type() != Variant::STRING);
+ ERR_FAIL_COND(!p_ops[0].is_string());
input->set_input_name((String)p_ops[0]);
return;
}
@@ -3667,6 +3667,7 @@ void VisualShaderEditor::_add_node(int p_idx, const Vector<Variant> &p_ops, cons
bool is_curve = (Object::cast_to<VisualShaderNodeCurveTexture>(vsnode.ptr()) != nullptr);
bool is_curve_xyz = (Object::cast_to<VisualShaderNodeCurveXYZTexture>(vsnode.ptr()) != nullptr);
bool is_parameter = (Object::cast_to<VisualShaderNodeParameter>(vsnode.ptr()) != nullptr);
+ bool is_mesh_emitter = (Object::cast_to<VisualShaderNodeParticleMeshEmitter>(vsnode.ptr()) != nullptr);
Point2 position = graph->get_scroll_offset();
@@ -3879,6 +3880,12 @@ void VisualShaderEditor::_add_node(int p_idx, const Vector<Variant> &p_ops, cons
if (is_texture2d_array) {
undo_redo->force_fixed_history();
undo_redo->add_do_method(vsnode.ptr(), "set_texture_array", ResourceLoader::load(p_resource_path));
+ return;
+ }
+
+ if (is_mesh_emitter) {
+ undo_redo->add_do_method(vsnode.ptr(), "set_mesh", ResourceLoader::load(p_resource_path));
+ return;
}
}
}
@@ -6062,6 +6069,11 @@ void VisualShaderEditor::drop_data_fw(const Point2 &p_point, const Variant &p_da
saved_node_pos = p_point + Vector2(0, i * 250 * EDSCALE);
saved_node_pos_dirty = true;
_add_node(cubemap_node_option_idx, {}, arr[i], i);
+ } else if (type == "Mesh" && visual_shader->get_mode() == Shader::MODE_PARTICLES &&
+ (visual_shader->get_shader_type() == VisualShader::TYPE_START || visual_shader->get_shader_type() == VisualShader::TYPE_START_CUSTOM)) {
+ saved_node_pos = p_point + Vector2(0, i * 250 * EDSCALE);
+ saved_node_pos_dirty = true;
+ _add_node(mesh_emitter_option_idx, {}, arr[i], i);
}
}
}
@@ -6370,7 +6382,7 @@ VisualShaderEditor::VisualShaderEditor() {
custom_mode_box->set_text(TTR("Custom"));
custom_mode_box->set_pressed(false);
custom_mode_box->set_visible(false);
- custom_mode_box->connect("toggled", callable_mp(this, &VisualShaderEditor::_custom_mode_toggled));
+ custom_mode_box->connect(SceneStringName(toggled), callable_mp(this, &VisualShaderEditor::_custom_mode_toggled));
edit_type_standard = memnew(OptionButton);
edit_type_standard->add_item(TTR("Vertex"));
@@ -7013,7 +7025,10 @@ VisualShaderEditor::VisualShaderEditor() {
add_options.push_back(AddOption("MultiplyByAxisAngle (*)", "Particles/Transform", "VisualShaderNodeParticleMultiplyByAxisAngle", TTR("A node for help to multiply a position input vector by rotation using specific axis. Intended to work with emitters."), {}, VisualShaderNode::PORT_TYPE_VECTOR_3D, TYPE_FLAGS_EMIT | TYPE_FLAGS_PROCESS | TYPE_FLAGS_COLLIDE, Shader::MODE_PARTICLES));
add_options.push_back(AddOption("BoxEmitter", "Particles/Emitters", "VisualShaderNodeParticleBoxEmitter", "", {}, VisualShaderNode::PORT_TYPE_VECTOR_3D, TYPE_FLAGS_EMIT, Shader::MODE_PARTICLES));
+
+ mesh_emitter_option_idx = add_options.size();
add_options.push_back(AddOption("MeshEmitter", "Particles/Emitters", "VisualShaderNodeParticleMeshEmitter", "", {}, VisualShaderNode::PORT_TYPE_VECTOR_3D, TYPE_FLAGS_EMIT, Shader::MODE_PARTICLES));
+
add_options.push_back(AddOption("RingEmitter", "Particles/Emitters", "VisualShaderNodeParticleRingEmitter", "", {}, VisualShaderNode::PORT_TYPE_VECTOR_3D, TYPE_FLAGS_EMIT, Shader::MODE_PARTICLES));
add_options.push_back(AddOption("SphereEmitter", "Particles/Emitters", "VisualShaderNodeParticleSphereEmitter", "", {}, VisualShaderNode::PORT_TYPE_VECTOR_3D, TYPE_FLAGS_EMIT, Shader::MODE_PARTICLES));
diff --git a/editor/plugins/visual_shader_editor_plugin.h b/editor/plugins/visual_shader_editor_plugin.h
index b17036e39f..69b2f30c40 100644
--- a/editor/plugins/visual_shader_editor_plugin.h
+++ b/editor/plugins/visual_shader_editor_plugin.h
@@ -414,6 +414,7 @@ class VisualShaderEditor : public ShaderEditor {
int custom_node_option_idx;
int curve_node_option_idx;
int curve_xyz_node_option_idx;
+ int mesh_emitter_option_idx;
List<String> keyword_list;
List<VisualShaderNodeParameterRef> uniform_refs;
diff --git a/editor/progress_dialog.cpp b/editor/progress_dialog.cpp
index c21723d1ba..2f345e5161 100644
--- a/editor/progress_dialog.cpp
+++ b/editor/progress_dialog.cpp
@@ -202,14 +202,13 @@ void ProgressDialog::add_task(const String &p_task, const String &p_label, int p
bool ProgressDialog::task_step(const String &p_task, const String &p_state, int p_step, bool p_force_redraw) {
ERR_FAIL_COND_V(!tasks.has(p_task), canceled);
+ Task &t = tasks[p_task];
if (!p_force_redraw) {
uint64_t tus = OS::get_singleton()->get_ticks_usec();
- if (tus - last_progress_tick < 200000) { //200ms
+ if (tus - t.last_progress_tick < 200000) { //200ms
return canceled;
}
}
-
- Task &t = tasks[p_task];
if (p_step < 0) {
t.progress->set_value(t.progress->get_value() + 1);
} else {
@@ -217,7 +216,7 @@ bool ProgressDialog::task_step(const String &p_task, const String &p_state, int
}
t.state->set_text(p_state);
- last_progress_tick = OS::get_singleton()->get_ticks_usec();
+ t.last_progress_tick = OS::get_singleton()->get_ticks_usec();
_update_ui();
return canceled;
@@ -252,7 +251,6 @@ ProgressDialog::ProgressDialog() {
main->set_anchors_and_offsets_preset(Control::PRESET_FULL_RECT);
set_exclusive(true);
set_flag(Window::FLAG_POPUP, false);
- last_progress_tick = 0;
singleton = this;
cancel_hb = memnew(HBoxContainer);
main->add_child(cancel_hb);
diff --git a/editor/progress_dialog.h b/editor/progress_dialog.h
index 82d59219da..355812b0b7 100644
--- a/editor/progress_dialog.h
+++ b/editor/progress_dialog.h
@@ -71,13 +71,13 @@ class ProgressDialog : public PopupPanel {
VBoxContainer *vb = nullptr;
ProgressBar *progress = nullptr;
Label *state = nullptr;
+ uint64_t last_progress_tick = 0;
};
HBoxContainer *cancel_hb = nullptr;
Button *cancel = nullptr;
HashMap<String, Task> tasks;
VBoxContainer *main = nullptr;
- uint64_t last_progress_tick;
LocalVector<Window *> host_windows;
diff --git a/editor/project_manager/project_dialog.cpp b/editor/project_manager/project_dialog.cpp
index 28d2362c9c..9b009c0a89 100644
--- a/editor/project_manager/project_dialog.cpp
+++ b/editor/project_manager/project_dialog.cpp
@@ -857,7 +857,7 @@ ProjectDialog::ProjectDialog() {
create_dir->set_text(TTR("Create Folder"));
create_dir->set_pressed(true);
pphb_label->add_child(create_dir);
- create_dir->connect("toggled", callable_mp(this, &ProjectDialog::_create_dir_toggled));
+ create_dir->connect(SceneStringName(toggled), callable_mp(this, &ProjectDialog::_create_dir_toggled));
HBoxContainer *pphb = memnew(HBoxContainer);
project_path_container->add_child(pphb);
diff --git a/editor/project_settings_editor.cpp b/editor/project_settings_editor.cpp
index 373895b653..489fbb037f 100644
--- a/editor/project_settings_editor.cpp
+++ b/editor/project_settings_editor.cpp
@@ -652,7 +652,7 @@ ProjectSettingsEditor::ProjectSettingsEditor(EditorData *p_data) {
advanced = memnew(CheckButton);
advanced->set_text(TTR("Advanced Settings"));
- advanced->connect("toggled", callable_mp(this, &ProjectSettingsEditor::_advanced_toggled));
+ advanced->connect(SceneStringName(toggled), callable_mp(this, &ProjectSettingsEditor::_advanced_toggled));
search_bar->add_child(advanced);
custom_properties = memnew(HBoxContainer);
diff --git a/editor/property_selector.cpp b/editor/property_selector.cpp
index d47270841d..8f609850b8 100644
--- a/editor/property_selector.cpp
+++ b/editor/property_selector.cpp
@@ -162,6 +162,9 @@ void PropertySelector::_update_search() {
if (!found && !search_box->get_text().is_empty() && E.name.containsn(search_text)) {
item->select(0);
found = true;
+ } else if (!found && search_box->get_text().is_empty() && E.name == selected) {
+ item->select(0);
+ found = true;
}
item->set_selectable(0, true);
@@ -173,6 +176,12 @@ void PropertySelector::_update_search() {
if (category && category->get_first_child() == nullptr) {
memdelete(category); //old category was unused
}
+
+ if (found) {
+ // As we call this while adding items, defer until list is completely populated.
+ callable_mp(search_options, &Tree::scroll_to_item).call_deferred(search_options->get_selected(), true);
+ }
+
} else {
List<MethodInfo> methods;
@@ -305,12 +314,20 @@ void PropertySelector::_update_search() {
if (!found && !search_box->get_text().is_empty() && name.containsn(search_text)) {
item->select(0);
found = true;
+ } else if (!found && search_box->get_text().is_empty() && name == selected) {
+ item->select(0);
+ found = true;
}
}
if (category && category->get_first_child() == nullptr) {
memdelete(category); //old category was unused
}
+
+ if (found) {
+ // As we call this while adding items, defer until list is completely populated.
+ callable_mp(search_options, &Tree::scroll_to_item).call_deferred(search_options->get_selected(), true);
+ }
}
get_ok_button()->set_disabled(root->get_first_child() == nullptr);
diff --git a/editor/register_editor_types.cpp b/editor/register_editor_types.cpp
index 610ad3efdf..00377a0dd2 100644
--- a/editor/register_editor_types.cpp
+++ b/editor/register_editor_types.cpp
@@ -46,6 +46,7 @@
#include "editor/editor_translation_parser.h"
#include "editor/editor_undo_redo_manager.h"
#include "editor/export/editor_export_platform.h"
+#include "editor/export/editor_export_platform_extension.h"
#include "editor/export/editor_export_platform_pc.h"
#include "editor/export/editor_export_plugin.h"
#include "editor/filesystem_dock.h"
@@ -78,6 +79,7 @@
#include "editor/plugins/cpu_particles_2d_editor_plugin.h"
#include "editor/plugins/cpu_particles_3d_editor_plugin.h"
#include "editor/plugins/curve_editor_plugin.h"
+#include "editor/plugins/editor_context_menu_plugin.h"
#include "editor/plugins/editor_debugger_plugin.h"
#include "editor/plugins/editor_resource_tooltip_plugins.h"
#include "editor/plugins/font_config_plugin.h"
@@ -161,6 +163,8 @@ void register_editor_types() {
GDREGISTER_CLASS(EditorExportPlugin);
GDREGISTER_ABSTRACT_CLASS(EditorExportPlatform);
GDREGISTER_ABSTRACT_CLASS(EditorExportPlatformPC);
+ GDREGISTER_CLASS(EditorExportPlatformExtension);
+ GDREGISTER_ABSTRACT_CLASS(EditorExportPreset);
register_exporter_types();
@@ -176,6 +180,7 @@ void register_editor_types() {
GDREGISTER_CLASS(EditorResourcePicker);
GDREGISTER_CLASS(EditorScriptPicker);
GDREGISTER_ABSTRACT_CLASS(EditorUndoRedoManager);
+ GDREGISTER_CLASS(EditorContextMenuPlugin);
GDREGISTER_ABSTRACT_CLASS(FileSystemDock);
GDREGISTER_VIRTUAL_CLASS(EditorFileSystemImportFormatSupportQuery);
diff --git a/editor/rename_dialog.cpp b/editor/rename_dialog.cpp
index 71d2ccf124..2ef7de1538 100644
--- a/editor/rename_dialog.cpp
+++ b/editor/rename_dialog.cpp
@@ -304,7 +304,7 @@ RenameDialog::RenameDialog(SceneTreeEditor *p_scene_tree_editor) {
// ---- Connections
- cbut_collapse_features->connect("toggled", callable_mp(this, &RenameDialog::_features_toggled));
+ cbut_collapse_features->connect(SceneStringName(toggled), callable_mp(this, &RenameDialog::_features_toggled));
// Substitute Buttons
diff --git a/editor/run_instances_dialog.cpp b/editor/run_instances_dialog.cpp
index d617c899ad..bb32748653 100644
--- a/editor/run_instances_dialog.cpp
+++ b/editor/run_instances_dialog.cpp
@@ -304,7 +304,7 @@ RunInstancesDialog::RunInstancesDialog() {
args_gc->add_child(instance_count);
instance_count->connect(SceneStringName(value_changed), callable_mp(this, &RunInstancesDialog::_start_instance_timer).unbind(1));
instance_count->connect(SceneStringName(value_changed), callable_mp(this, &RunInstancesDialog::_refresh_argument_count).unbind(1));
- enable_multiple_instances_checkbox->connect("toggled", callable_mp(instance_count, &SpinBox::set_editable));
+ enable_multiple_instances_checkbox->connect(SceneStringName(toggled), callable_mp(instance_count, &SpinBox::set_editable));
instance_count->set_editable(enable_multiple_instances_checkbox->is_pressed());
main_args_edit = memnew(LineEdit);
diff --git a/editor/scene_tree_dock.cpp b/editor/scene_tree_dock.cpp
index f0ad4c4b5e..2a39b11815 100644
--- a/editor/scene_tree_dock.cpp
+++ b/editor/scene_tree_dock.cpp
@@ -50,6 +50,7 @@
#include "editor/node_dock.h"
#include "editor/plugins/animation_player_editor_plugin.h"
#include "editor/plugins/canvas_item_editor_plugin.h"
+#include "editor/plugins/editor_context_menu_plugin.h"
#include "editor/plugins/node_3d_editor_plugin.h"
#include "editor/plugins/script_editor_plugin.h"
#include "editor/reparent_dialog.h"
@@ -213,6 +214,10 @@ void SceneTreeDock::shortcut_input(const Ref<InputEvent> &p_event) {
} else if (ED_IS_SHORTCUT("scene_tree/delete", p_event)) {
_tool_selected(TOOL_ERASE);
} else {
+ int match_option = EditorNode::get_editor_data().match_context_menu_shortcut(EditorData::CONTEXT_SLOT_SCENE_TREE, p_event);
+ if (match_option) {
+ _tool_selected(match_option);
+ }
return;
}
@@ -1486,6 +1491,13 @@ void SceneTreeDock::_tool_selected(int p_tool, bool p_confirm_override) {
} break;
default: {
+ // Editor context plugin.
+ if (p_tool >= EditorData::CONTEXT_MENU_ITEM_ID_BASE) {
+ List<Node *> selection = editor_selection->get_selected_node_list();
+ EditorNode::get_editor_data().scene_tree_options_pressed(EditorData::CONTEXT_SLOT_SCENE_TREE, p_tool, selection);
+ break;
+ }
+
_filter_option_selected(p_tool);
if (p_tool >= EDIT_SUBRESOURCE_BASE) {
@@ -2239,8 +2251,7 @@ void SceneTreeDock::_node_reparent(NodePath p_path, bool p_keep_global_xform) {
}
void SceneTreeDock::_do_reparent(Node *p_new_parent, int p_position_in_parent, Vector<Node *> p_nodes, bool p_keep_global_xform) {
- Node *new_parent = p_new_parent;
- ERR_FAIL_NULL(new_parent);
+ ERR_FAIL_NULL(p_new_parent);
if (p_nodes.size() == 0) {
return; // Nothing to reparent.
@@ -2280,7 +2291,7 @@ void SceneTreeDock::_do_reparent(Node *p_new_parent, int p_position_in_parent, V
// Prevent selecting the hovered node and keep the reparented node(s) selected instead.
hovered_but_reparenting = true;
- Node *validate = new_parent;
+ Node *validate = p_new_parent;
while (validate) {
ERR_FAIL_COND_MSG(p_nodes.has(validate), "Selection changed at some point. Can't reparent.");
validate = validate->get_parent();
@@ -2302,7 +2313,7 @@ void SceneTreeDock::_do_reparent(Node *p_new_parent, int p_position_in_parent, V
// No undo implemented for this yet.
Node *node = p_nodes[ni];
- fill_path_renames(node, new_parent, &path_renames);
+ fill_path_renames(node, p_new_parent, &path_renames);
former_names.push_back(node->get_name());
List<Node *> owned;
@@ -2312,7 +2323,7 @@ void SceneTreeDock::_do_reparent(Node *p_new_parent, int p_position_in_parent, V
owners.push_back(E);
}
- bool same_parent = new_parent == node->get_parent();
+ bool same_parent = p_new_parent == node->get_parent();
if (same_parent && node->get_index(false) < p_position_in_parent + ni) {
inc--; // If the child will generate a gap when moved, adjust.
}
@@ -2323,17 +2334,17 @@ void SceneTreeDock::_do_reparent(Node *p_new_parent, int p_position_in_parent, V
need_edit = select_node_hovered_at_end_of_drag;
} else {
undo_redo->add_do_method(node->get_parent(), "remove_child", node);
- undo_redo->add_do_method(new_parent, "add_child", node, true);
+ undo_redo->add_do_method(p_new_parent, "add_child", node, true);
}
int new_position_in_parent = p_position_in_parent == -1 ? -1 : p_position_in_parent + inc;
if (new_position_in_parent >= 0 || same_parent) {
- undo_redo->add_do_method(new_parent, "move_child", node, new_position_in_parent);
+ undo_redo->add_do_method(p_new_parent, "move_child", node, new_position_in_parent);
}
EditorDebuggerNode *ed = EditorDebuggerNode::get_singleton();
String old_name = former_names[ni];
- String new_name = new_parent->validate_child_name(node);
+ String new_name = p_new_parent->validate_child_name(node);
// Name was modified, fix the path renames.
if (old_name.casecmp_to(new_name) != 0) {
@@ -2358,8 +2369,12 @@ void SceneTreeDock::_do_reparent(Node *p_new_parent, int p_position_in_parent, V
}
}
- undo_redo->add_do_method(ed, "live_debug_reparent_node", edited_scene->get_path_to(node), edited_scene->get_path_to(new_parent), new_name, new_position_in_parent);
- undo_redo->add_undo_method(ed, "live_debug_reparent_node", NodePath(String(edited_scene->get_path_to(new_parent)).path_join(new_name)), edited_scene->get_path_to(node->get_parent()), node->get_name(), node->get_index(false));
+ // FIXME: Live editing for "Reparent to New Node" option is broken.
+ // We must get the path to `p_new_parent` *after* it was added to the scene.
+ if (p_new_parent->is_inside_tree()) {
+ undo_redo->add_do_method(ed, "live_debug_reparent_node", edited_scene->get_path_to(node), edited_scene->get_path_to(p_new_parent), new_name, new_position_in_parent);
+ undo_redo->add_undo_method(ed, "live_debug_reparent_node", NodePath(String(edited_scene->get_path_to(p_new_parent)).path_join(new_name)), edited_scene->get_path_to(node->get_parent()), node->get_name(), node->get_index(false));
+ }
if (p_keep_global_xform) {
if (Object::cast_to<Node2D>(node)) {
@@ -2379,7 +2394,7 @@ void SceneTreeDock::_do_reparent(Node *p_new_parent, int p_position_in_parent, V
undo_redo->add_do_method(AnimationPlayerEditor::get_singleton()->get_track_editor(), "set_root", node);
}
- undo_redo->add_undo_method(new_parent, "remove_child", node);
+ undo_redo->add_undo_method(p_new_parent, "remove_child", node);
undo_redo->add_undo_method(node, "set_name", former_names[ni]);
inc++;
@@ -2397,7 +2412,7 @@ void SceneTreeDock::_do_reparent(Node *p_new_parent, int p_position_in_parent, V
}
int child_pos = node->get_index(false);
- bool reparented_to_container = Object::cast_to<Container>(new_parent) && Object::cast_to<Control>(node);
+ bool reparented_to_container = Object::cast_to<Container>(p_new_parent) && Object::cast_to<Control>(node);
undo_redo->add_undo_method(node->get_parent(), "add_child", node, true);
undo_redo->add_undo_method(node->get_parent(), "move_child", node, child_pos);
@@ -2654,6 +2669,13 @@ void SceneTreeDock::_delete_confirm(bool p_cut) {
}
}
+ if (!entire_scene) {
+ for (const Node *E : remove_list) {
+ // `move_child` + `get_index` doesn't really work for internal nodes.
+ ERR_FAIL_COND_MSG(E->get_internal_mode() != INTERNAL_MODE_DISABLED, "Trying to remove internal node, this is not supported.");
+ }
+ }
+
EditorNode::get_singleton()->hide_unused_editors(this);
EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
@@ -2666,10 +2688,6 @@ void SceneTreeDock::_delete_confirm(bool p_cut) {
undo_redo->add_undo_method(scene_tree, "update_tree");
undo_redo->add_undo_reference(edited_scene);
} else {
- for (const Node *E : remove_list) {
- // `move_child` + `get_index` doesn't really work for internal nodes.
- ERR_FAIL_COND_MSG(E->get_internal_mode() != INTERNAL_MODE_DISABLED, "Trying to remove internal node, this is not supported.");
- }
if (delete_tracks_checkbox->is_pressed() || p_cut) {
remove_list.sort_custom<Node::Comparator>(); // Sort nodes to keep positions.
HashMap<Node *, NodePath> path_renames;
@@ -2774,10 +2792,10 @@ void SceneTreeDock::_selection_changed() {
_update_script_button();
}
-void SceneTreeDock::_do_create(Node *p_parent) {
+Node *SceneTreeDock::_do_create(Node *p_parent) {
Variant c = create_dialog->instantiate_selected();
Node *child = Object::cast_to<Node>(c);
- ERR_FAIL_NULL(child);
+ ERR_FAIL_NULL_V(child, nullptr);
String new_name = p_parent->validate_child_name(child);
if (GLOBAL_GET("editor/naming/node_name_casing").operator int() != NAME_CASING_PASCAL_CASE) {
@@ -2791,8 +2809,6 @@ void SceneTreeDock::_do_create(Node *p_parent) {
if (edited_scene) {
undo_redo->add_do_method(p_parent, "add_child", child, true);
undo_redo->add_do_method(child, "set_owner", edited_scene);
- undo_redo->add_do_method(editor_selection, "clear");
- undo_redo->add_do_method(editor_selection, "add_node", child);
undo_redo->add_do_reference(child);
undo_redo->add_undo_method(p_parent, "remove_child", child);
@@ -2807,28 +2823,34 @@ void SceneTreeDock::_do_create(Node *p_parent) {
undo_redo->add_undo_method(EditorNode::get_singleton(), "set_edited_scene", (Object *)nullptr);
}
+ undo_redo->add_do_method(this, "_post_do_create", child);
undo_redo->commit_action();
- _push_item(c);
+
+ return child;
+}
+
+void SceneTreeDock::_post_do_create(Node *p_child) {
editor_selection->clear();
- editor_selection->add_node(child);
- if (Object::cast_to<Control>(c)) {
- //make editor more comfortable, so some controls don't appear super shrunk
- Control *ct = Object::cast_to<Control>(c);
+ editor_selection->add_node(p_child);
+ _push_item(p_child);
- Size2 ms = ct->get_minimum_size();
+ // Make editor more comfortable, so some controls don't appear super shrunk.
+ Control *control = Object::cast_to<Control>(p_child);
+ if (control) {
+ Size2 ms = control->get_minimum_size();
if (ms.width < 4) {
ms.width = 40;
}
if (ms.height < 4) {
ms.height = 40;
}
- if (ct->is_layout_rtl()) {
- ct->set_position(ct->get_position() - Vector2(ms.x, 0));
+ if (control->is_layout_rtl()) {
+ control->set_position(control->get_position() - Vector2(ms.x, 0));
}
- ct->set_size(ms);
+ control->set_size(ms);
}
- emit_signal(SNAME("node_created"), c);
+ emit_signal(SNAME("node_created"), p_child);
}
void SceneTreeDock::_create() {
@@ -2911,22 +2933,24 @@ void SceneTreeDock::_create() {
}
Node *parent = nullptr;
+ int original_position = -1;
if (only_one_top_node) {
parent = top_node->get_parent();
+ original_position = top_node->get_index(false);
} else {
parent = top_node->get_parent()->get_parent();
}
- _do_create(parent);
+ EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
+ undo_redo->create_action_for_history(TTR("Reparent to New Node"), editor_data->get_current_edited_scene_history_id());
+
+ Node *last_created = _do_create(parent);
Vector<Node *> nodes;
for (Node *E : selection) {
nodes.push_back(E);
}
- // This works because editor_selection was cleared and populated with last created node in _do_create()
- Node *last_created = editor_selection->get_selected_node_list().front()->get();
-
if (center_parent) {
// Find parent type and only average positions of relevant nodes.
Node3D *parent_node_3d = Object::cast_to<Node3D>(last_created);
@@ -2965,6 +2989,11 @@ void SceneTreeDock::_create() {
}
_do_reparent(last_created, -1, nodes, true);
+
+ if (only_one_top_node) {
+ undo_redo->add_do_method(parent, "move_child", last_created, original_position);
+ }
+ undo_redo->commit_action();
}
scene_tree->get_scene_tree()->grab_focus();
@@ -3683,7 +3712,7 @@ void SceneTreeDock::_tree_rmb(const Vector2 &p_menu_pos) {
if (selection.size() == 1) {
bool is_external = (!selection.front()->get()->get_scene_file_path().is_empty());
if (is_external) {
- bool is_inherited = selection.front()->get()->get_scene_inherited_state() != nullptr;
+ bool is_inherited = selection.front()->get()->get_scene_inherited_state().is_valid();
bool is_top_level = selection.front()->get()->get_owner() == nullptr;
if (is_inherited && is_top_level) {
menu->add_separator();
@@ -3730,6 +3759,15 @@ void SceneTreeDock::_tree_rmb(const Vector2 &p_menu_pos) {
menu->add_separator();
menu->add_icon_shortcut(get_editor_theme_icon(SNAME("Remove")), ED_GET_SHORTCUT("scene_tree/delete"), TOOL_ERASE);
}
+
+ Vector<String> p_paths;
+ Node *root = EditorNode::get_singleton()->get_edited_scene();
+ for (List<Node *>::Element *E = selection.front(); E; E = E->next()) {
+ String node_path = root->get_path().rel_path_to(E->get()->get_path());
+ p_paths.push_back(node_path);
+ }
+ EditorNode::get_editor_data().add_options_from_plugins(menu, EditorData::CONTEXT_SLOT_SCENE_TREE, p_paths);
+
menu->reset_size();
menu->set_position(p_menu_pos);
menu->popup();
@@ -4370,6 +4408,7 @@ void SceneTreeDock::_edit_subresource(int p_idx, const PopupMenu *p_from_menu) {
}
void SceneTreeDock::_bind_methods() {
+ ClassDB::bind_method(D_METHOD("_post_do_create"), &SceneTreeDock::_post_do_create);
ClassDB::bind_method(D_METHOD("_set_owners"), &SceneTreeDock::_set_owners);
ClassDB::bind_method(D_METHOD("_reparent_nodes_to_root"), &SceneTreeDock::_reparent_nodes_to_root);
ClassDB::bind_method(D_METHOD("_reparent_nodes_to_paths_with_transform_and_name"), &SceneTreeDock::_reparent_nodes_to_paths_with_transform_and_name);
@@ -4635,6 +4674,7 @@ SceneTreeDock::SceneTreeDock(Node *p_scene_root, EditorSelection *p_editor_selec
EDITOR_DEF("interface/editors/show_scene_tree_root_selection", true);
EDITOR_DEF("interface/editors/derive_script_globals_by_name", true);
EDITOR_DEF("docks/scene_tree/ask_before_deleting_related_animation_tracks", true);
+ EDITOR_DEF("docks/scene_tree/ask_before_revoking_unique_name", true);
EDITOR_DEF("_use_favorites_root_selection", false);
Resource::_update_configuration_warning = _update_configuration_warning;
diff --git a/editor/scene_tree_dock.h b/editor/scene_tree_dock.h
index f01e6598cf..1807ec5896 100644
--- a/editor/scene_tree_dock.h
+++ b/editor/scene_tree_dock.h
@@ -179,7 +179,8 @@ class SceneTreeDock : public VBoxContainer {
bool first_enter = true;
void _create();
- void _do_create(Node *p_parent);
+ Node *_do_create(Node *p_parent);
+ void _post_do_create(Node *p_child);
Node *scene_root = nullptr;
Node *edited_scene = nullptr;
Node *pending_click_select = nullptr;
diff --git a/editor/script_create_dialog.cpp b/editor/script_create_dialog.cpp
index f8673ddd44..0cb4952b04 100644
--- a/editor/script_create_dialog.cpp
+++ b/editor/script_create_dialog.cpp
@@ -111,15 +111,7 @@ static Vector<String> _get_hierarchy(const String &p_class_name) {
void ScriptCreateDialog::_notification(int p_what) {
switch (p_what) {
- case NOTIFICATION_ENTER_TREE:
- case NOTIFICATION_THEME_CHANGED: {
- for (int i = 0; i < ScriptServer::get_language_count(); i++) {
- Ref<Texture2D> language_icon = get_editor_theme_icon(ScriptServer::get_language(i)->get_type());
- if (language_icon.is_valid()) {
- language_menu->set_item_icon(i, language_icon);
- }
- }
-
+ case NOTIFICATION_ENTER_TREE: {
String last_language = EditorSettings::get_singleton()->get_project_metadata("script_setup", "last_selected_language", "");
if (!last_language.is_empty()) {
for (int i = 0; i < language_menu->get_item_count(); i++) {
@@ -131,9 +123,16 @@ void ScriptCreateDialog::_notification(int p_what) {
} else {
language_menu->select(default_language);
}
- if (EditorSettings::get_singleton()->has_meta("script_setup_use_script_templates")) {
- is_using_templates = bool(EditorSettings::get_singleton()->get_meta("script_setup_use_script_templates"));
- use_templates->set_pressed(is_using_templates);
+ is_using_templates = EDITOR_DEF("_script_setup_use_script_templates", false);
+ use_templates->set_pressed(is_using_templates);
+ } break;
+
+ case NOTIFICATION_THEME_CHANGED: {
+ for (int i = 0; i < ScriptServer::get_language_count(); i++) {
+ Ref<Texture2D> language_icon = get_editor_theme_icon(ScriptServer::get_language(i)->get_type());
+ if (language_icon.is_valid()) {
+ language_menu->set_item_icon(i, language_icon);
+ }
}
path_button->set_icon(get_editor_theme_icon(SNAME("Folder")));
@@ -297,12 +296,9 @@ void ScriptCreateDialog::_template_changed(int p_template) {
EditorSettings::get_singleton()->set_project_metadata("script_setup", "templates_dictionary", dic_templates_project);
} else {
// Save template info to editor dictionary (not a project template).
- Dictionary dic_templates;
- if (EditorSettings::get_singleton()->has_meta("script_setup_templates_dictionary")) {
- dic_templates = (Dictionary)EditorSettings::get_singleton()->get_meta("script_setup_templates_dictionary");
- }
+ Dictionary dic_templates = EDITOR_GET("_script_setup_templates_dictionary");
dic_templates[parent_name->get_text()] = sinfo.get_hash();
- EditorSettings::get_singleton()->set_meta("script_setup_templates_dictionary", dic_templates);
+ EditorSettings::get_singleton()->set("_script_setup_templates_dictionary", dic_templates);
// Remove template from project dictionary as we last used an editor level template.
Dictionary dic_templates_project = EditorSettings::get_singleton()->get_project_metadata("script_setup", "templates_dictionary", Dictionary());
if (dic_templates_project.has(parent_name->get_text())) {
@@ -415,7 +411,7 @@ void ScriptCreateDialog::_built_in_pressed() {
void ScriptCreateDialog::_use_template_pressed() {
is_using_templates = use_templates->is_pressed();
- EditorSettings::get_singleton()->set_meta("script_setup_use_script_templates", is_using_templates);
+ EditorSettings::get_singleton()->set("_script_setup_use_script_templates", is_using_templates);
validation_panel->update();
}
@@ -509,10 +505,7 @@ void ScriptCreateDialog::_update_template_menu() {
if (is_language_using_templates) {
// Get the latest templates used for each type of node from project settings then global settings.
Dictionary last_local_templates = EditorSettings::get_singleton()->get_project_metadata("script_setup", "templates_dictionary", Dictionary());
- Dictionary last_global_templates;
- if (EditorSettings::get_singleton()->has_meta("script_setup_templates_dictionary")) {
- last_global_templates = (Dictionary)EditorSettings::get_singleton()->get_meta("script_setup_templates_dictionary");
- }
+ Dictionary last_global_templates = EDITOR_GET("_script_setup_templates_dictionary");
String inherits_base_type = parent_name->get_text();
// If it inherits from a script, get its parent class first.
@@ -825,6 +818,8 @@ void ScriptCreateDialog::_bind_methods() {
}
ScriptCreateDialog::ScriptCreateDialog() {
+ EDITOR_DEF("_script_setup_templates_dictionary", Dictionary());
+
/* Main Controls */
GridContainer *gc = memnew(GridContainer);
diff --git a/editor/shader_create_dialog.cpp b/editor/shader_create_dialog.cpp
index fd9d5bc127..846e8867a1 100644
--- a/editor/shader_create_dialog.cpp
+++ b/editor/shader_create_dialog.cpp
@@ -629,7 +629,7 @@ ShaderCreateDialog::ShaderCreateDialog() {
internal = memnew(CheckBox);
internal->set_text(TTR("On"));
- internal->connect("toggled", callable_mp(this, &ShaderCreateDialog::_built_in_toggled));
+ internal->connect(SceneStringName(toggled), callable_mp(this, &ShaderCreateDialog::_built_in_toggled));
gc->add_child(memnew(Label(TTR("Built-in Shader:"))));
gc->add_child(internal);
diff --git a/editor/themes/editor_theme_manager.cpp b/editor/themes/editor_theme_manager.cpp
index f654707630..3041857d83 100644
--- a/editor/themes/editor_theme_manager.cpp
+++ b/editor/themes/editor_theme_manager.cpp
@@ -183,7 +183,7 @@ Ref<EditorTheme> EditorThemeManager::_create_base_theme(const Ref<EditorTheme> &
// If settings are comparable to the old theme, then just copy existing icons over.
// Otherwise, regenerate them.
- bool keep_old_icons = (p_old_theme != nullptr && theme->get_generated_icons_hash() == p_old_theme->get_generated_icons_hash());
+ bool keep_old_icons = (p_old_theme.is_valid() && theme->get_generated_icons_hash() == p_old_theme->get_generated_icons_hash());
if (keep_old_icons) {
print_verbose("EditorTheme: Can keep old icons, copying.");
editor_copy_icons(theme, p_old_theme);
@@ -865,7 +865,6 @@ void EditorThemeManager::_populate_standard_styles(const Ref<EditorTheme> &p_the
// CheckBox.
{
Ref<StyleBoxFlat> checkbox_style = p_config.panel_container_style->duplicate();
- checkbox_style->set_content_margin_individual((p_config.increased_margin + 2) * EDSCALE, p_config.base_margin * EDSCALE, (p_config.increased_margin + 2) * EDSCALE, p_config.base_margin * EDSCALE);
p_theme->set_stylebox(CoreStringName(normal), "CheckBox", checkbox_style);
p_theme->set_stylebox(SceneStringName(pressed), "CheckBox", checkbox_style);
@@ -1165,9 +1164,6 @@ void EditorThemeManager::_populate_standard_styles(const Ref<EditorTheme> &p_the
// LineEdit & TextEdit.
{
Ref<StyleBoxFlat> text_editor_style = p_config.button_style->duplicate();
- // The original button_style style has an extra 1 pixel offset that makes LineEdits not align with Buttons,
- // so this compensates for that.
- text_editor_style->set_content_margin(SIDE_TOP, text_editor_style->get_content_margin(SIDE_TOP) - 1 * EDSCALE);
// Don't round the bottom corners to make the line look sharper.
text_editor_style->set_corner_radius(CORNER_BOTTOM_LEFT, 0);
@@ -1507,7 +1503,9 @@ void EditorThemeManager::_populate_standard_styles(const Ref<EditorTheme> &p_the
p_theme->set_constant("buttons_vertical_separation", "SpinBox", 0);
p_theme->set_constant("field_and_buttons_separation", "SpinBox", 2);
p_theme->set_constant("buttons_width", "SpinBox", 16);
+#ifndef DISABLE_DEPRECATED
p_theme->set_constant("set_min_buttons_width_from_icons", "SpinBox", 1);
+#endif
}
// ProgressBar.
@@ -1804,7 +1802,9 @@ void EditorThemeManager::_populate_editor_styles(const Ref<EditorTheme> &p_theme
p_theme->set_color("background", EditorStringName(Editor), background_color_opaque);
p_theme->set_stylebox("Background", EditorStringName(EditorStyles), make_flat_stylebox(background_color_opaque, p_config.base_margin, p_config.base_margin, p_config.base_margin, p_config.base_margin));
- p_theme->set_stylebox("PanelForeground", EditorStringName(EditorStyles), p_config.base_style);
+ Ref<StyleBoxFlat> editor_panel_foreground = p_config.base_style->duplicate();
+ editor_panel_foreground->set_corner_radius_all(0);
+ p_theme->set_stylebox("PanelForeground", EditorStringName(EditorStyles), editor_panel_foreground);
// Editor focus.
p_theme->set_stylebox("Focus", EditorStringName(EditorStyles), p_config.button_style_focus);
diff --git a/gles3_builders.py b/gles3_builders.py
index a4928c81c5..a81d42b42e 100644
--- a/gles3_builders.py
+++ b/gles3_builders.py
@@ -3,7 +3,7 @@
import os.path
from typing import Optional
-from methods import print_error
+from methods import print_error, to_raw_cstring
class GLES3HeaderStruct:
@@ -553,20 +553,12 @@ def build_gles3_header(
fd.write("\t\tstatic const Feedback* _feedbacks=nullptr;\n")
fd.write("\t\tstatic const char _vertex_code[]={\n")
- for x in header_data.vertex_lines:
- for c in x:
- fd.write(str(ord(c)) + ",")
-
- fd.write(str(ord("\n")) + ",")
- fd.write("\t\t0};\n\n")
+ fd.write(to_raw_cstring(header_data.vertex_lines))
+ fd.write("\n\t\t};\n\n")
fd.write("\t\tstatic const char _fragment_code[]={\n")
- for x in header_data.fragment_lines:
- for c in x:
- fd.write(str(ord(c)) + ",")
-
- fd.write(str(ord("\n")) + ",")
- fd.write("\t\t0};\n\n")
+ fd.write(to_raw_cstring(header_data.fragment_lines))
+ fd.write("\n\t\t};\n\n")
fd.write(
'\t\t_setup(_vertex_code,_fragment_code,"'
diff --git a/glsl_builders.py b/glsl_builders.py
index 05aab3acbb..82c15fc93b 100644
--- a/glsl_builders.py
+++ b/glsl_builders.py
@@ -1,25 +1,9 @@
"""Functions used to generate source files during build time"""
import os.path
-from typing import Iterable, Optional
+from typing import Optional
-from methods import print_error
-
-
-def generate_inline_code(input_lines: Iterable[str], insert_newline: bool = True):
- """Take header data and generate inline code
-
- :param: input_lines: values for shared inline code
- :return: str - generated inline value
- """
- output = []
- for line in input_lines:
- if line:
- output.append(",".join(str(ord(c)) for c in line))
- if insert_newline:
- output.append("%s" % ord("\n"))
- output.append("0")
- return ",".join(output)
+from methods import print_error, to_raw_cstring
class RDHeaderStruct:
@@ -127,13 +111,13 @@ def build_rd_header(
if header_data.compute_lines:
body_parts = [
- "static const char _compute_code[] = {\n%s\n\t\t};" % generate_inline_code(header_data.compute_lines),
+ "static const char _compute_code[] = {\n%s\n\t\t};" % to_raw_cstring(header_data.compute_lines),
f'setup(nullptr, nullptr, _compute_code, "{out_file_class}");',
]
else:
body_parts = [
- "static const char _vertex_code[] = {\n%s\n\t\t};" % generate_inline_code(header_data.vertex_lines),
- "static const char _fragment_code[] = {\n%s\n\t\t};" % generate_inline_code(header_data.fragment_lines),
+ "static const char _vertex_code[] = {\n%s\n\t\t};" % to_raw_cstring(header_data.vertex_lines),
+ "static const char _fragment_code[] = {\n%s\n\t\t};" % to_raw_cstring(header_data.fragment_lines),
f'setup(_vertex_code, _fragment_code, nullptr, "{out_file_class}");',
]
@@ -211,7 +195,7 @@ def build_raw_header(
#define {out_file_ifdef}_RAW_H
static const char {out_file_base}[] = {{
- {generate_inline_code(header_data.code, insert_newline=False)}
+{to_raw_cstring(header_data.code)}
}};
#endif
"""
diff --git a/godot.manifest b/godot.manifest
index 30b80aff25..17b588cafd 100644
--- a/godot.manifest
+++ b/godot.manifest
@@ -1,5 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
+ <application xmlns="urn:schemas-microsoft-com:asm.v3">
+ <windowsSettings xmlns:ws2="http://schemas.microsoft.com/SMI/2016/WindowsSettings">
+ <ws2:longPathAware>true</ws2:longPathAware>
+ </windowsSettings>
+ </application>
<dependency>
<dependentAssembly>
<assemblyIdentity
diff --git a/main/main.cpp b/main/main.cpp
index a4f0ae54e9..18ffedef18 100644
--- a/main/main.cpp
+++ b/main/main.cpp
@@ -1037,7 +1037,8 @@ Error Main::setup(const char *execpath, int argc, char *argv[], bool p_second_ph
if (arg == "--audio-driver" ||
arg == "--display-driver" ||
arg == "--rendering-method" ||
- arg == "--rendering-driver") {
+ arg == "--rendering-driver" ||
+ arg == "--xr-mode") {
if (N) {
forwardable_cli_arguments[CLI_SCOPE_TOOL].push_back(arg);
forwardable_cli_arguments[CLI_SCOPE_TOOL].push_back(N->get());
@@ -2327,15 +2328,12 @@ Error Main::setup(const char *execpath, int argc, char *argv[], bool p_second_ph
}
}
- // note this is the desired rendering driver, it doesn't mean we will get it.
- // TODO - make sure this is updated in the case of fallbacks, so that the user interface
- // shows the correct driver string.
- OS::get_singleton()->set_current_rendering_driver_name(rendering_driver);
- OS::get_singleton()->set_current_rendering_method(rendering_method);
-
// always convert to lower case for consistency in the code
rendering_driver = rendering_driver.to_lower();
+ OS::get_singleton()->set_current_rendering_driver_name(rendering_driver);
+ OS::get_singleton()->set_current_rendering_method(rendering_method);
+
if (use_custom_res) {
if (!force_res) {
window_size.width = GLOBAL_GET("display/window/size/viewport_width");
@@ -2906,6 +2904,8 @@ Error Main::setup2(bool p_show_boot_logo) {
Error err;
display_server = DisplayServer::create(display_driver_idx, rendering_driver, window_mode, window_vsync_mode, window_flags, window_position, window_size, init_screen, context, err);
if (err != OK || display_server == nullptr) {
+ String last_name = DisplayServer::get_create_function_name(display_driver_idx);
+
// We can't use this display server, try other ones as fallback.
// Skip headless (always last registered) because that's not what users
// would expect if they didn't request it explicitly.
@@ -2913,6 +2913,9 @@ Error Main::setup2(bool p_show_boot_logo) {
if (i == display_driver_idx) {
continue; // Don't try the same twice.
}
+ String name = DisplayServer::get_create_function_name(i);
+ WARN_PRINT(vformat("Display driver %s failed, falling back to %s.", last_name, name));
+
display_server = DisplayServer::create(i, rendering_driver, window_mode, window_vsync_mode, window_flags, window_position, window_size, init_screen, context, err);
if (err == OK && display_server != nullptr) {
break;
@@ -3132,7 +3135,7 @@ Error Main::setup2(bool p_show_boot_logo) {
DisplayServer::set_early_window_clear_color_override(false);
- GLOBAL_DEF_BASIC(PropertyInfo(Variant::STRING, "application/config/icon", PROPERTY_HINT_FILE, "*.png,*.webp,*.svg"), String());
+ GLOBAL_DEF_BASIC(PropertyInfo(Variant::STRING, "application/config/icon", PROPERTY_HINT_FILE, "*.png,*.bmp,*.hdr,*.jpg,*.jpeg,*.svg,*.tga,*.exr,*.webp"), String());
GLOBAL_DEF(PropertyInfo(Variant::STRING, "application/config/macos_native_icon", PROPERTY_HINT_FILE, "*.icns"), String());
GLOBAL_DEF(PropertyInfo(Variant::STRING, "application/config/windows_native_icon", PROPERTY_HINT_FILE, "*.ico"), String());
@@ -3301,7 +3304,7 @@ Error Main::setup2(bool p_show_boot_logo) {
OS::get_singleton()->benchmark_end_measure("Startup", "Platforms");
- GLOBAL_DEF_BASIC(PropertyInfo(Variant::STRING, "display/mouse_cursor/custom_image", PROPERTY_HINT_FILE, "*.png,*.webp"), String());
+ GLOBAL_DEF_BASIC(PropertyInfo(Variant::STRING, "display/mouse_cursor/custom_image", PROPERTY_HINT_FILE, "*.png,*.bmp,*.hdr,*.jpg,*.jpeg,*.svg,*.tga,*.exr,*.webp"), String());
GLOBAL_DEF_BASIC("display/mouse_cursor/custom_image_hotspot", Vector2());
GLOBAL_DEF_BASIC("display/mouse_cursor/tooltip_position_offset", Point2(10, 10));
@@ -3394,7 +3397,8 @@ void Main::setup_boot_logo() {
boot_logo.instantiate();
Error load_err = ImageLoader::load_image(boot_logo_path, boot_logo);
if (load_err) {
- ERR_PRINT("Non-existing or invalid boot splash at '" + boot_logo_path + "'. Loading default splash.");
+ String msg = (boot_logo_path.ends_with(".png") ? "" : "The only supported format is PNG.");
+ ERR_PRINT("Non-existing or invalid boot splash at '" + boot_logo_path + +"'. " + msg + " Loading default splash.");
}
}
} else {
@@ -3545,13 +3549,16 @@ int Main::start() {
gdscript_docs_path = E->next()->get();
#endif
} else if (E->get() == "--export-release") {
+ ERR_FAIL_COND_V_MSG(!editor && !found_project, EXIT_FAILURE, "Please provide a valid project path when exporting, aborting.");
editor = true; //needs editor
_export_preset = E->next()->get();
} else if (E->get() == "--export-debug") {
+ ERR_FAIL_COND_V_MSG(!editor && !found_project, EXIT_FAILURE, "Please provide a valid project path when exporting, aborting.");
editor = true; //needs editor
_export_preset = E->next()->get();
export_debug = true;
} else if (E->get() == "--export-pack") {
+ ERR_FAIL_COND_V_MSG(!editor && !found_project, EXIT_FAILURE, "Please provide a valid project path when exporting, aborting.");
editor = true;
_export_preset = E->next()->get();
export_pack_only = true;
@@ -3563,6 +3570,8 @@ int Main::start() {
if (parsed_pair) {
E = E->next();
}
+ } else if (E->get().begins_with("--export-")) {
+ ERR_FAIL_V_MSG(EXIT_FAILURE, "Missing export preset name, aborting.");
}
#ifdef TOOLS_ENABLED
// Handle case where no path is given to --doctool.
@@ -4406,7 +4415,7 @@ bool Main::iteration() {
}
#ifdef TOOLS_ENABLED
- if (wait_for_import && EditorFileSystem::get_singleton()->doing_first_scan()) {
+ if (wait_for_import && EditorFileSystem::get_singleton() && EditorFileSystem::get_singleton()->doing_first_scan()) {
exit = false;
}
#endif
diff --git a/methods.py b/methods.py
index c725501fd9..bfd08cfc7b 100644
--- a/methods.py
+++ b/methods.py
@@ -8,7 +8,7 @@ from collections import OrderedDict
from enum import Enum
from io import StringIO, TextIOWrapper
from pathlib import Path
-from typing import Generator, Optional
+from typing import Generator, List, Optional, Union
# Get the "Godot" folder name ahead of time
base_folder_path = str(os.path.abspath(Path(__file__).parent)) + "/"
@@ -163,7 +163,7 @@ def add_source_files(self, sources, files, allow_gen=False):
def disable_warnings(self):
# 'self' is the environment
- if self.msvc:
+ if self.msvc and not using_clang(self):
# We have to remove existing warning level defines before appending /w,
# otherwise we get: "warning D9025 : overriding '/W3' with '/w'"
self["CCFLAGS"] = [x for x in self["CCFLAGS"] if not (x.startswith("/W") or x.startswith("/w"))]
@@ -495,6 +495,8 @@ def use_windows_spawn_fix(self, platform=None):
rv = proc.wait()
if rv:
print_error(err)
+ elif len(err) > 0 and not err.isspace():
+ print(err)
return rv
def mySpawn(sh, escape, cmd, args, env):
@@ -815,21 +817,20 @@ def get_compiler_version(env):
"apple_patch3": -1,
}
- if not env.msvc:
- # Not using -dumpversion as some GCC distros only return major, and
- # Clang used to return hardcoded 4.2.1: # https://reviews.llvm.org/D56803
- try:
- version = (
- subprocess.check_output([env.subst(env["CXX"]), "--version"], shell=(os.name == "nt"))
- .strip()
- .decode("utf-8")
- )
- except (subprocess.CalledProcessError, OSError):
- print_warning("Couldn't parse CXX environment variable to infer compiler version.")
- return ret
- else:
+ if env.msvc and not using_clang(env):
# TODO: Implement for MSVC
return ret
+
+ # Not using -dumpversion as some GCC distros only return major, and
+ # Clang used to return hardcoded 4.2.1: # https://reviews.llvm.org/D56803
+ try:
+ version = subprocess.check_output(
+ [env.subst(env["CXX"]), "--version"], shell=(os.name == "nt"), encoding="utf-8"
+ ).strip()
+ except (subprocess.CalledProcessError, OSError):
+ print_warning("Couldn't parse CXX environment variable to infer compiler version.")
+ return ret
+
match = re.search(
r"(?:(?<=version )|(?<=\) )|(?<=^))"
r"(?P<major>\d+)"
@@ -1641,3 +1642,43 @@ def generated_wrapper(
file.write(f"\n\n#endif // {header_guard}")
file.write("\n")
+
+
+def to_raw_cstring(value: Union[str, List[str]]) -> str:
+ MAX_LITERAL = 16 * 1024
+
+ if isinstance(value, list):
+ value = "\n".join(value) + "\n"
+
+ split: List[bytes] = []
+ offset = 0
+ encoded = value.encode()
+
+ while offset <= len(encoded):
+ segment = encoded[offset : offset + MAX_LITERAL]
+ offset += MAX_LITERAL
+ if len(segment) == MAX_LITERAL:
+ # Try to segment raw strings at double newlines to keep readable.
+ pretty_break = segment.rfind(b"\n\n")
+ if pretty_break != -1:
+ segment = segment[: pretty_break + 1]
+ offset -= MAX_LITERAL - pretty_break - 1
+ # If none found, ensure we end with valid utf8.
+ # https://github.com/halloleo/unicut/blob/master/truncate.py
+ elif segment[-1] & 0b10000000:
+ last_11xxxxxx_index = [i for i in range(-1, -5, -1) if segment[i] & 0b11000000 == 0b11000000][0]
+ last_11xxxxxx = segment[last_11xxxxxx_index]
+ if not last_11xxxxxx & 0b00100000:
+ last_char_length = 2
+ elif not last_11xxxxxx & 0b0010000:
+ last_char_length = 3
+ elif not last_11xxxxxx & 0b0001000:
+ last_char_length = 4
+
+ if last_char_length > -last_11xxxxxx_index:
+ segment = segment[:last_11xxxxxx_index]
+ offset += last_11xxxxxx_index
+
+ split += [segment]
+
+ return " ".join(f'R"<!>({x.decode()})<!>"' for x in split)
diff --git a/misc/dist/macos_template.app/Contents/Info.plist b/misc/dist/macos_template.app/Contents/Info.plist
index 78bb559c0e..708498dd9c 100644
--- a/misc/dist/macos_template.app/Contents/Info.plist
+++ b/misc/dist/macos_template.app/Contents/Info.plist
@@ -49,12 +49,17 @@ $usage_descriptions
<string>NSApplication</string>
<key>LSApplicationCategoryType</key>
<string>public.app-category.$app_category</string>
- <key>LSMinimumSystemVersion</key>
- <string>$min_version</string>
+ <key>LSArchitecturePriority</key>
+ <array>
+ <string>arm64</string>
+ <string>x86_64</string>
+ </array>
<key>LSMinimumSystemVersionByArchitecture</key>
<dict>
+ <key>arm64</key>
+ <string>$min_version_arm64</string>
<key>x86_64</key>
- <string>$min_version</string>
+ <string>$min_version_x86_64</string>
</dict>
<key>NSHighResolutionCapable</key>
$highres
diff --git a/misc/extension_api_validation/4.3-stable.expected b/misc/extension_api_validation/4.3-stable.expected
index 80735f28e7..ad77e30e11 100644
--- a/misc/extension_api_validation/4.3-stable.expected
+++ b/misc/extension_api_validation/4.3-stable.expected
@@ -48,3 +48,35 @@ GH-93605
Validate extension JSON: JSON file: Field was added in a way that breaks compatibility 'classes/Semaphore/methods/post': arguments
Optional arguments added. Compatibility methods registered.
+
+
+GH-95212
+--------
+Validate extension JSON: Error: Field 'classes/RegEx/methods/compile/arguments': size changed value in new API, from 1 to 2.
+Validate extension JSON: Error: Field 'classes/RegEx/methods/create_from_string/arguments': size changed value in new API, from 1 to 2.
+
+Add optional argument to control error printing on compilation fail. Compatibility methods registered.
+
+
+GH-95375
+--------
+Validate extension JSON: Error: Field 'classes/AudioStreamPlayer/properties/playing': setter changed value in new API, from "_set_playing" to &"set_playing".
+Validate extension JSON: Error: Field 'classes/AudioStreamPlayer2D/properties/playing': setter changed value in new API, from "_set_playing" to &"set_playing".
+Validate extension JSON: Error: Field 'classes/AudioStreamPlayer3D/properties/playing': setter changed value in new API, from "_set_playing" to &"set_playing".
+
+These setters have been renamed to expose them. GDExtension language bindings couldn't have exposed these properties before.
+
+
+GH-94322
+--------
+Validate extension JSON: Error: Field 'classes/EditorInterface/methods/popup_node_selector/arguments': size changed value in new API, from 2 to 3.
+Validate extension JSON: Error: Field 'classes/EditorInterface/methods/popup_property_selector/arguments': size changed value in new API, from 3 to 4.
+
+Added optional argument to popup_property_selector and popup_node_selector to specify the current value.
+
+
+GH-94434
+--------
+Validate extension JSON: Error: Field 'classes/OS/methods/execute_with_pipe/arguments': size changed value in new API, from 2 to 3.
+
+Optional argument added. Compatibility method registered.
diff --git a/modules/betsy/bc6h.glsl b/modules/betsy/bc6h.glsl
index 0d10d378fd..37e7591aea 100644
--- a/modules/betsy/bc6h.glsl
+++ b/modules/betsy/bc6h.glsl
@@ -1,7 +1,7 @@
#[versions]
signed = "#define SIGNED";
-unsigned = "";
+unsigned = "#define QUALITY"; // The "Quality" preset causes artifacting on signed data, so for now it's exclusive to unsigned.
#[compute]
#version 450
@@ -10,10 +10,6 @@ unsigned = "";
#include "UavCrossPlatform_piece_all.glsl"
#VERSION_DEFINES
-#define QUALITY
-
-//SIGNED macro is WIP
-//#define SIGNED
float3 f32tof16(float3 value) {
return float3(packHalf2x16(float2(value.x, 0.0)),
@@ -48,11 +44,59 @@ params;
const float HALF_MAX = 65504.0f;
const uint PATTERN_NUM = 32u;
+#ifdef SIGNED
+const float HALF_MIN = -65504.0f;
+#else
+const float HALF_MIN = 0.0f;
+#endif
+
+#ifdef SIGNED
+// https://github.com/godotengine/godot/pull/96377#issuecomment-2323488254
+// https://github.com/godotengine/godot/pull/96377#issuecomment-2323450950
+bool isNegative(float a) {
+ return a < 0.0f;
+}
+
+float CalcSignlessMSLE(float a, float b) {
+ float err = log2((b + 1.0f) / (a + 1.0f));
+ err = err * err;
+ return err;
+}
+
+float CrossCalcMSLE(float a, float b) {
+ float result = 0.0f;
+ result += CalcSignlessMSLE(0.0f, abs(a));
+ result += CalcSignlessMSLE(0.0f, abs(b));
+ return result;
+}
+
+float CalcMSLE(float3 a, float3 b) {
+ float result = 0.0f;
+ if (isNegative(a.x) != isNegative(b.x)) {
+ result += CrossCalcMSLE(a.x, b.x);
+ } else {
+ result += CalcSignlessMSLE(abs(a.x), abs(b.x));
+ }
+ if (isNegative(a.y) != isNegative(b.y)) {
+ result += CrossCalcMSLE(a.y, b.y);
+ } else {
+ result += CalcSignlessMSLE(abs(a.y), abs(b.y));
+ }
+ if (isNegative(a.z) != isNegative(b.z)) {
+ result += CrossCalcMSLE(a.z, b.z);
+ } else {
+ result += CalcSignlessMSLE(abs(a.z), abs(b.z));
+ }
+
+ return result;
+}
+#else
float CalcMSLE(float3 a, float3 b) {
float3 err = log2((b + 1.0f) / (a + 1.0f));
err = err * err;
return err.x + err.y + err.z;
}
+#endif
uint PatternFixupID(uint i) {
uint ret = 15u;
@@ -176,11 +220,6 @@ float3 Unquantize10(float3 x) {
float3 FinishUnquantize(float3 endpoint0Unq, float3 endpoint1Unq, float weight) {
float3 comp = (endpoint0Unq * (64.0f - weight) + endpoint1Unq * weight + 32.0f) * (31.0f / 2048.0f);
- /*float3 signVal;
- signVal.x = comp.x >= 0.0f ? 0.0f : 0x8000;
- signVal.y = comp.y >= 0.0f ? 0.0f : 0x8000;
- signVal.z = comp.z >= 0.0f ? 0.0f : 0x8000;*/
- //return f16tof32( uint3( signVal + abs( comp ) ) );
return f16tof32(uint3(comp));
}
#endif
@@ -207,6 +246,7 @@ uint ComputeIndex4(float texelPos, float endPoint0Pos, float endPoint1Pos) {
return uint(clamp(r * 14.93333f + 0.03333f + 0.5f, 0.0f, 15.0f));
}
+// This adds a bitflag to quantized values that signifies whether they are negative.
void SignExtend(inout float3 v1, uint mask, uint signFlag) {
int3 v = int3(v1);
v.x = (v.x & int(mask)) | (v.x < 0 ? int(signFlag) : 0);
@@ -215,6 +255,7 @@ void SignExtend(inout float3 v1, uint mask, uint signFlag) {
v1 = v;
}
+// Encodes a block with mode 11 (2x 10-bit endpoints).
void EncodeP1(inout uint4 block, inout float blockMSLE, float3 texels[16]) {
// compute endpoints (min/max RGB bbox)
float3 blockMin = texels[0];
@@ -250,6 +291,12 @@ void EncodeP1(inout uint4 block, inout float blockMSLE, float3 texels[16]) {
float endPoint0Pos = f32tof16(dot(blockMin, blockDir));
float endPoint1Pos = f32tof16(dot(blockMax, blockDir));
+#ifdef SIGNED
+ int maxVal10 = 0x1FF;
+ endpoint0 = clamp(endpoint0, -maxVal10, maxVal10);
+ endpoint1 = clamp(endpoint1, -maxVal10, maxVal10);
+#endif
+
// check if endpoint swap is required
float fixupTexelPos = f32tof16(dot(texels[0], blockDir));
uint fixupIndex = ComputeIndex4(fixupTexelPos, endPoint0Pos, endPoint1Pos);
@@ -276,6 +323,11 @@ void EncodeP1(inout uint4 block, inout float blockMSLE, float3 texels[16]) {
msle += CalcMSLE(texels[i], texelUnc);
}
+#ifdef SIGNED
+ SignExtend(endpoint0, 0x1FF, 0x200);
+ SignExtend(endpoint1, 0x1FF, 0x200);
+#endif
+
// encode block for mode 11
blockMSLE = msle;
block.x = 0x03;
@@ -316,11 +368,12 @@ float DistToLineSq(float3 PointOnLine, float3 LineDirection, float3 Point) {
return dot(x, x);
}
+// Gets the deviation from the source data of a particular pattern (smaller is better).
float EvaluateP2Pattern(uint pattern, float3 texels[16]) {
float3 p0BlockMin = float3(HALF_MAX, HALF_MAX, HALF_MAX);
- float3 p0BlockMax = float3(0.0f, 0.0f, 0.0f);
+ float3 p0BlockMax = float3(HALF_MIN, HALF_MIN, HALF_MIN);
float3 p1BlockMin = float3(HALF_MAX, HALF_MAX, HALF_MAX);
- float3 p1BlockMax = float3(0.0f, 0.0f, 0.0f);
+ float3 p1BlockMax = float3(HALF_MIN, HALF_MIN, HALF_MIN);
for (uint i = 0; i < 16; ++i) {
uint paletteID = Pattern(pattern, i);
@@ -350,11 +403,12 @@ float EvaluateP2Pattern(uint pattern, float3 texels[16]) {
return sqDistanceFromLine;
}
+// Encodes a block with either mode 2 (7-bit base, 3x 6-bit delta), or mode 6 (9-bit base, 3x 5-bit delta). Both use pattern encoding.
void EncodeP2Pattern(inout uint4 block, inout float blockMSLE, uint pattern, float3 texels[16]) {
float3 p0BlockMin = float3(HALF_MAX, HALF_MAX, HALF_MAX);
- float3 p0BlockMax = float3(0.0f, 0.0f, 0.0f);
+ float3 p0BlockMax = float3(HALF_MIN, HALF_MIN, HALF_MIN);
float3 p1BlockMin = float3(HALF_MAX, HALF_MAX, HALF_MAX);
- float3 p1BlockMax = float3(0.0f, 0.0f, 0.0f);
+ float3 p1BlockMax = float3(HALF_MIN, HALF_MIN, HALF_MIN);
for (uint i = 0u; i < 16u; ++i) {
uint paletteID = Pattern(pattern, i);
@@ -430,6 +484,13 @@ void EncodeP2Pattern(inout uint4 block, inout float blockMSLE, uint pattern, flo
endpoint952 = clamp(endpoint952, -maxVal95, maxVal95);
endpoint953 = clamp(endpoint953, -maxVal95, maxVal95);
+#ifdef SIGNED
+ int maxVal7 = 0x3F;
+ int maxVal9 = 0xFF;
+ endpoint760 = clamp(endpoint760, -maxVal7, maxVal7);
+ endpoint950 = clamp(endpoint950, -maxVal9, maxVal9);
+#endif
+
float3 endpoint760Unq = Unquantize7(endpoint760);
float3 endpoint761Unq = Unquantize7(endpoint760 + endpoint761);
float3 endpoint762Unq = Unquantize7(endpoint760 + endpoint762);
@@ -465,6 +526,11 @@ void EncodeP2Pattern(inout uint4 block, inout float blockMSLE, uint pattern, flo
SignExtend(endpoint952, 0xF, 0x10);
SignExtend(endpoint953, 0xF, 0x10);
+#ifdef SIGNED
+ SignExtend(endpoint760, 0x3F, 0x40);
+ SignExtend(endpoint950, 0xFF, 0x100);
+#endif
+
// encode block
float p2MSLE = min(msle76, msle95);
if (p2MSLE < blockMSLE) {
@@ -637,7 +703,7 @@ void main() {
float bestScore = EvaluateP2Pattern(0, texels);
uint bestPattern = 0;
- for (uint i = 1u; i < 32u; ++i) {
+ for (uint i = 1u; i < PATTERN_NUM; ++i) {
float score = EvaluateP2Pattern(i, texels);
if (score < bestScore) {
diff --git a/modules/betsy/image_compress_betsy.cpp b/modules/betsy/image_compress_betsy.cpp
index 7f723826d1..bc72203b2f 100644
--- a/modules/betsy/image_compress_betsy.cpp
+++ b/modules/betsy/image_compress_betsy.cpp
@@ -36,6 +36,9 @@
#if defined(VULKAN_ENABLED)
#include "drivers/vulkan/rendering_context_driver_vulkan.h"
#endif
+#if defined(METAL_ENABLED)
+#include "drivers/metal/rendering_context_driver_metal.h"
+#endif
#include "bc6h.glsl.gen.h"
@@ -66,10 +69,16 @@ Error _compress_betsy(BetsyFormat p_format, Image *r_img) {
if (rd == nullptr) {
#if defined(RD_ENABLED)
-#if defined(VULKAN_ENABLED)
- rcd = memnew(RenderingContextDriverVulkan);
+#if defined(METAL_ENABLED)
+ rcd = memnew(RenderingContextDriverMetal);
rd = memnew(RenderingDevice);
#endif
+#if defined(VULKAN_ENABLED)
+ if (rcd == nullptr) {
+ rcd = memnew(RenderingContextDriverVulkan);
+ rd = memnew(RenderingDevice);
+ }
+#endif
#endif
if (rcd != nullptr && rd != nullptr) {
err = rcd->initialize();
@@ -116,6 +125,7 @@ Error _compress_betsy(BetsyFormat p_format, Image *r_img) {
}
if (err != OK) {
+ compute_shader->print_errors("Betsy compress shader");
memdelete(rd);
if (rcd != nullptr) {
memdelete(rcd);
diff --git a/modules/fbx/editor/editor_scene_importer_fbx2gltf.cpp b/modules/fbx/editor/editor_scene_importer_fbx2gltf.cpp
index daa4db37df..f5b19f803a 100644
--- a/modules/fbx/editor/editor_scene_importer_fbx2gltf.cpp
+++ b/modules/fbx/editor/editor_scene_importer_fbx2gltf.cpp
@@ -124,7 +124,7 @@ Node *EditorSceneFormatImporterFBX2GLTF::import_scene(const String &p_path, uint
#endif
}
-Variant EditorSceneFormatImporterFBX2GLTF::get_option_visibility(const String &p_path, bool p_for_animation,
+Variant EditorSceneFormatImporterFBX2GLTF::get_option_visibility(const String &p_path, const String &p_scene_import_type,
const String &p_option, const HashMap<StringName, Variant> &p_options) {
// Remove all the FBX options except for 'fbx/importer' if the importer is fbx2gltf.
// These options are available only for ufbx.
diff --git a/modules/fbx/editor/editor_scene_importer_fbx2gltf.h b/modules/fbx/editor/editor_scene_importer_fbx2gltf.h
index c68e37f0d8..ce2bac6fcf 100644
--- a/modules/fbx/editor/editor_scene_importer_fbx2gltf.h
+++ b/modules/fbx/editor/editor_scene_importer_fbx2gltf.h
@@ -49,7 +49,7 @@ public:
List<String> *r_missing_deps, Error *r_err = nullptr) override;
virtual void get_import_options(const String &p_path,
List<ResourceImporter::ImportOption> *r_options) override;
- virtual Variant get_option_visibility(const String &p_path, bool p_for_animation, const String &p_option,
+ virtual Variant get_option_visibility(const String &p_path, const String &p_scene_import_type, const String &p_option,
const HashMap<StringName, Variant> &p_options) override;
virtual void handle_compatibility_options(HashMap<StringName, Variant> &p_import_params) const override;
};
diff --git a/modules/fbx/editor/editor_scene_importer_ufbx.cpp b/modules/fbx/editor/editor_scene_importer_ufbx.cpp
index 4d5f220539..64075c0664 100644
--- a/modules/fbx/editor/editor_scene_importer_ufbx.cpp
+++ b/modules/fbx/editor/editor_scene_importer_ufbx.cpp
@@ -88,7 +88,7 @@ Node *EditorSceneFormatImporterUFBX::import_scene(const String &p_path, uint32_t
return fbx->generate_scene(state, state->get_bake_fps(), (bool)p_options["animation/trimming"], false);
}
-Variant EditorSceneFormatImporterUFBX::get_option_visibility(const String &p_path, bool p_for_animation,
+Variant EditorSceneFormatImporterUFBX::get_option_visibility(const String &p_path, const String &p_scene_import_type,
const String &p_option, const HashMap<StringName, Variant> &p_options) {
return true;
}
diff --git a/modules/fbx/editor/editor_scene_importer_ufbx.h b/modules/fbx/editor/editor_scene_importer_ufbx.h
index b81b8df4c1..6e3eafc100 100644
--- a/modules/fbx/editor/editor_scene_importer_ufbx.h
+++ b/modules/fbx/editor/editor_scene_importer_ufbx.h
@@ -53,7 +53,7 @@ public:
List<String> *r_missing_deps, Error *r_err = nullptr) override;
virtual void get_import_options(const String &p_path,
List<ResourceImporter::ImportOption> *r_options) override;
- virtual Variant get_option_visibility(const String &p_path, bool p_for_animation, const String &p_option,
+ virtual Variant get_option_visibility(const String &p_path, const String &p_scene_import_type, const String &p_option,
const HashMap<StringName, Variant> &p_options) override;
virtual void handle_compatibility_options(HashMap<StringName, Variant> &p_import_params) const override;
};
diff --git a/modules/fbx/fbx_document.cpp b/modules/fbx/fbx_document.cpp
index 4e1a00cad6..8d4d0234da 100644
--- a/modules/fbx/fbx_document.cpp
+++ b/modules/fbx/fbx_document.cpp
@@ -875,7 +875,7 @@ Error FBXDocument::_parse_meshes(Ref<FBXState> p_state) {
const int material = int(fbx_material->typed_id);
ERR_FAIL_INDEX_V(material, p_state->materials.size(), ERR_FILE_CORRUPT);
Ref<Material> mat3d = p_state->materials[material];
- ERR_FAIL_NULL_V(mat3d, ERR_FILE_CORRUPT);
+ ERR_FAIL_COND_V(mat3d.is_null(), ERR_FILE_CORRUPT);
Ref<BaseMaterial3D> base_material = mat3d;
if (has_vertex_color && base_material.is_valid()) {
@@ -891,7 +891,7 @@ Error FBXDocument::_parse_meshes(Ref<FBXState> p_state) {
}
mat = mat3d;
}
- ERR_FAIL_NULL_V(mat, ERR_FILE_CORRUPT);
+ ERR_FAIL_COND_V(mat.is_null(), ERR_FILE_CORRUPT);
mat_name = mat->get_name();
}
import_mesh->add_surface(primitive, array, morphs,
@@ -1056,7 +1056,7 @@ GLTFImageIndex FBXDocument::_parse_image_save_image(Ref<FBXState> p_state, const
}
Error FBXDocument::_parse_images(Ref<FBXState> p_state, const String &p_base_path) {
- ERR_FAIL_NULL_V(p_state, ERR_INVALID_PARAMETER);
+ ERR_FAIL_COND_V(p_state.is_null(), ERR_INVALID_PARAMETER);
const ufbx_scene *fbx_scene = p_state->scene.get();
for (int texture_i = 0; texture_i < static_cast<int>(fbx_scene->texture_files.count); texture_i++) {
@@ -2118,7 +2118,6 @@ Error FBXDocument::_parse(Ref<FBXState> p_state, String p_path, Ref<FileAccess>
Node *FBXDocument::generate_scene(Ref<GLTFState> p_state, float p_bake_fps, bool p_trimming, bool p_remove_immutable_tracks) {
Ref<FBXState> state = p_state;
ERR_FAIL_COND_V(state.is_null(), nullptr);
- ERR_FAIL_NULL_V(state, nullptr);
ERR_FAIL_INDEX_V(0, state->root_nodes.size(), nullptr);
p_state->set_bake_fps(p_bake_fps);
GLTFNodeIndex fbx_root = state->root_nodes.write[0];
@@ -2246,7 +2245,7 @@ Error FBXDocument::append_from_file(String p_path, Ref<GLTFState> p_state, uint3
Error err;
Ref<FileAccess> file = FileAccess::open(p_path, FileAccess::READ, &err);
ERR_FAIL_COND_V(err != OK, ERR_FILE_CANT_OPEN);
- ERR_FAIL_NULL_V(file, ERR_FILE_CANT_OPEN);
+ ERR_FAIL_COND_V(file.is_null(), ERR_FILE_CANT_OPEN);
String base_path = p_base_path;
if (base_path.is_empty()) {
base_path = p_path.get_base_dir();
diff --git a/modules/gdscript/editor/gdscript_docgen.cpp b/modules/gdscript/editor/gdscript_docgen.cpp
index 35b69fab8c..32ef429b0d 100644
--- a/modules/gdscript/editor/gdscript_docgen.cpp
+++ b/modules/gdscript/editor/gdscript_docgen.cpp
@@ -84,6 +84,15 @@ void GDScriptDocGen::_doctype_from_gdtype(const GDType &p_gdtype, String &r_type
return;
}
}
+ if (p_gdtype.builtin_type == Variant::DICTIONARY && p_gdtype.has_container_element_types()) {
+ String key, value;
+ _doctype_from_gdtype(p_gdtype.get_container_element_type_or_variant(0), key, r_enum);
+ _doctype_from_gdtype(p_gdtype.get_container_element_type_or_variant(1), value, r_enum);
+ if (key != "Variant" || value != "Variant") {
+ r_type = "Dictionary[" + key + ", " + value + "]";
+ return;
+ }
+ }
r_type = Variant::get_type_name(p_gdtype.builtin_type);
return;
case GDType::NATIVE:
@@ -155,34 +164,82 @@ String GDScriptDocGen::_docvalue_from_variant(const Variant &p_variant, int p_re
return "<Object>";
case Variant::DICTIONARY: {
const Dictionary dict = p_variant;
+ String result;
- if (dict.is_empty()) {
- return "{}";
- }
+ if (dict.is_typed()) {
+ result += "Dictionary[";
+
+ Ref<Script> key_script = dict.get_typed_key_script();
+ if (key_script.is_valid()) {
+ if (key_script->get_global_name() != StringName()) {
+ result += key_script->get_global_name();
+ } else if (!key_script->get_path().get_file().is_empty()) {
+ result += key_script->get_path().get_file();
+ } else {
+ result += dict.get_typed_key_class_name();
+ }
+ } else if (dict.get_typed_key_class_name() != StringName()) {
+ result += dict.get_typed_key_class_name();
+ } else if (dict.is_typed_key()) {
+ result += Variant::get_type_name((Variant::Type)dict.get_typed_key_builtin());
+ } else {
+ result += "Variant";
+ }
+
+ result += ", ";
- if (p_recursion_level > MAX_RECURSION_LEVEL) {
- return "{...}";
+ Ref<Script> value_script = dict.get_typed_value_script();
+ if (value_script.is_valid()) {
+ if (value_script->get_global_name() != StringName()) {
+ result += value_script->get_global_name();
+ } else if (!value_script->get_path().get_file().is_empty()) {
+ result += value_script->get_path().get_file();
+ } else {
+ result += dict.get_typed_value_class_name();
+ }
+ } else if (dict.get_typed_value_class_name() != StringName()) {
+ result += dict.get_typed_value_class_name();
+ } else if (dict.is_typed_value()) {
+ result += Variant::get_type_name((Variant::Type)dict.get_typed_value_builtin());
+ } else {
+ result += "Variant";
+ }
+
+ result += "](";
}
- List<Variant> keys;
- dict.get_key_list(&keys);
- keys.sort();
+ if (dict.is_empty()) {
+ result += "{}";
+ } else if (p_recursion_level > MAX_RECURSION_LEVEL) {
+ result += "{...}";
+ } else {
+ result += "{";
+
+ List<Variant> keys;
+ dict.get_key_list(&keys);
+ keys.sort();
- String data;
- for (List<Variant>::Element *E = keys.front(); E; E = E->next()) {
- if (E->prev()) {
- data += ", ";
+ for (List<Variant>::Element *E = keys.front(); E; E = E->next()) {
+ if (E->prev()) {
+ result += ", ";
+ }
+ result += _docvalue_from_variant(E->get(), p_recursion_level + 1) + ": " + _docvalue_from_variant(dict[E->get()], p_recursion_level + 1);
}
- data += _docvalue_from_variant(E->get(), p_recursion_level + 1) + ": " + _docvalue_from_variant(dict[E->get()], p_recursion_level + 1);
+
+ result += "}";
+ }
+
+ if (dict.is_typed()) {
+ result += ")";
}
- return "{" + data + "}";
+ return result;
} break;
case Variant::ARRAY: {
const Array array = p_variant;
String result;
- if (array.get_typed_builtin() != Variant::NIL) {
+ if (array.is_typed()) {
result += "Array[";
Ref<Script> script = array.get_typed_script();
@@ -209,16 +266,18 @@ String GDScriptDocGen::_docvalue_from_variant(const Variant &p_variant, int p_re
result += "[...]";
} else {
result += "[";
+
for (int i = 0; i < array.size(); i++) {
if (i > 0) {
result += ", ";
}
result += _docvalue_from_variant(array[i], p_recursion_level + 1);
}
+
result += "]";
}
- if (array.get_typed_builtin() != Variant::NIL) {
+ if (array.is_typed()) {
result += ")";
}
diff --git a/modules/gdscript/editor/gdscript_translation_parser_plugin.cpp b/modules/gdscript/editor/gdscript_translation_parser_plugin.cpp
index d74d316704..b31ae878ce 100644
--- a/modules/gdscript/editor/gdscript_translation_parser_plugin.cpp
+++ b/modules/gdscript/editor/gdscript_translation_parser_plugin.cpp
@@ -71,7 +71,7 @@ Error GDScriptEditorTranslationParserPlugin::parse_file(const String &p_path, Ve
bool GDScriptEditorTranslationParserPlugin::_is_constant_string(const GDScriptParser::ExpressionNode *p_expression) {
ERR_FAIL_NULL_V(p_expression, false);
- return p_expression->is_constant && (p_expression->reduced_value.get_type() == Variant::STRING || p_expression->reduced_value.get_type() == Variant::STRING_NAME);
+ return p_expression->is_constant && p_expression->reduced_value.is_string();
}
void GDScriptEditorTranslationParserPlugin::_traverse_class(const GDScriptParser::ClassNode *p_class) {
diff --git a/modules/gdscript/gdscript.cpp b/modules/gdscript/gdscript.cpp
index 7bf5e946fb..276a12f5de 100644
--- a/modules/gdscript/gdscript.cpp
+++ b/modules/gdscript/gdscript.cpp
@@ -53,9 +53,11 @@
#include "core/io/file_access_encrypted.h"
#include "core/os/os.h"
+#include "scene/resources/packed_scene.h"
#include "scene/scene_string_names.h"
#ifdef TOOLS_ENABLED
+#include "core/extension/gdextension_manager.h"
#include "editor/editor_paths.h"
#endif
@@ -136,7 +138,7 @@ void GDScript::_super_implicit_constructor(GDScript *p_script, GDScriptInstance
}
}
ERR_FAIL_NULL(p_script->implicit_initializer);
- if (likely(valid)) {
+ if (likely(p_script->valid)) {
p_script->implicit_initializer->call(p_instance, nullptr, 0, r_error);
} else {
r_error.error = Callable::CallError::CALL_ERROR_INVALID_METHOD;
@@ -952,7 +954,8 @@ bool GDScript::_get(const StringName &p_name, Variant &r_ret) const {
if (E) {
if (likely(top->valid) && E->value.getter) {
Callable::CallError ce;
- r_ret = const_cast<GDScript *>(this)->callp(E->value.getter, nullptr, 0, ce);
+ const Variant ret = const_cast<GDScript *>(this)->callp(E->value.getter, nullptr, 0, ce);
+ r_ret = (ce.error == Callable::CallError::CALL_OK) ? ret : Variant();
return true;
}
r_ret = top->static_variables[E->value.index];
@@ -1725,10 +1728,9 @@ bool GDScriptInstance::get(const StringName &p_name, Variant &r_ret) const {
if (E) {
if (likely(script->valid) && E->value.getter) {
Callable::CallError err;
- r_ret = const_cast<GDScriptInstance *>(this)->callp(E->value.getter, nullptr, 0, err);
- if (err.error == Callable::CallError::CALL_OK) {
- return true;
- }
+ const Variant ret = const_cast<GDScriptInstance *>(this)->callp(E->value.getter, nullptr, 0, err);
+ r_ret = (err.error == Callable::CallError::CALL_OK) ? ret : Variant();
+ return true;
}
r_ret = members[E->value.index];
return true;
@@ -1750,7 +1752,8 @@ bool GDScriptInstance::get(const StringName &p_name, Variant &r_ret) const {
if (E) {
if (likely(sptr->valid) && E->value.getter) {
Callable::CallError ce;
- r_ret = const_cast<GDScript *>(sptr)->callp(E->value.getter, nullptr, 0, ce);
+ const Variant ret = const_cast<GDScript *>(sptr)->callp(E->value.getter, nullptr, 0, ce);
+ r_ret = (ce.error == Callable::CallError::CALL_OK) ? ret : Variant();
return true;
}
r_ret = sptr->static_variables[E->value.index];
@@ -2176,9 +2179,26 @@ void GDScriptLanguage::_add_global(const StringName &p_name, const Variant &p_va
global_array.write[globals[p_name]] = p_value;
return;
}
- globals[p_name] = global_array.size();
- global_array.push_back(p_value);
- _global_array = global_array.ptrw();
+
+ if (global_array_empty_indexes.size()) {
+ int index = global_array_empty_indexes[global_array_empty_indexes.size() - 1];
+ globals[p_name] = index;
+ global_array.write[index] = p_value;
+ global_array_empty_indexes.resize(global_array_empty_indexes.size() - 1);
+ } else {
+ globals[p_name] = global_array.size();
+ global_array.push_back(p_value);
+ _global_array = global_array.ptrw();
+ }
+}
+
+void GDScriptLanguage::_remove_global(const StringName &p_name) {
+ if (!globals.has(p_name)) {
+ return;
+ }
+ global_array_empty_indexes.push_back(globals[p_name]);
+ global_array.write[globals[p_name]] = Variant::NIL;
+ globals.erase(p_name);
}
void GDScriptLanguage::add_global_constant(const StringName &p_variable, const Variant &p_value) {
@@ -2236,11 +2256,40 @@ void GDScriptLanguage::init() {
_add_global(E.name, E.ptr);
}
+#ifdef TOOLS_ENABLED
+ if (Engine::get_singleton()->is_editor_hint()) {
+ GDExtensionManager::get_singleton()->connect("extension_loaded", callable_mp(this, &GDScriptLanguage::_extension_loaded));
+ GDExtensionManager::get_singleton()->connect("extension_unloading", callable_mp(this, &GDScriptLanguage::_extension_unloading));
+ }
+#endif
+
#ifdef TESTS_ENABLED
GDScriptTests::GDScriptTestRunner::handle_cmdline();
#endif
}
+#ifdef TOOLS_ENABLED
+void GDScriptLanguage::_extension_loaded(const Ref<GDExtension> &p_extension) {
+ List<StringName> class_list;
+ ClassDB::get_extension_class_list(p_extension, &class_list);
+ for (const StringName &n : class_list) {
+ if (globals.has(n)) {
+ continue;
+ }
+ Ref<GDScriptNativeClass> nc = memnew(GDScriptNativeClass(n));
+ _add_global(n, nc);
+ }
+}
+
+void GDScriptLanguage::_extension_unloading(const Ref<GDExtension> &p_extension) {
+ List<StringName> class_list;
+ ClassDB::get_extension_class_list(p_extension, &class_list);
+ for (const StringName &n : class_list) {
+ _remove_global(n);
+ }
+}
+#endif
+
String GDScriptLanguage::get_type() const {
return "GDScript";
}
@@ -2503,7 +2552,7 @@ void GDScriptLanguage::reload_scripts(const Array &p_scripts, bool p_soft_reload
SelfList<GDScript> *elem = script_list.first();
while (elem) {
// Scripts will reload all subclasses, so only reload root scripts.
- if (elem->self()->is_root_script() && elem->self()->get_path().is_resource_file()) {
+ if (elem->self()->is_root_script() && !elem->self()->get_path().is_empty()) {
scripts.push_back(Ref<GDScript>(elem->self())); //cast to gdscript to avoid being erased by accident
}
elem = elem->next();
@@ -2571,7 +2620,19 @@ void GDScriptLanguage::reload_scripts(const Array &p_scripts, bool p_soft_reload
for (KeyValue<Ref<GDScript>, HashMap<ObjectID, List<Pair<StringName, Variant>>>> &E : to_reload) {
Ref<GDScript> scr = E.key;
print_verbose("GDScript: Reloading: " + scr->get_path());
- scr->load_source_code(scr->get_path());
+ if (scr->is_built_in()) {
+ // TODO: It would be nice to do it more efficiently than loading the whole scene again.
+ Ref<PackedScene> scene = ResourceLoader::load(scr->get_path().get_slice("::", 0), "", ResourceFormatLoader::CACHE_MODE_IGNORE_DEEP);
+ ERR_CONTINUE(scene.is_null());
+
+ Ref<SceneState> state = scene->get_state();
+ Ref<GDScript> fresh = state->get_sub_resource(scr->get_path());
+ ERR_CONTINUE(fresh.is_null());
+
+ scr->set_source_code(fresh->get_source_code());
+ } else {
+ scr->load_source_code(scr->get_path());
+ }
scr->reload(p_soft_reload);
//restore state if saved
diff --git a/modules/gdscript/gdscript.h b/modules/gdscript/gdscript.h
index 6527a0ea4d..4d21651365 100644
--- a/modules/gdscript/gdscript.h
+++ b/modules/gdscript/gdscript.h
@@ -417,6 +417,7 @@ class GDScriptLanguage : public ScriptLanguage {
Vector<Variant> global_array;
HashMap<StringName, int> globals;
HashMap<StringName, Variant> named_globals;
+ Vector<int> global_array_empty_indexes;
struct CallLevel {
Variant *stack = nullptr;
@@ -448,6 +449,7 @@ class GDScriptLanguage : public ScriptLanguage {
int _debug_max_call_stack = 0;
void _add_global(const StringName &p_name, const Variant &p_value);
+ void _remove_global(const StringName &p_name);
friend class GDScriptInstance;
@@ -467,6 +469,11 @@ class GDScriptLanguage : public ScriptLanguage {
HashMap<String, ObjectID> orphan_subclasses;
+#ifdef TOOLS_ENABLED
+ void _extension_loaded(const Ref<GDExtension> &p_extension);
+ void _extension_unloading(const Ref<GDExtension> &p_extension);
+#endif
+
public:
int calls;
diff --git a/modules/gdscript/gdscript_analyzer.cpp b/modules/gdscript/gdscript_analyzer.cpp
index e98cae765b..7f0d5005cb 100644
--- a/modules/gdscript/gdscript_analyzer.cpp
+++ b/modules/gdscript/gdscript_analyzer.cpp
@@ -418,6 +418,12 @@ Error GDScriptAnalyzer::resolve_class_inheritance(GDScriptParser::ClassNode *p_c
return err;
}
+#ifdef DEBUG_ENABLED
+ if (!parser->_is_tool && ext_parser->get_parser()->_is_tool) {
+ parser->push_warning(p_class, GDScriptWarning::MISSING_TOOL);
+ }
+#endif
+
base = ext_parser->get_parser()->head->get_datatype();
} else {
if (p_class->extends.is_empty()) {
@@ -445,6 +451,13 @@ Error GDScriptAnalyzer::resolve_class_inheritance(GDScriptParser::ClassNode *p_c
push_error(vformat(R"(Could not resolve super class inheritance from "%s".)", name), id);
return err;
}
+
+#ifdef DEBUG_ENABLED
+ if (!parser->_is_tool && base_parser->get_parser()->_is_tool) {
+ parser->push_warning(p_class, GDScriptWarning::MISSING_TOOL);
+ }
+#endif
+
base = base_parser->get_parser()->head->get_datatype();
}
} else if (ProjectSettings::get_singleton()->has_autoload(name) && ProjectSettings::get_singleton()->get_autoload(name).is_singleton) {
@@ -465,6 +478,13 @@ Error GDScriptAnalyzer::resolve_class_inheritance(GDScriptParser::ClassNode *p_c
push_error(vformat(R"(Could not resolve super class inheritance from "%s".)", name), id);
return err;
}
+
+#ifdef DEBUG_ENABLED
+ if (!parser->_is_tool && info_parser->get_parser()->_is_tool) {
+ parser->push_warning(p_class, GDScriptWarning::MISSING_TOOL);
+ }
+#endif
+
base = info_parser->get_parser()->head->get_datatype();
} else if (class_exists(name)) {
if (Engine::get_singleton()->has_singleton(name)) {
@@ -704,6 +724,18 @@ GDScriptParser::DataType GDScriptAnalyzer::resolve_datatype(GDScriptParser::Type
result.set_container_element_type(0, container_type);
}
}
+ if (result.builtin_type == Variant::DICTIONARY) {
+ GDScriptParser::DataType key_type = type_from_metatype(resolve_datatype(p_type->get_container_type_or_null(0)));
+ if (key_type.kind != GDScriptParser::DataType::VARIANT) {
+ key_type.is_constant = false;
+ result.set_container_element_type(0, key_type);
+ }
+ GDScriptParser::DataType value_type = type_from_metatype(resolve_datatype(p_type->get_container_type_or_null(1)));
+ if (value_type.kind != GDScriptParser::DataType::VARIANT) {
+ value_type.is_constant = false;
+ result.set_container_element_type(1, value_type);
+ }
+ }
} else if (class_exists(first)) {
// Native engine classes.
result.kind = GDScriptParser::DataType::NATIVE;
@@ -864,11 +896,16 @@ GDScriptParser::DataType GDScriptAnalyzer::resolve_datatype(GDScriptParser::Type
if (!p_type->container_types.is_empty()) {
if (result.builtin_type == Variant::ARRAY) {
if (p_type->container_types.size() != 1) {
- push_error("Arrays require exactly one collection element type.", p_type);
+ push_error(R"(Typed arrays require exactly one collection element type.)", p_type);
+ return bad_type;
+ }
+ } else if (result.builtin_type == Variant::DICTIONARY) {
+ if (p_type->container_types.size() != 2) {
+ push_error(R"(Typed dictionaries require exactly two collection element types.)", p_type);
return bad_type;
}
} else {
- push_error("Only arrays can specify collection element types.", p_type);
+ push_error(R"(Only arrays and dictionaries can specify collection element types.)", p_type);
return bad_type;
}
}
@@ -1906,6 +1943,11 @@ void GDScriptAnalyzer::resolve_assignable(GDScriptParser::AssignableNode *p_assi
if (has_specified_type && specified_type.has_container_element_type(0)) {
update_array_literal_element_type(array, specified_type.get_container_element_type(0));
}
+ } else if (p_assignable->initializer->type == GDScriptParser::Node::DICTIONARY) {
+ GDScriptParser::DictionaryNode *dictionary = static_cast<GDScriptParser::DictionaryNode *>(p_assignable->initializer);
+ if (has_specified_type && specified_type.has_container_element_types()) {
+ update_dictionary_literal_element_type(dictionary, specified_type.get_container_element_type_or_variant(0), specified_type.get_container_element_type_or_variant(1));
+ }
}
if (is_constant && !p_assignable->initializer->is_constant) {
@@ -1967,7 +2009,7 @@ void GDScriptAnalyzer::resolve_assignable(GDScriptParser::AssignableNode *p_assi
} else {
push_error(vformat(R"(Cannot assign a value of type %s to %s "%s" with specified type %s.)", initializer_type.to_string(), p_kind, p_assignable->identifier->name, specified_type.to_string()), p_assignable->initializer);
}
- } else if (specified_type.has_container_element_type(0) && !initializer_type.has_container_element_type(0)) {
+ } else if ((specified_type.has_container_element_type(0) && !initializer_type.has_container_element_type(0)) || (specified_type.has_container_element_type(1) && !initializer_type.has_container_element_type(1))) {
mark_node_unsafe(p_assignable->initializer);
#ifdef DEBUG_ENABLED
} else if (specified_type.builtin_type == Variant::INT && initializer_type.builtin_type == Variant::FLOAT) {
@@ -2209,8 +2251,12 @@ void GDScriptAnalyzer::resolve_for(GDScriptParser::ForNode *p_for) {
} 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);
+ if (p_for->list) {
+ if (p_for->list->type == GDScriptParser::Node::ARRAY) {
+ update_array_literal_element_type(static_cast<GDScriptParser::ArrayNode *>(p_for->list), specified_type);
+ } else if (p_for->list->type == GDScriptParser::Node::DICTIONARY) {
+ update_dictionary_literal_element_type(static_cast<GDScriptParser::DictionaryNode *>(p_for->list), specified_type, GDScriptParser::DataType::get_variant_type());
+ }
}
}
p_for->variable->set_datatype(specified_type);
@@ -2412,6 +2458,9 @@ void GDScriptAnalyzer::resolve_return(GDScriptParser::ReturnNode *p_return) {
} else {
if (p_return->return_value->type == GDScriptParser::Node::ARRAY && has_expected_type && expected_type.has_container_element_type(0)) {
update_array_literal_element_type(static_cast<GDScriptParser::ArrayNode *>(p_return->return_value), expected_type.get_container_element_type(0));
+ } else if (p_return->return_value->type == GDScriptParser::Node::DICTIONARY && has_expected_type && expected_type.has_container_element_types()) {
+ update_dictionary_literal_element_type(static_cast<GDScriptParser::DictionaryNode *>(p_return->return_value),
+ expected_type.get_container_element_type_or_variant(0), expected_type.get_container_element_type_or_variant(1));
}
if (has_expected_type && expected_type.is_hard_type() && p_return->return_value->is_constant) {
update_const_expression_builtin_type(p_return->return_value, expected_type, "return");
@@ -2658,6 +2707,54 @@ void GDScriptAnalyzer::update_array_literal_element_type(GDScriptParser::ArrayNo
p_array->set_datatype(array_type);
}
+// When a dictionary literal is stored (or passed as function argument) to a typed context, we then assume the dictionary is typed.
+// This function determines which type is that (if any).
+void GDScriptAnalyzer::update_dictionary_literal_element_type(GDScriptParser::DictionaryNode *p_dictionary, const GDScriptParser::DataType &p_key_element_type, const GDScriptParser::DataType &p_value_element_type) {
+ GDScriptParser::DataType expected_key_type = p_key_element_type;
+ GDScriptParser::DataType expected_value_type = p_value_element_type;
+ expected_key_type.container_element_types.clear(); // Nested types (like `Dictionary[String, Array[int]]`) are not currently supported.
+ expected_value_type.container_element_types.clear();
+
+ for (int i = 0; i < p_dictionary->elements.size(); i++) {
+ GDScriptParser::ExpressionNode *key_element_node = p_dictionary->elements[i].key;
+ if (key_element_node->is_constant) {
+ update_const_expression_builtin_type(key_element_node, expected_key_type, "include");
+ }
+ const GDScriptParser::DataType &actual_key_type = key_element_node->get_datatype();
+ if (actual_key_type.has_no_type() || actual_key_type.is_variant() || !actual_key_type.is_hard_type()) {
+ mark_node_unsafe(key_element_node);
+ } else if (!is_type_compatible(expected_key_type, actual_key_type, true, p_dictionary)) {
+ if (is_type_compatible(actual_key_type, expected_key_type)) {
+ mark_node_unsafe(key_element_node);
+ } else {
+ push_error(vformat(R"(Cannot have a key of type "%s" in a dictionary of type "Dictionary[%s, %s]".)", actual_key_type.to_string(), expected_key_type.to_string(), expected_value_type.to_string()), key_element_node);
+ return;
+ }
+ }
+
+ GDScriptParser::ExpressionNode *value_element_node = p_dictionary->elements[i].value;
+ if (value_element_node->is_constant) {
+ update_const_expression_builtin_type(value_element_node, expected_value_type, "include");
+ }
+ const GDScriptParser::DataType &actual_value_type = value_element_node->get_datatype();
+ if (actual_value_type.has_no_type() || actual_value_type.is_variant() || !actual_value_type.is_hard_type()) {
+ mark_node_unsafe(value_element_node);
+ } else if (!is_type_compatible(expected_value_type, actual_value_type, true, p_dictionary)) {
+ if (is_type_compatible(actual_value_type, expected_value_type)) {
+ mark_node_unsafe(value_element_node);
+ } else {
+ push_error(vformat(R"(Cannot have a value of type "%s" in a dictionary of type "Dictionary[%s, %s]".)", actual_value_type.to_string(), expected_key_type.to_string(), expected_value_type.to_string()), value_element_node);
+ return;
+ }
+ }
+ }
+
+ GDScriptParser::DataType dictionary_type = p_dictionary->get_datatype();
+ dictionary_type.set_container_element_type(0, expected_key_type);
+ dictionary_type.set_container_element_type(1, expected_value_type);
+ p_dictionary->set_datatype(dictionary_type);
+}
+
void GDScriptAnalyzer::reduce_assignment(GDScriptParser::AssignmentNode *p_assignment) {
reduce_expression(p_assignment->assigned_value);
@@ -2750,9 +2847,12 @@ 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.
+ // Check if assigned value is an array/dictionary literal, so we can make it a typed container too if appropriate.
if (p_assignment->assigned_value->type == GDScriptParser::Node::ARRAY && assignee_type.is_hard_type() && assignee_type.has_container_element_type(0)) {
update_array_literal_element_type(static_cast<GDScriptParser::ArrayNode *>(p_assignment->assigned_value), assignee_type.get_container_element_type(0));
+ } else if (p_assignment->assigned_value->type == GDScriptParser::Node::DICTIONARY && assignee_type.is_hard_type() && assignee_type.has_container_element_types()) {
+ update_dictionary_literal_element_type(static_cast<GDScriptParser::DictionaryNode *>(p_assignment->assigned_value),
+ assignee_type.get_container_element_type_or_variant(0), assignee_type.get_container_element_type_or_variant(1));
}
if (p_assignment->operation == GDScriptParser::AssignmentNode::OP_NONE && assignee_type.is_hard_type() && p_assignment->assigned_value->is_constant) {
@@ -2830,8 +2930,8 @@ void GDScriptAnalyzer::reduce_assignment(GDScriptParser::AssignmentNode *p_assig
// weak non-variant assignee and incompatible result
downgrades_assignee = true;
}
- } else if (assignee_type.has_container_element_type(0) && !op_type.has_container_element_type(0)) {
- // typed array assignee and untyped array result
+ } else if ((assignee_type.has_container_element_type(0) && !op_type.has_container_element_type(0)) || (assignee_type.has_container_element_type(1) && !op_type.has_container_element_type(1))) {
+ // Typed assignee and untyped result.
mark_node_unsafe(p_assignment);
}
}
@@ -3029,10 +3129,13 @@ const char *check_for_renamed_identifier(String identifier, GDScriptParser::Node
void GDScriptAnalyzer::reduce_call(GDScriptParser::CallNode *p_call, bool p_is_await, bool p_is_root) {
bool all_is_constant = true;
HashMap<int, GDScriptParser::ArrayNode *> arrays; // For array literal to potentially type when passing.
+ HashMap<int, GDScriptParser::DictionaryNode *> dictionaries; // Same, but for dictionaries.
for (int i = 0; i < p_call->arguments.size(); i++) {
reduce_expression(p_call->arguments[i]);
if (p_call->arguments[i]->type == GDScriptParser::Node::ARRAY) {
arrays[i] = static_cast<GDScriptParser::ArrayNode *>(p_call->arguments[i]);
+ } else if (p_call->arguments[i]->type == GDScriptParser::Node::DICTIONARY) {
+ dictionaries[i] = static_cast<GDScriptParser::DictionaryNode *>(p_call->arguments[i]);
}
all_is_constant = all_is_constant && p_call->arguments[i]->is_constant;
}
@@ -3225,6 +3328,26 @@ 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);
}
}
+
+#ifdef DEBUG_ENABLED
+ // Consider `Signal(self, "my_signal")` as an implicit use of the signal.
+ if (builtin_type == Variant::SIGNAL && p_call->arguments.size() >= 2) {
+ const GDScriptParser::ExpressionNode *object_arg = p_call->arguments[0];
+ if (object_arg && object_arg->type == GDScriptParser::Node::SELF) {
+ const GDScriptParser::ExpressionNode *signal_arg = p_call->arguments[1];
+ if (signal_arg && signal_arg->is_constant) {
+ const StringName &signal_name = signal_arg->reduced_value;
+ if (parser->current_class->has_member(signal_name)) {
+ const GDScriptParser::ClassNode::Member &member = parser->current_class->get_member(signal_name);
+ if (member.type == GDScriptParser::ClassNode::Member::SIGNAL) {
+ member.signal->usages++;
+ }
+ }
+ }
+ }
+ }
+#endif
+
p_call->set_datatype(call_type);
return;
} else if (GDScriptUtilityFunctions::function_exists(function_name)) {
@@ -3417,6 +3540,14 @@ void GDScriptAnalyzer::reduce_call(GDScriptParser::CallNode *p_call, bool p_is_a
update_array_literal_element_type(E.value, par_types.get(index).get_container_element_type(0));
}
}
+ for (const KeyValue<int, GDScriptParser::DictionaryNode *> &E : dictionaries) {
+ int index = E.key;
+ if (index < par_types.size() && par_types.get(index).is_hard_type() && par_types.get(index).has_container_element_types()) {
+ GDScriptParser::DataType key = par_types.get(index).get_container_element_type_or_variant(0);
+ GDScriptParser::DataType value = par_types.get(index).get_container_element_type_or_variant(1);
+ update_dictionary_literal_element_type(E.value, key, value);
+ }
+ }
validate_call_arg(par_types, default_arg_count, method_flags.has_flag(METHOD_FLAG_VARARG), p_call);
if (base_type.kind == GDScriptParser::DataType::ENUM && base_type.is_meta_type) {
@@ -3459,6 +3590,20 @@ void GDScriptAnalyzer::reduce_call(GDScriptParser::CallNode *p_call, bool p_is_a
parser->push_warning(p_call, GDScriptWarning::STATIC_CALLED_ON_INSTANCE, p_call->function_name, caller_type);
}
+
+ // Consider `emit_signal()`, `connect()`, and `disconnect()` as implicit uses of the signal.
+ if (is_self && (p_call->function_name == SNAME("emit_signal") || p_call->function_name == SNAME("connect") || p_call->function_name == SNAME("disconnect")) && !p_call->arguments.is_empty()) {
+ const GDScriptParser::ExpressionNode *signal_arg = p_call->arguments[0];
+ if (signal_arg && signal_arg->is_constant) {
+ const StringName &signal_name = signal_arg->reduced_value;
+ if (parser->current_class->has_member(signal_name)) {
+ const GDScriptParser::ClassNode::Member &member = parser->current_class->get_member(signal_name);
+ if (member.type == GDScriptParser::ClassNode::Member::SIGNAL) {
+ member.signal->usages++;
+ }
+ }
+ }
+ }
#endif // DEBUG_ENABLED
call_type = return_type;
@@ -3547,6 +3692,11 @@ void GDScriptAnalyzer::reduce_cast(GDScriptParser::CastNode *p_cast) {
update_array_literal_element_type(static_cast<GDScriptParser::ArrayNode *>(p_cast->operand), cast_type.get_container_element_type(0));
}
+ if (p_cast->operand->type == GDScriptParser::Node::DICTIONARY && cast_type.has_container_element_types()) {
+ update_dictionary_literal_element_type(static_cast<GDScriptParser::DictionaryNode *>(p_cast->operand),
+ cast_type.get_container_element_type_or_variant(0), cast_type.get_container_element_type_or_variant(1));
+ }
+
if (!cast_type.is_variant()) {
GDScriptParser::DataType op_type = p_cast->operand->get_datatype();
if (op_type.is_variant() || !op_type.is_hard_type()) {
@@ -4571,10 +4721,23 @@ void GDScriptAnalyzer::reduce_subscript(GDScriptParser::SubscriptNode *p_subscri
reduce_identifier_from_base(p_subscript->attribute, &base_type);
GDScriptParser::DataType attr_type = p_subscript->attribute->get_datatype();
if (attr_type.is_set()) {
- valid = !attr_type.is_pseudo_type || p_can_be_pseudo_type;
- result_type = attr_type;
- p_subscript->is_constant = p_subscript->attribute->is_constant;
- p_subscript->reduced_value = p_subscript->attribute->reduced_value;
+ if (base_type.builtin_type == Variant::DICTIONARY && base_type.has_container_element_types()) {
+ Variant::Type key_type = base_type.get_container_element_type_or_variant(0).builtin_type;
+ valid = key_type == Variant::NIL || key_type == Variant::STRING || key_type == Variant::STRING_NAME;
+ if (base_type.has_container_element_type(1)) {
+ result_type = base_type.get_container_element_type(1);
+ result_type.type_source = base_type.type_source;
+ } else {
+ result_type.builtin_type = Variant::NIL;
+ result_type.kind = GDScriptParser::DataType::VARIANT;
+ result_type.type_source = GDScriptParser::DataType::UNDETECTED;
+ }
+ } else {
+ valid = !attr_type.is_pseudo_type || p_can_be_pseudo_type;
+ result_type = attr_type;
+ p_subscript->is_constant = p_subscript->attribute->is_constant;
+ p_subscript->reduced_value = p_subscript->attribute->reduced_value;
+ }
} else if (!base_type.is_meta_type || !base_type.is_constant) {
valid = base_type.kind != GDScriptParser::DataType::BUILTIN;
#ifdef DEBUG_ENABLED
@@ -4681,8 +4844,40 @@ void GDScriptAnalyzer::reduce_subscript(GDScriptParser::SubscriptNode *p_subscri
case Variant::SIGNAL:
case Variant::STRING_NAME:
break;
- // Here for completeness.
+ // Support depends on if the dictionary has a typed key, otherwise anything is valid.
case Variant::DICTIONARY:
+ if (base_type.has_container_element_type(0)) {
+ GDScriptParser::DataType key_type = base_type.get_container_element_type(0);
+ switch (index_type.builtin_type) {
+ // Null value will be treated as an empty object, allow.
+ case Variant::NIL:
+ error = key_type.builtin_type != Variant::OBJECT;
+ break;
+ // Objects are parsed for validity in a similar manner to container types.
+ case Variant::OBJECT:
+ if (key_type.builtin_type == Variant::OBJECT) {
+ error = !key_type.can_reference(index_type);
+ } else {
+ error = key_type.builtin_type != Variant::NIL;
+ }
+ break;
+ // String and StringName interchangeable in this context.
+ case Variant::STRING:
+ case Variant::STRING_NAME:
+ error = key_type.builtin_type != Variant::STRING_NAME && key_type.builtin_type != Variant::STRING;
+ break;
+ // Ints are valid indices for floats, but not the other way around.
+ case Variant::INT:
+ error = key_type.builtin_type != Variant::INT && key_type.builtin_type != Variant::FLOAT;
+ break;
+ // All other cases require the types to match exactly.
+ default:
+ error = key_type.builtin_type != index_type.builtin_type;
+ break;
+ }
+ }
+ break;
+ // Here for completeness.
case Variant::VARIANT_MAX:
break;
}
@@ -4771,7 +4966,6 @@ void GDScriptAnalyzer::reduce_subscript(GDScriptParser::SubscriptNode *p_subscri
case Variant::PROJECTION:
case Variant::PLANE:
case Variant::COLOR:
- case Variant::DICTIONARY:
case Variant::OBJECT:
result_type.kind = GDScriptParser::DataType::VARIANT;
result_type.type_source = GDScriptParser::DataType::UNDETECTED;
@@ -4786,6 +4980,16 @@ void GDScriptAnalyzer::reduce_subscript(GDScriptParser::SubscriptNode *p_subscri
result_type.type_source = GDScriptParser::DataType::UNDETECTED;
}
break;
+ // Can have two element types, but we only care about the value.
+ case Variant::DICTIONARY:
+ if (base_type.has_container_element_type(1)) {
+ result_type = base_type.get_container_element_type(1);
+ result_type.type_source = base_type.type_source;
+ } else {
+ result_type.kind = GDScriptParser::DataType::VARIANT;
+ result_type.type_source = GDScriptParser::DataType::UNDETECTED;
+ }
+ break;
// Here for completeness.
case Variant::VARIANT_MAX:
break;
@@ -4965,7 +5169,9 @@ Variant GDScriptAnalyzer::make_array_reduced_value(GDScriptParser::ArrayNode *p_
}
Variant GDScriptAnalyzer::make_dictionary_reduced_value(GDScriptParser::DictionaryNode *p_dictionary, bool &is_reduced) {
- Dictionary dictionary;
+ Dictionary dictionary = p_dictionary->get_datatype().has_container_element_types()
+ ? make_dictionary_from_element_datatype(p_dictionary->get_datatype().get_container_element_type_or_variant(0), p_dictionary->get_datatype().get_container_element_type_or_variant(1))
+ : Dictionary();
for (int i = 0; i < p_dictionary->elements.size(); i++) {
const GDScriptParser::DictionaryNode::Pair &element = p_dictionary->elements[i];
@@ -5052,6 +5258,49 @@ Array GDScriptAnalyzer::make_array_from_element_datatype(const GDScriptParser::D
return array;
}
+Dictionary GDScriptAnalyzer::make_dictionary_from_element_datatype(const GDScriptParser::DataType &p_key_element_datatype, const GDScriptParser::DataType &p_value_element_datatype, const GDScriptParser::Node *p_source_node) {
+ Dictionary dictionary;
+ StringName key_name;
+ Variant key_script;
+ StringName value_name;
+ Variant value_script;
+
+ if (p_key_element_datatype.builtin_type == Variant::OBJECT) {
+ Ref<Script> script_type = p_key_element_datatype.script_type;
+ if (p_key_element_datatype.kind == GDScriptParser::DataType::CLASS && script_type.is_null()) {
+ Error err = OK;
+ Ref<GDScript> scr = get_depended_shallow_script(p_key_element_datatype.script_path, err);
+ if (err) {
+ push_error(vformat(R"(Error while getting cache for script "%s".)", p_key_element_datatype.script_path), p_source_node);
+ return dictionary;
+ }
+ script_type.reference_ptr(scr->find_class(p_key_element_datatype.class_type->fqcn));
+ }
+
+ key_name = p_key_element_datatype.native_type;
+ key_script = script_type;
+ }
+
+ if (p_value_element_datatype.builtin_type == Variant::OBJECT) {
+ Ref<Script> script_type = p_value_element_datatype.script_type;
+ if (p_value_element_datatype.kind == GDScriptParser::DataType::CLASS && script_type.is_null()) {
+ Error err = OK;
+ Ref<GDScript> scr = get_depended_shallow_script(p_value_element_datatype.script_path, err);
+ if (err) {
+ push_error(vformat(R"(Error while getting cache for script "%s".)", p_value_element_datatype.script_path), p_source_node);
+ return dictionary;
+ }
+ script_type.reference_ptr(scr->find_class(p_value_element_datatype.class_type->fqcn));
+ }
+
+ value_name = p_value_element_datatype.native_type;
+ value_script = script_type;
+ }
+
+ dictionary.set_typed(p_key_element_datatype.builtin_type, key_name, key_script, p_value_element_datatype.builtin_type, value_name, value_script);
+ return dictionary;
+}
+
Variant GDScriptAnalyzer::make_variable_default_value(GDScriptParser::VariableNode *p_variable) {
Variant result = Variant();
@@ -5067,6 +5316,10 @@ Variant GDScriptAnalyzer::make_variable_default_value(GDScriptParser::VariableNo
if (datatype.kind == GDScriptParser::DataType::BUILTIN && datatype.builtin_type != Variant::OBJECT) {
if (datatype.builtin_type == Variant::ARRAY && datatype.has_container_element_type(0)) {
result = make_array_from_element_datatype(datatype.get_container_element_type(0));
+ } else if (datatype.builtin_type == Variant::DICTIONARY && datatype.has_container_element_types()) {
+ GDScriptParser::DataType key = datatype.get_container_element_type_or_variant(0);
+ GDScriptParser::DataType value = datatype.get_container_element_type_or_variant(1);
+ result = make_dictionary_from_element_datatype(key, value);
} else {
VariantInternal::initialize(&result, datatype.builtin_type);
}
@@ -5095,6 +5348,22 @@ GDScriptParser::DataType GDScriptAnalyzer::type_from_variant(const Variant &p_va
} else if (array.get_typed_builtin() != Variant::NIL) {
result.set_container_element_type(0, type_from_metatype(make_builtin_meta_type((Variant::Type)array.get_typed_builtin())));
}
+ } else if (p_value.get_type() == Variant::DICTIONARY) {
+ const Dictionary &dict = p_value;
+ if (dict.get_typed_key_script()) {
+ result.set_container_element_type(0, type_from_metatype(make_script_meta_type(dict.get_typed_key_script())));
+ } else if (dict.get_typed_key_class_name()) {
+ result.set_container_element_type(0, type_from_metatype(make_native_meta_type(dict.get_typed_key_class_name())));
+ } else if (dict.get_typed_key_builtin() != Variant::NIL) {
+ result.set_container_element_type(0, type_from_metatype(make_builtin_meta_type((Variant::Type)dict.get_typed_key_builtin())));
+ }
+ if (dict.get_typed_value_script()) {
+ result.set_container_element_type(1, type_from_metatype(make_script_meta_type(dict.get_typed_value_script())));
+ } else if (dict.get_typed_value_class_name()) {
+ result.set_container_element_type(1, type_from_metatype(make_native_meta_type(dict.get_typed_value_class_name())));
+ } else if (dict.get_typed_value_builtin() != Variant::NIL) {
+ result.set_container_element_type(1, type_from_metatype(make_builtin_meta_type((Variant::Type)dict.get_typed_value_builtin())));
+ }
} else if (p_value.get_type() == Variant::OBJECT) {
// Object is treated as a native type, not a builtin type.
result.kind = GDScriptParser::DataType::NATIVE;
@@ -5227,6 +5496,60 @@ GDScriptParser::DataType GDScriptAnalyzer::type_from_property(const PropertyInfo
}
elem_type.is_constant = false;
result.set_container_element_type(0, elem_type);
+ } else if (p_property.type == Variant::DICTIONARY && p_property.hint == PROPERTY_HINT_DICTIONARY_TYPE) {
+ // Check element type.
+ StringName key_elem_type_name = p_property.hint_string.get_slice(";", 0);
+ GDScriptParser::DataType key_elem_type;
+ key_elem_type.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT;
+
+ Variant::Type key_elem_builtin_type = GDScriptParser::get_builtin_type(key_elem_type_name);
+ if (key_elem_builtin_type < Variant::VARIANT_MAX) {
+ // Builtin type.
+ key_elem_type.kind = GDScriptParser::DataType::BUILTIN;
+ key_elem_type.builtin_type = key_elem_builtin_type;
+ } else if (class_exists(key_elem_type_name)) {
+ key_elem_type.kind = GDScriptParser::DataType::NATIVE;
+ key_elem_type.builtin_type = Variant::OBJECT;
+ key_elem_type.native_type = key_elem_type_name;
+ } else if (ScriptServer::is_global_class(key_elem_type_name)) {
+ // Just load this as it shouldn't be a GDScript.
+ Ref<Script> script = ResourceLoader::load(ScriptServer::get_global_class_path(key_elem_type_name));
+ key_elem_type.kind = GDScriptParser::DataType::SCRIPT;
+ key_elem_type.builtin_type = Variant::OBJECT;
+ key_elem_type.native_type = script->get_instance_base_type();
+ key_elem_type.script_type = script;
+ } else {
+ ERR_FAIL_V_MSG(result, "Could not find element type from property hint of a typed dictionary.");
+ }
+ key_elem_type.is_constant = false;
+
+ StringName value_elem_type_name = p_property.hint_string.get_slice(";", 1);
+ GDScriptParser::DataType value_elem_type;
+ value_elem_type.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT;
+
+ Variant::Type value_elem_builtin_type = GDScriptParser::get_builtin_type(value_elem_type_name);
+ if (value_elem_builtin_type < Variant::VARIANT_MAX) {
+ // Builtin type.
+ value_elem_type.kind = GDScriptParser::DataType::BUILTIN;
+ value_elem_type.builtin_type = value_elem_builtin_type;
+ } else if (class_exists(value_elem_type_name)) {
+ value_elem_type.kind = GDScriptParser::DataType::NATIVE;
+ value_elem_type.builtin_type = Variant::OBJECT;
+ value_elem_type.native_type = value_elem_type_name;
+ } else if (ScriptServer::is_global_class(value_elem_type_name)) {
+ // Just load this as it shouldn't be a GDScript.
+ Ref<Script> script = ResourceLoader::load(ScriptServer::get_global_class_path(value_elem_type_name));
+ value_elem_type.kind = GDScriptParser::DataType::SCRIPT;
+ value_elem_type.builtin_type = Variant::OBJECT;
+ value_elem_type.native_type = script->get_instance_base_type();
+ value_elem_type.script_type = script;
+ } else {
+ ERR_FAIL_V_MSG(result, "Could not find element type from property hint of a typed dictionary.");
+ }
+ value_elem_type.is_constant = false;
+
+ result.set_container_element_type(0, key_elem_type);
+ result.set_container_element_type(1, value_elem_type);
} else if (p_property.type == Variant::INT) {
// Check if it's enum.
if ((p_property.usage & PROPERTY_USAGE_CLASS_IS_ENUM) && p_property.class_name != StringName()) {
@@ -5647,6 +5970,15 @@ bool GDScriptAnalyzer::check_type_compatibility(const GDScriptParser::DataType &
valid = p_target.get_container_element_type(0) == p_source.get_container_element_type(0);
}
}
+ if (valid && p_target.builtin_type == Variant::DICTIONARY && p_source.builtin_type == Variant::DICTIONARY) {
+ // Check the element types.
+ if (p_target.has_container_element_type(0) && p_source.has_container_element_type(0)) {
+ valid = p_target.get_container_element_type(0) == p_source.get_container_element_type(0);
+ }
+ if (valid && p_target.has_container_element_type(1) && p_source.has_container_element_type(1)) {
+ valid = p_target.get_container_element_type(1) == p_source.get_container_element_type(1);
+ }
+ }
return valid;
}
diff --git a/modules/gdscript/gdscript_analyzer.h b/modules/gdscript/gdscript_analyzer.h
index 25e5aa9a2c..3b781409a4 100644
--- a/modules/gdscript/gdscript_analyzer.h
+++ b/modules/gdscript/gdscript_analyzer.h
@@ -125,6 +125,7 @@ class GDScriptAnalyzer {
// Helpers.
Array make_array_from_element_datatype(const GDScriptParser::DataType &p_element_datatype, const GDScriptParser::Node *p_source_node = nullptr);
+ Dictionary make_dictionary_from_element_datatype(const GDScriptParser::DataType &p_key_element_datatype, const GDScriptParser::DataType &p_value_element_datatype, const GDScriptParser::Node *p_source_node = nullptr);
GDScriptParser::DataType type_from_variant(const Variant &p_value, const GDScriptParser::Node *p_source);
static GDScriptParser::DataType type_from_metatype(const GDScriptParser::DataType &p_meta_type);
GDScriptParser::DataType type_from_property(const PropertyInfo &p_property, bool p_is_arg = false, bool p_is_readonly = false) const;
@@ -137,6 +138,7 @@ class GDScriptAnalyzer {
GDScriptParser::DataType get_operation_type(Variant::Operator p_operation, const GDScriptParser::DataType &p_a, bool &r_valid, const GDScriptParser::Node *p_source);
void update_const_expression_builtin_type(GDScriptParser::ExpressionNode *p_expression, const GDScriptParser::DataType &p_type, const char *p_usage, bool p_is_cast = false);
void update_array_literal_element_type(GDScriptParser::ArrayNode *p_array, const GDScriptParser::DataType &p_element_type);
+ void update_dictionary_literal_element_type(GDScriptParser::DictionaryNode *p_dictionary, const GDScriptParser::DataType &p_key_element_type, const GDScriptParser::DataType &p_value_element_type);
bool is_type_compatible(const GDScriptParser::DataType &p_target, const GDScriptParser::DataType &p_source, bool p_allow_implicit_conversion = false, const GDScriptParser::Node *p_source_node = nullptr);
void push_error(const String &p_message, const GDScriptParser::Node *p_origin = nullptr);
void mark_node_unsafe(const GDScriptParser::Node *p_node);
diff --git a/modules/gdscript/gdscript_byte_codegen.cpp b/modules/gdscript/gdscript_byte_codegen.cpp
index 4cda3d3037..b77c641eb5 100644
--- a/modules/gdscript/gdscript_byte_codegen.cpp
+++ b/modules/gdscript/gdscript_byte_codegen.cpp
@@ -634,6 +634,18 @@ void GDScriptByteCodeGenerator::write_type_test(const Address &p_target, const A
append(get_constant_pos(element_type.script_type) | (GDScriptFunction::ADDR_TYPE_CONSTANT << GDScriptFunction::ADDR_BITS));
append(element_type.builtin_type);
append(element_type.native_type);
+ } else if (p_type.builtin_type == Variant::DICTIONARY && p_type.has_container_element_types()) {
+ const GDScriptDataType &key_element_type = p_type.get_container_element_type_or_variant(0);
+ const GDScriptDataType &value_element_type = p_type.get_container_element_type_or_variant(1);
+ append_opcode(GDScriptFunction::OPCODE_TYPE_TEST_DICTIONARY);
+ append(p_target);
+ append(p_source);
+ append(get_constant_pos(key_element_type.script_type) | (GDScriptFunction::ADDR_TYPE_CONSTANT << GDScriptFunction::ADDR_BITS));
+ append(get_constant_pos(value_element_type.script_type) | (GDScriptFunction::ADDR_TYPE_CONSTANT << GDScriptFunction::ADDR_BITS));
+ append(key_element_type.builtin_type);
+ append(key_element_type.native_type);
+ append(value_element_type.builtin_type);
+ append(value_element_type.native_type);
} else {
append_opcode(GDScriptFunction::OPCODE_TYPE_TEST_BUILTIN);
append(p_target);
@@ -889,6 +901,18 @@ void GDScriptByteCodeGenerator::write_assign_with_conversion(const Address &p_ta
append(get_constant_pos(element_type.script_type) | (GDScriptFunction::ADDR_TYPE_CONSTANT << GDScriptFunction::ADDR_BITS));
append(element_type.builtin_type);
append(element_type.native_type);
+ } else if (p_target.type.builtin_type == Variant::DICTIONARY && p_target.type.has_container_element_types()) {
+ const GDScriptDataType &key_type = p_target.type.get_container_element_type_or_variant(0);
+ const GDScriptDataType &value_type = p_target.type.get_container_element_type_or_variant(1);
+ append_opcode(GDScriptFunction::OPCODE_ASSIGN_TYPED_DICTIONARY);
+ append(p_target);
+ append(p_source);
+ append(get_constant_pos(key_type.script_type) | (GDScriptFunction::ADDR_TYPE_CONSTANT << GDScriptFunction::ADDR_BITS));
+ append(get_constant_pos(value_type.script_type) | (GDScriptFunction::ADDR_TYPE_CONSTANT << GDScriptFunction::ADDR_BITS));
+ append(key_type.builtin_type);
+ append(key_type.native_type);
+ append(value_type.builtin_type);
+ append(value_type.native_type);
} else {
append_opcode(GDScriptFunction::OPCODE_ASSIGN_TYPED_BUILTIN);
append(p_target);
@@ -935,6 +959,18 @@ void GDScriptByteCodeGenerator::write_assign(const Address &p_target, const Addr
append(get_constant_pos(element_type.script_type) | (GDScriptFunction::ADDR_TYPE_CONSTANT << GDScriptFunction::ADDR_BITS));
append(element_type.builtin_type);
append(element_type.native_type);
+ } else if (p_target.type.kind == GDScriptDataType::BUILTIN && p_target.type.builtin_type == Variant::DICTIONARY && p_target.type.has_container_element_types()) {
+ const GDScriptDataType &key_type = p_target.type.get_container_element_type_or_variant(0);
+ const GDScriptDataType &value_type = p_target.type.get_container_element_type_or_variant(1);
+ append_opcode(GDScriptFunction::OPCODE_ASSIGN_TYPED_DICTIONARY);
+ append(p_target);
+ append(p_source);
+ append(get_constant_pos(key_type.script_type) | (GDScriptFunction::ADDR_TYPE_CONSTANT << GDScriptFunction::ADDR_BITS));
+ append(get_constant_pos(value_type.script_type) | (GDScriptFunction::ADDR_TYPE_CONSTANT << GDScriptFunction::ADDR_BITS));
+ append(key_type.builtin_type);
+ append(key_type.native_type);
+ append(value_type.builtin_type);
+ append(value_type.native_type);
} else if (p_target.type.kind == GDScriptDataType::BUILTIN && p_source.type.kind == GDScriptDataType::BUILTIN && p_target.type.builtin_type != p_source.type.builtin_type) {
// Need conversion.
append_opcode(GDScriptFunction::OPCODE_ASSIGN_TYPED_BUILTIN);
@@ -1434,6 +1470,23 @@ void GDScriptByteCodeGenerator::write_construct_dictionary(const Address &p_targ
ct.cleanup();
}
+void GDScriptByteCodeGenerator::write_construct_typed_dictionary(const Address &p_target, const GDScriptDataType &p_key_type, const GDScriptDataType &p_value_type, const Vector<Address> &p_arguments) {
+ append_opcode_and_argcount(GDScriptFunction::OPCODE_CONSTRUCT_TYPED_DICTIONARY, 3 + p_arguments.size());
+ for (int i = 0; i < p_arguments.size(); i++) {
+ append(p_arguments[i]);
+ }
+ CallTarget ct = get_call_target(p_target);
+ append(ct.target);
+ append(get_constant_pos(p_key_type.script_type) | (GDScriptFunction::ADDR_TYPE_CONSTANT << GDScriptFunction::ADDR_BITS));
+ append(get_constant_pos(p_value_type.script_type) | (GDScriptFunction::ADDR_TYPE_CONSTANT << GDScriptFunction::ADDR_BITS));
+ append(p_arguments.size() / 2); // This is number of key-value pairs, so only half of actual arguments.
+ append(p_key_type.builtin_type);
+ append(p_key_type.native_type);
+ append(p_value_type.builtin_type);
+ append(p_value_type.native_type);
+ ct.cleanup();
+}
+
void GDScriptByteCodeGenerator::write_await(const Address &p_target, const Address &p_operand) {
append_opcode(GDScriptFunction::OPCODE_AWAIT);
append(p_operand);
@@ -1711,6 +1764,19 @@ void GDScriptByteCodeGenerator::write_return(const Address &p_return_value) {
append(get_constant_pos(element_type.script_type) | (GDScriptFunction::ADDR_TYPE_CONSTANT << GDScriptFunction::ADDR_BITS));
append(element_type.builtin_type);
append(element_type.native_type);
+ } else if (function->return_type.kind == GDScriptDataType::BUILTIN && function->return_type.builtin_type == Variant::DICTIONARY &&
+ function->return_type.has_container_element_types()) {
+ // Typed dictionary.
+ const GDScriptDataType &key_type = function->return_type.get_container_element_type_or_variant(0);
+ const GDScriptDataType &value_type = function->return_type.get_container_element_type_or_variant(1);
+ append_opcode(GDScriptFunction::OPCODE_RETURN_TYPED_DICTIONARY);
+ append(p_return_value);
+ append(get_constant_pos(key_type.script_type) | (GDScriptFunction::ADDR_TYPE_CONSTANT << GDScriptFunction::ADDR_BITS));
+ append(get_constant_pos(value_type.script_type) | (GDScriptFunction::ADDR_TYPE_CONSTANT << GDScriptFunction::ADDR_BITS));
+ append(key_type.builtin_type);
+ append(key_type.native_type);
+ append(value_type.builtin_type);
+ append(value_type.native_type);
} else if (function->return_type.kind == GDScriptDataType::BUILTIN && p_return_value.type.kind == GDScriptDataType::BUILTIN && function->return_type.builtin_type != p_return_value.type.builtin_type) {
// Add conversion.
append_opcode(GDScriptFunction::OPCODE_RETURN_TYPED_BUILTIN);
@@ -1735,6 +1801,17 @@ void GDScriptByteCodeGenerator::write_return(const Address &p_return_value) {
append(get_constant_pos(element_type.script_type) | (GDScriptFunction::ADDR_TYPE_CONSTANT << GDScriptFunction::ADDR_BITS));
append(element_type.builtin_type);
append(element_type.native_type);
+ } else if (function->return_type.builtin_type == Variant::DICTIONARY && function->return_type.has_container_element_types()) {
+ const GDScriptDataType &key_type = function->return_type.get_container_element_type_or_variant(0);
+ const GDScriptDataType &value_type = function->return_type.get_container_element_type_or_variant(1);
+ append_opcode(GDScriptFunction::OPCODE_RETURN_TYPED_DICTIONARY);
+ append(p_return_value);
+ append(get_constant_pos(key_type.script_type) | (GDScriptFunction::ADDR_TYPE_CONSTANT << GDScriptFunction::ADDR_BITS));
+ append(get_constant_pos(value_type.script_type) | (GDScriptFunction::ADDR_TYPE_CONSTANT << GDScriptFunction::ADDR_BITS));
+ append(key_type.builtin_type);
+ append(key_type.native_type);
+ append(value_type.builtin_type);
+ append(value_type.native_type);
} else {
append_opcode(GDScriptFunction::OPCODE_RETURN_TYPED_BUILTIN);
append(p_return_value);
@@ -1803,6 +1880,13 @@ void GDScriptByteCodeGenerator::clear_address(const Address &p_address) {
case Variant::BOOL:
write_assign_false(p_address);
break;
+ case Variant::DICTIONARY:
+ if (p_address.type.has_container_element_types()) {
+ write_construct_typed_dictionary(p_address, p_address.type.get_container_element_type_or_variant(0), p_address.type.get_container_element_type_or_variant(1), Vector<GDScriptCodeGenerator::Address>());
+ } else {
+ write_construct(p_address, p_address.type.builtin_type, Vector<GDScriptCodeGenerator::Address>());
+ }
+ break;
case Variant::ARRAY:
if (p_address.type.has_container_element_type(0)) {
write_construct_typed_array(p_address, p_address.type.get_container_element_type(0), Vector<GDScriptCodeGenerator::Address>());
diff --git a/modules/gdscript/gdscript_byte_codegen.h b/modules/gdscript/gdscript_byte_codegen.h
index 34f56a2f5c..6303db71fd 100644
--- a/modules/gdscript/gdscript_byte_codegen.h
+++ b/modules/gdscript/gdscript_byte_codegen.h
@@ -529,6 +529,7 @@ public:
virtual void write_construct_array(const Address &p_target, const Vector<Address> &p_arguments) override;
virtual void write_construct_typed_array(const Address &p_target, const GDScriptDataType &p_element_type, const Vector<Address> &p_arguments) override;
virtual void write_construct_dictionary(const Address &p_target, const Vector<Address> &p_arguments) override;
+ virtual void write_construct_typed_dictionary(const Address &p_target, const GDScriptDataType &p_key_type, const GDScriptDataType &p_value_type, const Vector<Address> &p_arguments) override;
virtual void write_await(const Address &p_target, const Address &p_operand) override;
virtual void write_if(const Address &p_condition) override;
virtual void write_else() override;
diff --git a/modules/gdscript/gdscript_codegen.h b/modules/gdscript/gdscript_codegen.h
index c1c0b61395..f3c4acf1c3 100644
--- a/modules/gdscript/gdscript_codegen.h
+++ b/modules/gdscript/gdscript_codegen.h
@@ -142,6 +142,7 @@ public:
virtual void write_construct_array(const Address &p_target, const Vector<Address> &p_arguments) = 0;
virtual void write_construct_typed_array(const Address &p_target, const GDScriptDataType &p_element_type, const Vector<Address> &p_arguments) = 0;
virtual void write_construct_dictionary(const Address &p_target, const Vector<Address> &p_arguments) = 0;
+ virtual void write_construct_typed_dictionary(const Address &p_target, const GDScriptDataType &p_key_type, const GDScriptDataType &p_value_type, const Vector<Address> &p_arguments) = 0;
virtual void write_await(const Address &p_target, const Address &p_operand) = 0;
virtual void write_if(const Address &p_condition) = 0;
virtual void write_else() = 0;
diff --git a/modules/gdscript/gdscript_compiler.cpp b/modules/gdscript/gdscript_compiler.cpp
index d8b44a558f..eebf282633 100644
--- a/modules/gdscript/gdscript_compiler.cpp
+++ b/modules/gdscript/gdscript_compiler.cpp
@@ -532,10 +532,7 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code
Vector<GDScriptCodeGenerator::Address> elements;
// Create the result temporary first since it's the last to be killed.
- GDScriptDataType dict_type;
- dict_type.has_type = true;
- dict_type.kind = GDScriptDataType::BUILTIN;
- dict_type.builtin_type = Variant::DICTIONARY;
+ GDScriptDataType dict_type = _gdtype_from_datatype(dn->get_datatype(), codegen.script);
GDScriptCodeGenerator::Address result = codegen.add_temporary(dict_type);
for (int i = 0; i < dn->elements.size(); i++) {
@@ -566,7 +563,11 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code
elements.push_back(element);
}
- gen->write_construct_dictionary(result, elements);
+ if (dict_type.has_container_element_types()) {
+ gen->write_construct_typed_dictionary(result, dict_type.get_container_element_type_or_variant(0), dict_type.get_container_element_type_or_variant(1), elements);
+ } else {
+ gen->write_construct_dictionary(result, elements);
+ }
for (int i = 0; i < elements.size(); i++) {
if (elements[i].mode == GDScriptCodeGenerator::Address::TEMPORARY) {
@@ -2325,8 +2326,11 @@ GDScriptFunction *GDScriptCompiler::_parse_function(Error &r_error, GDScript *p_
GDScriptCodeGenerator::Address dst_address(GDScriptCodeGenerator::Address::MEMBER, codegen.script->member_indices[field->identifier->name].index, field_type);
- if (field_type.has_container_element_type(0)) {
+ if (field_type.builtin_type == Variant::ARRAY && field_type.has_container_element_type(0)) {
codegen.generator->write_construct_typed_array(dst_address, field_type.get_container_element_type(0), Vector<GDScriptCodeGenerator::Address>());
+ } else if (field_type.builtin_type == Variant::DICTIONARY && field_type.has_container_element_types()) {
+ codegen.generator->write_construct_typed_dictionary(dst_address, field_type.get_container_element_type_or_variant(0),
+ field_type.get_container_element_type_or_variant(1), Vector<GDScriptCodeGenerator::Address>());
} else if (field_type.kind == GDScriptDataType::BUILTIN) {
codegen.generator->write_construct(dst_address, field_type.builtin_type, Vector<GDScriptCodeGenerator::Address>());
}
@@ -2515,11 +2519,17 @@ GDScriptFunction *GDScriptCompiler::_make_static_initializer(Error &r_error, GDS
if (field_type.has_type) {
codegen.generator->write_newline(field->start_line);
- if (field_type.has_container_element_type(0)) {
+ if (field_type.builtin_type == Variant::ARRAY && field_type.has_container_element_type(0)) {
GDScriptCodeGenerator::Address temp = codegen.add_temporary(field_type);
codegen.generator->write_construct_typed_array(temp, field_type.get_container_element_type(0), Vector<GDScriptCodeGenerator::Address>());
codegen.generator->write_set_static_variable(temp, class_addr, p_script->static_variables_indices[field->identifier->name].index);
codegen.generator->pop_temporary();
+ } else if (field_type.builtin_type == Variant::DICTIONARY && field_type.has_container_element_types()) {
+ GDScriptCodeGenerator::Address temp = codegen.add_temporary(field_type);
+ codegen.generator->write_construct_typed_dictionary(temp, field_type.get_container_element_type_or_variant(0),
+ field_type.get_container_element_type_or_variant(1), Vector<GDScriptCodeGenerator::Address>());
+ codegen.generator->write_set_static_variable(temp, class_addr, p_script->static_variables_indices[field->identifier->name].index);
+ codegen.generator->pop_temporary();
} else if (field_type.kind == GDScriptDataType::BUILTIN) {
GDScriptCodeGenerator::Address temp = codegen.add_temporary(field_type);
codegen.generator->write_construct(temp, field_type.builtin_type, Vector<GDScriptCodeGenerator::Address>());
diff --git a/modules/gdscript/gdscript_disassembler.cpp b/modules/gdscript/gdscript_disassembler.cpp
index 0331045078..bc063693a3 100644
--- a/modules/gdscript/gdscript_disassembler.cpp
+++ b/modules/gdscript/gdscript_disassembler.cpp
@@ -176,6 +176,47 @@ void GDScriptFunction::disassemble(const Vector<String> &p_code_lines) const {
incr += 6;
} break;
+ case OPCODE_TYPE_TEST_DICTIONARY: {
+ text += "type test ";
+ text += DADDR(1);
+ text += " = ";
+ text += DADDR(2);
+ text += " is Dictionary[";
+
+ Ref<Script> key_script_type = get_constant(_code_ptr[ip + 3] & ADDR_MASK);
+ Variant::Type key_builtin_type = (Variant::Type)_code_ptr[ip + 5];
+ StringName key_native_type = get_global_name(_code_ptr[ip + 6]);
+
+ if (key_script_type.is_valid() && key_script_type->is_valid()) {
+ text += "script(";
+ text += GDScript::debug_get_script_name(key_script_type);
+ text += ")";
+ } else if (key_native_type != StringName()) {
+ text += key_native_type;
+ } else {
+ text += Variant::get_type_name(key_builtin_type);
+ }
+
+ text += ", ";
+
+ Ref<Script> value_script_type = get_constant(_code_ptr[ip + 4] & ADDR_MASK);
+ Variant::Type value_builtin_type = (Variant::Type)_code_ptr[ip + 7];
+ StringName value_native_type = get_global_name(_code_ptr[ip + 8]);
+
+ if (value_script_type.is_valid() && value_script_type->is_valid()) {
+ text += "script(";
+ text += GDScript::debug_get_script_name(value_script_type);
+ text += ")";
+ } else if (value_native_type != StringName()) {
+ text += value_native_type;
+ } else {
+ text += Variant::get_type_name(value_builtin_type);
+ }
+
+ text += "]";
+
+ incr += 9;
+ } break;
case OPCODE_TYPE_TEST_NATIVE: {
text += "type test ";
text += DADDR(1);
@@ -399,6 +440,14 @@ void GDScriptFunction::disassemble(const Vector<String> &p_code_lines) const {
incr += 6;
} break;
+ case OPCODE_ASSIGN_TYPED_DICTIONARY: {
+ text += "assign typed dictionary ";
+ text += DADDR(1);
+ text += " = ";
+ text += DADDR(2);
+
+ incr += 9;
+ } break;
case OPCODE_ASSIGN_TYPED_NATIVE: {
text += "assign typed native (";
text += DADDR(3);
@@ -564,6 +613,58 @@ void GDScriptFunction::disassemble(const Vector<String> &p_code_lines) const {
incr += 3 + argc * 2;
} break;
+ case OPCODE_CONSTRUCT_TYPED_DICTIONARY: {
+ int instr_var_args = _code_ptr[++ip];
+ int argc = _code_ptr[ip + 1 + instr_var_args];
+
+ Ref<Script> key_script_type = get_constant(_code_ptr[ip + argc * 2 + 2] & ADDR_MASK);
+ Variant::Type key_builtin_type = (Variant::Type)_code_ptr[ip + argc * 2 + 5];
+ StringName key_native_type = get_global_name(_code_ptr[ip + argc * 2 + 6]);
+
+ String key_type_name;
+ if (key_script_type.is_valid() && key_script_type->is_valid()) {
+ key_type_name = "script(" + GDScript::debug_get_script_name(key_script_type) + ")";
+ } else if (key_native_type != StringName()) {
+ key_type_name = key_native_type;
+ } else {
+ key_type_name = Variant::get_type_name(key_builtin_type);
+ }
+
+ Ref<Script> value_script_type = get_constant(_code_ptr[ip + argc * 2 + 3] & ADDR_MASK);
+ Variant::Type value_builtin_type = (Variant::Type)_code_ptr[ip + argc * 2 + 7];
+ StringName value_native_type = get_global_name(_code_ptr[ip + argc * 2 + 8]);
+
+ String value_type_name;
+ if (value_script_type.is_valid() && value_script_type->is_valid()) {
+ value_type_name = "script(" + GDScript::debug_get_script_name(value_script_type) + ")";
+ } else if (value_native_type != StringName()) {
+ value_type_name = value_native_type;
+ } else {
+ value_type_name = Variant::get_type_name(value_builtin_type);
+ }
+
+ text += "make_typed_dict (";
+ text += key_type_name;
+ text += ", ";
+ text += value_type_name;
+ text += ") ";
+
+ text += DADDR(1 + argc * 2);
+ text += " = {";
+
+ for (int i = 0; i < argc; i++) {
+ if (i > 0) {
+ text += ", ";
+ }
+ text += DADDR(1 + i * 2 + 0);
+ text += ": ";
+ text += DADDR(1 + i * 2 + 1);
+ }
+
+ text += "}";
+
+ incr += 9 + argc * 2;
+ } break;
case OPCODE_CALL:
case OPCODE_CALL_RETURN:
case OPCODE_CALL_ASYNC: {
@@ -978,6 +1079,12 @@ void GDScriptFunction::disassemble(const Vector<String> &p_code_lines) const {
incr += 5;
} break;
+ case OPCODE_RETURN_TYPED_DICTIONARY: {
+ text += "return typed dictionary ";
+ text += DADDR(1);
+
+ incr += 8;
+ } break;
case OPCODE_RETURN_TYPED_NATIVE: {
text += "return typed native (";
text += DADDR(2);
diff --git a/modules/gdscript/gdscript_editor.cpp b/modules/gdscript/gdscript_editor.cpp
index 636339ef1d..524f528f76 100644
--- a/modules/gdscript/gdscript_editor.cpp
+++ b/modules/gdscript/gdscript_editor.cpp
@@ -697,6 +697,10 @@ static String _get_visual_datatype(const PropertyInfo &p_info, bool p_is_arg, co
return _trim_parent_class(class_name, p_base_class);
} else if (p_info.type == Variant::ARRAY && p_info.hint == PROPERTY_HINT_ARRAY_TYPE && !p_info.hint_string.is_empty()) {
return "Array[" + _trim_parent_class(p_info.hint_string, p_base_class) + "]";
+ } else if (p_info.type == Variant::DICTIONARY && p_info.hint == PROPERTY_HINT_DICTIONARY_TYPE && !p_info.hint_string.is_empty()) {
+ const String key = p_info.hint_string.get_slice(";", 0);
+ const String value = p_info.hint_string.get_slice(";", 1);
+ return "Dictionary[" + _trim_parent_class(key, p_base_class) + ", " + _trim_parent_class(value, p_base_class) + "]";
} else if (p_info.type == Variant::NIL) {
if (p_is_arg || (p_info.usage & PROPERTY_USAGE_NIL_IS_VARIANT)) {
return "Variant";
@@ -1997,7 +2001,7 @@ static bool _guess_expression_type(GDScriptParser::CompletionContext &p_context,
}
// Look for valid indexing in other types
- if (!found && (index.value.get_type() == Variant::STRING || index.value.get_type() == Variant::NODE_PATH)) {
+ if (!found && (index.value.is_string() || index.value.get_type() == Variant::NODE_PATH)) {
StringName id = index.value;
found = _guess_identifier_type_from_base(c, base, id, r_type);
} else if (!found && index.type.kind == GDScriptParser::DataType::BUILTIN) {
@@ -2112,7 +2116,7 @@ static bool _guess_identifier_type(GDScriptParser::CompletionContext &p_context,
// Look in blocks first.
int last_assign_line = -1;
const GDScriptParser::ExpressionNode *last_assigned_expression = nullptr;
- GDScriptParser::DataType id_type;
+ GDScriptCompletionIdentifier id_type;
GDScriptParser::SuiteNode *suite = p_context.current_suite;
bool is_function_parameter = false;
@@ -2134,7 +2138,7 @@ static bool _guess_identifier_type(GDScriptParser::CompletionContext &p_context,
if (can_be_local && suite && suite->has_local(p_identifier->name)) {
const GDScriptParser::SuiteNode::Local &local = suite->get_local(p_identifier->name);
- id_type = local.get_datatype();
+ id_type.type = local.get_datatype();
// Check initializer as the first assignment.
switch (local.type) {
@@ -2172,7 +2176,7 @@ static bool _guess_identifier_type(GDScriptParser::CompletionContext &p_context,
base.type.is_meta_type = p_context.current_function && p_context.current_function->is_static;
if (_guess_identifier_type_from_base(p_context, base, p_identifier->name, base_identifier)) {
- id_type = base_identifier.type;
+ id_type = base_identifier;
}
}
}
@@ -2212,7 +2216,7 @@ static bool _guess_identifier_type(GDScriptParser::CompletionContext &p_context,
c.current_line = type_test->operand->start_line;
c.current_suite = suite;
if (type_test->test_datatype.is_hard_type()) {
- id_type = type_test->test_datatype;
+ id_type.type = type_test->test_datatype;
if (last_assign_line < c.current_line) {
// Override last assignment.
last_assign_line = c.current_line;
@@ -2230,10 +2234,10 @@ static bool _guess_identifier_type(GDScriptParser::CompletionContext &p_context,
c.current_line = last_assign_line;
GDScriptCompletionIdentifier assigned_type;
if (_guess_expression_type(c, last_assigned_expression, assigned_type)) {
- if (id_type.is_set() && assigned_type.type.is_set() && !GDScriptAnalyzer::check_type_compatibility(id_type, assigned_type.type)) {
+ if (id_type.type.is_set() && assigned_type.type.is_set() && !GDScriptAnalyzer::check_type_compatibility(id_type.type, assigned_type.type)) {
// The assigned type is incompatible. The annotated type takes priority.
+ r_type = id_type;
r_type.assigned_expression = last_assigned_expression;
- r_type.type = id_type;
} else {
r_type = assigned_type;
}
@@ -2251,8 +2255,8 @@ static bool _guess_identifier_type(GDScriptParser::CompletionContext &p_context,
GDScriptParser::FunctionNode *parent_function = base_type.class_type->get_member(p_context.current_function->identifier->name).function;
if (parent_function->parameters_indices.has(p_identifier->name)) {
const GDScriptParser::ParameterNode *parameter = parent_function->parameters[parent_function->parameters_indices[p_identifier->name]];
- if ((!id_type.is_set() || id_type.is_variant()) && parameter->get_datatype().is_hard_type()) {
- id_type = parameter->get_datatype();
+ if ((!id_type.type.is_set() || id_type.type.is_variant()) && parameter->get_datatype().is_hard_type()) {
+ id_type.type = parameter->get_datatype();
}
if (parameter->initializer) {
GDScriptParser::CompletionContext c = p_context;
@@ -2268,7 +2272,7 @@ static bool _guess_identifier_type(GDScriptParser::CompletionContext &p_context,
base_type = base_type.class_type->base_type;
break;
case GDScriptParser::DataType::NATIVE: {
- if (id_type.is_set() && !id_type.is_variant()) {
+ if (id_type.type.is_set() && !id_type.type.is_variant()) {
base_type = GDScriptParser::DataType();
break;
}
@@ -2289,8 +2293,8 @@ static bool _guess_identifier_type(GDScriptParser::CompletionContext &p_context,
}
}
- if (id_type.is_set() && !id_type.is_variant()) {
- r_type.type = id_type;
+ if (id_type.type.is_set() && !id_type.type.is_variant()) {
+ r_type = id_type;
return true;
}
diff --git a/modules/gdscript/gdscript_function.h b/modules/gdscript/gdscript_function.h
index ac4bab6d84..6433072b55 100644
--- a/modules/gdscript/gdscript_function.h
+++ b/modules/gdscript/gdscript_function.h
@@ -93,6 +93,41 @@ public:
} else {
valid = false;
}
+ } else if (valid && builtin_type == Variant::DICTIONARY && has_container_element_types()) {
+ Dictionary dictionary = p_variant;
+ if (dictionary.is_typed()) {
+ if (dictionary.is_typed_key()) {
+ GDScriptDataType key = get_container_element_type_or_variant(0);
+ Variant::Type key_builtin_type = (Variant::Type)dictionary.get_typed_key_builtin();
+ StringName key_native_type = dictionary.get_typed_key_class_name();
+ Ref<Script> key_script_type_ref = dictionary.get_typed_key_script();
+
+ if (key_script_type_ref.is_valid()) {
+ valid = (key.kind == SCRIPT || key.kind == GDSCRIPT) && key.script_type == key_script_type_ref.ptr();
+ } else if (key_native_type != StringName()) {
+ valid = key.kind == NATIVE && key.native_type == key_native_type;
+ } else {
+ valid = key.kind == BUILTIN && key.builtin_type == key_builtin_type;
+ }
+ }
+
+ if (valid && dictionary.is_typed_value()) {
+ GDScriptDataType value = get_container_element_type_or_variant(1);
+ Variant::Type value_builtin_type = (Variant::Type)dictionary.get_typed_value_builtin();
+ StringName value_native_type = dictionary.get_typed_value_class_name();
+ Ref<Script> value_script_type_ref = dictionary.get_typed_value_script();
+
+ if (value_script_type_ref.is_valid()) {
+ valid = (value.kind == SCRIPT || value.kind == GDSCRIPT) && value.script_type == value_script_type_ref.ptr();
+ } else if (value_native_type != StringName()) {
+ valid = value.kind == NATIVE && value.native_type == value_native_type;
+ } else {
+ valid = value.kind == BUILTIN && value.builtin_type == value_builtin_type;
+ }
+ }
+ } else {
+ valid = false;
+ }
} else if (!valid && p_allow_implicit_conversion) {
valid = Variant::can_convert_strict(var_type, builtin_type);
}
@@ -156,6 +191,10 @@ public:
}
return true;
case Variant::DICTIONARY:
+ if (has_container_element_types()) {
+ return get_container_element_type_or_variant(0).can_contain_object() || get_container_element_type_or_variant(1).can_contain_object();
+ }
+ return true;
case Variant::NIL:
case Variant::OBJECT:
return true;
@@ -220,6 +259,7 @@ public:
OPCODE_OPERATOR_VALIDATED,
OPCODE_TYPE_TEST_BUILTIN,
OPCODE_TYPE_TEST_ARRAY,
+ OPCODE_TYPE_TEST_DICTIONARY,
OPCODE_TYPE_TEST_NATIVE,
OPCODE_TYPE_TEST_SCRIPT,
OPCODE_SET_KEYED,
@@ -242,6 +282,7 @@ public:
OPCODE_ASSIGN_FALSE,
OPCODE_ASSIGN_TYPED_BUILTIN,
OPCODE_ASSIGN_TYPED_ARRAY,
+ OPCODE_ASSIGN_TYPED_DICTIONARY,
OPCODE_ASSIGN_TYPED_NATIVE,
OPCODE_ASSIGN_TYPED_SCRIPT,
OPCODE_CAST_TO_BUILTIN,
@@ -252,6 +293,7 @@ public:
OPCODE_CONSTRUCT_ARRAY,
OPCODE_CONSTRUCT_TYPED_ARRAY,
OPCODE_CONSTRUCT_DICTIONARY,
+ OPCODE_CONSTRUCT_TYPED_DICTIONARY,
OPCODE_CALL,
OPCODE_CALL_RETURN,
OPCODE_CALL_ASYNC,
@@ -280,6 +322,7 @@ public:
OPCODE_RETURN,
OPCODE_RETURN_TYPED_BUILTIN,
OPCODE_RETURN_TYPED_ARRAY,
+ OPCODE_RETURN_TYPED_DICTIONARY,
OPCODE_RETURN_TYPED_NATIVE,
OPCODE_RETURN_TYPED_SCRIPT,
OPCODE_ITERATE_BEGIN,
diff --git a/modules/gdscript/gdscript_lambda_callable.cpp b/modules/gdscript/gdscript_lambda_callable.cpp
index 2162a727b3..2de5811bca 100644
--- a/modules/gdscript/gdscript_lambda_callable.cpp
+++ b/modules/gdscript/gdscript_lambda_callable.cpp
@@ -150,7 +150,7 @@ void GDScriptLambdaCallable::call(const Variant **p_arguments, int p_argcount, V
GDScriptLambdaCallable::GDScriptLambdaCallable(Ref<GDScript> p_script, GDScriptFunction *p_function, const Vector<Variant> &p_captures) :
function(p_function) {
- ERR_FAIL_NULL(p_script.ptr());
+ ERR_FAIL_COND(p_script.is_null());
ERR_FAIL_NULL(p_function);
script = p_script;
captures = p_captures;
@@ -282,7 +282,7 @@ void GDScriptLambdaSelfCallable::call(const Variant **p_arguments, int p_argcoun
GDScriptLambdaSelfCallable::GDScriptLambdaSelfCallable(Ref<RefCounted> p_self, GDScriptFunction *p_function, const Vector<Variant> &p_captures) :
function(p_function) {
- ERR_FAIL_NULL(p_self.ptr());
+ ERR_FAIL_COND(p_self.is_null());
ERR_FAIL_NULL(p_function);
reference = p_self;
object = p_self.ptr();
diff --git a/modules/gdscript/gdscript_parser.cpp b/modules/gdscript/gdscript_parser.cpp
index 6e32733faa..65aa150be3 100644
--- a/modules/gdscript/gdscript_parser.cpp
+++ b/modules/gdscript/gdscript_parser.cpp
@@ -249,7 +249,7 @@ void GDScriptParser::override_completion_context(const Node *p_for_node, Complet
if (!for_completion) {
return;
}
- if (completion_context.node != p_for_node) {
+ if (p_for_node == nullptr || completion_context.node != p_for_node) {
return;
}
CompletionContext context;
@@ -260,11 +260,12 @@ void GDScriptParser::override_completion_context(const Node *p_for_node, Complet
context.current_line = tokenizer->get_cursor_line();
context.current_argument = p_argument;
context.node = p_node;
+ context.parser = this;
completion_context = context;
}
-void GDScriptParser::make_completion_context(CompletionType p_type, Node *p_node, int p_argument) {
- if (!for_completion) {
+void GDScriptParser::make_completion_context(CompletionType p_type, Node *p_node, int p_argument, bool p_force) {
+ if (!for_completion || (!p_force && completion_context.type != COMPLETION_NONE)) {
return;
}
if (previous.cursor_place != GDScriptTokenizerText::CURSOR_MIDDLE && previous.cursor_place != GDScriptTokenizerText::CURSOR_END && current.cursor_place == GDScriptTokenizerText::CURSOR_NONE) {
@@ -282,8 +283,8 @@ void GDScriptParser::make_completion_context(CompletionType p_type, Node *p_node
completion_context = context;
}
-void GDScriptParser::make_completion_context(CompletionType p_type, Variant::Type p_builtin_type) {
- if (!for_completion) {
+void GDScriptParser::make_completion_context(CompletionType p_type, Variant::Type p_builtin_type, bool p_force) {
+ if (!for_completion || (!p_force && completion_context.type != COMPLETION_NONE)) {
return;
}
if (previous.cursor_place != GDScriptTokenizerText::CURSOR_MIDDLE && previous.cursor_place != GDScriptTokenizerText::CURSOR_END && current.cursor_place == GDScriptTokenizerText::CURSOR_NONE) {
@@ -2470,7 +2471,7 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_precedence(Precedence p_pr
}
// Completion can appear whenever an expression is expected.
- make_completion_context(COMPLETION_IDENTIFIER, nullptr);
+ make_completion_context(COMPLETION_IDENTIFIER, nullptr, -1, false);
GDScriptTokenizer::Token token = current;
GDScriptTokenizer::Token::Type token_type = token.type;
@@ -2487,8 +2488,17 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_precedence(Precedence p_pr
advance(); // Only consume the token if there's a valid rule.
+ // After a token was consumed, update the completion context regardless of a previously set context.
+
ExpressionNode *previous_operand = (this->*prefix_rule)(nullptr, p_can_assign);
+#ifdef TOOLS_ENABLED
+ // HACK: We can't create a context in parse_identifier since it is used in places were we don't want completion.
+ if (previous_operand != nullptr && previous_operand->type == GDScriptParser::Node::IDENTIFIER && prefix_rule == static_cast<ParseFunction>(&GDScriptParser::parse_identifier)) {
+ make_completion_context(COMPLETION_IDENTIFIER, previous_operand);
+ }
+#endif
+
while (p_precedence <= get_rule(current.type)->precedence) {
if (previous_operand == nullptr || (p_stop_on_assign && current.type == GDScriptTokenizer::Token::EQUAL) || lambda_ended) {
return previous_operand;
@@ -2923,6 +2933,11 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_assignment(ExpressionNode
}
assignment->assignee = p_previous_operand;
assignment->assigned_value = parse_expression(false);
+#ifdef TOOLS_ENABLED
+ if (assignment->assigned_value != nullptr && assignment->assigned_value->type == GDScriptParser::Node::IDENTIFIER) {
+ override_completion_context(assignment->assigned_value, COMPLETION_ASSIGN, assignment);
+ }
+#endif
if (assignment->assigned_value == nullptr) {
push_error(R"(Expected an expression after "=".)");
}
@@ -3553,7 +3568,7 @@ GDScriptParser::TypeNode *GDScriptParser::parse_type(bool p_allow_void) {
type->type_chain.push_back(type_element);
if (match(GDScriptTokenizer::Token::BRACKET_OPEN)) {
- // Typed collection (like Array[int]).
+ // Typed collection (like Array[int], Dictionary[String, int]).
bool first_pass = true;
do {
TypeNode *container_type = parse_type(false); // Don't allow void for element type.
@@ -4092,7 +4107,7 @@ bool GDScriptParser::validate_annotation_arguments(AnnotationNode *p_annotation)
return true;
}
-bool GDScriptParser::tool_annotation(const AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class) {
+bool GDScriptParser::tool_annotation(AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class) {
#ifdef DEBUG_ENABLED
if (_is_tool) {
push_error(R"("@tool" annotation can only be used once.)", p_annotation);
@@ -4103,7 +4118,7 @@ bool GDScriptParser::tool_annotation(const AnnotationNode *p_annotation, Node *p
return true;
}
-bool GDScriptParser::icon_annotation(const AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class) {
+bool GDScriptParser::icon_annotation(AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class) {
ERR_FAIL_COND_V_MSG(p_target->type != Node::CLASS, false, R"("@icon" annotation can only be applied to classes.)");
ERR_FAIL_COND_V(p_annotation->resolved_arguments.is_empty(), false);
@@ -4134,7 +4149,7 @@ bool GDScriptParser::icon_annotation(const AnnotationNode *p_annotation, Node *p
return true;
}
-bool GDScriptParser::onready_annotation(const AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class) {
+bool GDScriptParser::onready_annotation(AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class) {
ERR_FAIL_COND_V_MSG(p_target->type != Node::VARIABLE, false, R"("@onready" annotation can only be applied to class variables.)");
if (current_class && !ClassDB::is_parent_class(current_class->get_datatype().native_type, SNAME("Node"))) {
@@ -4267,7 +4282,7 @@ static StringName _find_narrowest_native_or_global_class(const GDScriptParser::D
}
template <PropertyHint t_hint, Variant::Type t_type>
-bool GDScriptParser::export_annotations(const AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class) {
+bool GDScriptParser::export_annotations(AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class) {
ERR_FAIL_COND_V_MSG(p_target->type != Node::VARIABLE, false, vformat(R"("%s" annotation can only be applied to variables.)", p_annotation->name));
ERR_FAIL_NULL_V(p_class, false);
@@ -4370,6 +4385,14 @@ bool GDScriptParser::export_annotations(const AnnotationNode *p_annotation, Node
export_type.type_source = variable->datatype.type_source;
}
+ bool is_dict = false;
+ if (export_type.builtin_type == Variant::DICTIONARY && export_type.has_container_element_types()) {
+ is_dict = true;
+ DataType inner_type = export_type.get_container_element_type_or_variant(1);
+ export_type = export_type.get_container_element_type_or_variant(0);
+ export_type.set_container_element_type(0, inner_type); // Store earlier extracted value within key to separately parse after.
+ }
+
bool use_default_variable_type_check = true;
if (p_annotation->name == SNAME("@export_range")) {
@@ -4397,8 +4420,13 @@ bool GDScriptParser::export_annotations(const AnnotationNode *p_annotation, Node
}
if (export_type.is_variant() || export_type.has_no_type()) {
- push_error(R"(Cannot use simple "@export" annotation because the type of the initialized value can't be inferred.)", p_annotation);
- return false;
+ if (is_dict) {
+ // Dictionary allowed to have a variant key/value.
+ export_type.kind = GDScriptParser::DataType::BUILTIN;
+ } else {
+ push_error(R"(Cannot use simple "@export" annotation because the type of the initialized value can't be inferred.)", p_annotation);
+ return false;
+ }
}
switch (export_type.kind) {
@@ -4458,6 +4486,90 @@ bool GDScriptParser::export_annotations(const AnnotationNode *p_annotation, Node
push_error(vformat(R"(Node export is only supported in Node-derived classes, but the current class inherits "%s".)", p_class->base_type.to_string()), p_annotation);
return false;
}
+
+ if (is_dict) {
+ String key_prefix = itos(variable->export_info.type);
+ if (variable->export_info.hint) {
+ key_prefix += "/" + itos(variable->export_info.hint);
+ }
+ key_prefix += ":" + variable->export_info.hint_string;
+
+ // Now parse value.
+ export_type = export_type.get_container_element_type(0);
+
+ if (export_type.is_variant() || export_type.has_no_type()) {
+ export_type.kind = GDScriptParser::DataType::BUILTIN;
+ }
+ switch (export_type.kind) {
+ case GDScriptParser::DataType::BUILTIN:
+ variable->export_info.type = export_type.builtin_type;
+ variable->export_info.hint = PROPERTY_HINT_NONE;
+ variable->export_info.hint_string = String();
+ break;
+ case GDScriptParser::DataType::NATIVE:
+ case GDScriptParser::DataType::SCRIPT:
+ case GDScriptParser::DataType::CLASS: {
+ const StringName class_name = _find_narrowest_native_or_global_class(export_type);
+ if (ClassDB::is_parent_class(export_type.native_type, SNAME("Resource"))) {
+ variable->export_info.type = Variant::OBJECT;
+ variable->export_info.hint = PROPERTY_HINT_RESOURCE_TYPE;
+ variable->export_info.hint_string = class_name;
+ } else if (ClassDB::is_parent_class(export_type.native_type, SNAME("Node"))) {
+ variable->export_info.type = Variant::OBJECT;
+ variable->export_info.hint = PROPERTY_HINT_NODE_TYPE;
+ variable->export_info.hint_string = class_name;
+ } else {
+ push_error(R"(Export type can only be built-in, a resource, a node, or an enum.)", p_annotation);
+ return false;
+ }
+ } break;
+ case GDScriptParser::DataType::ENUM: {
+ 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();
+ }
+
+ 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.)", p_annotation);
+ return false;
+ }
+
+ if (variable->export_info.hint == PROPERTY_HINT_NODE_TYPE && !ClassDB::is_parent_class(p_class->base_type.native_type, SNAME("Node"))) {
+ push_error(vformat(R"(Node export is only supported in Node-derived classes, but the current class inherits "%s".)", p_class->base_type.to_string()), p_annotation);
+ return false;
+ }
+
+ String value_prefix = itos(variable->export_info.type);
+ if (variable->export_info.hint) {
+ value_prefix += "/" + itos(variable->export_info.hint);
+ }
+ value_prefix += ":" + variable->export_info.hint_string;
+
+ variable->export_info.type = Variant::DICTIONARY;
+ variable->export_info.hint = PROPERTY_HINT_TYPE_STRING;
+ variable->export_info.hint_string = key_prefix + ";" + value_prefix;
+ variable->export_info.usage = PROPERTY_USAGE_DEFAULT;
+ variable->export_info.class_name = StringName();
+ }
} else if (p_annotation->name == SNAME("@export_enum")) {
use_default_variable_type_check = false;
@@ -4506,7 +4618,7 @@ bool GDScriptParser::export_annotations(const AnnotationNode *p_annotation, Node
// For `@export_storage` and `@export_custom`, there is no need to check the variable type, argument values,
// or handle array exports in a special way, so they are implemented as separate methods.
-bool GDScriptParser::export_storage_annotation(const AnnotationNode *p_annotation, Node *p_node, ClassNode *p_class) {
+bool GDScriptParser::export_storage_annotation(AnnotationNode *p_annotation, Node *p_node, ClassNode *p_class) {
ERR_FAIL_COND_V_MSG(p_node->type != Node::VARIABLE, false, vformat(R"("%s" annotation can only be applied to variables.)", p_annotation->name));
VariableNode *variable = static_cast<VariableNode *>(p_node);
@@ -4528,7 +4640,7 @@ bool GDScriptParser::export_storage_annotation(const AnnotationNode *p_annotatio
return true;
}
-bool GDScriptParser::export_custom_annotation(const AnnotationNode *p_annotation, Node *p_node, ClassNode *p_class) {
+bool GDScriptParser::export_custom_annotation(AnnotationNode *p_annotation, Node *p_node, ClassNode *p_class) {
ERR_FAIL_COND_V_MSG(p_node->type != Node::VARIABLE, false, vformat(R"("%s" annotation can only be applied to variables.)", p_annotation->name));
ERR_FAIL_COND_V_MSG(p_annotation->resolved_arguments.size() < 2, false, R"(Annotation "@export_custom" requires 2 arguments.)");
@@ -4557,31 +4669,29 @@ bool GDScriptParser::export_custom_annotation(const AnnotationNode *p_annotation
}
template <PropertyUsageFlags t_usage>
-bool GDScriptParser::export_group_annotations(const AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class) {
- AnnotationNode *annotation = const_cast<AnnotationNode *>(p_annotation);
-
- if (annotation->resolved_arguments.is_empty()) {
+bool GDScriptParser::export_group_annotations(AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class) {
+ if (p_annotation->resolved_arguments.is_empty()) {
return false;
}
- annotation->export_info.name = annotation->resolved_arguments[0];
+ p_annotation->export_info.name = p_annotation->resolved_arguments[0];
switch (t_usage) {
case PROPERTY_USAGE_CATEGORY: {
- annotation->export_info.usage = t_usage;
+ p_annotation->export_info.usage = t_usage;
} break;
case PROPERTY_USAGE_GROUP: {
- annotation->export_info.usage = t_usage;
- if (annotation->resolved_arguments.size() == 2) {
- annotation->export_info.hint_string = annotation->resolved_arguments[1];
+ p_annotation->export_info.usage = t_usage;
+ if (p_annotation->resolved_arguments.size() == 2) {
+ p_annotation->export_info.hint_string = p_annotation->resolved_arguments[1];
}
} break;
case PROPERTY_USAGE_SUBGROUP: {
- annotation->export_info.usage = t_usage;
- if (annotation->resolved_arguments.size() == 2) {
- annotation->export_info.hint_string = annotation->resolved_arguments[1];
+ p_annotation->export_info.usage = t_usage;
+ if (p_annotation->resolved_arguments.size() == 2) {
+ p_annotation->export_info.hint_string = p_annotation->resolved_arguments[1];
}
} break;
}
@@ -4589,7 +4699,7 @@ bool GDScriptParser::export_group_annotations(const AnnotationNode *p_annotation
return true;
}
-bool GDScriptParser::warning_annotations(const AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class) {
+bool GDScriptParser::warning_annotations(AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class) {
#ifndef DEBUG_ENABLED
// Only available in debug builds.
return true;
@@ -4664,7 +4774,7 @@ bool GDScriptParser::warning_annotations(const AnnotationNode *p_annotation, Nod
#endif // DEBUG_ENABLED
}
-bool GDScriptParser::rpc_annotation(const AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class) {
+bool GDScriptParser::rpc_annotation(AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class) {
ERR_FAIL_COND_V_MSG(p_target->type != Node::FUNCTION, false, vformat(R"("%s" annotation can only be applied to functions.)", p_annotation->name));
FunctionNode *function = static_cast<FunctionNode *>(p_target);
@@ -4725,7 +4835,7 @@ bool GDScriptParser::rpc_annotation(const AnnotationNode *p_annotation, Node *p_
return true;
}
-bool GDScriptParser::static_unload_annotation(const AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class) {
+bool GDScriptParser::static_unload_annotation(AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class) {
ERR_FAIL_COND_V_MSG(p_target->type != Node::CLASS, false, vformat(R"("%s" annotation can only be applied to classes.)", p_annotation->name));
ClassNode *class_node = static_cast<ClassNode *>(p_target);
if (class_node->annotated_static_unload) {
@@ -4781,7 +4891,10 @@ String GDScriptParser::DataType::to_string() const {
return "null";
}
if (builtin_type == Variant::ARRAY && has_container_element_type(0)) {
- return vformat("Array[%s]", container_element_types[0].to_string());
+ return vformat("Array[%s]", get_container_element_type(0).to_string());
+ }
+ if (builtin_type == Variant::DICTIONARY && has_container_element_types()) {
+ return vformat("Dictionary[%s, %s]", get_container_element_type_or_variant(0).to_string(), get_container_element_type_or_variant(1).to_string());
}
return Variant::get_type_name(builtin_type);
case NATIVE:
@@ -4796,9 +4909,9 @@ String GDScriptParser::DataType::to_string() const {
return class_type->fqcn;
case SCRIPT: {
if (is_meta_type) {
- return script_type != nullptr ? script_type->get_class_name().operator String() : "";
+ return script_type.is_valid() ? script_type->get_class_name().operator String() : "";
}
- String name = script_type != nullptr ? script_type->get_name() : "";
+ String name = script_type.is_valid() ? script_type->get_name() : "";
if (!name.is_empty()) {
return name;
}
@@ -4870,6 +4983,72 @@ PropertyInfo GDScriptParser::DataType::to_property_info(const String &p_name) co
case UNRESOLVED:
break;
}
+ } else if (builtin_type == Variant::DICTIONARY && has_container_element_types()) {
+ const DataType key_type = get_container_element_type_or_variant(0);
+ const DataType value_type = get_container_element_type_or_variant(1);
+ if ((key_type.kind == VARIANT && value_type.kind == VARIANT) || key_type.kind == RESOLVING ||
+ key_type.kind == UNRESOLVED || value_type.kind == RESOLVING || value_type.kind == UNRESOLVED) {
+ break;
+ }
+ String key_hint, value_hint;
+ switch (key_type.kind) {
+ case BUILTIN:
+ key_hint = Variant::get_type_name(key_type.builtin_type);
+ break;
+ case NATIVE:
+ key_hint = key_type.native_type;
+ break;
+ case SCRIPT:
+ if (key_type.script_type.is_valid() && key_type.script_type->get_global_name() != StringName()) {
+ key_hint = key_type.script_type->get_global_name();
+ } else {
+ key_hint = key_type.native_type;
+ }
+ break;
+ case CLASS:
+ if (key_type.class_type != nullptr && key_type.class_type->get_global_name() != StringName()) {
+ key_hint = key_type.class_type->get_global_name();
+ } else {
+ key_hint = key_type.native_type;
+ }
+ break;
+ case ENUM:
+ key_hint = String(key_type.native_type).replace("::", ".");
+ break;
+ default:
+ key_hint = "Variant";
+ break;
+ }
+ switch (value_type.kind) {
+ case BUILTIN:
+ value_hint = Variant::get_type_name(value_type.builtin_type);
+ break;
+ case NATIVE:
+ value_hint = value_type.native_type;
+ break;
+ case SCRIPT:
+ if (value_type.script_type.is_valid() && value_type.script_type->get_global_name() != StringName()) {
+ value_hint = value_type.script_type->get_global_name();
+ } else {
+ value_hint = value_type.native_type;
+ }
+ break;
+ case CLASS:
+ if (value_type.class_type != nullptr && value_type.class_type->get_global_name() != StringName()) {
+ value_hint = value_type.class_type->get_global_name();
+ } else {
+ value_hint = value_type.native_type;
+ }
+ break;
+ case ENUM:
+ value_hint = String(value_type.native_type).replace("::", ".");
+ break;
+ default:
+ value_hint = "Variant";
+ break;
+ }
+ result.hint = PROPERTY_HINT_DICTIONARY_TYPE;
+ result.hint_string = key_hint + ";" + value_hint;
}
break;
case NATIVE:
@@ -4954,6 +5133,50 @@ GDScriptParser::DataType GDScriptParser::DataType::get_typed_container_type() co
return type;
}
+bool GDScriptParser::DataType::can_reference(const GDScriptParser::DataType &p_other) const {
+ if (p_other.is_meta_type) {
+ return false;
+ } else if (builtin_type != p_other.builtin_type) {
+ return false;
+ } else if (builtin_type != Variant::OBJECT) {
+ return true;
+ }
+
+ if (native_type == StringName()) {
+ return true;
+ } else if (p_other.native_type == StringName()) {
+ return false;
+ } else if (native_type != p_other.native_type && !ClassDB::is_parent_class(p_other.native_type, native_type)) {
+ return false;
+ }
+
+ Ref<Script> script = script_type;
+ if (kind == GDScriptParser::DataType::CLASS && script.is_null()) {
+ Error err = OK;
+ Ref<GDScript> scr = GDScriptCache::get_shallow_script(script_path, err);
+ ERR_FAIL_COND_V_MSG(err, false, vformat(R"(Error while getting cache for script "%s".)", script_path));
+ script.reference_ptr(scr->find_class(class_type->fqcn));
+ }
+
+ Ref<Script> script_other = p_other.script_type;
+ if (p_other.kind == GDScriptParser::DataType::CLASS && script_other.is_null()) {
+ Error err = OK;
+ Ref<GDScript> scr = GDScriptCache::get_shallow_script(p_other.script_path, err);
+ ERR_FAIL_COND_V_MSG(err, false, vformat(R"(Error while getting cache for script "%s".)", p_other.script_path));
+ script_other.reference_ptr(scr->find_class(p_other.class_type->fqcn));
+ }
+
+ if (script.is_null()) {
+ return true;
+ } else if (script_other.is_null()) {
+ return false;
+ } else if (script != script_other && !script_other->inherits_script(script)) {
+ return false;
+ }
+
+ return true;
+}
+
void GDScriptParser::complete_extents(Node *p_node) {
while (!nodes_in_progress.is_empty() && nodes_in_progress.back()->get() != p_node) {
ERR_PRINT("Parser bug: Mismatch in extents tracking stack.");
diff --git a/modules/gdscript/gdscript_parser.h b/modules/gdscript/gdscript_parser.h
index 2999fb11e4..7840474a89 100644
--- a/modules/gdscript/gdscript_parser.h
+++ b/modules/gdscript/gdscript_parser.h
@@ -189,6 +189,8 @@ public:
GDScriptParser::DataType get_typed_container_type() const;
+ bool can_reference(const DataType &p_other) const;
+
bool operator==(const DataType &p_other) const {
if (type_source == UNDETECTED || p_other.type_source == UNDETECTED) {
return true; // Can be considered equal for parsing purposes.
@@ -1371,7 +1373,7 @@ private:
bool in_lambda = false;
bool lambda_ended = false; // Marker for when a lambda ends, to apply an end of statement if needed.
- typedef bool (GDScriptParser::*AnnotationAction)(const AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class);
+ typedef bool (GDScriptParser::*AnnotationAction)(AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class);
struct AnnotationInfo {
enum TargetKind {
NONE = 0,
@@ -1455,8 +1457,11 @@ private:
}
void apply_pending_warnings();
#endif
- void make_completion_context(CompletionType p_type, Node *p_node, int p_argument = -1);
- void make_completion_context(CompletionType p_type, Variant::Type p_builtin_type);
+ // Setting p_force to false will prevent the completion context from being update if a context was already set before.
+ // This should only be done when we push context before we consumed any tokens for the corresponding structure.
+ // See parse_precedence for an example.
+ void make_completion_context(CompletionType p_type, Node *p_node, int p_argument = -1, bool p_force = true);
+ void make_completion_context(CompletionType p_type, Variant::Type p_builtin_type, bool p_force = true);
// In some cases it might become necessary to alter the completion context after parsing a subexpression.
// For example to not override COMPLETE_CALL_ARGUMENTS with COMPLETION_NONE from string literals.
void override_completion_context(const Node *p_for_node, CompletionType p_type, Node *p_node, int p_argument = -1);
@@ -1495,18 +1500,18 @@ private:
static bool register_annotation(const MethodInfo &p_info, uint32_t p_target_kinds, AnnotationAction p_apply, const Vector<Variant> &p_default_arguments = Vector<Variant>(), bool p_is_vararg = false);
bool validate_annotation_arguments(AnnotationNode *p_annotation);
void clear_unused_annotations();
- bool tool_annotation(const AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class);
- bool icon_annotation(const AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class);
- bool onready_annotation(const AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class);
+ bool tool_annotation(AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class);
+ bool icon_annotation(AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class);
+ bool onready_annotation(AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class);
template <PropertyHint t_hint, Variant::Type t_type>
- bool export_annotations(const AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class);
- bool export_storage_annotation(const AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class);
- bool export_custom_annotation(const AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class);
+ bool export_annotations(AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class);
+ bool export_storage_annotation(AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class);
+ bool export_custom_annotation(AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class);
template <PropertyUsageFlags t_usage>
- bool export_group_annotations(const AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class);
- bool warning_annotations(const AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class);
- bool rpc_annotation(const AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class);
- bool static_unload_annotation(const AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class);
+ bool export_group_annotations(AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class);
+ bool warning_annotations(AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class);
+ bool rpc_annotation(AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class);
+ bool static_unload_annotation(AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class);
// Statements.
Node *parse_statement();
VariableNode *parse_variable(bool p_is_static);
diff --git a/modules/gdscript/gdscript_utility_functions.cpp b/modules/gdscript/gdscript_utility_functions.cpp
index 3e1de628d2..59dd983ed2 100644
--- a/modules/gdscript/gdscript_utility_functions.cpp
+++ b/modules/gdscript/gdscript_utility_functions.cpp
@@ -232,7 +232,7 @@ struct GDScriptUtilityFunctionsDefinitions {
static inline void load(Variant *r_ret, const Variant **p_args, int p_arg_count, Callable::CallError &r_error) {
VALIDATE_ARG_COUNT(1);
- if (p_args[0]->get_type() != Variant::STRING) {
+ if (!p_args[0]->is_string()) {
r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT;
r_error.argument = 0;
r_error.expected = Variant::STRING;
diff --git a/modules/gdscript/gdscript_vm.cpp b/modules/gdscript/gdscript_vm.cpp
index ddb0cf9502..4617a0dbb9 100644
--- a/modules/gdscript/gdscript_vm.cpp
+++ b/modules/gdscript/gdscript_vm.cpp
@@ -84,9 +84,15 @@ static String _get_var_type(const Variant *p_var) {
if (p_var->get_type() == Variant::ARRAY) {
basestr = "Array";
const Array *p_array = VariantInternal::get_array(p_var);
- Variant::Type builtin_type = (Variant::Type)p_array->get_typed_builtin();
- if (builtin_type != Variant::NIL) {
- basestr += "[" + _get_element_type(builtin_type, p_array->get_typed_class_name(), p_array->get_typed_script()) + "]";
+ if (p_array->is_typed()) {
+ basestr += "[" + _get_element_type((Variant::Type)p_array->get_typed_builtin(), p_array->get_typed_class_name(), p_array->get_typed_script()) + "]";
+ }
+ } else if (p_var->get_type() == Variant::DICTIONARY) {
+ basestr = "Dictionary";
+ const Dictionary *p_dictionary = VariantInternal::get_dictionary(p_var);
+ if (p_dictionary->is_typed()) {
+ basestr += "[" + _get_element_type((Variant::Type)p_dictionary->get_typed_key_builtin(), p_dictionary->get_typed_key_class_name(), p_dictionary->get_typed_key_script()) +
+ ", " + _get_element_type((Variant::Type)p_dictionary->get_typed_value_builtin(), p_dictionary->get_typed_value_class_name(), p_dictionary->get_typed_value_script()) + "]";
}
} else {
basestr = Variant::get_type_name(p_var->get_type());
@@ -120,6 +126,16 @@ Variant GDScriptFunction::_get_default_variant_for_data_type(const GDScriptDataT
}
return array;
+ } else if (p_data_type.builtin_type == Variant::DICTIONARY) {
+ Dictionary dict;
+ // Typed dictionary.
+ if (p_data_type.has_container_element_types()) {
+ const GDScriptDataType &key_type = p_data_type.get_container_element_type_or_variant(0);
+ const GDScriptDataType &value_type = p_data_type.get_container_element_type_or_variant(1);
+ dict.set_typed(key_type.builtin_type, key_type.native_type, key_type.script_type, value_type.builtin_type, value_type.native_type, value_type.script_type);
+ }
+
+ return dict;
} else {
Callable::CallError ce;
Variant variant;
@@ -153,6 +169,9 @@ String GDScriptFunction::_get_call_error(const String &p_where, const Variant **
if (p_err.expected == Variant::ARRAY && p_argptrs[p_err.argument]->get_type() == p_err.expected) {
return "Invalid type in " + p_where + ". The array of argument " + itos(p_err.argument + 1) + " (" + _get_var_type(p_argptrs[p_err.argument]) + ") does not have the same element type as the expected typed array argument.";
}
+ if (p_err.expected == Variant::DICTIONARY && p_argptrs[p_err.argument]->get_type() == p_err.expected) {
+ return "Invalid type in " + p_where + ". The dictionary of argument " + itos(p_err.argument + 1) + " (" + _get_var_type(p_argptrs[p_err.argument]) + ") does not have the same element type as the expected typed dictionary argument.";
+ }
#endif // DEBUG_ENABLED
return "Invalid type in " + p_where + ". Cannot convert argument " + itos(p_err.argument + 1) + " from " + Variant::get_type_name(p_argptrs[p_err.argument]->get_type()) + " to " + Variant::get_type_name(Variant::Type(p_err.expected)) + ".";
case Callable::CallError::CALL_ERROR_TOO_MANY_ARGUMENTS:
@@ -215,6 +234,7 @@ void (*type_init_function_table[])(Variant *) = {
&&OPCODE_OPERATOR_VALIDATED, \
&&OPCODE_TYPE_TEST_BUILTIN, \
&&OPCODE_TYPE_TEST_ARRAY, \
+ &&OPCODE_TYPE_TEST_DICTIONARY, \
&&OPCODE_TYPE_TEST_NATIVE, \
&&OPCODE_TYPE_TEST_SCRIPT, \
&&OPCODE_SET_KEYED, \
@@ -237,6 +257,7 @@ void (*type_init_function_table[])(Variant *) = {
&&OPCODE_ASSIGN_FALSE, \
&&OPCODE_ASSIGN_TYPED_BUILTIN, \
&&OPCODE_ASSIGN_TYPED_ARRAY, \
+ &&OPCODE_ASSIGN_TYPED_DICTIONARY, \
&&OPCODE_ASSIGN_TYPED_NATIVE, \
&&OPCODE_ASSIGN_TYPED_SCRIPT, \
&&OPCODE_CAST_TO_BUILTIN, \
@@ -247,6 +268,7 @@ void (*type_init_function_table[])(Variant *) = {
&&OPCODE_CONSTRUCT_ARRAY, \
&&OPCODE_CONSTRUCT_TYPED_ARRAY, \
&&OPCODE_CONSTRUCT_DICTIONARY, \
+ &&OPCODE_CONSTRUCT_TYPED_DICTIONARY, \
&&OPCODE_CALL, \
&&OPCODE_CALL_RETURN, \
&&OPCODE_CALL_ASYNC, \
@@ -275,6 +297,7 @@ void (*type_init_function_table[])(Variant *) = {
&&OPCODE_RETURN, \
&&OPCODE_RETURN_TYPED_BUILTIN, \
&&OPCODE_RETURN_TYPED_ARRAY, \
+ &&OPCODE_RETURN_TYPED_DICTIONARY, \
&&OPCODE_RETURN_TYPED_NATIVE, \
&&OPCODE_RETURN_TYPED_SCRIPT, \
&&OPCODE_ITERATE_BEGIN, \
@@ -548,7 +571,12 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a
return _get_default_variant_for_data_type(return_type);
}
if (argument_types[i].kind == GDScriptDataType::BUILTIN) {
- if (argument_types[i].builtin_type == Variant::ARRAY && argument_types[i].has_container_element_type(0)) {
+ if (argument_types[i].builtin_type == Variant::DICTIONARY && argument_types[i].has_container_element_types()) {
+ const GDScriptDataType &arg_key_type = argument_types[i].get_container_element_type_or_variant(0);
+ const GDScriptDataType &arg_value_type = argument_types[i].get_container_element_type_or_variant(1);
+ Dictionary dict(p_args[i]->operator Dictionary(), arg_key_type.builtin_type, arg_key_type.native_type, arg_key_type.script_type, arg_value_type.builtin_type, arg_value_type.native_type, arg_value_type.script_type);
+ memnew_placement(&stack[i + 3], Variant(dict));
+ } else if (argument_types[i].builtin_type == Variant::ARRAY && argument_types[i].has_container_element_type(0)) {
const GDScriptDataType &arg_type = argument_types[i].container_element_types[0];
Array array(p_args[i]->operator Array(), arg_type.builtin_type, arg_type.native_type, arg_type.script_type);
memnew_placement(&stack[i + 3], Variant(array));
@@ -827,6 +855,36 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a
}
DISPATCH_OPCODE;
+ OPCODE(OPCODE_TYPE_TEST_DICTIONARY) {
+ CHECK_SPACE(9);
+
+ GET_VARIANT_PTR(dst, 0);
+ GET_VARIANT_PTR(value, 1);
+
+ GET_VARIANT_PTR(key_script_type, 2);
+ Variant::Type key_builtin_type = (Variant::Type)_code_ptr[ip + 5];
+ int key_native_type_idx = _code_ptr[ip + 6];
+ GD_ERR_BREAK(key_native_type_idx < 0 || key_native_type_idx >= _global_names_count);
+ const StringName key_native_type = _global_names_ptr[key_native_type_idx];
+
+ GET_VARIANT_PTR(value_script_type, 3);
+ Variant::Type value_builtin_type = (Variant::Type)_code_ptr[ip + 7];
+ int value_native_type_idx = _code_ptr[ip + 8];
+ GD_ERR_BREAK(value_native_type_idx < 0 || value_native_type_idx >= _global_names_count);
+ const StringName value_native_type = _global_names_ptr[value_native_type_idx];
+
+ bool result = false;
+ if (value->get_type() == Variant::DICTIONARY) {
+ Dictionary *dictionary = VariantInternal::get_dictionary(value);
+ result = dictionary->get_typed_key_builtin() == ((uint32_t)key_builtin_type) && dictionary->get_typed_key_class_name() == key_native_type && dictionary->get_typed_key_script() == *key_script_type &&
+ dictionary->get_typed_value_builtin() == ((uint32_t)value_builtin_type) && dictionary->get_typed_value_class_name() == value_native_type && dictionary->get_typed_value_script() == *value_script_type;
+ }
+
+ *dst = result;
+ ip += 9;
+ }
+ DISPATCH_OPCODE;
+
OPCODE(OPCODE_TYPE_TEST_NATIVE) {
CHECK_SPACE(4);
@@ -1384,6 +1442,50 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a
}
DISPATCH_OPCODE;
+ OPCODE(OPCODE_ASSIGN_TYPED_DICTIONARY) {
+ CHECK_SPACE(9);
+ GET_VARIANT_PTR(dst, 0);
+ GET_VARIANT_PTR(src, 1);
+
+ GET_VARIANT_PTR(key_script_type, 2);
+ Variant::Type key_builtin_type = (Variant::Type)_code_ptr[ip + 5];
+ int key_native_type_idx = _code_ptr[ip + 6];
+ GD_ERR_BREAK(key_native_type_idx < 0 || key_native_type_idx >= _global_names_count);
+ const StringName key_native_type = _global_names_ptr[key_native_type_idx];
+
+ GET_VARIANT_PTR(value_script_type, 3);
+ Variant::Type value_builtin_type = (Variant::Type)_code_ptr[ip + 7];
+ int value_native_type_idx = _code_ptr[ip + 8];
+ GD_ERR_BREAK(value_native_type_idx < 0 || value_native_type_idx >= _global_names_count);
+ const StringName value_native_type = _global_names_ptr[value_native_type_idx];
+
+ if (src->get_type() != Variant::DICTIONARY) {
+#ifdef DEBUG_ENABLED
+ err_text = vformat(R"(Trying to assign a value of type "%s" to a variable of type "Dictionary[%s, %s]".)",
+ _get_var_type(src), _get_element_type(key_builtin_type, key_native_type, *key_script_type),
+ _get_element_type(value_builtin_type, value_native_type, *value_script_type));
+#endif // DEBUG_ENABLED
+ OPCODE_BREAK;
+ }
+
+ Dictionary *dictionary = VariantInternal::get_dictionary(src);
+
+ if (dictionary->get_typed_key_builtin() != ((uint32_t)key_builtin_type) || dictionary->get_typed_key_class_name() != key_native_type || dictionary->get_typed_key_script() != *key_script_type ||
+ dictionary->get_typed_value_builtin() != ((uint32_t)value_builtin_type) || dictionary->get_typed_value_class_name() != value_native_type || dictionary->get_typed_value_script() != *value_script_type) {
+#ifdef DEBUG_ENABLED
+ err_text = vformat(R"(Trying to assign a dictionary of type "%s" to a variable of type "Dictionary[%s, %s]".)",
+ _get_var_type(src), _get_element_type(key_builtin_type, key_native_type, *key_script_type),
+ _get_element_type(value_builtin_type, value_native_type, *value_script_type));
+#endif // DEBUG_ENABLED
+ OPCODE_BREAK;
+ }
+
+ *dst = *src;
+
+ ip += 9;
+ }
+ DISPATCH_OPCODE;
+
OPCODE(OPCODE_ASSIGN_TYPED_NATIVE) {
CHECK_SPACE(4);
GET_VARIANT_PTR(dst, 0);
@@ -1703,12 +1805,51 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a
GET_INSTRUCTION_ARG(dst, argc * 2);
+ *dst = Variant(); // Clear potential previous typed dictionary.
+
*dst = dict;
ip += 2;
}
DISPATCH_OPCODE;
+ OPCODE(OPCODE_CONSTRUCT_TYPED_DICTIONARY) {
+ LOAD_INSTRUCTION_ARGS
+ CHECK_SPACE(6 + instr_arg_count);
+ ip += instr_arg_count;
+
+ int argc = _code_ptr[ip + 1];
+
+ GET_INSTRUCTION_ARG(key_script_type, argc * 2 + 1);
+ Variant::Type key_builtin_type = (Variant::Type)_code_ptr[ip + 2];
+ int key_native_type_idx = _code_ptr[ip + 3];
+ GD_ERR_BREAK(key_native_type_idx < 0 || key_native_type_idx >= _global_names_count);
+ const StringName key_native_type = _global_names_ptr[key_native_type_idx];
+
+ GET_INSTRUCTION_ARG(value_script_type, argc * 2 + 2);
+ Variant::Type value_builtin_type = (Variant::Type)_code_ptr[ip + 4];
+ int value_native_type_idx = _code_ptr[ip + 5];
+ GD_ERR_BREAK(value_native_type_idx < 0 || value_native_type_idx >= _global_names_count);
+ const StringName value_native_type = _global_names_ptr[value_native_type_idx];
+
+ Dictionary dict;
+
+ for (int i = 0; i < argc; i++) {
+ GET_INSTRUCTION_ARG(k, i * 2 + 0);
+ GET_INSTRUCTION_ARG(v, i * 2 + 1);
+ dict[*k] = *v;
+ }
+
+ GET_INSTRUCTION_ARG(dst, argc * 2);
+
+ *dst = Variant(); // Clear potential previous typed dictionary.
+
+ *dst = Dictionary(dict, key_builtin_type, key_native_type, *key_script_type, value_builtin_type, value_native_type, *value_script_type);
+
+ ip += 6;
+ }
+ DISPATCH_OPCODE;
+
OPCODE(OPCODE_CALL_ASYNC)
OPCODE(OPCODE_CALL_RETURN)
OPCODE(OPCODE_CALL) {
@@ -2657,6 +2798,51 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a
OPCODE_BREAK;
}
+ OPCODE(OPCODE_RETURN_TYPED_DICTIONARY) {
+ CHECK_SPACE(8);
+ GET_VARIANT_PTR(r, 0);
+
+ GET_VARIANT_PTR(key_script_type, 1);
+ Variant::Type key_builtin_type = (Variant::Type)_code_ptr[ip + 4];
+ int key_native_type_idx = _code_ptr[ip + 5];
+ GD_ERR_BREAK(key_native_type_idx < 0 || key_native_type_idx >= _global_names_count);
+ const StringName key_native_type = _global_names_ptr[key_native_type_idx];
+
+ GET_VARIANT_PTR(value_script_type, 2);
+ Variant::Type value_builtin_type = (Variant::Type)_code_ptr[ip + 6];
+ int value_native_type_idx = _code_ptr[ip + 7];
+ GD_ERR_BREAK(value_native_type_idx < 0 || value_native_type_idx >= _global_names_count);
+ const StringName value_native_type = _global_names_ptr[value_native_type_idx];
+
+ if (r->get_type() != Variant::DICTIONARY) {
+#ifdef DEBUG_ENABLED
+ err_text = vformat(R"(Trying to return a value of type "%s" where expected return type is "Dictionary[%s, %s]".)",
+ _get_var_type(r), _get_element_type(key_builtin_type, key_native_type, *key_script_type),
+ _get_element_type(value_builtin_type, value_native_type, *value_script_type));
+#endif // DEBUG_ENABLED
+ OPCODE_BREAK;
+ }
+
+ Dictionary *dictionary = VariantInternal::get_dictionary(r);
+
+ if (dictionary->get_typed_key_builtin() != ((uint32_t)key_builtin_type) || dictionary->get_typed_key_class_name() != key_native_type || dictionary->get_typed_key_script() != *key_script_type ||
+ dictionary->get_typed_value_builtin() != ((uint32_t)value_builtin_type) || dictionary->get_typed_value_class_name() != value_native_type || dictionary->get_typed_value_script() != *value_script_type) {
+#ifdef DEBUG_ENABLED
+ err_text = vformat(R"(Trying to return a dictionary of type "%s" where expected return type is "Dictionary[%s, %s]".)",
+ _get_var_type(r), _get_element_type(key_builtin_type, key_native_type, *key_script_type),
+ _get_element_type(value_builtin_type, value_native_type, *value_script_type));
+#endif // DEBUG_ENABLED
+ OPCODE_BREAK;
+ }
+
+ retvalue = *dictionary;
+
+#ifdef DEBUG_ENABLED
+ exit_ok = true;
+#endif // DEBUG_ENABLED
+ OPCODE_BREAK;
+ }
+
OPCODE(OPCODE_RETURN_TYPED_NATIVE) {
CHECK_SPACE(3);
GET_VARIANT_PTR(r, 0);
diff --git a/modules/gdscript/gdscript_warning.cpp b/modules/gdscript/gdscript_warning.cpp
index e8fb1d94b3..4ffb4bd9d1 100644
--- a/modules/gdscript/gdscript_warning.cpp
+++ b/modules/gdscript/gdscript_warning.cpp
@@ -109,6 +109,8 @@ String GDScriptWarning::get_message() const {
case STATIC_CALLED_ON_INSTANCE:
CHECK_SYMBOLS(2);
return vformat(R"*(The function "%s()" is a static function but was called from an instance. Instead, it should be directly called from the type: "%s.%s()".)*", symbols[0], symbols[1], symbols[0]);
+ case MISSING_TOOL:
+ return R"(The base class script has the "@tool" annotation, but this script does not have it.)";
case REDUNDANT_STATIC_UNLOAD:
return R"(The "@static_unload" annotation is redundant because the file does not have a class with static variables.)";
case REDUNDANT_AWAIT:
@@ -219,6 +221,7 @@ String GDScriptWarning::get_name_from_code(Code p_code) {
"UNSAFE_VOID_RETURN",
"RETURN_VALUE_DISCARDED",
"STATIC_CALLED_ON_INSTANCE",
+ "MISSING_TOOL",
"REDUNDANT_STATIC_UNLOAD",
"REDUNDANT_AWAIT",
"ASSERT_ALWAYS_TRUE",
diff --git a/modules/gdscript/gdscript_warning.h b/modules/gdscript/gdscript_warning.h
index 1c806bb4e2..ffcf00a830 100644
--- a/modules/gdscript/gdscript_warning.h
+++ b/modules/gdscript/gdscript_warning.h
@@ -70,6 +70,7 @@ public:
UNSAFE_VOID_RETURN, // Function returns void but returned a call to a function that can't be type checked.
RETURN_VALUE_DISCARDED, // Function call returns something but the value isn't used.
STATIC_CALLED_ON_INSTANCE, // A static method was called on an instance of a class instead of on the class itself.
+ MISSING_TOOL, // The base class script has the "@tool" annotation, but this script does not have it.
REDUNDANT_STATIC_UNLOAD, // The `@static_unload` annotation is used but the class does not have static data.
REDUNDANT_AWAIT, // await is used but expression is synchronous (not a signal nor a coroutine).
ASSERT_ALWAYS_TRUE, // Expression for assert argument is always true.
@@ -123,6 +124,7 @@ public:
WARN, // UNSAFE_VOID_RETURN
IGNORE, // RETURN_VALUE_DISCARDED // Too spammy by default on common cases (connect, Tween, etc.).
WARN, // STATIC_CALLED_ON_INSTANCE
+ WARN, // MISSING_TOOL
WARN, // REDUNDANT_STATIC_UNLOAD
WARN, // REDUNDANT_AWAIT
WARN, // ASSERT_ALWAYS_TRUE
diff --git a/modules/gdscript/language_server/gdscript_language_protocol.cpp b/modules/gdscript/language_server/gdscript_language_protocol.cpp
index 03d830741b..b636dbe580 100644
--- a/modules/gdscript/language_server/gdscript_language_protocol.cpp
+++ b/modules/gdscript/language_server/gdscript_language_protocol.cpp
@@ -196,7 +196,7 @@ Dictionary GDScriptLanguageProtocol::initialize(const Dictionary &p_params) {
ERR_FAIL_COND_V_MSG(!clients.has(latest_client_id), ret.to_json(),
vformat("GDScriptLanguageProtocol: Can't initialize invalid peer '%d'.", latest_client_id));
Ref<LSPeer> peer = clients.get(latest_client_id);
- if (peer != nullptr) {
+ if (peer.is_valid()) {
String msg = Variant(request).to_json_string();
msg = format_output(msg);
(*peer)->res_queue.push_back(msg.utf8());
@@ -298,7 +298,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_NULL(peer);
+ ERR_FAIL_COND(peer.is_null());
Dictionary message = make_notification(p_method, p_params);
String msg = Variant(message).to_json_string();
@@ -319,7 +319,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_NULL(peer);
+ ERR_FAIL_COND(peer.is_null());
Dictionary message = make_request(p_method, p_params, next_server_id);
next_server_id++;
diff --git a/modules/gdscript/language_server/gdscript_text_document.cpp b/modules/gdscript/language_server/gdscript_text_document.cpp
index 2224bb0040..fa5f279db9 100644
--- a/modules/gdscript/language_server/gdscript_text_document.cpp
+++ b/modules/gdscript/language_server/gdscript_text_document.cpp
@@ -309,7 +309,7 @@ Dictionary GDScriptTextDocument::resolve(const Dictionary &p_params) {
params.load(p_params["data"]);
symbol = GDScriptLanguageProtocol::get_singleton()->get_workspace()->resolve_symbol(params, item.label, item.kind == lsp::CompletionItemKind::Method || item.kind == lsp::CompletionItemKind::Function);
- } else if (data.get_type() == Variant::STRING) {
+ } else if (data.is_string()) {
String query = data;
Vector<String> param_symbols = query.split(SYMBOL_SEPERATOR, false);
diff --git a/modules/gdscript/language_server/godot_lsp.h b/modules/gdscript/language_server/godot_lsp.h
index 13c2693390..bdf339f5fe 100644
--- a/modules/gdscript/language_server/godot_lsp.h
+++ b/modules/gdscript/language_server/godot_lsp.h
@@ -1064,7 +1064,7 @@ struct CompletionItem {
}
if (p_dict.has("documentation")) {
Variant doc = p_dict["documentation"];
- if (doc.get_type() == Variant::STRING) {
+ if (doc.is_string()) {
documentation.value = doc;
} else if (doc.get_type() == Variant::DICTIONARY) {
Dictionary v = doc;
diff --git a/modules/gdscript/tests/scripts/analyzer/errors/for_loop_wrong_specified_type_with_literal_dictionary.gd b/modules/gdscript/tests/scripts/analyzer/errors/for_loop_wrong_specified_type_with_literal_dictionary.gd
new file mode 100644
index 0000000000..c180cca03c
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/errors/for_loop_wrong_specified_type_with_literal_dictionary.gd
@@ -0,0 +1,3 @@
+func test():
+ for key: int in { "a": 1 }:
+ print(key)
diff --git a/modules/gdscript/tests/scripts/analyzer/errors/for_loop_wrong_specified_type_with_literal_dictionary.out b/modules/gdscript/tests/scripts/analyzer/errors/for_loop_wrong_specified_type_with_literal_dictionary.out
new file mode 100644
index 0000000000..8530783673
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/errors/for_loop_wrong_specified_type_with_literal_dictionary.out
@@ -0,0 +1,2 @@
+GDTEST_ANALYZER_ERROR
+Cannot include a value of type "String" as "int".
diff --git a/modules/gdscript/tests/scripts/analyzer/errors/typed_dictionary_assign_differently_typed.gd b/modules/gdscript/tests/scripts/analyzer/errors/typed_dictionary_assign_differently_typed.gd
new file mode 100644
index 0000000000..75d1b7fe62
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/errors/typed_dictionary_assign_differently_typed.gd
@@ -0,0 +1,4 @@
+func test():
+ var differently: Dictionary[float, float] = { 1.0: 0.0 }
+ var typed: Dictionary[int, int] = differently
+ print('not ok')
diff --git a/modules/gdscript/tests/scripts/analyzer/errors/typed_dictionary_assign_differently_typed.out b/modules/gdscript/tests/scripts/analyzer/errors/typed_dictionary_assign_differently_typed.out
new file mode 100644
index 0000000000..e05d4be8c9
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/errors/typed_dictionary_assign_differently_typed.out
@@ -0,0 +1,2 @@
+GDTEST_ANALYZER_ERROR
+Cannot assign a value of type Dictionary[float, float] to variable "typed" with specified type Dictionary[int, int].
diff --git a/modules/gdscript/tests/scripts/analyzer/errors/typed_dictionary_assignment.gd b/modules/gdscript/tests/scripts/analyzer/errors/typed_dictionary_assignment.gd
new file mode 100644
index 0000000000..e0af71823a
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/errors/typed_dictionary_assignment.gd
@@ -0,0 +1,2 @@
+func test():
+ const dict: Dictionary[int, int] = { "Hello": "World" }
diff --git a/modules/gdscript/tests/scripts/analyzer/errors/typed_dictionary_assignment.out b/modules/gdscript/tests/scripts/analyzer/errors/typed_dictionary_assignment.out
new file mode 100644
index 0000000000..8530783673
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/errors/typed_dictionary_assignment.out
@@ -0,0 +1,2 @@
+GDTEST_ANALYZER_ERROR
+Cannot include a value of type "String" as "int".
diff --git a/modules/gdscript/tests/scripts/analyzer/errors/typed_dictionary_init_with_unconvertible_in_literal.gd b/modules/gdscript/tests/scripts/analyzer/errors/typed_dictionary_init_with_unconvertible_in_literal.gd
new file mode 100644
index 0000000000..814ba12aef
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/errors/typed_dictionary_init_with_unconvertible_in_literal.gd
@@ -0,0 +1,4 @@
+func test():
+ var unconvertible := 1
+ var typed: Dictionary[Object, Object] = { unconvertible: unconvertible }
+ print('not ok')
diff --git a/modules/gdscript/tests/scripts/analyzer/errors/typed_dictionary_init_with_unconvertible_in_literal.out b/modules/gdscript/tests/scripts/analyzer/errors/typed_dictionary_init_with_unconvertible_in_literal.out
new file mode 100644
index 0000000000..9d6c9d9144
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/errors/typed_dictionary_init_with_unconvertible_in_literal.out
@@ -0,0 +1,2 @@
+GDTEST_ANALYZER_ERROR
+Cannot have a key of type "int" in a dictionary of type "Dictionary[Object, Object]".
diff --git a/modules/gdscript/tests/scripts/analyzer/errors/typed_dictionary_pass_differently_to_typed.gd b/modules/gdscript/tests/scripts/analyzer/errors/typed_dictionary_pass_differently_to_typed.gd
new file mode 100644
index 0000000000..73d8ce2b96
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/errors/typed_dictionary_pass_differently_to_typed.gd
@@ -0,0 +1,7 @@
+func expect_typed(typed: Dictionary[int, int]):
+ print(typed.size())
+
+func test():
+ var differently: Dictionary[float, float] = { 1.0: 0.0 }
+ expect_typed(differently)
+ print('not ok')
diff --git a/modules/gdscript/tests/scripts/analyzer/errors/typed_dictionary_pass_differently_to_typed.out b/modules/gdscript/tests/scripts/analyzer/errors/typed_dictionary_pass_differently_to_typed.out
new file mode 100644
index 0000000000..302109cf8a
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/errors/typed_dictionary_pass_differently_to_typed.out
@@ -0,0 +1,2 @@
+GDTEST_ANALYZER_ERROR
+Invalid argument for "expect_typed()" function: argument 1 should be "Dictionary[int, int]" but is "Dictionary[float, float]".
diff --git a/modules/gdscript/tests/scripts/analyzer/features/assymetric_assignment_good.gd b/modules/gdscript/tests/scripts/analyzer/features/assymetric_assignment_good.gd
index efd8ad6edb..60bcde4b8c 100644
--- a/modules/gdscript/tests/scripts/analyzer/features/assymetric_assignment_good.gd
+++ b/modules/gdscript/tests/scripts/analyzer/features/assymetric_assignment_good.gd
@@ -3,14 +3,13 @@ const const_color: Color = 'red'
func func_color(arg_color: Color = 'blue') -> bool:
return arg_color == Color.BLUE
-@warning_ignore("assert_always_true")
func test():
- assert(const_color == Color.RED)
+ Utils.check(const_color == Color.RED)
- assert(func_color() == true)
- assert(func_color('blue') == true)
+ Utils.check(func_color() == true)
+ Utils.check(func_color('blue') == true)
var var_color: Color = 'green'
- assert(var_color == Color.GREEN)
+ Utils.check(var_color == Color.GREEN)
print('ok')
diff --git a/modules/gdscript/tests/scripts/analyzer/features/const_conversions.gd b/modules/gdscript/tests/scripts/analyzer/features/const_conversions.gd
index bed9dd0e96..5318d11f33 100644
--- a/modules/gdscript/tests/scripts/analyzer/features/const_conversions.gd
+++ b/modules/gdscript/tests/scripts/analyzer/features/const_conversions.gd
@@ -5,20 +5,19 @@ const const_float_cast: float = 76 as float
const const_packed_empty: PackedFloat64Array = []
const const_packed_ints: PackedFloat64Array = [52]
-@warning_ignore("assert_always_true")
func test():
- assert(typeof(const_float_int) == TYPE_FLOAT)
- assert(str(const_float_int) == '19')
- assert(typeof(const_float_plus) == TYPE_FLOAT)
- assert(str(const_float_plus) == '34')
- assert(typeof(const_float_cast) == TYPE_FLOAT)
- assert(str(const_float_cast) == '76')
+ Utils.check(typeof(const_float_int) == TYPE_FLOAT)
+ Utils.check(str(const_float_int) == '19')
+ Utils.check(typeof(const_float_plus) == TYPE_FLOAT)
+ Utils.check(str(const_float_plus) == '34')
+ Utils.check(typeof(const_float_cast) == TYPE_FLOAT)
+ Utils.check(str(const_float_cast) == '76')
- assert(typeof(const_packed_empty) == TYPE_PACKED_FLOAT64_ARRAY)
- assert(str(const_packed_empty) == '[]')
- assert(typeof(const_packed_ints) == TYPE_PACKED_FLOAT64_ARRAY)
- assert(str(const_packed_ints) == '[52]')
- assert(typeof(const_packed_ints[0]) == TYPE_FLOAT)
- assert(str(const_packed_ints[0]) == '52')
+ Utils.check(typeof(const_packed_empty) == TYPE_PACKED_FLOAT64_ARRAY)
+ Utils.check(str(const_packed_empty) == '[]')
+ Utils.check(typeof(const_packed_ints) == TYPE_PACKED_FLOAT64_ARRAY)
+ Utils.check(str(const_packed_ints) == '[52]')
+ Utils.check(typeof(const_packed_ints[0]) == TYPE_FLOAT)
+ Utils.check(str(const_packed_ints[0]) == '52')
print('ok')
diff --git a/modules/gdscript/tests/scripts/analyzer/features/enums_in_range_call.gd b/modules/gdscript/tests/scripts/analyzer/features/enums_in_range_call.gd
index d2d9d04508..a569488d3c 100644
--- a/modules/gdscript/tests/scripts/analyzer/features/enums_in_range_call.gd
+++ b/modules/gdscript/tests/scripts/analyzer/features/enums_in_range_call.gd
@@ -5,5 +5,5 @@ func test():
for value in range(E.E0, E.E3):
var inferable := value
total += inferable
- assert(total == 0 + 1 + 2)
+ Utils.check(total == 0 + 1 + 2)
print('ok')
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
index 39f490c4b3..ec89226328 100644
--- a/modules/gdscript/tests/scripts/analyzer/features/export_enum_as_dictionary.gd
+++ b/modules/gdscript/tests/scripts/analyzer/features/export_enum_as_dictionary.gd
@@ -2,8 +2,6 @@ class_name TestExportEnumAsDictionary
enum MyEnum {A, B, C}
-const Utils = preload("../../utils.notest.gd")
-
@export var test_1 = MyEnum
@export var test_2 = MyEnum.A
@export var test_3 := MyEnum
diff --git a/modules/gdscript/tests/scripts/analyzer/features/for_range_usage.gd b/modules/gdscript/tests/scripts/analyzer/features/for_range_usage.gd
index 4a7f10f1ee..9ce0782d5c 100644
--- a/modules/gdscript/tests/scripts/analyzer/features/for_range_usage.gd
+++ b/modules/gdscript/tests/scripts/analyzer/features/for_range_usage.gd
@@ -3,5 +3,5 @@ func test():
var result := ''
for i in range(array.size(), 0, -1):
result += str(array[i - 1])
- assert(result == '963')
+ Utils.check(result == '963')
print('ok')
diff --git a/modules/gdscript/tests/scripts/analyzer/features/function_match_parent_signature_with_extra_parameters.gd b/modules/gdscript/tests/scripts/analyzer/features/function_match_parent_signature_with_extra_parameters.gd
index d678f3acfc..e0cbdacb38 100644
--- a/modules/gdscript/tests/scripts/analyzer/features/function_match_parent_signature_with_extra_parameters.gd
+++ b/modules/gdscript/tests/scripts/analyzer/features/function_match_parent_signature_with_extra_parameters.gd
@@ -2,11 +2,11 @@ func test():
var instance := Parent.new()
var result := instance.my_function(1)
print(result)
- assert(result == 1)
+ Utils.check(result == 1)
instance = Child.new()
result = instance.my_function(2)
print(result)
- assert(result == 0)
+ Utils.check(result == 0)
class Parent:
func my_function(par1: int) -> int:
diff --git a/modules/gdscript/tests/scripts/analyzer/features/return_conversions.gd b/modules/gdscript/tests/scripts/analyzer/features/return_conversions.gd
index 0b1576e66e..cbe8e9da34 100644
--- a/modules/gdscript/tests/scripts/analyzer/features/return_conversions.gd
+++ b/modules/gdscript/tests/scripts/analyzer/features/return_conversions.gd
@@ -8,27 +8,27 @@ func convert_var_array_to_packed() -> PackedStringArray: var array := ['79']; re
func test():
var converted_literal_int := convert_literal_int_to_float()
- assert(typeof(converted_literal_int) == TYPE_FLOAT)
- assert(converted_literal_int == 76.0)
+ Utils.check(typeof(converted_literal_int) == TYPE_FLOAT)
+ Utils.check(converted_literal_int == 76.0)
var converted_arg_int := convert_arg_int_to_float(36)
- assert(typeof(converted_arg_int) == TYPE_FLOAT)
- assert(converted_arg_int == 36.0)
+ Utils.check(typeof(converted_arg_int) == TYPE_FLOAT)
+ Utils.check(converted_arg_int == 36.0)
var converted_var_int := convert_var_int_to_float()
- assert(typeof(converted_var_int) == TYPE_FLOAT)
- assert(converted_var_int == 59.0)
+ Utils.check(typeof(converted_var_int) == TYPE_FLOAT)
+ Utils.check(converted_var_int == 59.0)
var converted_literal_array := convert_literal_array_to_packed()
- assert(typeof(converted_literal_array) == TYPE_PACKED_STRING_ARRAY)
- assert(str(converted_literal_array) == '["46"]')
+ Utils.check(typeof(converted_literal_array) == TYPE_PACKED_STRING_ARRAY)
+ Utils.check(str(converted_literal_array) == '["46"]')
var converted_arg_array := convert_arg_array_to_packed(['91'])
- assert(typeof(converted_arg_array) == TYPE_PACKED_STRING_ARRAY)
- assert(str(converted_arg_array) == '["91"]')
+ Utils.check(typeof(converted_arg_array) == TYPE_PACKED_STRING_ARRAY)
+ Utils.check(str(converted_arg_array) == '["91"]')
var converted_var_array := convert_var_array_to_packed()
- assert(typeof(converted_var_array) == TYPE_PACKED_STRING_ARRAY)
- assert(str(converted_var_array) == '["79"]')
+ Utils.check(typeof(converted_var_array) == TYPE_PACKED_STRING_ARRAY)
+ Utils.check(str(converted_var_array) == '["79"]')
print('ok')
diff --git a/modules/gdscript/tests/scripts/analyzer/features/ternary_hard_infer.gd b/modules/gdscript/tests/scripts/analyzer/features/ternary_hard_infer.gd
index 44ca5f4dd0..d49acaacd3 100644
--- a/modules/gdscript/tests/scripts/analyzer/features/ternary_hard_infer.gd
+++ b/modules/gdscript/tests/scripts/analyzer/features/ternary_hard_infer.gd
@@ -2,7 +2,7 @@ func test():
var left_hard_int := 1
var right_hard_int := 2
var result_hard_int := left_hard_int if true else right_hard_int
- assert(result_hard_int == 1)
+ Utils.check(result_hard_int == 1)
@warning_ignore("inference_on_variant")
var left_hard_variant := 1 as Variant
@@ -10,6 +10,6 @@ func test():
var right_hard_variant := 2.0 as Variant
@warning_ignore("inference_on_variant")
var result_hard_variant := left_hard_variant if true else right_hard_variant
- assert(result_hard_variant == 1)
+ Utils.check(result_hard_variant == 1)
print('ok')
diff --git a/modules/gdscript/tests/scripts/analyzer/features/type_test_usage.gd b/modules/gdscript/tests/scripts/analyzer/features/type_test_usage.gd
index 12dc0b93df..ee30f01dfb 100644
--- a/modules/gdscript/tests/scripts/analyzer/features/type_test_usage.gd
+++ b/modules/gdscript/tests/scripts/analyzer/features/type_test_usage.gd
@@ -4,124 +4,123 @@ class A extends RefCounted:
class B extends A:
pass
-@warning_ignore("assert_always_true")
func test():
var builtin: Variant = 3
- assert((builtin is Variant) == true)
- assert((builtin is int) == true)
- assert(is_instance_of(builtin, TYPE_INT) == true)
- assert((builtin is float) == false)
- assert(is_instance_of(builtin, TYPE_FLOAT) == false)
+ Utils.check((builtin is Variant) == true)
+ Utils.check((builtin is int) == true)
+ Utils.check(is_instance_of(builtin, TYPE_INT) == true)
+ Utils.check((builtin is float) == false)
+ Utils.check(is_instance_of(builtin, TYPE_FLOAT) == false)
const const_builtin: Variant = 3
- assert((const_builtin is Variant) == true)
- assert((const_builtin is int) == true)
- assert(is_instance_of(const_builtin, TYPE_INT) == true)
- assert((const_builtin is float) == false)
- assert(is_instance_of(const_builtin, TYPE_FLOAT) == false)
+ Utils.check((const_builtin is Variant) == true)
+ Utils.check((const_builtin is int) == true)
+ Utils.check(is_instance_of(const_builtin, TYPE_INT) == true)
+ Utils.check((const_builtin is float) == false)
+ Utils.check(is_instance_of(const_builtin, TYPE_FLOAT) == false)
var int_array: Variant = [] as Array[int]
- assert((int_array is Variant) == true)
- assert((int_array is Array) == true)
- assert(is_instance_of(int_array, TYPE_ARRAY) == true)
- assert((int_array is Array[int]) == true)
- assert((int_array is Array[float]) == false)
- assert((int_array is int) == false)
- assert(is_instance_of(int_array, TYPE_INT) == false)
+ Utils.check((int_array is Variant) == true)
+ Utils.check((int_array is Array) == true)
+ Utils.check(is_instance_of(int_array, TYPE_ARRAY) == true)
+ Utils.check((int_array is Array[int]) == true)
+ Utils.check((int_array is Array[float]) == false)
+ Utils.check((int_array is int) == false)
+ Utils.check(is_instance_of(int_array, TYPE_INT) == false)
var const_int_array: Variant = [] as Array[int]
- assert((const_int_array is Variant) == true)
- assert((const_int_array is Array) == true)
- assert(is_instance_of(const_int_array, TYPE_ARRAY) == true)
- assert((const_int_array is Array[int]) == true)
- assert((const_int_array is Array[float]) == false)
- assert((const_int_array is int) == false)
- assert(is_instance_of(const_int_array, TYPE_INT) == false)
+ Utils.check((const_int_array is Variant) == true)
+ Utils.check((const_int_array is Array) == true)
+ Utils.check(is_instance_of(const_int_array, TYPE_ARRAY) == true)
+ Utils.check((const_int_array is Array[int]) == true)
+ Utils.check((const_int_array is Array[float]) == false)
+ Utils.check((const_int_array is int) == false)
+ Utils.check(is_instance_of(const_int_array, TYPE_INT) == false)
var b_array: Variant = [] as Array[B]
- assert((b_array is Variant) == true)
- assert((b_array is Array) == true)
- assert(is_instance_of(b_array, TYPE_ARRAY) == true)
- assert((b_array is Array[B]) == true)
- assert((b_array is Array[A]) == false)
- assert((b_array is Array[int]) == false)
- assert((b_array is int) == false)
- assert(is_instance_of(b_array, TYPE_INT) == false)
+ Utils.check((b_array is Variant) == true)
+ Utils.check((b_array is Array) == true)
+ Utils.check(is_instance_of(b_array, TYPE_ARRAY) == true)
+ Utils.check((b_array is Array[B]) == true)
+ Utils.check((b_array is Array[A]) == false)
+ Utils.check((b_array is Array[int]) == false)
+ Utils.check((b_array is int) == false)
+ Utils.check(is_instance_of(b_array, TYPE_INT) == false)
var const_b_array: Variant = [] as Array[B]
- assert((const_b_array is Variant) == true)
- assert((const_b_array is Array) == true)
- assert(is_instance_of(const_b_array, TYPE_ARRAY) == true)
- assert((const_b_array is Array[B]) == true)
- assert((const_b_array is Array[A]) == false)
- assert((const_b_array is Array[int]) == false)
- assert((const_b_array is int) == false)
- assert(is_instance_of(const_b_array, TYPE_INT) == false)
+ Utils.check((const_b_array is Variant) == true)
+ Utils.check((const_b_array is Array) == true)
+ Utils.check(is_instance_of(const_b_array, TYPE_ARRAY) == true)
+ Utils.check((const_b_array is Array[B]) == true)
+ Utils.check((const_b_array is Array[A]) == false)
+ Utils.check((const_b_array is Array[int]) == false)
+ Utils.check((const_b_array is int) == false)
+ Utils.check(is_instance_of(const_b_array, TYPE_INT) == false)
var native: Variant = RefCounted.new()
- assert((native is Variant) == true)
- assert((native is Object) == true)
- assert(is_instance_of(native, TYPE_OBJECT) == true)
- assert(is_instance_of(native, Object) == true)
- assert((native is RefCounted) == true)
- assert(is_instance_of(native, RefCounted) == true)
- assert((native is Node) == false)
- assert(is_instance_of(native, Node) == false)
- assert((native is int) == false)
- assert(is_instance_of(native, TYPE_INT) == false)
+ Utils.check((native is Variant) == true)
+ Utils.check((native is Object) == true)
+ Utils.check(is_instance_of(native, TYPE_OBJECT) == true)
+ Utils.check(is_instance_of(native, Object) == true)
+ Utils.check((native is RefCounted) == true)
+ Utils.check(is_instance_of(native, RefCounted) == true)
+ Utils.check((native is Node) == false)
+ Utils.check(is_instance_of(native, Node) == false)
+ Utils.check((native is int) == false)
+ Utils.check(is_instance_of(native, TYPE_INT) == false)
var a_script: Variant = A.new()
- assert((a_script is Variant) == true)
- assert((a_script is Object) == true)
- assert(is_instance_of(a_script, TYPE_OBJECT) == true)
- assert(is_instance_of(a_script, Object) == true)
- assert((a_script is RefCounted) == true)
- assert(is_instance_of(a_script, RefCounted) == true)
- assert((a_script is A) == true)
- assert(is_instance_of(a_script, A) == true)
- assert((a_script is B) == false)
- assert(is_instance_of(a_script, B) == false)
- assert((a_script is Node) == false)
- assert(is_instance_of(a_script, Node) == false)
- assert((a_script is int) == false)
- assert(is_instance_of(a_script, TYPE_INT) == false)
+ Utils.check((a_script is Variant) == true)
+ Utils.check((a_script is Object) == true)
+ Utils.check(is_instance_of(a_script, TYPE_OBJECT) == true)
+ Utils.check(is_instance_of(a_script, Object) == true)
+ Utils.check((a_script is RefCounted) == true)
+ Utils.check(is_instance_of(a_script, RefCounted) == true)
+ Utils.check((a_script is A) == true)
+ Utils.check(is_instance_of(a_script, A) == true)
+ Utils.check((a_script is B) == false)
+ Utils.check(is_instance_of(a_script, B) == false)
+ Utils.check((a_script is Node) == false)
+ Utils.check(is_instance_of(a_script, Node) == false)
+ Utils.check((a_script is int) == false)
+ Utils.check(is_instance_of(a_script, TYPE_INT) == false)
var b_script: Variant = B.new()
- assert((b_script is Variant) == true)
- assert((b_script is Object) == true)
- assert(is_instance_of(b_script, TYPE_OBJECT) == true)
- assert(is_instance_of(b_script, Object) == true)
- assert((b_script is RefCounted) == true)
- assert(is_instance_of(b_script, RefCounted) == true)
- assert((b_script is A) == true)
- assert(is_instance_of(b_script, A) == true)
- assert((b_script is B) == true)
- assert(is_instance_of(b_script, B) == true)
- assert((b_script is Node) == false)
- assert(is_instance_of(b_script, Node) == false)
- assert((b_script is int) == false)
- assert(is_instance_of(b_script, TYPE_INT) == false)
+ Utils.check((b_script is Variant) == true)
+ Utils.check((b_script is Object) == true)
+ Utils.check(is_instance_of(b_script, TYPE_OBJECT) == true)
+ Utils.check(is_instance_of(b_script, Object) == true)
+ Utils.check((b_script is RefCounted) == true)
+ Utils.check(is_instance_of(b_script, RefCounted) == true)
+ Utils.check((b_script is A) == true)
+ Utils.check(is_instance_of(b_script, A) == true)
+ Utils.check((b_script is B) == true)
+ Utils.check(is_instance_of(b_script, B) == true)
+ Utils.check((b_script is Node) == false)
+ Utils.check(is_instance_of(b_script, Node) == false)
+ Utils.check((b_script is int) == false)
+ Utils.check(is_instance_of(b_script, TYPE_INT) == false)
var var_null: Variant = null
- assert((var_null is Variant) == true)
- assert((var_null is int) == false)
- assert(is_instance_of(var_null, TYPE_INT) == false)
- assert((var_null is Object) == false)
- assert(is_instance_of(var_null, TYPE_OBJECT) == false)
- assert((var_null is RefCounted) == false)
- assert(is_instance_of(var_null, RefCounted) == false)
- assert((var_null is A) == false)
- assert(is_instance_of(var_null, A) == false)
+ Utils.check((var_null is Variant) == true)
+ Utils.check((var_null is int) == false)
+ Utils.check(is_instance_of(var_null, TYPE_INT) == false)
+ Utils.check((var_null is Object) == false)
+ Utils.check(is_instance_of(var_null, TYPE_OBJECT) == false)
+ Utils.check((var_null is RefCounted) == false)
+ Utils.check(is_instance_of(var_null, RefCounted) == false)
+ Utils.check((var_null is A) == false)
+ Utils.check(is_instance_of(var_null, A) == false)
const const_null: Variant = null
- assert((const_null is Variant) == true)
- assert((const_null is int) == false)
- assert(is_instance_of(const_null, TYPE_INT) == false)
- assert((const_null is Object) == false)
- assert(is_instance_of(const_null, TYPE_OBJECT) == false)
- assert((const_null is RefCounted) == false)
- assert(is_instance_of(const_null, RefCounted) == false)
- assert((const_null is A) == false)
- assert(is_instance_of(const_null, A) == false)
+ Utils.check((const_null is Variant) == true)
+ Utils.check((const_null is int) == false)
+ Utils.check(is_instance_of(const_null, TYPE_INT) == false)
+ Utils.check((const_null is Object) == false)
+ Utils.check(is_instance_of(const_null, TYPE_OBJECT) == false)
+ Utils.check((const_null is RefCounted) == false)
+ Utils.check(is_instance_of(const_null, RefCounted) == false)
+ Utils.check((const_null is A) == false)
+ Utils.check(is_instance_of(const_null, A) == false)
print('ok')
diff --git a/modules/gdscript/tests/scripts/analyzer/features/typed_array_usage.gd b/modules/gdscript/tests/scripts/analyzer/features/typed_array_usage.gd
index b000c82717..fe0274c27b 100644
--- a/modules/gdscript/tests/scripts/analyzer/features/typed_array_usage.gd
+++ b/modules/gdscript/tests/scripts/analyzer/features/typed_array_usage.gd
@@ -10,206 +10,205 @@ class Members:
var two: Array[int] = one
func check_passing() -> bool:
- assert(str(one) == '[104]')
- assert(str(two) == '[104]')
+ Utils.check(str(one) == '[104]')
+ Utils.check(str(two) == '[104]')
two.push_back(582)
- assert(str(one) == '[104, 582]')
- assert(str(two) == '[104, 582]')
+ Utils.check(str(one) == '[104, 582]')
+ Utils.check(str(two) == '[104, 582]')
two = [486]
- assert(str(one) == '[104, 582]')
- assert(str(two) == '[486]')
+ Utils.check(str(one) == '[104, 582]')
+ Utils.check(str(two) == '[486]')
return true
@warning_ignore("unsafe_method_access")
-@warning_ignore("assert_always_true")
@warning_ignore("return_value_discarded")
func test():
var untyped_basic = [459]
- assert(str(untyped_basic) == '[459]')
- assert(untyped_basic.get_typed_builtin() == TYPE_NIL)
+ Utils.check(str(untyped_basic) == '[459]')
+ Utils.check(untyped_basic.get_typed_builtin() == TYPE_NIL)
var inferred_basic := [366]
- assert(str(inferred_basic) == '[366]')
- assert(inferred_basic.get_typed_builtin() == TYPE_NIL)
+ Utils.check(str(inferred_basic) == '[366]')
+ Utils.check(inferred_basic.get_typed_builtin() == TYPE_NIL)
var typed_basic: Array = [521]
- assert(str(typed_basic) == '[521]')
- assert(typed_basic.get_typed_builtin() == TYPE_NIL)
+ Utils.check(str(typed_basic) == '[521]')
+ Utils.check(typed_basic.get_typed_builtin() == TYPE_NIL)
var empty_floats: Array[float] = []
- assert(str(empty_floats) == '[]')
- assert(empty_floats.get_typed_builtin() == TYPE_FLOAT)
+ Utils.check(str(empty_floats) == '[]')
+ Utils.check(empty_floats.get_typed_builtin() == TYPE_FLOAT)
untyped_basic = empty_floats
- assert(untyped_basic.get_typed_builtin() == TYPE_FLOAT)
+ Utils.check(untyped_basic.get_typed_builtin() == TYPE_FLOAT)
inferred_basic = empty_floats
- assert(inferred_basic.get_typed_builtin() == TYPE_FLOAT)
+ Utils.check(inferred_basic.get_typed_builtin() == TYPE_FLOAT)
typed_basic = empty_floats
- assert(typed_basic.get_typed_builtin() == TYPE_FLOAT)
+ Utils.check(typed_basic.get_typed_builtin() == TYPE_FLOAT)
empty_floats.push_back(705.0)
untyped_basic.push_back(430.0)
inferred_basic.push_back(263.0)
typed_basic.push_back(518.0)
- assert(str(empty_floats) == '[705, 430, 263, 518]')
- assert(str(untyped_basic) == '[705, 430, 263, 518]')
- assert(str(inferred_basic) == '[705, 430, 263, 518]')
- assert(str(typed_basic) == '[705, 430, 263, 518]')
+ Utils.check(str(empty_floats) == '[705, 430, 263, 518]')
+ Utils.check(str(untyped_basic) == '[705, 430, 263, 518]')
+ Utils.check(str(inferred_basic) == '[705, 430, 263, 518]')
+ Utils.check(str(typed_basic) == '[705, 430, 263, 518]')
const constant_float := 950.0
const constant_int := 170
var typed_float := 954.0
var filled_floats: Array[float] = [constant_float, constant_int, typed_float, empty_floats[1] + empty_floats[2]]
- assert(str(filled_floats) == '[950, 170, 954, 693]')
- assert(filled_floats.get_typed_builtin() == TYPE_FLOAT)
+ Utils.check(str(filled_floats) == '[950, 170, 954, 693]')
+ Utils.check(filled_floats.get_typed_builtin() == TYPE_FLOAT)
var casted_floats := [empty_floats[2] * 2] as Array[float]
- assert(str(casted_floats) == '[526]')
- assert(casted_floats.get_typed_builtin() == TYPE_FLOAT)
+ Utils.check(str(casted_floats) == '[526]')
+ Utils.check(casted_floats.get_typed_builtin() == TYPE_FLOAT)
var returned_floats = (func () -> Array[float]: return [554]).call()
- assert(str(returned_floats) == '[554]')
- assert(returned_floats.get_typed_builtin() == TYPE_FLOAT)
+ Utils.check(str(returned_floats) == '[554]')
+ Utils.check(returned_floats.get_typed_builtin() == TYPE_FLOAT)
var passed_floats = floats_identity([663.0 if randf() > 0.5 else 663.0])
- assert(str(passed_floats) == '[663]')
- assert(passed_floats.get_typed_builtin() == TYPE_FLOAT)
+ Utils.check(str(passed_floats) == '[663]')
+ Utils.check(passed_floats.get_typed_builtin() == TYPE_FLOAT)
var default_floats = (func (floats: Array[float] = [364.0]): return floats).call()
- assert(str(default_floats) == '[364]')
- assert(default_floats.get_typed_builtin() == TYPE_FLOAT)
+ Utils.check(str(default_floats) == '[364]')
+ Utils.check(default_floats.get_typed_builtin() == TYPE_FLOAT)
var typed_int := 556
var converted_floats: Array[float] = [typed_int]
converted_floats.push_back(498)
- assert(str(converted_floats) == '[556, 498]')
- assert(converted_floats.get_typed_builtin() == TYPE_FLOAT)
+ Utils.check(str(converted_floats) == '[556, 498]')
+ Utils.check(converted_floats.get_typed_builtin() == TYPE_FLOAT)
const constant_basic = [228]
- assert(str(constant_basic) == '[228]')
- assert(constant_basic.get_typed_builtin() == TYPE_NIL)
+ Utils.check(str(constant_basic) == '[228]')
+ Utils.check(constant_basic.get_typed_builtin() == TYPE_NIL)
const constant_floats: Array[float] = [constant_float - constant_basic[0] - constant_int]
- assert(str(constant_floats) == '[552]')
- assert(constant_floats.get_typed_builtin() == TYPE_FLOAT)
+ Utils.check(str(constant_floats) == '[552]')
+ Utils.check(constant_floats.get_typed_builtin() == TYPE_FLOAT)
var source_floats: Array[float] = [999.74]
untyped_basic = source_floats
var destination_floats: Array[float] = untyped_basic
destination_floats[0] -= 0.74
- assert(str(source_floats) == '[999]')
- assert(str(untyped_basic) == '[999]')
- assert(str(destination_floats) == '[999]')
- assert(destination_floats.get_typed_builtin() == TYPE_FLOAT)
+ Utils.check(str(source_floats) == '[999]')
+ Utils.check(str(untyped_basic) == '[999]')
+ Utils.check(str(destination_floats) == '[999]')
+ Utils.check(destination_floats.get_typed_builtin() == TYPE_FLOAT)
var duplicated_floats := empty_floats.duplicate().slice(2, 3)
duplicated_floats[0] *= 3
- assert(str(duplicated_floats) == '[789]')
- assert(duplicated_floats.get_typed_builtin() == TYPE_FLOAT)
+ Utils.check(str(duplicated_floats) == '[789]')
+ Utils.check(duplicated_floats.get_typed_builtin() == TYPE_FLOAT)
var b_objects: Array[B] = [B.new(), B.new() as A, null]
- assert(b_objects.size() == 3)
- assert(b_objects.get_typed_builtin() == TYPE_OBJECT)
- assert(b_objects.get_typed_script() == B)
+ Utils.check(b_objects.size() == 3)
+ Utils.check(b_objects.get_typed_builtin() == TYPE_OBJECT)
+ Utils.check(b_objects.get_typed_script() == B)
var a_objects: Array[A] = [A.new(), B.new(), null, b_objects[0]]
- assert(a_objects.size() == 4)
- assert(a_objects.get_typed_builtin() == TYPE_OBJECT)
- assert(a_objects.get_typed_script() == A)
+ Utils.check(a_objects.size() == 4)
+ Utils.check(a_objects.get_typed_builtin() == TYPE_OBJECT)
+ Utils.check(a_objects.get_typed_script() == A)
var a_passed = (func check_a_passing(p_objects: Array[A]): return p_objects.size()).call(a_objects)
- assert(a_passed == 4)
+ Utils.check(a_passed == 4)
var b_passed = (func check_b_passing(basic: Array): return basic[0] != null).call(b_objects)
- assert(b_passed == true)
+ Utils.check(b_passed == true)
var empty_strings: Array[String] = []
var empty_bools: Array[bool] = []
var empty_basic_one := []
var empty_basic_two := []
- assert(empty_strings == empty_bools)
- assert(empty_basic_one == empty_basic_two)
- assert(empty_strings.hash() == empty_bools.hash())
- assert(empty_basic_one.hash() == empty_basic_two.hash())
+ Utils.check(empty_strings == empty_bools)
+ Utils.check(empty_basic_one == empty_basic_two)
+ Utils.check(empty_strings.hash() == empty_bools.hash())
+ Utils.check(empty_basic_one.hash() == empty_basic_two.hash())
var assign_source: Array[int] = [527]
var assign_target: Array[int] = []
assign_target.assign(assign_source)
- assert(str(assign_source) == '[527]')
- assert(str(assign_target) == '[527]')
+ Utils.check(str(assign_source) == '[527]')
+ Utils.check(str(assign_target) == '[527]')
assign_source.push_back(657)
- assert(str(assign_source) == '[527, 657]')
- assert(str(assign_target) == '[527]')
+ Utils.check(str(assign_source) == '[527, 657]')
+ Utils.check(str(assign_target) == '[527]')
var defaults_passed = (func check_defaults_passing(one: Array[int] = [], two := one):
one.push_back(887)
two.push_back(198)
- assert(str(one) == '[887, 198]')
- assert(str(two) == '[887, 198]')
+ Utils.check(str(one) == '[887, 198]')
+ Utils.check(str(two) == '[887, 198]')
two = [130]
- assert(str(one) == '[887, 198]')
- assert(str(two) == '[130]')
+ Utils.check(str(one) == '[887, 198]')
+ Utils.check(str(two) == '[130]')
return true
).call()
- assert(defaults_passed == true)
+ Utils.check(defaults_passed == true)
var members := Members.new()
var members_passed := members.check_passing()
- assert(members_passed == true)
+ Utils.check(members_passed == true)
var resized_basic: Array = []
resized_basic.resize(1)
- assert(typeof(resized_basic[0]) == TYPE_NIL)
- assert(resized_basic[0] == null)
+ Utils.check(typeof(resized_basic[0]) == TYPE_NIL)
+ Utils.check(resized_basic[0] == null)
var resized_ints: Array[int] = []
resized_ints.resize(1)
- assert(typeof(resized_ints[0]) == TYPE_INT)
- assert(resized_ints[0] == 0)
+ Utils.check(typeof(resized_ints[0]) == TYPE_INT)
+ Utils.check(resized_ints[0] == 0)
var resized_arrays: Array[Array] = []
resized_arrays.resize(1)
- assert(typeof(resized_arrays[0]) == TYPE_ARRAY)
+ Utils.check(typeof(resized_arrays[0]) == TYPE_ARRAY)
resized_arrays[0].resize(1)
resized_arrays[0][0] = 523
- assert(str(resized_arrays) == '[[523]]')
+ Utils.check(str(resized_arrays) == '[[523]]')
var resized_objects: Array[Object] = []
resized_objects.resize(1)
- assert(typeof(resized_objects[0]) == TYPE_NIL)
- assert(resized_objects[0] == null)
+ Utils.check(typeof(resized_objects[0]) == TYPE_NIL)
+ Utils.check(resized_objects[0] == null)
var typed_enums: Array[E] = []
typed_enums.resize(1)
- assert(str(typed_enums) == '[0]')
+ Utils.check(str(typed_enums) == '[0]')
typed_enums[0] = E.E0
- assert(str(typed_enums) == '[391]')
- assert(typed_enums.get_typed_builtin() == TYPE_INT)
+ Utils.check(str(typed_enums) == '[391]')
+ Utils.check(typed_enums.get_typed_builtin() == TYPE_INT)
const const_enums: Array[E] = []
- assert(const_enums.get_typed_builtin() == TYPE_INT)
- assert(const_enums.get_typed_class_name() == &'')
+ Utils.check(const_enums.get_typed_builtin() == TYPE_INT)
+ Utils.check(const_enums.get_typed_class_name() == &'')
var a := A.new()
var typed_natives: Array[RefCounted] = [a]
var typed_scripts = Array(typed_natives, TYPE_OBJECT, "RefCounted", A)
- assert(typed_scripts[0] == a)
+ Utils.check(typed_scripts[0] == a)
print('ok')
diff --git a/modules/gdscript/tests/scripts/analyzer/features/typed_dictionary_as_default_parameter.gd b/modules/gdscript/tests/scripts/analyzer/features/typed_dictionary_as_default_parameter.gd
new file mode 100644
index 0000000000..65f5e7da07
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/features/typed_dictionary_as_default_parameter.gd
@@ -0,0 +1,20 @@
+func print_untyped(dictionary = { 0: 1 }) -> void:
+ print(dictionary)
+ print(dictionary.get_typed_key_builtin())
+ print(dictionary.get_typed_value_builtin())
+
+func print_inferred(dictionary := { 2: 3 }) -> void:
+ print(dictionary)
+ print(dictionary.get_typed_key_builtin())
+ print(dictionary.get_typed_value_builtin())
+
+func print_typed(dictionary: Dictionary[int, int] = { 4: 5 }) -> void:
+ print(dictionary)
+ print(dictionary.get_typed_key_builtin())
+ print(dictionary.get_typed_value_builtin())
+
+func test():
+ print_untyped()
+ print_inferred()
+ print_typed()
+ print('ok')
diff --git a/modules/gdscript/tests/scripts/analyzer/features/typed_dictionary_as_default_parameter.out b/modules/gdscript/tests/scripts/analyzer/features/typed_dictionary_as_default_parameter.out
new file mode 100644
index 0000000000..c31561bee3
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/features/typed_dictionary_as_default_parameter.out
@@ -0,0 +1,11 @@
+GDTEST_OK
+{ 0: 1 }
+0
+0
+{ 2: 3 }
+0
+0
+{ 4: 5 }
+2
+2
+ok
diff --git a/modules/gdscript/tests/scripts/analyzer/features/typed_dictionary_inferred_access_isnt_constant.gd b/modules/gdscript/tests/scripts/analyzer/features/typed_dictionary_inferred_access_isnt_constant.gd
new file mode 100644
index 0000000000..0aa3de2c4a
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/features/typed_dictionary_inferred_access_isnt_constant.gd
@@ -0,0 +1,4 @@
+func test():
+ var dict := { 0: 0 }
+ dict[0] = 1
+ print(dict[0])
diff --git a/modules/gdscript/tests/scripts/analyzer/features/typed_dictionary_inferred_access_isnt_constant.out b/modules/gdscript/tests/scripts/analyzer/features/typed_dictionary_inferred_access_isnt_constant.out
new file mode 100644
index 0000000000..a7f1357bb2
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/features/typed_dictionary_inferred_access_isnt_constant.out
@@ -0,0 +1,2 @@
+GDTEST_OK
+1
diff --git a/modules/gdscript/tests/scripts/analyzer/features/typed_dictionary_usage.gd b/modules/gdscript/tests/scripts/analyzer/features/typed_dictionary_usage.gd
new file mode 100644
index 0000000000..9d3fffd1de
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/features/typed_dictionary_usage.gd
@@ -0,0 +1,214 @@
+class A: pass
+class B extends A: pass
+
+enum E { E0 = 391, E1 = 193 }
+
+func floats_identity(floats: Dictionary[float, float]): return floats
+
+class Members:
+ var one: Dictionary[int, int] = { 104: 401 }
+ var two: Dictionary[int, int] = one
+
+ func check_passing() -> bool:
+ Utils.check(str(one) == '{ 104: 401 }')
+ Utils.check(str(two) == '{ 104: 401 }')
+ two[582] = 285
+ Utils.check(str(one) == '{ 104: 401, 582: 285 }')
+ Utils.check(str(two) == '{ 104: 401, 582: 285 }')
+ two = { 486: 684 }
+ Utils.check(str(one) == '{ 104: 401, 582: 285 }')
+ Utils.check(str(two) == '{ 486: 684 }')
+ return true
+
+
+@warning_ignore("unsafe_method_access")
+@warning_ignore("assert_always_true")
+@warning_ignore("return_value_discarded")
+func test():
+ var untyped_basic = { 459: 954 }
+ Utils.check(str(untyped_basic) == '{ 459: 954 }')
+ Utils.check(untyped_basic.get_typed_key_builtin() == TYPE_NIL)
+ Utils.check(untyped_basic.get_typed_value_builtin() == TYPE_NIL)
+
+ var inferred_basic := { 366: 663 }
+ Utils.check(str(inferred_basic) == '{ 366: 663 }')
+ Utils.check(inferred_basic.get_typed_key_builtin() == TYPE_NIL)
+ Utils.check(inferred_basic.get_typed_value_builtin() == TYPE_NIL)
+
+ var typed_basic: Dictionary = { 521: 125 }
+ Utils.check(str(typed_basic) == '{ 521: 125 }')
+ Utils.check(typed_basic.get_typed_key_builtin() == TYPE_NIL)
+ Utils.check(typed_basic.get_typed_value_builtin() == TYPE_NIL)
+
+
+ var empty_floats: Dictionary[float, float] = {}
+ Utils.check(str(empty_floats) == '{ }')
+ Utils.check(empty_floats.get_typed_key_builtin() == TYPE_FLOAT)
+ Utils.check(empty_floats.get_typed_value_builtin() == TYPE_FLOAT)
+
+ untyped_basic = empty_floats
+ Utils.check(untyped_basic.get_typed_key_builtin() == TYPE_FLOAT)
+ Utils.check(untyped_basic.get_typed_value_builtin() == TYPE_FLOAT)
+
+ inferred_basic = empty_floats
+ Utils.check(inferred_basic.get_typed_key_builtin() == TYPE_FLOAT)
+ Utils.check(inferred_basic.get_typed_value_builtin() == TYPE_FLOAT)
+
+ typed_basic = empty_floats
+ Utils.check(typed_basic.get_typed_key_builtin() == TYPE_FLOAT)
+ Utils.check(typed_basic.get_typed_value_builtin() == TYPE_FLOAT)
+
+ empty_floats[705.0] = 507.0
+ untyped_basic[430.0] = 34.0
+ inferred_basic[263.0] = 362.0
+ typed_basic[518.0] = 815.0
+ Utils.check(str(empty_floats) == '{ 705: 507, 430: 34, 263: 362, 518: 815 }')
+ Utils.check(str(untyped_basic) == '{ 705: 507, 430: 34, 263: 362, 518: 815 }')
+ Utils.check(str(inferred_basic) == '{ 705: 507, 430: 34, 263: 362, 518: 815 }')
+ Utils.check(str(typed_basic) == '{ 705: 507, 430: 34, 263: 362, 518: 815 }')
+
+
+ const constant_float := 950.0
+ const constant_int := 170
+ var typed_float := 954.0
+ var filled_floats: Dictionary[float, float] = { constant_float: constant_int, typed_float: empty_floats[430.0] + empty_floats[263.0] }
+ Utils.check(str(filled_floats) == '{ 950: 170, 954: 396 }')
+ Utils.check(filled_floats.get_typed_key_builtin() == TYPE_FLOAT)
+ Utils.check(filled_floats.get_typed_value_builtin() == TYPE_FLOAT)
+
+ var casted_floats := { empty_floats[263.0] * 2: empty_floats[263.0] / 2 } as Dictionary[float, float]
+ Utils.check(str(casted_floats) == '{ 724: 181 }')
+ Utils.check(casted_floats.get_typed_key_builtin() == TYPE_FLOAT)
+ Utils.check(casted_floats.get_typed_value_builtin() == TYPE_FLOAT)
+
+ var returned_floats = (func () -> Dictionary[float, float]: return { 554: 455 }).call()
+ Utils.check(str(returned_floats) == '{ 554: 455 }')
+ Utils.check(returned_floats.get_typed_key_builtin() == TYPE_FLOAT)
+ Utils.check(returned_floats.get_typed_value_builtin() == TYPE_FLOAT)
+
+ var passed_floats = floats_identity({ 663.0 if randf() > 0.5 else 663.0: 366.0 if randf() <= 0.5 else 366.0 })
+ Utils.check(str(passed_floats) == '{ 663: 366 }')
+ Utils.check(passed_floats.get_typed_key_builtin() == TYPE_FLOAT)
+ Utils.check(passed_floats.get_typed_value_builtin() == TYPE_FLOAT)
+
+ var default_floats = (func (floats: Dictionary[float, float] = { 364.0: 463.0 }): return floats).call()
+ Utils.check(str(default_floats) == '{ 364: 463 }')
+ Utils.check(default_floats.get_typed_key_builtin() == TYPE_FLOAT)
+ Utils.check(default_floats.get_typed_value_builtin() == TYPE_FLOAT)
+
+ var typed_int := 556
+ var converted_floats: Dictionary[float, float] = { typed_int: typed_int }
+ converted_floats[498.0] = 894
+ Utils.check(str(converted_floats) == '{ 556: 556, 498: 894 }')
+ Utils.check(converted_floats.get_typed_key_builtin() == TYPE_FLOAT)
+ Utils.check(converted_floats.get_typed_value_builtin() == TYPE_FLOAT)
+
+
+ const constant_basic = { 228: 822 }
+ Utils.check(str(constant_basic) == '{ 228: 822 }')
+ Utils.check(constant_basic.get_typed_key_builtin() == TYPE_NIL)
+ Utils.check(constant_basic.get_typed_value_builtin() == TYPE_NIL)
+
+ const constant_floats: Dictionary[float, float] = { constant_float - constant_basic[228] - constant_int: constant_float + constant_basic[228] + constant_int }
+ Utils.check(str(constant_floats) == '{ -42: 1942 }')
+ Utils.check(constant_floats.get_typed_key_builtin() == TYPE_FLOAT)
+ Utils.check(constant_floats.get_typed_value_builtin() == TYPE_FLOAT)
+
+
+ var source_floats: Dictionary[float, float] = { 999.74: 47.999 }
+ untyped_basic = source_floats
+ var destination_floats: Dictionary[float, float] = untyped_basic
+ destination_floats[999.74] -= 0.999
+ Utils.check(str(source_floats) == '{ 999.74: 47 }')
+ Utils.check(str(untyped_basic) == '{ 999.74: 47 }')
+ Utils.check(str(destination_floats) == '{ 999.74: 47 }')
+ Utils.check(destination_floats.get_typed_key_builtin() == TYPE_FLOAT)
+ Utils.check(destination_floats.get_typed_value_builtin() == TYPE_FLOAT)
+
+
+ var duplicated_floats := empty_floats.duplicate()
+ duplicated_floats.erase(705.0)
+ duplicated_floats.erase(430.0)
+ duplicated_floats.erase(518.0)
+ duplicated_floats[263.0] *= 3
+ Utils.check(str(duplicated_floats) == '{ 263: 1086 }')
+ Utils.check(duplicated_floats.get_typed_key_builtin() == TYPE_FLOAT)
+ Utils.check(duplicated_floats.get_typed_value_builtin() == TYPE_FLOAT)
+
+
+ var b_objects: Dictionary[int, B] = { 0: B.new(), 1: B.new() as A, 2: null }
+ Utils.check(b_objects.size() == 3)
+ Utils.check(b_objects.get_typed_value_builtin() == TYPE_OBJECT)
+ Utils.check(b_objects.get_typed_value_script() == B)
+
+ var a_objects: Dictionary[int, A] = { 0: A.new(), 1: B.new(), 2: null, 3: b_objects[0] }
+ Utils.check(a_objects.size() == 4)
+ Utils.check(a_objects.get_typed_value_builtin() == TYPE_OBJECT)
+ Utils.check(a_objects.get_typed_value_script() == A)
+
+ var a_passed = (func check_a_passing(p_objects: Dictionary[int, A]): return p_objects.size()).call(a_objects)
+ Utils.check(a_passed == 4)
+
+ var b_passed = (func check_b_passing(basic: Dictionary): return basic[0] != null).call(b_objects)
+ Utils.check(b_passed == true)
+
+
+ var empty_strings: Dictionary[String, String] = {}
+ var empty_bools: Dictionary[bool, bool] = {}
+ var empty_basic_one := {}
+ var empty_basic_two := {}
+ Utils.check(empty_strings == empty_bools)
+ Utils.check(empty_basic_one == empty_basic_two)
+ Utils.check(empty_strings.hash() == empty_bools.hash())
+ Utils.check(empty_basic_one.hash() == empty_basic_two.hash())
+
+
+ var assign_source: Dictionary[int, int] = { 527: 725 }
+ var assign_target: Dictionary[int, int] = {}
+ assign_target.assign(assign_source)
+ Utils.check(str(assign_source) == '{ 527: 725 }')
+ Utils.check(str(assign_target) == '{ 527: 725 }')
+ assign_source[657] = 756
+ Utils.check(str(assign_source) == '{ 527: 725, 657: 756 }')
+ Utils.check(str(assign_target) == '{ 527: 725 }')
+
+
+ var defaults_passed = (func check_defaults_passing(one: Dictionary[int, int] = {}, two := one):
+ one[887] = 788
+ two[198] = 891
+ Utils.check(str(one) == '{ 887: 788, 198: 891 }')
+ Utils.check(str(two) == '{ 887: 788, 198: 891 }')
+ two = {130: 31}
+ Utils.check(str(one) == '{ 887: 788, 198: 891 }')
+ Utils.check(str(two) == '{ 130: 31 }')
+ return true
+ ).call()
+ Utils.check(defaults_passed == true)
+
+
+ var members := Members.new()
+ var members_passed := members.check_passing()
+ Utils.check(members_passed == true)
+
+
+ var typed_enums: Dictionary[E, E] = {}
+ typed_enums[E.E0] = E.E1
+ Utils.check(str(typed_enums) == '{ 391: 193 }')
+ Utils.check(typed_enums.get_typed_key_builtin() == TYPE_INT)
+ Utils.check(typed_enums.get_typed_value_builtin() == TYPE_INT)
+
+ const const_enums: Dictionary[E, E] = {}
+ Utils.check(const_enums.get_typed_key_builtin() == TYPE_INT)
+ Utils.check(const_enums.get_typed_key_class_name() == &'')
+ Utils.check(const_enums.get_typed_value_builtin() == TYPE_INT)
+ Utils.check(const_enums.get_typed_value_class_name() == &'')
+
+
+ var a := A.new()
+ var b := B.new()
+ var typed_natives: Dictionary[RefCounted, RefCounted] = { a: b }
+ var typed_scripts = Dictionary(typed_natives, TYPE_OBJECT, "RefCounted", A, TYPE_OBJECT, "RefCounted", B)
+ Utils.check(typed_scripts[a] == b)
+
+
+ print('ok')
diff --git a/modules/gdscript/tests/scripts/analyzer/features/typed_dictionary_usage.out b/modules/gdscript/tests/scripts/analyzer/features/typed_dictionary_usage.out
new file mode 100644
index 0000000000..1b47ed10dc
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/features/typed_dictionary_usage.out
@@ -0,0 +1,2 @@
+GDTEST_OK
+ok
diff --git a/modules/gdscript/tests/scripts/analyzer/features/typed_dictionary_with_custom_class.gd b/modules/gdscript/tests/scripts/analyzer/features/typed_dictionary_with_custom_class.gd
new file mode 100644
index 0000000000..f4a23ade14
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/features/typed_dictionary_with_custom_class.gd
@@ -0,0 +1,9 @@
+class Inner:
+ var prop = "Inner"
+
+var dict: Dictionary[int, Inner] = { 0: Inner.new() }
+
+
+func test():
+ var element: Inner = dict[0]
+ print(element.prop)
diff --git a/modules/gdscript/tests/scripts/analyzer/features/typed_dictionary_with_custom_class.out b/modules/gdscript/tests/scripts/analyzer/features/typed_dictionary_with_custom_class.out
new file mode 100644
index 0000000000..8f250d2632
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/features/typed_dictionary_with_custom_class.out
@@ -0,0 +1,2 @@
+GDTEST_OK
+Inner
diff --git a/modules/gdscript/tests/scripts/analyzer/warnings/confusable_capture_reassignment.out b/modules/gdscript/tests/scripts/analyzer/warnings/confusable_capture_reassignment.out
index 8d953818eb..e6a1cab77d 100644
--- a/modules/gdscript/tests/scripts/analyzer/warnings/confusable_capture_reassignment.out
+++ b/modules/gdscript/tests/scripts/analyzer/warnings/confusable_capture_reassignment.out
@@ -15,5 +15,5 @@ GDTEST_OK
>> Line: 16
>> CONFUSABLE_CAPTURE_REASSIGNMENT
>> Reassigning lambda capture does not modify the outer local variable "array_assign".
-lambda 2 2 12 (2, 0) [2] [2] { "x": 2 }
-outer 2 1 1 (1, 0) [1] [2] { "x": 2 }
+lambda 2 2 12 (2, 0) [2] [2] { &"x": 2 }
+outer 2 1 1 (1, 0) [1] [2] { &"x": 2 }
diff --git a/modules/gdscript/tests/scripts/analyzer/warnings/non_tool_extends_tool.gd b/modules/gdscript/tests/scripts/analyzer/warnings/non_tool_extends_tool.gd
new file mode 100644
index 0000000000..95d497c3f3
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/warnings/non_tool_extends_tool.gd
@@ -0,0 +1,7 @@
+extends "./non_tool_extends_tool.notest.gd"
+
+class InnerClass extends "./non_tool_extends_tool.notest.gd":
+ pass
+
+func test():
+ pass
diff --git a/modules/gdscript/tests/scripts/analyzer/warnings/non_tool_extends_tool.notest.gd b/modules/gdscript/tests/scripts/analyzer/warnings/non_tool_extends_tool.notest.gd
new file mode 100644
index 0000000000..07427846d1
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/warnings/non_tool_extends_tool.notest.gd
@@ -0,0 +1 @@
+@tool
diff --git a/modules/gdscript/tests/scripts/analyzer/warnings/non_tool_extends_tool.out b/modules/gdscript/tests/scripts/analyzer/warnings/non_tool_extends_tool.out
new file mode 100644
index 0000000000..f65caf5222
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/warnings/non_tool_extends_tool.out
@@ -0,0 +1,9 @@
+GDTEST_OK
+>> WARNING
+>> Line: 1
+>> MISSING_TOOL
+>> The base class script has the "@tool" annotation, but this script does not have it.
+>> WARNING
+>> Line: 3
+>> MISSING_TOOL
+>> The base class script has the "@tool" annotation, but this script does not have it.
diff --git a/modules/gdscript/tests/scripts/analyzer/warnings/non_tool_extends_tool_ignored.gd b/modules/gdscript/tests/scripts/analyzer/warnings/non_tool_extends_tool_ignored.gd
new file mode 100644
index 0000000000..a452307d99
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/warnings/non_tool_extends_tool_ignored.gd
@@ -0,0 +1,9 @@
+@warning_ignore("missing_tool")
+extends "./non_tool_extends_tool.notest.gd"
+
+@warning_ignore("missing_tool")
+class InnerClass extends "./non_tool_extends_tool.notest.gd":
+ pass
+
+func test():
+ pass
diff --git a/modules/gdscript/tests/scripts/analyzer/warnings/non_tool_extends_tool_ignored.out b/modules/gdscript/tests/scripts/analyzer/warnings/non_tool_extends_tool_ignored.out
new file mode 100644
index 0000000000..d73c5eb7cd
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/warnings/non_tool_extends_tool_ignored.out
@@ -0,0 +1 @@
+GDTEST_OK
diff --git a/modules/gdscript/tests/scripts/analyzer/warnings/unused_signal.gd b/modules/gdscript/tests/scripts/analyzer/warnings/unused_signal.gd
index d937dfdcfe..37f118dc5d 100644
--- a/modules/gdscript/tests/scripts/analyzer/warnings/unused_signal.gd
+++ b/modules/gdscript/tests/scripts/analyzer/warnings/unused_signal.gd
@@ -1,12 +1,29 @@
-signal s1()
-signal s2()
-signal s3()
+# Doesn't produce the warning:
+signal used_as_first_class_signal()
+signal used_with_signal_constructor()
+signal used_with_signal_emit()
+signal used_with_object_emit_signal()
+signal used_with_object_connect()
+signal used_with_object_disconnect()
+signal used_with_self_prefix()
+
+# Produce the warning:
+signal used_with_dynamic_name()
+signal just_unused()
@warning_ignore("unused_signal")
-signal s4()
+signal unused_but_ignored()
func no_exec():
- s1.emit()
- print(s2)
+ print(used_as_first_class_signal)
+ print(Signal(self, "used_with_signal_constructor"))
+ used_with_signal_emit.emit()
+ print(emit_signal("used_with_object_emit_signal"))
+ print(connect("used_with_object_connect", Callable()))
+ disconnect("used_with_object_disconnect", Callable())
+ print(self.emit_signal("used_with_self_prefix"))
+
+ var dynamic_name := "used_with_dynamic_name"
+ print(emit_signal(dynamic_name))
func test():
pass
diff --git a/modules/gdscript/tests/scripts/analyzer/warnings/unused_signal.out b/modules/gdscript/tests/scripts/analyzer/warnings/unused_signal.out
index ff57017830..39ddf91c76 100644
--- a/modules/gdscript/tests/scripts/analyzer/warnings/unused_signal.out
+++ b/modules/gdscript/tests/scripts/analyzer/warnings/unused_signal.out
@@ -1,5 +1,9 @@
GDTEST_OK
>> WARNING
->> Line: 3
+>> Line: 11
>> UNUSED_SIGNAL
->> The signal "s3" is declared but never explicitly used in the class.
+>> The signal "used_with_dynamic_name" is declared but never explicitly used in the class.
+>> WARNING
+>> Line: 12
+>> UNUSED_SIGNAL
+>> The signal "just_unused" is declared but never explicitly used in the class.
diff --git a/modules/gdscript/tests/scripts/completion/assignment_options/enum_attribute.cfg b/modules/gdscript/tests/scripts/completion/assignment_options/enum_attribute.cfg
new file mode 100644
index 0000000000..e4759ac76b
--- /dev/null
+++ b/modules/gdscript/tests/scripts/completion/assignment_options/enum_attribute.cfg
@@ -0,0 +1,19 @@
+[output]
+include=[
+ {"display": "new"},
+ {"display": "SIZE_EXPAND"},
+ {"display": "SIZE_EXPAND_FILL"},
+ {"display": "SIZE_FILL"},
+ {"display": "SIZE_SHRINK_BEGIN"},
+ {"display": "SIZE_SHRINK_CENTER"},
+ {"display": "SIZE_SHRINK_END"},
+]
+exclude=[
+ {"display": "Control.SIZE_EXPAND"},
+ {"display": "Control.SIZE_EXPAND_FILL"},
+ {"display": "Control.SIZE_FILL"},
+ {"display": "Control.SIZE_SHRINK_BEGIN"},
+ {"display": "Control.SIZE_SHRINK_CENTER"},
+ {"display": "Control.SIZE_SHRINK_END"},
+ {"display": "contro_var"}
+]
diff --git a/modules/gdscript/tests/scripts/completion/assignment_options/enum_attribute.gd b/modules/gdscript/tests/scripts/completion/assignment_options/enum_attribute.gd
new file mode 100644
index 0000000000..4aeafb2e0a
--- /dev/null
+++ b/modules/gdscript/tests/scripts/completion/assignment_options/enum_attribute.gd
@@ -0,0 +1,7 @@
+extends Control
+
+var contro_var
+
+func _ready():
+ size_flags_horizontal = Control.➡
+ pass
diff --git a/modules/gdscript/tests/scripts/completion/assignment_options/enum_attribute_identifier.cfg b/modules/gdscript/tests/scripts/completion/assignment_options/enum_attribute_identifier.cfg
new file mode 100644
index 0000000000..e4759ac76b
--- /dev/null
+++ b/modules/gdscript/tests/scripts/completion/assignment_options/enum_attribute_identifier.cfg
@@ -0,0 +1,19 @@
+[output]
+include=[
+ {"display": "new"},
+ {"display": "SIZE_EXPAND"},
+ {"display": "SIZE_EXPAND_FILL"},
+ {"display": "SIZE_FILL"},
+ {"display": "SIZE_SHRINK_BEGIN"},
+ {"display": "SIZE_SHRINK_CENTER"},
+ {"display": "SIZE_SHRINK_END"},
+]
+exclude=[
+ {"display": "Control.SIZE_EXPAND"},
+ {"display": "Control.SIZE_EXPAND_FILL"},
+ {"display": "Control.SIZE_FILL"},
+ {"display": "Control.SIZE_SHRINK_BEGIN"},
+ {"display": "Control.SIZE_SHRINK_CENTER"},
+ {"display": "Control.SIZE_SHRINK_END"},
+ {"display": "contro_var"}
+]
diff --git a/modules/gdscript/tests/scripts/completion/assignment_options/enum_attribute_identifier.gd b/modules/gdscript/tests/scripts/completion/assignment_options/enum_attribute_identifier.gd
new file mode 100644
index 0000000000..47e9bd5a67
--- /dev/null
+++ b/modules/gdscript/tests/scripts/completion/assignment_options/enum_attribute_identifier.gd
@@ -0,0 +1,7 @@
+extends Control
+
+var contro_var
+
+func _ready():
+ size_flags_horizontal = Control.SIZE➡
+ pass
diff --git a/modules/gdscript/tests/scripts/completion/assignment_options/enum_identifier.cfg b/modules/gdscript/tests/scripts/completion/assignment_options/enum_identifier.cfg
new file mode 100644
index 0000000000..5cc4ec5fd3
--- /dev/null
+++ b/modules/gdscript/tests/scripts/completion/assignment_options/enum_identifier.cfg
@@ -0,0 +1,12 @@
+[output]
+include=[
+ {"display": "Control.SIZE_EXPAND"},
+ {"display": "Control.SIZE_EXPAND_FILL"},
+ {"display": "Control.SIZE_FILL"},
+ {"display": "Control.SIZE_SHRINK_BEGIN"},
+ {"display": "Control.SIZE_SHRINK_CENTER"},
+ {"display": "Control.SIZE_SHRINK_END"},
+]
+exclude=[
+ {"display": "contro_var"}
+]
diff --git a/modules/gdscript/tests/scripts/completion/assignment_options/enum_identifier.gd b/modules/gdscript/tests/scripts/completion/assignment_options/enum_identifier.gd
new file mode 100644
index 0000000000..5c96720bd3
--- /dev/null
+++ b/modules/gdscript/tests/scripts/completion/assignment_options/enum_identifier.gd
@@ -0,0 +1,7 @@
+extends Control
+
+var contro_var
+
+func _ready():
+ size_flags_horizontal = Con➡
+ pass
diff --git a/modules/gdscript/tests/scripts/completion/assignment_options/enum_no_identifier.cfg b/modules/gdscript/tests/scripts/completion/assignment_options/enum_no_identifier.cfg
new file mode 100644
index 0000000000..5cc4ec5fd3
--- /dev/null
+++ b/modules/gdscript/tests/scripts/completion/assignment_options/enum_no_identifier.cfg
@@ -0,0 +1,12 @@
+[output]
+include=[
+ {"display": "Control.SIZE_EXPAND"},
+ {"display": "Control.SIZE_EXPAND_FILL"},
+ {"display": "Control.SIZE_FILL"},
+ {"display": "Control.SIZE_SHRINK_BEGIN"},
+ {"display": "Control.SIZE_SHRINK_CENTER"},
+ {"display": "Control.SIZE_SHRINK_END"},
+]
+exclude=[
+ {"display": "contro_var"}
+]
diff --git a/modules/gdscript/tests/scripts/completion/assignment_options/enum_no_identifier.gd b/modules/gdscript/tests/scripts/completion/assignment_options/enum_no_identifier.gd
new file mode 100644
index 0000000000..d8bf13e51d
--- /dev/null
+++ b/modules/gdscript/tests/scripts/completion/assignment_options/enum_no_identifier.gd
@@ -0,0 +1,7 @@
+extends Control
+
+var contro_var
+
+func _ready():
+ size_flags_horizontal = ➡
+ pass
diff --git a/modules/gdscript/tests/scripts/parser/features/annotations.gd b/modules/gdscript/tests/scripts/parser/features/annotations.gd
index 7a7d6d953e..1e5d3fdcad 100644
--- a/modules/gdscript/tests/scripts/parser/features/annotations.gd
+++ b/modules/gdscript/tests/scripts/parser/features/annotations.gd
@@ -1,7 +1,5 @@
extends Node
-const Utils = preload("../../utils.notest.gd")
-
@export_enum("A", "B", "C") var test_1
@export_enum("A", "B", "C",) var test_2
diff --git a/modules/gdscript/tests/scripts/parser/features/class.gd b/modules/gdscript/tests/scripts/parser/features/class.gd
index af24b32322..482d04a63b 100644
--- a/modules/gdscript/tests/scripts/parser/features/class.gd
+++ b/modules/gdscript/tests/scripts/parser/features/class.gd
@@ -18,8 +18,8 @@ func test():
test_instance.number = 42
var test_sub = TestSub.new()
- assert(test_sub.number == 25) # From Test.
- assert(test_sub.other_string == "bye") # From TestSub.
+ Utils.check(test_sub.number == 25) # From Test.
+ Utils.check(test_sub.other_string == "bye") # From TestSub.
var _test_constructor = TestConstructor.new()
_test_constructor = TestConstructor.new(500)
diff --git a/modules/gdscript/tests/scripts/parser/features/dictionary_lua_style.out b/modules/gdscript/tests/scripts/parser/features/dictionary_lua_style.out
index 553d40d953..a8ef52583d 100644
--- a/modules/gdscript/tests/scripts/parser/features/dictionary_lua_style.out
+++ b/modules/gdscript/tests/scripts/parser/features/dictionary_lua_style.out
@@ -1,2 +1,2 @@
GDTEST_OK
-{ "a": 1, "b": 2, "with spaces": 3, "2": 4 }
+{ &"a": 1, &"b": 2, &"with spaces": 3, &"2": 4 }
diff --git a/modules/gdscript/tests/scripts/parser/features/dictionary_mixed_syntax.out b/modules/gdscript/tests/scripts/parser/features/dictionary_mixed_syntax.out
index cf79845f53..4e404e1d26 100644
--- a/modules/gdscript/tests/scripts/parser/features/dictionary_mixed_syntax.out
+++ b/modules/gdscript/tests/scripts/parser/features/dictionary_mixed_syntax.out
@@ -1,2 +1,2 @@
GDTEST_OK
-{ "hello": { "world": { "is": "beautiful" } } }
+{ "hello": { &"world": { "is": "beautiful" } } }
diff --git a/modules/gdscript/tests/scripts/parser/features/export_arrays.gd b/modules/gdscript/tests/scripts/parser/features/export_arrays.gd
index 0d97135a7b..cfda255905 100644
--- a/modules/gdscript/tests/scripts/parser/features/export_arrays.gd
+++ b/modules/gdscript/tests/scripts/parser/features/export_arrays.gd
@@ -1,5 +1,3 @@
-const Utils = preload("../../utils.notest.gd")
-
@export_dir var test_dir: Array[String]
@export_dir var test_dir_packed: PackedStringArray
@export_file var test_file: Array[String]
diff --git a/modules/gdscript/tests/scripts/parser/features/export_enum.gd b/modules/gdscript/tests/scripts/parser/features/export_enum.gd
index 7f0737f4db..d50f0b2528 100644
--- a/modules/gdscript/tests/scripts/parser/features/export_enum.gd
+++ b/modules/gdscript/tests/scripts/parser/features/export_enum.gd
@@ -1,5 +1,3 @@
-const Utils = preload("../../utils.notest.gd")
-
@export_enum("Red", "Green", "Blue") var test_untyped
@export_enum("Red:10", "Green:20", "Blue:30") var test_with_values
diff --git a/modules/gdscript/tests/scripts/parser/features/export_variable.gd b/modules/gdscript/tests/scripts/parser/features/export_variable.gd
index 8bcb2bcb9a..1e134d0e0e 100644
--- a/modules/gdscript/tests/scripts/parser/features/export_variable.gd
+++ b/modules/gdscript/tests/scripts/parser/features/export_variable.gd
@@ -1,7 +1,6 @@
class_name ExportVariableTest
extends Node
-const Utils = preload("../../utils.notest.gd")
const PreloadedGlobalClass = preload("./export_variable_global.notest.gd")
const PreloadedUnnamedClass = preload("./export_variable_unnamed.notest.gd")
diff --git a/modules/gdscript/tests/scripts/parser/features/good_continue_in_lambda.gd b/modules/gdscript/tests/scripts/parser/features/good_continue_in_lambda.gd
index 2fa45c1d7d..0ec118b6b7 100644
--- a/modules/gdscript/tests/scripts/parser/features/good_continue_in_lambda.gd
+++ b/modules/gdscript/tests/scripts/parser/features/good_continue_in_lambda.gd
@@ -9,5 +9,5 @@ func test():
j_string += str(j)
return j_string
i_string += lambda.call()
- assert(i_string == '0202')
+ Utils.check(i_string == '0202')
print('ok')
diff --git a/modules/gdscript/tests/scripts/parser/features/truthiness.gd b/modules/gdscript/tests/scripts/parser/features/truthiness.gd
index 9c67a152f5..736cda7f74 100644
--- a/modules/gdscript/tests/scripts/parser/features/truthiness.gd
+++ b/modules/gdscript/tests/scripts/parser/features/truthiness.gd
@@ -1,30 +1,32 @@
func test():
- # The assertions below should all evaluate to `true` for this test to pass.
- assert(true)
- assert(not false)
- assert(500)
- assert(not 0)
- assert(500.5)
- assert(not 0.0)
- assert("non-empty string")
- assert(["non-empty array"])
- assert({"non-empty": "dictionary"})
- assert(Vector2(1, 0))
- assert(Vector2i(-1, -1))
- assert(Vector3(0, 0, 0.0001))
- assert(Vector3i(0, 0, 10000))
+ # The checks below should all evaluate to `true` for this test to pass.
+ Utils.check(true)
+ Utils.check(not false)
+ Utils.check(500)
+ Utils.check(not 0)
+ Utils.check(500.5)
+ Utils.check(not 0.0)
+ Utils.check("non-empty string")
+ Utils.check(["non-empty array"])
+ Utils.check({"non-empty": "dictionary"})
+ Utils.check(Vector2(1, 0))
+ Utils.check(Vector2i(-1, -1))
+ Utils.check(Vector3(0, 0, 0.0001))
+ Utils.check(Vector3i(0, 0, 10000))
# Zero position is `true` only if the Rect2's size is non-zero.
- assert(Rect2(0, 0, 0, 1))
+ Utils.check(Rect2(0, 0, 0, 1))
# Zero size is `true` only if the position is non-zero.
- assert(Rect2(1, 1, 0, 0))
+ Utils.check(Rect2(1, 1, 0, 0))
# Zero position is `true` only if the Rect2's size is non-zero.
- assert(Rect2i(0, 0, 0, 1))
+ Utils.check(Rect2i(0, 0, 0, 1))
# Zero size is `true` only if the position is non-zero.
- assert(Rect2i(1, 1, 0, 0))
+ Utils.check(Rect2i(1, 1, 0, 0))
# A fully black color is only truthy if its alpha component is not equal to `1`.
- assert(Color(0, 0, 0, 0.5))
+ Utils.check(Color(0, 0, 0, 0.5))
+
+ print("ok")
diff --git a/modules/gdscript/tests/scripts/parser/features/truthiness.out b/modules/gdscript/tests/scripts/parser/features/truthiness.out
index 705524857b..1b47ed10dc 100644
--- a/modules/gdscript/tests/scripts/parser/features/truthiness.out
+++ b/modules/gdscript/tests/scripts/parser/features/truthiness.out
@@ -1,65 +1,2 @@
GDTEST_OK
->> WARNING
->> Line: 3
->> ASSERT_ALWAYS_TRUE
->> Assert statement is redundant because the expression is always true.
->> WARNING
->> Line: 4
->> ASSERT_ALWAYS_TRUE
->> Assert statement is redundant because the expression is always true.
->> WARNING
->> Line: 5
->> ASSERT_ALWAYS_TRUE
->> Assert statement is redundant because the expression is always true.
->> WARNING
->> Line: 6
->> ASSERT_ALWAYS_TRUE
->> Assert statement is redundant because the expression is always true.
->> WARNING
->> Line: 7
->> ASSERT_ALWAYS_TRUE
->> Assert statement is redundant because the expression is always true.
->> WARNING
->> Line: 8
->> ASSERT_ALWAYS_TRUE
->> Assert statement is redundant because the expression is always true.
->> WARNING
->> Line: 9
->> ASSERT_ALWAYS_TRUE
->> Assert statement is redundant because the expression is always true.
->> WARNING
->> Line: 12
->> ASSERT_ALWAYS_TRUE
->> Assert statement is redundant because the expression is always true.
->> WARNING
->> Line: 13
->> ASSERT_ALWAYS_TRUE
->> Assert statement is redundant because the expression is always true.
->> WARNING
->> Line: 14
->> ASSERT_ALWAYS_TRUE
->> Assert statement is redundant because the expression is always true.
->> WARNING
->> Line: 15
->> ASSERT_ALWAYS_TRUE
->> Assert statement is redundant because the expression is always true.
->> WARNING
->> Line: 18
->> ASSERT_ALWAYS_TRUE
->> Assert statement is redundant because the expression is always true.
->> WARNING
->> Line: 21
->> ASSERT_ALWAYS_TRUE
->> Assert statement is redundant because the expression is always true.
->> WARNING
->> Line: 24
->> ASSERT_ALWAYS_TRUE
->> Assert statement is redundant because the expression is always true.
->> WARNING
->> Line: 27
->> ASSERT_ALWAYS_TRUE
->> Assert statement is redundant because the expression is always true.
->> WARNING
->> Line: 30
->> ASSERT_ALWAYS_TRUE
->> Assert statement is redundant because the expression is always true.
+ok
diff --git a/modules/gdscript/tests/scripts/parser/features/typed_dictionaries.gd b/modules/gdscript/tests/scripts/parser/features/typed_dictionaries.gd
new file mode 100644
index 0000000000..57e6489484
--- /dev/null
+++ b/modules/gdscript/tests/scripts/parser/features/typed_dictionaries.gd
@@ -0,0 +1,5 @@
+func test():
+ var my_dictionary: Dictionary[int, String] = { 1: "one", 2: "two", 3: "three" }
+ var inferred_dictionary := { 1: "one", 2: "two", 3: "three" } # This is Dictionary[int, String].
+ print(my_dictionary)
+ print(inferred_dictionary)
diff --git a/modules/gdscript/tests/scripts/parser/features/typed_dictionaries.out b/modules/gdscript/tests/scripts/parser/features/typed_dictionaries.out
new file mode 100644
index 0000000000..6021c338ee
--- /dev/null
+++ b/modules/gdscript/tests/scripts/parser/features/typed_dictionaries.out
@@ -0,0 +1,3 @@
+GDTEST_OK
+{ 1: "one", 2: "two", 3: "three" }
+{ 1: "one", 2: "two", 3: "three" }
diff --git a/modules/gdscript/tests/scripts/runtime/errors/typed_dictionary_assign_basic_to_typed.gd b/modules/gdscript/tests/scripts/runtime/errors/typed_dictionary_assign_basic_to_typed.gd
new file mode 100644
index 0000000000..75004742a2
--- /dev/null
+++ b/modules/gdscript/tests/scripts/runtime/errors/typed_dictionary_assign_basic_to_typed.gd
@@ -0,0 +1,4 @@
+func test():
+ var basic := { 1: 1 }
+ var typed: Dictionary[int, int] = basic
+ print('not ok')
diff --git a/modules/gdscript/tests/scripts/runtime/errors/typed_dictionary_assign_basic_to_typed.out b/modules/gdscript/tests/scripts/runtime/errors/typed_dictionary_assign_basic_to_typed.out
new file mode 100644
index 0000000000..cadb17f570
--- /dev/null
+++ b/modules/gdscript/tests/scripts/runtime/errors/typed_dictionary_assign_basic_to_typed.out
@@ -0,0 +1,6 @@
+GDTEST_RUNTIME_ERROR
+>> SCRIPT ERROR
+>> on function: test()
+>> runtime/errors/typed_dictionary_assign_basic_to_typed.gd
+>> 3
+>> Trying to assign a dictionary of type "Dictionary" to a variable of type "Dictionary[int, int]".
diff --git a/modules/gdscript/tests/scripts/runtime/errors/typed_dictionary_assign_differently_typed.gd b/modules/gdscript/tests/scripts/runtime/errors/typed_dictionary_assign_differently_typed.gd
new file mode 100644
index 0000000000..e5ab4a1a85
--- /dev/null
+++ b/modules/gdscript/tests/scripts/runtime/errors/typed_dictionary_assign_differently_typed.gd
@@ -0,0 +1,4 @@
+func test():
+ var differently: Variant = { 1.0: 0.0 } as Dictionary[float, float]
+ var typed: Dictionary[int, int] = differently
+ print('not ok')
diff --git a/modules/gdscript/tests/scripts/runtime/errors/typed_dictionary_assign_differently_typed.out b/modules/gdscript/tests/scripts/runtime/errors/typed_dictionary_assign_differently_typed.out
new file mode 100644
index 0000000000..fe1e5d1285
--- /dev/null
+++ b/modules/gdscript/tests/scripts/runtime/errors/typed_dictionary_assign_differently_typed.out
@@ -0,0 +1,6 @@
+GDTEST_RUNTIME_ERROR
+>> SCRIPT ERROR
+>> on function: test()
+>> runtime/errors/typed_dictionary_assign_differently_typed.gd
+>> 3
+>> Trying to assign a dictionary of type "Dictionary[float, float]" to a variable of type "Dictionary[int, int]".
diff --git a/modules/gdscript/tests/scripts/runtime/errors/typed_dictionary_assign_wrong_to_typed.gd b/modules/gdscript/tests/scripts/runtime/errors/typed_dictionary_assign_wrong_to_typed.gd
new file mode 100644
index 0000000000..6cc0e57255
--- /dev/null
+++ b/modules/gdscript/tests/scripts/runtime/errors/typed_dictionary_assign_wrong_to_typed.gd
@@ -0,0 +1,7 @@
+class Foo: pass
+class Bar extends Foo: pass
+class Baz extends Foo: pass
+
+func test():
+ var typed: Dictionary[Bar, Bar] = { Baz.new() as Foo: Baz.new() as Foo }
+ print('not ok')
diff --git a/modules/gdscript/tests/scripts/runtime/errors/typed_dictionary_assign_wrong_to_typed.out b/modules/gdscript/tests/scripts/runtime/errors/typed_dictionary_assign_wrong_to_typed.out
new file mode 100644
index 0000000000..18a4c360e2
--- /dev/null
+++ b/modules/gdscript/tests/scripts/runtime/errors/typed_dictionary_assign_wrong_to_typed.out
@@ -0,0 +1,5 @@
+GDTEST_RUNTIME_ERROR
+>> ERROR
+>> Method/function failed.
+>> Unable to convert key from "Object" to "Object".
+not ok
diff --git a/modules/gdscript/tests/scripts/runtime/errors/typed_dictionary_pass_basic_to_typed.gd b/modules/gdscript/tests/scripts/runtime/errors/typed_dictionary_pass_basic_to_typed.gd
new file mode 100644
index 0000000000..8f7d732584
--- /dev/null
+++ b/modules/gdscript/tests/scripts/runtime/errors/typed_dictionary_pass_basic_to_typed.gd
@@ -0,0 +1,7 @@
+func expect_typed(typed: Dictionary[int, int]):
+ print(typed.size())
+
+func test():
+ var basic := { 1: 1 }
+ expect_typed(basic)
+ print('not ok')
diff --git a/modules/gdscript/tests/scripts/runtime/errors/typed_dictionary_pass_basic_to_typed.out b/modules/gdscript/tests/scripts/runtime/errors/typed_dictionary_pass_basic_to_typed.out
new file mode 100644
index 0000000000..fb45461701
--- /dev/null
+++ b/modules/gdscript/tests/scripts/runtime/errors/typed_dictionary_pass_basic_to_typed.out
@@ -0,0 +1,6 @@
+GDTEST_RUNTIME_ERROR
+>> SCRIPT ERROR
+>> on function: test()
+>> runtime/errors/typed_dictionary_pass_basic_to_typed.gd
+>> 6
+>> Invalid type in function 'expect_typed' in base 'RefCounted (typed_dictionary_pass_basic_to_typed.gd)'. The dictionary of argument 1 (Dictionary) does not have the same element type as the expected typed dictionary argument.
diff --git a/modules/gdscript/tests/scripts/runtime/errors/typed_dictionary_pass_differently_to_typed.gd b/modules/gdscript/tests/scripts/runtime/errors/typed_dictionary_pass_differently_to_typed.gd
new file mode 100644
index 0000000000..978a9fdfee
--- /dev/null
+++ b/modules/gdscript/tests/scripts/runtime/errors/typed_dictionary_pass_differently_to_typed.gd
@@ -0,0 +1,7 @@
+func expect_typed(typed: Dictionary[int, int]):
+ print(typed.size())
+
+func test():
+ var differently: Variant = { 1.0: 0.0 } as Dictionary[float, float]
+ expect_typed(differently)
+ print('not ok')
diff --git a/modules/gdscript/tests/scripts/runtime/errors/typed_dictionary_pass_differently_to_typed.out b/modules/gdscript/tests/scripts/runtime/errors/typed_dictionary_pass_differently_to_typed.out
new file mode 100644
index 0000000000..4036a1bf01
--- /dev/null
+++ b/modules/gdscript/tests/scripts/runtime/errors/typed_dictionary_pass_differently_to_typed.out
@@ -0,0 +1,6 @@
+GDTEST_RUNTIME_ERROR
+>> SCRIPT ERROR
+>> on function: test()
+>> runtime/errors/typed_dictionary_pass_differently_to_typed.gd
+>> 6
+>> Invalid type in function 'expect_typed' in base 'RefCounted (typed_dictionary_pass_differently_to_typed.gd)'. The dictionary of argument 1 (Dictionary[float, float]) does not have the same element type as the expected typed dictionary argument.
diff --git a/modules/gdscript/tests/scripts/runtime/features/array_string_stringname_equivalent.gd b/modules/gdscript/tests/scripts/runtime/features/array_string_stringname_equivalent.gd
index bd38259cec..6eec37d64d 100644
--- a/modules/gdscript/tests/scripts/runtime/features/array_string_stringname_equivalent.gd
+++ b/modules/gdscript/tests/scripts/runtime/features/array_string_stringname_equivalent.gd
@@ -25,7 +25,7 @@ func test():
print("String in Array[StringName]: ", "abc" in stringname_array)
var packed_string_array: PackedStringArray = []
- assert(!packed_string_array.push_back("abc"))
+ Utils.check(!packed_string_array.push_back("abc"))
print("StringName in PackedStringArray: ", &"abc" in packed_string_array)
string_array.push_back("abc")
diff --git a/modules/gdscript/tests/scripts/runtime/features/constants_are_read_only.gd b/modules/gdscript/tests/scripts/runtime/features/constants_are_read_only.gd
index d1746979be..6aa863c05f 100644
--- a/modules/gdscript/tests/scripts/runtime/features/constants_are_read_only.gd
+++ b/modules/gdscript/tests/scripts/runtime/features/constants_are_read_only.gd
@@ -1,10 +1,9 @@
const array: Array = [0]
const dictionary := {1: 2}
-@warning_ignore("assert_always_true")
func test():
- assert(array.is_read_only() == true)
- assert(str(array) == '[0]')
- assert(dictionary.is_read_only() == true)
- assert(str(dictionary) == '{ 1: 2 }')
+ Utils.check(array.is_read_only() == true)
+ Utils.check(str(array) == '[0]')
+ Utils.check(dictionary.is_read_only() == true)
+ Utils.check(str(dictionary) == '{ 1: 2 }')
print('ok')
diff --git a/modules/gdscript/tests/scripts/runtime/features/conversions_from_native_members.gd b/modules/gdscript/tests/scripts/runtime/features/conversions_from_native_members.gd
index a778fb1a94..0f2526667d 100644
--- a/modules/gdscript/tests/scripts/runtime/features/conversions_from_native_members.gd
+++ b/modules/gdscript/tests/scripts/runtime/features/conversions_from_native_members.gd
@@ -2,8 +2,8 @@ class Foo extends Node:
func _init():
name = 'f'
var string: String = name
- assert(typeof(string) == TYPE_STRING)
- assert(string == 'f')
+ Utils.check(typeof(string) == TYPE_STRING)
+ Utils.check(string == 'f')
print('ok')
func test():
diff --git a/modules/gdscript/tests/scripts/runtime/features/default_set_beforehand.gd b/modules/gdscript/tests/scripts/runtime/features/default_set_beforehand.gd
index 0851d939dc..9e67e75140 100644
--- a/modules/gdscript/tests/scripts/runtime/features/default_set_beforehand.gd
+++ b/modules/gdscript/tests/scripts/runtime/features/default_set_beforehand.gd
@@ -6,15 +6,15 @@ extends Node
@onready var later_untyped = [1]
func test():
- assert(typeof(later_inferred) == TYPE_ARRAY)
- assert(later_inferred.size() == 0)
+ Utils.check(typeof(later_inferred) == TYPE_ARRAY)
+ Utils.check(later_inferred.size() == 0)
- assert(typeof(later_static) == TYPE_ARRAY)
- assert(later_static.size() == 0)
+ Utils.check(typeof(later_static) == TYPE_ARRAY)
+ Utils.check(later_static.size() == 0)
- assert(typeof(later_static_with_init) == TYPE_ARRAY)
- assert(later_static_with_init.size() == 0)
+ Utils.check(typeof(later_static_with_init) == TYPE_ARRAY)
+ Utils.check(later_static_with_init.size() == 0)
- assert(typeof(later_untyped) == TYPE_NIL)
+ Utils.check(typeof(later_untyped) == TYPE_NIL)
print("ok")
diff --git a/modules/gdscript/tests/scripts/runtime/features/dictionary_string_stringname_equivalent.gd b/modules/gdscript/tests/scripts/runtime/features/dictionary_string_stringname_equivalent.gd
index 94bac1974f..de5eaabb79 100644
--- a/modules/gdscript/tests/scripts/runtime/features/dictionary_string_stringname_equivalent.gd
+++ b/modules/gdscript/tests/scripts/runtime/features/dictionary_string_stringname_equivalent.gd
@@ -7,11 +7,11 @@ func test():
stringname_dict[&"abc"] = 24
print("String key is TYPE_STRING: ", typeof(string_dict.keys()[0]) == TYPE_STRING)
- print("StringName key is TYPE_STRING: ", typeof(stringname_dict.keys()[0]) == TYPE_STRING)
+ print("StringName key is TYPE_STRING_NAME: ", typeof(stringname_dict.keys()[0]) == TYPE_STRING_NAME)
print("StringName gets String: ", string_dict.get(&"abc"))
print("String gets StringName: ", stringname_dict.get("abc"))
stringname_dict[&"abc"] = 42
- # They compare equal because StringName keys are converted to String.
+ # They compare equal because StringName keys are considered equivalent to String keys.
print("String Dictionary == StringName Dictionary: ", string_dict == stringname_dict)
diff --git a/modules/gdscript/tests/scripts/runtime/features/dictionary_string_stringname_equivalent.out b/modules/gdscript/tests/scripts/runtime/features/dictionary_string_stringname_equivalent.out
index ab5b89d55c..a1461912bf 100644
--- a/modules/gdscript/tests/scripts/runtime/features/dictionary_string_stringname_equivalent.out
+++ b/modules/gdscript/tests/scripts/runtime/features/dictionary_string_stringname_equivalent.out
@@ -1,6 +1,6 @@
GDTEST_OK
String key is TYPE_STRING: true
-StringName key is TYPE_STRING: true
+StringName key is TYPE_STRING_NAME: true
StringName gets String: 42
String gets StringName: 24
String Dictionary == StringName Dictionary: true
diff --git a/modules/gdscript/tests/scripts/runtime/features/export_group_no_name_conflict_with_properties.gd b/modules/gdscript/tests/scripts/runtime/features/export_group_no_name_conflict_with_properties.gd
index 0133d7fcfc..90df98e05b 100644
--- a/modules/gdscript/tests/scripts/runtime/features/export_group_no_name_conflict_with_properties.gd
+++ b/modules/gdscript/tests/scripts/runtime/features/export_group_no_name_conflict_with_properties.gd
@@ -1,5 +1,3 @@
-const Utils = preload("../../utils.notest.gd")
-
# GH-73843
@export_group("Resource")
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
index bc899a3a6f..393500bd9b 100644
--- 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
@@ -28,13 +28,18 @@ func test():
prints(var_to_str(e), var_to_str(elem))
print("Test String-keys dictionary.")
- var d1 := {a = 1, b = 2, c = 3}
+ 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}
+ 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())
+
+ print("Test implicitly typed dictionary literal.")
+ for k: StringName in { x = 123, y = 456, z = 789 }:
+ var key := k
+ prints(var_to_str(k), var_to_str(key))
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
index eeebdc4be5..89cc1b76fe 100644
--- 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
@@ -27,3 +27,7 @@ Test RefCounted-keys dictionary.
RefCounted RefCounted
Resource Resource
ConfigFile ConfigFile
+Test implicitly typed dictionary literal.
+&"x" &"x"
+&"y" &"y"
+&"z" &"z"
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
index 4cb51f8512..48a9349bf8 100644
--- a/modules/gdscript/tests/scripts/runtime/features/match_with_pattern_guards.gd
+++ b/modules/gdscript/tests/scripts/runtime/features/match_with_pattern_guards.gd
@@ -62,7 +62,7 @@ func test():
0 when side_effect():
print("will run the side effect call, but not this")
_:
- assert(global == 1)
+ Utils.check(global == 1)
print("side effect only ran once")
func side_effect():
diff --git a/modules/gdscript/tests/scripts/runtime/features/member_info.gd b/modules/gdscript/tests/scripts/runtime/features/member_info.gd
index 42b29eee43..4ce53aa395 100644
--- a/modules/gdscript/tests/scripts/runtime/features/member_info.gd
+++ b/modules/gdscript/tests/scripts/runtime/features/member_info.gd
@@ -5,8 +5,6 @@ class MyClass:
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
@@ -33,6 +31,16 @@ 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_dictionary: Dictionary
+var test_var_hard_dictionary_int_variant: Dictionary[int, Variant]
+var test_var_hard_dictionary_variant_int: Dictionary[Variant, int]
+var test_var_hard_dictionary_int_int: Dictionary[int, int]
+var test_var_hard_dictionary_variant_type: Dictionary[Variant.Type, Variant.Type]
+var test_var_hard_dictionary_node_process_mode: Dictionary[Node.ProcessMode, Node.ProcessMode]
+var test_var_hard_dictionary_my_enum: Dictionary[MyEnum, MyEnum]
+var test_var_hard_dictionary_resource: Dictionary[Resource, Resource]
+var test_var_hard_dictionary_this: Dictionary[TestMemberInfo, TestMemberInfo]
+var test_var_hard_dictionary_my_class: Dictionary[MyClass, MyClass]
var test_var_hard_resource: Resource
var test_var_hard_this: TestMemberInfo
var test_var_hard_my_class: MyClass
@@ -45,17 +53,17 @@ 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_1(_a: int, _b: Array[int], _c: Dictionary[int, int], _d: int = 1, _e = 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])
+signal test_signal_3(a: int, b: Array[int], c: Dictionary[int, int])
+signal test_signal_4(a: Variant.Type, b: Array[Variant.Type], c: Dictionary[Variant.Type, Variant.Type])
+signal test_signal_5(a: MyEnum, b: Array[MyEnum], c: Dictionary[MyEnum, MyEnum])
+signal test_signal_6(a: Resource, b: Array[Resource], c: Dictionary[Resource, Resource])
+signal test_signal_7(a: TestMemberInfo, b: Array[TestMemberInfo], c: Dictionary[TestMemberInfo, TestMemberInfo])
+signal test_signal_8(a: MyClass, b: Array[MyClass], c: Dictionary[MyClass, MyClass])
func no_exec():
test_signal_1.emit()
diff --git a/modules/gdscript/tests/scripts/runtime/features/member_info.out b/modules/gdscript/tests/scripts/runtime/features/member_info.out
index 7c826ac05a..2baf451aa5 100644
--- a/modules/gdscript/tests/scripts/runtime/features/member_info.out
+++ b/modules/gdscript/tests/scripts/runtime/features/member_info.out
@@ -23,6 +23,16 @@ 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_dictionary: Dictionary
+var test_var_hard_dictionary_int_variant: Dictionary[int, Variant]
+var test_var_hard_dictionary_variant_int: Dictionary[Variant, int]
+var test_var_hard_dictionary_int_int: Dictionary[int, int]
+var test_var_hard_dictionary_variant_type: Dictionary[Variant.Type, Variant.Type]
+var test_var_hard_dictionary_node_process_mode: Dictionary[Node.ProcessMode, Node.ProcessMode]
+var test_var_hard_dictionary_my_enum: Dictionary[TestMemberInfo.MyEnum, TestMemberInfo.MyEnum]
+var test_var_hard_dictionary_resource: Dictionary[Resource, Resource]
+var test_var_hard_dictionary_this: Dictionary[TestMemberInfo, TestMemberInfo]
+var test_var_hard_dictionary_my_class: Dictionary[RefCounted, RefCounted]
var test_var_hard_resource: Resource
var test_var_hard_this: TestMemberInfo
var test_var_hard_my_class: RefCounted
@@ -33,13 +43,13 @@ 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_1(_a: int, _b: Array[int], _c: Dictionary[int, int], _d: int = 1, _e: 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])
+signal test_signal_3(a: int, b: Array[int], c: Dictionary[int, int])
+signal test_signal_4(a: Variant.Type, b: Array[Variant.Type], c: Dictionary[Variant.Type, Variant.Type])
+signal test_signal_5(a: TestMemberInfo.MyEnum, b: Array[TestMemberInfo.MyEnum], c: Dictionary[TestMemberInfo.MyEnum, TestMemberInfo.MyEnum])
+signal test_signal_6(a: Resource, b: Array[Resource], c: Dictionary[Resource, Resource])
+signal test_signal_7(a: TestMemberInfo, b: Array[TestMemberInfo], c: Dictionary[TestMemberInfo, TestMemberInfo])
+signal test_signal_8(a: RefCounted, b: Array[RefCounted], c: Dictionary[RefCounted, 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
index ee5c1e1267..4ddbeaec0b 100644
--- a/modules/gdscript/tests/scripts/runtime/features/member_info_inheritance.gd
+++ b/modules/gdscript/tests/scripts/runtime/features/member_info_inheritance.gd
@@ -1,7 +1,5 @@
# GH-82169
-const Utils = preload("../../utils.notest.gd")
-
class A:
static var test_static_var_a1
static var test_static_var_a2
diff --git a/modules/gdscript/tests/scripts/runtime/features/metatypes.gd b/modules/gdscript/tests/scripts/runtime/features/metatypes.gd
index fd23ea0db5..d6847768e6 100644
--- a/modules/gdscript/tests/scripts/runtime/features/metatypes.gd
+++ b/modules/gdscript/tests/scripts/runtime/features/metatypes.gd
@@ -3,7 +3,6 @@ class MyClass:
enum MyEnum {A, B, C}
-const Utils = preload("../../utils.notest.gd")
const Other = preload("./metatypes.notest.gd")
var test_native := JSON
diff --git a/modules/gdscript/tests/scripts/runtime/features/set_does_not_leak.gd b/modules/gdscript/tests/scripts/runtime/features/set_does_not_leak.gd
index e1aba83507..dee36d3ae0 100644
--- a/modules/gdscript/tests/scripts/runtime/features/set_does_not_leak.gd
+++ b/modules/gdscript/tests/scripts/runtime/features/set_does_not_leak.gd
@@ -6,6 +6,6 @@ class MyObj:
func test():
var obj_1 = MyObj.new()
var obj_2 = MyObj.new()
- assert(obj_2.get_reference_count() == 1)
+ Utils.check(obj_2.get_reference_count() == 1)
obj_1.set(&"obj", obj_2)
- assert(obj_2.get_reference_count() == 1)
+ Utils.check(obj_2.get_reference_count() == 1)
diff --git a/modules/gdscript/tests/scripts/runtime/features/single_underscore_node_name.gd b/modules/gdscript/tests/scripts/runtime/features/single_underscore_node_name.gd
index b07c40b6da..11a670a7fb 100644
--- a/modules/gdscript/tests/scripts/runtime/features/single_underscore_node_name.gd
+++ b/modules/gdscript/tests/scripts/runtime/features/single_underscore_node_name.gd
@@ -12,4 +12,4 @@ func test() -> void:
node1.add_child(node2)
add_child(node3)
- assert(get_node("_/Child") == $_/Child)
+ Utils.check(get_node("_/Child") == $_/Child)
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 691b611574..d72662736e 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
@@ -14,33 +14,33 @@ func test():
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`.
+ Utils.check(not f) # Test unary operator reading from `nil`.
func test_utility(v, f):
abs(v) # Utility function.
- assert(not f) # Test unary operator reading from `nil`.
+ Utils.check(not f) # Test unary operator reading from `nil`.
func test_builtin_call(v, f):
@warning_ignore("unsafe_method_access")
v.angle() # Built-in method call.
- assert(not f) # Test unary operator reading from `nil`.
+ Utils.check(not f) # Test unary operator reading from `nil`.
func test_builtin_call_validated(v: Vector2, f):
@warning_ignore("return_value_discarded")
v.abs() # Built-in method call validated.
- assert(not f) # Test unary operator reading from `nil`.
+ Utils.check(not f) # Test unary operator reading from `nil`.
func test_object_call(v, f):
@warning_ignore("unsafe_method_access")
v.get_reference_count() # Native type method call.
- assert(not f) # Test unary operator reading from `nil`.
+ Utils.check(not f) # Test unary operator reading from `nil`.
func test_object_call_method_bind(v: Resource, f):
@warning_ignore("return_value_discarded")
v.duplicate() # Native type method call with MethodBind.
- assert(not f) # Test unary operator reading from `nil`.
+ Utils.check(not f) # Test unary operator reading from `nil`.
func test_object_call_method_bind_validated(v: RefCounted, f):
@warning_ignore("return_value_discarded")
v.get_reference_count() # Native type method call with validated MethodBind.
- assert(not f) # Test unary operator reading from `nil`.
+ Utils.check(not f) # Test unary operator reading from `nil`.
diff --git a/modules/gdscript/tests/scripts/runtime/features/typed_array_init_with_untyped_in_literal.gd b/modules/gdscript/tests/scripts/runtime/features/typed_array_init_with_untyped_in_literal.gd
index ec444b4ffa..859bfd7987 100644
--- a/modules/gdscript/tests/scripts/runtime/features/typed_array_init_with_untyped_in_literal.gd
+++ b/modules/gdscript/tests/scripts/runtime/features/typed_array_init_with_untyped_in_literal.gd
@@ -1,6 +1,6 @@
func test():
var untyped: Variant = 32
var typed: Array[int] = [untyped]
- assert(typed.get_typed_builtin() == TYPE_INT)
- assert(str(typed) == '[32]')
+ Utils.check(typed.get_typed_builtin() == TYPE_INT)
+ Utils.check(str(typed) == '[32]')
print('ok')
diff --git a/modules/gdscript/tests/scripts/runtime/features/typed_dictionary_implicit_cast_param.gd b/modules/gdscript/tests/scripts/runtime/features/typed_dictionary_implicit_cast_param.gd
new file mode 100644
index 0000000000..0371ee5630
--- /dev/null
+++ b/modules/gdscript/tests/scripts/runtime/features/typed_dictionary_implicit_cast_param.gd
@@ -0,0 +1,6 @@
+func test_param(dictionary: Dictionary[int, String]) -> void:
+ print(dictionary.get_typed_key_builtin() == TYPE_INT)
+ print(dictionary.get_typed_value_builtin() == TYPE_STRING)
+
+func test() -> void:
+ test_param({ 123: "some_string" })
diff --git a/modules/gdscript/tests/scripts/runtime/features/typed_dictionary_implicit_cast_param.out b/modules/gdscript/tests/scripts/runtime/features/typed_dictionary_implicit_cast_param.out
new file mode 100644
index 0000000000..9d111a8322
--- /dev/null
+++ b/modules/gdscript/tests/scripts/runtime/features/typed_dictionary_implicit_cast_param.out
@@ -0,0 +1,3 @@
+GDTEST_OK
+true
+true
diff --git a/modules/gdscript/tests/scripts/runtime/features/typed_dictionary_init_with_untyped_in_literal.gd b/modules/gdscript/tests/scripts/runtime/features/typed_dictionary_init_with_untyped_in_literal.gd
new file mode 100644
index 0000000000..ee51440d80
--- /dev/null
+++ b/modules/gdscript/tests/scripts/runtime/features/typed_dictionary_init_with_untyped_in_literal.gd
@@ -0,0 +1,7 @@
+func test():
+ var untyped: Variant = 32
+ var typed: Dictionary[int, int] = { untyped: untyped }
+ Utils.check(typed.get_typed_key_builtin() == TYPE_INT)
+ Utils.check(typed.get_typed_value_builtin() == TYPE_INT)
+ Utils.check(str(typed) == '{ 32: 32 }')
+ print('ok')
diff --git a/modules/gdscript/tests/scripts/runtime/features/typed_dictionary_init_with_untyped_in_literal.out b/modules/gdscript/tests/scripts/runtime/features/typed_dictionary_init_with_untyped_in_literal.out
new file mode 100644
index 0000000000..1b47ed10dc
--- /dev/null
+++ b/modules/gdscript/tests/scripts/runtime/features/typed_dictionary_init_with_untyped_in_literal.out
@@ -0,0 +1,2 @@
+GDTEST_OK
+ok
diff --git a/modules/gdscript/tests/scripts/utils.notest.gd b/modules/gdscript/tests/scripts/utils.notest.gd
index 7fdd6556ec..1e2788f765 100644
--- a/modules/gdscript/tests/scripts/utils.notest.gd
+++ b/modules/gdscript/tests/scripts/utils.notest.gd
@@ -1,3 +1,13 @@
+class_name Utils
+
+
+# `assert()` is not evaluated in non-debug builds. Do not use `assert()`
+# for anything other than testing the `assert()` itself.
+static func check(condition: Variant) -> void:
+ if not condition:
+ printerr("Check failed.")
+
+
static func get_type(property: Dictionary, is_return: bool = false) -> String:
match property.type:
TYPE_NIL:
@@ -14,6 +24,11 @@ static func get_type(property: Dictionary, is_return: bool = false) -> String:
if str(property.hint_string).is_empty():
return "Array[<unknown type>]"
return "Array[%s]" % property.hint_string
+ TYPE_DICTIONARY:
+ if property.hint == PROPERTY_HINT_DICTIONARY_TYPE:
+ if str(property.hint_string).is_empty():
+ return "Dictionary[<unknown type>, <unknown type>]"
+ return "Dictionary[%s]" % str(property.hint_string).replace(";", ", ")
TYPE_OBJECT:
if not str(property.class_name).is_empty():
return property.class_name
@@ -46,7 +61,7 @@ static func get_human_readable_hint_string(property: Dictionary) -> String:
while true:
if not hint_string.contains(":"):
- push_error("Invalid PROPERTY_HINT_TYPE_STRING format.")
+ printerr("Invalid PROPERTY_HINT_TYPE_STRING format.")
var elem_type_hint: String = hint_string.get_slice(":", 0)
hint_string = hint_string.substr(elem_type_hint.length() + 1)
@@ -58,7 +73,7 @@ static func get_human_readable_hint_string(property: Dictionary) -> String:
type_hint_prefixes += "<%s>:" % type_string(elem_type)
else:
if elem_type_hint.count("/") != 1:
- push_error("Invalid PROPERTY_HINT_TYPE_STRING format.")
+ printerr("Invalid PROPERTY_HINT_TYPE_STRING format.")
elem_type = elem_type_hint.get_slice("/", 0).to_int()
elem_hint = elem_type_hint.get_slice("/", 1).to_int()
type_hint_prefixes += "<%s>/<%s>:" % [
@@ -178,6 +193,8 @@ static func get_property_hint_name(hint: PropertyHint) -> String:
return "PROPERTY_HINT_INT_IS_POINTER"
PROPERTY_HINT_ARRAY_TYPE:
return "PROPERTY_HINT_ARRAY_TYPE"
+ PROPERTY_HINT_DICTIONARY_TYPE:
+ return "PROPERTY_HINT_DICTIONARY_TYPE"
PROPERTY_HINT_LOCALE_ID:
return "PROPERTY_HINT_LOCALE_ID"
PROPERTY_HINT_LOCALIZABLE_STRING:
@@ -188,7 +205,7 @@ static func get_property_hint_name(hint: PropertyHint) -> String:
return "PROPERTY_HINT_HIDE_QUATERNION_EDIT"
PROPERTY_HINT_PASSWORD:
return "PROPERTY_HINT_PASSWORD"
- push_error("Argument `hint` is invalid. Use `PROPERTY_HINT_*` constants.")
+ printerr("Argument `hint` is invalid. Use `PROPERTY_HINT_*` constants.")
return "<invalid hint>"
@@ -240,7 +257,7 @@ static func get_property_usage_string(usage: int) -> String:
usage &= ~flag[0]
if usage != PROPERTY_USAGE_NONE:
- push_error("Argument `usage` is invalid. Use `PROPERTY_USAGE_*` constants.")
+ printerr("Argument `usage` is invalid. Use `PROPERTY_USAGE_*` constants.")
return "<invalid usage flags>"
return result.left(-1)
diff --git a/modules/gltf/doc_classes/GLTFDocument.xml b/modules/gltf/doc_classes/GLTFDocument.xml
index ebeed015e9..10534594d3 100644
--- a/modules/gltf/doc_classes/GLTFDocument.xml
+++ b/modules/gltf/doc_classes/GLTFDocument.xml
@@ -63,6 +63,13 @@
The [param bake_fps] parameter overrides the bake_fps in [param state].
</description>
</method>
+ <method name="get_supported_gltf_extensions" qualifiers="static">
+ <return type="PackedStringArray" />
+ <description>
+ Returns a list of all support glTF extensions, including extensions supported directly by the engine, and extensions supported by user plugins registering [GLTFDocumentExtension] classes.
+ [b]Note:[/b] If this method is run before a GLTFDocumentExtension is registered, its extensions won't be included in the list. Be sure to only run this method after all extensions are registered. If you run this when the engine starts, consider waiting a frame before calling this method to ensure all extensions are registered.
+ </description>
+ </method>
<method name="register_gltf_document_extension" qualifiers="static">
<return type="void" />
<param index="0" name="extension" type="GLTFDocumentExtension" />
diff --git a/modules/gltf/editor/editor_scene_importer_blend.cpp b/modules/gltf/editor/editor_scene_importer_blend.cpp
index 0aab6e84b4..8e5a992bd4 100644
--- a/modules/gltf/editor/editor_scene_importer_blend.cpp
+++ b/modules/gltf/editor/editor_scene_importer_blend.cpp
@@ -336,7 +336,7 @@ Node *EditorSceneFormatImporterBlend::import_scene(const String &p_path, uint32_
#endif
}
-Variant EditorSceneFormatImporterBlend::get_option_visibility(const String &p_path, bool p_for_animation, const String &p_option,
+Variant EditorSceneFormatImporterBlend::get_option_visibility(const String &p_path, const String &p_scene_import_type, const String &p_option,
const HashMap<StringName, Variant> &p_options) {
if (p_path.get_extension().to_lower() != "blend") {
return true;
diff --git a/modules/gltf/editor/editor_scene_importer_blend.h b/modules/gltf/editor/editor_scene_importer_blend.h
index 6adace9276..17eb9e5709 100644
--- a/modules/gltf/editor/editor_scene_importer_blend.h
+++ b/modules/gltf/editor/editor_scene_importer_blend.h
@@ -74,7 +74,7 @@ public:
List<String> *r_missing_deps, Error *r_err = nullptr) override;
virtual void get_import_options(const String &p_path,
List<ResourceImporter::ImportOption> *r_options) override;
- virtual Variant get_option_visibility(const String &p_path, bool p_for_animation, const String &p_option,
+ virtual Variant get_option_visibility(const String &p_path, const String &p_scene_import_type, const String &p_option,
const HashMap<StringName, Variant> &p_options) override;
};
diff --git a/modules/gltf/editor/editor_scene_importer_gltf.cpp b/modules/gltf/editor/editor_scene_importer_gltf.cpp
index ce7e17d361..41e294cfc6 100644
--- a/modules/gltf/editor/editor_scene_importer_gltf.cpp
+++ b/modules/gltf/editor/editor_scene_importer_gltf.cpp
@@ -100,7 +100,7 @@ void EditorSceneFormatImporterGLTF::handle_compatibility_options(HashMap<StringN
}
}
-Variant EditorSceneFormatImporterGLTF::get_option_visibility(const String &p_path, bool p_for_animation,
+Variant EditorSceneFormatImporterGLTF::get_option_visibility(const String &p_path, const String &p_scene_import_type,
const String &p_option, const HashMap<StringName, Variant> &p_options) {
return true;
}
diff --git a/modules/gltf/editor/editor_scene_importer_gltf.h b/modules/gltf/editor/editor_scene_importer_gltf.h
index d0a9aaf05a..e17b6f3f2e 100644
--- a/modules/gltf/editor/editor_scene_importer_gltf.h
+++ b/modules/gltf/editor/editor_scene_importer_gltf.h
@@ -50,7 +50,7 @@ public:
virtual void get_import_options(const String &p_path,
List<ResourceImporter::ImportOption> *r_options) override;
virtual void handle_compatibility_options(HashMap<StringName, Variant> &p_import_params) const override;
- virtual Variant get_option_visibility(const String &p_path, bool p_for_animation,
+ virtual Variant get_option_visibility(const String &p_path, const String &p_scene_import_type,
const String &p_option, const HashMap<StringName, Variant> &p_options) override;
};
diff --git a/modules/gltf/extensions/gltf_document_extension.cpp b/modules/gltf/extensions/gltf_document_extension.cpp
index 9fdd6034a9..c6540ebb22 100644
--- a/modules/gltf/extensions/gltf_document_extension.cpp
+++ b/modules/gltf/extensions/gltf_document_extension.cpp
@@ -56,7 +56,7 @@ void GLTFDocumentExtension::_bind_methods() {
// Import process.
Error GLTFDocumentExtension::import_preflight(Ref<GLTFState> p_state, Vector<String> p_extensions) {
- ERR_FAIL_NULL_V(p_state, ERR_INVALID_PARAMETER);
+ ERR_FAIL_COND_V(p_state.is_null(), ERR_INVALID_PARAMETER);
Error err = OK;
GDVIRTUAL_CALL(_import_preflight, p_state, p_extensions, err);
return err;
@@ -69,16 +69,16 @@ Vector<String> GLTFDocumentExtension::get_supported_extensions() {
}
Error GLTFDocumentExtension::parse_node_extensions(Ref<GLTFState> p_state, Ref<GLTFNode> p_gltf_node, Dictionary &p_extensions) {
- ERR_FAIL_NULL_V(p_state, ERR_INVALID_PARAMETER);
- ERR_FAIL_NULL_V(p_gltf_node, ERR_INVALID_PARAMETER);
+ ERR_FAIL_COND_V(p_state.is_null(), ERR_INVALID_PARAMETER);
+ ERR_FAIL_COND_V(p_gltf_node.is_null(), ERR_INVALID_PARAMETER);
Error err = OK;
GDVIRTUAL_CALL(_parse_node_extensions, p_state, p_gltf_node, p_extensions, err);
return err;
}
Error GLTFDocumentExtension::parse_image_data(Ref<GLTFState> p_state, const PackedByteArray &p_image_data, const String &p_mime_type, Ref<Image> r_image) {
- ERR_FAIL_NULL_V(p_state, ERR_INVALID_PARAMETER);
- ERR_FAIL_NULL_V(r_image, ERR_INVALID_PARAMETER);
+ ERR_FAIL_COND_V(p_state.is_null(), ERR_INVALID_PARAMETER);
+ ERR_FAIL_COND_V(r_image.is_null(), ERR_INVALID_PARAMETER);
Error err = OK;
GDVIRTUAL_CALL(_parse_image_data, p_state, p_image_data, p_mime_type, r_image, err);
return err;
@@ -91,31 +91,31 @@ String GLTFDocumentExtension::get_image_file_extension() {
}
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);
+ ERR_FAIL_COND_V(p_state.is_null(), ERR_INVALID_PARAMETER);
+ ERR_FAIL_COND_V(r_gltf_texture.is_null(), ERR_INVALID_PARAMETER);
Error err = OK;
GDVIRTUAL_CALL(_parse_texture_json, p_state, p_texture_json, r_gltf_texture, err);
return err;
}
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_COND_V(p_state.is_null(), nullptr);
+ ERR_FAIL_COND_V(p_gltf_node.is_null(), nullptr);
Node3D *ret_node = nullptr;
GDVIRTUAL_CALL(_generate_scene_node, p_state, p_gltf_node, p_scene_parent, ret_node);
return ret_node;
}
Error GLTFDocumentExtension::import_post_parse(Ref<GLTFState> p_state) {
- ERR_FAIL_NULL_V(p_state, ERR_INVALID_PARAMETER);
+ ERR_FAIL_COND_V(p_state.is_null(), ERR_INVALID_PARAMETER);
Error err = OK;
GDVIRTUAL_CALL(_import_post_parse, p_state, err);
return err;
}
Error GLTFDocumentExtension::import_node(Ref<GLTFState> p_state, Ref<GLTFNode> p_gltf_node, Dictionary &r_dict, Node *p_node) {
- ERR_FAIL_NULL_V(p_state, ERR_INVALID_PARAMETER);
- ERR_FAIL_NULL_V(p_gltf_node, ERR_INVALID_PARAMETER);
+ ERR_FAIL_COND_V(p_state.is_null(), ERR_INVALID_PARAMETER);
+ ERR_FAIL_COND_V(p_gltf_node.is_null(), ERR_INVALID_PARAMETER);
ERR_FAIL_NULL_V(p_node, ERR_INVALID_PARAMETER);
Error err = OK;
GDVIRTUAL_CALL(_import_node, p_state, p_gltf_node, r_dict, p_node, err);
@@ -124,7 +124,7 @@ Error GLTFDocumentExtension::import_node(Ref<GLTFState> p_state, Ref<GLTFNode> p
Error GLTFDocumentExtension::import_post(Ref<GLTFState> p_state, Node *p_root) {
ERR_FAIL_NULL_V(p_root, ERR_INVALID_PARAMETER);
- ERR_FAIL_NULL_V(p_state, ERR_INVALID_PARAMETER);
+ ERR_FAIL_COND_V(p_state.is_null(), ERR_INVALID_PARAMETER);
Error err = OK;
GDVIRTUAL_CALL(_import_post, p_state, p_root, err);
return err;
@@ -139,14 +139,14 @@ Error GLTFDocumentExtension::export_preflight(Ref<GLTFState> p_state, Node *p_ro
}
void GLTFDocumentExtension::convert_scene_node(Ref<GLTFState> p_state, Ref<GLTFNode> p_gltf_node, Node *p_scene_node) {
- ERR_FAIL_NULL(p_state);
- ERR_FAIL_NULL(p_gltf_node);
+ ERR_FAIL_COND(p_state.is_null());
+ ERR_FAIL_COND(p_gltf_node.is_null());
ERR_FAIL_NULL(p_scene_node);
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);
+ ERR_FAIL_COND_V(p_state.is_null(), ERR_INVALID_PARAMETER);
Error err = OK;
GDVIRTUAL_CALL(_export_preserialize, p_state, err);
return err;
@@ -160,38 +160,38 @@ Vector<String> GLTFDocumentExtension::get_saveable_image_formats() {
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);
+ ERR_FAIL_COND_V(p_state.is_null(), ret);
+ ERR_FAIL_COND_V(p_image.is_null(), 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);
+ ERR_FAIL_COND_V(p_state.is_null(), ERR_INVALID_PARAMETER);
+ ERR_FAIL_COND_V(p_image.is_null(), 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);
+ ERR_FAIL_COND_V(p_state.is_null(), ERR_INVALID_PARAMETER);
+ ERR_FAIL_COND_V(p_gltf_texture.is_null(), 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);
+ ERR_FAIL_COND_V(p_state.is_null(), ERR_INVALID_PARAMETER);
+ ERR_FAIL_COND_V(p_gltf_node.is_null(), ERR_INVALID_PARAMETER);
Error err = OK;
GDVIRTUAL_CALL(_export_node, p_state, p_gltf_node, r_dict, p_node, err);
return err;
}
Error GLTFDocumentExtension::export_post(Ref<GLTFState> p_state) {
- ERR_FAIL_NULL_V(p_state, ERR_INVALID_PARAMETER);
+ ERR_FAIL_COND_V(p_state.is_null(), ERR_INVALID_PARAMETER);
Error err = OK;
GDVIRTUAL_CALL(_export_post, p_state, err);
return err;
diff --git a/modules/gltf/extensions/gltf_document_extension_convert_importer_mesh.cpp b/modules/gltf/extensions/gltf_document_extension_convert_importer_mesh.cpp
index 64117349e0..cde30bce18 100644
--- a/modules/gltf/extensions/gltf_document_extension_convert_importer_mesh.cpp
+++ b/modules/gltf/extensions/gltf_document_extension_convert_importer_mesh.cpp
@@ -45,7 +45,7 @@ void GLTFDocumentExtensionConvertImporterMesh::_copy_meta(Object *p_src_object,
Error GLTFDocumentExtensionConvertImporterMesh::import_post(Ref<GLTFState> p_state, Node *p_root) {
ERR_FAIL_NULL_V(p_root, ERR_INVALID_PARAMETER);
- ERR_FAIL_NULL_V(p_state, ERR_INVALID_PARAMETER);
+ ERR_FAIL_COND_V(p_state.is_null(), ERR_INVALID_PARAMETER);
List<Node *> queue;
queue.push_back(p_root);
List<Node *> delete_queue;
diff --git a/modules/gltf/extensions/physics/gltf_physics_shape.cpp b/modules/gltf/extensions/physics/gltf_physics_shape.cpp
index 0340eb11b5..0f2246ce18 100644
--- a/modules/gltf/extensions/physics/gltf_physics_shape.cpp
+++ b/modules/gltf/extensions/physics/gltf_physics_shape.cpp
@@ -229,7 +229,7 @@ Ref<GLTFPhysicsShape> GLTFPhysicsShape::from_resource(const Ref<Shape3D> &p_shap
}
Ref<Shape3D> GLTFPhysicsShape::to_resource(bool p_cache_shapes) {
- if (!p_cache_shapes || _shape_cache == nullptr) {
+ if (!p_cache_shapes || _shape_cache.is_null()) {
if (shape_type == "box") {
Ref<BoxShape3D> box;
box.instantiate();
diff --git a/modules/gltf/gltf_document.cpp b/modules/gltf/gltf_document.cpp
index cd25b93e6c..4653df7afe 100644
--- a/modules/gltf/gltf_document.cpp
+++ b/modules/gltf/gltf_document.cpp
@@ -69,6 +69,24 @@
#include <stdlib.h>
#include <cstdint>
+static void _attach_extras_to_meta(const Dictionary &p_extras, Ref<Resource> p_node) {
+ if (!p_extras.is_empty()) {
+ p_node->set_meta("extras", p_extras);
+ }
+}
+
+static void _attach_meta_to_extras(Ref<Resource> p_node, Dictionary &p_json) {
+ if (p_node->has_meta("extras")) {
+ Dictionary node_extras = p_node->get_meta("extras");
+ if (p_json.has("extras")) {
+ Dictionary extras = p_json["extras"];
+ extras.merge(node_extras);
+ } else {
+ p_json["extras"] = node_extras;
+ }
+ }
+}
+
static Ref<ImporterMesh> _mesh_to_importer_mesh(Ref<Mesh> p_mesh) {
Ref<ImporterMesh> importer_mesh;
importer_mesh.instantiate();
@@ -101,6 +119,7 @@ static Ref<ImporterMesh> _mesh_to_importer_mesh(Ref<Mesh> p_mesh) {
array, p_mesh->surface_get_blend_shape_arrays(surface_i), p_mesh->surface_get_lods(surface_i), mat,
mat_name, p_mesh->surface_get_format(surface_i));
}
+ importer_mesh->merge_meta_from(*p_mesh);
return importer_mesh;
}
@@ -280,8 +299,8 @@ Error GLTFDocument::_parse_json(const String &p_path, Ref<GLTFState> p_state) {
}
Error GLTFDocument::_parse_glb(Ref<FileAccess> p_file, Ref<GLTFState> p_state) {
- ERR_FAIL_NULL_V(p_file, ERR_INVALID_PARAMETER);
- ERR_FAIL_NULL_V(p_state, ERR_INVALID_PARAMETER);
+ ERR_FAIL_COND_V(p_file.is_null(), ERR_INVALID_PARAMETER);
+ ERR_FAIL_COND_V(p_state.is_null(), ERR_INVALID_PARAMETER);
ERR_FAIL_COND_V(p_file->get_position() != 0, ERR_FILE_CANT_READ);
uint32_t magic = p_file->get_32();
ERR_FAIL_COND_V(magic != 0x46546C67, ERR_FILE_UNRECOGNIZED); //glTF
@@ -458,7 +477,7 @@ Error GLTFDocument::_serialize_nodes(Ref<GLTFState> p_state) {
if (extensions.is_empty()) {
node.erase("extensions");
}
-
+ _attach_meta_to_extras(gltf_node, node);
nodes.push_back(node);
}
if (!nodes.is_empty()) {
@@ -624,6 +643,10 @@ Error GLTFDocument::_parse_nodes(Ref<GLTFState> p_state) {
}
}
+ if (n.has("extras")) {
+ _attach_extras_to_meta(n["extras"], node);
+ }
+
if (n.has("children")) {
const Array &children = n["children"];
for (int j = 0; j < children.size(); j++) {
@@ -2727,6 +2750,8 @@ Error GLTFDocument::_serialize_meshes(Ref<GLTFState> p_state) {
Dictionary e;
e["targetNames"] = target_names;
+ gltf_mesh["extras"] = e;
+ _attach_meta_to_extras(import_mesh, gltf_mesh);
weights.resize(target_names.size());
for (int name_i = 0; name_i < target_names.size(); name_i++) {
@@ -2742,8 +2767,6 @@ Error GLTFDocument::_serialize_meshes(Ref<GLTFState> p_state) {
ERR_FAIL_COND_V(target_names.size() != weights.size(), FAILED);
- gltf_mesh["extras"] = e;
-
gltf_mesh["primitives"] = primitives;
meshes.push_back(gltf_mesh);
@@ -2776,6 +2799,7 @@ Error GLTFDocument::_parse_meshes(Ref<GLTFState> p_state) {
Array primitives = d["primitives"];
const Dictionary &extras = d.has("extras") ? (Dictionary)d["extras"] : Dictionary();
+ _attach_extras_to_meta(extras, mesh);
Ref<ImporterMesh> import_mesh;
import_mesh.instantiate();
String mesh_name = "mesh";
@@ -3258,7 +3282,7 @@ Error GLTFDocument::_parse_meshes(Ref<GLTFState> p_state) {
const int material = p["material"];
ERR_FAIL_INDEX_V(material, p_state->materials.size(), ERR_FILE_CORRUPT);
Ref<Material> mat3d = p_state->materials[material];
- ERR_FAIL_NULL_V(mat3d, ERR_FILE_CORRUPT);
+ ERR_FAIL_COND_V(mat3d.is_null(), ERR_FILE_CORRUPT);
Ref<BaseMaterial3D> base_material = mat3d;
if (has_vertex_color && base_material.is_valid()) {
@@ -3274,7 +3298,7 @@ Error GLTFDocument::_parse_meshes(Ref<GLTFState> p_state) {
}
mat = mat3d;
}
- ERR_FAIL_NULL_V(mat, ERR_FILE_CORRUPT);
+ ERR_FAIL_COND_V(mat.is_null(), ERR_FILE_CORRUPT);
mat_name = mat->get_name();
}
import_mesh->add_surface(primitive, array, morphs,
@@ -3577,7 +3601,7 @@ void GLTFDocument::_parse_image_save_image(Ref<GLTFState> p_state, const Vector<
}
Error GLTFDocument::_parse_images(Ref<GLTFState> p_state, const String &p_base_path) {
- ERR_FAIL_NULL_V(p_state, ERR_INVALID_PARAMETER);
+ ERR_FAIL_COND_V(p_state.is_null(), ERR_INVALID_PARAMETER);
if (!p_state->json.has("images")) {
return OK;
}
@@ -4170,6 +4194,7 @@ Error GLTFDocument::_serialize_materials(Ref<GLTFState> p_state) {
}
d["extensions"] = extensions;
+ _attach_meta_to_extras(material, d);
materials.push_back(d);
}
if (!materials.size()) {
@@ -4372,6 +4397,10 @@ Error GLTFDocument::_parse_materials(Ref<GLTFState> p_state) {
}
}
}
+
+ if (material_dict.has("extras")) {
+ _attach_extras_to_meta(material_dict["extras"], material);
+ }
p_state->materials.push_back(material);
}
@@ -5161,6 +5190,7 @@ ImporterMeshInstance3D *GLTFDocument::_generate_mesh_instance(Ref<GLTFState> p_s
return mi;
}
mi->set_mesh(import_mesh);
+ import_mesh->merge_meta_from(*mesh);
return mi;
}
@@ -5285,6 +5315,7 @@ void GLTFDocument::_convert_scene_node(Ref<GLTFState> p_state, Node *p_current,
gltf_root = current_node_i;
p_state->root_nodes.push_back(gltf_root);
}
+ gltf_node->merge_meta_from(p_current);
_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++) {
_convert_scene_node(p_state, p_current->get_child(node_i), current_node_i, gltf_root);
@@ -5676,6 +5707,8 @@ void GLTFDocument::_generate_scene_node(Ref<GLTFState> p_state, const GLTFNodeIn
current_node->set_transform(gltf_node->transform);
}
+ current_node->merge_meta_from(*gltf_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, gltf_node->children[i], current_node, p_scene_root);
@@ -6934,14 +6967,14 @@ Dictionary _serialize_texture_transform_uv(Vector2 p_offset, Vector2 p_scale) {
}
Dictionary GLTFDocument::_serialize_texture_transform_uv1(Ref<BaseMaterial3D> p_material) {
- ERR_FAIL_NULL_V(p_material, Dictionary());
+ ERR_FAIL_COND_V(p_material.is_null(), Dictionary());
Vector3 offset = p_material->get_uv1_offset();
Vector3 scale = p_material->get_uv1_scale();
return _serialize_texture_transform_uv(Vector2(offset.x, offset.y), Vector2(scale.x, scale.y));
}
Dictionary GLTFDocument::_serialize_texture_transform_uv2(Ref<BaseMaterial3D> p_material) {
- ERR_FAIL_NULL_V(p_material, Dictionary());
+ ERR_FAIL_COND_V(p_material.is_null(), Dictionary());
Vector3 offset = p_material->get_uv2_offset();
Vector3 scale = p_material->get_uv2_scale();
return _serialize_texture_transform_uv(Vector2(offset.x, offset.y), Vector2(scale.x, scale.y));
@@ -7060,6 +7093,8 @@ void GLTFDocument::_bind_methods() {
&GLTFDocument::register_gltf_document_extension, DEFVAL(false));
ClassDB::bind_static_method("GLTFDocument", D_METHOD("unregister_gltf_document_extension", "extension"),
&GLTFDocument::unregister_gltf_document_extension);
+ ClassDB::bind_static_method("GLTFDocument", D_METHOD("get_supported_gltf_extensions"),
+ &GLTFDocument::get_supported_gltf_extensions);
}
void GLTFDocument::_build_parent_hierachy(Ref<GLTFState> p_state) {
@@ -7100,6 +7135,36 @@ Vector<Ref<GLTFDocumentExtension>> GLTFDocument::get_all_gltf_document_extension
return all_document_extensions;
}
+Vector<String> GLTFDocument::get_supported_gltf_extensions() {
+ HashSet<String> set = get_supported_gltf_extensions_hashset();
+ Vector<String> vec;
+ for (const String &s : set) {
+ vec.append(s);
+ }
+ vec.sort();
+ return vec;
+}
+
+HashSet<String> GLTFDocument::get_supported_gltf_extensions_hashset() {
+ HashSet<String> supported_extensions;
+ // If the extension is supported directly in GLTFDocument, list it here.
+ // Other built-in extensions are supported by GLTFDocumentExtension classes.
+ supported_extensions.insert("GODOT_single_root");
+ supported_extensions.insert("KHR_lights_punctual");
+ supported_extensions.insert("KHR_materials_emissive_strength");
+ supported_extensions.insert("KHR_materials_pbrSpecularGlossiness");
+ supported_extensions.insert("KHR_materials_unlit");
+ supported_extensions.insert("KHR_texture_transform");
+ for (Ref<GLTFDocumentExtension> ext : all_document_extensions) {
+ ERR_CONTINUE(ext.is_null());
+ Vector<String> ext_supported_extensions = ext->get_supported_extensions();
+ for (int i = 0; i < ext_supported_extensions.size(); ++i) {
+ supported_extensions.insert(ext_supported_extensions[i]);
+ }
+ }
+ return supported_extensions;
+}
+
PackedByteArray GLTFDocument::_serialize_glb_buffer(Ref<GLTFState> p_state, Error *r_err) {
Error err = _encode_buffer_glb(p_state, "");
if (r_err) {
@@ -7280,7 +7345,7 @@ Error GLTFDocument::_parse_gltf_state(Ref<GLTFState> p_state, const String &p_se
PackedByteArray GLTFDocument::generate_buffer(Ref<GLTFState> p_state) {
Ref<GLTFState> state = p_state;
- ERR_FAIL_NULL_V(state, PackedByteArray());
+ ERR_FAIL_COND_V(state.is_null(), PackedByteArray());
// For buffers, set the state filename to an empty string, but
// don't touch the base path, in case the user set it manually.
state->filename = "";
@@ -7292,7 +7357,7 @@ PackedByteArray GLTFDocument::generate_buffer(Ref<GLTFState> p_state) {
Error GLTFDocument::write_to_filesystem(Ref<GLTFState> p_state, const String &p_path) {
Ref<GLTFState> state = p_state;
- ERR_FAIL_NULL_V(state, ERR_INVALID_PARAMETER);
+ ERR_FAIL_COND_V(state.is_null(), ERR_INVALID_PARAMETER);
state->base_path = p_path.get_base_dir();
state->filename = p_path.get_file();
Error err = _serialize(state);
@@ -7308,7 +7373,7 @@ Error GLTFDocument::write_to_filesystem(Ref<GLTFState> p_state, const String &p_
Node *GLTFDocument::generate_scene(Ref<GLTFState> p_state, float p_bake_fps, bool p_trimming, bool p_remove_immutable_tracks) {
Ref<GLTFState> state = p_state;
- ERR_FAIL_NULL_V(state, nullptr);
+ ERR_FAIL_COND_V(state.is_null(), nullptr);
ERR_FAIL_INDEX_V(0, state->root_nodes.size(), nullptr);
Error err = OK;
p_state->set_bake_fps(p_bake_fps);
@@ -7426,7 +7491,7 @@ Error GLTFDocument::append_from_file(String p_path, Ref<GLTFState> p_state, uint
Error err;
Ref<FileAccess> file = FileAccess::open(p_path, FileAccess::READ, &err);
ERR_FAIL_COND_V(err != OK, ERR_FILE_CANT_OPEN);
- ERR_FAIL_NULL_V(file, ERR_FILE_CANT_OPEN);
+ ERR_FAIL_COND_V(file.is_null(), ERR_FILE_CANT_OPEN);
String base_path = p_base_path;
if (base_path.is_empty()) {
base_path = p_path.get_base_dir();
@@ -7443,7 +7508,7 @@ Error GLTFDocument::append_from_file(String p_path, Ref<GLTFState> p_state, uint
}
Error GLTFDocument::_parse_gltf_extensions(Ref<GLTFState> p_state) {
- ERR_FAIL_NULL_V(p_state, ERR_PARSE_ERROR);
+ ERR_FAIL_COND_V(p_state.is_null(), ERR_PARSE_ERROR);
if (p_state->json.has("extensionsUsed")) {
Vector<String> ext_array = p_state->json["extensionsUsed"];
p_state->extensions_used = ext_array;
@@ -7452,19 +7517,7 @@ Error GLTFDocument::_parse_gltf_extensions(Ref<GLTFState> p_state) {
Vector<String> ext_array = p_state->json["extensionsRequired"];
p_state->extensions_required = ext_array;
}
- HashSet<String> supported_extensions;
- supported_extensions.insert("KHR_lights_punctual");
- supported_extensions.insert("KHR_materials_pbrSpecularGlossiness");
- supported_extensions.insert("KHR_texture_transform");
- supported_extensions.insert("KHR_materials_unlit");
- supported_extensions.insert("KHR_materials_emissive_strength");
- for (Ref<GLTFDocumentExtension> ext : document_extensions) {
- ERR_CONTINUE(ext.is_null());
- Vector<String> ext_supported_extensions = ext->get_supported_extensions();
- for (int i = 0; i < ext_supported_extensions.size(); ++i) {
- supported_extensions.insert(ext_supported_extensions[i]);
- }
- }
+ HashSet<String> supported_extensions = get_supported_gltf_extensions_hashset();
Error ret = OK;
for (int i = 0; i < p_state->extensions_required.size(); i++) {
if (!supported_extensions.has(p_state->extensions_required[i])) {
diff --git a/modules/gltf/gltf_document.h b/modules/gltf/gltf_document.h
index d37544750d..b3e6dcf54a 100644
--- a/modules/gltf/gltf_document.h
+++ b/modules/gltf/gltf_document.h
@@ -92,6 +92,8 @@ public:
static void unregister_gltf_document_extension(Ref<GLTFDocumentExtension> p_extension);
static void unregister_all_gltf_document_extensions();
static Vector<Ref<GLTFDocumentExtension>> get_all_gltf_document_extensions();
+ static Vector<String> get_supported_gltf_extensions();
+ static HashSet<String> get_supported_gltf_extensions_hashset();
void set_naming_version(int p_version);
int get_naming_version() const;
diff --git a/modules/gltf/tests/test_gltf_extras.h b/modules/gltf/tests/test_gltf_extras.h
new file mode 100644
index 0000000000..96aadf3023
--- /dev/null
+++ b/modules/gltf/tests/test_gltf_extras.h
@@ -0,0 +1,165 @@
+/**************************************************************************/
+/* test_gltf_extras.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_GLTF_EXTRAS_H
+#define TEST_GLTF_EXTRAS_H
+
+#include "tests/test_macros.h"
+
+#ifdef TOOLS_ENABLED
+
+#include "core/os/os.h"
+#include "editor/import/3d/resource_importer_scene.h"
+#include "modules/gltf/editor/editor_scene_importer_gltf.h"
+#include "modules/gltf/gltf_document.h"
+#include "modules/gltf/gltf_state.h"
+#include "scene/3d/mesh_instance_3d.h"
+#include "scene/main/window.h"
+#include "scene/resources/3d/primitive_meshes.h"
+#include "scene/resources/material.h"
+#include "scene/resources/packed_scene.h"
+
+namespace TestGltfExtras {
+
+static Node *_gltf_export_then_import(Node *p_root, String &p_tempfilebase) {
+ Ref<GLTFDocument> doc;
+ doc.instantiate();
+ Ref<GLTFState> state;
+ state.instantiate();
+ Error err = doc->append_from_scene(p_root, state, EditorSceneFormatImporter::IMPORT_USE_NAMED_SKIN_BINDS);
+ CHECK_MESSAGE(err == OK, "GLTF state generation failed.");
+ err = doc->write_to_filesystem(state, p_tempfilebase + ".gltf");
+ CHECK_MESSAGE(err == OK, "Writing GLTF to cache dir failed.");
+
+ // Setting up importers.
+ Ref<ResourceImporterScene> import_scene = memnew(ResourceImporterScene("PackedScene", true));
+ ResourceFormatImporter::get_singleton()->add_importer(import_scene);
+ Ref<EditorSceneFormatImporterGLTF> import_gltf;
+ import_gltf.instantiate();
+ ResourceImporterScene::add_scene_importer(import_gltf);
+
+ // GTLF importer behaves differently outside of editor, it's too late to modify Engine::get_editor_hint
+ // as the registration of runtime extensions already happened, so remove them. See modules/gltf/register_types.cpp
+ GLTFDocument::unregister_all_gltf_document_extensions();
+
+ HashMap<StringName, Variant> options(20);
+ options["nodes/root_type"] = "";
+ options["nodes/root_name"] = "";
+ options["nodes/apply_root_scale"] = true;
+ options["nodes/root_scale"] = 1.0;
+ options["meshes/ensure_tangents"] = true;
+ options["meshes/generate_lods"] = false;
+ options["meshes/create_shadow_meshes"] = true;
+ options["meshes/light_baking"] = 1;
+ options["meshes/lightmap_texel_size"] = 0.2;
+ options["meshes/force_disable_compression"] = false;
+ options["skins/use_named_skins"] = true;
+ options["animation/import"] = true;
+ options["animation/fps"] = 30;
+ options["animation/trimming"] = false;
+ options["animation/remove_immutable_tracks"] = true;
+ options["import_script/path"] = "";
+ options["_subresources"] = Dictionary();
+ options["gltf/naming_version"] = 1;
+
+ // Process gltf file, note that this generates `.scn` resource from the 2nd argument.
+ err = import_scene->import(p_tempfilebase + ".gltf", p_tempfilebase, options, nullptr, nullptr, nullptr);
+ CHECK_MESSAGE(err == OK, "GLTF import failed.");
+ ResourceImporterScene::remove_scene_importer(import_gltf);
+
+ Ref<PackedScene> packed_scene = ResourceLoader::load(p_tempfilebase + ".scn", "", ResourceFormatLoader::CACHE_MODE_REPLACE, &err);
+ CHECK_MESSAGE(err == OK, "Loading scene failed.");
+ Node *p_scene = packed_scene->instantiate();
+ return p_scene;
+}
+
+TEST_CASE("[SceneTree][Node] GLTF test mesh and material meta export and import") {
+ // Setup scene.
+ Ref<StandardMaterial3D> original_material = memnew(StandardMaterial3D);
+ original_material->set_albedo(Color(1.0, .0, .0));
+ original_material->set_name("material");
+ Dictionary material_dict;
+ material_dict["node_type"] = "material";
+ original_material->set_meta("extras", material_dict);
+
+ Ref<PlaneMesh> original_meshdata = memnew(PlaneMesh);
+ original_meshdata->set_name("planemesh");
+ Dictionary meshdata_dict;
+ meshdata_dict["node_type"] = "planemesh";
+ original_meshdata->set_meta("extras", meshdata_dict);
+ original_meshdata->surface_set_material(0, original_material);
+
+ MeshInstance3D *original_mesh_instance = memnew(MeshInstance3D);
+ original_mesh_instance->set_mesh(original_meshdata);
+ original_mesh_instance->set_name("mesh_instance_3d");
+ Dictionary mesh_instance_dict;
+ mesh_instance_dict["node_type"] = "mesh_instance_3d";
+ original_mesh_instance->set_meta("extras", mesh_instance_dict);
+
+ Node3D *original = memnew(Node3D);
+ SceneTree::get_singleton()->get_root()->add_child(original);
+ original->add_child(original_mesh_instance);
+ original->set_name("node3d");
+ Dictionary node_dict;
+ node_dict["node_type"] = "node3d";
+ original->set_meta("extras", node_dict);
+ original->set_meta("meta_not_nested_under_extras", "should not propagate");
+
+ // Convert to GLFT and back.
+ String tempfile = OS::get_singleton()->get_cache_path().path_join("gltf_extras");
+ Node *loaded = _gltf_export_then_import(original, tempfile);
+
+ // Compare the results.
+ CHECK(loaded->get_name() == "node3d");
+ CHECK(Dictionary(loaded->get_meta("extras")).size() == 1);
+ CHECK(Dictionary(loaded->get_meta("extras"))["node_type"] == "node3d");
+ CHECK_FALSE(loaded->has_meta("meta_not_nested_under_extras"));
+ CHECK_FALSE(Dictionary(loaded->get_meta("extras")).has("meta_not_nested_under_extras"));
+
+ MeshInstance3D *mesh_instance_3d = Object::cast_to<MeshInstance3D>(loaded->find_child("mesh_instance_3d", false, true));
+ CHECK(mesh_instance_3d->get_name() == "mesh_instance_3d");
+ CHECK(Dictionary(mesh_instance_3d->get_meta("extras"))["node_type"] == "mesh_instance_3d");
+
+ Ref<Mesh> mesh = mesh_instance_3d->get_mesh();
+ CHECK(Dictionary(mesh->get_meta("extras"))["node_type"] == "planemesh");
+
+ Ref<Material> material = mesh->surface_get_material(0);
+ CHECK(material->get_name() == "material");
+ CHECK(Dictionary(material->get_meta("extras"))["node_type"] == "material");
+
+ memdelete(original_mesh_instance);
+ memdelete(original);
+ memdelete(loaded);
+}
+} // namespace TestGltfExtras
+
+#endif // TOOLS_ENABLED
+
+#endif // TEST_GLTF_EXTRAS_H
diff --git a/modules/interactive_music/audio_stream_interactive.cpp b/modules/interactive_music/audio_stream_interactive.cpp
index 8472c4e352..8656be988d 100644
--- a/modules/interactive_music/audio_stream_interactive.cpp
+++ b/modules/interactive_music/audio_stream_interactive.cpp
@@ -777,7 +777,7 @@ void AudioStreamPlaybackInteractive::_queue(int p_to_clip_index, bool p_is_auto_
if (stream->clips[p_to_clip_index].auto_advance == AudioStreamInteractive::AUTO_ADVANCE_ENABLED) {
int next_clip = stream->clips[p_to_clip_index].auto_advance_next_clip;
- if (next_clip >= 0 && next_clip < (int)stream->clip_count && states[next_clip].playback.is_valid() && next_clip != p_to_clip_index && next_clip != playback_current && (!transition.use_filler_clip || next_clip != transition.filler_clip)) {
+ if (next_clip >= 0 && next_clip < (int)stream->clip_count && states[next_clip].playback.is_valid() && next_clip != p_to_clip_index && (!transition.use_filler_clip || next_clip != transition.filler_clip)) {
auto_advance_to = next_clip;
}
}
@@ -905,7 +905,9 @@ void AudioStreamPlaybackInteractive::_mix_internal_state(int p_state_idx, int p_
// time to start!
from_frame = state.fade_wait * mix_rate;
state.fade_wait = 0;
- queue_next = state.auto_advance;
+ if (state.fade_speed == 0.0) {
+ queue_next = state.auto_advance;
+ }
playback_current = p_state_idx;
state.first_mix = false;
} else {
@@ -919,7 +921,6 @@ void AudioStreamPlaybackInteractive::_mix_internal_state(int p_state_idx, int p_
state.playback->mix(temp_buffer + from_frame, 1.0, p_frames - from_frame);
double frame_fade_inc = state.fade_speed * frame_inc;
-
for (int i = from_frame; i < p_frames; i++) {
if (state.fade_wait) {
// This is for fade out of existing stream;
@@ -933,6 +934,7 @@ void AudioStreamPlaybackInteractive::_mix_internal_state(int p_state_idx, int p_
state.fade_speed = 0.0;
frame_fade_inc = 0.0;
state.fade_volume = 1.0;
+ queue_next = state.auto_advance;
}
} else if (frame_fade_inc < 0.0) {
state.fade_volume += frame_fade_inc;
diff --git a/modules/interactive_music/doc_classes/AudioStreamInteractive.xml b/modules/interactive_music/doc_classes/AudioStreamInteractive.xml
index e8f8e7b760..17448724d1 100644
--- a/modules/interactive_music/doc_classes/AudioStreamInteractive.xml
+++ b/modules/interactive_music/doc_classes/AudioStreamInteractive.xml
@@ -4,7 +4,7 @@
Audio stream that can playback music interactively, combining clips and a transition table.
</brief_description>
<description>
- This is an audio stream that can playback music interactively, combining clips and a transition table. Clips must be added first, and the transition rules via the [method add_transition]. Additionally, this stream export a property parameter to control the playback via [AudioStreamPlayer], [AudioStreamPlayer2D], or [AudioStreamPlayer3D].
+ This is an audio stream that can playback music interactively, combining clips and a transition table. Clips must be added first, and then the transition rules via the [method add_transition]. Additionally, this stream exports a property parameter to control the playback via [AudioStreamPlayer], [AudioStreamPlayer2D], or [AudioStreamPlayer3D].
The way this is used is by filling a number of clips, then configuring the transition table. From there, clips are selected for playback and the music will smoothly go from the current to the new one while using the corresponding transition rule defined in the transition table.
</description>
<tutorials>
diff --git a/modules/lightmapper_rd/lightmapper_rd.cpp b/modules/lightmapper_rd/lightmapper_rd.cpp
index ddc51bcf6b..8ba6f9e2ba 100644
--- a/modules/lightmapper_rd/lightmapper_rd.cpp
+++ b/modules/lightmapper_rd/lightmapper_rd.cpp
@@ -44,6 +44,9 @@
#if defined(VULKAN_ENABLED)
#include "drivers/vulkan/rendering_context_driver_vulkan.h"
#endif
+#if defined(METAL_ENABLED)
+#include "drivers/metal/rendering_context_driver_metal.h"
+#endif
//uncomment this if you want to see textures from all the process saved
//#define DEBUG_TEXTURES
@@ -790,6 +793,35 @@ LightmapperRD::BakeError LightmapperRD::_dilate(RenderingDevice *rd, Ref<RDShade
return BAKE_OK;
}
+LightmapperRD::BakeError LightmapperRD::_pack_l1(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_pack = rd->shader_create_from_spirv(compute_shader->get_spirv_stages("pack_coeffs"));
+ ERR_FAIL_COND_V(compute_shader_pack.is_null(), BAKE_ERROR_LIGHTMAP_CANT_PRE_BAKE_MESHES); //internal check, should not happen
+ RID compute_shader_pack_pipeline = rd->compute_pipeline_create(compute_shader_pack);
+
+ RID dilate_uniform_set = rd->uniform_set_create(uniforms, compute_shader_pack, 1);
+
+ RD::ComputeListID compute_list = rd->compute_list_begin();
+ rd->compute_list_bind_compute_pipeline(compute_list, compute_shader_pack_pipeline);
+ rd->compute_list_bind_uniform_set(compute_list, compute_base_uniform_set, 0);
+ rd->compute_list_bind_uniform_set(compute_list, dilate_uniform_set, 1);
+ push_constant.region_ofs[0] = 0;
+ push_constant.region_ofs[1] = 0;
+ Vector3i group_size(Math::division_round_up(atlas_size.x, 8), Math::division_round_up(atlas_size.y, 8), 1); //restore group size
+
+ for (int i = 0; i < atlas_slices; i++) {
+ push_constant.atlas_slice = i;
+ rd->compute_list_set_push_constant(compute_list, &push_constant, sizeof(PushConstant));
+ rd->compute_list_dispatch(compute_list, group_size.x, group_size.y, group_size.z);
+ //no barrier, let them run all together
+ }
+ rd->compute_list_end();
+ rd->free(compute_shader_pack);
+
+ return BAKE_OK;
+}
+
Error LightmapperRD::_store_pfm(RenderingDevice *p_rd, RID p_atlas_tex, int p_index, const Size2i &p_atlas_size, const String &p_name) {
Vector<uint8_t> data = p_rd->texture_get_data(p_atlas_tex, p_index);
Ref<Image> img = Image::create_from_data(p_atlas_size.width, p_atlas_size.height, false, Image::FORMAT_RGBAH, data);
@@ -1043,10 +1075,16 @@ LightmapperRD::BakeError LightmapperRD::bake(BakeQuality p_quality, bool p_use_d
RenderingDevice *rd = RenderingServer::get_singleton()->create_local_rendering_device();
if (rd == nullptr) {
#if defined(RD_ENABLED)
-#if defined(VULKAN_ENABLED)
- rcd = memnew(RenderingContextDriverVulkan);
+#if defined(METAL_ENABLED)
+ rcd = memnew(RenderingContextDriverMetal);
rd = memnew(RenderingDevice);
#endif
+#if defined(VULKAN_ENABLED)
+ if (rcd == nullptr) {
+ rcd = memnew(RenderingContextDriverVulkan);
+ rd = memnew(RenderingDevice);
+ }
+#endif
#endif
if (rcd != nullptr && rd != nullptr) {
err = rcd->initialize();
@@ -1716,7 +1754,7 @@ LightmapperRD::BakeError LightmapperRD::bake(BakeQuality p_quality, bool p_use_d
light_probe_buffer = rd->storage_buffer_create(sizeof(float) * 4 * 9 * probe_positions.size());
if (p_step_function) {
- p_step_function(0.7, RTR("Baking lightprobes"), p_bake_userdata, true);
+ p_step_function(0.7, RTR("Baking light probes"), p_bake_userdata, true);
}
Vector<RD::Uniform> uniforms;
@@ -1993,6 +2031,14 @@ LightmapperRD::BakeError LightmapperRD::bake(BakeQuality p_quality, bool p_use_d
}
}
+ if (p_bake_sh) {
+ SWAP(light_accum_tex, light_accum_tex2);
+ BakeError error = _pack_l1(rd, compute_shader, compute_base_uniform_set, push_constant, light_accum_tex2, light_accum_tex, atlas_size, atlas_slices);
+ if (unlikely(error != BAKE_OK)) {
+ return error;
+ }
+ }
+
#ifdef DEBUG_TEXTURES
for (int i = 0; i < atlas_slices * (p_bake_sh ? 4 : 1); i++) {
diff --git a/modules/lightmapper_rd/lightmapper_rd.h b/modules/lightmapper_rd/lightmapper_rd.h
index 59c2d52e69..f43da39670 100644
--- a/modules/lightmapper_rd/lightmapper_rd.h
+++ b/modules/lightmapper_rd/lightmapper_rd.h
@@ -273,6 +273,7 @@ class LightmapperRD : public Lightmapper {
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, int p_denoiser_range, const Size2i &p_atlas_size, int p_atlas_slices, bool p_bake_sh, BakeStepFunc p_step_function, void *p_bake_userdata);
+ BakeError _pack_l1(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);
Error _store_pfm(RenderingDevice *p_rd, RID p_atlas_tex, int p_index, const Size2i &p_atlas_size, const String &p_name);
Ref<Image> _read_pfm(const String &p_name);
diff --git a/modules/lightmapper_rd/lm_compute.glsl b/modules/lightmapper_rd/lm_compute.glsl
index 88fc316679..2c85fff6f3 100644
--- a/modules/lightmapper_rd/lm_compute.glsl
+++ b/modules/lightmapper_rd/lm_compute.glsl
@@ -6,6 +6,7 @@ dilate = "#define MODE_DILATE";
unocclude = "#define MODE_UNOCCLUDE";
light_probes = "#define MODE_LIGHT_PROBES";
denoise = "#define MODE_DENOISE";
+pack_coeffs = "#define MODE_PACK_L1_COEFFS";
#[compute]
@@ -63,7 +64,7 @@ layout(rgba16f, set = 1, binding = 4) uniform restrict image2DArray accum_light;
layout(set = 1, binding = 5) uniform texture2D environment;
#endif
-#if defined(MODE_DILATE) || defined(MODE_DENOISE)
+#if defined(MODE_DILATE) || defined(MODE_DENOISE) || defined(MODE_PACK_L1_COEFFS)
layout(rgba16f, set = 1, binding = 0) uniform restrict writeonly image2DArray dest_light;
layout(set = 1, binding = 1) uniform texture2DArray source_light;
#endif
@@ -1037,4 +1038,28 @@ void main() {
imageStore(dest_light, ivec3(atlas_pos, lightmap_slice), vec4(denoised_rgb, input_light.a));
}
#endif
+
+#ifdef MODE_PACK_L1_COEFFS
+ vec4 base_coeff = texelFetch(sampler2DArray(source_light, linear_sampler), ivec3(atlas_pos, params.atlas_slice * 4), 0);
+
+ for (int i = 1; i < 4; i++) {
+ vec4 c = texelFetch(sampler2DArray(source_light, linear_sampler), ivec3(atlas_pos, params.atlas_slice * 4 + i), 0);
+
+ if (abs(base_coeff.r) > 0.0) {
+ c.r /= (base_coeff.r * 8);
+ }
+
+ if (abs(base_coeff.g) > 0.0) {
+ c.g /= (base_coeff.g * 8);
+ }
+
+ if (abs(base_coeff.b) > 0.0) {
+ c.b /= (base_coeff.b * 8);
+ }
+
+ c.rgb += vec3(0.5);
+ c.rgb = clamp(c.rgb, vec3(0.0), vec3(1.0));
+ imageStore(dest_light, ivec3(atlas_pos, params.atlas_slice * 4 + i), c);
+ }
+#endif
}
diff --git a/modules/minimp3/audio_stream_mp3.cpp b/modules/minimp3/audio_stream_mp3.cpp
index 5720f844bb..394213963a 100644
--- a/modules/minimp3/audio_stream_mp3.cpp
+++ b/modules/minimp3/audio_stream_mp3.cpp
@@ -57,7 +57,7 @@ int AudioStreamPlaybackMP3::_mix_internal(AudioFrame *p_buffer, int p_frames) {
mp3dec_frame_info_t frame_info;
mp3d_sample_t *buf_frame = nullptr;
- int samples_mixed = mp3dec_ex_read_frame(mp3d, &buf_frame, &frame_info, mp3_stream->channels);
+ int samples_mixed = mp3dec_ex_read_frame(&mp3d, &buf_frame, &frame_info, mp3_stream->channels);
if (samples_mixed) {
p_buffer[p_frames - todo] = AudioFrame(buf_frame[0], buf_frame[samples_mixed - 1]);
@@ -70,7 +70,7 @@ int AudioStreamPlaybackMP3::_mix_internal(AudioFrame *p_buffer, int p_frames) {
if (beat_loop && (int)frames_mixed >= beat_length_frames) {
for (int i = 0; i < FADE_SIZE; i++) {
- samples_mixed = mp3dec_ex_read_frame(mp3d, &buf_frame, &frame_info, mp3_stream->channels);
+ samples_mixed = mp3dec_ex_read_frame(&mp3d, &buf_frame, &frame_info, mp3_stream->channels);
loop_fade[i] = AudioFrame(buf_frame[0], buf_frame[samples_mixed - 1]);
if (!samples_mixed) {
break;
@@ -138,7 +138,7 @@ void AudioStreamPlaybackMP3::seek(double p_time) {
}
frames_mixed = uint32_t(mp3_stream->sample_rate * p_time);
- mp3dec_ex_seek(mp3d, (uint64_t)frames_mixed * mp3_stream->channels);
+ mp3dec_ex_seek(&mp3d, (uint64_t)frames_mixed * mp3_stream->channels);
}
void AudioStreamPlaybackMP3::tag_used_streams() {
@@ -181,10 +181,7 @@ Variant AudioStreamPlaybackMP3::get_parameter(const StringName &p_name) const {
}
AudioStreamPlaybackMP3::~AudioStreamPlaybackMP3() {
- if (mp3d) {
- mp3dec_ex_close(mp3d);
- memfree(mp3d);
- }
+ mp3dec_ex_close(&mp3d);
}
Ref<AudioStreamPlayback> AudioStreamMP3::instantiate_playback() {
@@ -197,9 +194,8 @@ Ref<AudioStreamPlayback> AudioStreamMP3::instantiate_playback() {
mp3s.instantiate();
mp3s->mp3_stream = Ref<AudioStreamMP3>(this);
- mp3s->mp3d = (mp3dec_ex_t *)memalloc(sizeof(mp3dec_ex_t));
- int errorcode = mp3dec_ex_open_buf(mp3s->mp3d, data.ptr(), data_len, MP3D_SEEK_TO_SAMPLE);
+ int errorcode = mp3dec_ex_open_buf(&mp3s->mp3d, data.ptr(), data_len, MP3D_SEEK_TO_SAMPLE);
mp3s->frames_mixed = 0;
mp3s->active = false;
@@ -224,15 +220,19 @@ void AudioStreamMP3::set_data(const Vector<uint8_t> &p_data) {
int src_data_len = p_data.size();
const uint8_t *src_datar = p_data.ptr();
- mp3dec_ex_t mp3d;
- int err = mp3dec_ex_open_buf(&mp3d, src_datar, src_data_len, MP3D_SEEK_TO_SAMPLE);
- ERR_FAIL_COND_MSG(err || mp3d.info.hz == 0, "Failed to decode mp3 file. Make sure it is a valid mp3 audio file.");
+ mp3dec_ex_t *mp3d = memnew(mp3dec_ex_t);
+ int err = mp3dec_ex_open_buf(mp3d, src_datar, src_data_len, MP3D_SEEK_TO_SAMPLE);
+ if (err || mp3d->info.hz == 0) {
+ memdelete(mp3d);
+ ERR_FAIL_MSG("Failed to decode mp3 file. Make sure it is a valid mp3 audio file.");
+ }
- channels = mp3d.info.channels;
- sample_rate = mp3d.info.hz;
- length = float(mp3d.samples) / (sample_rate * float(channels));
+ channels = mp3d->info.channels;
+ sample_rate = mp3d->info.hz;
+ length = float(mp3d->samples) / (sample_rate * float(channels));
- mp3dec_ex_close(&mp3d);
+ mp3dec_ex_close(mp3d);
+ memdelete(mp3d);
clear_data();
diff --git a/modules/minimp3/audio_stream_mp3.h b/modules/minimp3/audio_stream_mp3.h
index 81e8f8633c..39d389b8cd 100644
--- a/modules/minimp3/audio_stream_mp3.h
+++ b/modules/minimp3/audio_stream_mp3.h
@@ -49,7 +49,7 @@ class AudioStreamPlaybackMP3 : public AudioStreamPlaybackResampled {
bool looping_override = false;
bool looping = false;
- mp3dec_ex_t *mp3d = nullptr;
+ mp3dec_ex_t mp3d = {};
uint32_t frames_mixed = 0;
bool active = false;
int loops = 0;
diff --git a/modules/mono/build_scripts/build_assemblies.py b/modules/mono/build_scripts/build_assemblies.py
index efbf298f99..9f88b0575e 100755
--- a/modules/mono/build_scripts/build_assemblies.py
+++ b/modules/mono/build_scripts/build_assemblies.py
@@ -194,7 +194,7 @@ def run_msbuild(tools: ToolsLocation, sln: str, chdir_to: str, msbuild_args: Opt
return subprocess.call(args, env=msbuild_env, cwd=chdir_to)
-def build_godot_api(msbuild_tool, module_dir, output_dir, push_nupkgs_local, precision):
+def build_godot_api(msbuild_tool, module_dir, output_dir, push_nupkgs_local, precision, no_deprecated):
target_filenames = [
"GodotSharp.dll",
"GodotSharp.pdb",
@@ -217,6 +217,8 @@ def build_godot_api(msbuild_tool, module_dir, output_dir, push_nupkgs_local, pre
args += ["/p:ClearNuGetLocalCache=true", "/p:PushNuGetToLocalSource=" + push_nupkgs_local]
if precision == "double":
args += ["/p:GodotFloat64=true"]
+ if no_deprecated:
+ args += ["/p:GodotNoDeprecated=true"]
sln = os.path.join(module_dir, "glue/GodotSharp/GodotSharp.sln")
exit_code = run_msbuild(msbuild_tool, sln=sln, chdir_to=module_dir, msbuild_args=args)
@@ -336,12 +338,14 @@ def generate_sdk_package_versions():
f.write(constants)
-def build_all(msbuild_tool, module_dir, output_dir, godot_platform, dev_debug, push_nupkgs_local, precision):
+def build_all(
+ msbuild_tool, module_dir, output_dir, godot_platform, dev_debug, push_nupkgs_local, precision, no_deprecated
+):
# Generate SdkPackageVersions.props and VersionDocsUrl constant
generate_sdk_package_versions()
# Godot API
- exit_code = build_godot_api(msbuild_tool, module_dir, output_dir, push_nupkgs_local, precision)
+ exit_code = build_godot_api(msbuild_tool, module_dir, output_dir, push_nupkgs_local, precision, no_deprecated)
if exit_code != 0:
return exit_code
@@ -364,6 +368,8 @@ def build_all(msbuild_tool, module_dir, output_dir, godot_platform, dev_debug, p
args += ["/p:ClearNuGetLocalCache=true", "/p:PushNuGetToLocalSource=" + push_nupkgs_local]
if precision == "double":
args += ["/p:GodotFloat64=true"]
+ if no_deprecated:
+ args += ["/p:GodotNoDeprecated=true"]
sln = os.path.join(module_dir, "editor/Godot.NET.Sdk/Godot.NET.Sdk.sln")
exit_code = run_msbuild(msbuild_tool, sln=sln, chdir_to=module_dir, msbuild_args=args)
if exit_code != 0:
@@ -390,6 +396,12 @@ def main():
parser.add_argument(
"--precision", type=str, default="single", choices=["single", "double"], help="Floating-point precision level"
)
+ parser.add_argument(
+ "--no-deprecated",
+ action="store_true",
+ default=False,
+ help="Build GodotSharp without using deprecated features. This is required, if the engine was built with 'deprecated=no'.",
+ )
args = parser.parse_args()
@@ -414,6 +426,7 @@ def main():
args.dev_debug,
push_nupkgs_local,
args.precision,
+ args.no_deprecated,
)
sys.exit(exit_code)
diff --git a/modules/mono/csharp_script.cpp b/modules/mono/csharp_script.cpp
index 6d561c1566..177859f270 100644
--- a/modules/mono/csharp_script.cpp
+++ b/modules/mono/csharp_script.cpp
@@ -2796,7 +2796,7 @@ Ref<Resource> ResourceFormatLoaderCSharpScript::load(const String &p_path, const
if (GDMonoCache::godot_api_cache_updated) {
GDMonoCache::managed_callbacks.ScriptManagerBridge_GetOrCreateScriptBridgeForPath(&p_path, &scr);
- ERR_FAIL_NULL_V_MSG(scr, Ref<Resource>(), "Could not create C# script '" + real_path + "'.");
+ ERR_FAIL_COND_V_MSG(scr.is_null(), Ref<Resource>(), "Could not create C# script '" + real_path + "'.");
} else {
scr = Ref<CSharpScript>(memnew(CSharpScript));
}
diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/GeneratedSources/ExportedFields_ScriptProperties.generated.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/GeneratedSources/ExportedFields_ScriptProperties.generated.cs
index 6a3884dabf..c734dc7be1 100644
--- a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/GeneratedSources/ExportedFields_ScriptProperties.generated.cs
+++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/GeneratedSources/ExportedFields_ScriptProperties.generated.cs
@@ -807,7 +807,7 @@ partial class ExportedFields
properties.Add(new(type: (global::Godot.Variant.Type)23, name: PropertyName.@_fieldRid, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true));
properties.Add(new(type: (global::Godot.Variant.Type)27, name: PropertyName.@_fieldGodotDictionary, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true));
properties.Add(new(type: (global::Godot.Variant.Type)28, name: PropertyName.@_fieldGodotArray, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true));
- properties.Add(new(type: (global::Godot.Variant.Type)27, name: PropertyName.@_fieldGodotGenericDictionary, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true));
+ properties.Add(new(type: (global::Godot.Variant.Type)27, name: PropertyName.@_fieldGodotGenericDictionary, hint: (global::Godot.PropertyHint)38, hintString: "4/0:;1/0:", usage: (global::Godot.PropertyUsageFlags)4102, exported: true));
properties.Add(new(type: (global::Godot.Variant.Type)28, name: PropertyName.@_fieldGodotGenericArray, hint: (global::Godot.PropertyHint)23, hintString: "2/0:", usage: (global::Godot.PropertyUsageFlags)4102, exported: true));
properties.Add(new(type: (global::Godot.Variant.Type)31, name: PropertyName.@_fieldEmptyInt64Array, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true));
return properties;
diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/GeneratedSources/ExportedProperties_ScriptProperties.generated.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/GeneratedSources/ExportedProperties_ScriptProperties.generated.cs
index 3c48740773..0de840aa34 100644
--- a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/GeneratedSources/ExportedProperties_ScriptProperties.generated.cs
+++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/GeneratedSources/ExportedProperties_ScriptProperties.generated.cs
@@ -925,7 +925,7 @@ partial class ExportedProperties
properties.Add(new(type: (global::Godot.Variant.Type)23, name: PropertyName.@PropertyRid, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true));
properties.Add(new(type: (global::Godot.Variant.Type)27, name: PropertyName.@PropertyGodotDictionary, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true));
properties.Add(new(type: (global::Godot.Variant.Type)28, name: PropertyName.@PropertyGodotArray, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true));
- properties.Add(new(type: (global::Godot.Variant.Type)27, name: PropertyName.@PropertyGodotGenericDictionary, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true));
+ properties.Add(new(type: (global::Godot.Variant.Type)27, name: PropertyName.@PropertyGodotGenericDictionary, hint: (global::Godot.PropertyHint)38, hintString: "4/0:;1/0:", usage: (global::Godot.PropertyUsageFlags)4102, exported: true));
properties.Add(new(type: (global::Godot.Variant.Type)28, name: PropertyName.@PropertyGodotGenericArray, hint: (global::Godot.PropertyHint)23, hintString: "2/0:", usage: (global::Godot.PropertyUsageFlags)4102, exported: true));
return properties;
}
diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/GodotEnums.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/GodotEnums.cs
index 4cf6a9f431..bb4c4824e7 100644
--- a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/GodotEnums.cs
+++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/GodotEnums.cs
@@ -88,7 +88,8 @@ namespace Godot.SourceGenerators
HideQuaternionEdit = 35,
Password = 36,
LayersAvoidance = 37,
- Max = 38
+ DictionaryType = 38,
+ Max = 39
}
[Flags]
diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/MarshalUtils.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/MarshalUtils.cs
index d272832950..f5f51722b4 100644
--- a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/MarshalUtils.cs
+++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/MarshalUtils.cs
@@ -274,6 +274,14 @@ namespace Godot.SourceGenerators
return null;
}
+ public static ITypeSymbol[]? GetGenericElementTypes(ITypeSymbol typeSymbol)
+ {
+ if (typeSymbol is INamedTypeSymbol { IsGenericType: true } genericType)
+ return genericType.TypeArguments.ToArray();
+
+ return null;
+ }
+
private static StringBuilder Append(this StringBuilder source, string a, string b)
=> source.Append(a).Append(b);
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 29bae6413d..0f86b3b91c 100644
--- a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptPropertiesGenerator.cs
+++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptPropertiesGenerator.cs
@@ -1,4 +1,5 @@
using System.Collections.Generic;
+using System.Diagnostics;
using System.Linq;
using System.Text;
using Microsoft.CodeAnalysis;
@@ -728,8 +729,72 @@ namespace Godot.SourceGenerators
if (!isTypeArgument && variantType == VariantType.Dictionary)
{
- // TODO: Dictionaries are not supported in the inspector
- return false;
+ var elementTypes = MarshalUtils.GetGenericElementTypes(type);
+
+ if (elementTypes == null)
+ return false; // Non-generic Dictionary, so there's no hint to add
+ Debug.Assert(elementTypes.Length == 2);
+
+ var keyElementMarshalType = MarshalUtils.ConvertManagedTypeToMarshalType(elementTypes[0], typeCache)!.Value;
+ var keyElementVariantType = MarshalUtils.ConvertMarshalTypeToVariantType(keyElementMarshalType)!.Value;
+ var keyIsPresetHint = false;
+ var keyHintString = (string?)null;
+
+ if (keyElementVariantType == VariantType.String || keyElementVariantType == VariantType.StringName)
+ keyIsPresetHint = GetStringArrayEnumHint(keyElementVariantType, exportAttr, out keyHintString);
+
+ if (!keyIsPresetHint)
+ {
+ bool hintRes = TryGetMemberExportHint(typeCache, elementTypes[0],
+ exportAttr, keyElementVariantType, isTypeArgument: true,
+ out var keyElementHint, out var keyElementHintString);
+
+ // Format: type/hint:hint_string
+ if (hintRes)
+ {
+ keyHintString = (int)keyElementVariantType + "/" + (int)keyElementHint + ":";
+
+ if (keyElementHintString != null)
+ keyHintString += keyElementHintString;
+ }
+ else
+ {
+ keyHintString = (int)keyElementVariantType + "/" + (int)PropertyHint.None + ":";
+ }
+ }
+
+ var valueElementMarshalType = MarshalUtils.ConvertManagedTypeToMarshalType(elementTypes[1], typeCache)!.Value;
+ var valueElementVariantType = MarshalUtils.ConvertMarshalTypeToVariantType(valueElementMarshalType)!.Value;
+ var valueIsPresetHint = false;
+ var valueHintString = (string?)null;
+
+ if (valueElementVariantType == VariantType.String || valueElementVariantType == VariantType.StringName)
+ valueIsPresetHint = GetStringArrayEnumHint(valueElementVariantType, exportAttr, out valueHintString);
+
+ if (!valueIsPresetHint)
+ {
+ bool hintRes = TryGetMemberExportHint(typeCache, elementTypes[1],
+ exportAttr, valueElementVariantType, isTypeArgument: true,
+ out var valueElementHint, out var valueElementHintString);
+
+ // Format: type/hint:hint_string
+ if (hintRes)
+ {
+ valueHintString = (int)valueElementVariantType + "/" + (int)valueElementHint + ":";
+
+ if (valueElementHintString != null)
+ valueHintString += valueElementHintString;
+ }
+ else
+ {
+ valueHintString = (int)valueElementVariantType + "/" + (int)PropertyHint.None + ":";
+ }
+ }
+
+ hint = PropertyHint.DictionaryType;
+
+ hintString = keyHintString != null && valueHintString != null ? $"{keyHintString};{valueHintString}" : null;
+ return hintString != null;
}
return false;
diff --git a/modules/mono/editor/GodotTools/GodotTools.BuildLogger/GodotBuildLogger.cs b/modules/mono/editor/GodotTools/GodotTools.BuildLogger/GodotBuildLogger.cs
index b699765b8e..032d067ae4 100644
--- a/modules/mono/editor/GodotTools/GodotTools.BuildLogger/GodotBuildLogger.cs
+++ b/modules/mono/editor/GodotTools/GodotTools.BuildLogger/GodotBuildLogger.cs
@@ -86,7 +86,7 @@ namespace GodotTools.BuildLogger
WriteLine(line);
- string errorLine = $@"error,{e.File.CsvEscape()},{e.LineNumber},{e.ColumnNumber}," +
+ string errorLine = $@"error,{e.File?.CsvEscape() ?? string.Empty},{e.LineNumber},{e.ColumnNumber}," +
$"{e.Code?.CsvEscape() ?? string.Empty},{e.Message.CsvEscape()}," +
$"{e.ProjectFile?.CsvEscape() ?? string.Empty}";
_issuesStreamWriter.WriteLine(errorLine);
@@ -101,7 +101,7 @@ namespace GodotTools.BuildLogger
WriteLine(line);
- string warningLine = $@"warning,{e.File.CsvEscape()},{e.LineNumber},{e.ColumnNumber}," +
+ string warningLine = $@"warning,{e.File?.CsvEscape() ?? string.Empty},{e.LineNumber},{e.ColumnNumber}," +
$"{e.Code?.CsvEscape() ?? string.Empty},{e.Message.CsvEscape()}," +
$"{e.ProjectFile?.CsvEscape() ?? string.Empty}";
_issuesStreamWriter.WriteLine(warningLine);
diff --git a/modules/mono/editor/GodotTools/GodotTools/Build/BuildManager.cs b/modules/mono/editor/GodotTools/GodotTools/Build/BuildManager.cs
index ebb2677361..d7877fa5fc 100644
--- a/modules/mono/editor/GodotTools/GodotTools/Build/BuildManager.cs
+++ b/modules/mono/editor/GodotTools/GodotTools/Build/BuildManager.cs
@@ -265,11 +265,6 @@ namespace GodotTools.Build
success = Publish(buildInfo);
}
- if (!success)
- {
- ShowBuildErrorDialog("Failed to publish .NET project");
- }
-
return success;
}
diff --git a/modules/mono/editor/GodotTools/GodotTools/Export/ExportPlugin.cs b/modules/mono/editor/GodotTools/GodotTools/Export/ExportPlugin.cs
index ede0600ac1..a5f24fb67b 100644
--- a/modules/mono/editor/GodotTools/GodotTools/Export/ExportPlugin.cs
+++ b/modules/mono/editor/GodotTools/GodotTools/Export/ExportPlugin.cs
@@ -75,7 +75,19 @@ namespace GodotTools.Export
};
}
- private string? _maybeLastExportError;
+ private void AddExceptionMessage(EditorExportPlatform platform, Exception exception)
+ {
+ string? exceptionMessage = exception.Message;
+ if (string.IsNullOrEmpty(exceptionMessage))
+ {
+ exceptionMessage = $"Exception thrown: {exception.GetType().Name}";
+ }
+
+ platform.AddMessage(EditorExportPlatform.ExportMessageType.Error, "Export .NET Project", exceptionMessage);
+
+ // We also print exceptions as we receive them to stderr.
+ Console.Error.WriteLine(exception);
+ }
// With this method we can override how a file is exported in the PCK
public override void _ExportFile(string path, string type, string[] features)
@@ -92,8 +104,8 @@ namespace GodotTools.Export
if (!ProjectContainsDotNet())
{
- _maybeLastExportError = $"This project contains C# files but no solution file was found at the following path: {GodotSharpDirs.ProjectSlnPath}\n" +
- "A solution file is required for projects with C# files. Please ensure that the solution file exists in the specified location and try again.";
+ GetExportPlatform().AddMessage(EditorExportPlatform.ExportMessageType.Error, "Export .NET Project", $"This project contains C# files but no solution file was found at the following path: {GodotSharpDirs.ProjectSlnPath}\n" +
+ "A solution file is required for projects with C# files. Please ensure that the solution file exists in the specified location and try again.");
throw new InvalidOperationException($"{path} is a C# file but no solution file exists.");
}
@@ -124,16 +136,7 @@ namespace GodotTools.Export
}
catch (Exception e)
{
- _maybeLastExportError = e.Message;
-
- // 'maybeLastExportError' cannot be null or empty if there was an error, so we
- // must consider the possibility of exceptions being thrown without a message.
- if (string.IsNullOrEmpty(_maybeLastExportError))
- _maybeLastExportError = $"Exception thrown: {e.GetType().Name}";
-
- GD.PushError($"Failed to export project: {_maybeLastExportError}");
- Console.Error.WriteLine(e);
- // TODO: Do something on error once _ExportBegin supports failing.
+ AddExceptionMessage(GetExportPlatform(), e);
}
}
@@ -144,7 +147,9 @@ namespace GodotTools.Export
if (!ProjectContainsDotNet())
return;
- if (!DeterminePlatformFromFeatures(features, out string? platform))
+ string osName = GetExportPlatform().GetOsName();
+
+ if (!TryDeterminePlatformFromOSName(osName, out string? platform))
throw new NotSupportedException("Target platform not supported.");
if (!new[] { OS.Platforms.Windows, OS.Platforms.LinuxBSD, OS.Platforms.MacOS, OS.Platforms.Android, OS.Platforms.iOS }
@@ -445,25 +450,22 @@ namespace GodotTools.Export
Directory.Delete(folder, recursive: true);
}
_tempFolders.Clear();
-
- // TODO: The following is just a workaround until the export plugins can be made to abort with errors
-
- // We check for empty as well, because it's set to empty after hot-reloading
- if (!string.IsNullOrEmpty(_maybeLastExportError))
- {
- string lastExportError = _maybeLastExportError;
- _maybeLastExportError = null;
-
- GodotSharpEditor.Instance.ShowErrorDialog(lastExportError, "Failed to export C# project");
- }
}
- private static bool DeterminePlatformFromFeatures(IEnumerable<string> features, [NotNullWhen(true)] out string? platform)
+ /// <summary>
+ /// Tries to determine the platform from the export preset's platform OS name.
+ /// </summary>
+ /// <param name="osName">Name of the export operating system.</param>
+ /// <param name="platform">Platform name for the recognized supported platform.</param>
+ /// <returns>
+ /// <see langword="true"/> when the platform OS name is recognized as a supported platform,
+ /// <see langword="false"/> otherwise.
+ /// </returns>
+ private static bool TryDeterminePlatformFromOSName(string osName, [NotNullWhen(true)] out string? platform)
{
- foreach (var feature in features)
+ if (OS.PlatformFeatureMap.TryGetValue(osName, out platform))
{
- if (OS.PlatformFeatureMap.TryGetValue(feature, out platform))
- return true;
+ return true;
}
platform = null;
diff --git a/modules/mono/editor/bindings_generator.cpp b/modules/mono/editor/bindings_generator.cpp
index b26f6d1bbf..2ec073e4fa 100644
--- a/modules/mono/editor/bindings_generator.cpp
+++ b/modules/mono/editor/bindings_generator.cpp
@@ -3809,6 +3809,11 @@ bool BindingsGenerator::_populate_object_type_interfaces() {
} else if (return_info.type == Variant::ARRAY && return_info.hint == PROPERTY_HINT_ARRAY_TYPE) {
imethod.return_type.cname = Variant::get_type_name(return_info.type) + "_@generic";
imethod.return_type.generic_type_parameters.push_back(TypeReference(return_info.hint_string));
+ } else if (return_info.type == Variant::DICTIONARY && return_info.hint == PROPERTY_HINT_DICTIONARY_TYPE) {
+ imethod.return_type.cname = Variant::get_type_name(return_info.type) + "_@generic";
+ Vector<String> split = return_info.hint_string.split(";");
+ imethod.return_type.generic_type_parameters.push_back(TypeReference(split.get(0)));
+ imethod.return_type.generic_type_parameters.push_back(TypeReference(split.get(1)));
} else if (return_info.hint == PROPERTY_HINT_RESOURCE_TYPE) {
imethod.return_type.cname = return_info.hint_string;
} else if (return_info.type == Variant::NIL && return_info.usage & PROPERTY_USAGE_NIL_IS_VARIANT) {
@@ -3836,6 +3841,11 @@ bool BindingsGenerator::_populate_object_type_interfaces() {
} else if (arginfo.type == Variant::ARRAY && arginfo.hint == PROPERTY_HINT_ARRAY_TYPE) {
iarg.type.cname = Variant::get_type_name(arginfo.type) + "_@generic";
iarg.type.generic_type_parameters.push_back(TypeReference(arginfo.hint_string));
+ } else if (arginfo.type == Variant::DICTIONARY && arginfo.hint == PROPERTY_HINT_DICTIONARY_TYPE) {
+ iarg.type.cname = Variant::get_type_name(arginfo.type) + "_@generic";
+ Vector<String> split = arginfo.hint_string.split(";");
+ iarg.type.generic_type_parameters.push_back(TypeReference(split.get(0)));
+ iarg.type.generic_type_parameters.push_back(TypeReference(split.get(1)));
} else if (arginfo.hint == PROPERTY_HINT_RESOURCE_TYPE) {
iarg.type.cname = arginfo.hint_string;
} else if (arginfo.type == Variant::NIL) {
@@ -3963,6 +3973,11 @@ bool BindingsGenerator::_populate_object_type_interfaces() {
} else if (arginfo.type == Variant::ARRAY && arginfo.hint == PROPERTY_HINT_ARRAY_TYPE) {
iarg.type.cname = Variant::get_type_name(arginfo.type) + "_@generic";
iarg.type.generic_type_parameters.push_back(TypeReference(arginfo.hint_string));
+ } else if (arginfo.type == Variant::DICTIONARY && arginfo.hint == PROPERTY_HINT_DICTIONARY_TYPE) {
+ iarg.type.cname = Variant::get_type_name(arginfo.type) + "_@generic";
+ Vector<String> split = arginfo.hint_string.split(";");
+ iarg.type.generic_type_parameters.push_back(TypeReference(split.get(0)));
+ iarg.type.generic_type_parameters.push_back(TypeReference(split.get(1)));
} else if (arginfo.hint == PROPERTY_HINT_RESOURCE_TYPE) {
iarg.type.cname = arginfo.hint_string;
} else if (arginfo.type == Variant::NIL) {
diff --git a/modules/mono/editor/hostfxr_resolver.cpp b/modules/mono/editor/hostfxr_resolver.cpp
index 7fa482969e..9c37ac810a 100644
--- a/modules/mono/editor/hostfxr_resolver.cpp
+++ b/modules/mono/editor/hostfxr_resolver.cpp
@@ -216,6 +216,7 @@ bool get_default_installation_dir(String &r_dotnet_root) {
#endif
}
+#ifndef WINDOWS_ENABLED
bool get_install_location_from_file(const String &p_file_path, String &r_dotnet_root) {
Error err = OK;
Ref<FileAccess> f = FileAccess::open(p_file_path, FileAccess::READ, &err);
@@ -233,6 +234,7 @@ bool get_install_location_from_file(const String &p_file_path, String &r_dotnet_
r_dotnet_root = line;
return true;
}
+#endif
bool get_dotnet_self_registered_dir(String &r_dotnet_root) {
#if defined(WINDOWS_ENABLED)
@@ -260,7 +262,7 @@ bool get_dotnet_self_registered_dir(String &r_dotnet_root) {
return false;
}
- r_dotnet_root = String::utf16((const char16_t *)buffer.ptr());
+ r_dotnet_root = String::utf16((const char16_t *)buffer.ptr()).replace("\\", "/");
RegCloseKey(hkey);
return true;
#else
diff --git a/modules/mono/glue/GodotSharp/GodotSharp/GodotSharp.csproj b/modules/mono/glue/GodotSharp/GodotSharp/GodotSharp.csproj
index b838f8eac7..3a3134d160 100644
--- a/modules/mono/glue/GodotSharp/GodotSharp/GodotSharp.csproj
+++ b/modules/mono/glue/GodotSharp/GodotSharp/GodotSharp.csproj
@@ -135,7 +135,7 @@
<Compile Include="Properties\AssemblyInfo.cs" />
</ItemGroup>
<!-- Compat Sources -->
- <ItemGroup>
+ <ItemGroup Condition=" '$(GodotNoDeprecated)' == '' ">
<Compile Include="Compat.cs" />
</ItemGroup>
<!--
diff --git a/modules/mono/glue/GodotSharp/GodotSharpEditor/GodotSharpEditor.csproj b/modules/mono/glue/GodotSharp/GodotSharpEditor/GodotSharpEditor.csproj
index 65b4824f94..715c1a4d51 100644
--- a/modules/mono/glue/GodotSharp/GodotSharpEditor/GodotSharpEditor.csproj
+++ b/modules/mono/glue/GodotSharp/GodotSharpEditor/GodotSharpEditor.csproj
@@ -36,7 +36,7 @@
</ProjectReference>
</ItemGroup>
<!-- Compat Sources -->
- <ItemGroup>
+ <ItemGroup Condition=" '$(GodotNoDeprecated)' == '' ">
<Compile Include="Compat.cs" />
</ItemGroup>
<!--
diff --git a/modules/mono/godotsharp_dirs.cpp b/modules/mono/godotsharp_dirs.cpp
index 80e44011be..3935854a29 100644
--- a/modules/mono/godotsharp_dirs.cpp
+++ b/modules/mono/godotsharp_dirs.cpp
@@ -194,7 +194,7 @@ private:
if (!has_data) {
// 3. Extract the data to a temporary location to load from there.
Ref<DirAccess> da = DirAccess::create_for_path(packed_path);
- ERR_FAIL_NULL(da);
+ ERR_FAIL_COND(da.is_null());
ERR_FAIL_COND(da->copy_dir(packed_path, data_dir_root) != OK);
}
api_assemblies_dir = data_dir_root;
diff --git a/modules/multiplayer/editor/editor_network_profiler.cpp b/modules/multiplayer/editor/editor_network_profiler.cpp
index d5d4b465d8..212fd1ef6b 100644
--- a/modules/multiplayer/editor/editor_network_profiler.cpp
+++ b/modules/multiplayer/editor/editor_network_profiler.cpp
@@ -227,10 +227,10 @@ void EditorNetworkProfiler::add_sync_frame_data(const SyncInfo &p_frame) {
sync_data[p_frame.synchronizer].outgoing_syncs += p_frame.outgoing_syncs;
}
SyncInfo &info = sync_data[p_frame.synchronizer];
- if (info.incoming_syncs) {
+ if (p_frame.incoming_syncs) {
info.incoming_size = p_frame.incoming_size / p_frame.incoming_syncs;
}
- if (info.outgoing_syncs) {
+ if (p_frame.outgoing_syncs) {
info.outgoing_size = p_frame.outgoing_size / p_frame.outgoing_syncs;
}
}
diff --git a/modules/multiplayer/scene_rpc_interface.cpp b/modules/multiplayer/scene_rpc_interface.cpp
index 1463598ddc..592bb18a71 100644
--- a/modules/multiplayer/scene_rpc_interface.cpp
+++ b/modules/multiplayer/scene_rpc_interface.cpp
@@ -82,7 +82,7 @@ void SceneRPCInterface::_parse_rpc_config(const Variant &p_config, bool p_for_no
Array names = config.keys();
names.sort(); // Ensure ID order
for (int i = 0; i < names.size(); i++) {
- ERR_CONTINUE(names[i].get_type() != Variant::STRING && names[i].get_type() != Variant::STRING_NAME);
+ ERR_CONTINUE(!names[i].is_string());
String name = names[i].operator String();
ERR_CONTINUE(config[name].get_type() != Variant::DICTIONARY);
ERR_CONTINUE(!config[name].operator Dictionary().has("rpc_mode"));
diff --git a/modules/navigation/2d/nav_mesh_generator_2d.cpp b/modules/navigation/2d/nav_mesh_generator_2d.cpp
index 33b92f6266..78983187c7 100644
--- a/modules/navigation/2d/nav_mesh_generator_2d.cpp
+++ b/modules/navigation/2d/nav_mesh_generator_2d.cpp
@@ -87,57 +87,55 @@ void NavMeshGenerator2D::sync() {
return;
}
- baking_navmesh_mutex.lock();
- generator_task_mutex.lock();
+ MutexLock baking_navmesh_lock(baking_navmesh_mutex);
+ {
+ MutexLock generator_task_lock(generator_task_mutex);
- LocalVector<WorkerThreadPool::TaskID> finished_task_ids;
+ 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);
+ 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);
+ 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);
+ baking_navmeshes.erase(generator_task->navigation_mesh);
+ if (generator_task->callback.is_valid()) {
+ generator_emit_callback(generator_task->callback);
+ }
+ memdelete(generator_task);
}
- memdelete(generator_task);
}
- }
- for (WorkerThreadPool::TaskID finished_task_id : finished_task_ids) {
- generator_tasks.erase(finished_task_id);
+ 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();
+ MutexLock baking_navmesh_lock(baking_navmesh_mutex);
+ {
+ MutexLock generator_task_lock(generator_task_mutex);
- baking_navmeshes.clear();
+ 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();
+ 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_rid_rwlock.write_lock();
- for (NavMeshGeometryParser2D *parser : generator_parsers) {
- generator_parser_owner.free(parser->self);
+ generator_rid_rwlock.write_lock();
+ for (NavMeshGeometryParser2D *parser : generator_parsers) {
+ generator_parser_owner.free(parser->self);
+ }
+ generator_parsers.clear();
+ generator_rid_rwlock.write_unlock();
}
- generator_parsers.clear();
- generator_rid_rwlock.write_unlock();
-
- generator_task_mutex.unlock();
- baking_navmesh_mutex.unlock();
}
void NavMeshGenerator2D::finish() {
@@ -212,7 +210,7 @@ void NavMeshGenerator2D::bake_from_source_geometry_data_async(Ref<NavigationPoly
baking_navmeshes.insert(p_navigation_mesh);
baking_navmesh_mutex.unlock();
- generator_task_mutex.lock();
+ MutexLock generator_task_lock(generator_task_mutex);
NavMeshGeneratorTask2D *generator_task = memnew(NavMeshGeneratorTask2D);
generator_task->navigation_mesh = p_navigation_mesh;
generator_task->source_geometry_data = p_source_geometry_data;
@@ -220,14 +218,11 @@ void NavMeshGenerator2D::bake_from_source_geometry_data_async(Ref<NavigationPoly
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();
}
bool NavMeshGenerator2D::is_baking(Ref<NavigationPolygon> p_navigation_polygon) {
- baking_navmesh_mutex.lock();
- bool baking = baking_navmeshes.has(p_navigation_polygon);
- baking_navmesh_mutex.unlock();
- return baking;
+ MutexLock baking_navmesh_lock(baking_navmesh_mutex);
+ return baking_navmeshes.has(p_navigation_polygon);
}
void NavMeshGenerator2D::generator_thread_bake(void *p_arg) {
diff --git a/modules/navigation/3d/nav_mesh_generator_3d.cpp b/modules/navigation/3d/nav_mesh_generator_3d.cpp
index d17724baa0..e92a9d304b 100644
--- a/modules/navigation/3d/nav_mesh_generator_3d.cpp
+++ b/modules/navigation/3d/nav_mesh_generator_3d.cpp
@@ -100,57 +100,55 @@ void NavMeshGenerator3D::sync() {
return;
}
- baking_navmesh_mutex.lock();
- generator_task_mutex.lock();
+ MutexLock baking_navmesh_lock(baking_navmesh_mutex);
+ {
+ MutexLock generator_task_lock(generator_task_mutex);
- LocalVector<WorkerThreadPool::TaskID> finished_task_ids;
+ 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);
+ 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);
+ 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);
+ baking_navmeshes.erase(generator_task->navigation_mesh);
+ if (generator_task->callback.is_valid()) {
+ generator_emit_callback(generator_task->callback);
+ }
+ memdelete(generator_task);
}
- memdelete(generator_task);
}
- }
- for (WorkerThreadPool::TaskID finished_task_id : finished_task_ids) {
- generator_tasks.erase(finished_task_id);
+ 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();
+ MutexLock baking_navmesh_lock(baking_navmesh_mutex);
+ {
+ MutexLock generator_task_lock(generator_task_mutex);
- baking_navmeshes.clear();
+ 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();
+ 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_rid_rwlock.write_lock();
- for (NavMeshGeometryParser3D *parser : generator_parsers) {
- generator_parser_owner.free(parser->self);
+ generator_rid_rwlock.write_lock();
+ for (NavMeshGeometryParser3D *parser : generator_parsers) {
+ generator_parser_owner.free(parser->self);
+ }
+ generator_parsers.clear();
+ generator_rid_rwlock.write_unlock();
}
- generator_parsers.clear();
- generator_rid_rwlock.write_unlock();
-
- generator_task_mutex.unlock();
- baking_navmesh_mutex.unlock();
}
void NavMeshGenerator3D::finish() {
@@ -226,7 +224,7 @@ void NavMeshGenerator3D::bake_from_source_geometry_data_async(Ref<NavigationMesh
baking_navmeshes.insert(p_navigation_mesh);
baking_navmesh_mutex.unlock();
- generator_task_mutex.lock();
+ MutexLock generator_task_lock(generator_task_mutex);
NavMeshGeneratorTask3D *generator_task = memnew(NavMeshGeneratorTask3D);
generator_task->navigation_mesh = p_navigation_mesh;
generator_task->source_geometry_data = p_source_geometry_data;
@@ -234,14 +232,11 @@ void NavMeshGenerator3D::bake_from_source_geometry_data_async(Ref<NavigationMesh
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();
}
bool NavMeshGenerator3D::is_baking(Ref<NavigationMesh> p_navigation_mesh) {
- baking_navmesh_mutex.lock();
- bool baking = baking_navmeshes.has(p_navigation_mesh);
- baking_navmesh_mutex.unlock();
- return baking;
+ MutexLock baking_navmesh_lock(baking_navmesh_mutex);
+ return baking_navmeshes.has(p_navigation_mesh);
}
void NavMeshGenerator3D::generator_thread_bake(void *p_arg) {
diff --git a/modules/navigation/3d/nav_mesh_queries_3d.cpp b/modules/navigation/3d/nav_mesh_queries_3d.cpp
new file mode 100644
index 0000000000..70207f86ce
--- /dev/null
+++ b/modules/navigation/3d/nav_mesh_queries_3d.cpp
@@ -0,0 +1,715 @@
+/**************************************************************************/
+/* nav_mesh_queries_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_queries_3d.h"
+
+#include "../nav_base.h"
+
+#include "core/math/geometry_3d.h"
+
+#define THREE_POINTS_CROSS_PRODUCT(m_a, m_b, m_c) (((m_c) - (m_a)).cross((m_b) - (m_a)))
+
+#define APPEND_METADATA(poly) \
+ if (r_path_types) { \
+ r_path_types->push_back(poly->owner->get_type()); \
+ } \
+ if (r_path_rids) { \
+ r_path_rids->push_back(poly->owner->get_self()); \
+ } \
+ if (r_path_owners) { \
+ r_path_owners->push_back(poly->owner->get_owner_id()); \
+ }
+
+Vector3 NavMeshQueries3D::polygons_get_random_point(const LocalVector<gd::Polygon> &p_polygons, uint32_t p_navigation_layers, bool p_uniformly) {
+ const LocalVector<gd::Polygon> &region_polygons = p_polygons;
+
+ if (region_polygons.is_empty()) {
+ return Vector3();
+ }
+
+ if (p_uniformly) {
+ real_t accumulated_area = 0;
+ RBMap<real_t, uint32_t> region_area_map;
+
+ for (uint32_t rp_index = 0; rp_index < region_polygons.size(); rp_index++) {
+ const gd::Polygon &region_polygon = region_polygons[rp_index];
+ real_t polyon_area = region_polygon.surface_area;
+
+ if (polyon_area == 0.0) {
+ continue;
+ }
+ region_area_map[accumulated_area] = rp_index;
+ accumulated_area += polyon_area;
+ }
+ if (region_area_map.is_empty() || accumulated_area == 0) {
+ // All polygons have no real surface / no area.
+ return Vector3();
+ }
+
+ real_t region_area_map_pos = Math::random(real_t(0), accumulated_area);
+
+ RBMap<real_t, uint32_t>::Iterator region_E = region_area_map.find_closest(region_area_map_pos);
+ ERR_FAIL_COND_V(!region_E, Vector3());
+ uint32_t rrp_polygon_index = region_E->value;
+ ERR_FAIL_UNSIGNED_INDEX_V(rrp_polygon_index, region_polygons.size(), Vector3());
+
+ const gd::Polygon &rr_polygon = region_polygons[rrp_polygon_index];
+
+ real_t accumulated_polygon_area = 0;
+ RBMap<real_t, uint32_t> polygon_area_map;
+
+ for (uint32_t rpp_index = 2; rpp_index < rr_polygon.points.size(); rpp_index++) {
+ real_t face_area = Face3(rr_polygon.points[0].pos, rr_polygon.points[rpp_index - 1].pos, rr_polygon.points[rpp_index].pos).get_area();
+
+ if (face_area == 0.0) {
+ continue;
+ }
+ polygon_area_map[accumulated_polygon_area] = rpp_index;
+ accumulated_polygon_area += face_area;
+ }
+ if (polygon_area_map.is_empty() || accumulated_polygon_area == 0) {
+ // All faces have no real surface / no area.
+ return Vector3();
+ }
+
+ real_t polygon_area_map_pos = Math::random(real_t(0), accumulated_polygon_area);
+
+ RBMap<real_t, uint32_t>::Iterator polygon_E = polygon_area_map.find_closest(polygon_area_map_pos);
+ ERR_FAIL_COND_V(!polygon_E, Vector3());
+ uint32_t rrp_face_index = polygon_E->value;
+ ERR_FAIL_UNSIGNED_INDEX_V(rrp_face_index, rr_polygon.points.size(), Vector3());
+
+ const Face3 face(rr_polygon.points[0].pos, rr_polygon.points[rrp_face_index - 1].pos, rr_polygon.points[rrp_face_index].pos);
+
+ Vector3 face_random_position = face.get_random_point_inside();
+ return face_random_position;
+
+ } else {
+ uint32_t rrp_polygon_index = Math::random(int(0), region_polygons.size() - 1);
+
+ const gd::Polygon &rr_polygon = region_polygons[rrp_polygon_index];
+
+ uint32_t rrp_face_index = Math::random(int(2), rr_polygon.points.size() - 1);
+
+ const Face3 face(rr_polygon.points[0].pos, rr_polygon.points[rrp_face_index - 1].pos, rr_polygon.points[rrp_face_index].pos);
+
+ Vector3 face_random_position = face.get_random_point_inside();
+ return face_random_position;
+ }
+}
+
+Vector<Vector3> NavMeshQueries3D::polygons_get_path(const LocalVector<gd::Polygon> &p_polygons, Vector3 p_origin, Vector3 p_destination, bool p_optimize, uint32_t p_navigation_layers, Vector<int32_t> *r_path_types, TypedArray<RID> *r_path_rids, Vector<int64_t> *r_path_owners, const Vector3 &p_map_up, uint32_t p_link_polygons_size) {
+ // Clear metadata outputs.
+ if (r_path_types) {
+ r_path_types->clear();
+ }
+ if (r_path_rids) {
+ r_path_rids->clear();
+ }
+ if (r_path_owners) {
+ r_path_owners->clear();
+ }
+
+ // Find the start poly and the end poly on this map.
+ const gd::Polygon *begin_poly = nullptr;
+ const gd::Polygon *end_poly = nullptr;
+ Vector3 begin_point;
+ Vector3 end_point;
+ real_t begin_d = FLT_MAX;
+ real_t end_d = FLT_MAX;
+ // Find the initial poly and the end poly on this map.
+ for (const gd::Polygon &p : p_polygons) {
+ // Only consider the polygon if it in a region with compatible layers.
+ if ((p_navigation_layers & p.owner->get_navigation_layers()) == 0) {
+ continue;
+ }
+
+ // For each face check the distance between the origin/destination
+ for (size_t point_id = 2; point_id < p.points.size(); point_id++) {
+ const Face3 face(p.points[0].pos, p.points[point_id - 1].pos, p.points[point_id].pos);
+
+ Vector3 point = face.get_closest_point_to(p_origin);
+ real_t distance_to_point = point.distance_to(p_origin);
+ if (distance_to_point < begin_d) {
+ begin_d = distance_to_point;
+ begin_poly = &p;
+ begin_point = point;
+ }
+
+ point = face.get_closest_point_to(p_destination);
+ distance_to_point = point.distance_to(p_destination);
+ if (distance_to_point < end_d) {
+ end_d = distance_to_point;
+ end_poly = &p;
+ end_point = point;
+ }
+ }
+ }
+
+ // Check for trivial cases
+ if (!begin_poly || !end_poly) {
+ return Vector<Vector3>();
+ }
+ if (begin_poly == end_poly) {
+ if (r_path_types) {
+ r_path_types->resize(2);
+ r_path_types->write[0] = begin_poly->owner->get_type();
+ r_path_types->write[1] = end_poly->owner->get_type();
+ }
+
+ if (r_path_rids) {
+ r_path_rids->resize(2);
+ (*r_path_rids)[0] = begin_poly->owner->get_self();
+ (*r_path_rids)[1] = end_poly->owner->get_self();
+ }
+
+ if (r_path_owners) {
+ r_path_owners->resize(2);
+ r_path_owners->write[0] = begin_poly->owner->get_owner_id();
+ r_path_owners->write[1] = end_poly->owner->get_owner_id();
+ }
+
+ Vector<Vector3> path;
+ path.resize(2);
+ path.write[0] = begin_point;
+ path.write[1] = end_point;
+ return path;
+ }
+
+ // List of all reachable navigation polys.
+ LocalVector<gd::NavigationPoly> navigation_polys;
+ navigation_polys.resize(p_polygons.size() + p_link_polygons_size);
+
+ // Initialize the matching navigation polygon.
+ gd::NavigationPoly &begin_navigation_poly = navigation_polys[begin_poly->id];
+ begin_navigation_poly.poly = begin_poly;
+ begin_navigation_poly.entry = begin_point;
+ begin_navigation_poly.back_navigation_edge_pathway_start = begin_point;
+ begin_navigation_poly.back_navigation_edge_pathway_end = begin_point;
+
+ // Heap of polygons to travel next.
+ gd::Heap<gd::NavigationPoly *, gd::NavPolyTravelCostGreaterThan, gd::NavPolyHeapIndexer>
+ traversable_polys;
+ traversable_polys.reserve(p_polygons.size() * 0.25);
+
+ // This is an implementation of the A* algorithm.
+ int least_cost_id = begin_poly->id;
+ int prev_least_cost_id = -1;
+ bool found_route = false;
+
+ const gd::Polygon *reachable_end = nullptr;
+ real_t distance_to_reachable_end = FLT_MAX;
+ bool is_reachable = true;
+
+ while (true) {
+ // Takes the current least_cost_poly neighbors (iterating over its edges) and compute the traveled_distance.
+ for (const gd::Edge &edge : navigation_polys[least_cost_id].poly->edges) {
+ // Iterate over connections in this edge, then compute the new optimized travel distance assigned to this polygon.
+ for (int connection_index = 0; connection_index < edge.connections.size(); connection_index++) {
+ const gd::Edge::Connection &connection = edge.connections[connection_index];
+
+ // Only consider the connection to another polygon if this polygon is in a region with compatible layers.
+ if ((p_navigation_layers & connection.polygon->owner->get_navigation_layers()) == 0) {
+ continue;
+ }
+
+ const gd::NavigationPoly &least_cost_poly = navigation_polys[least_cost_id];
+ real_t poly_enter_cost = 0.0;
+ real_t poly_travel_cost = least_cost_poly.poly->owner->get_travel_cost();
+
+ if (prev_least_cost_id != -1 && navigation_polys[prev_least_cost_id].poly->owner->get_self() != least_cost_poly.poly->owner->get_self()) {
+ poly_enter_cost = least_cost_poly.poly->owner->get_enter_cost();
+ }
+ prev_least_cost_id = least_cost_id;
+
+ Vector3 pathway[2] = { connection.pathway_start, connection.pathway_end };
+ const Vector3 new_entry = Geometry3D::get_closest_point_to_segment(least_cost_poly.entry, pathway);
+ const real_t new_traveled_distance = least_cost_poly.entry.distance_to(new_entry) * poly_travel_cost + poly_enter_cost + least_cost_poly.traveled_distance;
+
+ // Check if the neighbor polygon has already been processed.
+ gd::NavigationPoly &neighbor_poly = navigation_polys[connection.polygon->id];
+ if (neighbor_poly.poly != nullptr) {
+ // If the neighbor polygon hasn't been traversed yet and the new path leading to
+ // it is shorter, update the polygon.
+ if (neighbor_poly.traversable_poly_index < traversable_polys.size() &&
+ new_traveled_distance < neighbor_poly.traveled_distance) {
+ neighbor_poly.back_navigation_poly_id = least_cost_id;
+ neighbor_poly.back_navigation_edge = connection.edge;
+ neighbor_poly.back_navigation_edge_pathway_start = connection.pathway_start;
+ neighbor_poly.back_navigation_edge_pathway_end = connection.pathway_end;
+ neighbor_poly.traveled_distance = new_traveled_distance;
+ neighbor_poly.distance_to_destination =
+ new_entry.distance_to(end_point) *
+ neighbor_poly.poly->owner->get_travel_cost();
+ neighbor_poly.entry = new_entry;
+
+ // Update the priority of the polygon in the heap.
+ traversable_polys.shift(neighbor_poly.traversable_poly_index);
+ }
+ } else {
+ // Initialize the matching navigation polygon.
+ neighbor_poly.poly = connection.polygon;
+ neighbor_poly.back_navigation_poly_id = least_cost_id;
+ neighbor_poly.back_navigation_edge = connection.edge;
+ neighbor_poly.back_navigation_edge_pathway_start = connection.pathway_start;
+ neighbor_poly.back_navigation_edge_pathway_end = connection.pathway_end;
+ neighbor_poly.traveled_distance = new_traveled_distance;
+ neighbor_poly.distance_to_destination =
+ new_entry.distance_to(end_point) *
+ neighbor_poly.poly->owner->get_travel_cost();
+ neighbor_poly.entry = new_entry;
+
+ // Add the polygon to the heap of polygons to traverse next.
+ traversable_polys.push(&neighbor_poly);
+ }
+ }
+ }
+
+ // When the heap of traversable polygons is empty at this point it means the end polygon is
+ // unreachable.
+ if (traversable_polys.is_empty()) {
+ // Thus use the further reachable polygon
+ ERR_BREAK_MSG(is_reachable == false, "It's not expect to not find the most reachable polygons");
+ is_reachable = false;
+ if (reachable_end == nullptr) {
+ // The path is not found and there is not a way out.
+ break;
+ }
+
+ // Set as end point the furthest reachable point.
+ end_poly = reachable_end;
+ end_d = FLT_MAX;
+ for (size_t point_id = 2; point_id < end_poly->points.size(); point_id++) {
+ Face3 f(end_poly->points[0].pos, end_poly->points[point_id - 1].pos, end_poly->points[point_id].pos);
+ Vector3 spoint = f.get_closest_point_to(p_destination);
+ real_t dpoint = spoint.distance_to(p_destination);
+ if (dpoint < end_d) {
+ end_point = spoint;
+ end_d = dpoint;
+ }
+ }
+
+ // Search all faces of start polygon as well.
+ bool closest_point_on_start_poly = false;
+ for (size_t point_id = 2; point_id < begin_poly->points.size(); point_id++) {
+ Face3 f(begin_poly->points[0].pos, begin_poly->points[point_id - 1].pos, begin_poly->points[point_id].pos);
+ Vector3 spoint = f.get_closest_point_to(p_destination);
+ real_t dpoint = spoint.distance_to(p_destination);
+ if (dpoint < end_d) {
+ end_point = spoint;
+ end_d = dpoint;
+ closest_point_on_start_poly = true;
+ }
+ }
+
+ if (closest_point_on_start_poly) {
+ // No point to run PostProcessing when start and end convex polygon is the same.
+ if (r_path_types) {
+ r_path_types->resize(2);
+ r_path_types->write[0] = begin_poly->owner->get_type();
+ r_path_types->write[1] = begin_poly->owner->get_type();
+ }
+
+ if (r_path_rids) {
+ r_path_rids->resize(2);
+ (*r_path_rids)[0] = begin_poly->owner->get_self();
+ (*r_path_rids)[1] = begin_poly->owner->get_self();
+ }
+
+ if (r_path_owners) {
+ r_path_owners->resize(2);
+ r_path_owners->write[0] = begin_poly->owner->get_owner_id();
+ r_path_owners->write[1] = begin_poly->owner->get_owner_id();
+ }
+
+ Vector<Vector3> path;
+ path.resize(2);
+ path.write[0] = begin_point;
+ path.write[1] = end_point;
+ return path;
+ }
+
+ for (gd::NavigationPoly &nav_poly : navigation_polys) {
+ nav_poly.poly = nullptr;
+ }
+ navigation_polys[begin_poly->id].poly = begin_poly;
+
+ least_cost_id = begin_poly->id;
+ prev_least_cost_id = -1;
+
+ reachable_end = nullptr;
+
+ continue;
+ }
+
+ // Pop the polygon with the lowest travel cost from the heap of traversable polygons.
+ least_cost_id = traversable_polys.pop()->poly->id;
+
+ // Store the farthest reachable end polygon in case our goal is not reachable.
+ if (is_reachable) {
+ real_t distance = navigation_polys[least_cost_id].entry.distance_to(p_destination);
+ if (distance_to_reachable_end > distance) {
+ distance_to_reachable_end = distance;
+ reachable_end = navigation_polys[least_cost_id].poly;
+ }
+ }
+
+ // Check if we reached the end
+ if (navigation_polys[least_cost_id].poly == end_poly) {
+ found_route = true;
+ break;
+ }
+ }
+
+ // We did not find a route but we have both a start polygon and an end polygon at this point.
+ // Usually this happens because there was not a single external or internal connected edge, e.g. our start polygon is an isolated, single convex polygon.
+ if (!found_route) {
+ end_d = FLT_MAX;
+ // Search all faces of the start polygon for the closest point to our target position.
+ for (size_t point_id = 2; point_id < begin_poly->points.size(); point_id++) {
+ Face3 f(begin_poly->points[0].pos, begin_poly->points[point_id - 1].pos, begin_poly->points[point_id].pos);
+ Vector3 spoint = f.get_closest_point_to(p_destination);
+ real_t dpoint = spoint.distance_to(p_destination);
+ if (dpoint < end_d) {
+ end_point = spoint;
+ end_d = dpoint;
+ }
+ }
+
+ if (r_path_types) {
+ r_path_types->resize(2);
+ r_path_types->write[0] = begin_poly->owner->get_type();
+ r_path_types->write[1] = begin_poly->owner->get_type();
+ }
+
+ if (r_path_rids) {
+ r_path_rids->resize(2);
+ (*r_path_rids)[0] = begin_poly->owner->get_self();
+ (*r_path_rids)[1] = begin_poly->owner->get_self();
+ }
+
+ if (r_path_owners) {
+ r_path_owners->resize(2);
+ r_path_owners->write[0] = begin_poly->owner->get_owner_id();
+ r_path_owners->write[1] = begin_poly->owner->get_owner_id();
+ }
+
+ Vector<Vector3> path;
+ path.resize(2);
+ path.write[0] = begin_point;
+ path.write[1] = end_point;
+ return path;
+ }
+
+ Vector<Vector3> path;
+ // Optimize the path.
+ 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;
+ Vector3 left_portal = apex_point;
+ gd::NavigationPoly *right_poly = apex_poly;
+ Vector3 right_portal = apex_point;
+
+ gd::NavigationPoly *p = apex_poly;
+
+ path.push_back(end_point);
+ APPEND_METADATA(end_poly);
+
+ while (p) {
+ // Set left and right points of the pathway between polygons.
+ Vector3 left = p->back_navigation_edge_pathway_start;
+ Vector3 right = p->back_navigation_edge_pathway_end;
+ if (THREE_POINTS_CROSS_PRODUCT(apex_point, left, right).dot(p_map_up) < 0) {
+ SWAP(left, right);
+ }
+
+ bool skip = false;
+ if (THREE_POINTS_CROSS_PRODUCT(apex_point, left_portal, left).dot(p_map_up) >= 0) {
+ //process
+ if (left_portal == apex_point || THREE_POINTS_CROSS_PRODUCT(apex_point, left, right_portal).dot(p_map_up) > 0) {
+ left_poly = p;
+ left_portal = left;
+ } else {
+ clip_path(navigation_polys, path, apex_poly, right_portal, right_poly, r_path_types, r_path_rids, r_path_owners, p_map_up);
+
+ apex_point = right_portal;
+ p = right_poly;
+ left_poly = p;
+ apex_poly = p;
+ left_portal = apex_point;
+ right_portal = apex_point;
+
+ path.push_back(apex_point);
+ APPEND_METADATA(apex_poly->poly);
+ skip = true;
+ }
+ }
+
+ if (!skip && THREE_POINTS_CROSS_PRODUCT(apex_point, right_portal, right).dot(p_map_up) <= 0) {
+ //process
+ if (right_portal == apex_point || THREE_POINTS_CROSS_PRODUCT(apex_point, right, left_portal).dot(p_map_up) < 0) {
+ right_poly = p;
+ right_portal = right;
+ } else {
+ clip_path(navigation_polys, path, apex_poly, left_portal, left_poly, r_path_types, r_path_rids, r_path_owners, p_map_up);
+
+ apex_point = left_portal;
+ p = left_poly;
+ right_poly = p;
+ apex_poly = p;
+ right_portal = apex_point;
+ left_portal = apex_point;
+
+ path.push_back(apex_point);
+ APPEND_METADATA(apex_poly->poly);
+ }
+ }
+
+ // Go to the previous polygon.
+ if (p->back_navigation_poly_id != -1) {
+ p = &navigation_polys[p->back_navigation_poly_id];
+ } else {
+ // The end
+ p = nullptr;
+ }
+ }
+
+ // If the last point is not the begin point, add it to the list.
+ if (path[path.size() - 1] != begin_point) {
+ path.push_back(begin_point);
+ APPEND_METADATA(begin_poly);
+ }
+
+ path.reverse();
+ if (r_path_types) {
+ r_path_types->reverse();
+ }
+ if (r_path_rids) {
+ r_path_rids->reverse();
+ }
+ if (r_path_owners) {
+ r_path_owners->reverse();
+ }
+
+ } else {
+ path.push_back(end_point);
+ APPEND_METADATA(end_poly);
+
+ // Add mid points
+ int np_id = least_cost_id;
+ while (np_id != -1 && navigation_polys[np_id].back_navigation_poly_id != -1) {
+ if (navigation_polys[np_id].back_navigation_edge != -1) {
+ int prev = navigation_polys[np_id].back_navigation_edge;
+ int prev_n = (navigation_polys[np_id].back_navigation_edge + 1) % navigation_polys[np_id].poly->points.size();
+ Vector3 point = (navigation_polys[np_id].poly->points[prev].pos + navigation_polys[np_id].poly->points[prev_n].pos) * 0.5;
+
+ path.push_back(point);
+ APPEND_METADATA(navigation_polys[np_id].poly);
+ } else {
+ path.push_back(navigation_polys[np_id].entry);
+ APPEND_METADATA(navigation_polys[np_id].poly);
+ }
+
+ np_id = navigation_polys[np_id].back_navigation_poly_id;
+ }
+
+ path.push_back(begin_point);
+ APPEND_METADATA(begin_poly);
+
+ path.reverse();
+ if (r_path_types) {
+ r_path_types->reverse();
+ }
+ if (r_path_rids) {
+ r_path_rids->reverse();
+ }
+ if (r_path_owners) {
+ r_path_owners->reverse();
+ }
+ }
+
+ // Ensure post conditions (path arrays MUST match in size).
+ CRASH_COND(r_path_types && path.size() != r_path_types->size());
+ CRASH_COND(r_path_rids && path.size() != r_path_rids->size());
+ CRASH_COND(r_path_owners && path.size() != r_path_owners->size());
+
+ return path;
+}
+
+Vector3 NavMeshQueries3D::polygons_get_closest_point_to_segment(const LocalVector<gd::Polygon> &p_polygons, const Vector3 &p_from, const Vector3 &p_to, const bool p_use_collision) {
+ bool use_collision = p_use_collision;
+ Vector3 closest_point;
+ real_t closest_point_distance = FLT_MAX;
+
+ for (const gd::Polygon &polygon : p_polygons) {
+ // For each face check the distance to the segment.
+ for (size_t point_id = 2; point_id < polygon.points.size(); point_id += 1) {
+ const Face3 face(polygon.points[0].pos, polygon.points[point_id - 1].pos, polygon.points[point_id].pos);
+ Vector3 intersection_point;
+ if (face.intersects_segment(p_from, p_to, &intersection_point)) {
+ const real_t d = p_from.distance_to(intersection_point);
+ if (!use_collision) {
+ closest_point = intersection_point;
+ use_collision = true;
+ closest_point_distance = d;
+ } else if (closest_point_distance > d) {
+ closest_point = intersection_point;
+ closest_point_distance = d;
+ }
+ }
+ // If segment does not itersect face, check the distance from segment's endpoints.
+ else if (!use_collision) {
+ const Vector3 p_from_closest = face.get_closest_point_to(p_from);
+ const real_t d_p_from = p_from.distance_to(p_from_closest);
+ if (closest_point_distance > d_p_from) {
+ closest_point = p_from_closest;
+ closest_point_distance = d_p_from;
+ }
+
+ const Vector3 p_to_closest = face.get_closest_point_to(p_to);
+ const real_t d_p_to = p_to.distance_to(p_to_closest);
+ if (closest_point_distance > d_p_to) {
+ closest_point = p_to_closest;
+ closest_point_distance = d_p_to;
+ }
+ }
+ }
+ // Finally, check for a case when shortest distance is between some point located on a face's edge and some point located on a line segment.
+ if (!use_collision) {
+ for (size_t point_id = 0; point_id < polygon.points.size(); point_id += 1) {
+ Vector3 a, b;
+
+ Geometry3D::get_closest_points_between_segments(
+ p_from,
+ p_to,
+ polygon.points[point_id].pos,
+ polygon.points[(point_id + 1) % polygon.points.size()].pos,
+ a,
+ b);
+
+ const real_t d = a.distance_to(b);
+ if (d < closest_point_distance) {
+ closest_point_distance = d;
+ closest_point = b;
+ }
+ }
+ }
+ }
+
+ return closest_point;
+}
+
+Vector3 NavMeshQueries3D::polygons_get_closest_point(const LocalVector<gd::Polygon> &p_polygons, const Vector3 &p_point) {
+ gd::ClosestPointQueryResult cp = polygons_get_closest_point_info(p_polygons, p_point);
+ return cp.point;
+}
+
+Vector3 NavMeshQueries3D::polygons_get_closest_point_normal(const LocalVector<gd::Polygon> &p_polygons, const Vector3 &p_point) {
+ gd::ClosestPointQueryResult cp = polygons_get_closest_point_info(p_polygons, p_point);
+ return cp.normal;
+}
+
+gd::ClosestPointQueryResult NavMeshQueries3D::polygons_get_closest_point_info(const LocalVector<gd::Polygon> &p_polygons, const Vector3 &p_point) {
+ gd::ClosestPointQueryResult result;
+ real_t closest_point_distance_squared = FLT_MAX;
+
+ for (const gd::Polygon &polygon : p_polygons) {
+ for (size_t point_id = 2; point_id < polygon.points.size(); point_id += 1) {
+ const Face3 face(polygon.points[0].pos, polygon.points[point_id - 1].pos, polygon.points[point_id].pos);
+ const Vector3 closest_point_on_face = face.get_closest_point_to(p_point);
+ const real_t distance_squared_to_point = closest_point_on_face.distance_squared_to(p_point);
+ if (distance_squared_to_point < closest_point_distance_squared) {
+ result.point = closest_point_on_face;
+ result.normal = face.get_plane().normal;
+ result.owner = polygon.owner->get_self();
+ closest_point_distance_squared = distance_squared_to_point;
+ }
+ }
+ }
+
+ return result;
+}
+
+RID NavMeshQueries3D::polygons_get_closest_point_owner(const LocalVector<gd::Polygon> &p_polygons, const Vector3 &p_point) {
+ gd::ClosestPointQueryResult cp = polygons_get_closest_point_info(p_polygons, p_point);
+ return cp.owner;
+}
+
+void NavMeshQueries3D::clip_path(const LocalVector<gd::NavigationPoly> &p_navigation_polys, Vector<Vector3> &path, const gd::NavigationPoly *from_poly, const Vector3 &p_to_point, const gd::NavigationPoly *p_to_poly, Vector<int32_t> *r_path_types, TypedArray<RID> *r_path_rids, Vector<int64_t> *r_path_owners, const Vector3 &p_map_up) {
+ Vector3 from = path[path.size() - 1];
+
+ if (from.is_equal_approx(p_to_point)) {
+ return;
+ }
+
+ Plane cut_plane;
+ cut_plane.normal = (from - p_to_point).cross(p_map_up);
+ if (cut_plane.normal == Vector3()) {
+ return;
+ }
+ cut_plane.normal.normalize();
+ cut_plane.d = cut_plane.normal.dot(from);
+
+ while (from_poly != p_to_poly) {
+ Vector3 pathway_start = from_poly->back_navigation_edge_pathway_start;
+ Vector3 pathway_end = from_poly->back_navigation_edge_pathway_end;
+
+ ERR_FAIL_COND(from_poly->back_navigation_poly_id == -1);
+ from_poly = &p_navigation_polys[from_poly->back_navigation_poly_id];
+
+ if (!pathway_start.is_equal_approx(pathway_end)) {
+ Vector3 inters;
+ if (cut_plane.intersects_segment(pathway_start, pathway_end, &inters)) {
+ if (!inters.is_equal_approx(p_to_point) && !inters.is_equal_approx(path[path.size() - 1])) {
+ path.push_back(inters);
+ APPEND_METADATA(from_poly->poly);
+ }
+ }
+ }
+ }
+}
+
+#endif // _3D_DISABLED
diff --git a/modules/navigation/3d/nav_mesh_queries_3d.h b/modules/navigation/3d/nav_mesh_queries_3d.h
new file mode 100644
index 0000000000..109bb2f971
--- /dev/null
+++ b/modules/navigation/3d/nav_mesh_queries_3d.h
@@ -0,0 +1,54 @@
+/**************************************************************************/
+/* nav_mesh_queries_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_QUERIES_3D_H
+#define NAV_MESH_QUERIES_3D_H
+
+#ifndef _3D_DISABLED
+
+#include "../nav_map.h"
+
+class NavMeshQueries3D {
+public:
+ static Vector3 polygons_get_random_point(const LocalVector<gd::Polygon> &p_polygons, uint32_t p_navigation_layers, bool p_uniformly);
+
+ static Vector<Vector3> polygons_get_path(const LocalVector<gd::Polygon> &p_polygons, Vector3 p_origin, Vector3 p_destination, bool p_optimize, uint32_t p_navigation_layers, Vector<int32_t> *r_path_types, TypedArray<RID> *r_path_rids, Vector<int64_t> *r_path_owners, const Vector3 &p_map_up, uint32_t p_link_polygons_size);
+ static Vector3 polygons_get_closest_point_to_segment(const LocalVector<gd::Polygon> &p_polygons, const Vector3 &p_from, const Vector3 &p_to, const bool p_use_collision);
+ static Vector3 polygons_get_closest_point(const LocalVector<gd::Polygon> &p_polygons, const Vector3 &p_point);
+ static Vector3 polygons_get_closest_point_normal(const LocalVector<gd::Polygon> &p_polygons, const Vector3 &p_point);
+ static gd::ClosestPointQueryResult polygons_get_closest_point_info(const LocalVector<gd::Polygon> &p_polygons, const Vector3 &p_point);
+ static RID polygons_get_closest_point_owner(const LocalVector<gd::Polygon> &p_polygons, const Vector3 &p_point);
+
+ static void clip_path(const LocalVector<gd::NavigationPoly> &p_navigation_polys, Vector<Vector3> &path, const gd::NavigationPoly *from_poly, const Vector3 &p_to_point, const gd::NavigationPoly *p_to_poly, Vector<int32_t> *r_path_types, TypedArray<RID> *r_path_rids, Vector<int64_t> *r_path_owners, const Vector3 &p_map_up);
+};
+
+#endif // _3D_DISABLED
+
+#endif // NAV_MESH_QUERIES_3D_H
diff --git a/modules/navigation/nav_map.cpp b/modules/navigation/nav_map.cpp
index 0c91e8dea3..dd77e81b45 100644
--- a/modules/navigation/nav_map.cpp
+++ b/modules/navigation/nav_map.cpp
@@ -35,25 +35,13 @@
#include "nav_obstacle.h"
#include "nav_region.h"
+#include "3d/nav_mesh_queries_3d.h"
+
#include "core/config/project_settings.h"
#include "core/object/worker_thread_pool.h"
#include <Obstacle2d.h>
-#define THREE_POINTS_CROSS_PRODUCT(m_a, m_b, m_c) (((m_c) - (m_a)).cross((m_b) - (m_a)))
-
-// Helper macro
-#define APPEND_METADATA(poly) \
- if (r_path_types) { \
- r_path_types->push_back(poly->owner->get_type()); \
- } \
- if (r_path_rids) { \
- r_path_rids->push_back(poly->owner->get_self()); \
- } \
- if (r_path_owners) { \
- r_path_owners->push_back(poly->owner->get_owner_id()); \
- }
-
#ifdef DEBUG_ENABLED
#define NAVMAP_ITERATION_ZERO_ERROR_MSG() \
ERR_PRINT_ONCE("NavigationServer navigation map query failed because it was made before first map synchronization.\n\
@@ -142,462 +130,9 @@ Vector<Vector3> NavMap::get_path(Vector3 p_origin, Vector3 p_destination, bool p
return Vector<Vector3>();
}
- // Clear metadata outputs.
- if (r_path_types) {
- r_path_types->clear();
- }
- if (r_path_rids) {
- r_path_rids->clear();
- }
- if (r_path_owners) {
- r_path_owners->clear();
- }
-
- // Find the start poly and the end poly on this map.
- const gd::Polygon *begin_poly = nullptr;
- const gd::Polygon *end_poly = nullptr;
- Vector3 begin_point;
- Vector3 end_point;
- real_t begin_d = FLT_MAX;
- real_t end_d = FLT_MAX;
- // Find the initial poly and the end poly on this map.
- for (const gd::Polygon &p : polygons) {
- // Only consider the polygon if it in a region with compatible layers.
- if ((p_navigation_layers & p.owner->get_navigation_layers()) == 0) {
- continue;
- }
-
- // For each face check the distance between the origin/destination
- for (size_t point_id = 2; point_id < p.points.size(); point_id++) {
- const Face3 face(p.points[0].pos, p.points[point_id - 1].pos, p.points[point_id].pos);
-
- Vector3 point = face.get_closest_point_to(p_origin);
- real_t distance_to_point = point.distance_to(p_origin);
- if (distance_to_point < begin_d) {
- begin_d = distance_to_point;
- begin_poly = &p;
- begin_point = point;
- }
-
- point = face.get_closest_point_to(p_destination);
- distance_to_point = point.distance_to(p_destination);
- if (distance_to_point < end_d) {
- end_d = distance_to_point;
- end_poly = &p;
- end_point = point;
- }
- }
- }
-
- // Check for trivial cases
- if (!begin_poly || !end_poly) {
- return Vector<Vector3>();
- }
- if (begin_poly == end_poly) {
- if (r_path_types) {
- r_path_types->resize(2);
- r_path_types->write[0] = begin_poly->owner->get_type();
- r_path_types->write[1] = end_poly->owner->get_type();
- }
-
- if (r_path_rids) {
- r_path_rids->resize(2);
- (*r_path_rids)[0] = begin_poly->owner->get_self();
- (*r_path_rids)[1] = end_poly->owner->get_self();
- }
-
- if (r_path_owners) {
- r_path_owners->resize(2);
- r_path_owners->write[0] = begin_poly->owner->get_owner_id();
- r_path_owners->write[1] = end_poly->owner->get_owner_id();
- }
-
- Vector<Vector3> path;
- path.resize(2);
- path.write[0] = begin_point;
- path.write[1] = end_point;
- return path;
- }
-
- // List of all reachable navigation polys.
- LocalVector<gd::NavigationPoly> navigation_polys;
- navigation_polys.reserve(polygons.size() * 0.75);
-
- // Add the start polygon to the reachable navigation polygons.
- gd::NavigationPoly begin_navigation_poly = gd::NavigationPoly(begin_poly);
- begin_navigation_poly.self_id = 0;
- begin_navigation_poly.entry = begin_point;
- begin_navigation_poly.back_navigation_edge_pathway_start = begin_point;
- begin_navigation_poly.back_navigation_edge_pathway_end = begin_point;
- navigation_polys.push_back(begin_navigation_poly);
-
- // List of polygon IDs to visit.
- List<uint32_t> to_visit;
- to_visit.push_back(0);
-
- // This is an implementation of the A* algorithm.
- int least_cost_id = 0;
- int prev_least_cost_id = -1;
- bool found_route = false;
-
- const gd::Polygon *reachable_end = nullptr;
- real_t reachable_d = FLT_MAX;
- bool is_reachable = true;
-
- while (true) {
- // Takes the current least_cost_poly neighbors (iterating over its edges) and compute the traveled_distance.
- for (const gd::Edge &edge : navigation_polys[least_cost_id].poly->edges) {
- // Iterate over connections in this edge, then compute the new optimized travel distance assigned to this polygon.
- for (int connection_index = 0; connection_index < edge.connections.size(); connection_index++) {
- const gd::Edge::Connection &connection = edge.connections[connection_index];
-
- // Only consider the connection to another polygon if this polygon is in a region with compatible layers.
- if ((p_navigation_layers & connection.polygon->owner->get_navigation_layers()) == 0) {
- continue;
- }
-
- const gd::NavigationPoly &least_cost_poly = navigation_polys[least_cost_id];
- real_t poly_enter_cost = 0.0;
- real_t poly_travel_cost = least_cost_poly.poly->owner->get_travel_cost();
-
- if (prev_least_cost_id != -1 && (navigation_polys[prev_least_cost_id].poly->owner->get_self() != least_cost_poly.poly->owner->get_self())) {
- poly_enter_cost = least_cost_poly.poly->owner->get_enter_cost();
- }
- prev_least_cost_id = least_cost_id;
-
- Vector3 pathway[2] = { connection.pathway_start, connection.pathway_end };
- const Vector3 new_entry = Geometry3D::get_closest_point_to_segment(least_cost_poly.entry, pathway);
- const real_t new_distance = (least_cost_poly.entry.distance_to(new_entry) * poly_travel_cost) + poly_enter_cost + least_cost_poly.traveled_distance;
-
- int64_t already_visited_polygon_index = navigation_polys.find(gd::NavigationPoly(connection.polygon));
-
- if (already_visited_polygon_index != -1) {
- // Polygon already visited, check if we can reduce the travel cost.
- gd::NavigationPoly &avp = navigation_polys[already_visited_polygon_index];
- if (new_distance < avp.traveled_distance) {
- avp.back_navigation_poly_id = least_cost_id;
- avp.back_navigation_edge = connection.edge;
- avp.back_navigation_edge_pathway_start = connection.pathway_start;
- avp.back_navigation_edge_pathway_end = connection.pathway_end;
- avp.traveled_distance = new_distance;
- avp.entry = new_entry;
- }
- } else {
- // Add the neighbor polygon to the reachable ones.
- gd::NavigationPoly new_navigation_poly = gd::NavigationPoly(connection.polygon);
- new_navigation_poly.self_id = navigation_polys.size();
- new_navigation_poly.back_navigation_poly_id = least_cost_id;
- new_navigation_poly.back_navigation_edge = connection.edge;
- new_navigation_poly.back_navigation_edge_pathway_start = connection.pathway_start;
- new_navigation_poly.back_navigation_edge_pathway_end = connection.pathway_end;
- new_navigation_poly.traveled_distance = new_distance;
- new_navigation_poly.entry = new_entry;
- navigation_polys.push_back(new_navigation_poly);
-
- // Add the neighbor polygon to the polygons to visit.
- to_visit.push_back(navigation_polys.size() - 1);
- }
- }
- }
-
- // Removes the least cost polygon from the list of polygons to visit so we can advance.
- to_visit.erase(least_cost_id);
-
- // When the list of polygons to visit is empty at this point it means the End Polygon is not reachable
- if (to_visit.size() == 0) {
- // Thus use the further reachable polygon
- ERR_BREAK_MSG(is_reachable == false, "It's not expect to not find the most reachable polygons");
- is_reachable = false;
- if (reachable_end == nullptr) {
- // The path is not found and there is not a way out.
- break;
- }
-
- // Set as end point the furthest reachable point.
- end_poly = reachable_end;
- end_d = FLT_MAX;
- for (size_t point_id = 2; point_id < end_poly->points.size(); point_id++) {
- Face3 f(end_poly->points[0].pos, end_poly->points[point_id - 1].pos, end_poly->points[point_id].pos);
- Vector3 spoint = f.get_closest_point_to(p_destination);
- real_t dpoint = spoint.distance_to(p_destination);
- if (dpoint < end_d) {
- end_point = spoint;
- end_d = dpoint;
- }
- }
-
- // Search all faces of start polygon as well.
- bool closest_point_on_start_poly = false;
- for (size_t point_id = 2; point_id < begin_poly->points.size(); point_id++) {
- Face3 f(begin_poly->points[0].pos, begin_poly->points[point_id - 1].pos, begin_poly->points[point_id].pos);
- Vector3 spoint = f.get_closest_point_to(p_destination);
- real_t dpoint = spoint.distance_to(p_destination);
- if (dpoint < end_d) {
- end_point = spoint;
- end_d = dpoint;
- closest_point_on_start_poly = true;
- }
- }
-
- if (closest_point_on_start_poly) {
- // No point to run PostProcessing when start and end convex polygon is the same.
- if (r_path_types) {
- r_path_types->resize(2);
- r_path_types->write[0] = begin_poly->owner->get_type();
- r_path_types->write[1] = begin_poly->owner->get_type();
- }
-
- if (r_path_rids) {
- r_path_rids->resize(2);
- (*r_path_rids)[0] = begin_poly->owner->get_self();
- (*r_path_rids)[1] = begin_poly->owner->get_self();
- }
-
- if (r_path_owners) {
- r_path_owners->resize(2);
- r_path_owners->write[0] = begin_poly->owner->get_owner_id();
- r_path_owners->write[1] = begin_poly->owner->get_owner_id();
- }
-
- Vector<Vector3> path;
- path.resize(2);
- path.write[0] = begin_point;
- path.write[1] = end_point;
- return path;
- }
-
- // Reset open and navigation_polys
- gd::NavigationPoly np = navigation_polys[0];
- navigation_polys.clear();
- navigation_polys.push_back(np);
- to_visit.clear();
- to_visit.push_back(0);
- least_cost_id = 0;
- prev_least_cost_id = -1;
-
- reachable_end = nullptr;
-
- continue;
- }
-
- // Find the polygon with the minimum cost from the list of polygons to visit.
- least_cost_id = -1;
- real_t least_cost = FLT_MAX;
- for (List<uint32_t>::Element *element = to_visit.front(); element != nullptr; element = element->next()) {
- gd::NavigationPoly *np = &navigation_polys[element->get()];
- real_t cost = np->traveled_distance;
- cost += (np->entry.distance_to(end_point) * np->poly->owner->get_travel_cost());
- if (cost < least_cost) {
- least_cost_id = np->self_id;
- least_cost = cost;
- }
- }
-
- ERR_BREAK(least_cost_id == -1);
-
- // Stores the further reachable end polygon, in case our goal is not reachable.
- if (is_reachable) {
- real_t d = navigation_polys[least_cost_id].entry.distance_to(p_destination);
- if (reachable_d > d) {
- reachable_d = d;
- reachable_end = navigation_polys[least_cost_id].poly;
- }
- }
-
- // Check if we reached the end
- if (navigation_polys[least_cost_id].poly == end_poly) {
- found_route = true;
- break;
- }
- }
-
- // We did not find a route but we have both a start polygon and an end polygon at this point.
- // Usually this happens because there was not a single external or internal connected edge, e.g. our start polygon is an isolated, single convex polygon.
- if (!found_route) {
- end_d = FLT_MAX;
- // Search all faces of the start polygon for the closest point to our target position.
- for (size_t point_id = 2; point_id < begin_poly->points.size(); point_id++) {
- Face3 f(begin_poly->points[0].pos, begin_poly->points[point_id - 1].pos, begin_poly->points[point_id].pos);
- Vector3 spoint = f.get_closest_point_to(p_destination);
- real_t dpoint = spoint.distance_to(p_destination);
- if (dpoint < end_d) {
- end_point = spoint;
- end_d = dpoint;
- }
- }
-
- if (r_path_types) {
- r_path_types->resize(2);
- r_path_types->write[0] = begin_poly->owner->get_type();
- r_path_types->write[1] = begin_poly->owner->get_type();
- }
-
- if (r_path_rids) {
- r_path_rids->resize(2);
- (*r_path_rids)[0] = begin_poly->owner->get_self();
- (*r_path_rids)[1] = begin_poly->owner->get_self();
- }
-
- if (r_path_owners) {
- r_path_owners->resize(2);
- r_path_owners->write[0] = begin_poly->owner->get_owner_id();
- r_path_owners->write[1] = begin_poly->owner->get_owner_id();
- }
-
- Vector<Vector3> path;
- path.resize(2);
- path.write[0] = begin_point;
- path.write[1] = end_point;
- return path;
- }
-
- Vector<Vector3> path;
- // Optimize the path.
- 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;
- Vector3 left_portal = apex_point;
- gd::NavigationPoly *right_poly = apex_poly;
- Vector3 right_portal = apex_point;
-
- gd::NavigationPoly *p = apex_poly;
-
- path.push_back(end_point);
- APPEND_METADATA(end_poly);
-
- while (p) {
- // Set left and right points of the pathway between polygons.
- Vector3 left = p->back_navigation_edge_pathway_start;
- Vector3 right = p->back_navigation_edge_pathway_end;
- if (THREE_POINTS_CROSS_PRODUCT(apex_point, left, right).dot(up) < 0) {
- SWAP(left, right);
- }
-
- bool skip = false;
- if (THREE_POINTS_CROSS_PRODUCT(apex_point, left_portal, left).dot(up) >= 0) {
- //process
- if (left_portal == apex_point || THREE_POINTS_CROSS_PRODUCT(apex_point, left, right_portal).dot(up) > 0) {
- left_poly = p;
- left_portal = left;
- } else {
- clip_path(navigation_polys, path, apex_poly, right_portal, right_poly, r_path_types, r_path_rids, r_path_owners);
-
- apex_point = right_portal;
- p = right_poly;
- left_poly = p;
- apex_poly = p;
- left_portal = apex_point;
- right_portal = apex_point;
-
- path.push_back(apex_point);
- APPEND_METADATA(apex_poly->poly);
- skip = true;
- }
- }
-
- if (!skip && THREE_POINTS_CROSS_PRODUCT(apex_point, right_portal, right).dot(up) <= 0) {
- //process
- if (right_portal == apex_point || THREE_POINTS_CROSS_PRODUCT(apex_point, right, left_portal).dot(up) < 0) {
- right_poly = p;
- right_portal = right;
- } else {
- clip_path(navigation_polys, path, apex_poly, left_portal, left_poly, r_path_types, r_path_rids, r_path_owners);
-
- apex_point = left_portal;
- p = left_poly;
- right_poly = p;
- apex_poly = p;
- right_portal = apex_point;
- left_portal = apex_point;
-
- path.push_back(apex_point);
- APPEND_METADATA(apex_poly->poly);
- }
- }
-
- // Go to the previous polygon.
- if (p->back_navigation_poly_id != -1) {
- p = &navigation_polys[p->back_navigation_poly_id];
- } else {
- // The end
- p = nullptr;
- }
- }
-
- // If the last point is not the begin point, add it to the list.
- if (path[path.size() - 1] != begin_point) {
- path.push_back(begin_point);
- APPEND_METADATA(begin_poly);
- }
-
- path.reverse();
- if (r_path_types) {
- r_path_types->reverse();
- }
- if (r_path_rids) {
- r_path_rids->reverse();
- }
- if (r_path_owners) {
- r_path_owners->reverse();
- }
-
- } else {
- path.push_back(end_point);
- APPEND_METADATA(end_poly);
-
- // Add mid points
- int np_id = least_cost_id;
- while (np_id != -1 && navigation_polys[np_id].back_navigation_poly_id != -1) {
- if (navigation_polys[np_id].back_navigation_edge != -1) {
- int prev = navigation_polys[np_id].back_navigation_edge;
- int prev_n = (navigation_polys[np_id].back_navigation_edge + 1) % navigation_polys[np_id].poly->points.size();
- Vector3 point = (navigation_polys[np_id].poly->points[prev].pos + navigation_polys[np_id].poly->points[prev_n].pos) * 0.5;
-
- path.push_back(point);
- APPEND_METADATA(navigation_polys[np_id].poly);
- } else {
- path.push_back(navigation_polys[np_id].entry);
- APPEND_METADATA(navigation_polys[np_id].poly);
- }
-
- np_id = navigation_polys[np_id].back_navigation_poly_id;
- }
-
- path.push_back(begin_point);
- APPEND_METADATA(begin_poly);
-
- path.reverse();
- if (r_path_types) {
- r_path_types->reverse();
- }
- if (r_path_rids) {
- r_path_rids->reverse();
- }
- if (r_path_owners) {
- r_path_owners->reverse();
- }
- }
-
- // Ensure post conditions (path arrays MUST match in size).
- CRASH_COND(r_path_types && path.size() != r_path_types->size());
- CRASH_COND(r_path_rids && path.size() != r_path_rids->size());
- CRASH_COND(r_path_owners && path.size() != r_path_owners->size());
-
- return path;
+ return NavMeshQueries3D::polygons_get_path(
+ polygons, p_origin, p_destination, p_optimize, p_navigation_layers,
+ r_path_types, r_path_rids, r_path_owners, up, link_polygons.size());
}
Vector3 NavMap::get_closest_point_to_segment(const Vector3 &p_from, const Vector3 &p_to, const bool p_use_collision) const {
@@ -607,66 +142,7 @@ Vector3 NavMap::get_closest_point_to_segment(const Vector3 &p_from, const Vector
return Vector3();
}
- bool use_collision = p_use_collision;
- Vector3 closest_point;
- real_t closest_point_d = FLT_MAX;
-
- for (const gd::Polygon &p : polygons) {
- // For each face check the distance to the segment
- for (size_t point_id = 2; point_id < p.points.size(); point_id += 1) {
- const Face3 f(p.points[0].pos, p.points[point_id - 1].pos, p.points[point_id].pos);
- Vector3 inters;
- if (f.intersects_segment(p_from, p_to, &inters)) {
- const real_t d = p_from.distance_to(inters);
- if (use_collision == false) {
- closest_point = inters;
- use_collision = true;
- closest_point_d = d;
- } else if (closest_point_d > d) {
- closest_point = inters;
- closest_point_d = d;
- }
- }
- // If segment does not itersect face, check the distance from segment's endpoints.
- else if (!use_collision) {
- const Vector3 p_from_closest = f.get_closest_point_to(p_from);
- const real_t d_p_from = p_from.distance_to(p_from_closest);
- if (closest_point_d > d_p_from) {
- closest_point = p_from_closest;
- closest_point_d = d_p_from;
- }
-
- const Vector3 p_to_closest = f.get_closest_point_to(p_to);
- const real_t d_p_to = p_to.distance_to(p_to_closest);
- if (closest_point_d > d_p_to) {
- closest_point = p_to_closest;
- closest_point_d = d_p_to;
- }
- }
- }
- // Finally, check for a case when shortest distance is between some point located on a face's edge and some point located on a line segment.
- if (!use_collision) {
- for (size_t point_id = 0; point_id < p.points.size(); point_id += 1) {
- Vector3 a, b;
-
- Geometry3D::get_closest_points_between_segments(
- p_from,
- p_to,
- p.points[point_id].pos,
- p.points[(point_id + 1) % p.points.size()].pos,
- a,
- b);
-
- const real_t d = a.distance_to(b);
- if (d < closest_point_d) {
- closest_point_d = d;
- closest_point = b;
- }
- }
- }
- }
-
- return closest_point;
+ return NavMeshQueries3D::polygons_get_closest_point_to_segment(polygons, p_from, p_to, p_use_collision);
}
Vector3 NavMap::get_closest_point(const Vector3 &p_point) const {
@@ -675,8 +151,8 @@ Vector3 NavMap::get_closest_point(const Vector3 &p_point) const {
NAVMAP_ITERATION_ZERO_ERROR_MSG();
return Vector3();
}
- gd::ClosestPointQueryResult cp = get_closest_point_info(p_point);
- return cp.point;
+
+ return NavMeshQueries3D::polygons_get_closest_point(polygons, p_point);
}
Vector3 NavMap::get_closest_point_normal(const Vector3 &p_point) const {
@@ -685,8 +161,8 @@ Vector3 NavMap::get_closest_point_normal(const Vector3 &p_point) const {
NAVMAP_ITERATION_ZERO_ERROR_MSG();
return Vector3();
}
- gd::ClosestPointQueryResult cp = get_closest_point_info(p_point);
- return cp.normal;
+
+ return NavMeshQueries3D::polygons_get_closest_point_normal(polygons, p_point);
}
RID NavMap::get_closest_point_owner(const Vector3 &p_point) const {
@@ -695,32 +171,14 @@ RID NavMap::get_closest_point_owner(const Vector3 &p_point) const {
NAVMAP_ITERATION_ZERO_ERROR_MSG();
return RID();
}
- gd::ClosestPointQueryResult cp = get_closest_point_info(p_point);
- return cp.owner;
+
+ return NavMeshQueries3D::polygons_get_closest_point_owner(polygons, p_point);
}
gd::ClosestPointQueryResult NavMap::get_closest_point_info(const Vector3 &p_point) const {
RWLockRead read_lock(map_rwlock);
- gd::ClosestPointQueryResult result;
- real_t closest_point_ds = FLT_MAX;
-
- for (const gd::Polygon &p : polygons) {
- // For each face check the distance to the point
- for (size_t point_id = 2; point_id < p.points.size(); point_id += 1) {
- const Face3 f(p.points[0].pos, p.points[point_id - 1].pos, p.points[point_id].pos);
- const Vector3 inters = f.get_closest_point_to(p_point);
- const real_t ds = inters.distance_squared_to(p_point);
- if (ds < closest_point_ds) {
- result.point = inters;
- result.normal = f.get_plane().normal;
- result.owner = p.owner->get_self();
- closest_point_ds = ds;
- }
- }
- }
-
- return result;
+ return NavMeshQueries3D::polygons_get_closest_point_info(polygons, p_point);
}
void NavMap::add_region(NavRegion *p_region) {
@@ -943,29 +401,30 @@ void NavMap::sync() {
}
// Resize the polygon count.
- int count = 0;
+ int polygon_count = 0;
for (const NavRegion *region : regions) {
if (!region->get_enabled()) {
continue;
}
- count += region->get_polygons().size();
+ polygon_count += region->get_polygons().size();
}
- polygons.resize(count);
+ polygons.resize(polygon_count);
// Copy all region polygons in the map.
- count = 0;
+ polygon_count = 0;
for (const NavRegion *region : regions) {
if (!region->get_enabled()) {
continue;
}
const LocalVector<gd::Polygon> &polygons_source = region->get_polygons();
for (uint32_t n = 0; n < polygons_source.size(); n++) {
- polygons[count + n] = polygons_source[n];
+ polygons[polygon_count] = polygons_source[n];
+ polygons[polygon_count].id = polygon_count;
+ polygon_count++;
}
- count += region->get_polygons().size();
}
- _new_pm_polygon_count = polygons.size();
+ _new_pm_polygon_count = polygon_count;
// Group all edges per key.
HashMap<gd::EdgeKey, Vector<gd::Edge::Connection>, gd::EdgeKey> connections;
@@ -1136,6 +595,7 @@ void NavMap::sync() {
// If we have both a start and end point, then create a synthetic polygon to route through.
if (closest_start_polygon && closest_end_polygon) {
gd::Polygon &new_polygon = link_polygons[link_poly_idx++];
+ new_polygon.id = polygon_count++;
new_polygon.owner = link;
new_polygon.edges.clear();
@@ -1391,39 +851,6 @@ void NavMap::dispatch_callbacks() {
}
}
-void NavMap::clip_path(const LocalVector<gd::NavigationPoly> &p_navigation_polys, Vector<Vector3> &path, const gd::NavigationPoly *from_poly, const Vector3 &p_to_point, const gd::NavigationPoly *p_to_poly, Vector<int32_t> *r_path_types, TypedArray<RID> *r_path_rids, Vector<int64_t> *r_path_owners) const {
- Vector3 from = path[path.size() - 1];
-
- if (from.is_equal_approx(p_to_point)) {
- return;
- }
- Plane cut_plane;
- cut_plane.normal = (from - p_to_point).cross(up);
- if (cut_plane.normal == Vector3()) {
- return;
- }
- cut_plane.normal.normalize();
- cut_plane.d = cut_plane.normal.dot(from);
-
- while (from_poly != p_to_poly) {
- Vector3 pathway_start = from_poly->back_navigation_edge_pathway_start;
- Vector3 pathway_end = from_poly->back_navigation_edge_pathway_end;
-
- ERR_FAIL_COND(from_poly->back_navigation_poly_id == -1);
- from_poly = &p_navigation_polys[from_poly->back_navigation_poly_id];
-
- if (!pathway_start.is_equal_approx(pathway_end)) {
- Vector3 inters;
- if (cut_plane.intersects_segment(pathway_start, pathway_end, &inters)) {
- if (!inters.is_equal_approx(p_to_point) && !inters.is_equal_approx(path[path.size() - 1])) {
- path.push_back(inters);
- APPEND_METADATA(from_poly->poly);
- }
- }
- }
- }
-}
-
void NavMap::_update_merge_rasterizer_cell_dimensions() {
merge_rasterizer_cell_size = cell_size * merge_rasterizer_cell_scale;
merge_rasterizer_cell_height = cell_height * merge_rasterizer_cell_scale;
diff --git a/modules/navigation/nav_map.h b/modules/navigation/nav_map.h
index 82e8854b7a..b9120c04d9 100644
--- a/modules/navigation/nav_map.h
+++ b/modules/navigation/nav_map.h
@@ -36,6 +36,7 @@
#include "core/math/math_defs.h"
#include "core/object/worker_thread_pool.h"
+#include "servers/navigation/navigation_globals.h"
#include <KdTree2d.h>
#include <KdTree3d.h>
@@ -55,21 +56,21 @@ class NavMap : public NavRid {
/// To find the polygons edges the vertices are displaced in a grid where
/// each cell has the following cell_size and cell_height.
- real_t cell_size = 0.25; // Must match ProjectSettings default 3D cell_size and NavigationMesh cell_size.
- real_t cell_height = 0.25; // Must match ProjectSettings default 3D cell_height and NavigationMesh cell_height.
+ real_t cell_size = NavigationDefaults3D::navmesh_cell_size;
+ real_t cell_height = NavigationDefaults3D::navmesh_cell_height;
// For the inter-region merging to work, internal rasterization is performed.
- float merge_rasterizer_cell_size = 0.25;
- float merge_rasterizer_cell_height = 0.25;
+ float merge_rasterizer_cell_size = NavigationDefaults3D::navmesh_cell_size;
+ float merge_rasterizer_cell_height = NavigationDefaults3D::navmesh_cell_height;
// This value is used to control sensitivity of internal rasterizer.
float merge_rasterizer_cell_scale = 1.0;
bool use_edge_connections = true;
/// This value is used to detect the near edges to connect.
- real_t edge_connection_margin = 0.25;
+ real_t edge_connection_margin = NavigationDefaults3D::edge_connection_margin;
/// This value is used to limit how far links search to find polygons to connect to.
- real_t link_connection_radius = 1.0;
+ real_t link_connection_radius = NavigationDefaults3D::link_connection_radius;
bool regenerate_polygons = true;
bool regenerate_links = true;
@@ -231,7 +232,6 @@ private:
void compute_single_avoidance_step_2d(uint32_t index, NavAgent **agent);
void compute_single_avoidance_step_3d(uint32_t index, NavAgent **agent);
- void clip_path(const LocalVector<gd::NavigationPoly> &p_navigation_polys, Vector<Vector3> &path, const gd::NavigationPoly *from_poly, const Vector3 &p_to_point, const gd::NavigationPoly *p_to_poly, Vector<int32_t> *r_path_types, TypedArray<RID> *r_path_rids, Vector<int64_t> *r_path_owners) const;
void _update_rvo_simulation();
void _update_rvo_obstacles_tree_2d();
void _update_rvo_agents_tree_2d();
diff --git a/modules/navigation/nav_region.cpp b/modules/navigation/nav_region.cpp
index 85510bd416..7a44adecbc 100644
--- a/modules/navigation/nav_region.cpp
+++ b/modules/navigation/nav_region.cpp
@@ -32,6 +32,8 @@
#include "nav_map.h"
+#include "3d/nav_mesh_queries_3d.h"
+
void NavRegion::set_map(NavMap *p_map) {
if (map == p_map) {
return;
@@ -108,81 +110,7 @@ Vector3 NavRegion::get_random_point(uint32_t p_navigation_layers, bool p_uniform
return Vector3();
}
- const LocalVector<gd::Polygon> &region_polygons = get_polygons();
-
- if (region_polygons.is_empty()) {
- return Vector3();
- }
-
- if (p_uniformly) {
- real_t accumulated_area = 0;
- RBMap<real_t, uint32_t> region_area_map;
-
- for (uint32_t rp_index = 0; rp_index < region_polygons.size(); rp_index++) {
- const gd::Polygon &region_polygon = region_polygons[rp_index];
- real_t polyon_area = region_polygon.surface_area;
-
- if (polyon_area == 0.0) {
- continue;
- }
- region_area_map[accumulated_area] = rp_index;
- accumulated_area += polyon_area;
- }
- if (region_area_map.is_empty() || accumulated_area == 0) {
- // All polygons have no real surface / no area.
- return Vector3();
- }
-
- real_t region_area_map_pos = Math::random(real_t(0), accumulated_area);
-
- RBMap<real_t, uint32_t>::Iterator region_E = region_area_map.find_closest(region_area_map_pos);
- ERR_FAIL_COND_V(!region_E, Vector3());
- uint32_t rrp_polygon_index = region_E->value;
- ERR_FAIL_UNSIGNED_INDEX_V(rrp_polygon_index, region_polygons.size(), Vector3());
-
- const gd::Polygon &rr_polygon = region_polygons[rrp_polygon_index];
-
- real_t accumulated_polygon_area = 0;
- RBMap<real_t, uint32_t> polygon_area_map;
-
- for (uint32_t rpp_index = 2; rpp_index < rr_polygon.points.size(); rpp_index++) {
- real_t face_area = Face3(rr_polygon.points[0].pos, rr_polygon.points[rpp_index - 1].pos, rr_polygon.points[rpp_index].pos).get_area();
-
- if (face_area == 0.0) {
- continue;
- }
- polygon_area_map[accumulated_polygon_area] = rpp_index;
- accumulated_polygon_area += face_area;
- }
- if (polygon_area_map.is_empty() || accumulated_polygon_area == 0) {
- // All faces have no real surface / no area.
- return Vector3();
- }
-
- real_t polygon_area_map_pos = Math::random(real_t(0), accumulated_polygon_area);
-
- RBMap<real_t, uint32_t>::Iterator polygon_E = polygon_area_map.find_closest(polygon_area_map_pos);
- ERR_FAIL_COND_V(!polygon_E, Vector3());
- uint32_t rrp_face_index = polygon_E->value;
- ERR_FAIL_UNSIGNED_INDEX_V(rrp_face_index, rr_polygon.points.size(), Vector3());
-
- const Face3 face(rr_polygon.points[0].pos, rr_polygon.points[rrp_face_index - 1].pos, rr_polygon.points[rrp_face_index].pos);
-
- Vector3 face_random_position = face.get_random_point_inside();
- return face_random_position;
-
- } else {
- uint32_t rrp_polygon_index = Math::random(int(0), region_polygons.size() - 1);
-
- const gd::Polygon &rr_polygon = region_polygons[rrp_polygon_index];
-
- uint32_t rrp_face_index = Math::random(int(2), rr_polygon.points.size() - 1);
-
- const Face3 face(rr_polygon.points[0].pos, rr_polygon.points[rrp_face_index - 1].pos, rr_polygon.points[rrp_face_index].pos);
-
- Vector3 face_random_position = face.get_random_point_inside();
- return face_random_position;
- }
+ return NavMeshQueries3D::polygons_get_random_point(get_polygons(), p_navigation_layers, p_uniformly);
}
bool NavRegion::sync() {
diff --git a/modules/navigation/nav_utils.h b/modules/navigation/nav_utils.h
index c3939e9979..ba4c44b748 100644
--- a/modules/navigation/nav_utils.h
+++ b/modules/navigation/nav_utils.h
@@ -98,6 +98,9 @@ struct Edge {
};
struct Polygon {
+ /// Id of the polygon in the map.
+ uint32_t id = UINT32_MAX;
+
/// Navigation region or link that contains this polygon.
const NavBase *owner = nullptr;
@@ -111,9 +114,11 @@ struct Polygon {
};
struct NavigationPoly {
- uint32_t self_id = 0;
/// This poly.
- const Polygon *poly;
+ const Polygon *poly = nullptr;
+
+ /// Index in the heap of traversable polygons.
+ uint32_t traversable_poly_index = UINT32_MAX;
/// Those 4 variables are used to travel the path backwards.
int back_navigation_poly_id = -1;
@@ -123,20 +128,44 @@ struct NavigationPoly {
/// The entry position of this poly.
Vector3 entry;
- /// The distance to the destination.
+ /// The distance traveled until now (g cost).
real_t traveled_distance = 0.0;
+ /// The distance to the destination (h cost).
+ real_t distance_to_destination = 0.0;
- NavigationPoly() { poly = nullptr; }
+ /// The total travel cost (f cost).
+ real_t total_travel_cost() const {
+ return traveled_distance + distance_to_destination;
+ }
- NavigationPoly(const Polygon *p_poly) :
- poly(p_poly) {}
+ bool operator==(const NavigationPoly &p_other) const {
+ return poly == p_other.poly;
+ }
- bool operator==(const NavigationPoly &other) const {
- return poly == other.poly;
+ bool operator!=(const NavigationPoly &p_other) const {
+ return !(*this == p_other);
}
+};
+
+struct NavPolyTravelCostGreaterThan {
+ // Returns `true` if the travel cost of `a` is higher than that of `b`.
+ bool operator()(const NavigationPoly *p_poly_a, const NavigationPoly *p_poly_b) const {
+ real_t f_cost_a = p_poly_a->total_travel_cost();
+ real_t h_cost_a = p_poly_a->distance_to_destination;
+ real_t f_cost_b = p_poly_b->total_travel_cost();
+ real_t h_cost_b = p_poly_b->distance_to_destination;
- bool operator!=(const NavigationPoly &other) const {
- return !operator==(other);
+ if (f_cost_a != f_cost_b) {
+ return f_cost_a > f_cost_b;
+ } else {
+ return h_cost_a > h_cost_b;
+ }
+ }
+};
+
+struct NavPolyHeapIndexer {
+ void operator()(NavigationPoly *p_poly, uint32_t p_heap_index) const {
+ p_poly->traversable_poly_index = p_heap_index;
}
};
@@ -146,6 +175,129 @@ struct ClosestPointQueryResult {
RID owner;
};
+template <typename T>
+struct NoopIndexer {
+ void operator()(const T &p_value, uint32_t p_index) {}
+};
+
+/**
+ * A max-heap implementation that notifies of element index changes.
+ */
+template <typename T, typename LessThan = Comparator<T>, typename Indexer = NoopIndexer<T>>
+class Heap {
+ LocalVector<T> _buffer;
+
+ LessThan _less_than;
+ Indexer _indexer;
+
+public:
+ void reserve(uint32_t p_size) {
+ _buffer.reserve(p_size);
+ }
+
+ uint32_t size() const {
+ return _buffer.size();
+ }
+
+ bool is_empty() const {
+ return _buffer.is_empty();
+ }
+
+ void push(const T &p_element) {
+ _buffer.push_back(p_element);
+ _indexer(p_element, _buffer.size() - 1);
+ _shift_up(_buffer.size() - 1);
+ }
+
+ T pop() {
+ ERR_FAIL_COND_V_MSG(_buffer.is_empty(), T(), "Can't pop an empty heap.");
+ T value = _buffer[0];
+ _indexer(value, UINT32_MAX);
+ if (_buffer.size() > 1) {
+ _buffer[0] = _buffer[_buffer.size() - 1];
+ _indexer(_buffer[0], 0);
+ _buffer.remove_at(_buffer.size() - 1);
+ _shift_down(0);
+ } else {
+ _buffer.remove_at(_buffer.size() - 1);
+ }
+ return value;
+ }
+
+ /**
+ * Update the position of the element in the heap if necessary.
+ */
+ void shift(uint32_t p_index) {
+ ERR_FAIL_UNSIGNED_INDEX_MSG(p_index, _buffer.size(), "Heap element index is out of range.");
+ if (!_shift_up(p_index)) {
+ _shift_down(p_index);
+ }
+ }
+
+ void clear() {
+ for (const T &value : _buffer) {
+ _indexer(value, UINT32_MAX);
+ }
+ _buffer.clear();
+ }
+
+ Heap() {}
+
+ Heap(const LessThan &p_less_than) :
+ _less_than(p_less_than) {}
+
+ Heap(const Indexer &p_indexer) :
+ _indexer(p_indexer) {}
+
+ Heap(const LessThan &p_less_than, const Indexer &p_indexer) :
+ _less_than(p_less_than), _indexer(p_indexer) {}
+
+private:
+ bool _shift_up(uint32_t p_index) {
+ T value = _buffer[p_index];
+ uint32_t current_index = p_index;
+ uint32_t parent_index = (current_index - 1) / 2;
+ while (current_index > 0 && _less_than(_buffer[parent_index], value)) {
+ _buffer[current_index] = _buffer[parent_index];
+ _indexer(_buffer[current_index], current_index);
+ current_index = parent_index;
+ parent_index = (current_index - 1) / 2;
+ }
+ if (current_index != p_index) {
+ _buffer[current_index] = value;
+ _indexer(value, current_index);
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ bool _shift_down(uint32_t p_index) {
+ T value = _buffer[p_index];
+ uint32_t current_index = p_index;
+ uint32_t child_index = 2 * current_index + 1;
+ while (child_index < _buffer.size()) {
+ if (child_index + 1 < _buffer.size() &&
+ _less_than(_buffer[child_index], _buffer[child_index + 1])) {
+ child_index++;
+ }
+ if (_less_than(_buffer[child_index], value)) {
+ break;
+ }
+ _buffer[current_index] = _buffer[child_index];
+ _indexer(_buffer[current_index], current_index);
+ current_index = child_index;
+ child_index = 2 * current_index + 1;
+ }
+ if (current_index != p_index) {
+ _buffer[current_index] = value;
+ _indexer(value, current_index);
+ return true;
+ } else {
+ return false;
+ }
+ }
+};
} // namespace gd
#endif // NAV_UTILS_H
diff --git a/modules/noise/noise_texture_2d.cpp b/modules/noise/noise_texture_2d.cpp
index 0960b2ad36..b55b1141e1 100644
--- a/modules/noise/noise_texture_2d.cpp
+++ b/modules/noise/noise_texture_2d.cpp
@@ -194,6 +194,9 @@ Ref<Image> NoiseTexture2D::_modulate_with_gradient(Ref<Image> p_image, Ref<Gradi
void NoiseTexture2D::_update_texture() {
bool use_thread = true;
+#ifndef THREADS_ENABLED
+ use_thread = false;
+#endif
if (first_time) {
use_thread = false;
first_time = false;
diff --git a/modules/noise/noise_texture_3d.cpp b/modules/noise/noise_texture_3d.cpp
index 9047491344..e3cca8a09f 100644
--- a/modules/noise/noise_texture_3d.cpp
+++ b/modules/noise/noise_texture_3d.cpp
@@ -187,6 +187,9 @@ Ref<Image> NoiseTexture3D::_modulate_with_gradient(Ref<Image> p_image, Ref<Gradi
void NoiseTexture3D::_update_texture() {
bool use_thread = true;
+#ifndef THREADS_ENABLED
+ use_thread = false;
+#endif
if (first_time) {
use_thread = false;
first_time = false;
diff --git a/modules/noise/tests/test_noise_texture_2d.h b/modules/noise/tests/test_noise_texture_2d.h
index 938e8fd6ab..0d18d66e74 100644
--- a/modules/noise/tests/test_noise_texture_2d.h
+++ b/modules/noise/tests/test_noise_texture_2d.h
@@ -135,7 +135,7 @@ TEST_CASE("[NoiseTexture][SceneTree] Getter and setter") {
noise_texture->set_noise(noise);
CHECK(noise_texture->get_noise() == noise);
noise_texture->set_noise(nullptr);
- CHECK(noise_texture->get_noise() == nullptr);
+ CHECK(noise_texture->get_noise().is_null());
noise_texture->set_width(8);
noise_texture->set_height(4);
@@ -190,7 +190,7 @@ TEST_CASE("[NoiseTexture][SceneTree] Getter and setter") {
noise_texture->set_color_ramp(gradient);
CHECK(noise_texture->get_color_ramp() == gradient);
noise_texture->set_color_ramp(nullptr);
- CHECK(noise_texture->get_color_ramp() == nullptr);
+ CHECK(noise_texture->get_color_ramp().is_null());
}
TEST_CASE("[NoiseTexture2D][SceneTree] Generating a basic noise texture with mipmaps and color ramp modulation") {
diff --git a/modules/noise/tests/test_noise_texture_3d.h b/modules/noise/tests/test_noise_texture_3d.h
index b708eac43b..434cd20a08 100644
--- a/modules/noise/tests/test_noise_texture_3d.h
+++ b/modules/noise/tests/test_noise_texture_3d.h
@@ -133,7 +133,7 @@ TEST_CASE("[NoiseTexture][SceneTree] Getter and setter") {
noise_texture->set_noise(noise);
CHECK(noise_texture->get_noise() == noise);
noise_texture->set_noise(nullptr);
- CHECK(noise_texture->get_noise() == nullptr);
+ CHECK(noise_texture->get_noise().is_null());
noise_texture->set_width(8);
noise_texture->set_height(4);
@@ -174,7 +174,7 @@ TEST_CASE("[NoiseTexture][SceneTree] Getter and setter") {
noise_texture->set_color_ramp(gradient);
CHECK(noise_texture->get_color_ramp() == gradient);
noise_texture->set_color_ramp(nullptr);
- CHECK(noise_texture->get_color_ramp() == nullptr);
+ CHECK(noise_texture->get_color_ramp().is_null());
}
TEST_CASE("[NoiseTexture3D][SceneTree] Generating a basic noise texture with mipmaps and color ramp modulation") {
diff --git a/modules/openxr/openxr_api.cpp b/modules/openxr/openxr_api.cpp
index ecf7c05789..a19a75e722 100644
--- a/modules/openxr/openxr_api.cpp
+++ b/modules/openxr/openxr_api.cpp
@@ -271,17 +271,14 @@ OpenXRAPI *OpenXRAPI::singleton = nullptr;
Vector<OpenXRExtensionWrapper *> OpenXRAPI::registered_extension_wrappers;
bool OpenXRAPI::openxr_is_enabled(bool p_check_run_in_editor) {
- // @TODO we need an overrule switch so we can force enable openxr, i.e run "godot --openxr_enabled"
-
- if (Engine::get_singleton()->is_editor_hint() && p_check_run_in_editor) {
- // Disabled for now, using XR inside of the editor we'll be working on during the coming months.
- return false;
- } else {
- if (XRServer::get_xr_mode() == XRServer::XRMODE_DEFAULT) {
- return GLOBAL_GET("xr/openxr/enabled");
+ if (XRServer::get_xr_mode() == XRServer::XRMODE_DEFAULT) {
+ if (Engine::get_singleton()->is_editor_hint() && p_check_run_in_editor) {
+ return GLOBAL_GET("xr/openxr/enabled.editor");
} else {
- return XRServer::get_xr_mode() == XRServer::XRMODE_ON;
+ return GLOBAL_GET("xr/openxr/enabled");
}
+ } else {
+ return XRServer::get_xr_mode() == XRServer::XRMODE_ON;
}
}
@@ -557,14 +554,11 @@ bool OpenXRAPI::create_instance() {
extension_ptrs.push_back(enabled_extensions[i].get_data());
}
- // Get our project name
- String project_name = GLOBAL_GET("application/config/name");
-
// Create our OpenXR instance
XrApplicationInfo application_info{
- "", // applicationName, we'll set this down below
+ "Godot Engine", // applicationName, if we're running a game we'll update this down below.
1, // applicationVersion, we don't currently have this
- "Godot Game Engine", // engineName
+ "Godot Engine", // engineName
VERSION_MAJOR * 10000 + VERSION_MINOR * 100 + VERSION_PATCH, // engineVersion 4.0 -> 40000, 4.0.1 -> 40001, 4.1 -> 40100, etc.
XR_API_VERSION_1_0 // apiVersion
};
@@ -588,7 +582,11 @@ bool OpenXRAPI::create_instance() {
extension_ptrs.ptr() // enabledExtensionNames
};
- copy_string_to_char_buffer(project_name, instance_create_info.applicationInfo.applicationName, XR_MAX_APPLICATION_NAME_SIZE);
+ // Get our project name
+ String project_name = GLOBAL_GET("application/config/name");
+ if (!project_name.is_empty()) {
+ copy_string_to_char_buffer(project_name, instance_create_info.applicationInfo.applicationName, XR_MAX_APPLICATION_NAME_SIZE);
+ }
XrResult result = xrCreateInstance(&instance_create_info, &instance);
ERR_FAIL_COND_V_MSG(XR_FAILED(result), false, "Failed to create XR instance.");
@@ -2583,7 +2581,6 @@ OpenXRAPI::OpenXRAPI() {
if (Engine::get_singleton()->is_editor_hint()) {
// Enabled OpenXR in the editor? Adjust our settings for the editor
-
} else {
// Load settings from project settings
int form_factor_setting = GLOBAL_GET("xr/openxr/form_factor");
diff --git a/modules/regex/doc_classes/RegEx.xml b/modules/regex/doc_classes/RegEx.xml
index ab74fce3a9..e12dc43b6f 100644
--- a/modules/regex/doc_classes/RegEx.xml
+++ b/modules/regex/doc_classes/RegEx.xml
@@ -58,15 +58,17 @@
<method name="compile">
<return type="int" enum="Error" />
<param index="0" name="pattern" type="String" />
+ <param index="1" name="show_error" type="bool" default="true" />
<description>
- Compiles and assign the search pattern to use. Returns [constant OK] if the compilation is successful. If an error is encountered, details are printed to standard output and an error is returned.
+ Compiles and assign the search pattern to use. Returns [constant OK] if the compilation is successful. If compilation fails, returns [constant FAILED] and when [param show_error] is [code]true[/code], details are printed to standard output.
</description>
</method>
<method name="create_from_string" qualifiers="static">
<return type="RegEx" />
<param index="0" name="pattern" type="String" />
+ <param index="1" name="show_error" type="bool" default="true" />
<description>
- Creates and compiles a new [RegEx] object.
+ Creates and compiles a new [RegEx] object. See also [method compile].
</description>
</method>
<method name="get_group_count" qualifiers="const">
diff --git a/modules/regex/regex.compat.inc b/modules/regex/regex.compat.inc
new file mode 100644
index 0000000000..0c380655a4
--- /dev/null
+++ b/modules/regex/regex.compat.inc
@@ -0,0 +1,46 @@
+/**************************************************************************/
+/* regex.compat.inc */
+/**************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/**************************************************************************/
+/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/**************************************************************************/
+
+#ifndef DISABLE_DEPRECATED
+
+Ref<RegEx> RegEx::_create_from_string_bind_compat_95212(const String &p_pattern) {
+ return create_from_string(p_pattern, true);
+}
+
+Error RegEx::_compile_bind_compat_95212(const String &p_pattern) {
+ return compile(p_pattern, true);
+}
+
+void RegEx::_bind_compatibility_methods() {
+ ClassDB::bind_compatibility_static_method("RegEx", D_METHOD("create_from_string", "pattern"), &RegEx::_create_from_string_bind_compat_95212);
+ ClassDB::bind_compatibility_method(D_METHOD("compile", "pattern"), &RegEx::_compile_bind_compat_95212);
+}
+
+#endif
diff --git a/modules/regex/regex.cpp b/modules/regex/regex.cpp
index 9f34a6ca6a..9c366408a0 100644
--- a/modules/regex/regex.cpp
+++ b/modules/regex/regex.cpp
@@ -29,6 +29,7 @@
/**************************************************************************/
#include "regex.h"
+#include "regex.compat.inc"
#include "core/os/memory.h"
@@ -53,8 +54,8 @@ int RegExMatch::_find(const Variant &p_name) const {
return -1;
}
return i;
- } else if (p_name.get_type() == Variant::STRING || p_name.get_type() == Variant::STRING_NAME) {
- HashMap<String, int>::ConstIterator found = names.find((String)p_name);
+ } else if (p_name.is_string()) {
+ HashMap<String, int>::ConstIterator found = names.find(p_name);
if (found) {
return found->value;
}
@@ -161,10 +162,10 @@ void RegEx::_pattern_info(uint32_t what, void *where) const {
pcre2_pattern_info_32((pcre2_code_32 *)code, what, where);
}
-Ref<RegEx> RegEx::create_from_string(const String &p_pattern) {
+Ref<RegEx> RegEx::create_from_string(const String &p_pattern, bool p_show_error) {
Ref<RegEx> ret;
ret.instantiate();
- ret->compile(p_pattern);
+ ret->compile(p_pattern, p_show_error);
return ret;
}
@@ -175,7 +176,7 @@ void RegEx::clear() {
}
}
-Error RegEx::compile(const String &p_pattern) {
+Error RegEx::compile(const String &p_pattern, bool p_show_error) {
pattern = p_pattern;
clear();
@@ -192,10 +193,12 @@ Error RegEx::compile(const String &p_pattern) {
pcre2_compile_context_free_32(cctx);
if (!code) {
- PCRE2_UCHAR32 buf[256];
- pcre2_get_error_message_32(err, buf, 256);
- String message = String::num(offset) + ": " + String((const char32_t *)buf);
- ERR_PRINT(message.utf8());
+ if (p_show_error) {
+ PCRE2_UCHAR32 buf[256];
+ pcre2_get_error_message_32(err, buf, 256);
+ String message = String::num(offset) + ": " + String((const char32_t *)buf);
+ ERR_PRINT(message.utf8());
+ }
return FAILED;
}
return OK;
@@ -395,10 +398,10 @@ RegEx::~RegEx() {
}
void RegEx::_bind_methods() {
- ClassDB::bind_static_method("RegEx", D_METHOD("create_from_string", "pattern"), &RegEx::create_from_string);
+ ClassDB::bind_static_method("RegEx", D_METHOD("create_from_string", "pattern", "show_error"), &RegEx::create_from_string, DEFVAL(true));
ClassDB::bind_method(D_METHOD("clear"), &RegEx::clear);
- ClassDB::bind_method(D_METHOD("compile", "pattern"), &RegEx::compile);
+ ClassDB::bind_method(D_METHOD("compile", "pattern", "show_error"), &RegEx::compile, DEFVAL(true));
ClassDB::bind_method(D_METHOD("search", "subject", "offset", "end"), &RegEx::search, DEFVAL(0), DEFVAL(-1));
ClassDB::bind_method(D_METHOD("search_all", "subject", "offset", "end"), &RegEx::search_all, DEFVAL(0), DEFVAL(-1));
ClassDB::bind_method(D_METHOD("sub", "subject", "replacement", "all", "offset", "end"), &RegEx::sub, DEFVAL(false), DEFVAL(0), DEFVAL(-1));
diff --git a/modules/regex/regex.h b/modules/regex/regex.h
index 13476d69de..cb8b0459ad 100644
--- a/modules/regex/regex.h
+++ b/modules/regex/regex.h
@@ -81,11 +81,17 @@ class RegEx : public RefCounted {
protected:
static void _bind_methods();
+#ifndef DISABLE_DEPRECATED
+ static Ref<RegEx> _create_from_string_bind_compat_95212(const String &p_pattern);
+ Error _compile_bind_compat_95212(const String &p_pattern);
+ static void _bind_compatibility_methods();
+#endif
+
public:
- static Ref<RegEx> create_from_string(const String &p_pattern);
+ static Ref<RegEx> create_from_string(const String &p_pattern, bool p_show_error = true);
void clear();
- Error compile(const String &p_pattern);
+ Error compile(const String &p_pattern, bool p_show_error = true);
Ref<RegExMatch> search(const String &p_subject, int p_offset = 0, int p_end = -1) const;
TypedArray<RegExMatch> search_all(const String &p_subject, int p_offset = 0, int p_end = -1) const;
diff --git a/modules/regex/tests/test_regex.h b/modules/regex/tests/test_regex.h
index af58e2487b..7e8e456341 100644
--- a/modules/regex/tests/test_regex.h
+++ b/modules/regex/tests/test_regex.h
@@ -80,32 +80,32 @@ TEST_CASE("[RegEx] Searching") {
REQUIRE(re.is_valid());
Ref<RegExMatch> match = re.search(s);
- REQUIRE(match != nullptr);
+ REQUIRE(match.is_valid());
CHECK(match->get_string(0) == "ea");
match = re.search(s, 1, 2);
- REQUIRE(match != nullptr);
+ REQUIRE(match.is_valid());
CHECK(match->get_string(0) == "e");
match = re.search(s, 2, 4);
- REQUIRE(match != nullptr);
+ REQUIRE(match.is_valid());
CHECK(match->get_string(0) == "a");
match = re.search(s, 3, 5);
- CHECK(match == nullptr);
+ CHECK(match.is_null());
match = re.search(s, 6, 2);
- CHECK(match == nullptr);
+ CHECK(match.is_null());
const Array all_results = re.search_all(s);
CHECK(all_results.size() == 2);
match = all_results[0];
- REQUIRE(match != nullptr);
+ REQUIRE(match.is_valid());
CHECK(match->get_string(0) == "ea");
match = all_results[1];
- REQUIRE(match != nullptr);
+ REQUIRE(match.is_valid());
CHECK(match->get_string(0) == "i");
CHECK(re.compile(numerics) == OK);
CHECK(re.is_valid());
- CHECK(re.search(s) == nullptr);
+ CHECK(re.search(s).is_null());
CHECK(re.search_all(s).size() == 0);
}
@@ -168,7 +168,7 @@ TEST_CASE("[RegEx] Uninitialized use") {
RegEx re;
ERR_PRINT_OFF;
- CHECK(re.search(s) == nullptr);
+ CHECK(re.search(s).is_null());
CHECK(re.search_all(s).size() == 0);
CHECK(re.sub(s, "") == "");
CHECK(re.get_group_count() == 0);
@@ -237,10 +237,10 @@ TEST_CASE("[RegEx] Invalid end position") {
const Array all_results = re.search_all(s, 0, 10);
CHECK(all_results.size() == 2);
match = all_results[0];
- REQUIRE(match != nullptr);
+ REQUIRE(match.is_valid());
CHECK(match->get_string(0) == String("o"));
match = all_results[1];
- REQUIRE(match != nullptr);
+ REQUIRE(match.is_valid());
CHECK(match->get_string(0) == String("o"));
CHECK(re.sub(s, "", true, 0, 10) == "Gdt");
@@ -251,7 +251,7 @@ TEST_CASE("[RegEx] Get match string list") {
RegEx re("(Go)(dot)");
Ref<RegExMatch> match = re.search(s);
- REQUIRE(match != nullptr);
+ REQUIRE(match.is_valid());
PackedStringArray result;
result.append("Godot");
result.append("Go");
@@ -265,14 +265,14 @@ TEST_CASE("[RegEx] Match start and end positions") {
RegEx re1("pattern");
REQUIRE(re1.is_valid());
Ref<RegExMatch> match = re1.search(s);
- REQUIRE(match != nullptr);
+ REQUIRE(match.is_valid());
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);
+ REQUIRE(match.is_valid());
CHECK(match->get_start("vowel") == 2);
CHECK(match->get_end("vowel") == 3);
}
@@ -307,7 +307,7 @@ TEST_CASE("[RegEx] Simple lookahead") {
RegEx re("o(?=t)");
REQUIRE(re.is_valid());
Ref<RegExMatch> match = re.search(s);
- REQUIRE(match != nullptr);
+ REQUIRE(match.is_valid());
CHECK(match->get_start(0) == 3);
CHECK(match->get_end(0) == 4);
}
@@ -325,12 +325,12 @@ TEST_CASE("[RegEx] Lookahead groups empty matches") {
CHECK(all_results.size() == 2);
match = all_results[0];
- REQUIRE(match != nullptr);
+ REQUIRE(match.is_valid());
CHECK(match->get_string(0) == String(""));
CHECK(match->get_string(1) == String("12"));
match = all_results[1];
- REQUIRE(match != nullptr);
+ REQUIRE(match.is_valid());
CHECK(match->get_string(0) == String(""));
CHECK(match->get_string(1) == String("2"));
}
@@ -341,7 +341,7 @@ TEST_CASE("[RegEx] Simple lookbehind") {
RegEx re("(?<=d)o");
REQUIRE(re.is_valid());
Ref<RegExMatch> match = re.search(s);
- REQUIRE(match != nullptr);
+ REQUIRE(match.is_valid());
CHECK(match->get_start(0) == 3);
CHECK(match->get_end(0) == 4);
}
@@ -355,22 +355,22 @@ TEST_CASE("[RegEx] Simple lookbehind search all") {
CHECK(all_results.size() == 4);
Ref<RegExMatch> match = all_results[0];
- REQUIRE(match != nullptr);
+ REQUIRE(match.is_valid());
CHECK(match->get_start(0) == 1);
CHECK(match->get_end(0) == 2);
match = all_results[1];
- REQUIRE(match != nullptr);
+ REQUIRE(match.is_valid());
CHECK(match->get_start(0) == 3);
CHECK(match->get_end(0) == 4);
match = all_results[2];
- REQUIRE(match != nullptr);
+ REQUIRE(match.is_valid());
CHECK(match->get_start(0) == 7);
CHECK(match->get_end(0) == 8);
match = all_results[3];
- REQUIRE(match != nullptr);
+ REQUIRE(match.is_valid());
CHECK(match->get_start(0) == 9);
CHECK(match->get_end(0) == 10);
}
@@ -386,7 +386,7 @@ TEST_CASE("[RegEx] Lookbehind groups empty matches") {
CHECK(all_results.size() == 3);
match = all_results[0];
- REQUIRE(match != nullptr);
+ REQUIRE(match.is_valid());
CHECK(match->get_start(0) == 2);
CHECK(match->get_end(0) == 2);
CHECK(match->get_start(1) == 1);
@@ -395,7 +395,7 @@ TEST_CASE("[RegEx] Lookbehind groups empty matches") {
CHECK(match->get_string(1) == String("b"));
match = all_results[1];
- REQUIRE(match != nullptr);
+ REQUIRE(match.is_valid());
CHECK(match->get_start(0) == 6);
CHECK(match->get_end(0) == 6);
CHECK(match->get_start(1) == 5);
@@ -404,7 +404,7 @@ TEST_CASE("[RegEx] Lookbehind groups empty matches") {
CHECK(match->get_string(1) == String("b"));
match = all_results[2];
- REQUIRE(match != nullptr);
+ REQUIRE(match.is_valid());
CHECK(match->get_start(0) == 8);
CHECK(match->get_end(0) == 8);
CHECK(match->get_start(1) == 7);
diff --git a/modules/text_server_adv/text_server_adv.cpp b/modules/text_server_adv/text_server_adv.cpp
index d0c22e9e4d..3322300dda 100644
--- a/modules/text_server_adv/text_server_adv.cpp
+++ b/modules/text_server_adv/text_server_adv.cpp
@@ -136,11 +136,12 @@ hb_position_t TextServerAdvanced::_bmp_get_glyph_h_advance(hb_font_t *p_font, vo
return 0;
}
- if (!bm_font->face->glyph_map.has(p_glyph)) {
+ HashMap<int32_t, FontGlyph>::Iterator E = bm_font->face->glyph_map.find(p_glyph);
+ if (!E) {
return 0;
}
- return bm_font->face->glyph_map[p_glyph].advance.x * 64;
+ return E->value.advance.x * 64;
}
hb_position_t TextServerAdvanced::_bmp_get_glyph_v_advance(hb_font_t *p_font, void *p_font_data, hb_codepoint_t p_glyph, void *p_user_data) {
@@ -150,11 +151,12 @@ hb_position_t TextServerAdvanced::_bmp_get_glyph_v_advance(hb_font_t *p_font, vo
return 0;
}
- if (!bm_font->face->glyph_map.has(p_glyph)) {
+ HashMap<int32_t, FontGlyph>::Iterator E = bm_font->face->glyph_map.find(p_glyph);
+ if (!E) {
return 0;
}
- return -bm_font->face->glyph_map[p_glyph].advance.y * 64;
+ return -E->value.advance.y * 64;
}
hb_position_t TextServerAdvanced::_bmp_get_glyph_h_kerning(hb_font_t *p_font, void *p_font_data, hb_codepoint_t p_left_glyph, hb_codepoint_t p_right_glyph, void *p_user_data) {
@@ -178,11 +180,12 @@ hb_bool_t TextServerAdvanced::_bmp_get_glyph_v_origin(hb_font_t *p_font, void *p
return false;
}
- if (!bm_font->face->glyph_map.has(p_glyph)) {
+ HashMap<int32_t, FontGlyph>::Iterator E = bm_font->face->glyph_map.find(p_glyph);
+ if (!E) {
return false;
}
- *r_x = bm_font->face->glyph_map[p_glyph].advance.x * 32;
+ *r_x = E->value.advance.x * 32;
*r_y = -bm_font->face->ascent * 64;
return true;
@@ -195,14 +198,15 @@ hb_bool_t TextServerAdvanced::_bmp_get_glyph_extents(hb_font_t *p_font, void *p_
return false;
}
- if (!bm_font->face->glyph_map.has(p_glyph)) {
+ HashMap<int32_t, FontGlyph>::Iterator E = bm_font->face->glyph_map.find(p_glyph);
+ if (!E) {
return false;
}
r_extents->x_bearing = 0;
r_extents->y_bearing = 0;
- r_extents->width = bm_font->face->glyph_map[p_glyph].rect.size.x * 64;
- r_extents->height = bm_font->face->glyph_map[p_glyph].rect.size.y * 64;
+ r_extents->width = E->value.rect.size.x * 64;
+ r_extents->height = E->value.rect.size.y * 64;
return true;
}
@@ -1188,18 +1192,21 @@ _FORCE_INLINE_ TextServerAdvanced::FontGlyph TextServerAdvanced::rasterize_bitma
/* Font Cache */
/*************************************************************************/
-_FORCE_INLINE_ bool TextServerAdvanced::_ensure_glyph(FontAdvanced *p_font_data, const Vector2i &p_size, int32_t p_glyph) const {
- ERR_FAIL_COND_V(!_ensure_cache_for_size(p_font_data, p_size), false);
+_FORCE_INLINE_ bool TextServerAdvanced::_ensure_glyph(FontAdvanced *p_font_data, const Vector2i &p_size, int32_t p_glyph, FontGlyph &r_glyph) const {
+ FontForSizeAdvanced *fd = nullptr;
+ ERR_FAIL_COND_V(!_ensure_cache_for_size(p_font_data, p_size, fd), false);
int32_t glyph_index = p_glyph & 0xffffff; // Remove subpixel shifts.
- FontForSizeAdvanced *fd = p_font_data->cache[p_size];
- if (fd->glyph_map.has(p_glyph)) {
- return fd->glyph_map[p_glyph].found;
+ HashMap<int32_t, FontGlyph>::Iterator E = fd->glyph_map.find(p_glyph);
+ if (E) {
+ r_glyph = E->value;
+ return E->value.found;
}
if (glyph_index == 0) { // Non graphical or invalid glyph, do not render.
- fd->glyph_map[p_glyph] = FontGlyph();
+ E = fd->glyph_map.insert(p_glyph, FontGlyph());
+ r_glyph = E->value;
return true;
}
@@ -1235,7 +1242,8 @@ _FORCE_INLINE_ bool TextServerAdvanced::_ensure_glyph(FontAdvanced *p_font_data,
int error = FT_Load_Glyph(fd->face, glyph_index, flags);
if (error) {
- fd->glyph_map[p_glyph] = FontGlyph();
+ E = fd->glyph_map.insert(p_glyph, FontGlyph());
+ r_glyph = E->value;
return false;
}
@@ -1339,17 +1347,22 @@ _FORCE_INLINE_ bool TextServerAdvanced::_ensure_glyph(FontAdvanced *p_font_data,
cleanup_stroker:
FT_Stroker_Done(stroker);
}
- fd->glyph_map[p_glyph] = gl;
+ E = fd->glyph_map.insert(p_glyph, gl);
+ r_glyph = E->value;
return gl.found;
}
#endif
- fd->glyph_map[p_glyph] = FontGlyph();
+ E = fd->glyph_map.insert(p_glyph, FontGlyph());
+ r_glyph = E->value;
return false;
}
-_FORCE_INLINE_ bool TextServerAdvanced::_ensure_cache_for_size(FontAdvanced *p_font_data, const Vector2i &p_size) const {
+_FORCE_INLINE_ bool TextServerAdvanced::_ensure_cache_for_size(FontAdvanced *p_font_data, const Vector2i &p_size, FontForSizeAdvanced *&r_cache_for_size) const {
ERR_FAIL_COND_V(p_size.x <= 0, false);
- if (p_font_data->cache.has(p_size)) {
+
+ HashMap<Vector2i, FontForSizeAdvanced *>::Iterator E = p_font_data->cache.find(p_size);
+ if (E) {
+ r_cache_for_size = E->value;
return true;
}
@@ -1840,7 +1853,8 @@ _FORCE_INLINE_ bool TextServerAdvanced::_ensure_cache_for_size(FontAdvanced *p_f
// Init bitmap font.
fd->hb_handle = _bmp_font_create(fd, nullptr);
}
- p_font_data->cache[p_size] = fd;
+ p_font_data->cache.insert(p_size, fd);
+ r_cache_for_size = fd;
return true;
}
@@ -1864,9 +1878,10 @@ hb_font_t *TextServerAdvanced::_font_get_hb_handle(const RID &p_font_rid, int64_
MutexLock lock(fd->mutex);
Vector2i size = _get_size(fd, p_size);
- ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size), nullptr);
+ FontForSizeAdvanced *ffsd = nullptr;
+ ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size, ffsd), nullptr);
- return fd->cache[size]->hb_handle;
+ return ffsd->hb_handle;
}
RID TextServerAdvanced::_create_font() {
@@ -1989,7 +2004,8 @@ void TextServerAdvanced::_font_set_style(const RID &p_font_rid, BitField<FontSty
MutexLock lock(fd->mutex);
Vector2i size = _get_size(fd, 16);
- ERR_FAIL_COND(!_ensure_cache_for_size(fd, size));
+ FontForSizeAdvanced *ffsd = nullptr;
+ ERR_FAIL_COND(!_ensure_cache_for_size(fd, size, ffsd));
fd->style_flags = p_style;
}
@@ -1999,7 +2015,8 @@ BitField<TextServer::FontStyle> TextServerAdvanced::_font_get_style(const RID &p
MutexLock lock(fd->mutex);
Vector2i size = _get_size(fd, 16);
- ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size), 0);
+ FontForSizeAdvanced *ffsd = nullptr;
+ ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size, ffsd), 0);
return fd->style_flags;
}
@@ -2009,7 +2026,8 @@ void TextServerAdvanced::_font_set_style_name(const RID &p_font_rid, const Strin
MutexLock lock(fd->mutex);
Vector2i size = _get_size(fd, 16);
- ERR_FAIL_COND(!_ensure_cache_for_size(fd, size));
+ FontForSizeAdvanced *ffsd = nullptr;
+ ERR_FAIL_COND(!_ensure_cache_for_size(fd, size, ffsd));
fd->style_name = p_name;
}
@@ -2019,7 +2037,8 @@ String TextServerAdvanced::_font_get_style_name(const RID &p_font_rid) const {
MutexLock lock(fd->mutex);
Vector2i size = _get_size(fd, 16);
- ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size), String());
+ FontForSizeAdvanced *ffsd = nullptr;
+ ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size, ffsd), String());
return fd->style_name;
}
@@ -2029,7 +2048,8 @@ void TextServerAdvanced::_font_set_weight(const RID &p_font_rid, int64_t p_weigh
MutexLock lock(fd->mutex);
Vector2i size = _get_size(fd, 16);
- ERR_FAIL_COND(!_ensure_cache_for_size(fd, size));
+ FontForSizeAdvanced *ffsd = nullptr;
+ ERR_FAIL_COND(!_ensure_cache_for_size(fd, size, ffsd));
fd->weight = CLAMP(p_weight, 100, 999);
}
@@ -2039,7 +2059,8 @@ int64_t TextServerAdvanced::_font_get_weight(const RID &p_font_rid) const {
MutexLock lock(fd->mutex);
Vector2i size = _get_size(fd, 16);
- ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size), 400);
+ FontForSizeAdvanced *ffsd = nullptr;
+ ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size, ffsd), 400);
return fd->weight;
}
@@ -2049,7 +2070,8 @@ void TextServerAdvanced::_font_set_stretch(const RID &p_font_rid, int64_t p_stre
MutexLock lock(fd->mutex);
Vector2i size = _get_size(fd, 16);
- ERR_FAIL_COND(!_ensure_cache_for_size(fd, size));
+ FontForSizeAdvanced *ffsd = nullptr;
+ ERR_FAIL_COND(!_ensure_cache_for_size(fd, size, ffsd));
fd->stretch = CLAMP(p_stretch, 50, 200);
}
@@ -2059,7 +2081,8 @@ int64_t TextServerAdvanced::_font_get_stretch(const RID &p_font_rid) const {
MutexLock lock(fd->mutex);
Vector2i size = _get_size(fd, 16);
- ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size), 100);
+ FontForSizeAdvanced *ffsd = nullptr;
+ ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size, ffsd), 100);
return fd->stretch;
}
@@ -2069,7 +2092,8 @@ void TextServerAdvanced::_font_set_name(const RID &p_font_rid, const String &p_n
MutexLock lock(fd->mutex);
Vector2i size = _get_size(fd, 16);
- ERR_FAIL_COND(!_ensure_cache_for_size(fd, size));
+ FontForSizeAdvanced *ffsd = nullptr;
+ ERR_FAIL_COND(!_ensure_cache_for_size(fd, size, ffsd));
fd->font_name = p_name;
}
@@ -2079,7 +2103,8 @@ String TextServerAdvanced::_font_get_name(const RID &p_font_rid) const {
MutexLock lock(fd->mutex);
Vector2i size = _get_size(fd, 16);
- ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size), String());
+ FontForSizeAdvanced *ffsd = nullptr;
+ ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size, ffsd), String());
return fd->font_name;
}
@@ -2089,9 +2114,10 @@ Dictionary TextServerAdvanced::_font_get_ot_name_strings(const RID &p_font_rid)
MutexLock lock(fd->mutex);
Vector2i size = _get_size(fd, 16);
- ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size), Dictionary());
+ FontForSizeAdvanced *ffsd = nullptr;
+ ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size, ffsd), Dictionary());
- hb_face_t *hb_face = hb_font_get_face(fd->cache[size]->hb_handle);
+ hb_face_t *hb_face = hb_font_get_face(ffsd->hb_handle);
unsigned int num_entries = 0;
const hb_ot_name_entry_t *names = hb_ot_name_list_names(hb_face, &num_entries);
@@ -2599,8 +2625,9 @@ void TextServerAdvanced::_font_set_ascent(const RID &p_font_rid, int64_t p_size,
MutexLock lock(fd->mutex);
Vector2i size = _get_size(fd, p_size);
- ERR_FAIL_COND(!_ensure_cache_for_size(fd, size));
- fd->cache[size]->ascent = p_ascent;
+ FontForSizeAdvanced *ffsd = nullptr;
+ ERR_FAIL_COND(!_ensure_cache_for_size(fd, size, ffsd));
+ ffsd->ascent = p_ascent;
}
double TextServerAdvanced::_font_get_ascent(const RID &p_font_rid, int64_t p_size) const {
@@ -2610,18 +2637,19 @@ double TextServerAdvanced::_font_get_ascent(const RID &p_font_rid, int64_t p_siz
MutexLock lock(fd->mutex);
Vector2i size = _get_size(fd, p_size);
- ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size), 0.0);
+ FontForSizeAdvanced *ffsd = nullptr;
+ ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size, ffsd), 0.0);
if (fd->msdf) {
- return fd->cache[size]->ascent * (double)p_size / (double)fd->msdf_source_size;
+ return ffsd->ascent * (double)p_size / (double)fd->msdf_source_size;
} else if (fd->fixed_size > 0 && fd->fixed_size_scale_mode != FIXED_SIZE_SCALE_DISABLE && size.x != p_size) {
if (fd->fixed_size_scale_mode == FIXED_SIZE_SCALE_ENABLED) {
- return fd->cache[size]->ascent * (double)p_size / (double)fd->fixed_size;
+ return ffsd->ascent * (double)p_size / (double)fd->fixed_size;
} else {
- return fd->cache[size]->ascent * Math::round((double)p_size / (double)fd->fixed_size);
+ return ffsd->ascent * Math::round((double)p_size / (double)fd->fixed_size);
}
} else {
- return fd->cache[size]->ascent;
+ return ffsd->ascent;
}
}
@@ -2631,8 +2659,9 @@ void TextServerAdvanced::_font_set_descent(const RID &p_font_rid, int64_t p_size
Vector2i size = _get_size(fd, p_size);
- ERR_FAIL_COND(!_ensure_cache_for_size(fd, size));
- fd->cache[size]->descent = p_descent;
+ FontForSizeAdvanced *ffsd = nullptr;
+ ERR_FAIL_COND(!_ensure_cache_for_size(fd, size, ffsd));
+ ffsd->descent = p_descent;
}
double TextServerAdvanced::_font_get_descent(const RID &p_font_rid, int64_t p_size) const {
@@ -2642,18 +2671,19 @@ double TextServerAdvanced::_font_get_descent(const RID &p_font_rid, int64_t p_si
MutexLock lock(fd->mutex);
Vector2i size = _get_size(fd, p_size);
- ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size), 0.0);
+ FontForSizeAdvanced *ffsd = nullptr;
+ ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size, ffsd), 0.0);
if (fd->msdf) {
- return fd->cache[size]->descent * (double)p_size / (double)fd->msdf_source_size;
+ return ffsd->descent * (double)p_size / (double)fd->msdf_source_size;
} else if (fd->fixed_size > 0 && fd->fixed_size_scale_mode != FIXED_SIZE_SCALE_DISABLE && size.x != p_size) {
if (fd->fixed_size_scale_mode == FIXED_SIZE_SCALE_ENABLED) {
- return fd->cache[size]->descent * (double)p_size / (double)fd->fixed_size;
+ return ffsd->descent * (double)p_size / (double)fd->fixed_size;
} else {
- return fd->cache[size]->descent * Math::round((double)p_size / (double)fd->fixed_size);
+ return ffsd->descent * Math::round((double)p_size / (double)fd->fixed_size);
}
} else {
- return fd->cache[size]->descent;
+ return ffsd->descent;
}
}
@@ -2664,8 +2694,9 @@ void TextServerAdvanced::_font_set_underline_position(const RID &p_font_rid, int
MutexLock lock(fd->mutex);
Vector2i size = _get_size(fd, p_size);
- ERR_FAIL_COND(!_ensure_cache_for_size(fd, size));
- fd->cache[size]->underline_position = p_underline_position;
+ FontForSizeAdvanced *ffsd = nullptr;
+ ERR_FAIL_COND(!_ensure_cache_for_size(fd, size, ffsd));
+ ffsd->underline_position = p_underline_position;
}
double TextServerAdvanced::_font_get_underline_position(const RID &p_font_rid, int64_t p_size) const {
@@ -2675,18 +2706,19 @@ double TextServerAdvanced::_font_get_underline_position(const RID &p_font_rid, i
MutexLock lock(fd->mutex);
Vector2i size = _get_size(fd, p_size);
- ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size), 0.0);
+ FontForSizeAdvanced *ffsd = nullptr;
+ ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size, ffsd), 0.0);
if (fd->msdf) {
- return fd->cache[size]->underline_position * (double)p_size / (double)fd->msdf_source_size;
+ return ffsd->underline_position * (double)p_size / (double)fd->msdf_source_size;
} else if (fd->fixed_size > 0 && fd->fixed_size_scale_mode != FIXED_SIZE_SCALE_DISABLE && size.x != p_size) {
if (fd->fixed_size_scale_mode == FIXED_SIZE_SCALE_ENABLED) {
- return fd->cache[size]->underline_position * (double)p_size / (double)fd->fixed_size;
+ return ffsd->underline_position * (double)p_size / (double)fd->fixed_size;
} else {
- return fd->cache[size]->underline_position * Math::round((double)p_size / (double)fd->fixed_size);
+ return ffsd->underline_position * Math::round((double)p_size / (double)fd->fixed_size);
}
} else {
- return fd->cache[size]->underline_position;
+ return ffsd->underline_position;
}
}
@@ -2697,8 +2729,9 @@ void TextServerAdvanced::_font_set_underline_thickness(const RID &p_font_rid, in
MutexLock lock(fd->mutex);
Vector2i size = _get_size(fd, p_size);
- ERR_FAIL_COND(!_ensure_cache_for_size(fd, size));
- fd->cache[size]->underline_thickness = p_underline_thickness;
+ FontForSizeAdvanced *ffsd = nullptr;
+ ERR_FAIL_COND(!_ensure_cache_for_size(fd, size, ffsd));
+ ffsd->underline_thickness = p_underline_thickness;
}
double TextServerAdvanced::_font_get_underline_thickness(const RID &p_font_rid, int64_t p_size) const {
@@ -2708,18 +2741,19 @@ double TextServerAdvanced::_font_get_underline_thickness(const RID &p_font_rid,
MutexLock lock(fd->mutex);
Vector2i size = _get_size(fd, p_size);
- ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size), 0.0);
+ FontForSizeAdvanced *ffsd = nullptr;
+ ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size, ffsd), 0.0);
if (fd->msdf) {
- return fd->cache[size]->underline_thickness * (double)p_size / (double)fd->msdf_source_size;
+ return ffsd->underline_thickness * (double)p_size / (double)fd->msdf_source_size;
} else if (fd->fixed_size > 0 && fd->fixed_size_scale_mode != FIXED_SIZE_SCALE_DISABLE && size.x != p_size) {
if (fd->fixed_size_scale_mode == FIXED_SIZE_SCALE_ENABLED) {
- return fd->cache[size]->underline_thickness * (double)p_size / (double)fd->fixed_size;
+ return ffsd->underline_thickness * (double)p_size / (double)fd->fixed_size;
} else {
- return fd->cache[size]->underline_thickness * Math::round((double)p_size / (double)fd->fixed_size);
+ return ffsd->underline_thickness * Math::round((double)p_size / (double)fd->fixed_size);
}
} else {
- return fd->cache[size]->underline_thickness;
+ return ffsd->underline_thickness;
}
}
@@ -2730,13 +2764,15 @@ void TextServerAdvanced::_font_set_scale(const RID &p_font_rid, int64_t p_size,
MutexLock lock(fd->mutex);
Vector2i size = _get_size(fd, p_size);
- ERR_FAIL_COND(!_ensure_cache_for_size(fd, size));
+ FontForSizeAdvanced *ffsd = nullptr;
+ ERR_FAIL_COND(!_ensure_cache_for_size(fd, size, ffsd));
+
#ifdef MODULE_FREETYPE_ENABLED
- if (fd->cache[size]->face) {
+ if (ffsd->face) {
return; // Do not override scale for dynamic fonts, it's calculated automatically.
}
#endif
- fd->cache[size]->scale = p_scale;
+ ffsd->scale = p_scale;
}
double TextServerAdvanced::_font_get_scale(const RID &p_font_rid, int64_t p_size) const {
@@ -2746,18 +2782,19 @@ double TextServerAdvanced::_font_get_scale(const RID &p_font_rid, int64_t p_size
MutexLock lock(fd->mutex);
Vector2i size = _get_size(fd, p_size);
- ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size), 0.0);
+ FontForSizeAdvanced *ffsd = nullptr;
+ ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size, ffsd), 0.0);
if (fd->msdf) {
- return fd->cache[size]->scale * (double)p_size / (double)fd->msdf_source_size;
+ return ffsd->scale * (double)p_size / (double)fd->msdf_source_size;
} else if (fd->fixed_size > 0 && fd->fixed_size_scale_mode != FIXED_SIZE_SCALE_DISABLE && size.x != p_size) {
if (fd->fixed_size_scale_mode == FIXED_SIZE_SCALE_ENABLED) {
- return fd->cache[size]->scale * (double)p_size / (double)fd->fixed_size;
+ return ffsd->scale * (double)p_size / (double)fd->fixed_size;
} else {
- return fd->cache[size]->scale * Math::round((double)p_size / (double)fd->fixed_size);
+ return ffsd->scale * Math::round((double)p_size / (double)fd->fixed_size);
}
} else {
- return fd->cache[size]->scale / fd->cache[size]->oversampling;
+ return ffsd->scale / ffsd->oversampling;
}
}
@@ -2768,9 +2805,10 @@ int64_t TextServerAdvanced::_font_get_texture_count(const RID &p_font_rid, const
MutexLock lock(fd->mutex);
Vector2i size = _get_size_outline(fd, p_size);
- ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size), 0);
+ FontForSizeAdvanced *ffsd = nullptr;
+ ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size, ffsd), 0);
- return fd->cache[size]->textures.size();
+ return ffsd->textures.size();
}
void TextServerAdvanced::_font_clear_textures(const RID &p_font_rid, const Vector2i &p_size) {
@@ -2779,8 +2817,9 @@ void TextServerAdvanced::_font_clear_textures(const RID &p_font_rid, const Vecto
MutexLock lock(fd->mutex);
Vector2i size = _get_size_outline(fd, p_size);
- ERR_FAIL_COND(!_ensure_cache_for_size(fd, size));
- fd->cache[size]->textures.clear();
+ FontForSizeAdvanced *ffsd = nullptr;
+ ERR_FAIL_COND(!_ensure_cache_for_size(fd, size, ffsd));
+ ffsd->textures.clear();
}
void TextServerAdvanced::_font_remove_texture(const RID &p_font_rid, const Vector2i &p_size, int64_t p_texture_index) {
@@ -2789,10 +2828,11 @@ void TextServerAdvanced::_font_remove_texture(const RID &p_font_rid, const Vecto
MutexLock lock(fd->mutex);
Vector2i size = _get_size_outline(fd, p_size);
- ERR_FAIL_COND(!_ensure_cache_for_size(fd, size));
- ERR_FAIL_INDEX(p_texture_index, fd->cache[size]->textures.size());
+ FontForSizeAdvanced *ffsd = nullptr;
+ ERR_FAIL_COND(!_ensure_cache_for_size(fd, size, ffsd));
+ ERR_FAIL_INDEX(p_texture_index, ffsd->textures.size());
- fd->cache[size]->textures.remove_at(p_texture_index);
+ ffsd->textures.remove_at(p_texture_index);
}
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) {
@@ -2802,13 +2842,14 @@ void TextServerAdvanced::_font_set_texture_image(const RID &p_font_rid, const Ve
MutexLock lock(fd->mutex);
Vector2i size = _get_size_outline(fd, p_size);
- ERR_FAIL_COND(!_ensure_cache_for_size(fd, size));
+ FontForSizeAdvanced *ffsd = nullptr;
+ ERR_FAIL_COND(!_ensure_cache_for_size(fd, size, ffsd));
ERR_FAIL_COND(p_texture_index < 0);
- if (p_texture_index >= fd->cache[size]->textures.size()) {
- fd->cache[size]->textures.resize(p_texture_index + 1);
+ if (p_texture_index >= ffsd->textures.size()) {
+ ffsd->textures.resize(p_texture_index + 1);
}
- ShelfPackTexture &tex = fd->cache[size]->textures.write[p_texture_index];
+ ShelfPackTexture &tex = ffsd->textures.write[p_texture_index];
tex.image = p_image;
tex.texture_w = p_image->get_width();
@@ -2829,10 +2870,11 @@ Ref<Image> TextServerAdvanced::_font_get_texture_image(const RID &p_font_rid, co
MutexLock lock(fd->mutex);
Vector2i size = _get_size_outline(fd, p_size);
- ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size), Ref<Image>());
- ERR_FAIL_INDEX_V(p_texture_index, fd->cache[size]->textures.size(), Ref<Image>());
+ FontForSizeAdvanced *ffsd = nullptr;
+ ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size, ffsd), Ref<Image>());
+ ERR_FAIL_INDEX_V(p_texture_index, ffsd->textures.size(), Ref<Image>());
- const ShelfPackTexture &tex = fd->cache[size]->textures[p_texture_index];
+ const ShelfPackTexture &tex = ffsd->textures[p_texture_index];
return tex.image;
}
@@ -2843,13 +2885,14 @@ void TextServerAdvanced::_font_set_texture_offsets(const RID &p_font_rid, const
MutexLock lock(fd->mutex);
Vector2i size = _get_size_outline(fd, p_size);
- ERR_FAIL_COND(!_ensure_cache_for_size(fd, size));
+ FontForSizeAdvanced *ffsd = nullptr;
+ ERR_FAIL_COND(!_ensure_cache_for_size(fd, size, ffsd));
ERR_FAIL_COND(p_texture_index < 0);
- if (p_texture_index >= fd->cache[size]->textures.size()) {
- fd->cache[size]->textures.resize(p_texture_index + 1);
+ if (p_texture_index >= ffsd->textures.size()) {
+ ffsd->textures.resize(p_texture_index + 1);
}
- ShelfPackTexture &tex = fd->cache[size]->textures.write[p_texture_index];
+ ShelfPackTexture &tex = ffsd->textures.write[p_texture_index];
tex.shelves.clear();
for (int32_t i = 0; i < p_offsets.size(); i += 4) {
tex.shelves.push_back(Shelf(p_offsets[i], p_offsets[i + 1], p_offsets[i + 2], p_offsets[i + 3]));
@@ -2862,10 +2905,11 @@ PackedInt32Array TextServerAdvanced::_font_get_texture_offsets(const RID &p_font
MutexLock lock(fd->mutex);
Vector2i size = _get_size_outline(fd, p_size);
- ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size), PackedInt32Array());
- ERR_FAIL_INDEX_V(p_texture_index, fd->cache[size]->textures.size(), PackedInt32Array());
+ FontForSizeAdvanced *ffsd = nullptr;
+ ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size, ffsd), PackedInt32Array());
+ ERR_FAIL_INDEX_V(p_texture_index, ffsd->textures.size(), PackedInt32Array());
- const ShelfPackTexture &tex = fd->cache[size]->textures[p_texture_index];
+ const ShelfPackTexture &tex = ffsd->textures[p_texture_index];
PackedInt32Array ret;
ret.resize(tex.shelves.size() * 4);
@@ -2887,10 +2931,11 @@ PackedInt32Array TextServerAdvanced::_font_get_glyph_list(const RID &p_font_rid,
MutexLock lock(fd->mutex);
Vector2i size = _get_size_outline(fd, p_size);
- ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size), PackedInt32Array());
+ FontForSizeAdvanced *ffsd = nullptr;
+ ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size, ffsd), PackedInt32Array());
PackedInt32Array ret;
- const HashMap<int32_t, FontGlyph> &gl = fd->cache[size]->glyph_map;
+ const HashMap<int32_t, FontGlyph> &gl = ffsd->glyph_map;
for (const KeyValue<int32_t, FontGlyph> &E : gl) {
ret.push_back(E.key);
}
@@ -2903,9 +2948,10 @@ void TextServerAdvanced::_font_clear_glyphs(const RID &p_font_rid, const Vector2
MutexLock lock(fd->mutex);
Vector2i size = _get_size_outline(fd, p_size);
- ERR_FAIL_COND(!_ensure_cache_for_size(fd, size));
+ FontForSizeAdvanced *ffsd = nullptr;
+ ERR_FAIL_COND(!_ensure_cache_for_size(fd, size, ffsd));
- fd->cache[size]->glyph_map.clear();
+ ffsd->glyph_map.clear();
}
void TextServerAdvanced::_font_remove_glyph(const RID &p_font_rid, const Vector2i &p_size, int64_t p_glyph) {
@@ -2914,9 +2960,10 @@ void TextServerAdvanced::_font_remove_glyph(const RID &p_font_rid, const Vector2
MutexLock lock(fd->mutex);
Vector2i size = _get_size_outline(fd, p_size);
- ERR_FAIL_COND(!_ensure_cache_for_size(fd, size));
+ FontForSizeAdvanced *ffsd = nullptr;
+ ERR_FAIL_COND(!_ensure_cache_for_size(fd, size, ffsd));
- fd->cache[size]->glyph_map.erase(p_glyph);
+ ffsd->glyph_map.erase(p_glyph);
}
double TextServerAdvanced::_get_extra_advance(RID p_font_rid, int p_font_size) const {
@@ -2940,22 +2987,22 @@ Vector2 TextServerAdvanced::_font_get_glyph_advance(const RID &p_font_rid, int64
MutexLock lock(fd->mutex);
Vector2i size = _get_size(fd, p_size);
- ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size), Vector2());
+ FontForSizeAdvanced *ffsd = nullptr;
+ ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size, ffsd), Vector2());
int mod = 0;
if (fd->antialiasing == FONT_ANTIALIASING_LCD) {
- TextServer::FontLCDSubpixelLayout layout = (TextServer::FontLCDSubpixelLayout)(int)GLOBAL_GET("gui/theme/lcd_subpixel_layout");
+ TextServer::FontLCDSubpixelLayout layout = lcd_subpixel_layout.get();
if (layout != FONT_LCD_SUBPIXEL_LAYOUT_NONE) {
mod = (layout << 24);
}
}
- if (!_ensure_glyph(fd, size, p_glyph | mod)) {
+ FontGlyph fgl;
+ if (!_ensure_glyph(fd, size, p_glyph | mod, fgl)) {
return Vector2(); // Invalid or non graphicl glyph, do not display errors.
}
- const HashMap<int32_t, FontGlyph> &gl = fd->cache[size]->glyph_map;
-
Vector2 ea;
if (fd->embolden != 0.0) {
ea.x = fd->embolden * double(size.x) / 64.0;
@@ -2963,17 +3010,17 @@ Vector2 TextServerAdvanced::_font_get_glyph_advance(const RID &p_font_rid, int64
double scale = _font_get_scale(p_font_rid, p_size);
if (fd->msdf) {
- return (gl[p_glyph | mod].advance + ea) * (double)p_size / (double)fd->msdf_source_size;
+ return (fgl.advance + ea) * (double)p_size / (double)fd->msdf_source_size;
} else if (fd->fixed_size > 0 && fd->fixed_size_scale_mode != FIXED_SIZE_SCALE_DISABLE && size.x != p_size) {
if (fd->fixed_size_scale_mode == FIXED_SIZE_SCALE_ENABLED) {
- return (gl[p_glyph | mod].advance + ea) * (double)p_size / (double)fd->fixed_size;
+ return (fgl.advance + ea) * (double)p_size / (double)fd->fixed_size;
} else {
- return (gl[p_glyph | mod].advance + ea) * Math::round((double)p_size / (double)fd->fixed_size);
+ return (fgl.advance + ea) * Math::round((double)p_size / (double)fd->fixed_size);
}
} else if ((scale == 1.0) && ((fd->subpixel_positioning == SUBPIXEL_POSITIONING_DISABLED) || (fd->subpixel_positioning == SUBPIXEL_POSITIONING_AUTO && size.x > SUBPIXEL_POSITIONING_ONE_HALF_MAX_SIZE))) {
- return (gl[p_glyph | mod].advance + ea).round();
+ return (fgl.advance + ea).round();
} else {
- return gl[p_glyph | mod].advance + ea;
+ return fgl.advance + ea;
}
}
@@ -2984,12 +3031,13 @@ void TextServerAdvanced::_font_set_glyph_advance(const RID &p_font_rid, int64_t
MutexLock lock(fd->mutex);
Vector2i size = _get_size(fd, p_size);
- ERR_FAIL_COND(!_ensure_cache_for_size(fd, size));
+ FontForSizeAdvanced *ffsd = nullptr;
+ ERR_FAIL_COND(!_ensure_cache_for_size(fd, size, ffsd));
- HashMap<int32_t, FontGlyph> &gl = fd->cache[size]->glyph_map;
+ FontGlyph &fgl = ffsd->glyph_map[p_glyph];
- gl[p_glyph].advance = p_advance;
- gl[p_glyph].found = true;
+ fgl.advance = p_advance;
+ fgl.found = true;
}
Vector2 TextServerAdvanced::_font_get_glyph_offset(const RID &p_font_rid, const Vector2i &p_size, int64_t p_glyph) const {
@@ -2999,32 +3047,32 @@ Vector2 TextServerAdvanced::_font_get_glyph_offset(const RID &p_font_rid, const
MutexLock lock(fd->mutex);
Vector2i size = _get_size_outline(fd, p_size);
- ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size), Vector2());
+ FontForSizeAdvanced *ffsd = nullptr;
+ ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size, ffsd), Vector2());
int mod = 0;
if (fd->antialiasing == FONT_ANTIALIASING_LCD) {
- TextServer::FontLCDSubpixelLayout layout = (TextServer::FontLCDSubpixelLayout)(int)GLOBAL_GET("gui/theme/lcd_subpixel_layout");
+ TextServer::FontLCDSubpixelLayout layout = lcd_subpixel_layout.get();
if (layout != FONT_LCD_SUBPIXEL_LAYOUT_NONE) {
mod = (layout << 24);
}
}
- if (!_ensure_glyph(fd, size, p_glyph | mod)) {
+ FontGlyph fgl;
+ if (!_ensure_glyph(fd, size, p_glyph | mod, fgl)) {
return Vector2(); // Invalid or non graphicl glyph, do not display errors.
}
- const HashMap<int32_t, FontGlyph> &gl = fd->cache[size]->glyph_map;
-
if (fd->msdf) {
- return gl[p_glyph | mod].rect.position * (double)p_size.x / (double)fd->msdf_source_size;
+ return fgl.rect.position * (double)p_size.x / (double)fd->msdf_source_size;
} else if (fd->fixed_size > 0 && fd->fixed_size_scale_mode != FIXED_SIZE_SCALE_DISABLE && size.x != p_size.x) {
if (fd->fixed_size_scale_mode == FIXED_SIZE_SCALE_ENABLED) {
- return gl[p_glyph | mod].rect.position * (double)p_size.x / (double)fd->fixed_size;
+ return fgl.rect.position * (double)p_size.x / (double)fd->fixed_size;
} else {
- return gl[p_glyph | mod].rect.position * Math::round((double)p_size.x / (double)fd->fixed_size);
+ return fgl.rect.position * Math::round((double)p_size.x / (double)fd->fixed_size);
}
} else {
- return gl[p_glyph | mod].rect.position;
+ return fgl.rect.position;
}
}
@@ -3035,12 +3083,13 @@ void TextServerAdvanced::_font_set_glyph_offset(const RID &p_font_rid, const Vec
MutexLock lock(fd->mutex);
Vector2i size = _get_size_outline(fd, p_size);
- ERR_FAIL_COND(!_ensure_cache_for_size(fd, size));
+ FontForSizeAdvanced *ffsd = nullptr;
+ ERR_FAIL_COND(!_ensure_cache_for_size(fd, size, ffsd));
- HashMap<int32_t, FontGlyph> &gl = fd->cache[size]->glyph_map;
+ FontGlyph &fgl = ffsd->glyph_map[p_glyph];
- gl[p_glyph].rect.position = p_offset;
- gl[p_glyph].found = true;
+ fgl.rect.position = p_offset;
+ fgl.found = true;
}
Vector2 TextServerAdvanced::_font_get_glyph_size(const RID &p_font_rid, const Vector2i &p_size, int64_t p_glyph) const {
@@ -3050,32 +3099,32 @@ Vector2 TextServerAdvanced::_font_get_glyph_size(const RID &p_font_rid, const Ve
MutexLock lock(fd->mutex);
Vector2i size = _get_size_outline(fd, p_size);
- ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size), Vector2());
+ FontForSizeAdvanced *ffsd = nullptr;
+ ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size, ffsd), Vector2());
int mod = 0;
if (fd->antialiasing == FONT_ANTIALIASING_LCD) {
- TextServer::FontLCDSubpixelLayout layout = (TextServer::FontLCDSubpixelLayout)(int)GLOBAL_GET("gui/theme/lcd_subpixel_layout");
+ TextServer::FontLCDSubpixelLayout layout = lcd_subpixel_layout.get();
if (layout != FONT_LCD_SUBPIXEL_LAYOUT_NONE) {
mod = (layout << 24);
}
}
- if (!_ensure_glyph(fd, size, p_glyph | mod)) {
+ FontGlyph fgl;
+ if (!_ensure_glyph(fd, size, p_glyph | mod, fgl)) {
return Vector2(); // Invalid or non graphicl glyph, do not display errors.
}
- const HashMap<int32_t, FontGlyph> &gl = fd->cache[size]->glyph_map;
-
if (fd->msdf) {
- return gl[p_glyph | mod].rect.size * (double)p_size.x / (double)fd->msdf_source_size;
+ return fgl.rect.size * (double)p_size.x / (double)fd->msdf_source_size;
} else if (fd->fixed_size > 0 && fd->fixed_size_scale_mode != FIXED_SIZE_SCALE_DISABLE && size.x != p_size.x) {
if (fd->fixed_size_scale_mode == FIXED_SIZE_SCALE_ENABLED) {
- return gl[p_glyph | mod].rect.size * (double)p_size.x / (double)fd->fixed_size;
+ return fgl.rect.size * (double)p_size.x / (double)fd->fixed_size;
} else {
- return gl[p_glyph | mod].rect.size * Math::round((double)p_size.x / (double)fd->fixed_size);
+ return fgl.rect.size * Math::round((double)p_size.x / (double)fd->fixed_size);
}
} else {
- return gl[p_glyph | mod].rect.size;
+ return fgl.rect.size;
}
}
@@ -3086,12 +3135,13 @@ void TextServerAdvanced::_font_set_glyph_size(const RID &p_font_rid, const Vecto
MutexLock lock(fd->mutex);
Vector2i size = _get_size_outline(fd, p_size);
- ERR_FAIL_COND(!_ensure_cache_for_size(fd, size));
+ FontForSizeAdvanced *ffsd = nullptr;
+ ERR_FAIL_COND(!_ensure_cache_for_size(fd, size, ffsd));
- HashMap<int32_t, FontGlyph> &gl = fd->cache[size]->glyph_map;
+ FontGlyph &fgl = ffsd->glyph_map[p_glyph];
- gl[p_glyph].rect.size = p_gl_size;
- gl[p_glyph].found = true;
+ fgl.rect.size = p_gl_size;
+ fgl.found = true;
}
Rect2 TextServerAdvanced::_font_get_glyph_uv_rect(const RID &p_font_rid, const Vector2i &p_size, int64_t p_glyph) const {
@@ -3101,22 +3151,23 @@ Rect2 TextServerAdvanced::_font_get_glyph_uv_rect(const RID &p_font_rid, const V
MutexLock lock(fd->mutex);
Vector2i size = _get_size_outline(fd, p_size);
- ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size), Rect2());
+ FontForSizeAdvanced *ffsd = nullptr;
+ ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size, ffsd), Rect2());
int mod = 0;
if (fd->antialiasing == FONT_ANTIALIASING_LCD) {
- TextServer::FontLCDSubpixelLayout layout = (TextServer::FontLCDSubpixelLayout)(int)GLOBAL_GET("gui/theme/lcd_subpixel_layout");
+ TextServer::FontLCDSubpixelLayout layout = lcd_subpixel_layout.get();
if (layout != FONT_LCD_SUBPIXEL_LAYOUT_NONE) {
mod = (layout << 24);
}
}
- if (!_ensure_glyph(fd, size, p_glyph | mod)) {
+ FontGlyph fgl;
+ if (!_ensure_glyph(fd, size, p_glyph | mod, fgl)) {
return Rect2(); // Invalid or non graphicl glyph, do not display errors.
}
- const HashMap<int32_t, FontGlyph> &gl = fd->cache[size]->glyph_map;
- return gl[p_glyph | mod].uv_rect;
+ return fgl.uv_rect;
}
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) {
@@ -3126,12 +3177,13 @@ void TextServerAdvanced::_font_set_glyph_uv_rect(const RID &p_font_rid, const Ve
MutexLock lock(fd->mutex);
Vector2i size = _get_size_outline(fd, p_size);
- ERR_FAIL_COND(!_ensure_cache_for_size(fd, size));
+ FontForSizeAdvanced *ffsd = nullptr;
+ ERR_FAIL_COND(!_ensure_cache_for_size(fd, size, ffsd));
- HashMap<int32_t, FontGlyph> &gl = fd->cache[size]->glyph_map;
+ FontGlyph &fgl = ffsd->glyph_map[p_glyph];
- gl[p_glyph].uv_rect = p_uv_rect;
- gl[p_glyph].found = true;
+ fgl.uv_rect = p_uv_rect;
+ fgl.found = true;
}
int64_t TextServerAdvanced::_font_get_glyph_texture_idx(const RID &p_font_rid, const Vector2i &p_size, int64_t p_glyph) const {
@@ -3141,22 +3193,23 @@ int64_t TextServerAdvanced::_font_get_glyph_texture_idx(const RID &p_font_rid, c
MutexLock lock(fd->mutex);
Vector2i size = _get_size_outline(fd, p_size);
- ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size), -1);
+ FontForSizeAdvanced *ffsd = nullptr;
+ ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size, ffsd), -1);
int mod = 0;
if (fd->antialiasing == FONT_ANTIALIASING_LCD) {
- TextServer::FontLCDSubpixelLayout layout = (TextServer::FontLCDSubpixelLayout)(int)GLOBAL_GET("gui/theme/lcd_subpixel_layout");
+ TextServer::FontLCDSubpixelLayout layout = lcd_subpixel_layout.get();
if (layout != FONT_LCD_SUBPIXEL_LAYOUT_NONE) {
mod = (layout << 24);
}
}
- if (!_ensure_glyph(fd, size, p_glyph | mod)) {
+ FontGlyph fgl;
+ if (!_ensure_glyph(fd, size, p_glyph | mod, fgl)) {
return -1; // Invalid or non graphicl glyph, do not display errors.
}
- const HashMap<int32_t, FontGlyph> &gl = fd->cache[size]->glyph_map;
- return gl[p_glyph | mod].texture_idx;
+ return fgl.texture_idx;
}
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) {
@@ -3166,12 +3219,13 @@ void TextServerAdvanced::_font_set_glyph_texture_idx(const RID &p_font_rid, cons
MutexLock lock(fd->mutex);
Vector2i size = _get_size_outline(fd, p_size);
- ERR_FAIL_COND(!_ensure_cache_for_size(fd, size));
+ FontForSizeAdvanced *ffsd = nullptr;
+ ERR_FAIL_COND(!_ensure_cache_for_size(fd, size, ffsd));
- HashMap<int32_t, FontGlyph> &gl = fd->cache[size]->glyph_map;
+ FontGlyph &fgl = ffsd->glyph_map[p_glyph];
- gl[p_glyph].texture_idx = p_texture_idx;
- gl[p_glyph].found = true;
+ fgl.texture_idx = p_texture_idx;
+ fgl.found = true;
}
RID TextServerAdvanced::_font_get_glyph_texture_rid(const RID &p_font_rid, const Vector2i &p_size, int64_t p_glyph) const {
@@ -3181,27 +3235,28 @@ RID TextServerAdvanced::_font_get_glyph_texture_rid(const RID &p_font_rid, const
MutexLock lock(fd->mutex);
Vector2i size = _get_size_outline(fd, p_size);
- ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size), RID());
+ FontForSizeAdvanced *ffsd = nullptr;
+ ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size, ffsd), RID());
int mod = 0;
if (fd->antialiasing == FONT_ANTIALIASING_LCD) {
- TextServer::FontLCDSubpixelLayout layout = (TextServer::FontLCDSubpixelLayout)(int)GLOBAL_GET("gui/theme/lcd_subpixel_layout");
+ TextServer::FontLCDSubpixelLayout layout = lcd_subpixel_layout.get();
if (layout != FONT_LCD_SUBPIXEL_LAYOUT_NONE) {
mod = (layout << 24);
}
}
- if (!_ensure_glyph(fd, size, p_glyph | mod)) {
+ FontGlyph fgl;
+ if (!_ensure_glyph(fd, size, p_glyph | mod, fgl)) {
return RID(); // Invalid or non graphicl glyph, do not display errors.
}
- const HashMap<int32_t, FontGlyph> &gl = fd->cache[size]->glyph_map;
- ERR_FAIL_COND_V(gl[p_glyph | mod].texture_idx < -1 || gl[p_glyph | mod].texture_idx >= fd->cache[size]->textures.size(), RID());
+ ERR_FAIL_COND_V(fgl.texture_idx < -1 || fgl.texture_idx >= ffsd->textures.size(), RID());
if (RenderingServer::get_singleton() != nullptr) {
- if (gl[p_glyph | mod].texture_idx != -1) {
- if (fd->cache[size]->textures[gl[p_glyph | mod].texture_idx].dirty) {
- ShelfPackTexture &tex = fd->cache[size]->textures.write[gl[p_glyph | mod].texture_idx];
+ if (fgl.texture_idx != -1) {
+ if (ffsd->textures[fgl.texture_idx].dirty) {
+ ShelfPackTexture &tex = ffsd->textures.write[fgl.texture_idx];
Ref<Image> img = tex.image;
if (fd->mipmaps && !img->has_mipmaps()) {
img = tex.image->duplicate();
@@ -3214,7 +3269,7 @@ RID TextServerAdvanced::_font_get_glyph_texture_rid(const RID &p_font_rid, const
}
tex.dirty = false;
}
- return fd->cache[size]->textures[gl[p_glyph | mod].texture_idx].texture->get_rid();
+ return ffsd->textures[fgl.texture_idx].texture->get_rid();
}
}
@@ -3228,27 +3283,28 @@ Size2 TextServerAdvanced::_font_get_glyph_texture_size(const RID &p_font_rid, co
MutexLock lock(fd->mutex);
Vector2i size = _get_size_outline(fd, p_size);
- ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size), Size2());
+ FontForSizeAdvanced *ffsd = nullptr;
+ ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size, ffsd), Size2());
int mod = 0;
if (fd->antialiasing == FONT_ANTIALIASING_LCD) {
- TextServer::FontLCDSubpixelLayout layout = (TextServer::FontLCDSubpixelLayout)(int)GLOBAL_GET("gui/theme/lcd_subpixel_layout");
+ TextServer::FontLCDSubpixelLayout layout = lcd_subpixel_layout.get();
if (layout != FONT_LCD_SUBPIXEL_LAYOUT_NONE) {
mod = (layout << 24);
}
}
- if (!_ensure_glyph(fd, size, p_glyph | mod)) {
+ FontGlyph fgl;
+ if (!_ensure_glyph(fd, size, p_glyph | mod, fgl)) {
return Size2(); // Invalid or non graphicl glyph, do not display errors.
}
- const HashMap<int32_t, FontGlyph> &gl = fd->cache[size]->glyph_map;
- ERR_FAIL_COND_V(gl[p_glyph | mod].texture_idx < -1 || gl[p_glyph | mod].texture_idx >= fd->cache[size]->textures.size(), Size2());
+ ERR_FAIL_COND_V(fgl.texture_idx < -1 || fgl.texture_idx >= ffsd->textures.size(), Size2());
if (RenderingServer::get_singleton() != nullptr) {
- if (gl[p_glyph | mod].texture_idx != -1) {
- if (fd->cache[size]->textures[gl[p_glyph | mod].texture_idx].dirty) {
- ShelfPackTexture &tex = fd->cache[size]->textures.write[gl[p_glyph | mod].texture_idx];
+ if (fgl.texture_idx != -1) {
+ if (ffsd->textures[fgl.texture_idx].dirty) {
+ ShelfPackTexture &tex = ffsd->textures.write[fgl.texture_idx];
Ref<Image> img = tex.image;
if (fd->mipmaps && !img->has_mipmaps()) {
img = tex.image->duplicate();
@@ -3261,7 +3317,7 @@ Size2 TextServerAdvanced::_font_get_glyph_texture_size(const RID &p_font_rid, co
}
tex.dirty = false;
}
- return fd->cache[size]->textures[gl[p_glyph | mod].texture_idx].texture->get_size();
+ return ffsd->textures[fgl.texture_idx].texture->get_size();
}
}
@@ -3275,7 +3331,8 @@ Dictionary TextServerAdvanced::_font_get_glyph_contours(const RID &p_font_rid, i
MutexLock lock(fd->mutex);
Vector2i size = _get_size(fd, p_size);
- ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size), Dictionary());
+ FontForSizeAdvanced *ffsd = nullptr;
+ ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size, ffsd), Dictionary());
#ifdef MODULE_FREETYPE_ENABLED
PackedVector3Array points;
@@ -3283,20 +3340,20 @@ Dictionary TextServerAdvanced::_font_get_glyph_contours(const RID &p_font_rid, i
int32_t index = p_index & 0xffffff; // Remove subpixel shifts.
- int error = FT_Load_Glyph(fd->cache[size]->face, index, FT_LOAD_NO_BITMAP | (fd->force_autohinter ? FT_LOAD_FORCE_AUTOHINT : 0));
+ int error = FT_Load_Glyph(ffsd->face, index, FT_LOAD_NO_BITMAP | (fd->force_autohinter ? FT_LOAD_FORCE_AUTOHINT : 0));
ERR_FAIL_COND_V(error, Dictionary());
if (fd->embolden != 0.f) {
FT_Pos strength = fd->embolden * p_size * 4; // 26.6 fractional units (1 / 64).
- FT_Outline_Embolden(&fd->cache[size]->face->glyph->outline, strength);
+ FT_Outline_Embolden(&ffsd->face->glyph->outline, strength);
}
if (fd->transform != Transform2D()) {
FT_Matrix mat = { FT_Fixed(fd->transform[0][0] * 65536), FT_Fixed(fd->transform[0][1] * 65536), FT_Fixed(fd->transform[1][0] * 65536), FT_Fixed(fd->transform[1][1] * 65536) }; // 16.16 fractional units (1 / 65536).
- FT_Outline_Transform(&fd->cache[size]->face->glyph->outline, &mat);
+ FT_Outline_Transform(&ffsd->face->glyph->outline, &mat);
}
- double scale = (1.0 / 64.0) / fd->cache[size]->oversampling * fd->cache[size]->scale;
+ double scale = (1.0 / 64.0) / ffsd->oversampling * ffsd->scale;
if (fd->msdf) {
scale = scale * (double)p_size / (double)fd->msdf_source_size;
} else if (fd->fixed_size > 0 && fd->fixed_size_scale_mode != FIXED_SIZE_SCALE_DISABLE && size.x != p_size) {
@@ -3306,13 +3363,13 @@ Dictionary TextServerAdvanced::_font_get_glyph_contours(const RID &p_font_rid, i
scale = scale * Math::round((double)p_size / (double)fd->fixed_size);
}
}
- for (short i = 0; i < fd->cache[size]->face->glyph->outline.n_points; i++) {
- points.push_back(Vector3(fd->cache[size]->face->glyph->outline.points[i].x * scale, -fd->cache[size]->face->glyph->outline.points[i].y * scale, FT_CURVE_TAG(fd->cache[size]->face->glyph->outline.tags[i])));
+ for (short i = 0; i < ffsd->face->glyph->outline.n_points; i++) {
+ points.push_back(Vector3(ffsd->face->glyph->outline.points[i].x * scale, -ffsd->face->glyph->outline.points[i].y * scale, FT_CURVE_TAG(ffsd->face->glyph->outline.tags[i])));
}
- for (short i = 0; i < fd->cache[size]->face->glyph->outline.n_contours; i++) {
- contours.push_back(fd->cache[size]->face->glyph->outline.contours[i]);
+ for (short i = 0; i < ffsd->face->glyph->outline.n_contours; i++) {
+ contours.push_back(ffsd->face->glyph->outline.contours[i]);
}
- bool orientation = (FT_Outline_Get_Orientation(&fd->cache[size]->face->glyph->outline) == FT_ORIENTATION_FILL_RIGHT);
+ bool orientation = (FT_Outline_Get_Orientation(&ffsd->face->glyph->outline) == FT_ORIENTATION_FILL_RIGHT);
Dictionary out;
out["points"] = points;
@@ -3331,7 +3388,8 @@ TypedArray<Vector2i> TextServerAdvanced::_font_get_kerning_list(const RID &p_fon
MutexLock lock(fd->mutex);
Vector2i size = _get_size(fd, p_size);
- ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size), TypedArray<Vector2i>());
+ FontForSizeAdvanced *ffsd = nullptr;
+ ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size, ffsd), TypedArray<Vector2i>());
TypedArray<Vector2i> ret;
for (const KeyValue<Vector2i, Vector2> &E : fd->cache[size]->kerning_map) {
@@ -3347,8 +3405,9 @@ void TextServerAdvanced::_font_clear_kerning_map(const RID &p_font_rid, int64_t
MutexLock lock(fd->mutex);
Vector2i size = _get_size(fd, p_size);
- ERR_FAIL_COND(!_ensure_cache_for_size(fd, size));
- fd->cache[size]->kerning_map.clear();
+ FontForSizeAdvanced *ffsd = nullptr;
+ ERR_FAIL_COND(!_ensure_cache_for_size(fd, size, ffsd));
+ ffsd->kerning_map.clear();
}
void TextServerAdvanced::_font_remove_kerning(const RID &p_font_rid, int64_t p_size, const Vector2i &p_glyph_pair) {
@@ -3358,8 +3417,9 @@ void TextServerAdvanced::_font_remove_kerning(const RID &p_font_rid, int64_t p_s
MutexLock lock(fd->mutex);
Vector2i size = _get_size(fd, p_size);
- ERR_FAIL_COND(!_ensure_cache_for_size(fd, size));
- fd->cache[size]->kerning_map.erase(p_glyph_pair);
+ FontForSizeAdvanced *ffsd = nullptr;
+ ERR_FAIL_COND(!_ensure_cache_for_size(fd, size, ffsd));
+ ffsd->kerning_map.erase(p_glyph_pair);
}
void TextServerAdvanced::_font_set_kerning(const RID &p_font_rid, int64_t p_size, const Vector2i &p_glyph_pair, const Vector2 &p_kerning) {
@@ -3369,8 +3429,9 @@ void TextServerAdvanced::_font_set_kerning(const RID &p_font_rid, int64_t p_size
MutexLock lock(fd->mutex);
Vector2i size = _get_size(fd, p_size);
- ERR_FAIL_COND(!_ensure_cache_for_size(fd, size));
- fd->cache[size]->kerning_map[p_glyph_pair] = p_kerning;
+ FontForSizeAdvanced *ffsd = nullptr;
+ ERR_FAIL_COND(!_ensure_cache_for_size(fd, size, ffsd));
+ ffsd->kerning_map[p_glyph_pair] = p_kerning;
}
Vector2 TextServerAdvanced::_font_get_kerning(const RID &p_font_rid, int64_t p_size, const Vector2i &p_glyph_pair) const {
@@ -3380,9 +3441,10 @@ Vector2 TextServerAdvanced::_font_get_kerning(const RID &p_font_rid, int64_t p_s
MutexLock lock(fd->mutex);
Vector2i size = _get_size(fd, p_size);
- ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size), Vector2());
+ FontForSizeAdvanced *ffsd = nullptr;
+ ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size, ffsd), Vector2());
- const HashMap<Vector2i, Vector2> &kern = fd->cache[size]->kerning_map;
+ const HashMap<Vector2i, Vector2> &kern = ffsd->kerning_map;
if (kern.has(p_glyph_pair)) {
if (fd->msdf) {
@@ -3398,9 +3460,9 @@ Vector2 TextServerAdvanced::_font_get_kerning(const RID &p_font_rid, int64_t p_s
}
} else {
#ifdef MODULE_FREETYPE_ENABLED
- if (fd->cache[size]->face) {
+ if (ffsd->face) {
FT_Vector delta;
- FT_Get_Kerning(fd->cache[size]->face, p_glyph_pair.x, p_glyph_pair.y, FT_KERNING_DEFAULT, &delta);
+ FT_Get_Kerning(ffsd->face, p_glyph_pair.x, p_glyph_pair.y, FT_KERNING_DEFAULT, &delta);
if (fd->msdf) {
return Vector2(delta.x, delta.y) * (double)p_size / (double)fd->msdf_source_size;
} else if (fd->fixed_size > 0 && fd->fixed_size_scale_mode != FIXED_SIZE_SCALE_DISABLE && size.x != p_size) {
@@ -3426,14 +3488,15 @@ int64_t TextServerAdvanced::_font_get_glyph_index(const RID &p_font_rid, int64_t
MutexLock lock(fd->mutex);
Vector2i size = _get_size(fd, p_size);
- ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size), 0);
+ FontForSizeAdvanced *ffsd = nullptr;
+ ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size, ffsd), 0);
#ifdef MODULE_FREETYPE_ENABLED
- if (fd->cache[size]->face) {
+ if (ffsd->face) {
if (p_variation_selector) {
- return FT_Face_GetCharVariantIndex(fd->cache[size]->face, p_char, p_variation_selector);
+ return FT_Face_GetCharVariantIndex(ffsd->face, p_char, p_variation_selector);
} else {
- return FT_Get_Char_Index(fd->cache[size]->face, p_char);
+ return FT_Get_Char_Index(ffsd->face, p_char);
}
} else {
return (int64_t)p_char;
@@ -3449,23 +3512,24 @@ int64_t TextServerAdvanced::_font_get_char_from_glyph_index(const RID &p_font_ri
MutexLock lock(fd->mutex);
Vector2i size = _get_size(fd, p_size);
- ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size), 0);
+ FontForSizeAdvanced *ffsd = nullptr;
+ ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size, ffsd), 0);
#ifdef MODULE_FREETYPE_ENABLED
- if (fd->cache[size]->inv_glyph_map.is_empty()) {
- FT_Face face = fd->cache[size]->face;
+ if (ffsd->inv_glyph_map.is_empty()) {
+ FT_Face face = ffsd->face;
FT_UInt gindex;
FT_ULong charcode = FT_Get_First_Char(face, &gindex);
while (gindex != 0) {
if (charcode != 0) {
- fd->cache[size]->inv_glyph_map[gindex] = charcode;
+ ffsd->inv_glyph_map[gindex] = charcode;
}
charcode = FT_Get_Next_Char(face, charcode, &gindex);
}
}
- if (fd->cache[size]->inv_glyph_map.has(p_glyph_index)) {
- return fd->cache[size]->inv_glyph_map[p_glyph_index];
+ if (ffsd->inv_glyph_map.has(p_glyph_index)) {
+ return ffsd->inv_glyph_map[p_glyph_index];
} else {
return 0;
}
@@ -3482,17 +3546,19 @@ bool TextServerAdvanced::_font_has_char(const RID &p_font_rid, int64_t p_char) c
}
MutexLock lock(fd->mutex);
+ FontForSizeAdvanced *ffsd = nullptr;
if (fd->cache.is_empty()) {
- ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, fd->msdf ? Vector2i(fd->msdf_source_size, 0) : Vector2i(16, 0)), false);
+ ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, fd->msdf ? Vector2i(fd->msdf_source_size, 0) : Vector2i(16, 0), ffsd), false);
+ } else {
+ ffsd = fd->cache.begin()->value;
}
- FontForSizeAdvanced *at_size = fd->cache.begin()->value;
#ifdef MODULE_FREETYPE_ENABLED
- if (at_size && at_size->face) {
- return FT_Get_Char_Index(at_size->face, p_char) != 0;
+ if (ffsd->face) {
+ return FT_Get_Char_Index(ffsd->face, p_char) != 0;
}
#endif
- return (at_size) ? at_size->glyph_map.has((int32_t)p_char) : false;
+ return ffsd->glyph_map.has((int32_t)p_char);
}
String TextServerAdvanced::_font_get_supported_chars(const RID &p_font_rid) const {
@@ -3500,30 +3566,30 @@ String TextServerAdvanced::_font_get_supported_chars(const RID &p_font_rid) cons
ERR_FAIL_NULL_V(fd, String());
MutexLock lock(fd->mutex);
+ FontForSizeAdvanced *ffsd = nullptr;
if (fd->cache.is_empty()) {
- ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, fd->msdf ? Vector2i(fd->msdf_source_size, 0) : Vector2i(16, 0)), String());
+ ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, fd->msdf ? Vector2i(fd->msdf_source_size, 0) : Vector2i(16, 0), ffsd), String());
+ } else {
+ ffsd = fd->cache.begin()->value;
}
- FontForSizeAdvanced *at_size = fd->cache.begin()->value;
String chars;
#ifdef MODULE_FREETYPE_ENABLED
- if (at_size && at_size->face) {
+ if (ffsd->face) {
FT_UInt gindex;
- FT_ULong charcode = FT_Get_First_Char(at_size->face, &gindex);
+ FT_ULong charcode = FT_Get_First_Char(ffsd->face, &gindex);
while (gindex != 0) {
if (charcode != 0) {
chars = chars + String::chr(charcode);
}
- charcode = FT_Get_Next_Char(at_size->face, charcode, &gindex);
+ charcode = FT_Get_Next_Char(ffsd->face, charcode, &gindex);
}
return chars;
}
#endif
- if (at_size) {
- const HashMap<int32_t, FontGlyph> &gl = at_size->glyph_map;
- for (const KeyValue<int32_t, FontGlyph> &E : gl) {
- chars = chars + String::chr(E.key);
- }
+ const HashMap<int32_t, FontGlyph> &gl = ffsd->glyph_map;
+ for (const KeyValue<int32_t, FontGlyph> &E : gl) {
+ chars = chars + String::chr(E.key);
}
return chars;
}
@@ -3533,10 +3599,12 @@ PackedInt32Array TextServerAdvanced::_font_get_supported_glyphs(const RID &p_fon
ERR_FAIL_NULL_V(fd, PackedInt32Array());
MutexLock lock(fd->mutex);
+ FontForSizeAdvanced *at_size = nullptr;
if (fd->cache.is_empty()) {
- ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, fd->msdf ? Vector2i(fd->msdf_source_size, 0) : Vector2i(16, 0)), PackedInt32Array());
+ ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, fd->msdf ? Vector2i(fd->msdf_source_size, 0) : Vector2i(16, 0), at_size), PackedInt32Array());
+ } else {
+ at_size = fd->cache.begin()->value;
}
- FontForSizeAdvanced *at_size = fd->cache.begin()->value;
PackedInt32Array glyphs;
#ifdef MODULE_FREETYPE_ENABLED
@@ -3567,25 +3635,27 @@ void TextServerAdvanced::_font_render_range(const RID &p_font_rid, const Vector2
MutexLock lock(fd->mutex);
Vector2i size = _get_size_outline(fd, p_size);
- ERR_FAIL_COND(!_ensure_cache_for_size(fd, size));
+ FontForSizeAdvanced *ffsd = nullptr;
+ ERR_FAIL_COND(!_ensure_cache_for_size(fd, size, ffsd));
for (int64_t i = p_start; i <= p_end; i++) {
#ifdef MODULE_FREETYPE_ENABLED
- int32_t idx = FT_Get_Char_Index(fd->cache[size]->face, i);
- if (fd->cache[size]->face) {
+ int32_t idx = FT_Get_Char_Index(ffsd->face, i);
+ if (ffsd->face) {
+ FontGlyph fgl;
if (fd->msdf) {
- _ensure_glyph(fd, size, (int32_t)idx);
+ _ensure_glyph(fd, size, (int32_t)idx, fgl);
} else {
for (int aa = 0; aa < ((fd->antialiasing == FONT_ANTIALIASING_LCD) ? FONT_LCD_SUBPIXEL_LAYOUT_MAX : 1); aa++) {
if ((fd->subpixel_positioning == SUBPIXEL_POSITIONING_ONE_QUARTER) || (fd->subpixel_positioning == SUBPIXEL_POSITIONING_AUTO && size.x <= SUBPIXEL_POSITIONING_ONE_QUARTER_MAX_SIZE)) {
- _ensure_glyph(fd, size, (int32_t)idx | (0 << 27) | (aa << 24));
- _ensure_glyph(fd, size, (int32_t)idx | (1 << 27) | (aa << 24));
- _ensure_glyph(fd, size, (int32_t)idx | (2 << 27) | (aa << 24));
- _ensure_glyph(fd, size, (int32_t)idx | (3 << 27) | (aa << 24));
+ _ensure_glyph(fd, size, (int32_t)idx | (0 << 27) | (aa << 24), fgl);
+ _ensure_glyph(fd, size, (int32_t)idx | (1 << 27) | (aa << 24), fgl);
+ _ensure_glyph(fd, size, (int32_t)idx | (2 << 27) | (aa << 24), fgl);
+ _ensure_glyph(fd, size, (int32_t)idx | (3 << 27) | (aa << 24), fgl);
} else if ((fd->subpixel_positioning == SUBPIXEL_POSITIONING_ONE_HALF) || (fd->subpixel_positioning == SUBPIXEL_POSITIONING_AUTO && size.x <= SUBPIXEL_POSITIONING_ONE_HALF_MAX_SIZE)) {
- _ensure_glyph(fd, size, (int32_t)idx | (1 << 27) | (aa << 24));
- _ensure_glyph(fd, size, (int32_t)idx | (0 << 27) | (aa << 24));
+ _ensure_glyph(fd, size, (int32_t)idx | (1 << 27) | (aa << 24), fgl);
+ _ensure_glyph(fd, size, (int32_t)idx | (0 << 27) | (aa << 24), fgl);
} else {
- _ensure_glyph(fd, size, (int32_t)idx | (aa << 24));
+ _ensure_glyph(fd, size, (int32_t)idx | (aa << 24), fgl);
}
}
}
@@ -3600,24 +3670,26 @@ void TextServerAdvanced::_font_render_glyph(const RID &p_font_rid, const Vector2
MutexLock lock(fd->mutex);
Vector2i size = _get_size_outline(fd, p_size);
- ERR_FAIL_COND(!_ensure_cache_for_size(fd, size));
+ FontForSizeAdvanced *ffsd = nullptr;
+ ERR_FAIL_COND(!_ensure_cache_for_size(fd, size, ffsd));
#ifdef MODULE_FREETYPE_ENABLED
int32_t idx = p_index & 0xffffff; // Remove subpixel shifts.
- if (fd->cache[size]->face) {
+ if (ffsd->face) {
+ FontGlyph fgl;
if (fd->msdf) {
- _ensure_glyph(fd, size, (int32_t)idx);
+ _ensure_glyph(fd, size, (int32_t)idx, fgl);
} else {
for (int aa = 0; aa < ((fd->antialiasing == FONT_ANTIALIASING_LCD) ? FONT_LCD_SUBPIXEL_LAYOUT_MAX : 1); aa++) {
if ((fd->subpixel_positioning == SUBPIXEL_POSITIONING_ONE_QUARTER) || (fd->subpixel_positioning == SUBPIXEL_POSITIONING_AUTO && size.x <= SUBPIXEL_POSITIONING_ONE_QUARTER_MAX_SIZE)) {
- _ensure_glyph(fd, size, (int32_t)idx | (0 << 27) | (aa << 24));
- _ensure_glyph(fd, size, (int32_t)idx | (1 << 27) | (aa << 24));
- _ensure_glyph(fd, size, (int32_t)idx | (2 << 27) | (aa << 24));
- _ensure_glyph(fd, size, (int32_t)idx | (3 << 27) | (aa << 24));
+ _ensure_glyph(fd, size, (int32_t)idx | (0 << 27) | (aa << 24), fgl);
+ _ensure_glyph(fd, size, (int32_t)idx | (1 << 27) | (aa << 24), fgl);
+ _ensure_glyph(fd, size, (int32_t)idx | (2 << 27) | (aa << 24), fgl);
+ _ensure_glyph(fd, size, (int32_t)idx | (3 << 27) | (aa << 24), fgl);
} else if ((fd->subpixel_positioning == SUBPIXEL_POSITIONING_ONE_HALF) || (fd->subpixel_positioning == SUBPIXEL_POSITIONING_AUTO && size.x <= SUBPIXEL_POSITIONING_ONE_HALF_MAX_SIZE)) {
- _ensure_glyph(fd, size, (int32_t)idx | (1 << 27) | (aa << 24));
- _ensure_glyph(fd, size, (int32_t)idx | (0 << 27) | (aa << 24));
+ _ensure_glyph(fd, size, (int32_t)idx | (1 << 27) | (aa << 24), fgl);
+ _ensure_glyph(fd, size, (int32_t)idx | (0 << 27) | (aa << 24), fgl);
} else {
- _ensure_glyph(fd, size, (int32_t)idx | (aa << 24));
+ _ensure_glyph(fd, size, (int32_t)idx | (aa << 24), fgl);
}
}
}
@@ -3634,16 +3706,17 @@ void TextServerAdvanced::_font_draw_glyph(const RID &p_font_rid, const RID &p_ca
MutexLock lock(fd->mutex);
Vector2i size = _get_size(fd, p_size);
- ERR_FAIL_COND(!_ensure_cache_for_size(fd, size));
+ FontForSizeAdvanced *ffsd = nullptr;
+ ERR_FAIL_COND(!_ensure_cache_for_size(fd, size, ffsd));
int32_t index = p_index & 0xffffff; // Remove subpixel shifts.
bool lcd_aa = false;
#ifdef MODULE_FREETYPE_ENABLED
- if (!fd->msdf && fd->cache[size]->face) {
+ if (!fd->msdf && ffsd->face) {
// LCD layout, bits 24, 25, 26
if (fd->antialiasing == FONT_ANTIALIASING_LCD) {
- TextServer::FontLCDSubpixelLayout layout = (TextServer::FontLCDSubpixelLayout)(int)GLOBAL_GET("gui/theme/lcd_subpixel_layout");
+ TextServer::FontLCDSubpixelLayout layout = lcd_subpixel_layout.get();
if (layout != FONT_LCD_SUBPIXEL_LAYOUT_NONE) {
lcd_aa = true;
index = index | (layout << 24);
@@ -3660,24 +3733,24 @@ void TextServerAdvanced::_font_draw_glyph(const RID &p_font_rid, const RID &p_ca
}
#endif
- if (!_ensure_glyph(fd, size, index)) {
+ FontGlyph fgl;
+ if (!_ensure_glyph(fd, size, index, fgl)) {
return; // Invalid or non-graphical glyph, do not display errors, nothing to draw.
}
- const FontGlyph &gl = fd->cache[size]->glyph_map[index];
- if (gl.found) {
- ERR_FAIL_COND(gl.texture_idx < -1 || gl.texture_idx >= fd->cache[size]->textures.size());
+ if (fgl.found) {
+ ERR_FAIL_COND(fgl.texture_idx < -1 || fgl.texture_idx >= ffsd->textures.size());
- if (gl.texture_idx != -1) {
+ if (fgl.texture_idx != -1) {
Color modulate = p_color;
#ifdef MODULE_FREETYPE_ENABLED
- if (fd->cache[size]->face && fd->cache[size]->textures[gl.texture_idx].image.is_valid() && (fd->cache[size]->textures[gl.texture_idx].image->get_format() == Image::FORMAT_RGBA8) && !lcd_aa && !fd->msdf) {
+ if (ffsd->face && ffsd->textures[fgl.texture_idx].image.is_valid() && (ffsd->textures[fgl.texture_idx].image->get_format() == Image::FORMAT_RGBA8) && !lcd_aa && !fd->msdf) {
modulate.r = modulate.g = modulate.b = 1.0;
}
#endif
if (RenderingServer::get_singleton() != nullptr) {
- if (fd->cache[size]->textures[gl.texture_idx].dirty) {
- ShelfPackTexture &tex = fd->cache[size]->textures.write[gl.texture_idx];
+ if (ffsd->textures[fgl.texture_idx].dirty) {
+ ShelfPackTexture &tex = ffsd->textures.write[fgl.texture_idx];
Ref<Image> img = tex.image;
if (fd->mipmaps && !img->has_mipmaps()) {
img = tex.image->duplicate();
@@ -3690,12 +3763,12 @@ void TextServerAdvanced::_font_draw_glyph(const RID &p_font_rid, const RID &p_ca
}
tex.dirty = false;
}
- RID texture = fd->cache[size]->textures[gl.texture_idx].texture->get_rid();
+ RID texture = ffsd->textures[fgl.texture_idx].texture->get_rid();
if (fd->msdf) {
Point2 cpos = p_pos;
- cpos += gl.rect.position * (double)p_size / (double)fd->msdf_source_size;
- Size2 csize = gl.rect.size * (double)p_size / (double)fd->msdf_source_size;
- RenderingServer::get_singleton()->canvas_item_add_msdf_texture_rect_region(p_canvas, Rect2(cpos, csize), texture, gl.uv_rect, modulate, 0, fd->msdf_range, (double)p_size / (double)fd->msdf_source_size);
+ cpos += fgl.rect.position * (double)p_size / (double)fd->msdf_source_size;
+ Size2 csize = fgl.rect.size * (double)p_size / (double)fd->msdf_source_size;
+ RenderingServer::get_singleton()->canvas_item_add_msdf_texture_rect_region(p_canvas, Rect2(cpos, csize), texture, fgl.uv_rect, modulate, 0, fd->msdf_range, (double)p_size / (double)fd->msdf_source_size);
} else {
double scale = _font_get_scale(p_font_rid, p_size);
Point2 cpos = p_pos;
@@ -3708,8 +3781,8 @@ void TextServerAdvanced::_font_draw_glyph(const RID &p_font_rid, const RID &p_ca
cpos.y = Math::floor(cpos.y);
cpos.x = Math::floor(cpos.x);
}
- Vector2 gpos = gl.rect.position;
- Size2 csize = gl.rect.size;
+ Vector2 gpos = fgl.rect.position;
+ Size2 csize = fgl.rect.size;
if (fd->fixed_size > 0 && fd->fixed_size_scale_mode != FIXED_SIZE_SCALE_DISABLE && size.x != p_size) {
if (fd->fixed_size_scale_mode == FIXED_SIZE_SCALE_ENABLED) {
double gl_scale = (double)p_size / (double)fd->fixed_size;
@@ -3723,9 +3796,9 @@ void TextServerAdvanced::_font_draw_glyph(const RID &p_font_rid, const RID &p_ca
}
cpos += gpos;
if (lcd_aa) {
- RenderingServer::get_singleton()->canvas_item_add_lcd_texture_rect_region(p_canvas, Rect2(cpos, csize), texture, gl.uv_rect, modulate);
+ RenderingServer::get_singleton()->canvas_item_add_lcd_texture_rect_region(p_canvas, Rect2(cpos, csize), texture, fgl.uv_rect, modulate);
} else {
- RenderingServer::get_singleton()->canvas_item_add_texture_rect_region(p_canvas, Rect2(cpos, csize), texture, gl.uv_rect, modulate, false, false);
+ RenderingServer::get_singleton()->canvas_item_add_texture_rect_region(p_canvas, Rect2(cpos, csize), texture, fgl.uv_rect, modulate, false, false);
}
}
}
@@ -3742,16 +3815,17 @@ void TextServerAdvanced::_font_draw_glyph_outline(const RID &p_font_rid, const R
MutexLock lock(fd->mutex);
Vector2i size = _get_size_outline(fd, Vector2i(p_size, p_outline_size));
- ERR_FAIL_COND(!_ensure_cache_for_size(fd, size));
+ FontForSizeAdvanced *ffsd = nullptr;
+ ERR_FAIL_COND(!_ensure_cache_for_size(fd, size, ffsd));
int32_t index = p_index & 0xffffff; // Remove subpixel shifts.
bool lcd_aa = false;
#ifdef MODULE_FREETYPE_ENABLED
- if (!fd->msdf && fd->cache[size]->face) {
+ if (!fd->msdf && ffsd->face) {
// LCD layout, bits 24, 25, 26
if (fd->antialiasing == FONT_ANTIALIASING_LCD) {
- TextServer::FontLCDSubpixelLayout layout = (TextServer::FontLCDSubpixelLayout)(int)GLOBAL_GET("gui/theme/lcd_subpixel_layout");
+ TextServer::FontLCDSubpixelLayout layout = lcd_subpixel_layout.get();
if (layout != FONT_LCD_SUBPIXEL_LAYOUT_NONE) {
lcd_aa = true;
index = index | (layout << 24);
@@ -3768,24 +3842,24 @@ void TextServerAdvanced::_font_draw_glyph_outline(const RID &p_font_rid, const R
}
#endif
- if (!_ensure_glyph(fd, size, index)) {
+ FontGlyph fgl;
+ if (!_ensure_glyph(fd, size, index, fgl)) {
return; // Invalid or non-graphical glyph, do not display errors, nothing to draw.
}
- const FontGlyph &gl = fd->cache[size]->glyph_map[index];
- if (gl.found) {
- ERR_FAIL_COND(gl.texture_idx < -1 || gl.texture_idx >= fd->cache[size]->textures.size());
+ if (fgl.found) {
+ ERR_FAIL_COND(fgl.texture_idx < -1 || fgl.texture_idx >= ffsd->textures.size());
- if (gl.texture_idx != -1) {
+ if (fgl.texture_idx != -1) {
Color modulate = p_color;
#ifdef MODULE_FREETYPE_ENABLED
- if (fd->cache[size]->face && fd->cache[size]->textures[gl.texture_idx].image.is_valid() && (fd->cache[size]->textures[gl.texture_idx].image->get_format() == Image::FORMAT_RGBA8) && !lcd_aa && !fd->msdf) {
+ if (ffsd->face && fd->cache[size]->textures[fgl.texture_idx].image.is_valid() && (ffsd->textures[fgl.texture_idx].image->get_format() == Image::FORMAT_RGBA8) && !lcd_aa && !fd->msdf) {
modulate.r = modulate.g = modulate.b = 1.0;
}
#endif
if (RenderingServer::get_singleton() != nullptr) {
- if (fd->cache[size]->textures[gl.texture_idx].dirty) {
- ShelfPackTexture &tex = fd->cache[size]->textures.write[gl.texture_idx];
+ if (ffsd->textures[fgl.texture_idx].dirty) {
+ ShelfPackTexture &tex = ffsd->textures.write[fgl.texture_idx];
Ref<Image> img = tex.image;
if (fd->mipmaps && !img->has_mipmaps()) {
img = tex.image->duplicate();
@@ -3798,12 +3872,12 @@ void TextServerAdvanced::_font_draw_glyph_outline(const RID &p_font_rid, const R
}
tex.dirty = false;
}
- RID texture = fd->cache[size]->textures[gl.texture_idx].texture->get_rid();
+ RID texture = ffsd->textures[fgl.texture_idx].texture->get_rid();
if (fd->msdf) {
Point2 cpos = p_pos;
- cpos += gl.rect.position * (double)p_size / (double)fd->msdf_source_size;
- Size2 csize = gl.rect.size * (double)p_size / (double)fd->msdf_source_size;
- RenderingServer::get_singleton()->canvas_item_add_msdf_texture_rect_region(p_canvas, Rect2(cpos, csize), texture, gl.uv_rect, modulate, p_outline_size, fd->msdf_range, (double)p_size / (double)fd->msdf_source_size);
+ cpos += fgl.rect.position * (double)p_size / (double)fd->msdf_source_size;
+ Size2 csize = fgl.rect.size * (double)p_size / (double)fd->msdf_source_size;
+ RenderingServer::get_singleton()->canvas_item_add_msdf_texture_rect_region(p_canvas, Rect2(cpos, csize), texture, fgl.uv_rect, modulate, p_outline_size, fd->msdf_range, (double)p_size / (double)fd->msdf_source_size);
} else {
Point2 cpos = p_pos;
double scale = _font_get_scale(p_font_rid, p_size);
@@ -3816,8 +3890,8 @@ void TextServerAdvanced::_font_draw_glyph_outline(const RID &p_font_rid, const R
cpos.y = Math::floor(cpos.y);
cpos.x = Math::floor(cpos.x);
}
- Vector2 gpos = gl.rect.position;
- Size2 csize = gl.rect.size;
+ Vector2 gpos = fgl.rect.position;
+ Size2 csize = fgl.rect.size;
if (fd->fixed_size > 0 && fd->fixed_size_scale_mode != FIXED_SIZE_SCALE_DISABLE && size.x != p_size) {
if (fd->fixed_size_scale_mode == FIXED_SIZE_SCALE_ENABLED) {
double gl_scale = (double)p_size / (double)fd->fixed_size;
@@ -3831,9 +3905,9 @@ void TextServerAdvanced::_font_draw_glyph_outline(const RID &p_font_rid, const R
}
cpos += gpos;
if (lcd_aa) {
- RenderingServer::get_singleton()->canvas_item_add_lcd_texture_rect_region(p_canvas, Rect2(cpos, csize), texture, gl.uv_rect, modulate);
+ RenderingServer::get_singleton()->canvas_item_add_lcd_texture_rect_region(p_canvas, Rect2(cpos, csize), texture, fgl.uv_rect, modulate);
} else {
- RenderingServer::get_singleton()->canvas_item_add_texture_rect_region(p_canvas, Rect2(cpos, csize), texture, gl.uv_rect, modulate, false, false);
+ RenderingServer::get_singleton()->canvas_item_add_texture_rect_region(p_canvas, Rect2(cpos, csize), texture, fgl.uv_rect, modulate, false, false);
}
}
}
@@ -3898,7 +3972,8 @@ bool TextServerAdvanced::_font_is_script_supported(const RID &p_font_rid, const
return fd->script_support_overrides[p_script];
} else {
Vector2i size = _get_size(fd, 16);
- ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size), false);
+ FontForSizeAdvanced *ffsd = nullptr;
+ ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size, ffsd), false);
return fd->supported_scripts.has(hb_tag_from_string(p_script.ascii().get_data(), -1));
}
}
@@ -3945,7 +4020,8 @@ void TextServerAdvanced::_font_set_opentype_feature_overrides(const RID &p_font_
MutexLock lock(fd->mutex);
Vector2i size = _get_size(fd, 16);
- ERR_FAIL_COND(!_ensure_cache_for_size(fd, size));
+ FontForSizeAdvanced *ffsd = nullptr;
+ ERR_FAIL_COND(!_ensure_cache_for_size(fd, size, ffsd));
fd->feature_overrides = p_overrides;
}
@@ -3963,7 +4039,8 @@ Dictionary TextServerAdvanced::_font_supported_feature_list(const RID &p_font_ri
MutexLock lock(fd->mutex);
Vector2i size = _get_size(fd, 16);
- ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size), Dictionary());
+ FontForSizeAdvanced *ffsd = nullptr;
+ ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size, ffsd), Dictionary());
return fd->supported_features;
}
@@ -3973,7 +4050,8 @@ Dictionary TextServerAdvanced::_font_supported_variation_list(const RID &p_font_
MutexLock lock(fd->mutex);
Vector2i size = _get_size(fd, 16);
- ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size), Dictionary());
+ FontForSizeAdvanced *ffsd = nullptr;
+ ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size, ffsd), Dictionary());
return fd->supported_varaitions;
}
@@ -4048,7 +4126,7 @@ int64_t TextServerAdvanced::_convert_pos_inv(const ShapedTextDataAdvanced *p_sd,
}
void TextServerAdvanced::invalidate(TextServerAdvanced::ShapedTextDataAdvanced *p_shaped, bool p_text) {
- p_shaped->valid = false;
+ p_shaped->valid.clear();
p_shaped->sort_valid = false;
p_shaped->line_breaks_valid = false;
p_shaped->justification_ops_valid = false;
@@ -4404,7 +4482,7 @@ bool TextServerAdvanced::_shaped_text_resize_object(const RID &p_shaped, const V
sd->objects[p_key].rect.size = p_size;
sd->objects[p_key].inline_align = p_inline_align;
sd->objects[p_key].baseline = p_baseline;
- if (sd->valid) {
+ if (sd->valid.is_set()) {
// Recalc string metrics.
sd->ascent = 0;
sd->descent = 0;
@@ -4548,7 +4626,7 @@ RID TextServerAdvanced::_shaped_text_substr(const RID &p_shaped, int64_t p_start
if (sd->parent != RID()) {
return _shaped_text_substr(sd->parent, p_start, p_length);
}
- if (!sd->valid) {
+ if (!sd->valid.is_set()) {
const_cast<TextServerAdvanced *>(this)->_shaped_text_shape(p_shaped);
}
ERR_FAIL_COND_V(p_start < 0 || p_length < 0, RID());
@@ -4576,7 +4654,7 @@ RID TextServerAdvanced::_shaped_text_substr(const RID &p_shaped, int64_t p_start
}
bool TextServerAdvanced::_shape_substr(ShapedTextDataAdvanced *p_new_sd, const ShapedTextDataAdvanced *p_sd, int64_t p_start, int64_t p_length) const {
- if (p_new_sd->valid) {
+ if (p_new_sd->valid.is_set()) {
return true;
}
@@ -4725,7 +4803,7 @@ bool TextServerAdvanced::_shape_substr(ShapedTextDataAdvanced *p_new_sd, const S
_realign(p_new_sd);
}
- p_new_sd->valid = true;
+ p_new_sd->valid.set();
return true;
}
@@ -4743,7 +4821,7 @@ double TextServerAdvanced::_shaped_text_fit_to_width(const RID &p_shaped, double
ERR_FAIL_NULL_V(sd, 0.0);
MutexLock lock(sd->mutex);
- if (!sd->valid) {
+ if (!sd->valid.is_set()) {
const_cast<TextServerAdvanced *>(this)->_shaped_text_shape(p_shaped);
}
if (!sd->justification_ops_valid) {
@@ -4900,7 +4978,7 @@ double TextServerAdvanced::_shaped_text_tab_align(const RID &p_shaped, const Pac
ERR_FAIL_NULL_V(sd, 0.0);
MutexLock lock(sd->mutex);
- if (!sd->valid) {
+ if (!sd->valid.is_set()) {
const_cast<TextServerAdvanced *>(this)->_shaped_text_shape(p_shaped);
}
if (!sd->line_breaks_valid) {
@@ -5117,7 +5195,7 @@ void TextServerAdvanced::_shaped_text_overrun_trim_to_width(const RID &p_shaped_
ERR_FAIL_NULL_MSG(sd, "ShapedTextDataAdvanced invalid.");
MutexLock lock(sd->mutex);
- if (!sd->valid) {
+ if (!sd->valid.is_set()) {
_shaped_text_shape(p_shaped_line);
}
@@ -5144,7 +5222,7 @@ void TextServerAdvanced::_shaped_text_overrun_trim_to_width(const RID &p_shaped_
Vector<ShapedTextDataAdvanced::Span> &spans = sd->spans;
if (sd->parent != RID()) {
ShapedTextDataAdvanced *parent_sd = shaped_owner.get_or_null(sd->parent);
- ERR_FAIL_COND(!parent_sd->valid);
+ ERR_FAIL_COND(!parent_sd->valid.is_set());
spans = parent_sd->spans;
}
@@ -5355,7 +5433,7 @@ void TextServerAdvanced::_update_chars(ShapedTextDataAdvanced *p_sd) const {
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);
+ ERR_FAIL_COND(!parent_sd->valid.is_set());
spans = parent_sd->spans;
}
@@ -5403,7 +5481,7 @@ PackedInt32Array TextServerAdvanced::_shaped_text_get_character_breaks(const RID
ERR_FAIL_NULL_V(sd, PackedInt32Array());
MutexLock lock(sd->mutex);
- if (!sd->valid) {
+ if (!sd->valid.is_set()) {
const_cast<TextServerAdvanced *>(this)->_shaped_text_shape(p_shaped);
}
@@ -5417,7 +5495,7 @@ bool TextServerAdvanced::_shaped_text_update_breaks(const RID &p_shaped) {
ERR_FAIL_NULL_V(sd, false);
MutexLock lock(sd->mutex);
- if (!sd->valid) {
+ if (!sd->valid.is_set()) {
_shaped_text_shape(p_shaped);
}
@@ -5688,7 +5766,7 @@ bool TextServerAdvanced::_shaped_text_update_justification_ops(const RID &p_shap
ERR_FAIL_NULL_V(sd, false);
MutexLock lock(sd->mutex);
- if (!sd->valid) {
+ if (!sd->valid.is_set()) {
_shaped_text_shape(p_shaped);
}
if (!sd->line_breaks_valid) {
@@ -5902,7 +5980,7 @@ _FORCE_INLINE_ void TextServerAdvanced::_add_featuers(const Dictionary &p_source
int32_t value = values[i];
if (value >= 0) {
hb_feature_t feature;
- if (keys[i].get_type() == Variant::STRING) {
+ if (keys[i].is_string()) {
feature.tag = _name_to_tag(keys[i]);
} else {
feature.tag = keys[i];
@@ -6068,7 +6146,7 @@ void TextServerAdvanced::_shape_run(ShapedTextDataAdvanced *p_sd, int64_t p_star
int mod = 0;
if (fd->antialiasing == FONT_ANTIALIASING_LCD) {
- TextServer::FontLCDSubpixelLayout layout = (TextServer::FontLCDSubpixelLayout)(int)GLOBAL_GET("gui/theme/lcd_subpixel_layout");
+ TextServer::FontLCDSubpixelLayout layout = lcd_subpixel_layout.get();
if (layout != FONT_LCD_SUBPIXEL_LAYOUT_NONE) {
mod = (layout << 24);
}
@@ -6128,7 +6206,8 @@ void TextServerAdvanced::_shape_run(ShapedTextDataAdvanced *p_sd, int64_t p_star
gl.index = glyph_info[i].codepoint;
if (gl.index != 0) {
- _ensure_glyph(fd, fss, gl.index | mod);
+ FontGlyph fgl;
+ _ensure_glyph(fd, fss, gl.index | mod, fgl);
if (subpos) {
gl.x_off = (double)glyph_pos[i].x_offset / (64.0 / scale);
} else if (p_sd->orientation == ORIENTATION_HORIZONTAL) {
@@ -6240,7 +6319,7 @@ bool TextServerAdvanced::_shaped_text_shape(const RID &p_shaped) {
ERR_FAIL_NULL_V(sd, false);
MutexLock lock(sd->mutex);
- if (sd->valid) {
+ if (sd->valid.is_set()) {
return true;
}
@@ -6248,13 +6327,13 @@ bool TextServerAdvanced::_shaped_text_shape(const RID &p_shaped) {
if (sd->parent != RID()) {
_shaped_text_shape(sd->parent);
ShapedTextDataAdvanced *parent_sd = shaped_owner.get_or_null(sd->parent);
- ERR_FAIL_COND_V(!parent_sd->valid, false);
+ ERR_FAIL_COND_V(!parent_sd->valid.is_set(), false);
ERR_FAIL_COND_V(!_shape_substr(sd, parent_sd, sd->start, sd->end - sd->start), false);
return true;
}
if (sd->text.length() == 0) {
- sd->valid = true;
+ sd->valid.set();
return true;
}
@@ -6447,16 +6526,16 @@ bool TextServerAdvanced::_shaped_text_shape(const RID &p_shaped) {
}
_realign(sd);
- sd->valid = true;
- return sd->valid;
+ sd->valid.set();
+ return sd->valid.is_set();
}
bool TextServerAdvanced::_shaped_text_is_ready(const RID &p_shaped) const {
const ShapedTextDataAdvanced *sd = shaped_owner.get_or_null(p_shaped);
ERR_FAIL_NULL_V(sd, false);
- MutexLock lock(sd->mutex);
- return sd->valid;
+ // Atomic read is safe and faster.
+ return sd->valid.is_set();
}
const Glyph *TextServerAdvanced::_shaped_text_get_glyphs(const RID &p_shaped) const {
@@ -6464,7 +6543,7 @@ const Glyph *TextServerAdvanced::_shaped_text_get_glyphs(const RID &p_shaped) co
ERR_FAIL_NULL_V(sd, nullptr);
MutexLock lock(sd->mutex);
- if (!sd->valid) {
+ if (!sd->valid.is_set()) {
const_cast<TextServerAdvanced *>(this)->_shaped_text_shape(p_shaped);
}
return sd->glyphs.ptr();
@@ -6475,7 +6554,7 @@ int64_t TextServerAdvanced::_shaped_text_get_glyph_count(const RID &p_shaped) co
ERR_FAIL_NULL_V(sd, 0);
MutexLock lock(sd->mutex);
- if (!sd->valid) {
+ if (!sd->valid.is_set()) {
const_cast<TextServerAdvanced *>(this)->_shaped_text_shape(p_shaped);
}
return sd->glyphs.size();
@@ -6486,7 +6565,7 @@ const Glyph *TextServerAdvanced::_shaped_text_sort_logical(const RID &p_shaped)
ERR_FAIL_NULL_V(sd, nullptr);
MutexLock lock(sd->mutex);
- if (!sd->valid) {
+ if (!sd->valid.is_set()) {
const_cast<TextServerAdvanced *>(this)->_shaped_text_shape(p_shaped);
}
@@ -6526,7 +6605,7 @@ Rect2 TextServerAdvanced::_shaped_text_get_object_rect(const RID &p_shaped, cons
MutexLock lock(sd->mutex);
ERR_FAIL_COND_V(!sd->objects.has(p_key), Rect2());
- if (!sd->valid) {
+ if (!sd->valid.is_set()) {
const_cast<TextServerAdvanced *>(this)->_shaped_text_shape(p_shaped);
}
return sd->objects[p_key].rect;
@@ -6547,7 +6626,7 @@ int64_t TextServerAdvanced::_shaped_text_get_object_glyph(const RID &p_shaped, c
MutexLock lock(sd->mutex);
ERR_FAIL_COND_V(!sd->objects.has(p_key), -1);
- if (!sd->valid) {
+ if (!sd->valid.is_set()) {
const_cast<TextServerAdvanced *>(this)->_shaped_text_shape(p_shaped);
}
const ShapedTextDataAdvanced::EmbeddedObject &obj = sd->objects[p_key];
@@ -6566,7 +6645,7 @@ Size2 TextServerAdvanced::_shaped_text_get_size(const RID &p_shaped) const {
ERR_FAIL_NULL_V(sd, Size2());
MutexLock lock(sd->mutex);
- if (!sd->valid) {
+ if (!sd->valid.is_set()) {
const_cast<TextServerAdvanced *>(this)->_shaped_text_shape(p_shaped);
}
if (sd->orientation == TextServer::ORIENTATION_HORIZONTAL) {
@@ -6581,7 +6660,7 @@ double TextServerAdvanced::_shaped_text_get_ascent(const RID &p_shaped) const {
ERR_FAIL_NULL_V(sd, 0.0);
MutexLock lock(sd->mutex);
- if (!sd->valid) {
+ if (!sd->valid.is_set()) {
const_cast<TextServerAdvanced *>(this)->_shaped_text_shape(p_shaped);
}
return sd->ascent + sd->extra_spacing[SPACING_TOP];
@@ -6592,7 +6671,7 @@ double TextServerAdvanced::_shaped_text_get_descent(const RID &p_shaped) const {
ERR_FAIL_NULL_V(sd, 0.0);
MutexLock lock(sd->mutex);
- if (!sd->valid) {
+ if (!sd->valid.is_set()) {
const_cast<TextServerAdvanced *>(this)->_shaped_text_shape(p_shaped);
}
return sd->descent + sd->extra_spacing[SPACING_BOTTOM];
@@ -6603,7 +6682,7 @@ double TextServerAdvanced::_shaped_text_get_width(const RID &p_shaped) const {
ERR_FAIL_NULL_V(sd, 0.0);
MutexLock lock(sd->mutex);
- if (!sd->valid) {
+ if (!sd->valid.is_set()) {
const_cast<TextServerAdvanced *>(this)->_shaped_text_shape(p_shaped);
}
return Math::ceil(sd->text_trimmed ? sd->width_trimmed : sd->width);
@@ -6614,7 +6693,7 @@ double TextServerAdvanced::_shaped_text_get_underline_position(const RID &p_shap
ERR_FAIL_NULL_V(sd, 0.0);
MutexLock lock(sd->mutex);
- if (!sd->valid) {
+ if (!sd->valid.is_set()) {
const_cast<TextServerAdvanced *>(this)->_shaped_text_shape(p_shaped);
}
@@ -6626,7 +6705,7 @@ double TextServerAdvanced::_shaped_text_get_underline_thickness(const RID &p_sha
ERR_FAIL_NULL_V(sd, 0.0);
MutexLock lock(sd->mutex);
- if (!sd->valid) {
+ if (!sd->valid.is_set()) {
const_cast<TextServerAdvanced *>(this)->_shaped_text_shape(p_shaped);
}
@@ -7424,10 +7503,15 @@ bool TextServerAdvanced::_is_valid_letter(uint64_t p_unicode) const {
return u_isalpha(p_unicode);
}
+void TextServerAdvanced::_update_settings() {
+ lcd_subpixel_layout.set((TextServer::FontLCDSubpixelLayout)(int)GLOBAL_GET("gui/theme/lcd_subpixel_layout"));
+}
+
TextServerAdvanced::TextServerAdvanced() {
_insert_num_systems_lang();
_insert_feature_sets();
_bmp_create_font_funcs();
+ ProjectSettings::get_singleton()->connect("settings_changed", callable_mp(this, &TextServerAdvanced::_update_settings));
}
void TextServerAdvanced::_cleanup() {
diff --git a/modules/text_server_adv/text_server_adv.h b/modules/text_server_adv/text_server_adv.h
index fdebb8e4cd..448be9ebe4 100644
--- a/modules/text_server_adv/text_server_adv.h
+++ b/modules/text_server_adv/text_server_adv.h
@@ -74,6 +74,7 @@
#include <godot_cpp/templates/hash_map.hpp>
#include <godot_cpp/templates/hash_set.hpp>
#include <godot_cpp/templates/rid_owner.hpp>
+#include <godot_cpp/templates/safe_refcount.hpp>
#include <godot_cpp/templates/vector.hpp>
using namespace godot;
@@ -85,6 +86,7 @@ using namespace godot;
#include "core/object/worker_thread_pool.h"
#include "core/templates/hash_map.h"
#include "core/templates/rid_owner.h"
+#include "core/templates/safe_refcount.h"
#include "scene/resources/image_texture.h"
#include "servers/text/text_server_extension.h"
@@ -151,6 +153,9 @@ class TextServerAdvanced : public TextServerExtension {
HashMap<StringName, int32_t> feature_sets;
HashMap<int32_t, FeatureInfo> feature_sets_inv;
+ SafeNumeric<TextServer::FontLCDSubpixelLayout> lcd_subpixel_layout{ TextServer::FontLCDSubpixelLayout::FONT_LCD_SUBPIXEL_LAYOUT_NONE };
+ void _update_settings();
+
void _insert_num_systems_lang();
void _insert_feature_sets();
_FORCE_INLINE_ void _insert_feature(const StringName &p_name, int32_t p_tag, Variant::Type p_vtype = Variant::INT, bool p_hidden = false);
@@ -327,7 +332,7 @@ class TextServerAdvanced : public TextServerExtension {
int extra_spacing[4] = { 0, 0, 0, 0 };
double baseline_offset = 0.0;
- HashMap<Vector2i, FontForSizeAdvanced *, VariantHasher, VariantComparator> cache;
+ HashMap<Vector2i, FontForSizeAdvanced *> cache;
bool face_init = false;
HashSet<uint32_t> supported_scripts;
@@ -359,8 +364,8 @@ class TextServerAdvanced : public TextServerExtension {
#ifdef MODULE_FREETYPE_ENABLED
_FORCE_INLINE_ FontGlyph rasterize_bitmap(FontForSizeAdvanced *p_data, int p_rect_margin, FT_Bitmap p_bitmap, int p_yofs, int p_xofs, const Vector2 &p_advance, bool p_bgra) const;
#endif
- _FORCE_INLINE_ bool _ensure_glyph(FontAdvanced *p_font_data, const Vector2i &p_size, int32_t p_glyph) const;
- _FORCE_INLINE_ bool _ensure_cache_for_size(FontAdvanced *p_font_data, const Vector2i &p_size) const;
+ _FORCE_INLINE_ bool _ensure_glyph(FontAdvanced *p_font_data, const Vector2i &p_size, int32_t p_glyph, FontGlyph &r_glyph) const;
+ _FORCE_INLINE_ bool _ensure_cache_for_size(FontAdvanced *p_font_data, const Vector2i &p_size, FontForSizeAdvanced *&r_cache_for_size) const;
_FORCE_INLINE_ void _font_clear_cache(FontAdvanced *p_font_data);
static void _generateMTSDF_threaded(void *p_td, uint32_t p_y);
@@ -487,7 +492,7 @@ class TextServerAdvanced : public TextServerExtension {
/* Shaped data */
TextServer::Direction para_direction = DIRECTION_LTR; // Detected text direction.
int base_para_direction = UBIDI_DEFAULT_LTR;
- bool valid = false; // String is shaped.
+ SafeFlag valid{ false }; // String is shaped.
bool line_breaks_valid = false; // Line and word break flags are populated (and virtual zero width spaces inserted).
bool justification_ops_valid = false; // Virtual elongation glyphs are added to the string.
bool sort_valid = false;
diff --git a/modules/text_server_fb/text_server_fb.cpp b/modules/text_server_fb/text_server_fb.cpp
index a7ddfc719e..540ba19cac 100644
--- a/modules/text_server_fb/text_server_fb.cpp
+++ b/modules/text_server_fb/text_server_fb.cpp
@@ -624,18 +624,21 @@ _FORCE_INLINE_ TextServerFallback::FontGlyph TextServerFallback::rasterize_bitma
/* Font Cache */
/*************************************************************************/
-_FORCE_INLINE_ bool TextServerFallback::_ensure_glyph(FontFallback *p_font_data, const Vector2i &p_size, int32_t p_glyph) const {
- ERR_FAIL_COND_V(!_ensure_cache_for_size(p_font_data, p_size), false);
+_FORCE_INLINE_ bool TextServerFallback::_ensure_glyph(FontFallback *p_font_data, const Vector2i &p_size, int32_t p_glyph, FontGlyph &r_glyph) const {
+ FontForSizeFallback *fd = nullptr;
+ ERR_FAIL_COND_V(!_ensure_cache_for_size(p_font_data, p_size, fd), false);
int32_t glyph_index = p_glyph & 0xffffff; // Remove subpixel shifts.
- FontForSizeFallback *fd = p_font_data->cache[p_size];
- if (fd->glyph_map.has(p_glyph)) {
- return fd->glyph_map[p_glyph].found;
+ HashMap<int32_t, FontGlyph>::Iterator E = fd->glyph_map.find(p_glyph);
+ if (E) {
+ r_glyph = E->value;
+ return E->value.found;
}
if (glyph_index == 0) { // Non graphical or invalid glyph, do not render.
- fd->glyph_map[p_glyph] = FontGlyph();
+ E = fd->glyph_map.insert(p_glyph, FontGlyph());
+ r_glyph = E->value;
return true;
}
@@ -673,7 +676,8 @@ _FORCE_INLINE_ bool TextServerFallback::_ensure_glyph(FontFallback *p_font_data,
int error = FT_Load_Glyph(fd->face, glyph_index, flags);
if (error) {
- fd->glyph_map[p_glyph] = FontGlyph();
+ E = fd->glyph_map.insert(p_glyph, FontGlyph());
+ r_glyph = E->value;
return false;
}
@@ -777,20 +781,26 @@ _FORCE_INLINE_ bool TextServerFallback::_ensure_glyph(FontFallback *p_font_data,
cleanup_stroker:
FT_Stroker_Done(stroker);
}
- fd->glyph_map[p_glyph] = gl;
+ E = fd->glyph_map.insert(p_glyph, gl);
+ r_glyph = E->value;
return gl.found;
}
#endif
- fd->glyph_map[p_glyph] = FontGlyph();
+ E = fd->glyph_map.insert(p_glyph, FontGlyph());
+ r_glyph = E->value;
return false;
}
-_FORCE_INLINE_ bool TextServerFallback::_ensure_cache_for_size(FontFallback *p_font_data, const Vector2i &p_size) const {
+_FORCE_INLINE_ bool TextServerFallback::_ensure_cache_for_size(FontFallback *p_font_data, const Vector2i &p_size, FontForSizeFallback *&r_cache_for_size) const {
ERR_FAIL_COND_V(p_size.x <= 0, false);
- if (p_font_data->cache.has(p_size)) {
+
+ HashMap<Vector2i, FontForSizeFallback *>::Iterator E = p_font_data->cache.find(p_size);
+ if (E) {
+ r_cache_for_size = E->value;
return true;
}
+ r_cache_for_size = nullptr;
FontForSizeFallback *fd = memnew(FontForSizeFallback);
fd->size = p_size;
if (p_font_data->data_ptr && (p_font_data->data_size > 0)) {
@@ -973,7 +983,9 @@ _FORCE_INLINE_ bool TextServerFallback::_ensure_cache_for_size(FontFallback *p_f
ERR_FAIL_V_MSG(false, "FreeType: Can't load dynamic font, engine is compiled without FreeType support!");
#endif
}
- p_font_data->cache[p_size] = fd;
+
+ p_font_data->cache.insert(p_size, fd);
+ r_cache_for_size = fd;
return true;
}
@@ -1041,7 +1053,8 @@ void TextServerFallback::_font_set_style(const RID &p_font_rid, BitField<FontSty
MutexLock lock(fd->mutex);
Vector2i size = _get_size(fd, 16);
- ERR_FAIL_COND(!_ensure_cache_for_size(fd, size));
+ FontForSizeFallback *ffsd = nullptr;
+ ERR_FAIL_COND(!_ensure_cache_for_size(fd, size, ffsd));
fd->style_flags = p_style;
}
@@ -1119,7 +1132,8 @@ BitField<TextServer::FontStyle> TextServerFallback::_font_get_style(const RID &p
MutexLock lock(fd->mutex);
Vector2i size = _get_size(fd, 16);
- ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size), 0);
+ FontForSizeFallback *ffsd = nullptr;
+ ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size, ffsd), 0);
return fd->style_flags;
}
@@ -1129,7 +1143,8 @@ void TextServerFallback::_font_set_style_name(const RID &p_font_rid, const Strin
MutexLock lock(fd->mutex);
Vector2i size = _get_size(fd, 16);
- ERR_FAIL_COND(!_ensure_cache_for_size(fd, size));
+ FontForSizeFallback *ffsd = nullptr;
+ ERR_FAIL_COND(!_ensure_cache_for_size(fd, size, ffsd));
fd->style_name = p_name;
}
@@ -1139,7 +1154,8 @@ String TextServerFallback::_font_get_style_name(const RID &p_font_rid) const {
MutexLock lock(fd->mutex);
Vector2i size = _get_size(fd, 16);
- ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size), String());
+ FontForSizeFallback *ffsd = nullptr;
+ ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size, ffsd), String());
return fd->style_name;
}
@@ -1149,7 +1165,8 @@ void TextServerFallback::_font_set_weight(const RID &p_font_rid, int64_t p_weigh
MutexLock lock(fd->mutex);
Vector2i size = _get_size(fd, 16);
- ERR_FAIL_COND(!_ensure_cache_for_size(fd, size));
+ FontForSizeFallback *ffsd = nullptr;
+ ERR_FAIL_COND(!_ensure_cache_for_size(fd, size, ffsd));
fd->weight = CLAMP(p_weight, 100, 999);
}
@@ -1159,7 +1176,8 @@ int64_t TextServerFallback::_font_get_weight(const RID &p_font_rid) const {
MutexLock lock(fd->mutex);
Vector2i size = _get_size(fd, 16);
- ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size), 400);
+ FontForSizeFallback *ffsd = nullptr;
+ ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size, ffsd), 400);
return fd->weight;
}
@@ -1169,7 +1187,8 @@ void TextServerFallback::_font_set_stretch(const RID &p_font_rid, int64_t p_stre
MutexLock lock(fd->mutex);
Vector2i size = _get_size(fd, 16);
- ERR_FAIL_COND(!_ensure_cache_for_size(fd, size));
+ FontForSizeFallback *ffsd = nullptr;
+ ERR_FAIL_COND(!_ensure_cache_for_size(fd, size, ffsd));
fd->stretch = CLAMP(p_stretch, 50, 200);
}
@@ -1179,7 +1198,8 @@ int64_t TextServerFallback::_font_get_stretch(const RID &p_font_rid) const {
MutexLock lock(fd->mutex);
Vector2i size = _get_size(fd, 16);
- ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size), 100);
+ FontForSizeFallback *ffsd = nullptr;
+ ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size, ffsd), 100);
return fd->stretch;
}
@@ -1189,7 +1209,8 @@ void TextServerFallback::_font_set_name(const RID &p_font_rid, const String &p_n
MutexLock lock(fd->mutex);
Vector2i size = _get_size(fd, 16);
- ERR_FAIL_COND(!_ensure_cache_for_size(fd, size));
+ FontForSizeFallback *ffsd = nullptr;
+ ERR_FAIL_COND(!_ensure_cache_for_size(fd, size, ffsd));
fd->font_name = p_name;
}
@@ -1199,7 +1220,8 @@ String TextServerFallback::_font_get_name(const RID &p_font_rid) const {
MutexLock lock(fd->mutex);
Vector2i size = _get_size(fd, 16);
- ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size), String());
+ FontForSizeFallback *ffsd = nullptr;
+ ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size, ffsd), String());
return fd->font_name;
}
@@ -1607,8 +1629,9 @@ void TextServerFallback::_font_set_ascent(const RID &p_font_rid, int64_t p_size,
MutexLock lock(fd->mutex);
Vector2i size = _get_size(fd, p_size);
- ERR_FAIL_COND(!_ensure_cache_for_size(fd, size));
- fd->cache[size]->ascent = p_ascent;
+ FontForSizeFallback *ffsd = nullptr;
+ ERR_FAIL_COND(!_ensure_cache_for_size(fd, size, ffsd));
+ ffsd->ascent = p_ascent;
}
double TextServerFallback::_font_get_ascent(const RID &p_font_rid, int64_t p_size) const {
@@ -1618,18 +1641,19 @@ double TextServerFallback::_font_get_ascent(const RID &p_font_rid, int64_t p_siz
MutexLock lock(fd->mutex);
Vector2i size = _get_size(fd, p_size);
- ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size), 0.0);
+ FontForSizeFallback *ffsd = nullptr;
+ ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size, ffsd), 0.0);
if (fd->msdf) {
- return fd->cache[size]->ascent * (double)p_size / (double)fd->msdf_source_size;
+ return ffsd->ascent * (double)p_size / (double)fd->msdf_source_size;
} else if (fd->fixed_size > 0 && fd->fixed_size_scale_mode != FIXED_SIZE_SCALE_DISABLE && size.x != p_size) {
if (fd->fixed_size_scale_mode == FIXED_SIZE_SCALE_ENABLED) {
- return fd->cache[size]->ascent * (double)p_size / (double)fd->fixed_size;
+ return ffsd->ascent * (double)p_size / (double)fd->fixed_size;
} else {
- return fd->cache[size]->ascent * Math::round((double)p_size / (double)fd->fixed_size);
+ return ffsd->ascent * Math::round((double)p_size / (double)fd->fixed_size);
}
} else {
- return fd->cache[size]->ascent;
+ return ffsd->ascent;
}
}
@@ -1639,8 +1663,9 @@ void TextServerFallback::_font_set_descent(const RID &p_font_rid, int64_t p_size
Vector2i size = _get_size(fd, p_size);
- ERR_FAIL_COND(!_ensure_cache_for_size(fd, size));
- fd->cache[size]->descent = p_descent;
+ FontForSizeFallback *ffsd = nullptr;
+ ERR_FAIL_COND(!_ensure_cache_for_size(fd, size, ffsd));
+ ffsd->descent = p_descent;
}
double TextServerFallback::_font_get_descent(const RID &p_font_rid, int64_t p_size) const {
@@ -1650,18 +1675,19 @@ double TextServerFallback::_font_get_descent(const RID &p_font_rid, int64_t p_si
MutexLock lock(fd->mutex);
Vector2i size = _get_size(fd, p_size);
- ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size), 0.0);
+ FontForSizeFallback *ffsd = nullptr;
+ ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size, ffsd), 0.0);
if (fd->msdf) {
- return fd->cache[size]->descent * (double)p_size / (double)fd->msdf_source_size;
+ return ffsd->descent * (double)p_size / (double)fd->msdf_source_size;
} else if (fd->fixed_size > 0 && fd->fixed_size_scale_mode != FIXED_SIZE_SCALE_DISABLE && size.x != p_size) {
if (fd->fixed_size_scale_mode == FIXED_SIZE_SCALE_ENABLED) {
- return fd->cache[size]->descent * (double)p_size / (double)fd->fixed_size;
+ return ffsd->descent * (double)p_size / (double)fd->fixed_size;
} else {
- return fd->cache[size]->descent * Math::round((double)p_size / (double)fd->fixed_size);
+ return ffsd->descent * Math::round((double)p_size / (double)fd->fixed_size);
}
} else {
- return fd->cache[size]->descent;
+ return ffsd->descent;
}
}
@@ -1672,8 +1698,9 @@ void TextServerFallback::_font_set_underline_position(const RID &p_font_rid, int
MutexLock lock(fd->mutex);
Vector2i size = _get_size(fd, p_size);
- ERR_FAIL_COND(!_ensure_cache_for_size(fd, size));
- fd->cache[size]->underline_position = p_underline_position;
+ FontForSizeFallback *ffsd = nullptr;
+ ERR_FAIL_COND(!_ensure_cache_for_size(fd, size, ffsd));
+ ffsd->underline_position = p_underline_position;
}
double TextServerFallback::_font_get_underline_position(const RID &p_font_rid, int64_t p_size) const {
@@ -1683,18 +1710,19 @@ double TextServerFallback::_font_get_underline_position(const RID &p_font_rid, i
MutexLock lock(fd->mutex);
Vector2i size = _get_size(fd, p_size);
- ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size), 0.0);
+ FontForSizeFallback *ffsd = nullptr;
+ ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size, ffsd), 0.0);
if (fd->msdf) {
- return fd->cache[size]->underline_position * (double)p_size / (double)fd->msdf_source_size;
+ return ffsd->underline_position * (double)p_size / (double)fd->msdf_source_size;
} else if (fd->fixed_size > 0 && fd->fixed_size_scale_mode != FIXED_SIZE_SCALE_DISABLE && size.x != p_size) {
if (fd->fixed_size_scale_mode == FIXED_SIZE_SCALE_ENABLED) {
- return fd->cache[size]->underline_position * (double)p_size / (double)fd->fixed_size;
+ return ffsd->underline_position * (double)p_size / (double)fd->fixed_size;
} else {
- return fd->cache[size]->underline_position * Math::round((double)p_size / (double)fd->fixed_size);
+ return ffsd->underline_position * Math::round((double)p_size / (double)fd->fixed_size);
}
} else {
- return fd->cache[size]->underline_position;
+ return ffsd->underline_position;
}
}
@@ -1705,8 +1733,9 @@ void TextServerFallback::_font_set_underline_thickness(const RID &p_font_rid, in
MutexLock lock(fd->mutex);
Vector2i size = _get_size(fd, p_size);
- ERR_FAIL_COND(!_ensure_cache_for_size(fd, size));
- fd->cache[size]->underline_thickness = p_underline_thickness;
+ FontForSizeFallback *ffsd = nullptr;
+ ERR_FAIL_COND(!_ensure_cache_for_size(fd, size, ffsd));
+ ffsd->underline_thickness = p_underline_thickness;
}
double TextServerFallback::_font_get_underline_thickness(const RID &p_font_rid, int64_t p_size) const {
@@ -1716,18 +1745,19 @@ double TextServerFallback::_font_get_underline_thickness(const RID &p_font_rid,
MutexLock lock(fd->mutex);
Vector2i size = _get_size(fd, p_size);
- ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size), 0.0);
+ FontForSizeFallback *ffsd = nullptr;
+ ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size, ffsd), 0.0);
if (fd->msdf) {
- return fd->cache[size]->underline_thickness * (double)p_size / (double)fd->msdf_source_size;
+ return ffsd->underline_thickness * (double)p_size / (double)fd->msdf_source_size;
} else if (fd->fixed_size > 0 && fd->fixed_size_scale_mode != FIXED_SIZE_SCALE_DISABLE && size.x != p_size) {
if (fd->fixed_size_scale_mode == FIXED_SIZE_SCALE_ENABLED) {
- return fd->cache[size]->underline_thickness * (double)p_size / (double)fd->fixed_size;
+ return ffsd->underline_thickness * (double)p_size / (double)fd->fixed_size;
} else {
- return fd->cache[size]->underline_thickness * Math::round((double)p_size / (double)fd->fixed_size);
+ return ffsd->underline_thickness * Math::round((double)p_size / (double)fd->fixed_size);
}
} else {
- return fd->cache[size]->underline_thickness;
+ return ffsd->underline_thickness;
}
}
@@ -1738,13 +1768,14 @@ void TextServerFallback::_font_set_scale(const RID &p_font_rid, int64_t p_size,
MutexLock lock(fd->mutex);
Vector2i size = _get_size(fd, p_size);
- ERR_FAIL_COND(!_ensure_cache_for_size(fd, size));
+ FontForSizeFallback *ffsd = nullptr;
+ ERR_FAIL_COND(!_ensure_cache_for_size(fd, size, ffsd));
#ifdef MODULE_FREETYPE_ENABLED
- if (fd->cache[size]->face) {
+ if (ffsd->face) {
return; // Do not override scale for dynamic fonts, it's calculated automatically.
}
#endif
- fd->cache[size]->scale = p_scale;
+ ffsd->scale = p_scale;
}
double TextServerFallback::_font_get_scale(const RID &p_font_rid, int64_t p_size) const {
@@ -1754,18 +1785,19 @@ double TextServerFallback::_font_get_scale(const RID &p_font_rid, int64_t p_size
MutexLock lock(fd->mutex);
Vector2i size = _get_size(fd, p_size);
- ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size), 0.0);
+ FontForSizeFallback *ffsd = nullptr;
+ ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size, ffsd), 0.0);
if (fd->msdf) {
- return fd->cache[size]->scale * (double)p_size / (double)fd->msdf_source_size;
+ return ffsd->scale * (double)p_size / (double)fd->msdf_source_size;
} else if (fd->fixed_size > 0 && fd->fixed_size_scale_mode != FIXED_SIZE_SCALE_DISABLE && size.x != p_size) {
if (fd->fixed_size_scale_mode == FIXED_SIZE_SCALE_ENABLED) {
- return fd->cache[size]->scale * (double)p_size / (double)fd->fixed_size;
+ return ffsd->scale * (double)p_size / (double)fd->fixed_size;
} else {
- return fd->cache[size]->scale * Math::round((double)p_size / (double)fd->fixed_size);
+ return ffsd->scale * Math::round((double)p_size / (double)fd->fixed_size);
}
} else {
- return fd->cache[size]->scale / fd->cache[size]->oversampling;
+ return ffsd->scale / ffsd->oversampling;
}
}
@@ -1776,9 +1808,10 @@ int64_t TextServerFallback::_font_get_texture_count(const RID &p_font_rid, const
MutexLock lock(fd->mutex);
Vector2i size = _get_size_outline(fd, p_size);
- ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size), 0);
+ FontForSizeFallback *ffsd = nullptr;
+ ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size, ffsd), 0);
- return fd->cache[size]->textures.size();
+ return ffsd->textures.size();
}
void TextServerFallback::_font_clear_textures(const RID &p_font_rid, const Vector2i &p_size) {
@@ -1787,8 +1820,9 @@ void TextServerFallback::_font_clear_textures(const RID &p_font_rid, const Vecto
MutexLock lock(fd->mutex);
Vector2i size = _get_size_outline(fd, p_size);
- ERR_FAIL_COND(!_ensure_cache_for_size(fd, size));
- fd->cache[size]->textures.clear();
+ FontForSizeFallback *ffsd = nullptr;
+ ERR_FAIL_COND(!_ensure_cache_for_size(fd, size, ffsd));
+ ffsd->textures.clear();
}
void TextServerFallback::_font_remove_texture(const RID &p_font_rid, const Vector2i &p_size, int64_t p_texture_index) {
@@ -1797,10 +1831,11 @@ void TextServerFallback::_font_remove_texture(const RID &p_font_rid, const Vecto
MutexLock lock(fd->mutex);
Vector2i size = _get_size_outline(fd, p_size);
- ERR_FAIL_COND(!_ensure_cache_for_size(fd, size));
- ERR_FAIL_INDEX(p_texture_index, fd->cache[size]->textures.size());
+ FontForSizeFallback *ffsd = nullptr;
+ ERR_FAIL_COND(!_ensure_cache_for_size(fd, size, ffsd));
+ ERR_FAIL_INDEX(p_texture_index, ffsd->textures.size());
- fd->cache[size]->textures.remove_at(p_texture_index);
+ ffsd->textures.remove_at(p_texture_index);
}
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) {
@@ -1810,13 +1845,14 @@ void TextServerFallback::_font_set_texture_image(const RID &p_font_rid, const Ve
MutexLock lock(fd->mutex);
Vector2i size = _get_size_outline(fd, p_size);
- ERR_FAIL_COND(!_ensure_cache_for_size(fd, size));
+ FontForSizeFallback *ffsd = nullptr;
+ ERR_FAIL_COND(!_ensure_cache_for_size(fd, size, ffsd));
ERR_FAIL_COND(p_texture_index < 0);
- if (p_texture_index >= fd->cache[size]->textures.size()) {
- fd->cache[size]->textures.resize(p_texture_index + 1);
+ if (p_texture_index >= ffsd->textures.size()) {
+ ffsd->textures.resize(p_texture_index + 1);
}
- ShelfPackTexture &tex = fd->cache[size]->textures.write[p_texture_index];
+ ShelfPackTexture &tex = ffsd->textures.write[p_texture_index];
tex.image = p_image;
tex.texture_w = p_image->get_width();
@@ -1837,10 +1873,11 @@ Ref<Image> TextServerFallback::_font_get_texture_image(const RID &p_font_rid, co
MutexLock lock(fd->mutex);
Vector2i size = _get_size_outline(fd, p_size);
- ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size), Ref<Image>());
- ERR_FAIL_INDEX_V(p_texture_index, fd->cache[size]->textures.size(), Ref<Image>());
+ FontForSizeFallback *ffsd = nullptr;
+ ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size, ffsd), Ref<Image>());
+ ERR_FAIL_INDEX_V(p_texture_index, ffsd->textures.size(), Ref<Image>());
- const ShelfPackTexture &tex = fd->cache[size]->textures[p_texture_index];
+ const ShelfPackTexture &tex = ffsd->textures[p_texture_index];
return tex.image;
}
@@ -1851,13 +1888,14 @@ void TextServerFallback::_font_set_texture_offsets(const RID &p_font_rid, const
MutexLock lock(fd->mutex);
Vector2i size = _get_size_outline(fd, p_size);
- ERR_FAIL_COND(!_ensure_cache_for_size(fd, size));
+ FontForSizeFallback *ffsd = nullptr;
+ ERR_FAIL_COND(!_ensure_cache_for_size(fd, size, ffsd));
ERR_FAIL_COND(p_texture_index < 0);
- if (p_texture_index >= fd->cache[size]->textures.size()) {
- fd->cache[size]->textures.resize(p_texture_index + 1);
+ if (p_texture_index >= ffsd->textures.size()) {
+ ffsd->textures.resize(p_texture_index + 1);
}
- ShelfPackTexture &tex = fd->cache[size]->textures.write[p_texture_index];
+ ShelfPackTexture &tex = ffsd->textures.write[p_texture_index];
tex.shelves.clear();
for (int32_t i = 0; i < p_offsets.size(); i += 4) {
tex.shelves.push_back(Shelf(p_offsets[i], p_offsets[i + 1], p_offsets[i + 2], p_offsets[i + 3]));
@@ -1870,10 +1908,11 @@ PackedInt32Array TextServerFallback::_font_get_texture_offsets(const RID &p_font
MutexLock lock(fd->mutex);
Vector2i size = _get_size_outline(fd, p_size);
- ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size), PackedInt32Array());
- ERR_FAIL_INDEX_V(p_texture_index, fd->cache[size]->textures.size(), PackedInt32Array());
+ FontForSizeFallback *ffsd = nullptr;
+ ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size, ffsd), PackedInt32Array());
+ ERR_FAIL_INDEX_V(p_texture_index, ffsd->textures.size(), PackedInt32Array());
- const ShelfPackTexture &tex = fd->cache[size]->textures[p_texture_index];
+ const ShelfPackTexture &tex = ffsd->textures[p_texture_index];
PackedInt32Array ret;
ret.resize(tex.shelves.size() * 4);
@@ -1895,10 +1934,11 @@ PackedInt32Array TextServerFallback::_font_get_glyph_list(const RID &p_font_rid,
MutexLock lock(fd->mutex);
Vector2i size = _get_size_outline(fd, p_size);
- ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size), PackedInt32Array());
+ FontForSizeFallback *ffsd = nullptr;
+ ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size, ffsd), PackedInt32Array());
PackedInt32Array ret;
- const HashMap<int32_t, FontGlyph> &gl = fd->cache[size]->glyph_map;
+ const HashMap<int32_t, FontGlyph> &gl = ffsd->glyph_map;
for (const KeyValue<int32_t, FontGlyph> &E : gl) {
ret.push_back(E.key);
}
@@ -1911,9 +1951,10 @@ void TextServerFallback::_font_clear_glyphs(const RID &p_font_rid, const Vector2
MutexLock lock(fd->mutex);
Vector2i size = _get_size_outline(fd, p_size);
- ERR_FAIL_COND(!_ensure_cache_for_size(fd, size));
+ FontForSizeFallback *ffsd = nullptr;
+ ERR_FAIL_COND(!_ensure_cache_for_size(fd, size, ffsd));
- fd->cache[size]->glyph_map.clear();
+ ffsd->glyph_map.clear();
}
void TextServerFallback::_font_remove_glyph(const RID &p_font_rid, const Vector2i &p_size, int64_t p_glyph) {
@@ -1922,9 +1963,10 @@ void TextServerFallback::_font_remove_glyph(const RID &p_font_rid, const Vector2
MutexLock lock(fd->mutex);
Vector2i size = _get_size_outline(fd, p_size);
- ERR_FAIL_COND(!_ensure_cache_for_size(fd, size));
+ FontForSizeFallback *ffsd = nullptr;
+ ERR_FAIL_COND(!_ensure_cache_for_size(fd, size, ffsd));
- fd->cache[size]->glyph_map.erase(p_glyph);
+ ffsd->glyph_map.erase(p_glyph);
}
Vector2 TextServerFallback::_font_get_glyph_advance(const RID &p_font_rid, int64_t p_size, int64_t p_glyph) const {
@@ -1934,22 +1976,22 @@ Vector2 TextServerFallback::_font_get_glyph_advance(const RID &p_font_rid, int64
MutexLock lock(fd->mutex);
Vector2i size = _get_size(fd, p_size);
- ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size), Vector2());
+ FontForSizeFallback *ffsd = nullptr;
+ ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size, ffsd), Vector2());
int mod = 0;
if (fd->antialiasing == FONT_ANTIALIASING_LCD) {
- TextServer::FontLCDSubpixelLayout layout = (TextServer::FontLCDSubpixelLayout)(int)GLOBAL_GET("gui/theme/lcd_subpixel_layout");
+ TextServer::FontLCDSubpixelLayout layout = lcd_subpixel_layout.get();
if (layout != FONT_LCD_SUBPIXEL_LAYOUT_NONE) {
mod = (layout << 24);
}
}
- if (!_ensure_glyph(fd, size, p_glyph | mod)) {
+ FontGlyph fgl;
+ if (!_ensure_glyph(fd, size, p_glyph | mod, fgl)) {
return Vector2(); // Invalid or non graphicl glyph, do not display errors.
}
- const HashMap<int32_t, FontGlyph> &gl = fd->cache[size]->glyph_map;
-
Vector2 ea;
if (fd->embolden != 0.0) {
ea.x = fd->embolden * double(size.x) / 64.0;
@@ -1957,17 +1999,17 @@ Vector2 TextServerFallback::_font_get_glyph_advance(const RID &p_font_rid, int64
double scale = _font_get_scale(p_font_rid, p_size);
if (fd->msdf) {
- return (gl[p_glyph | mod].advance + ea) * (double)p_size / (double)fd->msdf_source_size;
+ return (fgl.advance + ea) * (double)p_size / (double)fd->msdf_source_size;
} else if (fd->fixed_size > 0 && fd->fixed_size_scale_mode != FIXED_SIZE_SCALE_DISABLE && size.x != p_size) {
if (fd->fixed_size_scale_mode == FIXED_SIZE_SCALE_ENABLED) {
- return (gl[p_glyph | mod].advance + ea) * (double)p_size / (double)fd->fixed_size;
+ return (fgl.advance + ea) * (double)p_size / (double)fd->fixed_size;
} else {
- return (gl[p_glyph | mod].advance + ea) * Math::round((double)p_size / (double)fd->fixed_size);
+ return (fgl.advance + ea) * Math::round((double)p_size / (double)fd->fixed_size);
}
} else if ((scale == 1.0) && ((fd->subpixel_positioning == SUBPIXEL_POSITIONING_DISABLED) || (fd->subpixel_positioning == SUBPIXEL_POSITIONING_AUTO && size.x > SUBPIXEL_POSITIONING_ONE_HALF_MAX_SIZE))) {
- return (gl[p_glyph | mod].advance + ea).round();
+ return (fgl.advance + ea).round();
} else {
- return gl[p_glyph | mod].advance + ea;
+ return fgl.advance + ea;
}
}
@@ -1978,12 +2020,13 @@ void TextServerFallback::_font_set_glyph_advance(const RID &p_font_rid, int64_t
MutexLock lock(fd->mutex);
Vector2i size = _get_size(fd, p_size);
- ERR_FAIL_COND(!_ensure_cache_for_size(fd, size));
+ FontForSizeFallback *ffsd = nullptr;
+ ERR_FAIL_COND(!_ensure_cache_for_size(fd, size, ffsd));
- HashMap<int32_t, FontGlyph> &gl = fd->cache[size]->glyph_map;
+ FontGlyph &fgl = ffsd->glyph_map[p_glyph];
- gl[p_glyph].advance = p_advance;
- gl[p_glyph].found = true;
+ fgl.advance = p_advance;
+ fgl.found = true;
}
Vector2 TextServerFallback::_font_get_glyph_offset(const RID &p_font_rid, const Vector2i &p_size, int64_t p_glyph) const {
@@ -1993,32 +2036,32 @@ Vector2 TextServerFallback::_font_get_glyph_offset(const RID &p_font_rid, const
MutexLock lock(fd->mutex);
Vector2i size = _get_size_outline(fd, p_size);
- ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size), Vector2());
+ FontForSizeFallback *ffsd = nullptr;
+ ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size, ffsd), Vector2());
int mod = 0;
if (fd->antialiasing == FONT_ANTIALIASING_LCD) {
- TextServer::FontLCDSubpixelLayout layout = (TextServer::FontLCDSubpixelLayout)(int)GLOBAL_GET("gui/theme/lcd_subpixel_layout");
+ TextServer::FontLCDSubpixelLayout layout = lcd_subpixel_layout.get();
if (layout != FONT_LCD_SUBPIXEL_LAYOUT_NONE) {
mod = (layout << 24);
}
}
- if (!_ensure_glyph(fd, size, p_glyph | mod)) {
+ FontGlyph fgl;
+ if (!_ensure_glyph(fd, size, p_glyph | mod, fgl)) {
return Vector2(); // Invalid or non graphicl glyph, do not display errors.
}
- const HashMap<int32_t, FontGlyph> &gl = fd->cache[size]->glyph_map;
-
if (fd->msdf) {
- return gl[p_glyph | mod].rect.position * (double)p_size.x / (double)fd->msdf_source_size;
+ return fgl.rect.position * (double)p_size.x / (double)fd->msdf_source_size;
} else if (fd->fixed_size > 0 && fd->fixed_size_scale_mode != FIXED_SIZE_SCALE_DISABLE && size.x != p_size.x) {
if (fd->fixed_size_scale_mode == FIXED_SIZE_SCALE_ENABLED) {
- return gl[p_glyph | mod].rect.position * (double)p_size.x / (double)fd->fixed_size;
+ return fgl.rect.position * (double)p_size.x / (double)fd->fixed_size;
} else {
- return gl[p_glyph | mod].rect.position * Math::round((double)p_size.x / (double)fd->fixed_size);
+ return fgl.rect.position * Math::round((double)p_size.x / (double)fd->fixed_size);
}
} else {
- return gl[p_glyph | mod].rect.position;
+ return fgl.rect.position;
}
}
@@ -2029,12 +2072,13 @@ void TextServerFallback::_font_set_glyph_offset(const RID &p_font_rid, const Vec
MutexLock lock(fd->mutex);
Vector2i size = _get_size_outline(fd, p_size);
- ERR_FAIL_COND(!_ensure_cache_for_size(fd, size));
+ FontForSizeFallback *ffsd = nullptr;
+ ERR_FAIL_COND(!_ensure_cache_for_size(fd, size, ffsd));
- HashMap<int32_t, FontGlyph> &gl = fd->cache[size]->glyph_map;
+ FontGlyph &fgl = ffsd->glyph_map[p_glyph];
- gl[p_glyph].rect.position = p_offset;
- gl[p_glyph].found = true;
+ fgl.rect.position = p_offset;
+ fgl.found = true;
}
Vector2 TextServerFallback::_font_get_glyph_size(const RID &p_font_rid, const Vector2i &p_size, int64_t p_glyph) const {
@@ -2044,32 +2088,32 @@ Vector2 TextServerFallback::_font_get_glyph_size(const RID &p_font_rid, const Ve
MutexLock lock(fd->mutex);
Vector2i size = _get_size_outline(fd, p_size);
- ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size), Vector2());
+ FontForSizeFallback *ffsd = nullptr;
+ ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size, ffsd), Vector2());
int mod = 0;
if (fd->antialiasing == FONT_ANTIALIASING_LCD) {
- TextServer::FontLCDSubpixelLayout layout = (TextServer::FontLCDSubpixelLayout)(int)GLOBAL_GET("gui/theme/lcd_subpixel_layout");
+ TextServer::FontLCDSubpixelLayout layout = lcd_subpixel_layout.get();
if (layout != FONT_LCD_SUBPIXEL_LAYOUT_NONE) {
mod = (layout << 24);
}
}
- if (!_ensure_glyph(fd, size, p_glyph | mod)) {
+ FontGlyph fgl;
+ if (!_ensure_glyph(fd, size, p_glyph | mod, fgl)) {
return Vector2(); // Invalid or non graphicl glyph, do not display errors.
}
- const HashMap<int32_t, FontGlyph> &gl = fd->cache[size]->glyph_map;
-
if (fd->msdf) {
- return gl[p_glyph | mod].rect.size * (double)p_size.x / (double)fd->msdf_source_size;
+ return fgl.rect.size * (double)p_size.x / (double)fd->msdf_source_size;
} else if (fd->fixed_size > 0 && fd->fixed_size_scale_mode != FIXED_SIZE_SCALE_DISABLE && size.x != p_size.x) {
if (fd->fixed_size_scale_mode == FIXED_SIZE_SCALE_ENABLED) {
- return gl[p_glyph | mod].rect.size * (double)p_size.x / (double)fd->fixed_size;
+ return fgl.rect.size * (double)p_size.x / (double)fd->fixed_size;
} else {
- return gl[p_glyph | mod].rect.size * Math::round((double)p_size.x / (double)fd->fixed_size);
+ return fgl.rect.size * Math::round((double)p_size.x / (double)fd->fixed_size);
}
} else {
- return gl[p_glyph | mod].rect.size;
+ return fgl.rect.size;
}
}
@@ -2080,12 +2124,13 @@ void TextServerFallback::_font_set_glyph_size(const RID &p_font_rid, const Vecto
MutexLock lock(fd->mutex);
Vector2i size = _get_size_outline(fd, p_size);
- ERR_FAIL_COND(!_ensure_cache_for_size(fd, size));
+ FontForSizeFallback *ffsd = nullptr;
+ ERR_FAIL_COND(!_ensure_cache_for_size(fd, size, ffsd));
- HashMap<int32_t, FontGlyph> &gl = fd->cache[size]->glyph_map;
+ FontGlyph &fgl = ffsd->glyph_map[p_glyph];
- gl[p_glyph].rect.size = p_gl_size;
- gl[p_glyph].found = true;
+ fgl.rect.size = p_gl_size;
+ fgl.found = true;
}
Rect2 TextServerFallback::_font_get_glyph_uv_rect(const RID &p_font_rid, const Vector2i &p_size, int64_t p_glyph) const {
@@ -2095,22 +2140,23 @@ Rect2 TextServerFallback::_font_get_glyph_uv_rect(const RID &p_font_rid, const V
MutexLock lock(fd->mutex);
Vector2i size = _get_size_outline(fd, p_size);
- ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size), Rect2());
+ FontForSizeFallback *ffsd = nullptr;
+ ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size, ffsd), Rect2());
int mod = 0;
if (fd->antialiasing == FONT_ANTIALIASING_LCD) {
- TextServer::FontLCDSubpixelLayout layout = (TextServer::FontLCDSubpixelLayout)(int)GLOBAL_GET("gui/theme/lcd_subpixel_layout");
+ TextServer::FontLCDSubpixelLayout layout = lcd_subpixel_layout.get();
if (layout != FONT_LCD_SUBPIXEL_LAYOUT_NONE) {
mod = (layout << 24);
}
}
- if (!_ensure_glyph(fd, size, p_glyph | mod)) {
+ FontGlyph fgl;
+ if (!_ensure_glyph(fd, size, p_glyph | mod, fgl)) {
return Rect2(); // Invalid or non graphicl glyph, do not display errors.
}
- const HashMap<int32_t, FontGlyph> &gl = fd->cache[size]->glyph_map;
- return gl[p_glyph | mod].uv_rect;
+ return fgl.uv_rect;
}
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) {
@@ -2120,12 +2166,13 @@ void TextServerFallback::_font_set_glyph_uv_rect(const RID &p_font_rid, const Ve
MutexLock lock(fd->mutex);
Vector2i size = _get_size_outline(fd, p_size);
- ERR_FAIL_COND(!_ensure_cache_for_size(fd, size));
+ FontForSizeFallback *ffsd = nullptr;
+ ERR_FAIL_COND(!_ensure_cache_for_size(fd, size, ffsd));
- HashMap<int32_t, FontGlyph> &gl = fd->cache[size]->glyph_map;
+ FontGlyph &fgl = ffsd->glyph_map[p_glyph];
- gl[p_glyph].uv_rect = p_uv_rect;
- gl[p_glyph].found = true;
+ fgl.uv_rect = p_uv_rect;
+ fgl.found = true;
}
int64_t TextServerFallback::_font_get_glyph_texture_idx(const RID &p_font_rid, const Vector2i &p_size, int64_t p_glyph) const {
@@ -2135,22 +2182,23 @@ int64_t TextServerFallback::_font_get_glyph_texture_idx(const RID &p_font_rid, c
MutexLock lock(fd->mutex);
Vector2i size = _get_size_outline(fd, p_size);
- ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size), -1);
+ FontForSizeFallback *ffsd = nullptr;
+ ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size, ffsd), -1);
int mod = 0;
if (fd->antialiasing == FONT_ANTIALIASING_LCD) {
- TextServer::FontLCDSubpixelLayout layout = (TextServer::FontLCDSubpixelLayout)(int)GLOBAL_GET("gui/theme/lcd_subpixel_layout");
+ TextServer::FontLCDSubpixelLayout layout = lcd_subpixel_layout.get();
if (layout != FONT_LCD_SUBPIXEL_LAYOUT_NONE) {
mod = (layout << 24);
}
}
- if (!_ensure_glyph(fd, size, p_glyph | mod)) {
+ FontGlyph fgl;
+ if (!_ensure_glyph(fd, size, p_glyph | mod, fgl)) {
return -1; // Invalid or non graphicl glyph, do not display errors.
}
- const HashMap<int32_t, FontGlyph> &gl = fd->cache[size]->glyph_map;
- return gl[p_glyph | mod].texture_idx;
+ return fgl.texture_idx;
}
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) {
@@ -2160,12 +2208,13 @@ void TextServerFallback::_font_set_glyph_texture_idx(const RID &p_font_rid, cons
MutexLock lock(fd->mutex);
Vector2i size = _get_size_outline(fd, p_size);
- ERR_FAIL_COND(!_ensure_cache_for_size(fd, size));
+ FontForSizeFallback *ffsd = nullptr;
+ ERR_FAIL_COND(!_ensure_cache_for_size(fd, size, ffsd));
- HashMap<int32_t, FontGlyph> &gl = fd->cache[size]->glyph_map;
+ FontGlyph &fgl = ffsd->glyph_map[p_glyph];
- gl[p_glyph].texture_idx = p_texture_idx;
- gl[p_glyph].found = true;
+ fgl.texture_idx = p_texture_idx;
+ fgl.found = true;
}
RID TextServerFallback::_font_get_glyph_texture_rid(const RID &p_font_rid, const Vector2i &p_size, int64_t p_glyph) const {
@@ -2175,27 +2224,28 @@ RID TextServerFallback::_font_get_glyph_texture_rid(const RID &p_font_rid, const
MutexLock lock(fd->mutex);
Vector2i size = _get_size_outline(fd, p_size);
- ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size), RID());
+ FontForSizeFallback *ffsd = nullptr;
+ ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size, ffsd), RID());
int mod = 0;
if (fd->antialiasing == FONT_ANTIALIASING_LCD) {
- TextServer::FontLCDSubpixelLayout layout = (TextServer::FontLCDSubpixelLayout)(int)GLOBAL_GET("gui/theme/lcd_subpixel_layout");
+ TextServer::FontLCDSubpixelLayout layout = lcd_subpixel_layout.get();
if (layout != FONT_LCD_SUBPIXEL_LAYOUT_NONE) {
mod = (layout << 24);
}
}
- if (!_ensure_glyph(fd, size, p_glyph | mod)) {
+ FontGlyph fgl;
+ if (!_ensure_glyph(fd, size, p_glyph | mod, fgl)) {
return RID(); // Invalid or non graphicl glyph, do not display errors.
}
- const HashMap<int32_t, FontGlyph> &gl = fd->cache[size]->glyph_map;
- ERR_FAIL_COND_V(gl[p_glyph | mod].texture_idx < -1 || gl[p_glyph | mod].texture_idx >= fd->cache[size]->textures.size(), RID());
+ ERR_FAIL_COND_V(fgl.texture_idx < -1 || fgl.texture_idx >= ffsd->textures.size(), RID());
if (RenderingServer::get_singleton() != nullptr) {
- if (gl[p_glyph | mod].texture_idx != -1) {
- if (fd->cache[size]->textures[gl[p_glyph | mod].texture_idx].dirty) {
- ShelfPackTexture &tex = fd->cache[size]->textures.write[gl[p_glyph | mod].texture_idx];
+ if (fgl.texture_idx != -1) {
+ if (ffsd->textures[fgl.texture_idx].dirty) {
+ ShelfPackTexture &tex = ffsd->textures.write[fgl.texture_idx];
Ref<Image> img = tex.image;
if (fd->mipmaps && !img->has_mipmaps()) {
img = tex.image->duplicate();
@@ -2208,7 +2258,7 @@ RID TextServerFallback::_font_get_glyph_texture_rid(const RID &p_font_rid, const
}
tex.dirty = false;
}
- return fd->cache[size]->textures[gl[p_glyph | mod].texture_idx].texture->get_rid();
+ return ffsd->textures[fgl.texture_idx].texture->get_rid();
}
}
@@ -2222,27 +2272,28 @@ Size2 TextServerFallback::_font_get_glyph_texture_size(const RID &p_font_rid, co
MutexLock lock(fd->mutex);
Vector2i size = _get_size_outline(fd, p_size);
- ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size), Size2());
+ FontForSizeFallback *ffsd = nullptr;
+ ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size, ffsd), Size2());
int mod = 0;
if (fd->antialiasing == FONT_ANTIALIASING_LCD) {
- TextServer::FontLCDSubpixelLayout layout = (TextServer::FontLCDSubpixelLayout)(int)GLOBAL_GET("gui/theme/lcd_subpixel_layout");
+ TextServer::FontLCDSubpixelLayout layout = lcd_subpixel_layout.get();
if (layout != FONT_LCD_SUBPIXEL_LAYOUT_NONE) {
mod = (layout << 24);
}
}
- if (!_ensure_glyph(fd, size, p_glyph | mod)) {
+ FontGlyph fgl;
+ if (!_ensure_glyph(fd, size, p_glyph | mod, fgl)) {
return Size2(); // Invalid or non graphicl glyph, do not display errors.
}
- const HashMap<int32_t, FontGlyph> &gl = fd->cache[size]->glyph_map;
- ERR_FAIL_COND_V(gl[p_glyph | mod].texture_idx < -1 || gl[p_glyph | mod].texture_idx >= fd->cache[size]->textures.size(), Size2());
+ ERR_FAIL_COND_V(fgl.texture_idx < -1 || fgl.texture_idx >= ffsd->textures.size(), Size2());
if (RenderingServer::get_singleton() != nullptr) {
- if (gl[p_glyph | mod].texture_idx != -1) {
- if (fd->cache[size]->textures[gl[p_glyph | mod].texture_idx].dirty) {
- ShelfPackTexture &tex = fd->cache[size]->textures.write[gl[p_glyph | mod].texture_idx];
+ if (fgl.texture_idx != -1) {
+ if (ffsd->textures[fgl.texture_idx].dirty) {
+ ShelfPackTexture &tex = ffsd->textures.write[fgl.texture_idx];
Ref<Image> img = tex.image;
if (fd->mipmaps && !img->has_mipmaps()) {
img = tex.image->duplicate();
@@ -2255,7 +2306,7 @@ Size2 TextServerFallback::_font_get_glyph_texture_size(const RID &p_font_rid, co
}
tex.dirty = false;
}
- return fd->cache[size]->textures[gl[p_glyph | mod].texture_idx].texture->get_size();
+ return ffsd->textures[fgl.texture_idx].texture->get_size();
}
}
@@ -2269,7 +2320,8 @@ Dictionary TextServerFallback::_font_get_glyph_contours(const RID &p_font_rid, i
MutexLock lock(fd->mutex);
Vector2i size = _get_size(fd, p_size);
- ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size), Dictionary());
+ FontForSizeFallback *ffsd = nullptr;
+ ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size, ffsd), Dictionary());
#ifdef MODULE_FREETYPE_ENABLED
PackedVector3Array points;
@@ -2277,20 +2329,20 @@ Dictionary TextServerFallback::_font_get_glyph_contours(const RID &p_font_rid, i
int32_t index = p_index & 0xffffff; // Remove subpixel shifts.
- int error = FT_Load_Glyph(fd->cache[size]->face, FT_Get_Char_Index(fd->cache[size]->face, index), FT_LOAD_NO_BITMAP | (fd->force_autohinter ? FT_LOAD_FORCE_AUTOHINT : 0));
+ int error = FT_Load_Glyph(ffsd->face, FT_Get_Char_Index(ffsd->face, index), FT_LOAD_NO_BITMAP | (fd->force_autohinter ? FT_LOAD_FORCE_AUTOHINT : 0));
ERR_FAIL_COND_V(error, Dictionary());
if (fd->embolden != 0.f) {
FT_Pos strength = fd->embolden * p_size * 4; // 26.6 fractional units (1 / 64).
- FT_Outline_Embolden(&fd->cache[size]->face->glyph->outline, strength);
+ FT_Outline_Embolden(&ffsd->face->glyph->outline, strength);
}
if (fd->transform != Transform2D()) {
FT_Matrix mat = { FT_Fixed(fd->transform[0][0] * 65536), FT_Fixed(fd->transform[0][1] * 65536), FT_Fixed(fd->transform[1][0] * 65536), FT_Fixed(fd->transform[1][1] * 65536) }; // 16.16 fractional units (1 / 65536).
- FT_Outline_Transform(&fd->cache[size]->face->glyph->outline, &mat);
+ FT_Outline_Transform(&ffsd->face->glyph->outline, &mat);
}
- double scale = (1.0 / 64.0) / fd->cache[size]->oversampling * fd->cache[size]->scale;
+ double scale = (1.0 / 64.0) / ffsd->oversampling * ffsd->scale;
if (fd->msdf) {
scale = scale * (double)p_size / (double)fd->msdf_source_size;
} else if (fd->fixed_size > 0 && fd->fixed_size_scale_mode != FIXED_SIZE_SCALE_DISABLE && size.x != p_size) {
@@ -2300,13 +2352,13 @@ Dictionary TextServerFallback::_font_get_glyph_contours(const RID &p_font_rid, i
scale = scale * Math::round((double)p_size / (double)fd->fixed_size);
}
}
- for (short i = 0; i < fd->cache[size]->face->glyph->outline.n_points; i++) {
- points.push_back(Vector3(fd->cache[size]->face->glyph->outline.points[i].x * scale, -fd->cache[size]->face->glyph->outline.points[i].y * scale, FT_CURVE_TAG(fd->cache[size]->face->glyph->outline.tags[i])));
+ for (short i = 0; i < ffsd->face->glyph->outline.n_points; i++) {
+ points.push_back(Vector3(ffsd->face->glyph->outline.points[i].x * scale, -ffsd->face->glyph->outline.points[i].y * scale, FT_CURVE_TAG(ffsd->face->glyph->outline.tags[i])));
}
- for (short i = 0; i < fd->cache[size]->face->glyph->outline.n_contours; i++) {
- contours.push_back(fd->cache[size]->face->glyph->outline.contours[i]);
+ for (short i = 0; i < ffsd->face->glyph->outline.n_contours; i++) {
+ contours.push_back(ffsd->face->glyph->outline.contours[i]);
}
- bool orientation = (FT_Outline_Get_Orientation(&fd->cache[size]->face->glyph->outline) == FT_ORIENTATION_FILL_RIGHT);
+ bool orientation = (FT_Outline_Get_Orientation(&ffsd->face->glyph->outline) == FT_ORIENTATION_FILL_RIGHT);
Dictionary out;
out["points"] = points;
@@ -2325,10 +2377,11 @@ TypedArray<Vector2i> TextServerFallback::_font_get_kerning_list(const RID &p_fon
MutexLock lock(fd->mutex);
Vector2i size = _get_size(fd, p_size);
- ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size), TypedArray<Vector2i>());
+ FontForSizeFallback *ffsd = nullptr;
+ ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size, ffsd), TypedArray<Vector2i>());
TypedArray<Vector2i> ret;
- for (const KeyValue<Vector2i, Vector2> &E : fd->cache[size]->kerning_map) {
+ for (const KeyValue<Vector2i, Vector2> &E : ffsd->kerning_map) {
ret.push_back(E.key);
}
return ret;
@@ -2341,8 +2394,9 @@ void TextServerFallback::_font_clear_kerning_map(const RID &p_font_rid, int64_t
MutexLock lock(fd->mutex);
Vector2i size = _get_size(fd, p_size);
- ERR_FAIL_COND(!_ensure_cache_for_size(fd, size));
- fd->cache[size]->kerning_map.clear();
+ FontForSizeFallback *ffsd = nullptr;
+ ERR_FAIL_COND(!_ensure_cache_for_size(fd, size, ffsd));
+ ffsd->kerning_map.clear();
}
void TextServerFallback::_font_remove_kerning(const RID &p_font_rid, int64_t p_size, const Vector2i &p_glyph_pair) {
@@ -2352,8 +2406,9 @@ void TextServerFallback::_font_remove_kerning(const RID &p_font_rid, int64_t p_s
MutexLock lock(fd->mutex);
Vector2i size = _get_size(fd, p_size);
- ERR_FAIL_COND(!_ensure_cache_for_size(fd, size));
- fd->cache[size]->kerning_map.erase(p_glyph_pair);
+ FontForSizeFallback *ffsd = nullptr;
+ ERR_FAIL_COND(!_ensure_cache_for_size(fd, size, ffsd));
+ ffsd->kerning_map.erase(p_glyph_pair);
}
void TextServerFallback::_font_set_kerning(const RID &p_font_rid, int64_t p_size, const Vector2i &p_glyph_pair, const Vector2 &p_kerning) {
@@ -2363,8 +2418,9 @@ void TextServerFallback::_font_set_kerning(const RID &p_font_rid, int64_t p_size
MutexLock lock(fd->mutex);
Vector2i size = _get_size(fd, p_size);
- ERR_FAIL_COND(!_ensure_cache_for_size(fd, size));
- fd->cache[size]->kerning_map[p_glyph_pair] = p_kerning;
+ FontForSizeFallback *ffsd = nullptr;
+ ERR_FAIL_COND(!_ensure_cache_for_size(fd, size, ffsd));
+ ffsd->kerning_map[p_glyph_pair] = p_kerning;
}
Vector2 TextServerFallback::_font_get_kerning(const RID &p_font_rid, int64_t p_size, const Vector2i &p_glyph_pair) const {
@@ -2374,9 +2430,10 @@ Vector2 TextServerFallback::_font_get_kerning(const RID &p_font_rid, int64_t p_s
MutexLock lock(fd->mutex);
Vector2i size = _get_size(fd, p_size);
- ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size), Vector2());
+ FontForSizeFallback *ffsd = nullptr;
+ ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size, ffsd), Vector2());
- const HashMap<Vector2i, Vector2> &kern = fd->cache[size]->kerning_map;
+ const HashMap<Vector2i, Vector2> &kern = ffsd->kerning_map;
if (kern.has(p_glyph_pair)) {
if (fd->msdf) {
@@ -2392,11 +2449,11 @@ Vector2 TextServerFallback::_font_get_kerning(const RID &p_font_rid, int64_t p_s
}
} else {
#ifdef MODULE_FREETYPE_ENABLED
- if (fd->cache[size]->face) {
+ if (ffsd->face) {
FT_Vector delta;
- int32_t glyph_a = FT_Get_Char_Index(fd->cache[size]->face, p_glyph_pair.x);
- int32_t glyph_b = FT_Get_Char_Index(fd->cache[size]->face, p_glyph_pair.y);
- FT_Get_Kerning(fd->cache[size]->face, glyph_a, glyph_b, FT_KERNING_DEFAULT, &delta);
+ int32_t glyph_a = FT_Get_Char_Index(ffsd->face, p_glyph_pair.x);
+ int32_t glyph_b = FT_Get_Char_Index(ffsd->face, p_glyph_pair.y);
+ FT_Get_Kerning(ffsd->face, glyph_a, glyph_b, FT_KERNING_DEFAULT, &delta);
if (fd->msdf) {
return Vector2(delta.x, delta.y) * (double)p_size / (double)fd->msdf_source_size;
} else if (fd->fixed_size > 0 && fd->fixed_size_scale_mode != FIXED_SIZE_SCALE_DISABLE && size.x != p_size) {
@@ -2431,17 +2488,19 @@ bool TextServerFallback::_font_has_char(const RID &p_font_rid, int64_t p_char) c
}
MutexLock lock(fd->mutex);
+ FontForSizeFallback *ffsd = nullptr;
if (fd->cache.is_empty()) {
- ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, fd->msdf ? Vector2i(fd->msdf_source_size, 0) : Vector2i(16, 0)), false);
+ ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, fd->msdf ? Vector2i(fd->msdf_source_size, 0) : Vector2i(16, 0), ffsd), false);
+ } else {
+ ffsd = fd->cache.begin()->value;
}
- FontForSizeFallback *at_size = fd->cache.begin()->value;
#ifdef MODULE_FREETYPE_ENABLED
- if (at_size && at_size->face) {
- return FT_Get_Char_Index(at_size->face, p_char) != 0;
+ if (ffsd->face) {
+ return FT_Get_Char_Index(ffsd->face, p_char) != 0;
}
#endif
- return (at_size) ? at_size->glyph_map.has((int32_t)p_char) : false;
+ return ffsd->glyph_map.has((int32_t)p_char);
}
String TextServerFallback::_font_get_supported_chars(const RID &p_font_rid) const {
@@ -2449,30 +2508,30 @@ String TextServerFallback::_font_get_supported_chars(const RID &p_font_rid) cons
ERR_FAIL_NULL_V(fd, String());
MutexLock lock(fd->mutex);
+ FontForSizeFallback *ffsd = nullptr;
if (fd->cache.is_empty()) {
- ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, fd->msdf ? Vector2i(fd->msdf_source_size, 0) : Vector2i(16, 0)), String());
+ ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, fd->msdf ? Vector2i(fd->msdf_source_size, 0) : Vector2i(16, 0), ffsd), String());
+ } else {
+ ffsd = fd->cache.begin()->value;
}
- FontForSizeFallback *at_size = fd->cache.begin()->value;
String chars;
#ifdef MODULE_FREETYPE_ENABLED
- if (at_size && at_size->face) {
+ if (ffsd->face) {
FT_UInt gindex;
- FT_ULong charcode = FT_Get_First_Char(at_size->face, &gindex);
+ FT_ULong charcode = FT_Get_First_Char(ffsd->face, &gindex);
while (gindex != 0) {
if (charcode != 0) {
chars = chars + String::chr(charcode);
}
- charcode = FT_Get_Next_Char(at_size->face, charcode, &gindex);
+ charcode = FT_Get_Next_Char(ffsd->face, charcode, &gindex);
}
return chars;
}
#endif
- if (at_size) {
- const HashMap<int32_t, FontGlyph> &gl = at_size->glyph_map;
- for (const KeyValue<int32_t, FontGlyph> &E : gl) {
- chars = chars + String::chr(E.key);
- }
+ const HashMap<int32_t, FontGlyph> &gl = ffsd->glyph_map;
+ for (const KeyValue<int32_t, FontGlyph> &E : gl) {
+ chars = chars + String::chr(E.key);
}
return chars;
}
@@ -2482,10 +2541,12 @@ PackedInt32Array TextServerFallback::_font_get_supported_glyphs(const RID &p_fon
ERR_FAIL_NULL_V(fd, PackedInt32Array());
MutexLock lock(fd->mutex);
+ FontForSizeFallback *at_size = nullptr;
if (fd->cache.is_empty()) {
- ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, fd->msdf ? Vector2i(fd->msdf_source_size, 0) : Vector2i(16, 0)), PackedInt32Array());
+ ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, fd->msdf ? Vector2i(fd->msdf_source_size, 0) : Vector2i(16, 0), at_size), PackedInt32Array());
+ } else {
+ at_size = fd->cache.begin()->value;
}
- FontForSizeFallback *at_size = fd->cache.begin()->value;
PackedInt32Array glyphs;
#ifdef MODULE_FREETYPE_ENABLED
@@ -2516,25 +2577,27 @@ void TextServerFallback::_font_render_range(const RID &p_font_rid, const Vector2
MutexLock lock(fd->mutex);
Vector2i size = _get_size_outline(fd, p_size);
- ERR_FAIL_COND(!_ensure_cache_for_size(fd, size));
+ FontForSizeFallback *ffsd = nullptr;
+ ERR_FAIL_COND(!_ensure_cache_for_size(fd, size, ffsd));
for (int64_t i = p_start; i <= p_end; i++) {
#ifdef MODULE_FREETYPE_ENABLED
int32_t idx = i;
- if (fd->cache[size]->face) {
+ if (ffsd->face) {
+ FontGlyph fgl;
if (fd->msdf) {
- _ensure_glyph(fd, size, (int32_t)idx);
+ _ensure_glyph(fd, size, (int32_t)idx, fgl);
} else {
for (int aa = 0; aa < ((fd->antialiasing == FONT_ANTIALIASING_LCD) ? FONT_LCD_SUBPIXEL_LAYOUT_MAX : 1); aa++) {
if ((fd->subpixel_positioning == SUBPIXEL_POSITIONING_ONE_QUARTER) || (fd->subpixel_positioning == SUBPIXEL_POSITIONING_AUTO && size.x <= SUBPIXEL_POSITIONING_ONE_QUARTER_MAX_SIZE)) {
- _ensure_glyph(fd, size, (int32_t)idx | (0 << 27) | (aa << 24));
- _ensure_glyph(fd, size, (int32_t)idx | (1 << 27) | (aa << 24));
- _ensure_glyph(fd, size, (int32_t)idx | (2 << 27) | (aa << 24));
- _ensure_glyph(fd, size, (int32_t)idx | (3 << 27) | (aa << 24));
+ _ensure_glyph(fd, size, (int32_t)idx | (0 << 27) | (aa << 24), fgl);
+ _ensure_glyph(fd, size, (int32_t)idx | (1 << 27) | (aa << 24), fgl);
+ _ensure_glyph(fd, size, (int32_t)idx | (2 << 27) | (aa << 24), fgl);
+ _ensure_glyph(fd, size, (int32_t)idx | (3 << 27) | (aa << 24), fgl);
} else if ((fd->subpixel_positioning == SUBPIXEL_POSITIONING_ONE_HALF) || (fd->subpixel_positioning == SUBPIXEL_POSITIONING_AUTO && size.x <= SUBPIXEL_POSITIONING_ONE_HALF_MAX_SIZE)) {
- _ensure_glyph(fd, size, (int32_t)idx | (1 << 27) | (aa << 24));
- _ensure_glyph(fd, size, (int32_t)idx | (0 << 27) | (aa << 24));
+ _ensure_glyph(fd, size, (int32_t)idx | (1 << 27) | (aa << 24), fgl);
+ _ensure_glyph(fd, size, (int32_t)idx | (0 << 27) | (aa << 24), fgl);
} else {
- _ensure_glyph(fd, size, (int32_t)idx | (aa << 24));
+ _ensure_glyph(fd, size, (int32_t)idx | (aa << 24), fgl);
}
}
}
@@ -2549,24 +2612,26 @@ void TextServerFallback::_font_render_glyph(const RID &p_font_rid, const Vector2
MutexLock lock(fd->mutex);
Vector2i size = _get_size_outline(fd, p_size);
- ERR_FAIL_COND(!_ensure_cache_for_size(fd, size));
+ FontForSizeFallback *ffsd = nullptr;
+ ERR_FAIL_COND(!_ensure_cache_for_size(fd, size, ffsd));
#ifdef MODULE_FREETYPE_ENABLED
int32_t idx = p_index & 0xffffff; // Remove subpixel shifts.
- if (fd->cache[size]->face) {
+ if (ffsd->face) {
+ FontGlyph fgl;
if (fd->msdf) {
- _ensure_glyph(fd, size, (int32_t)idx);
+ _ensure_glyph(fd, size, (int32_t)idx, fgl);
} else {
for (int aa = 0; aa < ((fd->antialiasing == FONT_ANTIALIASING_LCD) ? FONT_LCD_SUBPIXEL_LAYOUT_MAX : 1); aa++) {
if ((fd->subpixel_positioning == SUBPIXEL_POSITIONING_ONE_QUARTER) || (fd->subpixel_positioning == SUBPIXEL_POSITIONING_AUTO && size.x <= SUBPIXEL_POSITIONING_ONE_QUARTER_MAX_SIZE)) {
- _ensure_glyph(fd, size, (int32_t)idx | (0 << 27) | (aa << 24));
- _ensure_glyph(fd, size, (int32_t)idx | (1 << 27) | (aa << 24));
- _ensure_glyph(fd, size, (int32_t)idx | (2 << 27) | (aa << 24));
- _ensure_glyph(fd, size, (int32_t)idx | (3 << 27) | (aa << 24));
+ _ensure_glyph(fd, size, (int32_t)idx | (0 << 27) | (aa << 24), fgl);
+ _ensure_glyph(fd, size, (int32_t)idx | (1 << 27) | (aa << 24), fgl);
+ _ensure_glyph(fd, size, (int32_t)idx | (2 << 27) | (aa << 24), fgl);
+ _ensure_glyph(fd, size, (int32_t)idx | (3 << 27) | (aa << 24), fgl);
} else if ((fd->subpixel_positioning == SUBPIXEL_POSITIONING_ONE_HALF) || (fd->subpixel_positioning == SUBPIXEL_POSITIONING_AUTO && size.x <= SUBPIXEL_POSITIONING_ONE_HALF_MAX_SIZE)) {
- _ensure_glyph(fd, size, (int32_t)idx | (1 << 27) | (aa << 24));
- _ensure_glyph(fd, size, (int32_t)idx | (0 << 27) | (aa << 24));
+ _ensure_glyph(fd, size, (int32_t)idx | (1 << 27) | (aa << 24), fgl);
+ _ensure_glyph(fd, size, (int32_t)idx | (0 << 27) | (aa << 24), fgl);
} else {
- _ensure_glyph(fd, size, (int32_t)idx | (aa << 24));
+ _ensure_glyph(fd, size, (int32_t)idx | (aa << 24), fgl);
}
}
}
@@ -2583,16 +2648,17 @@ void TextServerFallback::_font_draw_glyph(const RID &p_font_rid, const RID &p_ca
MutexLock lock(fd->mutex);
Vector2i size = _get_size(fd, p_size);
- ERR_FAIL_COND(!_ensure_cache_for_size(fd, size));
+ FontForSizeFallback *ffsd = nullptr;
+ ERR_FAIL_COND(!_ensure_cache_for_size(fd, size, ffsd));
int32_t index = p_index & 0xffffff; // Remove subpixel shifts.
bool lcd_aa = false;
#ifdef MODULE_FREETYPE_ENABLED
- if (!fd->msdf && fd->cache[size]->face) {
+ if (!fd->msdf && ffsd->face) {
// LCD layout, bits 24, 25, 26
if (fd->antialiasing == FONT_ANTIALIASING_LCD) {
- TextServer::FontLCDSubpixelLayout layout = (TextServer::FontLCDSubpixelLayout)(int)GLOBAL_GET("gui/theme/lcd_subpixel_layout");
+ TextServer::FontLCDSubpixelLayout layout = lcd_subpixel_layout.get();
if (layout != FONT_LCD_SUBPIXEL_LAYOUT_NONE) {
lcd_aa = true;
index = index | (layout << 24);
@@ -2609,24 +2675,24 @@ void TextServerFallback::_font_draw_glyph(const RID &p_font_rid, const RID &p_ca
}
#endif
- if (!_ensure_glyph(fd, size, index)) {
+ FontGlyph fgl;
+ if (!_ensure_glyph(fd, size, index, fgl)) {
return; // Invalid or non-graphical glyph, do not display errors, nothing to draw.
}
- const FontGlyph &gl = fd->cache[size]->glyph_map[index];
- if (gl.found) {
- ERR_FAIL_COND(gl.texture_idx < -1 || gl.texture_idx >= fd->cache[size]->textures.size());
+ if (fgl.found) {
+ ERR_FAIL_COND(fgl.texture_idx < -1 || fgl.texture_idx >= ffsd->textures.size());
- if (gl.texture_idx != -1) {
+ if (fgl.texture_idx != -1) {
Color modulate = p_color;
#ifdef MODULE_FREETYPE_ENABLED
- if (fd->cache[size]->face && fd->cache[size]->textures[gl.texture_idx].image.is_valid() && (fd->cache[size]->textures[gl.texture_idx].image->get_format() == Image::FORMAT_RGBA8) && !lcd_aa && !fd->msdf) {
+ if (ffsd->face && ffsd->textures[fgl.texture_idx].image.is_valid() && (ffsd->textures[fgl.texture_idx].image->get_format() == Image::FORMAT_RGBA8) && !lcd_aa && !fd->msdf) {
modulate.r = modulate.g = modulate.b = 1.0;
}
#endif
if (RenderingServer::get_singleton() != nullptr) {
- if (fd->cache[size]->textures[gl.texture_idx].dirty) {
- ShelfPackTexture &tex = fd->cache[size]->textures.write[gl.texture_idx];
+ if (ffsd->textures[fgl.texture_idx].dirty) {
+ ShelfPackTexture &tex = ffsd->textures.write[fgl.texture_idx];
Ref<Image> img = tex.image;
if (fd->mipmaps && !img->has_mipmaps()) {
img = tex.image->duplicate();
@@ -2639,12 +2705,12 @@ void TextServerFallback::_font_draw_glyph(const RID &p_font_rid, const RID &p_ca
}
tex.dirty = false;
}
- RID texture = fd->cache[size]->textures[gl.texture_idx].texture->get_rid();
+ RID texture = ffsd->textures[fgl.texture_idx].texture->get_rid();
if (fd->msdf) {
Point2 cpos = p_pos;
- cpos += gl.rect.position * (double)p_size / (double)fd->msdf_source_size;
- Size2 csize = gl.rect.size * (double)p_size / (double)fd->msdf_source_size;
- RenderingServer::get_singleton()->canvas_item_add_msdf_texture_rect_region(p_canvas, Rect2(cpos, csize), texture, gl.uv_rect, modulate, 0, fd->msdf_range, (double)p_size / (double)fd->msdf_source_size);
+ cpos += fgl.rect.position * (double)p_size / (double)fd->msdf_source_size;
+ Size2 csize = fgl.rect.size * (double)p_size / (double)fd->msdf_source_size;
+ RenderingServer::get_singleton()->canvas_item_add_msdf_texture_rect_region(p_canvas, Rect2(cpos, csize), texture, fgl.uv_rect, modulate, 0, fd->msdf_range, (double)p_size / (double)fd->msdf_source_size);
} else {
Point2 cpos = p_pos;
double scale = _font_get_scale(p_font_rid, p_size);
@@ -2657,8 +2723,8 @@ void TextServerFallback::_font_draw_glyph(const RID &p_font_rid, const RID &p_ca
cpos.y = Math::floor(cpos.y);
cpos.x = Math::floor(cpos.x);
}
- Vector2 gpos = gl.rect.position;
- Size2 csize = gl.rect.size;
+ Vector2 gpos = fgl.rect.position;
+ Size2 csize = fgl.rect.size;
if (fd->fixed_size > 0 && fd->fixed_size_scale_mode != FIXED_SIZE_SCALE_DISABLE && size.x != p_size) {
if (fd->fixed_size_scale_mode == FIXED_SIZE_SCALE_ENABLED) {
double gl_scale = (double)p_size / (double)fd->fixed_size;
@@ -2672,9 +2738,9 @@ void TextServerFallback::_font_draw_glyph(const RID &p_font_rid, const RID &p_ca
}
cpos += gpos;
if (lcd_aa) {
- RenderingServer::get_singleton()->canvas_item_add_lcd_texture_rect_region(p_canvas, Rect2(cpos, csize), texture, gl.uv_rect, modulate);
+ RenderingServer::get_singleton()->canvas_item_add_lcd_texture_rect_region(p_canvas, Rect2(cpos, csize), texture, fgl.uv_rect, modulate);
} else {
- RenderingServer::get_singleton()->canvas_item_add_texture_rect_region(p_canvas, Rect2(cpos, csize), texture, gl.uv_rect, modulate, false, false);
+ RenderingServer::get_singleton()->canvas_item_add_texture_rect_region(p_canvas, Rect2(cpos, csize), texture, fgl.uv_rect, modulate, false, false);
}
}
}
@@ -2691,16 +2757,17 @@ void TextServerFallback::_font_draw_glyph_outline(const RID &p_font_rid, const R
MutexLock lock(fd->mutex);
Vector2i size = _get_size_outline(fd, Vector2i(p_size, p_outline_size));
- ERR_FAIL_COND(!_ensure_cache_for_size(fd, size));
+ FontForSizeFallback *ffsd = nullptr;
+ ERR_FAIL_COND(!_ensure_cache_for_size(fd, size, ffsd));
int32_t index = p_index & 0xffffff; // Remove subpixel shifts.
bool lcd_aa = false;
#ifdef MODULE_FREETYPE_ENABLED
- if (!fd->msdf && fd->cache[size]->face) {
+ if (!fd->msdf && ffsd->face) {
// LCD layout, bits 24, 25, 26
if (fd->antialiasing == FONT_ANTIALIASING_LCD) {
- TextServer::FontLCDSubpixelLayout layout = (TextServer::FontLCDSubpixelLayout)(int)GLOBAL_GET("gui/theme/lcd_subpixel_layout");
+ TextServer::FontLCDSubpixelLayout layout = lcd_subpixel_layout.get();
if (layout != FONT_LCD_SUBPIXEL_LAYOUT_NONE) {
lcd_aa = true;
index = index | (layout << 24);
@@ -2717,24 +2784,24 @@ void TextServerFallback::_font_draw_glyph_outline(const RID &p_font_rid, const R
}
#endif
- if (!_ensure_glyph(fd, size, index)) {
+ FontGlyph fgl;
+ if (!_ensure_glyph(fd, size, index, fgl)) {
return; // Invalid or non-graphical glyph, do not display errors, nothing to draw.
}
- const FontGlyph &gl = fd->cache[size]->glyph_map[index];
- if (gl.found) {
- ERR_FAIL_COND(gl.texture_idx < -1 || gl.texture_idx >= fd->cache[size]->textures.size());
+ if (fgl.found) {
+ ERR_FAIL_COND(fgl.texture_idx < -1 || fgl.texture_idx >= ffsd->textures.size());
- if (gl.texture_idx != -1) {
+ if (fgl.texture_idx != -1) {
Color modulate = p_color;
#ifdef MODULE_FREETYPE_ENABLED
- if (fd->cache[size]->face && fd->cache[size]->textures[gl.texture_idx].image.is_valid() && (fd->cache[size]->textures[gl.texture_idx].image->get_format() == Image::FORMAT_RGBA8) && !lcd_aa && !fd->msdf) {
+ if (ffsd->face && ffsd->textures[fgl.texture_idx].image.is_valid() && (ffsd->textures[fgl.texture_idx].image->get_format() == Image::FORMAT_RGBA8) && !lcd_aa && !fd->msdf) {
modulate.r = modulate.g = modulate.b = 1.0;
}
#endif
if (RenderingServer::get_singleton() != nullptr) {
- if (fd->cache[size]->textures[gl.texture_idx].dirty) {
- ShelfPackTexture &tex = fd->cache[size]->textures.write[gl.texture_idx];
+ if (ffsd->textures[fgl.texture_idx].dirty) {
+ ShelfPackTexture &tex = ffsd->textures.write[fgl.texture_idx];
Ref<Image> img = tex.image;
if (fd->mipmaps && !img->has_mipmaps()) {
img = tex.image->duplicate();
@@ -2747,12 +2814,12 @@ void TextServerFallback::_font_draw_glyph_outline(const RID &p_font_rid, const R
}
tex.dirty = false;
}
- RID texture = fd->cache[size]->textures[gl.texture_idx].texture->get_rid();
+ RID texture = ffsd->textures[fgl.texture_idx].texture->get_rid();
if (fd->msdf) {
Point2 cpos = p_pos;
- cpos += gl.rect.position * (double)p_size / (double)fd->msdf_source_size;
- Size2 csize = gl.rect.size * (double)p_size / (double)fd->msdf_source_size;
- RenderingServer::get_singleton()->canvas_item_add_msdf_texture_rect_region(p_canvas, Rect2(cpos, csize), texture, gl.uv_rect, modulate, p_outline_size, fd->msdf_range, (double)p_size / (double)fd->msdf_source_size);
+ cpos += fgl.rect.position * (double)p_size / (double)fd->msdf_source_size;
+ Size2 csize = fgl.rect.size * (double)p_size / (double)fd->msdf_source_size;
+ RenderingServer::get_singleton()->canvas_item_add_msdf_texture_rect_region(p_canvas, Rect2(cpos, csize), texture, fgl.uv_rect, modulate, p_outline_size, fd->msdf_range, (double)p_size / (double)fd->msdf_source_size);
} else {
Point2 cpos = p_pos;
double scale = _font_get_scale(p_font_rid, p_size);
@@ -2765,8 +2832,8 @@ void TextServerFallback::_font_draw_glyph_outline(const RID &p_font_rid, const R
cpos.y = Math::floor(cpos.y);
cpos.x = Math::floor(cpos.x);
}
- Vector2 gpos = gl.rect.position;
- Size2 csize = gl.rect.size;
+ Vector2 gpos = fgl.rect.position;
+ Size2 csize = fgl.rect.size;
if (fd->fixed_size > 0 && fd->fixed_size_scale_mode != FIXED_SIZE_SCALE_DISABLE && size.x != p_size) {
if (fd->fixed_size_scale_mode == FIXED_SIZE_SCALE_ENABLED) {
double gl_scale = (double)p_size / (double)fd->fixed_size;
@@ -2780,9 +2847,9 @@ void TextServerFallback::_font_draw_glyph_outline(const RID &p_font_rid, const R
}
cpos += gpos;
if (lcd_aa) {
- RenderingServer::get_singleton()->canvas_item_add_lcd_texture_rect_region(p_canvas, Rect2(cpos, csize), texture, gl.uv_rect, modulate);
+ RenderingServer::get_singleton()->canvas_item_add_lcd_texture_rect_region(p_canvas, Rect2(cpos, csize), texture, fgl.uv_rect, modulate);
} else {
- RenderingServer::get_singleton()->canvas_item_add_texture_rect_region(p_canvas, Rect2(cpos, csize), texture, gl.uv_rect, modulate, false, false);
+ RenderingServer::get_singleton()->canvas_item_add_texture_rect_region(p_canvas, Rect2(cpos, csize), texture, fgl.uv_rect, modulate, false, false);
}
}
}
@@ -2872,7 +2939,8 @@ void TextServerFallback::_font_remove_script_support_override(const RID &p_font_
MutexLock lock(fd->mutex);
Vector2i size = _get_size(fd, 16);
- ERR_FAIL_COND(!_ensure_cache_for_size(fd, size));
+ FontForSizeFallback *ffsd = nullptr;
+ ERR_FAIL_COND(!_ensure_cache_for_size(fd, size, ffsd));
fd->script_support_overrides.erase(p_script);
}
@@ -2894,7 +2962,8 @@ void TextServerFallback::_font_set_opentype_feature_overrides(const RID &p_font_
MutexLock lock(fd->mutex);
Vector2i size = _get_size(fd, 16);
- ERR_FAIL_COND(!_ensure_cache_for_size(fd, size));
+ FontForSizeFallback *ffsd = nullptr;
+ ERR_FAIL_COND(!_ensure_cache_for_size(fd, size, ffsd));
fd->feature_overrides = p_overrides;
}
@@ -2916,7 +2985,8 @@ Dictionary TextServerFallback::_font_supported_variation_list(const RID &p_font_
MutexLock lock(fd->mutex);
Vector2i size = _get_size(fd, 16);
- ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size), Dictionary());
+ FontForSizeFallback *ffsd = nullptr;
+ ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size, ffsd), Dictionary());
return fd->supported_varaitions;
}
@@ -2953,7 +3023,7 @@ void TextServerFallback::_font_set_global_oversampling(double p_oversampling) {
/*************************************************************************/
void TextServerFallback::invalidate(ShapedTextDataFallback *p_shaped) {
- p_shaped->valid = false;
+ p_shaped->valid.clear();
p_shaped->sort_valid = false;
p_shaped->line_breaks_valid = false;
p_shaped->justification_ops_valid = false;
@@ -3192,7 +3262,7 @@ void TextServerFallback::_shaped_set_span_update_font(const RID &p_shaped, int64
span.font_size = p_size;
span.features = p_opentype_features;
- sd->valid = false;
+ sd->valid.clear();
}
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) {
@@ -3288,7 +3358,7 @@ bool TextServerFallback::_shaped_text_resize_object(const RID &p_shaped, const V
sd->objects[p_key].rect.size = p_size;
sd->objects[p_key].inline_align = p_inline_align;
sd->objects[p_key].baseline = p_baseline;
- if (sd->valid) {
+ if (sd->valid.is_set()) {
// Recalc string metrics.
sd->ascent = 0;
sd->descent = 0;
@@ -3431,7 +3501,7 @@ RID TextServerFallback::_shaped_text_substr(const RID &p_shaped, int64_t p_start
if (sd->parent != RID()) {
return _shaped_text_substr(sd->parent, p_start, p_length);
}
- if (!sd->valid) {
+ if (!sd->valid.is_set()) {
const_cast<TextServerFallback *>(this)->_shaped_text_shape(p_shaped);
}
ERR_FAIL_COND_V(p_start < 0 || p_length < 0, RID());
@@ -3514,7 +3584,7 @@ RID TextServerFallback::_shaped_text_substr(const RID &p_shaped, int64_t p_start
_realign(new_sd);
}
- new_sd->valid = true;
+ new_sd->valid.set();
return shaped_owner.make_rid(new_sd);
}
@@ -3532,7 +3602,7 @@ double TextServerFallback::_shaped_text_fit_to_width(const RID &p_shaped, double
ERR_FAIL_NULL_V(sd, 0.0);
MutexLock lock(sd->mutex);
- if (!sd->valid) {
+ if (!sd->valid.is_set()) {
const_cast<TextServerFallback *>(this)->_shaped_text_shape(p_shaped);
}
if (!sd->justification_ops_valid) {
@@ -3641,7 +3711,7 @@ double TextServerFallback::_shaped_text_tab_align(const RID &p_shaped, const Pac
ERR_FAIL_NULL_V(sd, 0.0);
MutexLock lock(sd->mutex);
- if (!sd->valid) {
+ if (!sd->valid.is_set()) {
const_cast<TextServerFallback *>(this)->_shaped_text_shape(p_shaped);
}
if (!sd->line_breaks_valid) {
@@ -3697,7 +3767,7 @@ bool TextServerFallback::_shaped_text_update_breaks(const RID &p_shaped) {
ERR_FAIL_NULL_V(sd, false);
MutexLock lock(sd->mutex);
- if (!sd->valid) {
+ if (!sd->valid.is_set()) {
_shaped_text_shape(p_shaped);
}
@@ -3761,7 +3831,7 @@ bool TextServerFallback::_shaped_text_update_justification_ops(const RID &p_shap
ERR_FAIL_NULL_V(sd, false);
MutexLock lock(sd->mutex);
- if (!sd->valid) {
+ if (!sd->valid.is_set()) {
_shaped_text_shape(p_shaped);
}
if (!sd->line_breaks_valid) {
@@ -3940,7 +4010,7 @@ void TextServerFallback::_shaped_text_overrun_trim_to_width(const RID &p_shaped_
ERR_FAIL_NULL_MSG(sd, "ShapedTextDataFallback invalid.");
MutexLock lock(sd->mutex);
- if (!sd->valid) {
+ if (!sd->valid.is_set()) {
_shaped_text_shape(p_shaped_line);
}
@@ -3967,7 +4037,7 @@ void TextServerFallback::_shaped_text_overrun_trim_to_width(const RID &p_shaped_
Vector<ShapedTextDataFallback::Span> &spans = sd->spans;
if (sd->parent != RID()) {
ShapedTextDataFallback *parent_sd = shaped_owner.get_or_null(sd->parent);
- ERR_FAIL_COND(!parent_sd->valid);
+ ERR_FAIL_COND(!parent_sd->valid.is_set());
spans = parent_sd->spans;
}
@@ -4161,7 +4231,7 @@ bool TextServerFallback::_shaped_text_shape(const RID &p_shaped) {
ERR_FAIL_NULL_V(sd, false);
MutexLock lock(sd->mutex);
- if (sd->valid) {
+ if (sd->valid.is_set()) {
return true;
}
@@ -4178,7 +4248,7 @@ bool TextServerFallback::_shaped_text_shape(const RID &p_shaped) {
sd->glyphs.clear();
if (sd->text.length() == 0) {
- sd->valid = true;
+ sd->valid.set();
return true;
}
@@ -4307,16 +4377,15 @@ bool TextServerFallback::_shaped_text_shape(const RID &p_shaped) {
// Align embedded objects to baseline.
_realign(sd);
- sd->valid = true;
- return sd->valid;
+ sd->valid.set();
+ return sd->valid.is_set();
}
bool TextServerFallback::_shaped_text_is_ready(const RID &p_shaped) const {
const ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped);
ERR_FAIL_NULL_V(sd, false);
- MutexLock lock(sd->mutex);
- return sd->valid;
+ return sd->valid.is_set();
}
const Glyph *TextServerFallback::_shaped_text_get_glyphs(const RID &p_shaped) const {
@@ -4324,7 +4393,7 @@ const Glyph *TextServerFallback::_shaped_text_get_glyphs(const RID &p_shaped) co
ERR_FAIL_NULL_V(sd, nullptr);
MutexLock lock(sd->mutex);
- if (!sd->valid) {
+ if (!sd->valid.is_set()) {
const_cast<TextServerFallback *>(this)->_shaped_text_shape(p_shaped);
}
return sd->glyphs.ptr();
@@ -4335,7 +4404,7 @@ int64_t TextServerFallback::_shaped_text_get_glyph_count(const RID &p_shaped) co
ERR_FAIL_NULL_V(sd, 0);
MutexLock lock(sd->mutex);
- if (!sd->valid) {
+ if (!sd->valid.is_set()) {
const_cast<TextServerFallback *>(this)->_shaped_text_shape(p_shaped);
}
return sd->glyphs.size();
@@ -4346,7 +4415,7 @@ const Glyph *TextServerFallback::_shaped_text_sort_logical(const RID &p_shaped)
ERR_FAIL_NULL_V(sd, nullptr);
MutexLock lock(sd->mutex);
- if (!sd->valid) {
+ if (!sd->valid.is_set()) {
const_cast<TextServerFallback *>(this)->_shaped_text_shape(p_shaped);
}
@@ -4380,7 +4449,7 @@ Rect2 TextServerFallback::_shaped_text_get_object_rect(const RID &p_shaped, cons
MutexLock lock(sd->mutex);
ERR_FAIL_COND_V(!sd->objects.has(p_key), Rect2());
- if (!sd->valid) {
+ if (!sd->valid.is_set()) {
const_cast<TextServerFallback *>(this)->_shaped_text_shape(p_shaped);
}
return sd->objects[p_key].rect;
@@ -4417,7 +4486,7 @@ Size2 TextServerFallback::_shaped_text_get_size(const RID &p_shaped) const {
ERR_FAIL_NULL_V(sd, Size2());
MutexLock lock(sd->mutex);
- if (!sd->valid) {
+ if (!sd->valid.is_set()) {
const_cast<TextServerFallback *>(this)->_shaped_text_shape(p_shaped);
}
if (sd->orientation == TextServer::ORIENTATION_HORIZONTAL) {
@@ -4432,7 +4501,7 @@ double TextServerFallback::_shaped_text_get_ascent(const RID &p_shaped) const {
ERR_FAIL_NULL_V(sd, 0.0);
MutexLock lock(sd->mutex);
- if (!sd->valid) {
+ if (!sd->valid.is_set()) {
const_cast<TextServerFallback *>(this)->_shaped_text_shape(p_shaped);
}
return sd->ascent + sd->extra_spacing[SPACING_TOP];
@@ -4443,7 +4512,7 @@ double TextServerFallback::_shaped_text_get_descent(const RID &p_shaped) const {
ERR_FAIL_NULL_V(sd, 0.0);
MutexLock lock(sd->mutex);
- if (!sd->valid) {
+ if (!sd->valid.is_set()) {
const_cast<TextServerFallback *>(this)->_shaped_text_shape(p_shaped);
}
return sd->descent + sd->extra_spacing[SPACING_BOTTOM];
@@ -4454,7 +4523,7 @@ double TextServerFallback::_shaped_text_get_width(const RID &p_shaped) const {
ERR_FAIL_NULL_V(sd, 0.0);
MutexLock lock(sd->mutex);
- if (!sd->valid) {
+ if (!sd->valid.is_set()) {
const_cast<TextServerFallback *>(this)->_shaped_text_shape(p_shaped);
}
return Math::ceil(sd->width);
@@ -4465,7 +4534,7 @@ double TextServerFallback::_shaped_text_get_underline_position(const RID &p_shap
ERR_FAIL_NULL_V(sd, 0.0);
MutexLock lock(sd->mutex);
- if (!sd->valid) {
+ if (!sd->valid.is_set()) {
const_cast<TextServerFallback *>(this)->_shaped_text_shape(p_shaped);
}
@@ -4477,7 +4546,7 @@ double TextServerFallback::_shaped_text_get_underline_thickness(const RID &p_sha
ERR_FAIL_NULL_V(sd, 0.0);
MutexLock lock(sd->mutex);
- if (!sd->valid) {
+ if (!sd->valid.is_set()) {
const_cast<TextServerFallback *>(this)->_shaped_text_shape(p_shaped);
}
@@ -4489,7 +4558,7 @@ PackedInt32Array TextServerFallback::_shaped_text_get_character_breaks(const RID
ERR_FAIL_NULL_V(sd, PackedInt32Array());
MutexLock lock(sd->mutex);
- if (!sd->valid) {
+ if (!sd->valid.is_set()) {
const_cast<TextServerFallback *>(this)->_shaped_text_shape(p_shaped);
}
@@ -4626,8 +4695,13 @@ PackedInt32Array TextServerFallback::_string_get_word_breaks(const String &p_str
return ret;
}
+void TextServerFallback::_update_settings() {
+ lcd_subpixel_layout.set((TextServer::FontLCDSubpixelLayout)(int)GLOBAL_GET("gui/theme/lcd_subpixel_layout"));
+}
+
TextServerFallback::TextServerFallback() {
_insert_feature_sets();
+ ProjectSettings::get_singleton()->connect("settings_changed", callable_mp(this, &TextServerFallback::_update_settings));
};
void TextServerFallback::_cleanup() {
diff --git a/modules/text_server_fb/text_server_fb.h b/modules/text_server_fb/text_server_fb.h
index 1b76c6fa0f..ee1f72401f 100644
--- a/modules/text_server_fb/text_server_fb.h
+++ b/modules/text_server_fb/text_server_fb.h
@@ -72,6 +72,7 @@
#include <godot_cpp/templates/hash_map.hpp>
#include <godot_cpp/templates/hash_set.hpp>
#include <godot_cpp/templates/rid_owner.hpp>
+#include <godot_cpp/templates/safe_refcount.hpp>
#include <godot_cpp/templates/vector.hpp>
using namespace godot;
@@ -83,6 +84,7 @@ using namespace godot;
#include "core/object/worker_thread_pool.h"
#include "core/templates/hash_map.h"
#include "core/templates/rid_owner.h"
+#include "core/templates/safe_refcount.h"
#include "scene/resources/image_texture.h"
#include "servers/text/text_server_extension.h"
@@ -116,6 +118,9 @@ class TextServerFallback : public TextServerExtension {
HashMap<StringName, int32_t> feature_sets;
HashMap<int32_t, StringName> feature_sets_inv;
+ SafeNumeric<TextServer::FontLCDSubpixelLayout> lcd_subpixel_layout{ TextServer::FontLCDSubpixelLayout::FONT_LCD_SUBPIXEL_LAYOUT_NONE };
+ void _update_settings();
+
void _insert_feature_sets();
_FORCE_INLINE_ void _insert_feature(const StringName &p_name, int32_t p_tag);
@@ -278,7 +283,7 @@ class TextServerFallback : public TextServerExtension {
int extra_spacing[4] = { 0, 0, 0, 0 };
double baseline_offset = 0.0;
- HashMap<Vector2i, FontForSizeFallback *, VariantHasher, VariantComparator> cache;
+ HashMap<Vector2i, FontForSizeFallback *> cache;
bool face_init = false;
Dictionary supported_varaitions;
@@ -308,8 +313,8 @@ class TextServerFallback : public TextServerExtension {
#ifdef MODULE_FREETYPE_ENABLED
_FORCE_INLINE_ FontGlyph rasterize_bitmap(FontForSizeFallback *p_data, int p_rect_margin, FT_Bitmap p_bitmap, int p_yofs, int p_xofs, const Vector2 &p_advance, bool p_bgra) const;
#endif
- _FORCE_INLINE_ bool _ensure_glyph(FontFallback *p_font_data, const Vector2i &p_size, int32_t p_glyph) const;
- _FORCE_INLINE_ bool _ensure_cache_for_size(FontFallback *p_font_data, const Vector2i &p_size) const;
+ _FORCE_INLINE_ bool _ensure_glyph(FontFallback *p_font_data, const Vector2i &p_size, int32_t p_glyph, FontGlyph &r_glyph) const;
+ _FORCE_INLINE_ bool _ensure_cache_for_size(FontFallback *p_font_data, const Vector2i &p_size, FontForSizeFallback *&r_cache_for_size) const;
_FORCE_INLINE_ void _font_clear_cache(FontFallback *p_font_data);
static void _generateMTSDF_threaded(void *p_td, uint32_t p_y);
@@ -432,7 +437,7 @@ class TextServerFallback : public TextServerExtension {
/* Shaped data */
TextServer::Direction para_direction = DIRECTION_LTR; // Detected text direction.
- bool valid = false; // String is shaped.
+ SafeFlag valid{ false }; // String is shaped.
bool line_breaks_valid = false; // Line and word break flags are populated (and virtual zero width spaces inserted).
bool justification_ops_valid = false; // Virtual elongation glyphs are added to the string.
bool sort_valid = false;
diff --git a/modules/upnp/upnp.cpp b/modules/upnp/upnp.cpp
index 70f0ea346b..6bdb261b50 100644
--- a/modules/upnp/upnp.cpp
+++ b/modules/upnp/upnp.cpp
@@ -229,14 +229,14 @@ Ref<UPNPDevice> UPNP::get_device(int index) const {
}
void UPNP::add_device(Ref<UPNPDevice> device) {
- ERR_FAIL_NULL(device);
+ ERR_FAIL_COND(device.is_null());
devices.push_back(device);
}
void UPNP::set_device(int index, Ref<UPNPDevice> device) {
ERR_FAIL_INDEX(index, devices.size());
- ERR_FAIL_NULL(device);
+ ERR_FAIL_COND(device.is_null());
devices.set(index, device);
}
@@ -257,7 +257,7 @@ Ref<UPNPDevice> UPNP::get_gateway() const {
for (int i = 0; i < devices.size(); i++) {
Ref<UPNPDevice> dev = get_device(i);
- if (dev != nullptr && dev->is_valid_gateway()) {
+ if (dev.is_valid() && dev->is_valid_gateway()) {
return dev;
}
}
@@ -292,7 +292,7 @@ bool UPNP::is_discover_ipv6() const {
String UPNP::query_external_address() const {
Ref<UPNPDevice> dev = get_gateway();
- if (dev == nullptr) {
+ if (dev.is_null()) {
return "";
}
@@ -302,7 +302,7 @@ String UPNP::query_external_address() const {
int UPNP::add_port_mapping(int port, int port_internal, String desc, String proto, int duration) const {
Ref<UPNPDevice> dev = get_gateway();
- if (dev == nullptr) {
+ if (dev.is_null()) {
return UPNP_RESULT_NO_GATEWAY;
}
@@ -312,7 +312,7 @@ int UPNP::add_port_mapping(int port, int port_internal, String desc, String prot
int UPNP::delete_port_mapping(int port, String proto) const {
Ref<UPNPDevice> dev = get_gateway();
- if (dev == nullptr) {
+ if (dev.is_null()) {
return UPNP_RESULT_NO_GATEWAY;
}
diff --git a/modules/zip/zip_packer.cpp b/modules/zip/zip_packer.cpp
index e96d9da7a9..4e182f9787 100644
--- a/modules/zip/zip_packer.cpp
+++ b/modules/zip/zip_packer.cpp
@@ -48,7 +48,7 @@ Error ZIPPacker::close() {
Error err = zipClose(zf, nullptr) == ZIP_OK ? OK : FAILED;
if (err == OK) {
- DEV_ASSERT(fa == nullptr);
+ DEV_ASSERT(fa.is_null());
zf = nullptr;
}
diff --git a/modules/zip/zip_reader.cpp b/modules/zip/zip_reader.cpp
index 123d1e5d46..76f48edb69 100644
--- a/modules/zip/zip_reader.cpp
+++ b/modules/zip/zip_reader.cpp
@@ -48,7 +48,7 @@ Error ZIPReader::close() {
Error err = unzClose(uzf) == UNZ_OK ? OK : FAILED;
if (err == OK) {
- DEV_ASSERT(fa == nullptr);
+ DEV_ASSERT(fa.is_null());
uzf = nullptr;
}
diff --git a/platform/SCsub b/platform/SCsub
index b07023efed..cdaa6074ba 100644
--- a/platform/SCsub
+++ b/platform/SCsub
@@ -15,12 +15,12 @@ def export_icon_builder(target, source, env):
src_path = Path(str(source[0]))
src_name = src_path.stem
platform = src_path.parent.parent.stem
- with open(str(source[0]), "rb") as file:
- svg = "".join([f"\\{hex(x)[1:]}" for x in file.read()])
+ with open(str(source[0]), "r") as file:
+ svg = file.read()
with methods.generated_wrapper(target, prefix=platform) as file:
file.write(
f"""\
-static const char *_{platform}_{src_name}_svg = "{svg}";
+static const char *_{platform}_{src_name}_svg = {methods.to_raw_cstring(svg)};
"""
)
diff --git a/platform/android/api/api.cpp b/platform/android/api/api.cpp
index 60c369951c..6920f801e5 100644
--- a/platform/android/api/api.cpp
+++ b/platform/android/api/api.cpp
@@ -49,6 +49,7 @@ void register_android_api() {
#endif
GDREGISTER_CLASS(JavaClass);
+ GDREGISTER_CLASS(JavaObject);
GDREGISTER_CLASS(JavaClassWrapper);
Engine::get_singleton()->add_singleton(Engine::Singleton("JavaClassWrapper", JavaClassWrapper::get_singleton()));
}
@@ -59,6 +60,16 @@ void unregister_android_api() {
#endif
}
+void JavaClass::_bind_methods() {
+ ClassDB::bind_method(D_METHOD("get_java_class_name"), &JavaClass::get_java_class_name);
+ ClassDB::bind_method(D_METHOD("get_java_method_list"), &JavaClass::get_java_method_list);
+ ClassDB::bind_method(D_METHOD("get_java_parent_class"), &JavaClass::get_java_parent_class);
+}
+
+void JavaObject::_bind_methods() {
+ ClassDB::bind_method(D_METHOD("get_java_class"), &JavaObject::get_java_class);
+}
+
void JavaClassWrapper::_bind_methods() {
ClassDB::bind_method(D_METHOD("wrap", "name"), &JavaClassWrapper::wrap);
}
@@ -69,13 +80,32 @@ Variant JavaClass::callp(const StringName &, const Variant **, int, Callable::Ca
return Variant();
}
+String JavaClass::get_java_class_name() const {
+ return "";
+}
+
+TypedArray<Dictionary> JavaClass::get_java_method_list() const {
+ return TypedArray<Dictionary>();
+}
+
+Ref<JavaClass> JavaClass::get_java_parent_class() const {
+ return Ref<JavaClass>();
+}
+
JavaClass::JavaClass() {
}
+JavaClass::~JavaClass() {
+}
+
Variant JavaObject::callp(const StringName &, const Variant **, int, Callable::CallError &) {
return Variant();
}
+Ref<JavaClass> JavaObject::get_java_class() const {
+ return Ref<JavaClass>();
+}
+
JavaClassWrapper *JavaClassWrapper::singleton = nullptr;
Ref<JavaClass> JavaClassWrapper::wrap(const String &) {
diff --git a/platform/android/api/java_class_wrapper.h b/platform/android/api/java_class_wrapper.h
index e21a331ab9..52df1644be 100644
--- a/platform/android/api/java_class_wrapper.h
+++ b/platform/android/api/java_class_wrapper.h
@@ -32,6 +32,7 @@
#define JAVA_CLASS_WRAPPER_H
#include "core/object/ref_counted.h"
+#include "core/variant/typed_array.h"
#ifdef ANDROID_ENABLED
#include <android/log.h>
@@ -67,6 +68,7 @@ class JavaClass : public RefCounted {
struct MethodInfo {
bool _static = false;
+ bool _constructor = false;
Vector<uint32_t> param_types;
Vector<StringName> param_sigs;
uint32_t return_type = 0;
@@ -174,14 +176,29 @@ class JavaClass : public RefCounted {
bool _call_method(JavaObject *p_instance, const StringName &p_method, const Variant **p_args, int p_argcount, Callable::CallError &r_error, Variant &ret);
friend class JavaClassWrapper;
+ friend class JavaObject;
+ String java_class_name;
+ String java_constructor_name;
HashMap<StringName, List<MethodInfo>> methods;
jclass _class;
#endif
+protected:
+ static void _bind_methods();
+
public:
virtual Variant callp(const StringName &p_method, const Variant **p_args, int p_argcount, Callable::CallError &r_error) override;
+ String get_java_class_name() const;
+ TypedArray<Dictionary> get_java_method_list() const;
+ Ref<JavaClass> get_java_parent_class() const;
+
+#ifdef ANDROID_ENABLED
+ virtual String to_string() override;
+#endif
+
JavaClass();
+ ~JavaClass();
};
class JavaObject : public RefCounted {
@@ -191,14 +208,24 @@ class JavaObject : public RefCounted {
Ref<JavaClass> base_class;
friend class JavaClass;
- jobject instance;
+ jobject instance = nullptr;
#endif
+protected:
+ static void _bind_methods();
+
public:
virtual Variant callp(const StringName &p_method, const Variant **p_args, int p_argcount, Callable::CallError &r_error) override;
+ Ref<JavaClass> get_java_class() const;
+
#ifdef ANDROID_ENABLED
- JavaObject(const Ref<JavaClass> &p_base, jobject *p_instance);
+ virtual String to_string() override;
+
+ jobject get_instance() { return instance; }
+
+ JavaObject();
+ JavaObject(const Ref<JavaClass> &p_base, jobject p_instance);
~JavaObject();
#endif
};
@@ -209,13 +236,17 @@ class JavaClassWrapper : public Object {
#ifdef ANDROID_ENABLED
RBMap<String, Ref<JavaClass>> class_cache;
friend class JavaClass;
- jmethodID getDeclaredMethods;
- jmethodID getFields;
- jmethodID getParameterTypes;
- jmethodID getReturnType;
- jmethodID getModifiers;
- jmethodID getName;
+ jmethodID Class_getDeclaredConstructors;
+ jmethodID Class_getDeclaredMethods;
+ jmethodID Class_getFields;
jmethodID Class_getName;
+ jmethodID Class_getSuperclass;
+ jmethodID Constructor_getParameterTypes;
+ jmethodID Constructor_getModifiers;
+ jmethodID Method_getParameterTypes;
+ jmethodID Method_getReturnType;
+ jmethodID Method_getModifiers;
+ jmethodID Method_getName;
jmethodID Field_getName;
jmethodID Field_getModifiers;
jmethodID Field_get;
@@ -242,6 +273,8 @@ public:
Ref<JavaClass> wrap(const String &p_class);
#ifdef ANDROID_ENABLED
+ Ref<JavaClass> wrap_jclass(jclass p_class);
+
JavaClassWrapper(jobject p_activity = nullptr);
#else
JavaClassWrapper();
diff --git a/platform/android/api/jni_singleton.h b/platform/android/api/jni_singleton.h
index 087fd1bace..06afc4eb78 100644
--- a/platform/android/api/jni_singleton.h
+++ b/platform/android/api/jni_singleton.h
@@ -180,6 +180,11 @@ public:
env->DeleteLocalRef(obj);
} break;
+ case Variant::OBJECT: {
+ jobject obj = env->CallObjectMethodA(instance, E->get().method, v);
+ ret = _jobject_to_variant(env, obj);
+ env->DeleteLocalRef(obj);
+ } break;
default: {
env->PopLocalFrame(nullptr);
ERR_FAIL_V(Variant());
diff --git a/platform/android/display_server_android.cpp b/platform/android/display_server_android.cpp
index 8dc0e869d0..5bb520bd73 100644
--- a/platform/android/display_server_android.cpp
+++ b/platform/android/display_server_android.cpp
@@ -455,11 +455,15 @@ Size2i DisplayServerAndroid::window_get_size_with_decorations(DisplayServer::Win
}
void DisplayServerAndroid::window_set_mode(DisplayServer::WindowMode p_mode, DisplayServer::WindowID p_window) {
- // Not supported on Android.
+ OS_Android::get_singleton()->get_godot_java()->enable_immersive_mode(p_mode == WINDOW_MODE_FULLSCREEN || p_mode == WINDOW_MODE_EXCLUSIVE_FULLSCREEN);
}
DisplayServer::WindowMode DisplayServerAndroid::window_get_mode(DisplayServer::WindowID p_window) const {
- return WINDOW_MODE_FULLSCREEN;
+ if (OS_Android::get_singleton()->get_godot_java()->is_in_immersive_mode()) {
+ return WINDOW_MODE_FULLSCREEN;
+ } else {
+ return WINDOW_MODE_MAXIMIZED;
+ }
}
bool DisplayServerAndroid::window_is_maximize_allowed(DisplayServer::WindowID p_window) const {
diff --git a/platform/android/export/export_plugin.cpp b/platform/android/export/export_plugin.cpp
index 0fdaca4839..f8ac591a78 100644
--- a/platform/android/export/export_plugin.cpp
+++ b/platform/android/export/export_plugin.cpp
@@ -2002,7 +2002,7 @@ String EditorExportPlatformAndroid::get_device_architecture(int p_index) const {
return devices[p_index].architecture;
}
-Error EditorExportPlatformAndroid::run(const Ref<EditorExportPreset> &p_preset, int p_device, int p_debug_flags) {
+Error EditorExportPlatformAndroid::run(const Ref<EditorExportPreset> &p_preset, int p_device, BitField<EditorExportPlatform::DebugFlags> p_debug_flags) {
ERR_FAIL_INDEX_V(p_device, devices.size(), ERR_INVALID_PARAMETER);
String can_export_error;
@@ -2024,11 +2024,11 @@ Error EditorExportPlatformAndroid::run(const Ref<EditorExportPreset> &p_preset,
}
const bool use_wifi_for_remote_debug = EDITOR_GET("export/android/use_wifi_for_remote_debug");
- const bool use_remote = (p_debug_flags & DEBUG_FLAG_REMOTE_DEBUG) || (p_debug_flags & DEBUG_FLAG_DUMB_CLIENT);
+ const bool use_remote = p_debug_flags.has_flag(DEBUG_FLAG_REMOTE_DEBUG) || p_debug_flags.has_flag(DEBUG_FLAG_DUMB_CLIENT);
const bool use_reverse = devices[p_device].api_level >= 21 && !use_wifi_for_remote_debug;
if (use_reverse) {
- p_debug_flags |= DEBUG_FLAG_REMOTE_DEBUG_LOCALHOST;
+ p_debug_flags.set_flag(DEBUG_FLAG_REMOTE_DEBUG_LOCALHOST);
}
String tmp_export_path = EditorPaths::get_singleton()->get_cache_dir().path_join("tmpexport." + uitos(OS::get_singleton()->get_unix_time()) + ".apk");
@@ -2107,7 +2107,7 @@ Error EditorExportPlatformAndroid::run(const Ref<EditorExportPreset> &p_preset,
OS::get_singleton()->execute(adb, args, &output, &rv, true);
print_verbose(output);
- if (p_debug_flags & DEBUG_FLAG_REMOTE_DEBUG) {
+ if (p_debug_flags.has_flag(DEBUG_FLAG_REMOTE_DEBUG)) {
int dbg_port = EDITOR_GET("network/debug/remote_port");
args.clear();
args.push_back("-s");
@@ -2122,7 +2122,7 @@ Error EditorExportPlatformAndroid::run(const Ref<EditorExportPreset> &p_preset,
print_line("Reverse result: " + itos(rv));
}
- if (p_debug_flags & DEBUG_FLAG_DUMB_CLIENT) {
+ if (p_debug_flags.has_flag(DEBUG_FLAG_DUMB_CLIENT)) {
int fs_port = EDITOR_GET("filesystem/file_server/port");
args.clear();
@@ -2667,7 +2667,7 @@ Error EditorExportPlatformAndroid::save_apk_expansion_file(const Ref<EditorExpor
return err;
}
-void EditorExportPlatformAndroid::get_command_line_flags(const Ref<EditorExportPreset> &p_preset, const String &p_path, int p_flags, Vector<uint8_t> &r_command_line_flags) {
+void EditorExportPlatformAndroid::get_command_line_flags(const Ref<EditorExportPreset> &p_preset, const String &p_path, BitField<EditorExportPlatform::DebugFlags> p_flags, Vector<uint8_t> &r_command_line_flags) {
String cmdline = p_preset->get("command_line/extra_args");
Vector<String> command_line_strings = cmdline.strip_edges().split(" ");
for (int i = 0; i < command_line_strings.size(); i++) {
@@ -2677,7 +2677,7 @@ void EditorExportPlatformAndroid::get_command_line_flags(const Ref<EditorExportP
}
}
- gen_export_flags(command_line_strings, p_flags);
+ command_line_strings.append_array(gen_export_flags(p_flags));
bool apk_expansion = p_preset->get("apk_expansion/enable");
if (apk_expansion) {
@@ -2700,7 +2700,7 @@ void EditorExportPlatformAndroid::get_command_line_flags(const Ref<EditorExportP
bool immersive = p_preset->get("screen/immersive_mode");
if (immersive) {
- command_line_strings.push_back("--use_immersive");
+ command_line_strings.push_back("--fullscreen");
}
bool debug_opengl = p_preset->get("graphics/opengl_debug");
@@ -3000,13 +3000,13 @@ bool EditorExportPlatformAndroid::_is_clean_build_required(const Ref<EditorExpor
return have_plugins_changed || has_build_dir_changed || first_build;
}
-Error EditorExportPlatformAndroid::export_project(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, int p_flags) {
+Error EditorExportPlatformAndroid::export_project(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, BitField<EditorExportPlatform::DebugFlags> p_flags) {
int export_format = int(p_preset->get("gradle_build/export_format"));
bool should_sign = p_preset->get("package/signed");
return export_project_helper(p_preset, p_debug, p_path, export_format, should_sign, p_flags);
}
-Error EditorExportPlatformAndroid::export_project_helper(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, int export_format, bool should_sign, int p_flags) {
+Error EditorExportPlatformAndroid::export_project_helper(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, int export_format, bool should_sign, BitField<EditorExportPlatform::DebugFlags> p_flags) {
ExportNotifier notifier(*this, p_preset, p_debug, p_path, p_flags);
const String base_dir = p_path.get_base_dir();
@@ -3022,7 +3022,7 @@ Error EditorExportPlatformAndroid::export_project_helper(const Ref<EditorExportP
bool use_gradle_build = bool(p_preset->get("gradle_build/use_gradle_build"));
String gradle_build_directory = use_gradle_build ? ExportTemplateManager::get_android_build_directory(p_preset) : "";
- bool p_give_internet = p_flags & (DEBUG_FLAG_DUMB_CLIENT | DEBUG_FLAG_REMOTE_DEBUG);
+ bool p_give_internet = p_flags.has_flag(DEBUG_FLAG_DUMB_CLIENT) || p_flags.has_flag(DEBUG_FLAG_REMOTE_DEBUG);
bool apk_expansion = p_preset->get("apk_expansion/enable");
Vector<ABI> enabled_abis = get_enabled_abis(p_preset);
@@ -3127,7 +3127,7 @@ Error EditorExportPlatformAndroid::export_project_helper(const Ref<EditorExportP
user_data.assets_directory = assets_directory;
user_data.libs_directory = gradle_build_directory.path_join("libs");
user_data.debug = p_debug;
- if (p_flags & DEBUG_FLAG_DUMB_CLIENT) {
+ if (p_flags.has_flag(DEBUG_FLAG_DUMB_CLIENT)) {
err = export_project_files(p_preset, p_debug, ignore_apk_file, &user_data, copy_gradle_so);
} else {
err = export_project_files(p_preset, p_debug, rename_and_store_file_in_gradle_project, &user_data, copy_gradle_so);
@@ -3500,7 +3500,7 @@ Error EditorExportPlatformAndroid::export_project_helper(const Ref<EditorExportP
}
err = OK;
- if (p_flags & DEBUG_FLAG_DUMB_CLIENT) {
+ if (p_flags.has_flag(DEBUG_FLAG_DUMB_CLIENT)) {
APKExportData ed;
ed.ep = &ep;
ed.apk = unaligned_apk;
diff --git a/platform/android/export/export_plugin.h b/platform/android/export/export_plugin.h
index 97bbd0c7bc..708288fbf4 100644
--- a/platform/android/export/export_plugin.h
+++ b/platform/android/export/export_plugin.h
@@ -214,7 +214,7 @@ public:
virtual String get_device_architecture(int p_index) const override;
- virtual Error run(const Ref<EditorExportPreset> &p_preset, int p_device, int p_debug_flags) override;
+ virtual Error run(const Ref<EditorExportPreset> &p_preset, int p_device, BitField<EditorExportPlatform::DebugFlags> p_debug_flags) override;
virtual Ref<Texture2D> get_run_icon() const override;
@@ -242,7 +242,7 @@ public:
Error save_apk_expansion_file(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path);
- void get_command_line_flags(const Ref<EditorExportPreset> &p_preset, const String &p_path, int p_flags, Vector<uint8_t> &r_command_line_flags);
+ void get_command_line_flags(const Ref<EditorExportPreset> &p_preset, const String &p_path, BitField<EditorExportPlatform::DebugFlags> p_flags, Vector<uint8_t> &r_command_line_flags);
Error sign_apk(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &export_path, EditorProgress &ep);
@@ -253,9 +253,9 @@ public:
static String join_list(const List<String> &p_parts, const String &p_separator);
static String join_abis(const Vector<ABI> &p_parts, const String &p_separator, bool p_use_arch);
- virtual Error export_project(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, int p_flags = 0) override;
+ virtual Error export_project(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, BitField<EditorExportPlatform::DebugFlags> p_flags = 0) override;
- Error export_project_helper(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, int export_format, bool should_sign, int p_flags);
+ Error export_project_helper(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, int export_format, bool should_sign, BitField<EditorExportPlatform::DebugFlags> p_flags);
virtual void get_platform_features(List<String> *r_features) const override;
diff --git a/platform/android/file_access_android.cpp b/platform/android/file_access_android.cpp
index ae336d6f9d..59b669eabb 100644
--- a/platform/android/file_access_android.cpp
+++ b/platform/android/file_access_android.cpp
@@ -113,87 +113,6 @@ bool FileAccessAndroid::eof_reached() const {
return eof;
}
-uint8_t FileAccessAndroid::get_8() const {
- if (pos >= len) {
- eof = true;
- return 0;
- }
-
- uint8_t byte;
- AAsset_read(asset, &byte, 1);
- pos++;
- return byte;
-}
-
-uint16_t FileAccessAndroid::get_16() const {
- if (pos >= len) {
- eof = true;
- return 0;
- }
-
- uint16_t bytes = 0;
- int r = AAsset_read(asset, &bytes, 2);
-
- if (r >= 0) {
- pos += r;
- if (pos >= len) {
- eof = true;
- }
- }
-
- if (big_endian) {
- bytes = BSWAP16(bytes);
- }
-
- return bytes;
-}
-
-uint32_t FileAccessAndroid::get_32() const {
- if (pos >= len) {
- eof = true;
- return 0;
- }
-
- uint32_t bytes = 0;
- int r = AAsset_read(asset, &bytes, 4);
-
- if (r >= 0) {
- pos += r;
- if (pos >= len) {
- eof = true;
- }
- }
-
- if (big_endian) {
- bytes = BSWAP32(bytes);
- }
-
- return bytes;
-}
-
-uint64_t FileAccessAndroid::get_64() const {
- if (pos >= len) {
- eof = true;
- return 0;
- }
-
- uint64_t bytes = 0;
- int r = AAsset_read(asset, &bytes, 8);
-
- if (r >= 0) {
- pos += r;
- if (pos >= len) {
- eof = true;
- }
- }
-
- if (big_endian) {
- bytes = BSWAP64(bytes);
- }
-
- return bytes;
-}
-
uint64_t FileAccessAndroid::get_buffer(uint8_t *p_dst, uint64_t p_length) const {
ERR_FAIL_COND_V(!p_dst && p_length > 0, -1);
@@ -209,6 +128,7 @@ uint64_t FileAccessAndroid::get_buffer(uint8_t *p_dst, uint64_t p_length) const
pos = len;
}
}
+
return r;
}
@@ -220,19 +140,7 @@ void FileAccessAndroid::flush() {
ERR_FAIL();
}
-void FileAccessAndroid::store_8(uint8_t p_dest) {
- ERR_FAIL();
-}
-
-void FileAccessAndroid::store_16(uint16_t p_dest) {
- ERR_FAIL();
-}
-
-void FileAccessAndroid::store_32(uint32_t p_dest) {
- ERR_FAIL();
-}
-
-void FileAccessAndroid::store_64(uint64_t p_dest) {
+void FileAccessAndroid::store_buffer(const uint8_t *p_src, uint64_t p_length) {
ERR_FAIL();
}
diff --git a/platform/android/file_access_android.h b/platform/android/file_access_android.h
index b465a92c78..3224ab50b9 100644
--- a/platform/android/file_access_android.h
+++ b/platform/android/file_access_android.h
@@ -68,19 +68,12 @@ public:
virtual bool eof_reached() const override; // reading passed EOF
virtual Error resize(int64_t p_length) override { return ERR_UNAVAILABLE; }
- virtual uint8_t get_8() const override; // get a byte
- virtual uint16_t get_16() const override;
- virtual uint32_t get_32() const override;
- virtual uint64_t get_64() const override;
virtual uint64_t get_buffer(uint8_t *p_dst, uint64_t p_length) const override;
virtual Error get_error() const override; // get last error
virtual void flush() override;
- virtual void store_8(uint8_t p_dest) override; // store a byte
- virtual void store_16(uint16_t p_dest) override;
- virtual void store_32(uint32_t p_dest) override;
- virtual void store_64(uint64_t p_dest) override;
+ virtual void store_buffer(const uint8_t *p_src, uint64_t p_length) override;
virtual bool file_exists(const String &p_path) override; // return true if a file exists
diff --git a/platform/android/file_access_filesystem_jandroid.cpp b/platform/android/file_access_filesystem_jandroid.cpp
index 9ae48dfb10..8b52a00ed8 100644
--- a/platform/android/file_access_filesystem_jandroid.cpp
+++ b/platform/android/file_access_filesystem_jandroid.cpp
@@ -169,43 +169,6 @@ void FileAccessFilesystemJAndroid::_set_eof(bool eof) {
}
}
-uint8_t FileAccessFilesystemJAndroid::get_8() const {
- ERR_FAIL_COND_V_MSG(!is_open(), 0, "File must be opened before use.");
- uint8_t byte;
- get_buffer(&byte, 1);
- return byte;
-}
-
-uint16_t FileAccessFilesystemJAndroid::get_16() const {
- ERR_FAIL_COND_V_MSG(!is_open(), 0, "File must be opened before use.");
- uint16_t bytes = 0;
- get_buffer(reinterpret_cast<uint8_t *>(&bytes), 2);
- if (big_endian) {
- bytes = BSWAP16(bytes);
- }
- return bytes;
-}
-
-uint32_t FileAccessFilesystemJAndroid::get_32() const {
- ERR_FAIL_COND_V_MSG(!is_open(), 0, "File must be opened before use.");
- uint32_t bytes = 0;
- get_buffer(reinterpret_cast<uint8_t *>(&bytes), 4);
- if (big_endian) {
- bytes = BSWAP32(bytes);
- }
- return bytes;
-}
-
-uint64_t FileAccessFilesystemJAndroid::get_64() const {
- ERR_FAIL_COND_V_MSG(!is_open(), 0, "File must be opened before use.");
- uint64_t bytes = 0;
- get_buffer(reinterpret_cast<uint8_t *>(&bytes), 8);
- if (big_endian) {
- bytes = BSWAP64(bytes);
- }
- return bytes;
-}
-
String FileAccessFilesystemJAndroid::get_line() const {
ERR_FAIL_COND_V_MSG(!is_open(), String(), "File must be opened before use.");
@@ -271,31 +234,6 @@ uint64_t FileAccessFilesystemJAndroid::get_buffer(uint8_t *p_dst, uint64_t p_len
}
}
-void FileAccessFilesystemJAndroid::store_8(uint8_t p_dest) {
- store_buffer(&p_dest, 1);
-}
-
-void FileAccessFilesystemJAndroid::store_16(uint16_t p_dest) {
- if (big_endian) {
- p_dest = BSWAP16(p_dest);
- }
- store_buffer(reinterpret_cast<uint8_t *>(&p_dest), 2);
-}
-
-void FileAccessFilesystemJAndroid::store_32(uint32_t p_dest) {
- if (big_endian) {
- p_dest = BSWAP32(p_dest);
- }
- store_buffer(reinterpret_cast<uint8_t *>(&p_dest), 4);
-}
-
-void FileAccessFilesystemJAndroid::store_64(uint64_t p_dest) {
- if (big_endian) {
- p_dest = BSWAP64(p_dest);
- }
- store_buffer(reinterpret_cast<uint8_t *>(&p_dest), 8);
-}
-
void FileAccessFilesystemJAndroid::store_buffer(const uint8_t *p_src, uint64_t p_length) {
if (_file_write) {
ERR_FAIL_COND_MSG(!is_open(), "File must be opened before use.");
diff --git a/platform/android/file_access_filesystem_jandroid.h b/platform/android/file_access_filesystem_jandroid.h
index 2795ac02ac..1345b72fa6 100644
--- a/platform/android/file_access_filesystem_jandroid.h
+++ b/platform/android/file_access_filesystem_jandroid.h
@@ -78,20 +78,12 @@ public:
virtual bool eof_reached() const override; ///< reading passed EOF
virtual Error resize(int64_t p_length) override;
- virtual uint8_t get_8() const override; ///< get a byte
- virtual uint16_t get_16() const override;
- virtual uint32_t get_32() const override;
- virtual uint64_t get_64() const override;
virtual String get_line() const override; ///< get a line
virtual uint64_t get_buffer(uint8_t *p_dst, uint64_t p_length) const override;
virtual Error get_error() const override; ///< get last error
virtual void flush() override;
- virtual void store_8(uint8_t p_dest) override; ///< store a byte
- virtual void store_16(uint16_t p_dest) override;
- virtual void store_32(uint32_t p_dest) override;
- virtual void store_64(uint64_t p_dest) override;
virtual void store_buffer(const uint8_t *p_src, uint64_t p_length) override;
virtual bool file_exists(const String &p_path) override; ///< return true if a file exists
diff --git a/platform/android/java/app/build.gradle b/platform/android/java/app/build.gradle
index 05b4f379b3..b9d15deec9 100644
--- a/platform/android/java/app/build.gradle
+++ b/platform/android/java/app/build.gradle
@@ -12,6 +12,7 @@ allprojects {
mavenCentral()
gradlePluginPortal()
maven { url "https://plugins.gradle.org/m2/" }
+ maven { url "https://s01.oss.sonatype.org/content/repositories/snapshots/"}
// Godot user plugins custom maven repos
String[] mavenRepos = getGodotPluginsMavenRepos()
diff --git a/platform/android/java/app/settings.gradle b/platform/android/java/app/settings.gradle
index dcac44e393..e758d4e99a 100644
--- a/platform/android/java/app/settings.gradle
+++ b/platform/android/java/app/settings.gradle
@@ -11,6 +11,7 @@ pluginManagement {
mavenCentral()
gradlePluginPortal()
maven { url "https://plugins.gradle.org/m2/" }
+ maven { url "https://s01.oss.sonatype.org/content/repositories/snapshots/"}
}
}
diff --git a/platform/android/java/build.gradle b/platform/android/java/build.gradle
index 771bda6948..974f072c18 100644
--- a/platform/android/java/build.gradle
+++ b/platform/android/java/build.gradle
@@ -18,12 +18,14 @@ allprojects {
mavenCentral()
gradlePluginPortal()
maven { url "https://plugins.gradle.org/m2/" }
+ maven { url "https://s01.oss.sonatype.org/content/repositories/snapshots/"}
}
}
ext {
supportedAbis = ["arm32", "arm64", "x86_32", "x86_64"]
supportedFlavors = ["editor", "template"]
+ supportedEditorVendors = ["google", "meta"]
supportedFlavorsBuildTypes = [
"editor": ["dev", "debug", "release"],
"template": ["dev", "debug", "release"]
@@ -92,15 +94,20 @@ def templateExcludedBuildTask() {
/**
* Generates the build tasks for the given flavor
* @param flavor Must be one of the supported flavors ('template' / 'editor')
+ * @param editorVendor Must be one of the supported editor vendors ('google' / 'meta')
*/
-def generateBuildTasks(String flavor = "template") {
+def generateBuildTasks(String flavor = "template", String editorVendor = "google") {
if (!supportedFlavors.contains(flavor)) {
throw new GradleException("Invalid build flavor: $flavor")
}
+ if (!supportedEditorVendors.contains(editorVendor)) {
+ throw new GradleException("Invalid editor vendor: $editorVendor")
+ }
+ String capitalizedEditorVendor = editorVendor.capitalize()
def buildTasks = []
- // Only build the apks and aar files for which we have native shared libraries unless we intend
+ // Only build the binary files for which we have native shared libraries unless we intend
// to run the scons build tasks.
boolean excludeSconsBuildTasks = excludeSconsBuildTasks()
boolean isTemplate = flavor == "template"
@@ -163,28 +170,28 @@ def generateBuildTasks(String flavor = "template") {
}
} else {
// Copy the generated editor apk to the bin directory.
- String copyEditorApkTaskName = "copyEditor${capitalizedTarget}ApkToBin"
+ String copyEditorApkTaskName = "copyEditor${capitalizedEditorVendor}${capitalizedTarget}ApkToBin"
if (tasks.findByName(copyEditorApkTaskName) != null) {
buildTasks += tasks.getByName(copyEditorApkTaskName)
} else {
buildTasks += tasks.create(name: copyEditorApkTaskName, type: Copy) {
- dependsOn ":editor:assemble${capitalizedTarget}"
- from("editor/build/outputs/apk/${target}")
+ dependsOn ":editor:assemble${capitalizedEditorVendor}${capitalizedTarget}"
+ from("editor/build/outputs/apk/${editorVendor}/${target}")
into(androidEditorBuildsDir)
- include("android_editor-${target}*.apk")
+ include("android_editor-${editorVendor}-${target}*.apk")
}
}
// Copy the generated editor aab to the bin directory.
- String copyEditorAabTaskName = "copyEditor${capitalizedTarget}AabToBin"
+ String copyEditorAabTaskName = "copyEditor${capitalizedEditorVendor}${capitalizedTarget}AabToBin"
if (tasks.findByName(copyEditorAabTaskName) != null) {
buildTasks += tasks.getByName(copyEditorAabTaskName)
} else {
buildTasks += tasks.create(name: copyEditorAabTaskName, type: Copy) {
- dependsOn ":editor:bundle${capitalizedTarget}"
- from("editor/build/outputs/bundle/${target}")
+ dependsOn ":editor:bundle${capitalizedEditorVendor}${capitalizedTarget}"
+ from("editor/build/outputs/bundle/${editorVendor}${capitalizedTarget}")
into(androidEditorBuildsDir)
- include("android_editor-${target}*.aab")
+ include("android_editor-${editorVendor}-${target}*.aab")
}
}
}
@@ -197,15 +204,27 @@ def generateBuildTasks(String flavor = "template") {
}
/**
- * Generate the Godot Editor Android apk.
+ * Generate the Godot Editor Android binaries.
*
* Note: Unless the 'generateNativeLibs` argument is specified, the Godot 'tools' shared libraries
* must have been generated (via scons) prior to running this gradle task.
- * The task will only build the apk(s) for which the shared libraries is available.
+ * The task will only build the binaries for which the shared libraries is available.
*/
task generateGodotEditor {
gradle.startParameter.excludedTaskNames += templateExcludedBuildTask()
- dependsOn = generateBuildTasks("editor")
+ dependsOn = generateBuildTasks("editor", "google")
+}
+
+/**
+ * Generate the Godot Editor Android binaries for Meta devices.
+ *
+ * Note: Unless the 'generateNativeLibs` argument is specified, the Godot 'tools' shared libraries
+ * must have been generated (via scons) prior to running this gradle task.
+ * The task will only build the binaries for which the shared libraries is available.
+ */
+task generateGodotMetaEditor {
+ gradle.startParameter.excludedTaskNames += templateExcludedBuildTask()
+ dependsOn = generateBuildTasks("editor", "meta")
}
/**
diff --git a/platform/android/java/editor/build.gradle b/platform/android/java/editor/build.gradle
index f9a3e10680..54d6b9b5f3 100644
--- a/platform/android/java/editor/build.gradle
+++ b/platform/android/java/editor/build.gradle
@@ -5,16 +5,6 @@ plugins {
id 'base'
}
-dependencies {
- implementation "androidx.fragment:fragment:$versions.fragmentVersion"
- implementation project(":lib")
-
- implementation "androidx.window:window:1.3.0"
- implementation "androidx.core:core-splashscreen:$versions.splashscreenVersion"
- implementation "androidx.constraintlayout:constraintlayout:2.1.4"
- implementation "org.bouncycastle:bcprov-jdk15to18:1.77"
-}
-
ext {
// Retrieve the build number from the environment variable; default to 0 if none is specified.
// The build number is added as a suffix to the version code for upload to the Google Play store.
@@ -37,7 +27,7 @@ ext {
// Return the keystore file used for signing the release build.
getGodotKeystoreFile = { ->
def keyStore = System.getenv("GODOT_ANDROID_SIGN_KEYSTORE")
- if (keyStore == null) {
+ if (keyStore == null || keyStore.isEmpty()) {
return null
}
return file(keyStore)
@@ -154,4 +144,37 @@ android {
doNotStrip '**/*.so'
}
}
+
+ flavorDimensions = ["vendor"]
+ productFlavors {
+ google {
+ dimension "vendor"
+ missingDimensionStrategy 'products', 'editor'
+ }
+ meta {
+ dimension "vendor"
+ missingDimensionStrategy 'products', 'editor'
+ ndk {
+ //noinspection ChromeOsAbiSupport
+ abiFilters "arm64-v8a"
+ }
+ applicationIdSuffix ".meta"
+ versionNameSuffix "-meta"
+ minSdkVersion 23
+ targetSdkVersion 32
+ }
+ }
+}
+
+dependencies {
+ implementation "androidx.fragment:fragment:$versions.fragmentVersion"
+ implementation project(":lib")
+
+ implementation "androidx.window:window:1.3.0"
+ implementation "androidx.core:core-splashscreen:$versions.splashscreenVersion"
+ implementation "androidx.constraintlayout:constraintlayout:2.1.4"
+ implementation "org.bouncycastle:bcprov-jdk15to18:1.77"
+
+ // Meta dependencies
+ metaImplementation "org.godotengine:godot-openxr-vendors-meta:3.0.0-stable"
}
diff --git a/platform/android/java/editor/src/google/java/org/godotengine/editor/GodotEditor.kt b/platform/android/java/editor/src/google/java/org/godotengine/editor/GodotEditor.kt
new file mode 100644
index 0000000000..f15d9f7768
--- /dev/null
+++ b/platform/android/java/editor/src/google/java/org/godotengine/editor/GodotEditor.kt
@@ -0,0 +1,39 @@
+/**************************************************************************/
+/* GodotEditor.kt */
+/**************************************************************************/
+/* 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. */
+/**************************************************************************/
+
+package org.godotengine.editor
+
+/**
+ * Primary window of the Godot Editor.
+ *
+ * This is the implementation of the editor used when running on regular Android devices.
+ */
+open class GodotEditor : BaseGodotEditor() {
+}
diff --git a/platform/android/java/editor/src/main/AndroidManifest.xml b/platform/android/java/editor/src/main/AndroidManifest.xml
index c7d14a3f49..a875745860 100644
--- a/platform/android/java/editor/src/main/AndroidManifest.xml
+++ b/platform/android/java/editor/src/main/AndroidManifest.xml
@@ -42,6 +42,7 @@
android:name=".GodotEditor"
android:configChanges="orientation|keyboardHidden|screenSize|smallestScreenSize|density|keyboard|navigation|screenLayout|uiMode"
android:exported="true"
+ android:icon="@mipmap/icon"
android:launchMode="singleTask"
android:screenOrientation="userLandscape">
<layout
@@ -59,9 +60,11 @@
android:name=".GodotGame"
android:configChanges="orientation|keyboardHidden|screenSize|smallestScreenSize|density|keyboard|navigation|screenLayout|uiMode"
android:exported="false"
- android:label="@string/godot_project_name_string"
+ android:icon="@mipmap/ic_play_window"
+ android:label="@string/godot_game_activity_name"
android:launchMode="singleTask"
android:process=":GodotGame"
+ android:supportsPictureInPicture="true"
android:screenOrientation="userLandscape">
<layout
android:defaultWidth="@dimen/editor_default_window_width"
diff --git a/platform/android/java/editor/src/main/java/org/godotengine/editor/GodotEditor.kt b/platform/android/java/editor/src/main/java/org/godotengine/editor/BaseGodotEditor.kt
index 9cc133046b..50daf44d2a 100644
--- a/platform/android/java/editor/src/main/java/org/godotengine/editor/GodotEditor.kt
+++ b/platform/android/java/editor/src/main/java/org/godotengine/editor/BaseGodotEditor.kt
@@ -1,5 +1,5 @@
/**************************************************************************/
-/* GodotEditor.kt */
+/* BaseGodotEditor.kt */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
@@ -32,6 +32,7 @@ package org.godotengine.editor
import android.Manifest
import android.app.ActivityManager
+import android.app.ActivityOptions
import android.content.ComponentName
import android.content.Context
import android.content.Intent
@@ -39,6 +40,7 @@ import android.content.pm.PackageManager
import android.os.*
import android.util.Log
import android.view.View
+import android.view.WindowManager
import android.widget.Toast
import androidx.annotation.CallSuper
import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
@@ -50,6 +52,8 @@ import org.godotengine.godot.GodotLib
import org.godotengine.godot.error.Error
import org.godotengine.godot.utils.PermissionsUtil
import org.godotengine.godot.utils.ProcessPhoenix
+import org.godotengine.godot.utils.isHorizonOSDevice
+import org.godotengine.godot.utils.isNativeXRDevice
import java.util.*
import kotlin.math.min
@@ -59,27 +63,35 @@ import kotlin.math.min
* This provides the basic templates for the activities making up this application.
* Each derived activity runs in its own process, which enable up to have several instances of
* the Godot engine up and running at the same time.
- *
- * It also plays the role of the primary editor window.
*/
-open class GodotEditor : GodotActivity() {
+abstract class BaseGodotEditor : GodotActivity() {
companion object {
- private val TAG = GodotEditor::class.java.simpleName
+ private val TAG = BaseGodotEditor::class.java.simpleName
private const val WAIT_FOR_DEBUGGER = false
- private const val EXTRA_COMMAND_LINE_PARAMS = "command_line_params"
+ @JvmStatic
+ protected val EXTRA_COMMAND_LINE_PARAMS = "command_line_params"
+ @JvmStatic
+ protected val EXTRA_PIP_AVAILABLE = "pip_available"
+ @JvmStatic
+ protected val EXTRA_LAUNCH_IN_PIP = "launch_in_pip_requested"
// Command line arguments
- private const val EDITOR_ARG = "--editor"
- private const val EDITOR_ARG_SHORT = "-e"
- private const val EDITOR_PROJECT_MANAGER_ARG = "--project-manager"
- private const val EDITOR_PROJECT_MANAGER_ARG_SHORT = "-p"
+ private const val FULLSCREEN_ARG = "--fullscreen"
+ private const val FULLSCREEN_ARG_SHORT = "-f"
+ internal const val EDITOR_ARG = "--editor"
+ internal const val EDITOR_ARG_SHORT = "-e"
+ internal const val EDITOR_PROJECT_MANAGER_ARG = "--project-manager"
+ internal const val EDITOR_PROJECT_MANAGER_ARG_SHORT = "-p"
+ internal const val BREAKPOINTS_ARG = "--breakpoints"
+ internal const val BREAKPOINTS_ARG_SHORT = "-b"
+ internal const val XR_MODE_ARG = "--xr-mode"
// Info for the various classes used by the editor
internal val EDITOR_MAIN_INFO = EditorWindowInfo(GodotEditor::class.java, 777, "")
- internal val RUN_GAME_INFO = EditorWindowInfo(GodotGame::class.java, 667, ":GodotGame", LaunchAdjacentPolicy.AUTO)
+ internal val RUN_GAME_INFO = EditorWindowInfo(GodotGame::class.java, 667, ":GodotGame", LaunchPolicy.AUTO, true)
/**
* Sets of constants to specify the window to use to run the project.
@@ -90,24 +102,59 @@ open class GodotEditor : GodotActivity() {
private const val ANDROID_WINDOW_AUTO = 0
private const val ANDROID_WINDOW_SAME_AS_EDITOR = 1
private const val ANDROID_WINDOW_SIDE_BY_SIDE_WITH_EDITOR = 2
+ private const val ANDROID_WINDOW_SAME_AS_EDITOR_AND_LAUNCH_IN_PIP_MODE = 3
+
+ /**
+ * Sets of constants to specify the Play window PiP mode.
+ *
+ * Should match the values in `editor/editor_settings.cpp'` for the
+ * 'run/window_placement/play_window_pip_mode' setting.
+ */
+ private const val PLAY_WINDOW_PIP_DISABLED = 0
+ private const val PLAY_WINDOW_PIP_ENABLED = 1
+ private const val PLAY_WINDOW_PIP_ENABLED_FOR_SAME_AS_EDITOR = 2
}
+ private val editorMessageDispatcher = EditorMessageDispatcher(this)
private val commandLineParams = ArrayList<String>()
private val editorLoadingIndicator: View? by lazy { findViewById(R.id.editor_loading_indicator) }
override fun getGodotAppLayout() = R.layout.godot_editor_layout
+ internal open fun getEditorWindowInfo() = EDITOR_MAIN_INFO
+
+ /**
+ * Set of permissions to be excluded when requesting all permissions at startup.
+ *
+ * The permissions in this set will be requested on demand based on use cases.
+ */
+ @CallSuper
+ protected open fun getExcludedPermissions(): MutableSet<String> {
+ return mutableSetOf(
+ // The RECORD_AUDIO permission is requested when the "audio/driver/enable_input" project
+ // setting is enabled.
+ Manifest.permission.RECORD_AUDIO
+ )
+ }
+
override fun onCreate(savedInstanceState: Bundle?) {
installSplashScreen()
+ // Prevent the editor window from showing in the display cutout
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P && getEditorWindowInfo() == EDITOR_MAIN_INFO) {
+ window.attributes.layoutInDisplayCutoutMode = WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER
+ }
+
// We exclude certain permissions from the set we request at startup, as they'll be
- // requested on demand based on use-cases.
- PermissionsUtil.requestManifestPermissions(this, setOf(Manifest.permission.RECORD_AUDIO))
+ // requested on demand based on use cases.
+ PermissionsUtil.requestManifestPermissions(this, getExcludedPermissions())
val params = intent.getStringArrayExtra(EXTRA_COMMAND_LINE_PARAMS)
Log.d(TAG, "Starting intent $intent with parameters ${params.contentToString()}")
updateCommandLineParams(params?.asList() ?: emptyList())
+ editorMessageDispatcher.parseStartIntent(packageManager, intent)
+
if (BuildConfig.BUILD_TYPE == "dev" && WAIT_FOR_DEBUGGER) {
Debug.waitForDebugger()
}
@@ -120,8 +167,6 @@ open class GodotEditor : GodotActivity() {
val longPressEnabled = enableLongPressGestures()
val panScaleEnabled = enablePanAndScaleGestures()
- checkForProjectPermissionsToEnable()
-
runOnUiThread {
// Enable long press, panning and scaling gestures
godotFragment?.godot?.renderView?.inputHandler?.apply {
@@ -139,17 +184,6 @@ open class GodotEditor : GodotActivity() {
}
}
- /**
- * Check for project permissions to enable
- */
- protected open fun checkForProjectPermissionsToEnable() {
- // Check for RECORD_AUDIO permission
- val audioInputEnabled = java.lang.Boolean.parseBoolean(GodotLib.getGlobal("audio/driver/enable_input"))
- if (audioInputEnabled) {
- PermissionsUtil.requestPermission(Manifest.permission.RECORD_AUDIO, this)
- }
- }
-
@CallSuper
protected open fun updateCommandLineParams(args: List<String>) {
// Update the list of command line params with the new args
@@ -164,7 +198,7 @@ open class GodotEditor : GodotActivity() {
final override fun getCommandLine() = commandLineParams
- protected open fun getEditorWindowInfo(args: Array<String>): EditorWindowInfo {
+ protected open fun retrieveEditorWindowInfo(args: Array<String>): EditorWindowInfo {
var hasEditor = false
var i = 0
@@ -189,35 +223,86 @@ open class GodotEditor : GodotActivity() {
}
}
- override fun onNewGodotInstanceRequested(args: Array<String>): Int {
- val editorWindowInfo = getEditorWindowInfo(args)
+ protected fun getNewGodotInstanceIntent(editorWindowInfo: EditorWindowInfo, args: Array<String>): Intent {
+ val updatedArgs = if (editorWindowInfo == EDITOR_MAIN_INFO &&
+ godot?.isInImmersiveMode() == true &&
+ !args.contains(FULLSCREEN_ARG) &&
+ !args.contains(FULLSCREEN_ARG_SHORT)
+ ) {
+ // If we're launching an editor window (project manager or editor) and we're in
+ // fullscreen mode, we want to remain in fullscreen mode.
+ // This doesn't apply to the play / game window since for that window fullscreen is
+ // controlled by the game logic.
+ args + FULLSCREEN_ARG
+ } else {
+ args
+ }
- // Launch a new activity
val newInstance = Intent()
.setComponent(ComponentName(this, editorWindowInfo.windowClassName))
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
- .putExtra(EXTRA_COMMAND_LINE_PARAMS, args)
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
- if (editorWindowInfo.launchAdjacentPolicy == LaunchAdjacentPolicy.ENABLED ||
- (editorWindowInfo.launchAdjacentPolicy == LaunchAdjacentPolicy.AUTO && shouldGameLaunchAdjacent())) {
+ .putExtra(EXTRA_COMMAND_LINE_PARAMS, updatedArgs)
+
+ val launchPolicy = resolveLaunchPolicyIfNeeded(editorWindowInfo.launchPolicy)
+ val isPiPAvailable = if (editorWindowInfo.supportsPiPMode && hasPiPSystemFeature()) {
+ val pipMode = getPlayWindowPiPMode()
+ pipMode == PLAY_WINDOW_PIP_ENABLED ||
+ (pipMode == PLAY_WINDOW_PIP_ENABLED_FOR_SAME_AS_EDITOR &&
+ (launchPolicy == LaunchPolicy.SAME || launchPolicy == LaunchPolicy.SAME_AND_LAUNCH_IN_PIP_MODE))
+ } else {
+ false
+ }
+ newInstance.putExtra(EXTRA_PIP_AVAILABLE, isPiPAvailable)
+
+ var launchInPiP = false
+ if (launchPolicy == LaunchPolicy.ADJACENT) {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
Log.v(TAG, "Adding flag for adjacent launch")
newInstance.addFlags(Intent.FLAG_ACTIVITY_LAUNCH_ADJACENT)
}
+ } else if (launchPolicy == LaunchPolicy.SAME) {
+ launchInPiP = isPiPAvailable &&
+ (updatedArgs.contains(BREAKPOINTS_ARG) || updatedArgs.contains(BREAKPOINTS_ARG_SHORT))
+ } else if (launchPolicy == LaunchPolicy.SAME_AND_LAUNCH_IN_PIP_MODE) {
+ launchInPiP = isPiPAvailable
+ }
+
+ if (launchInPiP) {
+ Log.v(TAG, "Launching in PiP mode")
+ newInstance.putExtra(EXTRA_LAUNCH_IN_PIP, launchInPiP)
+ }
+ return newInstance
+ }
+
+ override fun onNewGodotInstanceRequested(args: Array<String>): Int {
+ val editorWindowInfo = retrieveEditorWindowInfo(args)
+
+ // Launch a new activity
+ val sourceView = godotFragment?.view
+ val activityOptions = if (sourceView == null) {
+ null
+ } else {
+ val startX = sourceView.width / 2
+ val startY = sourceView.height / 2
+ ActivityOptions.makeScaleUpAnimation(sourceView, startX, startY, 0, 0)
}
+
+ val newInstance = getNewGodotInstanceIntent(editorWindowInfo, args)
if (editorWindowInfo.windowClassName == javaClass.name) {
Log.d(TAG, "Restarting ${editorWindowInfo.windowClassName} with parameters ${args.contentToString()}")
val godot = godot
if (godot != null) {
godot.destroyAndKillProcess {
- ProcessPhoenix.triggerRebirth(this, newInstance)
+ ProcessPhoenix.triggerRebirth(this, activityOptions?.toBundle(), newInstance)
}
} else {
- ProcessPhoenix.triggerRebirth(this, newInstance)
+ ProcessPhoenix.triggerRebirth(this, activityOptions?.toBundle(), newInstance)
}
} else {
Log.d(TAG, "Starting ${editorWindowInfo.windowClassName} with parameters ${args.contentToString()}")
newInstance.putExtra(EXTRA_NEW_LAUNCH, true)
- startActivity(newInstance)
+ .putExtra(EditorMessageDispatcher.EXTRA_MSG_DISPATCHER_PAYLOAD, editorMessageDispatcher.getMessageDispatcherPayload())
+ startActivity(newInstance, activityOptions?.toBundle())
}
return editorWindowInfo.windowId
}
@@ -231,6 +316,12 @@ open class GodotEditor : GodotActivity() {
return true
}
+ // Send an inter-process message to request the target editor window to force quit.
+ if (editorMessageDispatcher.requestForceQuit(editorWindowInfo.windowId)) {
+ return true
+ }
+
+ // Fallback to killing the target process.
val processName = packageName + editorWindowInfo.processNameSuffix
val activityManager = getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager
val runningProcesses = activityManager.runningAppProcesses
@@ -285,29 +376,80 @@ open class GodotEditor : GodotActivity() {
java.lang.Boolean.parseBoolean(GodotLib.getEditorSetting("interface/touchscreen/enable_pan_and_scale_gestures"))
/**
- * Whether we should launch the new godot instance in an adjacent window
- * @see https://developer.android.com/reference/android/content/Intent#FLAG_ACTIVITY_LAUNCH_ADJACENT
+ * Retrieves the play window pip mode editor setting.
+ */
+ private fun getPlayWindowPiPMode(): Int {
+ return try {
+ Integer.parseInt(GodotLib.getEditorSetting("run/window_placement/play_window_pip_mode"))
+ } catch (e: NumberFormatException) {
+ PLAY_WINDOW_PIP_ENABLED_FOR_SAME_AS_EDITOR
+ }
+ }
+
+ /**
+ * If the launch policy is [LaunchPolicy.AUTO], resolve it into a specific policy based on the
+ * editor setting or device and screen metrics.
+ *
+ * If the launch policy is [LaunchPolicy.PIP] but PIP is not supported, fallback to the default
+ * launch policy.
*/
- private fun shouldGameLaunchAdjacent(): Boolean {
- return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
- try {
- when (Integer.parseInt(GodotLib.getEditorSetting("run/window_placement/android_window"))) {
- ANDROID_WINDOW_SAME_AS_EDITOR -> false
- ANDROID_WINDOW_SIDE_BY_SIDE_WITH_EDITOR -> true
- else -> {
- // ANDROID_WINDOW_AUTO
- isInMultiWindowMode || isLargeScreen
+ private fun resolveLaunchPolicyIfNeeded(policy: LaunchPolicy): LaunchPolicy {
+ val inMultiWindowMode = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
+ isInMultiWindowMode
+ } else {
+ false
+ }
+ val defaultLaunchPolicy = if (inMultiWindowMode || isLargeScreen) {
+ LaunchPolicy.ADJACENT
+ } else {
+ LaunchPolicy.SAME
+ }
+
+ return when (policy) {
+ LaunchPolicy.AUTO -> {
+ if (isHorizonOSDevice()) {
+ // Horizon OS UX is more desktop-like and has support for launching adjacent
+ // windows. So we always want to launch in adjacent mode when auto is selected.
+ LaunchPolicy.ADJACENT
+ } else {
+ try {
+ when (Integer.parseInt(GodotLib.getEditorSetting("run/window_placement/android_window"))) {
+ ANDROID_WINDOW_SAME_AS_EDITOR -> LaunchPolicy.SAME
+ ANDROID_WINDOW_SIDE_BY_SIDE_WITH_EDITOR -> LaunchPolicy.ADJACENT
+ ANDROID_WINDOW_SAME_AS_EDITOR_AND_LAUNCH_IN_PIP_MODE -> LaunchPolicy.SAME_AND_LAUNCH_IN_PIP_MODE
+ else -> {
+ // ANDROID_WINDOW_AUTO
+ defaultLaunchPolicy
+ }
+ }
+ } catch (e: NumberFormatException) {
+ Log.w(TAG, "Error parsing the Android window placement editor setting", e)
+ // Fall-back to the default launch policy
+ defaultLaunchPolicy
}
}
- } catch (e: NumberFormatException) {
- // Fall-back to the 'Auto' behavior
- isInMultiWindowMode || isLargeScreen
}
- } else {
- false
+
+ else -> {
+ policy
+ }
}
}
+ /**
+ * Returns true the if the device supports picture-in-picture (PiP)
+ */
+ protected open fun hasPiPSystemFeature(): Boolean {
+ if (isNativeXRDevice()) {
+ // Known native XR devices do not support PiP.
+ // Will need to revisit as they update their OS.
+ return false
+ }
+
+ return Build.VERSION.SDK_INT >= Build.VERSION_CODES.N &&
+ packageManager.hasSystemFeature(PackageManager.FEATURE_PICTURE_IN_PICTURE)
+ }
+
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
// Check if we got the MANAGE_EXTERNAL_STORAGE permission
diff --git a/platform/android/java/editor/src/main/java/org/godotengine/editor/EditorMessageDispatcher.kt b/platform/android/java/editor/src/main/java/org/godotengine/editor/EditorMessageDispatcher.kt
new file mode 100644
index 0000000000..f5a6ed7dab
--- /dev/null
+++ b/platform/android/java/editor/src/main/java/org/godotengine/editor/EditorMessageDispatcher.kt
@@ -0,0 +1,196 @@
+/**************************************************************************/
+/* EditorMessageDispatcher.kt */
+/**************************************************************************/
+/* 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. */
+/**************************************************************************/
+
+package org.godotengine.editor
+
+import android.annotation.SuppressLint
+import android.content.Intent
+import android.content.pm.PackageManager
+import android.os.Bundle
+import android.os.Handler
+import android.os.Message
+import android.os.Messenger
+import android.os.RemoteException
+import android.util.Log
+import java.util.concurrent.ConcurrentHashMap
+
+/**
+ * Used by the [BaseGodotEditor] classes to dispatch messages across processes.
+ */
+internal class EditorMessageDispatcher(private val editor: BaseGodotEditor) {
+
+ companion object {
+ private val TAG = EditorMessageDispatcher::class.java.simpleName
+
+ /**
+ * Extra used to pass the message dispatcher payload through an [Intent]
+ */
+ const val EXTRA_MSG_DISPATCHER_PAYLOAD = "message_dispatcher_payload"
+
+ /**
+ * Key used to pass the editor id through a [Bundle]
+ */
+ private const val KEY_EDITOR_ID = "editor_id"
+
+ /**
+ * Key used to pass the editor messenger through a [Bundle]
+ */
+ private const val KEY_EDITOR_MESSENGER = "editor_messenger"
+
+ /**
+ * Requests the recipient to quit right away.
+ */
+ private const val MSG_FORCE_QUIT = 0
+
+ /**
+ * Requests the recipient to store the passed [android.os.Messenger] instance.
+ */
+ private const val MSG_REGISTER_MESSENGER = 1
+ }
+
+ private val recipientsMessengers = ConcurrentHashMap<Int, Messenger>()
+
+ @SuppressLint("HandlerLeak")
+ private val dispatcherHandler = object : Handler() {
+ override fun handleMessage(msg: Message) {
+ when (msg.what) {
+ MSG_FORCE_QUIT -> editor.finish()
+
+ MSG_REGISTER_MESSENGER -> {
+ val editorId = msg.arg1
+ val messenger = msg.replyTo
+ registerMessenger(editorId, messenger)
+ }
+
+ else -> super.handleMessage(msg)
+ }
+ }
+ }
+
+ /**
+ * Request the window with the given [editorId] to force quit.
+ */
+ fun requestForceQuit(editorId: Int): Boolean {
+ val messenger = recipientsMessengers[editorId] ?: return false
+ return try {
+ Log.v(TAG, "Requesting 'forceQuit' for $editorId")
+ val msg = Message.obtain(null, MSG_FORCE_QUIT)
+ messenger.send(msg)
+ true
+ } catch (e: RemoteException) {
+ Log.e(TAG, "Error requesting 'forceQuit' to $editorId", e)
+ recipientsMessengers.remove(editorId)
+ false
+ }
+ }
+
+ /**
+ * Utility method to register a receiver messenger.
+ */
+ private fun registerMessenger(editorId: Int, messenger: Messenger?, messengerDeathCallback: Runnable? = null) {
+ try {
+ if (messenger == null) {
+ Log.w(TAG, "Invalid 'replyTo' payload")
+ } else if (messenger.binder.isBinderAlive) {
+ messenger.binder.linkToDeath({
+ Log.v(TAG, "Removing messenger for $editorId")
+ recipientsMessengers.remove(editorId)
+ messengerDeathCallback?.run()
+ }, 0)
+ recipientsMessengers[editorId] = messenger
+ }
+ } catch (e: RemoteException) {
+ Log.e(TAG, "Unable to register messenger from $editorId", e)
+ recipientsMessengers.remove(editorId)
+ }
+ }
+
+ /**
+ * Utility method to register a [Messenger] attached to this handler with a host.
+ *
+ * This is done so that the host can send request to the editor instance attached to this handle.
+ *
+ * Note that this is only done when the editor instance is internal (not exported) to prevent
+ * arbitrary apps from having the ability to send requests.
+ */
+ private fun registerSelfTo(pm: PackageManager, host: Messenger?, selfId: Int) {
+ try {
+ if (host == null || !host.binder.isBinderAlive) {
+ Log.v(TAG, "Host is unavailable")
+ return
+ }
+
+ val activityInfo = pm.getActivityInfo(editor.componentName, 0)
+ if (activityInfo.exported) {
+ Log.v(TAG, "Not registering self to host as we're exported")
+ return
+ }
+
+ Log.v(TAG, "Registering self $selfId to host")
+ val msg = Message.obtain(null, MSG_REGISTER_MESSENGER)
+ msg.arg1 = selfId
+ msg.replyTo = Messenger(dispatcherHandler)
+ host.send(msg)
+ } catch (e: RemoteException) {
+ Log.e(TAG, "Unable to register self with host", e)
+ }
+ }
+
+ /**
+ * Parses the starting intent and retrieve an editor messenger if available
+ */
+ fun parseStartIntent(pm: PackageManager, intent: Intent) {
+ val messengerBundle = intent.getBundleExtra(EXTRA_MSG_DISPATCHER_PAYLOAD) ?: return
+
+ // Retrieve the sender messenger payload and store it. This can be used to communicate back
+ // to the sender.
+ val senderId = messengerBundle.getInt(KEY_EDITOR_ID)
+ val senderMessenger: Messenger? = messengerBundle.getParcelable(KEY_EDITOR_MESSENGER)
+ registerMessenger(senderId, senderMessenger) {
+ // Terminate current instance when parent is no longer available.
+ Log.d(TAG, "Terminating current editor instance because parent is no longer available")
+ editor.finish()
+ }
+
+ // Register ourselves to the sender so that it can communicate with us.
+ registerSelfTo(pm, senderMessenger, editor.getEditorWindowInfo().windowId)
+ }
+
+ /**
+ * Returns the payload used by the [EditorMessageDispatcher] class to establish an IPC bridge
+ * across editor instances.
+ */
+ fun getMessageDispatcherPayload(): Bundle {
+ return Bundle().apply {
+ putInt(KEY_EDITOR_ID, editor.getEditorWindowInfo().windowId)
+ putParcelable(KEY_EDITOR_MESSENGER, Messenger(dispatcherHandler))
+ }
+ }
+}
diff --git a/platform/android/java/editor/src/main/java/org/godotengine/editor/EditorWindowInfo.kt b/platform/android/java/editor/src/main/java/org/godotengine/editor/EditorWindowInfo.kt
index 0da1d01aed..2e1de9a607 100644
--- a/platform/android/java/editor/src/main/java/org/godotengine/editor/EditorWindowInfo.kt
+++ b/platform/android/java/editor/src/main/java/org/godotengine/editor/EditorWindowInfo.kt
@@ -31,23 +31,29 @@
package org.godotengine.editor
/**
- * Specifies the policy for adjacent launches.
+ * Specifies the policy for launches.
*/
-enum class LaunchAdjacentPolicy {
+enum class LaunchPolicy {
/**
- * Adjacent launches are disabled.
+ * Launch policy is determined by the editor settings or based on the device and screen metrics.
*/
- DISABLED,
+ AUTO,
+
/**
- * Adjacent launches are enabled / disabled based on the device and screen metrics.
+ * Launches happen in the same window.
*/
- AUTO,
+ SAME,
/**
* Adjacent launches are enabled.
*/
- ENABLED
+ ADJACENT,
+
+ /**
+ * Launches happen in the same window but start in PiP mode.
+ */
+ SAME_AND_LAUNCH_IN_PIP_MODE
}
/**
@@ -57,12 +63,14 @@ data class EditorWindowInfo(
val windowClassName: String,
val windowId: Int,
val processNameSuffix: String,
- val launchAdjacentPolicy: LaunchAdjacentPolicy = LaunchAdjacentPolicy.DISABLED
+ val launchPolicy: LaunchPolicy = LaunchPolicy.SAME,
+ val supportsPiPMode: Boolean = false
) {
constructor(
windowClass: Class<*>,
windowId: Int,
processNameSuffix: String,
- launchAdjacentPolicy: LaunchAdjacentPolicy = LaunchAdjacentPolicy.DISABLED
- ) : this(windowClass.name, windowId, processNameSuffix, launchAdjacentPolicy)
+ launchPolicy: LaunchPolicy = LaunchPolicy.SAME,
+ supportsPiPMode: Boolean = false
+ ) : this(windowClass.name, windowId, processNameSuffix, launchPolicy, supportsPiPMode)
}
diff --git a/platform/android/java/editor/src/main/java/org/godotengine/editor/GodotGame.kt b/platform/android/java/editor/src/main/java/org/godotengine/editor/GodotGame.kt
index 2bcfba559c..e52d566347 100644
--- a/platform/android/java/editor/src/main/java/org/godotengine/editor/GodotGame.kt
+++ b/platform/android/java/editor/src/main/java/org/godotengine/editor/GodotGame.kt
@@ -30,14 +30,109 @@
package org.godotengine.editor
+import android.Manifest
+import android.annotation.SuppressLint
+import android.app.PictureInPictureParams
+import android.content.Intent
+import android.graphics.Rect
+import android.os.Build
+import android.os.Bundle
+import android.util.Log
+import android.view.View
+import androidx.annotation.CallSuper
import org.godotengine.godot.GodotLib
+import org.godotengine.godot.utils.PermissionsUtil
+import org.godotengine.godot.utils.ProcessPhoenix
/**
* Drives the 'run project' window of the Godot Editor.
*/
-class GodotGame : GodotEditor() {
+open class GodotGame : GodotEditor() {
- override fun getGodotAppLayout() = org.godotengine.godot.R.layout.godot_app_layout
+ companion object {
+ private val TAG = GodotGame::class.java.simpleName
+ }
+
+ private val gameViewSourceRectHint = Rect()
+ private val pipButton: View? by lazy {
+ findViewById(R.id.godot_pip_button)
+ }
+
+ private var pipAvailable = false
+
+ @SuppressLint("ClickableViewAccessibility")
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+ val gameView = findViewById<View>(R.id.godot_fragment_container)
+ gameView?.addOnLayoutChangeListener { v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom ->
+ gameView.getGlobalVisibleRect(gameViewSourceRectHint)
+ }
+ }
+
+ pipButton?.setOnClickListener { enterPiPMode() }
+
+ handleStartIntent(intent)
+ }
+
+ override fun onNewIntent(newIntent: Intent) {
+ super.onNewIntent(newIntent)
+ handleStartIntent(newIntent)
+ }
+
+ private fun handleStartIntent(intent: Intent) {
+ pipAvailable = intent.getBooleanExtra(EXTRA_PIP_AVAILABLE, pipAvailable)
+ updatePiPButtonVisibility()
+
+ val pipLaunchRequested = intent.getBooleanExtra(EXTRA_LAUNCH_IN_PIP, false)
+ if (pipLaunchRequested) {
+ enterPiPMode()
+ }
+ }
+
+ private fun updatePiPButtonVisibility() {
+ pipButton?.visibility = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N && pipAvailable && !isInPictureInPictureMode) {
+ View.VISIBLE
+ } else {
+ View.GONE
+ }
+ }
+
+ private fun enterPiPMode() {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N && pipAvailable) {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+ val builder = PictureInPictureParams.Builder().setSourceRectHint(gameViewSourceRectHint)
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
+ builder.setSeamlessResizeEnabled(false)
+ }
+ setPictureInPictureParams(builder.build())
+ }
+
+ Log.v(TAG, "Entering PiP mode")
+ enterPictureInPictureMode()
+ }
+ }
+
+ override fun onPictureInPictureModeChanged(isInPictureInPictureMode: Boolean) {
+ super.onPictureInPictureModeChanged(isInPictureInPictureMode)
+ Log.v(TAG, "onPictureInPictureModeChanged: $isInPictureInPictureMode")
+ updatePiPButtonVisibility()
+ }
+
+ override fun onStop() {
+ super.onStop()
+
+ val isInPiPMode = Build.VERSION.SDK_INT >= Build.VERSION_CODES.N && isInPictureInPictureMode
+ if (isInPiPMode && !isFinishing) {
+ // We get in this state when PiP is closed, so we terminate the activity.
+ finish()
+ }
+ }
+
+ override fun getGodotAppLayout() = R.layout.godot_game_layout
+
+ override fun getEditorWindowInfo() = RUN_GAME_INFO
override fun overrideOrientationRequest() = false
@@ -45,8 +140,53 @@ class GodotGame : GodotEditor() {
override fun enablePanAndScaleGestures() = java.lang.Boolean.parseBoolean(GodotLib.getGlobal("input_devices/pointing/android/enable_pan_and_scale_gestures"))
- override fun checkForProjectPermissionsToEnable() {
- // Nothing to do.. by the time we get here, the project permissions will have already
- // been requested by the Editor window.
+ override fun onGodotSetupCompleted() {
+ super.onGodotSetupCompleted()
+ Log.v(TAG, "OnGodotSetupCompleted")
+
+ // Check if we should be running in XR instead (if available) as it's possible we were
+ // launched from the project manager which doesn't have that information.
+ val launchingArgs = intent.getStringArrayExtra(EXTRA_COMMAND_LINE_PARAMS)
+ if (launchingArgs != null) {
+ val editorWindowInfo = retrieveEditorWindowInfo(launchingArgs)
+ if (editorWindowInfo != getEditorWindowInfo()) {
+ val relaunchIntent = getNewGodotInstanceIntent(editorWindowInfo, launchingArgs)
+ relaunchIntent.putExtra(EXTRA_NEW_LAUNCH, true)
+ .putExtra(EditorMessageDispatcher.EXTRA_MSG_DISPATCHER_PAYLOAD, intent.getBundleExtra(EditorMessageDispatcher.EXTRA_MSG_DISPATCHER_PAYLOAD))
+
+ Log.d(TAG, "Relaunching XR project using ${editorWindowInfo.windowClassName} with parameters ${launchingArgs.contentToString()}")
+ val godot = godot
+ if (godot != null) {
+ godot.destroyAndKillProcess {
+ ProcessPhoenix.triggerRebirth(this, relaunchIntent)
+ }
+ } else {
+ ProcessPhoenix.triggerRebirth(this, relaunchIntent)
+ }
+ return
+ }
+ }
+
+ // Request project runtime permissions if necessary
+ val permissionsToEnable = getProjectPermissionsToEnable()
+ if (permissionsToEnable.isNotEmpty()) {
+ PermissionsUtil.requestPermissions(this, permissionsToEnable)
+ }
+ }
+
+ /**
+ * Check for project permissions to enable
+ */
+ @CallSuper
+ protected open fun getProjectPermissionsToEnable(): MutableList<String> {
+ val permissionsToEnable = mutableListOf<String>()
+
+ // Check for RECORD_AUDIO permission
+ val audioInputEnabled = java.lang.Boolean.parseBoolean(GodotLib.getGlobal("audio/driver/enable_input"))
+ if (audioInputEnabled) {
+ permissionsToEnable.add(Manifest.permission.RECORD_AUDIO)
+ }
+
+ return permissionsToEnable
}
}
diff --git a/platform/android/java/editor/src/main/res/drawable/ic_play_window_foreground.xml b/platform/android/java/editor/src/main/res/drawable/ic_play_window_foreground.xml
new file mode 100644
index 0000000000..41bc5475c8
--- /dev/null
+++ b/platform/android/java/editor/src/main/res/drawable/ic_play_window_foreground.xml
@@ -0,0 +1,25 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="108dp"
+ android:height="108dp"
+ android:tint="#FFFFFF"
+ android:viewportWidth="24"
+ android:viewportHeight="24">
+ <group
+ android:scaleX="0.522"
+ android:scaleY="0.522"
+ android:translateX="5.736"
+ android:translateY="5.736">
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M21.58,16.09l-1.09,-7.66C20.21,6.46 18.52,5 16.53,5H7.47C5.48,5 3.79,6.46 3.51,8.43l-1.09,7.66C2.2,17.63 3.39,19 4.94,19h0c0.68,0 1.32,-0.27 1.8,-0.75L9,16h6l2.25,2.25c0.48,0.48 1.13,0.75 1.8,0.75h0C20.61,19 21.8,17.63 21.58,16.09zM19.48,16.81C19.4,16.9 19.27,17 19.06,17c-0.15,0 -0.29,-0.06 -0.39,-0.16L15.83,14H8.17l-2.84,2.84C5.23,16.94 5.09,17 4.94,17c-0.21,0 -0.34,-0.1 -0.42,-0.19c-0.08,-0.09 -0.16,-0.23 -0.13,-0.44l1.09,-7.66C5.63,7.74 6.48,7 7.47,7h9.06c0.99,0 1.84,0.74 1.98,1.72l1.09,7.66C19.63,16.58 19.55,16.72 19.48,16.81z" />
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M9,8l-1,0l0,2l-2,0l0,1l2,0l0,2l1,0l0,-2l2,0l0,-1l-2,0z" />
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M17,12m-1,0a1,1 0,1 1,2 0a1,1 0,1 1,-2 0" />
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M15,9m-1,0a1,1 0,1 1,2 0a1,1 0,1 1,-2 0" />
+ </group>
+</vector>
diff --git a/platform/android/java/editor/src/main/res/drawable/outline_fullscreen_exit_48.xml b/platform/android/java/editor/src/main/res/drawable/outline_fullscreen_exit_48.xml
new file mode 100644
index 0000000000..c8b5a15d19
--- /dev/null
+++ b/platform/android/java/editor/src/main/res/drawable/outline_fullscreen_exit_48.xml
@@ -0,0 +1,12 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="48dp"
+ android:height="48dp"
+ android:tint="#FFFFFF"
+ android:viewportWidth="24"
+ android:viewportHeight="24">
+
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M5,16h3v3h2v-5L5,14v2zM8,8L5,8v2h5L10,5L8,5v3zM14,19h2v-3h3v-2h-5v5zM16,8L16,5h-2v5h5L19,8h-3z" />
+
+</vector>
diff --git a/platform/android/java/editor/src/main/res/drawable/pip_button_activated_bg_drawable.xml b/platform/android/java/editor/src/main/res/drawable/pip_button_activated_bg_drawable.xml
new file mode 100644
index 0000000000..aeaa96ce54
--- /dev/null
+++ b/platform/android/java/editor/src/main/res/drawable/pip_button_activated_bg_drawable.xml
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="utf-8"?>
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+ android:shape="oval">
+
+ <size
+ android:width="60dp"
+ android:height="60dp" />
+
+ <solid android:color="#44000000" />
+</shape>
diff --git a/platform/android/java/editor/src/main/res/drawable/pip_button_bg_drawable.xml b/platform/android/java/editor/src/main/res/drawable/pip_button_bg_drawable.xml
new file mode 100644
index 0000000000..e9b2959275
--- /dev/null
+++ b/platform/android/java/editor/src/main/res/drawable/pip_button_bg_drawable.xml
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="utf-8"?>
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+
+ <item android:drawable="@drawable/pip_button_activated_bg_drawable" android:state_pressed="true" />
+ <item android:drawable="@drawable/pip_button_activated_bg_drawable" android:state_hovered="true" />
+
+ <item android:drawable="@drawable/pip_button_default_bg_drawable" />
+
+</selector>
diff --git a/platform/android/java/editor/src/main/res/drawable/pip_button_default_bg_drawable.xml b/platform/android/java/editor/src/main/res/drawable/pip_button_default_bg_drawable.xml
new file mode 100644
index 0000000000..a8919689fe
--- /dev/null
+++ b/platform/android/java/editor/src/main/res/drawable/pip_button_default_bg_drawable.xml
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="utf-8"?>
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+ android:shape="oval">
+
+ <size
+ android:width="60dp"
+ android:height="60dp" />
+
+ <solid android:color="#13000000" />
+</shape>
diff --git a/platform/android/java/editor/src/main/res/layout/godot_game_layout.xml b/platform/android/java/editor/src/main/res/layout/godot_game_layout.xml
new file mode 100644
index 0000000000..d53787c87e
--- /dev/null
+++ b/platform/android/java/editor/src/main/res/layout/godot_game_layout.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+
+ <FrameLayout
+ android:id="@+id/godot_fragment_container"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"/>
+
+ <ImageView
+ android:id="@+id/godot_pip_button"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_margin="36dp"
+ android:contentDescription="@string/pip_button_description"
+ android:background="@drawable/pip_button_bg_drawable"
+ android:scaleType="center"
+ android:src="@drawable/outline_fullscreen_exit_48"
+ android:visibility="gone"
+ android:layout_gravity="end|top"
+ tools:visibility="visible" />
+
+</FrameLayout>
diff --git a/platform/android/java/editor/src/main/res/mipmap-anydpi-v26/ic_play_window.xml b/platform/android/java/editor/src/main/res/mipmap-anydpi-v26/ic_play_window.xml
new file mode 100644
index 0000000000..a3aabf2ee0
--- /dev/null
+++ b/platform/android/java/editor/src/main/res/mipmap-anydpi-v26/ic_play_window.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
+ <background android:drawable="@mipmap/icon_background"/>
+ <foreground android:drawable="@drawable/ic_play_window_foreground"/>
+</adaptive-icon>
diff --git a/platform/android/java/editor/src/main/res/mipmap-hdpi/ic_play_window.png b/platform/android/java/editor/src/main/res/mipmap-hdpi/ic_play_window.png
new file mode 100644
index 0000000000..a5ce40241f
--- /dev/null
+++ b/platform/android/java/editor/src/main/res/mipmap-hdpi/ic_play_window.png
Binary files differ
diff --git a/platform/android/java/editor/src/main/res/mipmap-mdpi/ic_play_window.png b/platform/android/java/editor/src/main/res/mipmap-mdpi/ic_play_window.png
new file mode 100644
index 0000000000..147adb6127
--- /dev/null
+++ b/platform/android/java/editor/src/main/res/mipmap-mdpi/ic_play_window.png
Binary files differ
diff --git a/platform/android/java/editor/src/main/res/mipmap-xhdpi/ic_play_window.png b/platform/android/java/editor/src/main/res/mipmap-xhdpi/ic_play_window.png
new file mode 100644
index 0000000000..0b1db1b923
--- /dev/null
+++ b/platform/android/java/editor/src/main/res/mipmap-xhdpi/ic_play_window.png
Binary files differ
diff --git a/platform/android/java/editor/src/main/res/mipmap-xxhdpi/ic_play_window.png b/platform/android/java/editor/src/main/res/mipmap-xxhdpi/ic_play_window.png
new file mode 100644
index 0000000000..39d7450390
--- /dev/null
+++ b/platform/android/java/editor/src/main/res/mipmap-xxhdpi/ic_play_window.png
Binary files differ
diff --git a/platform/android/java/editor/src/main/res/mipmap-xxxhdpi/ic_play_window.png b/platform/android/java/editor/src/main/res/mipmap-xxxhdpi/ic_play_window.png
new file mode 100644
index 0000000000..b7a09a15b5
--- /dev/null
+++ b/platform/android/java/editor/src/main/res/mipmap-xxxhdpi/ic_play_window.png
Binary files differ
diff --git a/platform/android/java/editor/src/main/res/values/dimens.xml b/platform/android/java/editor/src/main/res/values/dimens.xml
index 98bfe40179..1e486872e6 100644
--- a/platform/android/java/editor/src/main/res/values/dimens.xml
+++ b/platform/android/java/editor/src/main/res/values/dimens.xml
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
- <dimen name="editor_default_window_height">600dp</dimen>
+ <dimen name="editor_default_window_height">640dp</dimen>
<dimen name="editor_default_window_width">1024dp</dimen>
</resources>
diff --git a/platform/android/java/editor/src/main/res/values/strings.xml b/platform/android/java/editor/src/main/res/values/strings.xml
index 909711ab18..0ad54ac3a1 100644
--- a/platform/android/java/editor/src/main/res/values/strings.xml
+++ b/platform/android/java/editor/src/main/res/values/strings.xml
@@ -1,4 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
+ <string name="godot_game_activity_name">Godot Play window</string>
<string name="denied_storage_permission_error_msg">Missing storage access permission!</string>
+ <string name="pip_button_description">Button used to toggle picture-in-picture mode for the Play window</string>
</resources>
diff --git a/platform/android/java/editor/src/meta/AndroidManifest.xml b/platform/android/java/editor/src/meta/AndroidManifest.xml
new file mode 100644
index 0000000000..06442ac4e6
--- /dev/null
+++ b/platform/android/java/editor/src/meta/AndroidManifest.xml
@@ -0,0 +1,99 @@
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
+ xmlns:horizonos="http://schemas.horizonos/sdk">
+
+ <horizonos:uses-horizonos-sdk
+ horizonos:minSdkVersion="69"
+ horizonos:targetSdkVersion="69" />
+
+ <uses-feature
+ android:name="android.hardware.vr.headtracking"
+ android:required="true"
+ android:version="1"/>
+
+ <!-- Oculus Quest hand tracking -->
+ <uses-permission android:name="com.oculus.permission.HAND_TRACKING" />
+ <uses-feature
+ android:name="oculus.software.handtracking"
+ android:required="false" />
+
+ <!-- Passthrough feature flag -->
+ <uses-feature android:name="com.oculus.feature.PASSTHROUGH"
+ android:required="false" />
+
+ <!-- Overlay keyboard support -->
+ <uses-feature android:name="oculus.software.overlay_keyboard" android:required="false"/>
+
+ <!-- Render model -->
+ <uses-permission android:name="com.oculus.permission.RENDER_MODEL" />
+ <uses-feature android:name="com.oculus.feature.RENDER_MODEL" android:required="false" />
+
+ <!-- Anchor api -->
+ <uses-permission android:name="com.oculus.permission.USE_ANCHOR_API" />
+
+ <!-- Scene api -->
+ <uses-permission android:name="com.oculus.permission.USE_SCENE" />
+
+ <application>
+
+ <activity
+ android:name=".GodotEditor"
+ android:exported="true"
+ android:screenOrientation="landscape"
+ tools:node="merge"
+ tools:replace="android:screenOrientation">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.DEFAULT" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ <category android:name="com.oculus.intent.category.2D" />
+ </intent-filter>
+
+ <meta-data android:name="com.oculus.vrshell.free_resizing_lock_aspect_ratio" android:value="true"/>
+ </activity>
+
+ <activity
+ android:name=".GodotXRGame"
+ android:configChanges="orientation|keyboardHidden|screenSize|smallestScreenSize|density|keyboard|navigation|screenLayout|uiMode"
+ android:process=":GodotXRGame"
+ android:launchMode="singleTask"
+ android:icon="@mipmap/ic_play_window"
+ android:label="@string/godot_game_activity_name"
+ android:exported="false"
+ android:screenOrientation="landscape"
+ android:resizeableActivity="false"
+ android:theme="@android:style/Theme.Black.NoTitleBar.Fullscreen">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.DEFAULT" />
+ <category android:name="com.oculus.intent.category.VR" />
+ <category android:name="org.khronos.openxr.intent.category.IMMERSIVE_HMD" />
+ </intent-filter>
+ </activity>
+
+ <!-- Supported Meta devices -->
+ <meta-data
+ android:name="com.oculus.supportedDevices"
+ android:value="quest3|questpro"
+ tools:replace="android:value" />
+
+ <!--
+ We remove this meta-data originating from the vendors plugin as we only need the loader for
+ now since the project being edited provides its own version of the vendors plugin.
+
+ This needs to be removed once we start implementing the immersive version of the project
+ manager and editor windows.
+ -->
+ <meta-data
+ android:name="org.godotengine.plugin.v2.GodotOpenXRMeta"
+ android:value="org.godotengine.openxr.vendors.meta.GodotOpenXRMeta"
+ tools:node="remove" />
+
+ <!-- Enable system splash screen -->
+ <meta-data android:name="com.oculus.ossplash" android:value="true"/>
+ <!-- Enable passthrough background during the splash screen -->
+ <meta-data android:name="com.oculus.ossplash.background" android:value="passthrough-contextual"/>
+
+ </application>
+
+</manifest>
diff --git a/platform/android/java/editor/src/meta/assets/vr_splash.png b/platform/android/java/editor/src/meta/assets/vr_splash.png
new file mode 100644
index 0000000000..7bddd4325a
--- /dev/null
+++ b/platform/android/java/editor/src/meta/assets/vr_splash.png
Binary files differ
diff --git a/platform/android/java/editor/src/meta/java/org/godotengine/editor/GodotEditor.kt b/platform/android/java/editor/src/meta/java/org/godotengine/editor/GodotEditor.kt
new file mode 100644
index 0000000000..9f0440e87d
--- /dev/null
+++ b/platform/android/java/editor/src/meta/java/org/godotengine/editor/GodotEditor.kt
@@ -0,0 +1,94 @@
+/**************************************************************************/
+/* GodotEditor.kt */
+/**************************************************************************/
+/* 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. */
+/**************************************************************************/
+
+package org.godotengine.editor
+
+import org.godotengine.godot.GodotLib
+import org.godotengine.godot.utils.isNativeXRDevice
+
+/**
+ * Primary window of the Godot Editor.
+ *
+ * This is the implementation of the editor used when running on Meta devices.
+ */
+open class GodotEditor : BaseGodotEditor() {
+
+ companion object {
+ private val TAG = GodotEditor::class.java.simpleName
+
+ internal val XR_RUN_GAME_INFO = EditorWindowInfo(GodotXRGame::class.java, 1667, ":GodotXRGame")
+
+ internal const val USE_ANCHOR_API_PERMISSION = "com.oculus.permission.USE_ANCHOR_API"
+ internal const val USE_SCENE_PERMISSION = "com.oculus.permission.USE_SCENE"
+ }
+
+ override fun getExcludedPermissions(): MutableSet<String> {
+ val excludedPermissions = super.getExcludedPermissions()
+ // The USE_ANCHOR_API and USE_SCENE permissions are requested when the "xr/openxr/enabled"
+ // project setting is enabled.
+ excludedPermissions.add(USE_ANCHOR_API_PERMISSION)
+ excludedPermissions.add(USE_SCENE_PERMISSION)
+ return excludedPermissions
+ }
+
+ override fun retrieveEditorWindowInfo(args: Array<String>): EditorWindowInfo {
+ var hasEditor = false
+ var xrModeOn = false
+
+ var i = 0
+ while (i < args.size) {
+ when (args[i++]) {
+ EDITOR_ARG, EDITOR_ARG_SHORT, EDITOR_PROJECT_MANAGER_ARG, EDITOR_PROJECT_MANAGER_ARG_SHORT -> hasEditor = true
+ XR_MODE_ARG -> {
+ val argValue = args[i++]
+ xrModeOn = xrModeOn || ("on" == argValue)
+ }
+ }
+ }
+
+ return if (hasEditor) {
+ EDITOR_MAIN_INFO
+ } else {
+ val openxrEnabled = GodotLib.getGlobal("xr/openxr/enabled").toBoolean()
+ if (openxrEnabled && isNativeXRDevice()) {
+ XR_RUN_GAME_INFO
+ } else {
+ RUN_GAME_INFO
+ }
+ }
+ }
+
+ override fun getEditorWindowInfoForInstanceId(instanceId: Int): EditorWindowInfo? {
+ return when (instanceId) {
+ XR_RUN_GAME_INFO.windowId -> XR_RUN_GAME_INFO
+ else -> super.getEditorWindowInfoForInstanceId(instanceId)
+ }
+ }
+}
diff --git a/platform/android/java/editor/src/meta/java/org/godotengine/editor/GodotXRGame.kt b/platform/android/java/editor/src/meta/java/org/godotengine/editor/GodotXRGame.kt
new file mode 100644
index 0000000000..d71fbb53f2
--- /dev/null
+++ b/platform/android/java/editor/src/meta/java/org/godotengine/editor/GodotXRGame.kt
@@ -0,0 +1,71 @@
+/*************************************************************************/
+/* GodotXRGame.kt */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */
+/* */
+/* 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. */
+/*************************************************************************/
+
+package org.godotengine.editor
+
+import org.godotengine.godot.GodotLib
+import org.godotengine.godot.utils.PermissionsUtil
+import org.godotengine.godot.xr.XRMode
+
+/**
+ * Provide support for running XR apps / games from the editor window.
+ */
+open class GodotXRGame: GodotGame() {
+
+ override fun overrideOrientationRequest() = true
+
+ override fun updateCommandLineParams(args: List<String>) {
+ val updatedArgs = ArrayList<String>()
+ if (!args.contains(XRMode.OPENXR.cmdLineArg)) {
+ updatedArgs.add(XRMode.OPENXR.cmdLineArg)
+ }
+ if (!args.contains(XR_MODE_ARG)) {
+ updatedArgs.add(XR_MODE_ARG)
+ updatedArgs.add("on")
+ }
+ updatedArgs.addAll(args)
+
+ super.updateCommandLineParams(updatedArgs)
+ }
+
+ override fun getEditorWindowInfo() = XR_RUN_GAME_INFO
+
+ override fun getProjectPermissionsToEnable(): MutableList<String> {
+ val permissionsToEnable = super.getProjectPermissionsToEnable()
+
+ val openxrEnabled = GodotLib.getGlobal("xr/openxr/enabled").toBoolean()
+ if (openxrEnabled) {
+ permissionsToEnable.add(USE_ANCHOR_API_PERMISSION)
+ permissionsToEnable.add(USE_SCENE_PERMISSION)
+ }
+
+ return permissionsToEnable
+ }
+}
diff --git a/platform/android/java/lib/build.gradle b/platform/android/java/lib/build.gradle
index 81ab598b90..f6aee434e5 100644
--- a/platform/android/java/lib/build.gradle
+++ b/platform/android/java/lib/build.gradle
@@ -51,7 +51,7 @@ android {
}
}
- flavorDimensions "products"
+ flavorDimensions = ["products"]
productFlavors {
editor {}
template {}
@@ -104,7 +104,7 @@ android {
}
boolean devBuild = buildType == "dev"
- boolean debugSymbols = devBuild || isAndroidStudio()
+ boolean debugSymbols = devBuild
boolean runTests = devBuild
boolean productionBuild = !devBuild
boolean storeRelease = buildType == "release"
diff --git a/platform/android/java/lib/src/org/godotengine/godot/Godot.kt b/platform/android/java/lib/src/org/godotengine/godot/Godot.kt
index 49e8ffb008..38bd336e2d 100644
--- a/platform/android/java/lib/src/org/godotengine/godot/Godot.kt
+++ b/platform/android/java/lib/src/org/godotengine/godot/Godot.kt
@@ -42,13 +42,16 @@ import android.hardware.Sensor
import android.hardware.SensorManager
import android.os.*
import android.util.Log
+import android.util.TypedValue
import android.view.*
import android.widget.FrameLayout
import androidx.annotation.Keep
import androidx.annotation.StringRes
import androidx.core.view.ViewCompat
+import androidx.core.view.WindowCompat
import androidx.core.view.WindowInsetsAnimationCompat
import androidx.core.view.WindowInsetsCompat
+import androidx.core.view.WindowInsetsControllerCompat
import com.google.android.vending.expansion.downloader.*
import org.godotengine.godot.error.Error
import org.godotengine.godot.input.GodotEditText
@@ -105,36 +108,26 @@ class Godot(private val context: Context) {
GodotPluginRegistry.getPluginRegistry()
}
- private val accelerometer_enabled = AtomicBoolean(false)
+ private val accelerometerEnabled = AtomicBoolean(false)
private val mAccelerometer: Sensor? by lazy {
mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER)
}
- private val gravity_enabled = AtomicBoolean(false)
+ private val gravityEnabled = AtomicBoolean(false)
private val mGravity: Sensor? by lazy {
mSensorManager.getDefaultSensor(Sensor.TYPE_GRAVITY)
}
- private val magnetometer_enabled = AtomicBoolean(false)
+ private val magnetometerEnabled = AtomicBoolean(false)
private val mMagnetometer: Sensor? by lazy {
mSensorManager.getDefaultSensor(Sensor.TYPE_MAGNETIC_FIELD)
}
- private val gyroscope_enabled = AtomicBoolean(false)
+ private val gyroscopeEnabled = AtomicBoolean(false)
private val mGyroscope: Sensor? by lazy {
mSensorManager.getDefaultSensor(Sensor.TYPE_GYROSCOPE)
}
- private val uiChangeListener = View.OnSystemUiVisibilityChangeListener { visibility: Int ->
- if (visibility and View.SYSTEM_UI_FLAG_FULLSCREEN == 0) {
- val decorView = requireActivity().window.decorView
- decorView.systemUiVisibility = View.SYSTEM_UI_FLAG_LAYOUT_STABLE or
- View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION or
- View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN or
- View.SYSTEM_UI_FLAG_HIDE_NAVIGATION or
- View.SYSTEM_UI_FLAG_FULLSCREEN or View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY
- }}
-
val tts = GodotTTS(context)
val directoryAccessHandler = DirectoryAccessHandler(context)
val fileAccessHandler = FileAccessHandler(context)
@@ -185,7 +178,7 @@ class Godot(private val context: Context) {
private var xrMode = XRMode.REGULAR
private var expansionPackPath: String = ""
private var useApkExpansion = false
- private var useImmersive = false
+ private val useImmersive = AtomicBoolean(false)
private var useDebugOpengl = false
private var darkMode = false
@@ -254,15 +247,9 @@ class Godot(private val context: Context) {
xrMode = XRMode.OPENXR
} else if (commandLine[i] == "--debug_opengl") {
useDebugOpengl = true
- } else if (commandLine[i] == "--use_immersive") {
- useImmersive = true
- window.decorView.systemUiVisibility = View.SYSTEM_UI_FLAG_LAYOUT_STABLE or
- View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION or
- View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN or
- View.SYSTEM_UI_FLAG_HIDE_NAVIGATION or // hide nav bar
- View.SYSTEM_UI_FLAG_FULLSCREEN or // hide status bar
- View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY
- registerUiChangeListener()
+ } else if (commandLine[i] == "--fullscreen") {
+ useImmersive.set(true)
+ newArgs.add(commandLine[i])
} else if (commandLine[i] == "--use_apk_expansion") {
useApkExpansion = true
} else if (hasExtra && commandLine[i] == "--apk_expansion_md5") {
@@ -336,6 +323,54 @@ class Godot(private val context: Context) {
}
/**
+ * Toggle immersive mode.
+ * Must be called from the UI thread.
+ */
+ private fun enableImmersiveMode(enabled: Boolean, override: Boolean = false) {
+ val activity = getActivity() ?: return
+ val window = activity.window ?: return
+
+ if (!useImmersive.compareAndSet(!enabled, enabled) && !override) {
+ return
+ }
+
+ WindowCompat.setDecorFitsSystemWindows(window, !enabled)
+ val controller = WindowInsetsControllerCompat(window, window.decorView)
+ if (enabled) {
+ controller.hide(WindowInsetsCompat.Type.systemBars())
+ controller.systemBarsBehavior = WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE
+ } else {
+ val fullScreenThemeValue = TypedValue()
+ val hasStatusBar = if (activity.theme.resolveAttribute(android.R.attr.windowFullscreen, fullScreenThemeValue, true) && fullScreenThemeValue.type == TypedValue.TYPE_INT_BOOLEAN) {
+ fullScreenThemeValue.data == 0
+ } else {
+ // Fallback to checking the editor build
+ !isEditorBuild()
+ }
+
+ val types = if (hasStatusBar) {
+ WindowInsetsCompat.Type.navigationBars() or WindowInsetsCompat.Type.statusBars()
+ } else {
+ WindowInsetsCompat.Type.navigationBars()
+ }
+ controller.show(types)
+ }
+ }
+
+ /**
+ * Invoked from the render thread to toggle the immersive mode.
+ */
+ @Keep
+ private fun nativeEnableImmersiveMode(enabled: Boolean) {
+ runOnUiThread {
+ enableImmersiveMode(enabled)
+ }
+ }
+
+ @Keep
+ fun isInImmersiveMode() = useImmersive.get()
+
+ /**
* Initializes the native layer of the Godot engine.
*
* This must be preceded by [onCreate] and followed by [onInitRenderView] to complete
@@ -552,15 +587,7 @@ class Godot(private val context: Context) {
renderView?.onActivityResumed()
registerSensorsIfNeeded()
- if (useImmersive) {
- val window = requireActivity().window
- window.decorView.systemUiVisibility = View.SYSTEM_UI_FLAG_LAYOUT_STABLE or
- View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION or
- View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN or
- View.SYSTEM_UI_FLAG_HIDE_NAVIGATION or // hide nav bar
- View.SYSTEM_UI_FLAG_FULLSCREEN or // hide status bar
- View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY
- }
+ enableImmersiveMode(useImmersive.get(), true)
for (plugin in pluginRegistry.allPlugins) {
plugin.onMainResume()
}
@@ -571,16 +598,16 @@ class Godot(private val context: Context) {
return
}
- if (accelerometer_enabled.get() && mAccelerometer != null) {
+ if (accelerometerEnabled.get() && mAccelerometer != null) {
mSensorManager.registerListener(godotInputHandler, mAccelerometer, SensorManager.SENSOR_DELAY_GAME)
}
- if (gravity_enabled.get() && mGravity != null) {
+ if (gravityEnabled.get() && mGravity != null) {
mSensorManager.registerListener(godotInputHandler, mGravity, SensorManager.SENSOR_DELAY_GAME)
}
- if (magnetometer_enabled.get() && mMagnetometer != null) {
+ if (magnetometerEnabled.get() && mMagnetometer != null) {
mSensorManager.registerListener(godotInputHandler, mMagnetometer, SensorManager.SENSOR_DELAY_GAME)
}
- if (gyroscope_enabled.get() && mGyroscope != null) {
+ if (gyroscopeEnabled.get() && mGyroscope != null) {
mSensorManager.registerListener(godotInputHandler, mGyroscope, SensorManager.SENSOR_DELAY_GAME)
}
}
@@ -696,10 +723,10 @@ class Godot(private val context: Context) {
Log.v(TAG, "OnGodotMainLoopStarted")
godotMainLoopStarted.set(true)
- accelerometer_enabled.set(java.lang.Boolean.parseBoolean(GodotLib.getGlobal("input_devices/sensors/enable_accelerometer")))
- gravity_enabled.set(java.lang.Boolean.parseBoolean(GodotLib.getGlobal("input_devices/sensors/enable_gravity")))
- gyroscope_enabled.set(java.lang.Boolean.parseBoolean(GodotLib.getGlobal("input_devices/sensors/enable_gyroscope")))
- magnetometer_enabled.set(java.lang.Boolean.parseBoolean(GodotLib.getGlobal("input_devices/sensors/enable_magnetometer")))
+ accelerometerEnabled.set(java.lang.Boolean.parseBoolean(GodotLib.getGlobal("input_devices/sensors/enable_accelerometer")))
+ gravityEnabled.set(java.lang.Boolean.parseBoolean(GodotLib.getGlobal("input_devices/sensors/enable_gravity")))
+ gyroscopeEnabled.set(java.lang.Boolean.parseBoolean(GodotLib.getGlobal("input_devices/sensors/enable_gyroscope")))
+ magnetometerEnabled.set(java.lang.Boolean.parseBoolean(GodotLib.getGlobal("input_devices/sensors/enable_magnetometer")))
runOnUiThread {
registerSensorsIfNeeded()
@@ -724,11 +751,6 @@ class Godot(private val context: Context) {
primaryHost?.onGodotRestartRequested(this)
}
- private fun registerUiChangeListener() {
- val decorView = requireActivity().window.decorView
- decorView.setOnSystemUiVisibilityChangeListener(uiChangeListener)
- }
-
fun alert(
@StringRes messageResId: Int,
@StringRes titleResId: Int,
diff --git a/platform/android/java/lib/src/org/godotengine/godot/GodotActivity.kt b/platform/android/java/lib/src/org/godotengine/godot/GodotActivity.kt
index 913e3d04c5..474c6e9b2f 100644
--- a/platform/android/java/lib/src/org/godotengine/godot/GodotActivity.kt
+++ b/platform/android/java/lib/src/org/godotengine/godot/GodotActivity.kt
@@ -53,8 +53,6 @@ abstract class GodotActivity : FragmentActivity(), GodotHost {
private val TAG = GodotActivity::class.java.simpleName
@JvmStatic
- protected val EXTRA_FORCE_QUIT = "force_quit_requested"
- @JvmStatic
protected val EXTRA_NEW_LAUNCH = "new_launch_requested"
}
@@ -128,12 +126,6 @@ abstract class GodotActivity : FragmentActivity(), GodotHost {
}
private fun handleStartIntent(intent: Intent, newLaunch: Boolean) {
- val forceQuitRequested = intent.getBooleanExtra(EXTRA_FORCE_QUIT, false)
- if (forceQuitRequested) {
- Log.d(TAG, "Force quit requested, terminating..")
- ProcessPhoenix.forceQuit(this)
- return
- }
if (!newLaunch) {
val newLaunchRequested = intent.getBooleanExtra(EXTRA_NEW_LAUNCH, false)
if (newLaunchRequested) {
diff --git a/platform/android/java/lib/src/org/godotengine/godot/GodotRenderView.java b/platform/android/java/lib/src/org/godotengine/godot/GodotRenderView.java
index 30821eaa8e..9db9ef6080 100644
--- a/platform/android/java/lib/src/org/godotengine/godot/GodotRenderView.java
+++ b/platform/android/java/lib/src/org/godotengine/godot/GodotRenderView.java
@@ -31,6 +31,7 @@
package org.godotengine.godot;
import org.godotengine.godot.input.GodotInputHandler;
+import org.godotengine.godot.utils.DeviceUtils;
import android.view.SurfaceView;
@@ -63,7 +64,11 @@ public interface GodotRenderView {
void setPointerIcon(int pointerType);
+ /**
+ * @return true if pointer capture is supported.
+ */
default boolean canCapturePointer() {
- return getInputHandler().canCapturePointer();
+ // Pointer capture is not supported on Horizon OS
+ return !DeviceUtils.isHorizonOSDevice() && getInputHandler().canCapturePointer();
}
}
diff --git a/platform/android/java/lib/src/org/godotengine/godot/GodotVulkanRenderView.java b/platform/android/java/lib/src/org/godotengine/godot/GodotVulkanRenderView.java
index d5b05913d8..8b3880d32d 100644
--- a/platform/android/java/lib/src/org/godotengine/godot/GodotVulkanRenderView.java
+++ b/platform/android/java/lib/src/org/godotengine/godot/GodotVulkanRenderView.java
@@ -68,6 +68,7 @@ class GodotVulkanRenderView extends VkSurfaceView implements GodotRenderView {
setPointerIcon(PointerIcon.getSystemIcon(getContext(), PointerIcon.TYPE_DEFAULT));
}
setFocusableInTouchMode(true);
+ setClickable(false);
}
@Override
@@ -132,17 +133,17 @@ class GodotVulkanRenderView extends VkSurfaceView implements GodotRenderView {
@Override
public boolean onKeyUp(final int keyCode, KeyEvent event) {
- return mInputHandler.onKeyUp(keyCode, event);
+ return mInputHandler.onKeyUp(keyCode, event) || super.onKeyUp(keyCode, event);
}
@Override
public boolean onKeyDown(final int keyCode, KeyEvent event) {
- return mInputHandler.onKeyDown(keyCode, event);
+ return mInputHandler.onKeyDown(keyCode, event) || super.onKeyDown(keyCode, event);
}
@Override
public boolean onGenericMotionEvent(MotionEvent event) {
- return mInputHandler.onGenericMotionEvent(event);
+ return mInputHandler.onGenericMotionEvent(event) || super.onGenericMotionEvent(event);
}
@Override
diff --git a/platform/android/java/lib/src/org/godotengine/godot/utils/DeviceUtils.kt b/platform/android/java/lib/src/org/godotengine/godot/utils/DeviceUtils.kt
new file mode 100644
index 0000000000..abe0c5f885
--- /dev/null
+++ b/platform/android/java/lib/src/org/godotengine/godot/utils/DeviceUtils.kt
@@ -0,0 +1,52 @@
+/**************************************************************************/
+/* DeviceUtils.kt */
+/**************************************************************************/
+/* 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. */
+/**************************************************************************/
+
+/**
+ * Contains utility methods for detecting specific devices.
+ */
+@file:JvmName("DeviceUtils")
+
+package org.godotengine.godot.utils
+
+import android.os.Build
+
+/**
+ * Returns true if running on Meta's Horizon OS.
+ */
+fun isHorizonOSDevice(): Boolean {
+ return "Oculus".equals(Build.BRAND, true)
+}
+
+/**
+ * Returns true if running on a native Android XR device.
+ */
+fun isNativeXRDevice(): Boolean {
+ return isHorizonOSDevice()
+}
diff --git a/platform/android/java/lib/src/org/godotengine/godot/utils/ProcessPhoenix.java b/platform/android/java/lib/src/org/godotengine/godot/utils/ProcessPhoenix.java
index b1bce45fbb..d9afdf90b1 100644
--- a/platform/android/java/lib/src/org/godotengine/godot/utils/ProcessPhoenix.java
+++ b/platform/android/java/lib/src/org/godotengine/godot/utils/ProcessPhoenix.java
@@ -24,6 +24,7 @@ package org.godotengine.godot.utils;
import android.app.Activity;
import android.app.ActivityManager;
+import android.app.ActivityOptions;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
@@ -44,6 +45,9 @@ import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
*/
public final class ProcessPhoenix extends Activity {
private static final String KEY_RESTART_INTENTS = "phoenix_restart_intents";
+ // -- GODOT start --
+ private static final String KEY_RESTART_ACTIVITY_OPTIONS = "phoenix_restart_activity_options";
+ // -- GODOT end --
private static final String KEY_MAIN_PROCESS_PID = "phoenix_main_process_pid";
/**
@@ -56,12 +60,23 @@ public final class ProcessPhoenix extends Activity {
triggerRebirth(context, getRestartIntent(context));
}
+ // -- GODOT start --
/**
* Call to restart the application process using the specified intents.
* <p>
* Behavior of the current process after invoking this method is undefined.
*/
public static void triggerRebirth(Context context, Intent... nextIntents) {
+ triggerRebirth(context, null, nextIntents);
+ }
+
+ /**
+ * Call to restart the application process using the specified intents launched with the given
+ * {@link ActivityOptions}.
+ * <p>
+ * Behavior of the current process after invoking this method is undefined.
+ */
+ public static void triggerRebirth(Context context, Bundle activityOptions, Intent... nextIntents) {
if (nextIntents.length < 1) {
throw new IllegalArgumentException("intents cannot be empty");
}
@@ -72,10 +87,12 @@ public final class ProcessPhoenix extends Activity {
intent.addFlags(FLAG_ACTIVITY_NEW_TASK); // In case we are called with non-Activity context.
intent.putParcelableArrayListExtra(KEY_RESTART_INTENTS, new ArrayList<>(Arrays.asList(nextIntents)));
intent.putExtra(KEY_MAIN_PROCESS_PID, Process.myPid());
+ if (activityOptions != null) {
+ intent.putExtra(KEY_RESTART_ACTIVITY_OPTIONS, activityOptions);
+ }
context.startActivity(intent);
}
- // -- GODOT start --
/**
* Finish the activity and kill its process
*/
@@ -112,9 +129,11 @@ public final class ProcessPhoenix extends Activity {
super.onCreate(savedInstanceState);
// -- GODOT start --
- ArrayList<Intent> intents = getIntent().getParcelableArrayListExtra(KEY_RESTART_INTENTS);
- startActivities(intents.toArray(new Intent[intents.size()]));
- forceQuit(this, getIntent().getIntExtra(KEY_MAIN_PROCESS_PID, -1));
+ Intent launchIntent = getIntent();
+ ArrayList<Intent> intents = launchIntent.getParcelableArrayListExtra(KEY_RESTART_INTENTS);
+ Bundle activityOptions = launchIntent.getBundleExtra(KEY_RESTART_ACTIVITY_OPTIONS);
+ startActivities(intents.toArray(new Intent[intents.size()]), activityOptions);
+ forceQuit(this, launchIntent.getIntExtra(KEY_MAIN_PROCESS_PID, -1));
// -- GODOT end --
}
diff --git a/platform/android/java/nativeSrcsConfigs/CMakeLists.txt b/platform/android/java/nativeSrcsConfigs/CMakeLists.txt
index 96b6dfc9f3..a7d2774db5 100644
--- a/platform/android/java/nativeSrcsConfigs/CMakeLists.txt
+++ b/platform/android/java/nativeSrcsConfigs/CMakeLists.txt
@@ -8,6 +8,7 @@ set(CMAKE_CXX_EXTENSIONS OFF)
set(GODOT_ROOT_DIR ../../../..)
set(ANDROID_ROOT_DIR "${GODOT_ROOT_DIR}/platform/android" CACHE STRING "")
+set(OPENXR_INCLUDE_DIR "${GODOT_ROOT_DIR}/thirdparty/openxr/include" CACHE STRING "")
# Get sources
file(GLOB_RECURSE SOURCES ${GODOT_ROOT_DIR}/*.c**)
@@ -17,6 +18,7 @@ add_executable(${PROJECT_NAME} ${SOURCES} ${HEADERS})
target_include_directories(${PROJECT_NAME}
SYSTEM PUBLIC
${GODOT_ROOT_DIR}
- ${ANDROID_ROOT_DIR})
+ ${ANDROID_ROOT_DIR}
+ ${OPENXR_INCLUDE_DIR})
add_definitions(-DUNIX_ENABLED -DVULKAN_ENABLED -DANDROID_ENABLED -DGLES3_ENABLED -DTOOLS_ENABLED)
diff --git a/platform/android/java/settings.gradle b/platform/android/java/settings.gradle
index 3137e74244..41418a4a45 100644
--- a/platform/android/java/settings.gradle
+++ b/platform/android/java/settings.gradle
@@ -14,6 +14,7 @@ pluginManagement {
mavenCentral()
gradlePluginPortal()
maven { url "https://plugins.gradle.org/m2/" }
+ maven { url "https://s01.oss.sonatype.org/content/repositories/snapshots/"}
}
}
diff --git a/platform/android/java_class_wrapper.cpp b/platform/android/java_class_wrapper.cpp
index a309a6ab74..c92717e922 100644
--- a/platform/android/java_class_wrapper.cpp
+++ b/platform/android/java_class_wrapper.cpp
@@ -44,7 +44,7 @@ bool JavaClass::_call_method(JavaObject *p_instance, const StringName &p_method,
MethodInfo *method = nullptr;
for (MethodInfo &E : M->value) {
- if (!p_instance && !E._static) {
+ if (!p_instance && !E._static && !E._constructor) {
r_error.error = Callable::CallError::CALL_ERROR_INSTANCE_IS_NULL;
continue;
}
@@ -97,20 +97,24 @@ bool JavaClass::_call_method(JavaObject *p_instance, const StringName &p_method,
}
} break;
case ARG_TYPE_STRING: {
- if (p_args[i]->get_type() != Variant::STRING) {
+ if (!p_args[i]->is_string()) {
arg_expected = Variant::STRING;
}
} break;
case ARG_TYPE_CLASS: {
- if (p_args[i]->get_type() != Variant::OBJECT) {
+ if (p_args[i]->get_type() != Variant::OBJECT && p_args[i]->get_type() != Variant::NIL) {
arg_expected = Variant::OBJECT;
} else {
Ref<RefCounted> ref = *p_args[i];
- if (!ref.is_null()) {
+ if (ref.is_valid()) {
if (Object::cast_to<JavaObject>(ref.ptr())) {
Ref<JavaObject> jo = ref;
//could be faster
- jclass c = env->FindClass(E.param_sigs[i].operator String().utf8().get_data());
+ String cn = E.param_sigs[i].operator String();
+ if (cn.begins_with("L") && cn.ends_with(";")) {
+ cn = cn.substr(1, cn.length() - 2);
+ }
+ jclass c = env->FindClass(cn.utf8().get_data());
if (!c || !env->IsInstanceOf(jo->instance, c)) {
arg_expected = Variant::OBJECT;
} else {
@@ -458,7 +462,9 @@ bool JavaClass::_call_method(JavaObject *p_instance, const StringName &p_method,
} break;
default: {
jobject obj;
- if (method->_static) {
+ if (method->_constructor) {
+ obj = env->NewObject(_class, method->method, argv);
+ } else if (method->_static) {
obj = env->CallStaticObjectMethodA(_class, method->method, argv);
} else {
obj = env->CallObjectMethodA(p_instance->instance, method->method, argv);
@@ -487,7 +493,9 @@ bool JavaClass::_call_method(JavaObject *p_instance, const StringName &p_method,
Variant JavaClass::callp(const StringName &p_method, const Variant **p_args, int p_argcount, Callable::CallError &r_error) {
Variant ret;
- bool found = _call_method(nullptr, p_method, p_args, p_argcount, r_error, ret);
+
+ String method = (p_method == java_constructor_name) ? "<init>" : p_method;
+ bool found = _call_method(nullptr, method, p_args, p_argcount, r_error, ret);
if (found) {
return ret;
}
@@ -495,19 +503,156 @@ Variant JavaClass::callp(const StringName &p_method, const Variant **p_args, int
return RefCounted::callp(p_method, p_args, p_argcount, r_error);
}
+String JavaClass::get_java_class_name() const {
+ return java_class_name;
+}
+
+TypedArray<Dictionary> JavaClass::get_java_method_list() const {
+ TypedArray<Dictionary> method_list;
+
+ for (const KeyValue<StringName, List<MethodInfo>> &item : methods) {
+ for (const MethodInfo &mi : item.value) {
+ Dictionary method;
+
+ method["name"] = mi._constructor ? java_constructor_name : String(item.key);
+ method["id"] = (uint64_t)mi.method;
+ method["default_args"] = Array();
+ method["flags"] = METHOD_FLAGS_DEFAULT & (mi._static || mi._constructor ? METHOD_FLAG_STATIC : METHOD_FLAG_NORMAL);
+
+ {
+ Array a;
+
+ for (uint32_t argtype : mi.param_types) {
+ Dictionary d;
+
+ Variant::Type t = Variant::NIL;
+ float likelihood = 0.0;
+ _convert_to_variant_type(argtype, t, likelihood);
+ d["type"] = t;
+ if (t == Variant::OBJECT) {
+ d["hint"] = PROPERTY_HINT_RESOURCE_TYPE;
+ d["hint_string"] = "JavaObject";
+ } else {
+ d["hint"] = 0;
+ d["hint_string"] = "";
+ }
+
+ a.push_back(d);
+ }
+
+ method["args"] = a;
+ }
+
+ {
+ Dictionary d;
+
+ if (mi._constructor) {
+ d["type"] = Variant::OBJECT;
+ d["hint"] = PROPERTY_HINT_RESOURCE_TYPE;
+ d["hint_string"] = "JavaObject";
+ } else {
+ Variant::Type t = Variant::NIL;
+ float likelihood = 0.0;
+ _convert_to_variant_type(mi.return_type, t, likelihood);
+ d["type"] = t;
+ if (t == Variant::OBJECT) {
+ d["hint"] = PROPERTY_HINT_RESOURCE_TYPE;
+ d["hint_string"] = "JavaObject";
+ } else {
+ d["hint"] = 0;
+ d["hint_string"] = "";
+ }
+ }
+
+ method["return_type"] = d;
+ }
+
+ method_list.push_back(method);
+ }
+ }
+
+ return method_list;
+}
+
+Ref<JavaClass> JavaClass::get_java_parent_class() const {
+ ERR_FAIL_NULL_V(_class, Ref<JavaClass>());
+
+ JNIEnv *env = get_jni_env();
+ ERR_FAIL_NULL_V(env, Ref<JavaClass>());
+
+ jclass superclass = (jclass)env->CallObjectMethod(_class, JavaClassWrapper::singleton->Class_getSuperclass);
+ if (!superclass) {
+ return Ref<JavaClass>();
+ }
+
+ Ref<JavaClass> ret = JavaClassWrapper::singleton->wrap_jclass(superclass);
+ env->DeleteLocalRef(superclass);
+ return ret;
+}
+
+String JavaClass::to_string() {
+ return "<JavaClass:" + java_class_name + ">";
+}
+
JavaClass::JavaClass() {
}
+JavaClass::~JavaClass() {
+ if (_class) {
+ JNIEnv *env = get_jni_env();
+ ERR_FAIL_NULL(env);
+
+ env->DeleteGlobalRef(_class);
+ }
+}
+
/////////////////////
Variant JavaObject::callp(const StringName &p_method, const Variant **p_args, int p_argcount, Callable::CallError &r_error) {
- return Variant();
+ if (instance) {
+ Ref<JavaClass> c = base_class;
+ while (c.is_valid()) {
+ Variant ret;
+ bool found = c->_call_method(this, p_method, p_args, p_argcount, r_error, ret);
+ if (found) {
+ return ret;
+ }
+ c = c->get_java_parent_class();
+ }
+ }
+
+ return RefCounted::callp(p_method, p_args, p_argcount, r_error);
+}
+
+Ref<JavaClass> JavaObject::get_java_class() const {
+ return base_class;
}
-JavaObject::JavaObject(const Ref<JavaClass> &p_base, jobject *p_instance) {
+String JavaObject::to_string() {
+ if (base_class.is_valid() && instance) {
+ return "<JavaObject:" + base_class->java_class_name + " \"" + (String)call("toString") + "\">";
+ }
+ return RefCounted::to_string();
+}
+
+JavaObject::JavaObject() {
+}
+
+JavaObject::JavaObject(const Ref<JavaClass> &p_base, jobject p_instance) {
+ JNIEnv *env = get_jni_env();
+ ERR_FAIL_NULL(env);
+
+ base_class = p_base;
+ instance = env->NewGlobalRef(p_instance);
}
JavaObject::~JavaObject() {
+ if (instance) {
+ JNIEnv *env = get_jni_env();
+ ERR_FAIL_NULL(env);
+
+ env->DeleteGlobalRef(instance);
+ }
}
////////////////////
@@ -649,6 +794,16 @@ bool JavaClass::_convert_object_to_variant(JNIEnv *env, jobject obj, Variant &va
return true;
} break;
case ARG_TYPE_CLASS: {
+ jclass java_class = env->GetObjectClass(obj);
+ Ref<JavaClass> java_class_wrapped = JavaClassWrapper::singleton->wrap_jclass(java_class);
+ env->DeleteLocalRef(java_class);
+
+ if (java_class_wrapped.is_valid()) {
+ Ref<JavaObject> ret = Ref<JavaObject>(memnew(JavaObject(java_class_wrapped, obj)));
+ var = ret;
+ return true;
+ }
+
return false;
} break;
case ARG_ARRAY_BIT | ARG_TYPE_VOID: {
@@ -966,43 +1121,67 @@ bool JavaClass::_convert_object_to_variant(JNIEnv *env, jobject obj, Variant &va
}
Ref<JavaClass> JavaClassWrapper::wrap(const String &p_class) {
- if (class_cache.has(p_class)) {
- return class_cache[p_class];
+ String class_name_dots = p_class.replace("/", ".");
+ if (class_cache.has(class_name_dots)) {
+ return class_cache[class_name_dots];
}
JNIEnv *env = get_jni_env();
ERR_FAIL_NULL_V(env, Ref<JavaClass>());
- jclass bclass = env->FindClass(p_class.utf8().get_data());
+ jclass bclass = env->FindClass(class_name_dots.replace(".", "/").utf8().get_data());
ERR_FAIL_NULL_V(bclass, Ref<JavaClass>());
- jobjectArray methods = (jobjectArray)env->CallObjectMethod(bclass, getDeclaredMethods);
+ jobjectArray constructors = (jobjectArray)env->CallObjectMethod(bclass, Class_getDeclaredConstructors);
+ ERR_FAIL_NULL_V(constructors, Ref<JavaClass>());
+ jobjectArray methods = (jobjectArray)env->CallObjectMethod(bclass, Class_getDeclaredMethods);
ERR_FAIL_NULL_V(methods, Ref<JavaClass>());
Ref<JavaClass> java_class = memnew(JavaClass);
+ java_class->java_class_name = class_name_dots;
+ Vector<String> class_name_parts = class_name_dots.split(".");
+ java_class->java_constructor_name = class_name_parts[class_name_parts.size() - 1];
+ java_class->_class = (jclass)env->NewGlobalRef(bclass);
+ class_cache[class_name_dots] = java_class;
+
+ LocalVector<jobject> methods_and_constructors;
+ int constructor_count = env->GetArrayLength(constructors);
+ int method_count = env->GetArrayLength(methods);
+ methods_and_constructors.resize(method_count + constructor_count);
+ for (int i = 0; i < constructor_count; i++) {
+ methods_and_constructors[i] = env->GetObjectArrayElement(constructors, i);
+ }
+ for (int i = 0; i < method_count; i++) {
+ methods_and_constructors[constructor_count + i] = env->GetObjectArrayElement(methods, i);
+ }
- int count = env->GetArrayLength(methods);
-
- for (int i = 0; i < count; i++) {
- jobject obj = env->GetObjectArrayElement(methods, i);
+ for (int i = 0; i < (int)methods_and_constructors.size(); i++) {
+ jobject obj = methods_and_constructors[i];
ERR_CONTINUE(!obj);
- jstring name = (jstring)env->CallObjectMethod(obj, getName);
- String str_method = jstring_to_string(name, env);
- env->DeleteLocalRef(name);
+ bool is_constructor = i < constructor_count;
+
+ String str_method;
+ if (is_constructor) {
+ str_method = "<init>";
+ } else {
+ jstring name = (jstring)env->CallObjectMethod(obj, Method_getName);
+ str_method = jstring_to_string(name, env);
+ env->DeleteLocalRef(name);
+ }
Vector<String> params;
- jint mods = env->CallIntMethod(obj, getModifiers);
+ jint mods = env->CallIntMethod(obj, is_constructor ? Constructor_getModifiers : Method_getModifiers);
if (!(mods & 0x0001)) {
env->DeleteLocalRef(obj);
continue; //not public bye
}
- jobjectArray param_types = (jobjectArray)env->CallObjectMethod(obj, getParameterTypes);
- int count2 = env->GetArrayLength(param_types);
+ jobjectArray param_types = (jobjectArray)env->CallObjectMethod(obj, is_constructor ? Constructor_getParameterTypes : Method_getParameterTypes);
+ int count = env->GetArrayLength(param_types);
if (!java_class->methods.has(str_method)) {
java_class->methods[str_method] = List<JavaClass::MethodInfo>();
@@ -1010,10 +1189,11 @@ Ref<JavaClass> JavaClassWrapper::wrap(const String &p_class) {
JavaClass::MethodInfo mi;
mi._static = (mods & 0x8) != 0;
+ mi._constructor = is_constructor;
bool valid = true;
String signature = "(";
- for (int j = 0; j < count2; j++) {
+ for (int j = 0; j < count; j++) {
jobject obj2 = env->GetObjectArrayElement(param_types, j);
String strsig;
uint32_t sig = 0;
@@ -1029,7 +1209,7 @@ Ref<JavaClass> JavaClassWrapper::wrap(const String &p_class) {
}
if (!valid) {
- print_line("Method can't be bound (unsupported arguments): " + p_class + "::" + str_method);
+ print_line("Method can't be bound (unsupported arguments): " + class_name_dots + "::" + str_method);
env->DeleteLocalRef(obj);
env->DeleteLocalRef(param_types);
continue;
@@ -1037,21 +1217,28 @@ Ref<JavaClass> JavaClassWrapper::wrap(const String &p_class) {
signature += ")";
- jobject return_type = (jobject)env->CallObjectMethod(obj, getReturnType);
+ if (is_constructor) {
+ signature += "V";
+ mi.return_type = JavaClass::ARG_TYPE_CLASS;
+ } else {
+ jobject return_type = (jobject)env->CallObjectMethod(obj, Method_getReturnType);
+
+ String strsig;
+ uint32_t sig = 0;
+ if (!_get_type_sig(env, return_type, sig, strsig)) {
+ print_line("Method can't be bound (unsupported return type): " + class_name_dots + "::" + str_method);
+ env->DeleteLocalRef(obj);
+ env->DeleteLocalRef(param_types);
+ env->DeleteLocalRef(return_type);
+ continue;
+ }
+
+ signature += strsig;
+ mi.return_type = sig;
- String strsig;
- uint32_t sig = 0;
- if (!_get_type_sig(env, return_type, sig, strsig)) {
- print_line("Method can't be bound (unsupported return type): " + p_class + "::" + str_method);
- env->DeleteLocalRef(obj);
- env->DeleteLocalRef(param_types);
env->DeleteLocalRef(return_type);
- continue;
}
- signature += strsig;
- mi.return_type = sig;
-
bool discard = false;
for (List<JavaClass::MethodInfo>::Element *E = java_class->methods[str_method].front(); E; E = E->next()) {
@@ -1103,14 +1290,14 @@ Ref<JavaClass> JavaClassWrapper::wrap(const String &p_class) {
env->DeleteLocalRef(obj);
env->DeleteLocalRef(param_types);
- env->DeleteLocalRef(return_type);
}
+ env->DeleteLocalRef(constructors);
env->DeleteLocalRef(methods);
- jobjectArray fields = (jobjectArray)env->CallObjectMethod(bclass, getFields);
+ jobjectArray fields = (jobjectArray)env->CallObjectMethod(bclass, Class_getFields);
- count = env->GetArrayLength(fields);
+ int count = env->GetArrayLength(fields);
for (int i = 0; i < count; i++) {
jobject obj = env->GetObjectArrayElement(fields, i);
@@ -1146,7 +1333,18 @@ Ref<JavaClass> JavaClassWrapper::wrap(const String &p_class) {
env->DeleteLocalRef(fields);
- return Ref<JavaClass>();
+ return java_class;
+}
+
+Ref<JavaClass> JavaClassWrapper::wrap_jclass(jclass p_class) {
+ JNIEnv *env = get_jni_env();
+ ERR_FAIL_NULL_V(env, Ref<JavaClass>());
+
+ jstring class_name = (jstring)env->CallObjectMethod(p_class, Class_getName);
+ String class_name_string = jstring_to_string(class_name, env);
+ env->DeleteLocalRef(class_name);
+
+ return wrap(class_name_string);
}
JavaClassWrapper *JavaClassWrapper::singleton = nullptr;
@@ -1158,16 +1356,23 @@ JavaClassWrapper::JavaClassWrapper(jobject p_activity) {
ERR_FAIL_NULL(env);
jclass bclass = env->FindClass("java/lang/Class");
- getDeclaredMethods = env->GetMethodID(bclass, "getDeclaredMethods", "()[Ljava/lang/reflect/Method;");
- getFields = env->GetMethodID(bclass, "getFields", "()[Ljava/lang/reflect/Field;");
+ Class_getDeclaredConstructors = env->GetMethodID(bclass, "getDeclaredConstructors", "()[Ljava/lang/reflect/Constructor;");
+ Class_getDeclaredMethods = env->GetMethodID(bclass, "getDeclaredMethods", "()[Ljava/lang/reflect/Method;");
+ Class_getFields = env->GetMethodID(bclass, "getFields", "()[Ljava/lang/reflect/Field;");
Class_getName = env->GetMethodID(bclass, "getName", "()Ljava/lang/String;");
+ Class_getSuperclass = env->GetMethodID(bclass, "getSuperclass", "()Ljava/lang/Class;");
+ env->DeleteLocalRef(bclass);
+
+ bclass = env->FindClass("java/lang/reflect/Constructor");
+ Constructor_getParameterTypes = env->GetMethodID(bclass, "getParameterTypes", "()[Ljava/lang/Class;");
+ Constructor_getModifiers = env->GetMethodID(bclass, "getModifiers", "()I");
env->DeleteLocalRef(bclass);
bclass = env->FindClass("java/lang/reflect/Method");
- getParameterTypes = env->GetMethodID(bclass, "getParameterTypes", "()[Ljava/lang/Class;");
- getReturnType = env->GetMethodID(bclass, "getReturnType", "()Ljava/lang/Class;");
- getName = env->GetMethodID(bclass, "getName", "()Ljava/lang/String;");
- getModifiers = env->GetMethodID(bclass, "getModifiers", "()I");
+ Method_getParameterTypes = env->GetMethodID(bclass, "getParameterTypes", "()[Ljava/lang/Class;");
+ Method_getReturnType = env->GetMethodID(bclass, "getReturnType", "()Ljava/lang/Class;");
+ Method_getName = env->GetMethodID(bclass, "getName", "()Ljava/lang/String;");
+ Method_getModifiers = env->GetMethodID(bclass, "getModifiers", "()I");
env->DeleteLocalRef(bclass);
bclass = env->FindClass("java/lang/reflect/Field");
diff --git a/platform/android/java_godot_lib_jni.cpp b/platform/android/java_godot_lib_jni.cpp
index 1114969de8..390677df22 100644
--- a/platform/android/java_godot_lib_jni.cpp
+++ b/platform/android/java_godot_lib_jni.cpp
@@ -51,7 +51,10 @@
#include "core/config/project_settings.h"
#include "core/input/input.h"
#include "main/main.h"
+
+#ifndef _3D_DISABLED
#include "servers/xr_server.h"
+#endif // _3D_DISABLED
#ifdef TOOLS_ENABLED
#include "editor/editor_settings.h"
@@ -271,14 +274,16 @@ JNIEXPORT jboolean JNICALL Java_org_godotengine_godot_GodotLib_step(JNIEnv *env,
}
if (step.get() == STEP_SHOW_LOGO) {
- bool xr_enabled;
+ bool xr_enabled = false;
+#ifndef _3D_DISABLED
+ // Unlike PCVR, there's no additional 2D screen onto which to render the boot logo,
+ // so we skip this step if xr is enabled.
if (XRServer::get_xr_mode() == XRServer::XRMODE_DEFAULT) {
xr_enabled = GLOBAL_GET("xr/shaders/enabled");
} else {
xr_enabled = XRServer::get_xr_mode() == XRServer::XRMODE_ON;
}
- // Unlike PCVR, there's no additional 2D screen onto which to render the boot logo,
- // so we skip this step if xr is enabled.
+#endif // _3D_DISABLED
if (!xr_enabled) {
Main::setup_boot_logo();
}
diff --git a/platform/android/java_godot_wrapper.cpp b/platform/android/java_godot_wrapper.cpp
index f1759af54a..d3b30e4589 100644
--- a/platform/android/java_godot_wrapper.cpp
+++ b/platform/android/java_godot_wrapper.cpp
@@ -86,6 +86,8 @@ GodotJavaWrapper::GodotJavaWrapper(JNIEnv *p_env, jobject p_activity, jobject p_
_has_feature = p_env->GetMethodID(godot_class, "hasFeature", "(Ljava/lang/String;)Z");
_sign_apk = p_env->GetMethodID(godot_class, "nativeSignApk", "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)I");
_verify_apk = p_env->GetMethodID(godot_class, "nativeVerifyApk", "(Ljava/lang/String;)I");
+ _enable_immersive_mode = p_env->GetMethodID(godot_class, "nativeEnableImmersiveMode", "(Z)V");
+ _is_in_immersive_mode = p_env->GetMethodID(godot_class, "isInImmersiveMode", "()Z");
}
GodotJavaWrapper::~GodotJavaWrapper() {
@@ -465,3 +467,21 @@ Error GodotJavaWrapper::verify_apk(const String &p_apk_path) {
return ERR_UNCONFIGURED;
}
}
+
+void GodotJavaWrapper::enable_immersive_mode(bool p_enabled) {
+ if (_enable_immersive_mode) {
+ JNIEnv *env = get_jni_env();
+ ERR_FAIL_NULL(env);
+ env->CallVoidMethod(godot_instance, _enable_immersive_mode, p_enabled);
+ }
+}
+
+bool GodotJavaWrapper::is_in_immersive_mode() {
+ if (_is_in_immersive_mode) {
+ JNIEnv *env = get_jni_env();
+ ERR_FAIL_NULL_V(env, false);
+ return env->CallBooleanMethod(godot_instance, _is_in_immersive_mode);
+ } else {
+ return false;
+ }
+}
diff --git a/platform/android/java_godot_wrapper.h b/platform/android/java_godot_wrapper.h
index 6b66565981..51d7f98541 100644
--- a/platform/android/java_godot_wrapper.h
+++ b/platform/android/java_godot_wrapper.h
@@ -77,6 +77,8 @@ private:
jmethodID _has_feature = nullptr;
jmethodID _sign_apk = nullptr;
jmethodID _verify_apk = nullptr;
+ jmethodID _enable_immersive_mode = nullptr;
+ jmethodID _is_in_immersive_mode = nullptr;
public:
GodotJavaWrapper(JNIEnv *p_env, jobject p_activity, jobject p_godot_instance);
@@ -122,6 +124,9 @@ public:
// Sign and verify apks
Error sign_apk(const String &p_input_path, const String &p_output_path, const String &p_keystore_path, const String &p_keystore_user, const String &p_keystore_password);
Error verify_apk(const String &p_apk_path);
+
+ void enable_immersive_mode(bool p_enabled);
+ bool is_in_immersive_mode();
};
#endif // JAVA_GODOT_WRAPPER_H
diff --git a/platform/android/jni_utils.cpp b/platform/android/jni_utils.cpp
index fc97d6eca4..4c17d03c60 100644
--- a/platform/android/jni_utils.cpp
+++ b/platform/android/jni_utils.cpp
@@ -30,6 +30,8 @@
#include "jni_utils.h"
+#include "api/java_class_wrapper.h"
+
jvalret _variant_to_jvalue(JNIEnv *env, Variant::Type p_type, const Variant *p_arg, bool force_jobject) {
jvalret v;
@@ -185,6 +187,16 @@ jvalret _variant_to_jvalue(JNIEnv *env, Variant::Type p_type, const Variant *p_a
v.obj = arr;
} break;
+ case Variant::OBJECT: {
+ Ref<JavaObject> generic_object = *p_arg;
+ if (generic_object.is_valid()) {
+ jobject obj = env->NewLocalRef(generic_object->get_instance());
+ v.val.l = obj;
+ v.obj = obj;
+ } else {
+ v.val.i = 0;
+ }
+ } break;
default: {
v.val.i = 0;
@@ -358,9 +370,11 @@ Variant _jobject_to_variant(JNIEnv *env, jobject obj) {
return ret;
}
+ Ref<JavaObject> generic_object(memnew(JavaObject(JavaClassWrapper::get_singleton()->wrap(name), obj)));
+
env->DeleteLocalRef(c);
- return Variant();
+ return generic_object;
}
Variant::Type get_jni_type(const String &p_type) {
@@ -395,10 +409,10 @@ Variant::Type get_jni_type(const String &p_type) {
idx++;
}
- return Variant::NIL;
+ return Variant::OBJECT;
}
-const char *get_jni_sig(const String &p_type) {
+String get_jni_sig(const String &p_type) {
static struct {
const char *name;
const char *sig;
@@ -430,5 +444,5 @@ const char *get_jni_sig(const String &p_type) {
idx++;
}
- return "Ljava/lang/Object;";
+ return "L" + p_type.replace(".", "/") + ";";
}
diff --git a/platform/android/jni_utils.h b/platform/android/jni_utils.h
index c608f9ebaa..631acd1cef 100644
--- a/platform/android/jni_utils.h
+++ b/platform/android/jni_utils.h
@@ -52,6 +52,6 @@ Variant _jobject_to_variant(JNIEnv *env, jobject obj);
Variant::Type get_jni_type(const String &p_type);
-const char *get_jni_sig(const String &p_type);
+String get_jni_sig(const String &p_type);
#endif // JNI_UTILS_H
diff --git a/platform/android/plugin/godot_plugin_jni.cpp b/platform/android/plugin/godot_plugin_jni.cpp
index e3cde145cb..75c8dd9528 100644
--- a/platform/android/plugin/godot_plugin_jni.cpp
+++ b/platform/android/plugin/godot_plugin_jni.cpp
@@ -35,7 +35,6 @@
#include "string_android.h"
#include "core/config/engine.h"
-#include "core/config/project_settings.h"
#include "core/error/error_macros.h"
static HashMap<String, JNISingleton *> jni_singletons;
@@ -43,7 +42,6 @@ static HashMap<String, JNISingleton *> jni_singletons;
void unregister_plugins_singletons() {
for (const KeyValue<String, JNISingleton *> &E : jni_singletons) {
Engine::get_singleton()->remove_singleton(E.key);
- ProjectSettings::get_singleton()->set(E.key, Variant());
if (E.value) {
memdelete(E.value);
@@ -64,7 +62,6 @@ JNIEXPORT jboolean JNICALL Java_org_godotengine_godot_plugin_GodotPlugin_nativeR
jni_singletons[singname] = s;
Engine::get_singleton()->add_singleton(Engine::Singleton(singname, s));
- ProjectSettings::get_singleton()->set(singname, s);
return true;
}
diff --git a/platform/ios/export/export_plugin.cpp b/platform/ios/export/export_plugin.cpp
index e4b5392c4e..b99e825540 100644
--- a/platform/ios/export/export_plugin.cpp
+++ b/platform/ios/export/export_plugin.cpp
@@ -2017,11 +2017,11 @@ Error EditorExportPlatformIOS::_export_ios_plugins(const Ref<EditorExportPreset>
return OK;
}
-Error EditorExportPlatformIOS::export_project(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, int p_flags) {
+Error EditorExportPlatformIOS::export_project(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, BitField<EditorExportPlatform::DebugFlags> p_flags) {
return _export_project_helper(p_preset, p_debug, p_path, p_flags, false, false);
}
-Error EditorExportPlatformIOS::_export_project_helper(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, int p_flags, bool p_simulator, bool p_oneclick) {
+Error EditorExportPlatformIOS::_export_project_helper(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, BitField<EditorExportPlatform::DebugFlags> p_flags, bool p_simulator, bool p_oneclick) {
ExportNotifier notifier(*this, p_preset, p_debug, p_path, p_flags);
const String dest_dir = p_path.get_base_dir() + "/";
@@ -2983,7 +2983,7 @@ void EditorExportPlatformIOS::_update_preset_status() {
}
#endif
-Error EditorExportPlatformIOS::run(const Ref<EditorExportPreset> &p_preset, int p_device, int p_debug_flags) {
+Error EditorExportPlatformIOS::run(const Ref<EditorExportPreset> &p_preset, int p_device, BitField<EditorExportPlatform::DebugFlags> p_debug_flags) {
#ifdef MACOS_ENABLED
ERR_FAIL_INDEX_V(p_device, devices.size(), ERR_INVALID_PARAMETER);
@@ -3029,11 +3029,11 @@ Error EditorExportPlatformIOS::run(const Ref<EditorExportPreset> &p_preset, int
String host = EDITOR_GET("network/debug/remote_host");
int remote_port = (int)EDITOR_GET("network/debug/remote_port");
- if (p_debug_flags & DEBUG_FLAG_REMOTE_DEBUG_LOCALHOST) {
+ if (p_debug_flags.has_flag(DEBUG_FLAG_REMOTE_DEBUG_LOCALHOST)) {
host = "localhost";
}
- if (p_debug_flags & DEBUG_FLAG_DUMB_CLIENT) {
+ if (p_debug_flags.has_flag(DEBUG_FLAG_DUMB_CLIENT)) {
int port = EDITOR_GET("filesystem/file_server/port");
String passwd = EDITOR_GET("filesystem/file_server/password");
cmd_args_list.push_back("--remote-fs");
@@ -3044,7 +3044,7 @@ Error EditorExportPlatformIOS::run(const Ref<EditorExportPreset> &p_preset, int
}
}
- if (p_debug_flags & DEBUG_FLAG_REMOTE_DEBUG) {
+ if (p_debug_flags.has_flag(DEBUG_FLAG_REMOTE_DEBUG)) {
cmd_args_list.push_back("--remote-debug");
cmd_args_list.push_back(get_debug_protocol() + host + ":" + String::num(remote_port));
@@ -3066,11 +3066,11 @@ Error EditorExportPlatformIOS::run(const Ref<EditorExportPreset> &p_preset, int
}
}
- if (p_debug_flags & DEBUG_FLAG_VIEW_COLLISIONS) {
+ if (p_debug_flags.has_flag(DEBUG_FLAG_VIEW_COLLISIONS)) {
cmd_args_list.push_back("--debug-collisions");
}
- if (p_debug_flags & DEBUG_FLAG_VIEW_NAVIGATION) {
+ if (p_debug_flags.has_flag(DEBUG_FLAG_VIEW_NAVIGATION)) {
cmd_args_list.push_back("--debug-navigation");
}
diff --git a/platform/ios/export/export_plugin.h b/platform/ios/export/export_plugin.h
index 1964906c27..db7c0553dd 100644
--- a/platform/ios/export/export_plugin.h
+++ b/platform/ios/export/export_plugin.h
@@ -146,7 +146,7 @@ class EditorExportPlatformIOS : public EditorExportPlatform {
Error _export_additional_assets(const Ref<EditorExportPreset> &p_preset, const String &p_out_dir, const Vector<SharedObject> &p_libraries, Vector<IOSExportAsset> &r_exported_assets);
Error _export_ios_plugins(const Ref<EditorExportPreset> &p_preset, IOSConfigData &p_config_data, const String &dest_dir, Vector<IOSExportAsset> &r_exported_assets, bool p_debug);
- Error _export_project_helper(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, int p_flags, bool p_simulator, bool p_oneclick);
+ Error _export_project_helper(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, BitField<EditorExportPlatform::DebugFlags> p_flags, bool p_simulator, bool p_oneclick);
bool is_package_name_valid(const String &p_package, String *r_error = nullptr) const;
@@ -169,7 +169,7 @@ public:
virtual Ref<ImageTexture> get_option_icon(int p_index) const override;
virtual String get_option_label(int p_index) const override;
virtual String get_option_tooltip(int p_index) const override;
- virtual Error run(const Ref<EditorExportPreset> &p_preset, int p_device, int p_debug_flags) override;
+ virtual Error run(const Ref<EditorExportPreset> &p_preset, int p_device, BitField<EditorExportPlatform::DebugFlags> p_debug_flags) override;
virtual bool poll_export() override {
bool dc = devices_changed.is_set();
@@ -202,7 +202,7 @@ public:
return list;
}
- virtual Error export_project(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, int p_flags = 0) override;
+ virtual Error export_project(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, BitField<EditorExportPlatform::DebugFlags> p_flags = 0) override;
virtual bool has_valid_export_configuration(const Ref<EditorExportPreset> &p_preset, String &r_error, bool &r_missing_templates, bool p_debug = false) const override;
virtual bool has_valid_project_configuration(const Ref<EditorExportPreset> &p_preset, String &r_error) const override;
diff --git a/platform/linuxbsd/export/export_plugin.cpp b/platform/linuxbsd/export/export_plugin.cpp
index 0032b898d2..69ba742f72 100644
--- a/platform/linuxbsd/export/export_plugin.cpp
+++ b/platform/linuxbsd/export/export_plugin.cpp
@@ -60,7 +60,7 @@ Error EditorExportPlatformLinuxBSD::_export_debug_script(const Ref<EditorExportP
return OK;
}
-Error EditorExportPlatformLinuxBSD::export_project(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, int p_flags) {
+Error EditorExportPlatformLinuxBSD::export_project(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, BitField<EditorExportPlatform::DebugFlags> p_flags) {
String custom_debug = p_preset->get("custom_template/debug");
String custom_release = p_preset->get("custom_template/release");
String arch = p_preset->get("binary_format/architecture");
@@ -458,7 +458,7 @@ void EditorExportPlatformLinuxBSD::cleanup() {
cleanup_commands.clear();
}
-Error EditorExportPlatformLinuxBSD::run(const Ref<EditorExportPreset> &p_preset, int p_device, int p_debug_flags) {
+Error EditorExportPlatformLinuxBSD::run(const Ref<EditorExportPreset> &p_preset, int p_device, BitField<EditorExportPlatform::DebugFlags> p_debug_flags) {
cleanup();
if (p_device) { // Stop command, cleanup only.
return OK;
@@ -512,8 +512,7 @@ Error EditorExportPlatformLinuxBSD::run(const Ref<EditorExportPreset> &p_preset,
String cmd_args;
{
- Vector<String> cmd_args_list;
- gen_debug_flags(cmd_args_list, p_debug_flags);
+ Vector<String> cmd_args_list = gen_export_flags(p_debug_flags);
for (int i = 0; i < cmd_args_list.size(); i++) {
if (i != 0) {
cmd_args += " ";
@@ -522,7 +521,7 @@ Error EditorExportPlatformLinuxBSD::run(const Ref<EditorExportPreset> &p_preset,
}
}
- const bool use_remote = (p_debug_flags & DEBUG_FLAG_REMOTE_DEBUG) || (p_debug_flags & DEBUG_FLAG_DUMB_CLIENT);
+ const bool use_remote = p_debug_flags.has_flag(DEBUG_FLAG_REMOTE_DEBUG) || p_debug_flags.has_flag(DEBUG_FLAG_DUMB_CLIENT);
int dbg_port = EditorSettings::get_singleton()->get("network/debug/remote_port");
print_line("Creating temporary directory...");
diff --git a/platform/linuxbsd/export/export_plugin.h b/platform/linuxbsd/export/export_plugin.h
index bbc55b82ce..1d9ef01d1a 100644
--- a/platform/linuxbsd/export/export_plugin.h
+++ b/platform/linuxbsd/export/export_plugin.h
@@ -76,7 +76,7 @@ public:
virtual List<String> get_binary_extensions(const Ref<EditorExportPreset> &p_preset) const override;
virtual bool get_export_option_visibility(const EditorExportPreset *p_preset, const String &p_option) const override;
virtual bool has_valid_export_configuration(const Ref<EditorExportPreset> &p_preset, String &r_error, bool &r_missing_templates, bool p_debug = false) const override;
- virtual Error export_project(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, int p_flags = 0) override;
+ virtual Error export_project(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, BitField<EditorExportPlatform::DebugFlags> p_flags = 0) override;
virtual String get_template_file_name(const String &p_target, const String &p_arch) const override;
virtual Error fixup_embedded_pck(const String &p_path, int64_t p_embedded_start, int64_t p_embedded_size) override;
virtual bool is_executable(const String &p_path) const override;
@@ -87,7 +87,7 @@ public:
virtual int get_options_count() const override;
virtual String get_option_label(int p_index) const override;
virtual String get_option_tooltip(int p_index) const override;
- virtual Error run(const Ref<EditorExportPreset> &p_preset, int p_device, int p_debug_flags) override;
+ virtual Error run(const Ref<EditorExportPreset> &p_preset, int p_device, BitField<EditorExportPlatform::DebugFlags> p_debug_flags) override;
virtual void cleanup() override;
EditorExportPlatformLinuxBSD();
diff --git a/platform/linuxbsd/freedesktop_portal_desktop.cpp b/platform/linuxbsd/freedesktop_portal_desktop.cpp
index 671da7fc2a..94a748e414 100644
--- a/platform/linuxbsd/freedesktop_portal_desktop.cpp
+++ b/platform/linuxbsd/freedesktop_portal_desktop.cpp
@@ -210,7 +210,15 @@ void FreeDesktopPortalDesktop::append_dbus_dict_filters(DBusMessageIter *p_iter,
append_dbus_string(&struct_iter, p_filter_names[i]);
dbus_message_iter_open_container(&struct_iter, DBUS_TYPE_ARRAY, "(us)", &array_iter);
- const String &flt = p_filter_exts[i];
+ const String &flt_orig = p_filter_exts[i];
+ String flt;
+ for (int j = 0; j < flt_orig.length(); j++) {
+ if (is_unicode_letter(flt_orig[j])) {
+ flt += vformat("[%c%c]", String::char_lowercase(flt_orig[j]), String::char_uppercase(flt_orig[j]));
+ } else {
+ flt += flt_orig[j];
+ }
+ }
int filter_slice_count = flt.get_slice_count(",");
for (int j = 0; j < filter_slice_count; j++) {
dbus_message_iter_open_container(&array_iter, DBUS_TYPE_STRUCT, nullptr, &array_struct_iter);
@@ -377,17 +385,26 @@ Error FreeDesktopPortalDesktop::file_dialog_show(DisplayServer::WindowID p_windo
String flt = tokens[0].strip_edges();
if (!flt.is_empty()) {
if (tokens.size() == 2) {
- filter_exts.push_back(flt);
+ if (flt == "*.*") {
+ filter_exts.push_back("*");
+ } else {
+ filter_exts.push_back(flt);
+ }
filter_names.push_back(tokens[1]);
} else {
- filter_exts.push_back(flt);
- filter_names.push_back(flt);
+ if (flt == "*.*") {
+ filter_exts.push_back("*");
+ filter_names.push_back(RTR("All Files"));
+ } else {
+ filter_exts.push_back(flt);
+ filter_names.push_back(flt);
+ }
}
}
}
}
if (filter_names.is_empty()) {
- filter_exts.push_back("*.*");
+ filter_exts.push_back("*");
filter_names.push_back(RTR("All Files"));
}
diff --git a/platform/linuxbsd/wayland/display_server_wayland.cpp b/platform/linuxbsd/wayland/display_server_wayland.cpp
index 93096fcdcc..2074d7228d 100644
--- a/platform/linuxbsd/wayland/display_server_wayland.cpp
+++ b/platform/linuxbsd/wayland/display_server_wayland.cpp
@@ -480,7 +480,7 @@ String DisplayServerWayland::clipboard_get_primary() const {
for (String mime : text_mimes) {
if (wayland_thread.primary_has_mime(mime)) {
print_verbose(vformat("Selecting media type \"%s\" from offered types.", mime));
- wayland_thread.primary_get_mime(mime);
+ data = wayland_thread.primary_get_mime(mime);
break;
}
}
@@ -1349,23 +1349,39 @@ DisplayServerWayland::DisplayServerWayland(const String &p_rendering_driver, Win
rendering_driver = p_rendering_driver;
+ bool driver_found = false;
+ String executable_name = OS::get_singleton()->get_executable_path().get_file();
+
#ifdef RD_ENABLED
#ifdef VULKAN_ENABLED
if (rendering_driver == "vulkan") {
rendering_context = memnew(RenderingContextDriverVulkanWayland);
}
-#endif
+#endif // VULKAN_ENABLED
if (rendering_context) {
if (rendering_context->initialize() != OK) {
- ERR_PRINT(vformat("Could not initialize %s", rendering_driver));
memdelete(rendering_context);
rendering_context = nullptr;
r_error = ERR_CANT_CREATE;
- return;
+
+ if (p_rendering_driver == "vulkan") {
+ OS::get_singleton()->alert(
+ vformat("Your video card drivers seem not to support the required Vulkan version.\n\n"
+ "If possible, consider updating your video card drivers or using the OpenGL 3 driver.\n\n"
+ "You can enable the OpenGL 3 driver by starting the engine from the\n"
+ "command line with the command:\n\n \"%s\" --rendering-driver opengl3\n\n"
+ "If you recently updated your video card drivers, try rebooting.",
+ executable_name),
+ "Unable to initialize Vulkan video driver");
+ }
+
+ ERR_FAIL_MSG(vformat("Could not initialize %s", rendering_driver));
}
+
+ driver_found = true;
}
-#endif
+#endif // RD_ENABLED
#ifdef GLES3_ENABLED
if (rendering_driver == "opengl3" || rendering_driver == "opengl3_es") {
@@ -1429,28 +1445,56 @@ DisplayServerWayland::DisplayServerWayland(const String &p_rendering_driver, Win
if (fallback) {
WARN_PRINT("Your video card drivers seem not to support the required OpenGL version, switching to OpenGLES.");
rendering_driver = "opengl3_es";
+ OS::get_singleton()->set_current_rendering_driver_name(rendering_driver);
} else {
r_error = ERR_UNAVAILABLE;
+
+ OS::get_singleton()->alert(
+ vformat("Your video card drivers seem not to support the required OpenGL 3.3 version.\n\n"
+ "If possible, consider updating your video card drivers or using the Vulkan driver.\n\n"
+ "You can enable the Vulkan driver by starting the engine from the\n"
+ "command line with the command:\n\n \"%s\" --rendering-driver vulkan\n\n"
+ "If you recently updated your video card drivers, try rebooting.",
+ executable_name),
+ "Unable to initialize OpenGL video driver");
+
ERR_FAIL_MSG("Could not initialize OpenGL.");
}
} else {
RasterizerGLES3::make_current(true);
+ driver_found = true;
}
}
if (rendering_driver == "opengl3_es") {
egl_manager = memnew(EGLManagerWaylandGLES);
- if (egl_manager->initialize(wayland_thread.get_wl_display()) != OK) {
+ if (egl_manager->initialize(wayland_thread.get_wl_display()) != OK || egl_manager->open_display(wayland_thread.get_wl_display()) != OK) {
memdelete(egl_manager);
egl_manager = nullptr;
r_error = ERR_CANT_CREATE;
- ERR_FAIL_MSG("Could not initialize GLES3.");
+
+ OS::get_singleton()->alert(
+ vformat("Your video card drivers seem not to support the required OpenGL ES 3.0 version.\n\n"
+ "If possible, consider updating your video card drivers or using the Vulkan driver.\n\n"
+ "You can enable the Vulkan driver by starting the engine from the\n"
+ "command line with the command:\n\n \"%s\" --rendering-driver vulkan\n\n"
+ "If you recently updated your video card drivers, try rebooting.",
+ executable_name),
+ "Unable to initialize OpenGL ES video driver");
+
+ ERR_FAIL_MSG("Could not initialize OpenGL ES.");
}
RasterizerGLES3::make_current(false);
+ driver_found = true;
}
}
+
+ if (!driver_found) {
+ r_error = ERR_UNAVAILABLE;
+ ERR_FAIL_MSG("Video driver not found.");
+ }
#endif // GLES3_ENABLED
cursor_set_shape(CURSOR_BUSY);
@@ -1481,12 +1525,12 @@ DisplayServerWayland::DisplayServerWayland(const String &p_rendering_driver, Win
RendererCompositorRD::make_current();
}
-#endif
+#endif // RD_ENABLED
#ifdef DBUS_ENABLED
portal_desktop = memnew(FreeDesktopPortalDesktop);
screensaver = memnew(FreeDesktopScreenSaver);
-#endif
+#endif // DBUS_ENABLED
screen_set_keep_on(GLOBAL_GET("display/window/energy_saving/keep_screen_on"));
diff --git a/platform/linuxbsd/x11/display_server_x11.cpp b/platform/linuxbsd/x11/display_server_x11.cpp
index 8a2f83be2d..840cadace3 100644
--- a/platform/linuxbsd/x11/display_server_x11.cpp
+++ b/platform/linuxbsd/x11/display_server_x11.cpp
@@ -5428,25 +5428,6 @@ Vector<String> DisplayServerX11::get_rendering_drivers_func() {
DisplayServer *DisplayServerX11::create_func(const String &p_rendering_driver, WindowMode p_mode, VSyncMode p_vsync_mode, uint32_t p_flags, const Vector2i *p_position, const Vector2i &p_resolution, int p_screen, Context p_context, Error &r_error) {
DisplayServer *ds = memnew(DisplayServerX11(p_rendering_driver, p_mode, p_vsync_mode, p_flags, p_position, p_resolution, p_screen, p_context, r_error));
- if (r_error != OK) {
- if (p_rendering_driver == "vulkan") {
- String executable_name = OS::get_singleton()->get_executable_path().get_file();
- OS::get_singleton()->alert(
- vformat("Your video card drivers seem not to support the required Vulkan version.\n\n"
- "If possible, consider updating your video card drivers or using the OpenGL 3 driver.\n\n"
- "You can enable the OpenGL 3 driver by starting the engine from the\n"
- "command line with the command:\n\n \"%s\" --rendering-driver opengl3\n\n"
- "If you recently updated your video card drivers, try rebooting.",
- executable_name),
- "Unable to initialize Vulkan video driver");
- } else {
- OS::get_singleton()->alert(
- "Your video card drivers seem not to support the required OpenGL 3.3 version.\n\n"
- "If possible, consider updating your video card drivers.\n\n"
- "If you recently updated your video card drivers, try rebooting.",
- "Unable to initialize OpenGL video driver");
- }
- }
return ds;
}
@@ -6160,25 +6141,40 @@ DisplayServerX11::DisplayServerX11(const String &p_rendering_driver, WindowMode
rendering_driver = p_rendering_driver;
bool driver_found = false;
+ String executable_name = OS::get_singleton()->get_executable_path().get_file();
+
+ // Initialize context and rendering device.
+
#if defined(RD_ENABLED)
#if defined(VULKAN_ENABLED)
if (rendering_driver == "vulkan") {
rendering_context = memnew(RenderingContextDriverVulkanX11);
}
-#endif
+#endif // VULKAN_ENABLED
if (rendering_context) {
if (rendering_context->initialize() != OK) {
- ERR_PRINT(vformat("Could not initialize %s", rendering_driver));
memdelete(rendering_context);
rendering_context = nullptr;
r_error = ERR_CANT_CREATE;
- return;
+
+ if (p_rendering_driver == "vulkan") {
+ OS::get_singleton()->alert(
+ vformat("Your video card drivers seem not to support the required Vulkan version.\n\n"
+ "If possible, consider updating your video card drivers or using the OpenGL 3 driver.\n\n"
+ "You can enable the OpenGL 3 driver by starting the engine from the\n"
+ "command line with the command:\n\n \"%s\" --rendering-driver opengl3\n\n"
+ "If you recently updated your video card drivers, try rebooting.",
+ executable_name),
+ "Unable to initialize Vulkan video driver");
+ }
+
+ ERR_FAIL_MSG(vformat("Could not initialize %s", rendering_driver));
}
driver_found = true;
}
-#endif
- // Initialize context and rendering device.
+#endif // RD_ENABLED
+
#if defined(GLES3_ENABLED)
if (rendering_driver == "opengl3" || rendering_driver == "opengl3_es") {
if (getenv("DRI_PRIME") == nullptr) {
@@ -6231,8 +6227,19 @@ DisplayServerX11::DisplayServerX11(const String &p_rendering_driver, WindowMode
if (fallback) {
WARN_PRINT("Your video card drivers seem not to support the required OpenGL version, switching to OpenGLES.");
rendering_driver = "opengl3_es";
+ OS::get_singleton()->set_current_rendering_driver_name(rendering_driver);
} else {
r_error = ERR_UNAVAILABLE;
+
+ OS::get_singleton()->alert(
+ vformat("Your video card drivers seem not to support the required OpenGL 3.3 version.\n\n"
+ "If possible, consider updating your video card drivers or using the Vulkan driver.\n\n"
+ "You can enable the Vulkan driver by starting the engine from the\n"
+ "command line with the command:\n\n \"%s\" --rendering-driver vulkan\n\n"
+ "If you recently updated your video card drivers, try rebooting.",
+ executable_name),
+ "Unable to initialize OpenGL video driver");
+
ERR_FAIL_MSG("Could not initialize OpenGL.");
}
} else {
@@ -6243,20 +6250,28 @@ DisplayServerX11::DisplayServerX11(const String &p_rendering_driver, WindowMode
if (rendering_driver == "opengl3_es") {
gl_manager_egl = memnew(GLManagerEGL_X11);
- if (gl_manager_egl->initialize() != OK) {
+ if (gl_manager_egl->initialize() != OK || gl_manager_egl->open_display(x11_display) != OK) {
memdelete(gl_manager_egl);
gl_manager_egl = nullptr;
r_error = ERR_UNAVAILABLE;
- ERR_FAIL_MSG("Could not initialize OpenGLES.");
+
+ OS::get_singleton()->alert(
+ "Your video card drivers seem not to support the required OpenGL ES 3.0 version.\n\n"
+ "If possible, consider updating your video card drivers.\n\n"
+ "If you recently updated your video card drivers, try rebooting.",
+ "Unable to initialize OpenGL ES video driver");
+
+ ERR_FAIL_MSG("Could not initialize OpenGL ES.");
}
driver_found = true;
RasterizerGLES3::make_current(false);
}
-#endif
+#endif // GLES3_ENABLED
+
if (!driver_found) {
r_error = ERR_UNAVAILABLE;
- ERR_FAIL_MSG("Video driver not found");
+ ERR_FAIL_MSG("Video driver not found.");
}
Point2i window_position;
@@ -6297,7 +6312,7 @@ DisplayServerX11::DisplayServerX11(const String &p_rendering_driver, WindowMode
RendererCompositorRD::make_current();
}
-#endif
+#endif // RD_ENABLED
{
//set all event master mask
@@ -6450,7 +6465,8 @@ DisplayServerX11::DisplayServerX11(const String &p_rendering_driver, WindowMode
screen_set_keep_on(GLOBAL_GET("display/window/energy_saving/keep_screen_on"));
portal_desktop = memnew(FreeDesktopPortalDesktop);
-#endif
+#endif // DBUS_ENABLED
+
XSetErrorHandler(&default_window_error_handler);
r_error = OK;
diff --git a/platform/macos/display_server_macos.mm b/platform/macos/display_server_macos.mm
index 989a9dcf6c..52dc51bc96 100644
--- a/platform/macos/display_server_macos.mm
+++ b/platform/macos/display_server_macos.mm
@@ -3615,6 +3615,7 @@ DisplayServerMacOS::DisplayServerMacOS(const String &p_rendering_driver, WindowM
WARN_PRINT("Your video card drivers seem not to support GLES3 / ANGLE or ANGLE dynamic libraries (libEGL.dylib and libGLESv2.dylib) are missing, switching to native OpenGL.");
#endif
rendering_driver = "opengl3";
+ OS::get_singleton()->set_current_rendering_driver_name(rendering_driver);
} else {
r_error = ERR_UNAVAILABLE;
ERR_FAIL_MSG("Could not initialize ANGLE OpenGL.");
diff --git a/platform/macos/doc_classes/EditorExportPlatformMacOS.xml b/platform/macos/doc_classes/EditorExportPlatformMacOS.xml
index 34ad52bbf6..c261c252ba 100644
--- a/platform/macos/doc_classes/EditorExportPlatformMacOS.xml
+++ b/platform/macos/doc_classes/EditorExportPlatformMacOS.xml
@@ -38,8 +38,11 @@
<member name="application/icon_interpolation" type="int" setter="" getter="">
Interpolation method used to resize application icon.
</member>
- <member name="application/min_macos_version" type="String" setter="" getter="">
- Minimum version of macOS required for this application to run in the [code]major.minor.patch[/code] or [code]major.minor[/code] format, can only contain numeric characters ([code]0-9[/code]) and periods ([code].[/code]).
+ <member name="application/min_macos_version_arm64" type="String" setter="" getter="">
+ Minimum version of macOS required for this application to run on Apple Silicon Macs, in the [code]major.minor.patch[/code] or [code]major.minor[/code] format, can only contain numeric characters ([code]0-9[/code]) and periods ([code].[/code]).
+ </member>
+ <member name="application/min_macos_version_x86_64" type="String" setter="" getter="">
+ Minimum version of macOS required for this application to run on Intel Macs, in the [code]major.minor.patch[/code] or [code]major.minor[/code] format, can only contain numeric characters ([code]0-9[/code]) and periods ([code].[/code]).
</member>
<member name="application/short_version" type="String" setter="" getter="">
Application version visible to the user, can only contain numeric characters ([code]0-9[/code]) and periods ([code].[/code]). Falls back to [member ProjectSettings.application/config/version] if left empty.
diff --git a/platform/macos/export/export_plugin.cpp b/platform/macos/export/export_plugin.cpp
index 290b0082fc..770871ae19 100644
--- a/platform/macos/export/export_plugin.cpp
+++ b/platform/macos/export/export_plugin.cpp
@@ -451,7 +451,8 @@ void EditorExportPlatformMacOS::get_export_options(List<ExportOption> *r_options
r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/version", PROPERTY_HINT_PLACEHOLDER_TEXT, "Leave empty to use project version"), ""));
r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/copyright"), ""));
r_options->push_back(ExportOption(PropertyInfo(Variant::DICTIONARY, "application/copyright_localized", PROPERTY_HINT_LOCALIZABLE_STRING), Dictionary()));
- r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/min_macos_version"), "10.12"));
+ r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/min_macos_version_x86_64"), "10.12"));
+ r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/min_macos_version_arm64"), "11.00"));
r_options->push_back(ExportOption(PropertyInfo(Variant::INT, "application/export_angle", PROPERTY_HINT_ENUM, "Auto,Yes,No"), 0, true));
r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "display/high_res"), true));
@@ -823,8 +824,12 @@ void EditorExportPlatformMacOS::_fix_plist(const Ref<EditorExportPreset> &p_pres
strnew += lines[i].replace("$app_category", cat.to_lower()) + "\n";
} else if (lines[i].contains("$copyright")) {
strnew += lines[i].replace("$copyright", p_preset->get("application/copyright")) + "\n";
+ } else if (lines[i].contains("$min_version_arm64")) {
+ strnew += lines[i].replace("$min_version_arm64", p_preset->get("application/min_macos_version_arm64")) + "\n";
+ } else if (lines[i].contains("$min_version_x86_64")) {
+ strnew += lines[i].replace("$min_version_x86_64", p_preset->get("application/min_macos_version_x86_64")) + "\n";
} else if (lines[i].contains("$min_version")) {
- strnew += lines[i].replace("$min_version", p_preset->get("application/min_macos_version")) + "\n";
+ strnew += lines[i].replace("$min_version", p_preset->get("application/min_macos_version_x86_64")) + "\n"; // Old template, use x86-64 version for both.
} else if (lines[i].contains("$highres")) {
strnew += lines[i].replace("$highres", p_preset->get("display/high_res") ? "\t<true/>" : "\t<false/>") + "\n";
} else if (lines[i].contains("$additional_plist_content")) {
@@ -1505,7 +1510,7 @@ Error EditorExportPlatformMacOS::_export_debug_script(const Ref<EditorExportPres
return OK;
}
-Error EditorExportPlatformMacOS::export_project(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, int p_flags) {
+Error EditorExportPlatformMacOS::export_project(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, BitField<EditorExportPlatform::DebugFlags> p_flags) {
ExportNotifier notifier(*this, p_preset, p_debug, p_path, p_flags);
const String base_dir = p_path.get_base_dir();
@@ -2511,7 +2516,7 @@ void EditorExportPlatformMacOS::cleanup() {
cleanup_commands.clear();
}
-Error EditorExportPlatformMacOS::run(const Ref<EditorExportPreset> &p_preset, int p_device, int p_debug_flags) {
+Error EditorExportPlatformMacOS::run(const Ref<EditorExportPreset> &p_preset, int p_device, BitField<EditorExportPlatform::DebugFlags> p_debug_flags) {
cleanup();
if (p_device) { // Stop command, cleanup only.
return OK;
@@ -2573,8 +2578,7 @@ Error EditorExportPlatformMacOS::run(const Ref<EditorExportPreset> &p_preset, in
String cmd_args;
{
- Vector<String> cmd_args_list;
- gen_debug_flags(cmd_args_list, p_debug_flags);
+ Vector<String> cmd_args_list = gen_export_flags(p_debug_flags);
for (int i = 0; i < cmd_args_list.size(); i++) {
if (i != 0) {
cmd_args += " ";
@@ -2583,7 +2587,7 @@ Error EditorExportPlatformMacOS::run(const Ref<EditorExportPreset> &p_preset, in
}
}
- const bool use_remote = (p_debug_flags & DEBUG_FLAG_REMOTE_DEBUG) || (p_debug_flags & DEBUG_FLAG_DUMB_CLIENT);
+ const bool use_remote = p_debug_flags.has_flag(DEBUG_FLAG_REMOTE_DEBUG) || p_debug_flags.has_flag(DEBUG_FLAG_DUMB_CLIENT);
int dbg_port = EditorSettings::get_singleton()->get("network/debug/remote_port");
print_line("Creating temporary directory...");
diff --git a/platform/macos/export/export_plugin.h b/platform/macos/export/export_plugin.h
index 062a2e5f95..5457c687d3 100644
--- a/platform/macos/export/export_plugin.h
+++ b/platform/macos/export/export_plugin.h
@@ -147,7 +147,7 @@ public:
virtual bool is_executable(const String &p_path) const override;
virtual List<String> get_binary_extensions(const Ref<EditorExportPreset> &p_preset) const override;
- virtual Error export_project(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, int p_flags = 0) override;
+ virtual Error export_project(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, BitField<EditorExportPlatform::DebugFlags> p_flags = 0) override;
virtual bool has_valid_export_configuration(const Ref<EditorExportPreset> &p_preset, String &r_error, bool &r_missing_templates, bool p_debug = false) const override;
virtual bool has_valid_project_configuration(const Ref<EditorExportPreset> &p_preset, String &r_error) const override;
@@ -167,7 +167,7 @@ public:
virtual int get_options_count() const override;
virtual String get_option_label(int p_index) const override;
virtual String get_option_tooltip(int p_index) const override;
- virtual Error run(const Ref<EditorExportPreset> &p_preset, int p_device, int p_debug_flags) override;
+ virtual Error run(const Ref<EditorExportPreset> &p_preset, int p_device, BitField<EditorExportPlatform::DebugFlags> p_debug_flags) override;
virtual void cleanup() override;
EditorExportPlatformMacOS();
diff --git a/platform/web/api/api.cpp b/platform/web/api/api.cpp
index a695091a04..9ddbe5d01d 100644
--- a/platform/web/api/api.cpp
+++ b/platform/web/api/api.cpp
@@ -99,7 +99,7 @@ Variant JavaScriptBridge::_create_object_bind(const Variant **p_args, int p_argc
r_error.expected = 1;
return Ref<JavaScriptObject>();
}
- if (p_args[0]->get_type() != Variant::STRING) {
+ if (!p_args[0]->is_string()) {
r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT;
r_error.argument = 0;
r_error.expected = Variant::STRING;
diff --git a/platform/web/export/export_plugin.cpp b/platform/web/export/export_plugin.cpp
index d8c1b6033d..5faab74d7b 100644
--- a/platform/web/export/export_plugin.cpp
+++ b/platform/web/export/export_plugin.cpp
@@ -130,15 +130,14 @@ void EditorExportPlatformWeb::_replace_strings(const HashMap<String, String> &p_
}
}
-void EditorExportPlatformWeb::_fix_html(Vector<uint8_t> &p_html, const Ref<EditorExportPreset> &p_preset, const String &p_name, bool p_debug, int p_flags, const Vector<SharedObject> p_shared_objects, const Dictionary &p_file_sizes) {
+void EditorExportPlatformWeb::_fix_html(Vector<uint8_t> &p_html, const Ref<EditorExportPreset> &p_preset, const String &p_name, bool p_debug, BitField<EditorExportPlatform::DebugFlags> p_flags, const Vector<SharedObject> p_shared_objects, const Dictionary &p_file_sizes) {
// Engine.js config
Dictionary config;
Array libs;
for (int i = 0; i < p_shared_objects.size(); i++) {
libs.push_back(p_shared_objects[i].path.get_file());
}
- Vector<String> flags;
- gen_export_flags(flags, p_flags & (~DEBUG_FLAG_DUMB_CLIENT));
+ Vector<String> flags = gen_export_flags(p_flags & (~DEBUG_FLAG_DUMB_CLIENT));
Array args;
for (int i = 0; i < flags.size(); i++) {
args.push_back(flags[i]);
@@ -450,7 +449,7 @@ List<String> EditorExportPlatformWeb::get_binary_extensions(const Ref<EditorExpo
return list;
}
-Error EditorExportPlatformWeb::export_project(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, int p_flags) {
+Error EditorExportPlatformWeb::export_project(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, BitField<EditorExportPlatform::DebugFlags> p_flags) {
ExportNotifier notifier(*this, p_preset, p_debug, p_path, p_flags);
const String custom_debug = p_preset->get("custom_template/debug");
@@ -744,7 +743,7 @@ String EditorExportPlatformWeb::get_option_tooltip(int p_index) const {
return "";
}
-Error EditorExportPlatformWeb::run(const Ref<EditorExportPreset> &p_preset, int p_option, int p_debug_flags) {
+Error EditorExportPlatformWeb::run(const Ref<EditorExportPreset> &p_preset, int p_option, BitField<EditorExportPlatform::DebugFlags> p_debug_flags) {
const uint16_t bind_port = EDITOR_GET("export/web/http_port");
// Resolve host if needed.
const String bind_host = EDITOR_GET("export/web/http_host");
diff --git a/platform/web/export/export_plugin.h b/platform/web/export/export_plugin.h
index 2f67d8107f..3c743e2e74 100644
--- a/platform/web/export/export_plugin.h
+++ b/platform/web/export/export_plugin.h
@@ -98,7 +98,7 @@ class EditorExportPlatformWeb : public EditorExportPlatform {
Error _extract_template(const String &p_template, const String &p_dir, const String &p_name, bool pwa);
void _replace_strings(const HashMap<String, String> &p_replaces, Vector<uint8_t> &r_template);
- void _fix_html(Vector<uint8_t> &p_html, const Ref<EditorExportPreset> &p_preset, const String &p_name, bool p_debug, int p_flags, const Vector<SharedObject> p_shared_objects, const Dictionary &p_file_sizes);
+ void _fix_html(Vector<uint8_t> &p_html, const Ref<EditorExportPreset> &p_preset, const String &p_name, bool p_debug, BitField<EditorExportPlatform::DebugFlags> p_flags, const Vector<SharedObject> p_shared_objects, const Dictionary &p_file_sizes);
Error _add_manifest_icon(const String &p_path, const String &p_icon, int p_size, Array &r_arr);
Error _build_pwa(const Ref<EditorExportPreset> &p_preset, const String p_path, const Vector<SharedObject> &p_shared_objects);
Error _write_or_error(const uint8_t *p_content, int p_len, String p_path);
@@ -120,14 +120,14 @@ public:
virtual bool has_valid_export_configuration(const Ref<EditorExportPreset> &p_preset, String &r_error, bool &r_missing_templates, bool p_debug = false) const override;
virtual bool has_valid_project_configuration(const Ref<EditorExportPreset> &p_preset, String &r_error) const override;
virtual List<String> get_binary_extensions(const Ref<EditorExportPreset> &p_preset) const override;
- virtual Error export_project(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, int p_flags = 0) override;
+ virtual Error export_project(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, BitField<EditorExportPlatform::DebugFlags> p_flags = 0) override;
virtual bool poll_export() override;
virtual int get_options_count() const override;
virtual String get_option_label(int p_index) const override;
virtual String get_option_tooltip(int p_index) const override;
virtual Ref<ImageTexture> get_option_icon(int p_index) const override;
- virtual Error run(const Ref<EditorExportPreset> &p_preset, int p_option, int p_debug_flags) override;
+ virtual Error run(const Ref<EditorExportPreset> &p_preset, int p_option, BitField<EditorExportPlatform::DebugFlags> p_debug_flags) override;
virtual Ref<Texture2D> get_run_icon() const override;
virtual void get_platform_features(List<String> *r_features) const override {
diff --git a/platform/web/javascript_bridge_singleton.cpp b/platform/web/javascript_bridge_singleton.cpp
index c4dbb405a3..502e830f82 100644
--- a/platform/web/javascript_bridge_singleton.cpp
+++ b/platform/web/javascript_bridge_singleton.cpp
@@ -304,7 +304,7 @@ Variant JavaScriptBridge::_create_object_bind(const Variant **p_args, int p_argc
r_error.expected = 1;
return Ref<JavaScriptObject>();
}
- if (p_args[0]->get_type() != Variant::STRING) {
+ if (!p_args[0]->is_string()) {
r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT;
r_error.argument = 0;
r_error.expected = Variant::STRING;
diff --git a/platform/web/os_web.cpp b/platform/web/os_web.cpp
index ef8f90421b..51facbaa84 100644
--- a/platform/web/os_web.cpp
+++ b/platform/web/os_web.cpp
@@ -105,7 +105,7 @@ Error OS_Web::execute(const String &p_path, const List<String> &p_arguments, Str
return create_process(p_path, p_arguments);
}
-Dictionary OS_Web::execute_with_pipe(const String &p_path, const List<String> &p_arguments) {
+Dictionary OS_Web::execute_with_pipe(const String &p_path, const List<String> &p_arguments, bool p_blocking) {
ERR_FAIL_V_MSG(Dictionary(), "OS::execute_with_pipe is not available on the Web platform.");
}
diff --git a/platform/web/os_web.h b/platform/web/os_web.h
index 55a5fcc6c6..1ddb745965 100644
--- a/platform/web/os_web.h
+++ b/platform/web/os_web.h
@@ -80,7 +80,7 @@ public:
bool main_loop_iterate();
Error execute(const String &p_path, const List<String> &p_arguments, String *r_pipe = nullptr, int *r_exitcode = nullptr, bool read_stderr = false, Mutex *p_pipe_mutex = nullptr, bool p_open_console = false) override;
- Dictionary execute_with_pipe(const String &p_path, const List<String> &p_arguments) override;
+ Dictionary execute_with_pipe(const String &p_path, const List<String> &p_arguments, bool p_blocking = true) override;
Error create_process(const String &p_path, const List<String> &p_arguments, ProcessID *r_child_id = nullptr, bool p_open_console = false) override;
Error kill(const ProcessID &p_pid) override;
int get_process_id() const override;
diff --git a/platform/windows/console_wrapper_windows.cpp b/platform/windows/console_wrapper_windows.cpp
index 133711a9ea..1ba09b236b 100644
--- a/platform/windows/console_wrapper_windows.cpp
+++ b/platform/windows/console_wrapper_windows.cpp
@@ -40,8 +40,8 @@
int main(int argc, char *argv[]) {
// Get executable name.
- WCHAR exe_name[MAX_PATH] = {};
- if (!GetModuleFileNameW(nullptr, exe_name, MAX_PATH)) {
+ WCHAR exe_name[32767] = {};
+ if (!GetModuleFileNameW(nullptr, exe_name, 32767)) {
wprintf(L"GetModuleFileName failed, error %d\n", GetLastError());
return -1;
}
diff --git a/platform/windows/crash_handler_windows_seh.cpp b/platform/windows/crash_handler_windows_seh.cpp
index 2abe285d31..a6015092e8 100644
--- a/platform/windows/crash_handler_windows_seh.cpp
+++ b/platform/windows/crash_handler_windows_seh.cpp
@@ -118,7 +118,7 @@ DWORD CrashHandlerException(EXCEPTION_POINTERS *ep) {
HANDLE process = GetCurrentProcess();
HANDLE hThread = GetCurrentThread();
DWORD offset_from_symbol = 0;
- IMAGEHLP_LINE64 line = { 0 };
+ IMAGEHLP_LINE64 line = {};
std::vector<module_data> modules;
DWORD cbNeeded;
std::vector<HMODULE> module_handles(1);
diff --git a/platform/windows/detect.py b/platform/windows/detect.py
index 11dd4548f1..92ac921cee 100644
--- a/platform/windows/detect.py
+++ b/platform/windows/detect.py
@@ -13,40 +13,33 @@ if TYPE_CHECKING:
# To match other platforms
STACK_SIZE = 8388608
+STACK_SIZE_SANITIZERS = 30 * 1024 * 1024
def get_name():
return "Windows"
-def try_cmd(test, prefix, arch):
+def try_cmd(test, prefix, arch, check_clang=False):
+ archs = ["x86_64", "x86_32", "arm64", "arm32"]
if arch:
+ archs = [arch]
+
+ for a in archs:
try:
out = subprocess.Popen(
- get_mingw_bin_prefix(prefix, arch) + test,
+ get_mingw_bin_prefix(prefix, a) + test,
shell=True,
stderr=subprocess.PIPE,
stdout=subprocess.PIPE,
)
- out.communicate()
+ outs, errs = out.communicate()
if out.returncode == 0:
+ if check_clang and not outs.startswith(b"clang"):
+ return False
return True
except Exception:
pass
- else:
- for a in ["x86_64", "x86_32", "arm64", "arm32"]:
- try:
- out = subprocess.Popen(
- get_mingw_bin_prefix(prefix, a) + test,
- shell=True,
- stderr=subprocess.PIPE,
- stdout=subprocess.PIPE,
- )
- out.communicate()
- if out.returncode == 0:
- return True
- except Exception:
- pass
return False
@@ -203,6 +196,7 @@ def get_opts():
BoolVariable("use_llvm", "Use the LLVM compiler", False),
BoolVariable("use_static_cpp", "Link MinGW/MSVC C++ runtime libraries statically", True),
BoolVariable("use_asan", "Use address sanitizer (ASAN)", False),
+ BoolVariable("use_ubsan", "Use LLVM compiler undefined behavior sanitizer (UBSAN)", False),
BoolVariable("debug_crt", "Compile with MSVC's debug CRT (/MDd)", False),
BoolVariable("incremental_link", "Use MSVC incremental linking. May increase or decrease build times.", False),
BoolVariable("silence_msvc", "Silence MSVC's cl/link stdout bloat, redirecting any errors to stderr.", True),
@@ -387,6 +381,15 @@ def configure_msvc(env: "SConsEnvironment", vcvars_msvc_config):
## Compile/link flags
+ if env["use_llvm"]:
+ env["CC"] = "clang-cl"
+ env["CXX"] = "clang-cl"
+ env["LINK"] = "lld-link"
+ env["AR"] = "llvm-lib"
+
+ env.AppendUnique(CPPDEFINES=["R128_STDC_ONLY"])
+ env.extra_suffix = ".llvm" + env.extra_suffix
+
env["MAXLINELENGTH"] = 8192 # Windows Vista and beyond, so always applicable.
if env["silence_msvc"] and not env.GetOption("clean"):
@@ -471,7 +474,6 @@ def configure_msvc(env: "SConsEnvironment", vcvars_msvc_config):
env.AppendUnique(CCFLAGS=["/Gd", "/GR", "/nologo"])
env.AppendUnique(CCFLAGS=["/utf-8"]) # Force to use Unicode encoding.
- env.AppendUnique(CXXFLAGS=["/TP"]) # assume all sources are C++
# Once it was thought that only debug builds would be too large,
# but this has recently stopped being true. See the mingw function
# for notes on why this shouldn't be enabled for gcc
@@ -507,6 +509,7 @@ def configure_msvc(env: "SConsEnvironment", vcvars_msvc_config):
if env["use_asan"]:
env.extra_suffix += ".san"
prebuilt_lib_extra_suffix = ".san"
+ env.AppendUnique(CPPDEFINES=["SANITIZERS_ENABLED"])
env.Append(CCFLAGS=["/fsanitize=address"])
env.Append(LINKFLAGS=["/INFERASANLIBS"])
@@ -595,6 +598,9 @@ def configure_msvc(env: "SConsEnvironment", vcvars_msvc_config):
if env["target"] in ["editor", "template_debug"]:
LIBS += ["psapi", "dbghelp"]
+ if env["use_llvm"]:
+ LIBS += [f"clang_rt.builtins-{env['arch']}"]
+
env.Append(LINKFLAGS=[p + env["LIBSUFFIX"] for p in LIBS])
if vcvars_msvc_config:
@@ -610,14 +616,22 @@ def configure_msvc(env: "SConsEnvironment", vcvars_msvc_config):
if env["lto"] != "none":
if env["lto"] == "thin":
- print_error("ThinLTO is only compatible with LLVM, use `use_llvm=yes` or `lto=full`.")
- sys.exit(255)
- env.AppendUnique(CCFLAGS=["/GL"])
- env.AppendUnique(ARFLAGS=["/LTCG"])
- if env["progress"]:
- env.AppendUnique(LINKFLAGS=["/LTCG:STATUS"])
+ if not env["use_llvm"]:
+ print("ThinLTO is only compatible with LLVM, use `use_llvm=yes` or `lto=full`.")
+ sys.exit(255)
+
+ env.Append(CCFLAGS=["-flto=thin"])
+ env.Append(LINKFLAGS=["-flto=thin"])
+ elif env["use_llvm"]:
+ env.Append(CCFLAGS=["-flto"])
+ env.Append(LINKFLAGS=["-flto"])
else:
- env.AppendUnique(LINKFLAGS=["/LTCG"])
+ env.AppendUnique(CCFLAGS=["/GL"])
+ env.AppendUnique(ARFLAGS=["/LTCG"])
+ if env["progress"]:
+ env.AppendUnique(LINKFLAGS=["/LTCG:STATUS"])
+ else:
+ env.AppendUnique(LINKFLAGS=["/LTCG"])
if vcvars_msvc_config:
env.Prepend(CPPPATH=[p for p in str(os.getenv("INCLUDE")).split(";")])
@@ -628,7 +642,66 @@ def configure_msvc(env: "SConsEnvironment", vcvars_msvc_config):
env["BUILDERS"]["Program"] = methods.precious_program
env.Append(LINKFLAGS=["/NATVIS:platform\\windows\\godot.natvis"])
- env.AppendUnique(LINKFLAGS=["/STACK:" + str(STACK_SIZE)])
+
+ if env["use_asan"]:
+ env.AppendUnique(LINKFLAGS=["/STACK:" + str(STACK_SIZE_SANITIZERS)])
+ else:
+ env.AppendUnique(LINKFLAGS=["/STACK:" + str(STACK_SIZE)])
+
+
+def get_ar_version(env):
+ ret = {
+ "major": -1,
+ "minor": -1,
+ "patch": -1,
+ "is_llvm": False,
+ }
+ try:
+ output = (
+ subprocess.check_output([env.subst(env["AR"]), "--version"], shell=(os.name == "nt"))
+ .strip()
+ .decode("utf-8")
+ )
+ except (subprocess.CalledProcessError, OSError):
+ print_warning("Couldn't check version of `ar`.")
+ return ret
+
+ match = re.search(r"GNU ar \(GNU Binutils\) (\d+)\.(\d+)(:?\.(\d+))?", output)
+ if match:
+ ret["major"] = int(match[1])
+ ret["minor"] = int(match[2])
+ if match[3]:
+ ret["patch"] = int(match[3])
+ else:
+ ret["patch"] = 0
+ return ret
+
+ match = re.search(r"LLVM version (\d+)\.(\d+)\.(\d+)", output)
+ if match:
+ ret["major"] = int(match[1])
+ ret["minor"] = int(match[2])
+ ret["patch"] = int(match[3])
+ ret["is_llvm"] = True
+ return ret
+
+ print_warning("Couldn't parse version of `ar`.")
+ return ret
+
+
+def get_is_ar_thin_supported(env):
+ """Check whether `ar --thin` is supported. It is only supported since Binutils 2.38 or LLVM 14."""
+ ar_version = get_ar_version(env)
+ if ar_version["major"] == -1:
+ return False
+
+ if ar_version["is_llvm"]:
+ return ar_version["major"] >= 14
+
+ if ar_version["major"] == 2:
+ return ar_version["minor"] >= 38
+
+ print_warning("Unknown Binutils `ar` version.")
+ return False
def configure_mingw(env: "SConsEnvironment"):
@@ -644,6 +717,10 @@ def configure_mingw(env: "SConsEnvironment"):
if env["use_llvm"] and not try_cmd("clang --version", env["mingw_prefix"], env["arch"]):
env["use_llvm"] = False
+ if not env["use_llvm"] and try_cmd("gcc --version", env["mingw_prefix"], env["arch"], True):
+ print("Detected GCC to be a wrapper for Clang.")
+ env["use_llvm"] = True
+
# TODO: Re-evaluate the need for this / streamline with common config.
if env["target"] == "template_release":
if env["arch"] != "arm64":
@@ -721,7 +798,10 @@ def configure_mingw(env: "SConsEnvironment"):
env.Append(CCFLAGS=["-flto"])
env.Append(LINKFLAGS=["-flto"])
- env.Append(LINKFLAGS=["-Wl,--stack," + str(STACK_SIZE)])
+ if env["use_asan"]:
+ env.Append(LINKFLAGS=["-Wl,--stack," + str(STACK_SIZE_SANITIZERS)])
+ else:
+ env.Append(LINKFLAGS=["-Wl,--stack," + str(STACK_SIZE)])
## Compile flags
@@ -732,6 +812,33 @@ def configure_mingw(env: "SConsEnvironment"):
if not env["use_llvm"]:
env.Append(CCFLAGS=["-mwindows"])
+ if env["use_asan"] or env["use_ubsan"]:
+ if not env["use_llvm"]:
+ print("GCC does not support sanitizers on Windows.")
+ sys.exit(255)
+ if env["arch"] not in ["x86_32", "x86_64"]:
+ print("Sanitizers are only supported for x86_32 and x86_64.")
+ sys.exit(255)
+
+ env.extra_suffix += ".san"
+ env.AppendUnique(CPPDEFINES=["SANITIZERS_ENABLED"])
+ san_flags = []
+ if env["use_asan"]:
+ san_flags.append("-fsanitize=address")
+ if env["use_ubsan"]:
+ san_flags.append("-fsanitize=undefined")
+ # Disable the vptr check since it gets triggered on any COM interface calls.
+ san_flags.append("-fno-sanitize=vptr")
+ env.Append(CFLAGS=san_flags)
+ env.Append(CCFLAGS=san_flags)
+ env.Append(LINKFLAGS=san_flags)
+
+ if env["use_llvm"] and os.name == "nt" and methods._colorize:
+ env.Append(CCFLAGS=["$(-fansi-escape-codes$)", "$(-fcolor-diagnostics$)"])
+
+ if get_is_ar_thin_supported(env):
+ env.Append(ARFLAGS=["--thin"])
+
env.Append(CPPDEFINES=["WINDOWS_ENABLED", "WASAPI_ENABLED", "WINMIDI_ENABLED"])
env.Append(
CPPDEFINES=[
diff --git a/platform/windows/display_server_windows.cpp b/platform/windows/display_server_windows.cpp
index 270112e624..50ebe7077f 100644
--- a/platform/windows/display_server_windows.cpp
+++ b/platform/windows/display_server_windows.cpp
@@ -317,7 +317,7 @@ public:
if (!lpw_path) {
return S_FALSE;
}
- String path = String::utf16((const char16_t *)lpw_path).simplify_path();
+ String path = String::utf16((const char16_t *)lpw_path).replace("\\", "/").trim_prefix(R"(\\?\)").simplify_path();
if (!path.begins_with(root.simplify_path())) {
return S_FALSE;
}
@@ -522,7 +522,7 @@ void DisplayServerWindows::_thread_fd_monitor(void *p_ud) {
if (!item.has("name") || !item.has("values") || !item.has("default")) {
continue;
}
- event_handler->add_option(pfdc, item["name"], item["values"], item["default_idx"]);
+ event_handler->add_option(pfdc, item["name"], item["values"], item["default"]);
}
event_handler->set_root(fd->root);
@@ -542,7 +542,26 @@ void DisplayServerWindows::_thread_fd_monitor(void *p_ud) {
pfd->SetOptions(flags | FOS_FORCEFILESYSTEM);
pfd->SetTitle((LPCWSTR)fd->title.utf16().ptr());
- String dir = fd->current_directory.replace("/", "\\");
+ String dir = ProjectSettings::get_singleton()->globalize_path(fd->current_directory);
+ if (dir == ".") {
+ dir = OS::get_singleton()->get_executable_path().get_base_dir();
+ }
+ if (dir.is_relative_path() || dir == ".") {
+ Char16String current_dir_name;
+ size_t str_len = GetCurrentDirectoryW(0, nullptr);
+ current_dir_name.resize(str_len + 1);
+ GetCurrentDirectoryW(current_dir_name.size(), (LPWSTR)current_dir_name.ptrw());
+ if (dir == ".") {
+ dir = String::utf16((const char16_t *)current_dir_name.get_data()).trim_prefix(R"(\\?\)").replace("\\", "/");
+ } else {
+ dir = String::utf16((const char16_t *)current_dir_name.get_data()).trim_prefix(R"(\\?\)").replace("\\", "/").path_join(dir);
+ }
+ }
+ dir = dir.simplify_path();
+ dir = dir.replace("/", "\\");
+ if (!dir.is_network_share_path() && !dir.begins_with(R"(\\?\)")) {
+ dir = R"(\\?\)" + dir;
+ }
IShellItem *shellitem = nullptr;
hr = SHCreateItemFromParsingName((LPCWSTR)dir.utf16().ptr(), nullptr, IID_IShellItem, (void **)&shellitem);
@@ -585,7 +604,7 @@ void DisplayServerWindows::_thread_fd_monitor(void *p_ud) {
PWSTR file_path = nullptr;
hr = result->GetDisplayName(SIGDN_FILESYSPATH, &file_path);
if (SUCCEEDED(hr)) {
- file_names.push_back(String::utf16((const char16_t *)file_path));
+ file_names.push_back(String::utf16((const char16_t *)file_path).replace("\\", "/").trim_prefix(R"(\\?\)"));
CoTaskMemFree(file_path);
}
result->Release();
@@ -599,69 +618,48 @@ void DisplayServerWindows::_thread_fd_monitor(void *p_ud) {
PWSTR file_path = nullptr;
hr = result->GetDisplayName(SIGDN_FILESYSPATH, &file_path);
if (SUCCEEDED(hr)) {
- file_names.push_back(String::utf16((const char16_t *)file_path));
+ file_names.push_back(String::utf16((const char16_t *)file_path).replace("\\", "/").trim_prefix(R"(\\?\)"));
CoTaskMemFree(file_path);
}
result->Release();
}
}
if (fd->callback.is_valid()) {
- if (fd->options_in_cb) {
- Variant v_result = true;
- Variant v_files = file_names;
- Variant v_index = index;
- Variant v_opt = options;
- const Variant *cb_args[4] = { &v_result, &v_files, &v_index, &v_opt };
-
- fd->callback.call_deferredp(cb_args, 4);
- } else {
- Variant v_result = true;
- Variant v_files = file_names;
- Variant v_index = index;
- const Variant *cb_args[3] = { &v_result, &v_files, &v_index };
-
- fd->callback.call_deferredp(cb_args, 3);
- }
+ MutexLock lock(ds->file_dialog_mutex);
+ FileDialogCallback cb;
+ cb.callback = fd->callback;
+ cb.status = true;
+ cb.files = file_names;
+ cb.index = index;
+ cb.options = options;
+ cb.opt_in_cb = fd->options_in_cb;
+ ds->pending_cbs.push_back(cb);
}
} else {
if (fd->callback.is_valid()) {
- if (fd->options_in_cb) {
- Variant v_result = false;
- Variant v_files = Vector<String>();
- Variant v_index = 0;
- Variant v_opt = Dictionary();
- const Variant *cb_args[4] = { &v_result, &v_files, &v_index, &v_opt };
-
- fd->callback.call_deferredp(cb_args, 4);
- } else {
- Variant v_result = false;
- Variant v_files = Vector<String>();
- Variant v_index = 0;
- const Variant *cb_args[3] = { &v_result, &v_files, &v_index };
-
- fd->callback.call_deferredp(cb_args, 3);
- }
+ MutexLock lock(ds->file_dialog_mutex);
+ FileDialogCallback cb;
+ cb.callback = fd->callback;
+ cb.status = false;
+ cb.files = Vector<String>();
+ cb.index = index;
+ cb.options = options;
+ cb.opt_in_cb = fd->options_in_cb;
+ ds->pending_cbs.push_back(cb);
}
}
pfd->Release();
} else {
if (fd->callback.is_valid()) {
- if (fd->options_in_cb) {
- Variant v_result = false;
- Variant v_files = Vector<String>();
- Variant v_index = 0;
- Variant v_opt = Dictionary();
- const Variant *cb_args[4] = { &v_result, &v_files, &v_index, &v_opt };
-
- fd->callback.call_deferredp(cb_args, 4);
- } else {
- Variant v_result = false;
- Variant v_files = Vector<String>();
- Variant v_index = 0;
- const Variant *cb_args[3] = { &v_result, &v_files, &v_index };
-
- fd->callback.call_deferredp(cb_args, 3);
- }
+ MutexLock lock(ds->file_dialog_mutex);
+ FileDialogCallback cb;
+ cb.callback = fd->callback;
+ cb.status = false;
+ cb.files = Vector<String>();
+ cb.index = 0;
+ cb.options = Dictionary();
+ cb.opt_in_cb = fd->options_in_cb;
+ ds->pending_cbs.push_back(cb);
}
}
{
@@ -749,6 +747,34 @@ Error DisplayServerWindows::_file_dialog_with_options_show(const String &p_title
return OK;
}
+void DisplayServerWindows::process_file_dialog_callbacks() {
+ MutexLock lock(file_dialog_mutex);
+ while (!pending_cbs.is_empty()) {
+ FileDialogCallback cb = pending_cbs.front()->get();
+ pending_cbs.pop_front();
+
+ if (cb.opt_in_cb) {
+ Variant ret;
+ Callable::CallError ce;
+ const Variant *args[4] = { &cb.status, &cb.files, &cb.index, &cb.options };
+
+ cb.callback.callp(args, 4, ret, ce);
+ if (ce.error != Callable::CallError::CALL_OK) {
+ ERR_PRINT(vformat("Failed to execute file dialog callback: %s.", Variant::get_callable_error_text(cb.callback, args, 4, ce)));
+ }
+ } else {
+ Variant ret;
+ Callable::CallError ce;
+ const Variant *args[3] = { &cb.status, &cb.files, &cb.index };
+
+ cb.callback.callp(args, 3, ret, ce);
+ if (ce.error != Callable::CallError::CALL_OK) {
+ ERR_PRINT(vformat("Failed to execute file dialog callback: %s.", Variant::get_callable_error_text(cb.callback, args, 3, ce)));
+ }
+ }
+ }
+}
+
void DisplayServerWindows::mouse_set_mode(MouseMode p_mode) {
_THREAD_SAFE_METHOD_
@@ -3171,6 +3197,7 @@ void DisplayServerWindows::process_events() {
memdelete(E->get());
E->erase();
}
+ process_file_dialog_callbacks();
}
void DisplayServerWindows::force_process_and_drop_events() {
@@ -6131,6 +6158,7 @@ DisplayServerWindows::DisplayServerWindows(const String &p_rendering_driver, Win
if (rendering_context->initialize() == OK) {
WARN_PRINT("Your video card drivers seem not to support Direct3D 12, switching to Vulkan.");
rendering_driver = "vulkan";
+ OS::get_singleton()->set_current_rendering_driver_name(rendering_driver);
failed = false;
}
}
@@ -6144,6 +6172,7 @@ DisplayServerWindows::DisplayServerWindows(const String &p_rendering_driver, Win
if (rendering_context->initialize() == OK) {
WARN_PRINT("Your video card drivers seem not to support Vulkan, switching to Direct3D 12.");
rendering_driver = "d3d12";
+ OS::get_singleton()->set_current_rendering_driver_name(rendering_driver);
failed = false;
}
}
@@ -6222,6 +6251,7 @@ DisplayServerWindows::DisplayServerWindows(const String &p_rendering_driver, Win
}
}
rendering_driver = "opengl3_angle";
+ OS::get_singleton()->set_current_rendering_driver_name(rendering_driver);
}
}
diff --git a/platform/windows/display_server_windows.h b/platform/windows/display_server_windows.h
index 3deb7ac8b0..54e1c9681d 100644
--- a/platform/windows/display_server_windows.h
+++ b/platform/windows/display_server_windows.h
@@ -572,6 +572,16 @@ class DisplayServerWindows : public DisplayServer {
Mutex file_dialog_mutex;
List<FileDialogData *> file_dialogs;
HashMap<HWND, FileDialogData *> file_dialog_wnd;
+ struct FileDialogCallback {
+ Callable callback;
+ Variant status;
+ Variant files;
+ Variant index;
+ Variant options;
+ bool opt_in_cb = false;
+ };
+ List<FileDialogCallback> pending_cbs;
+ void process_file_dialog_callbacks();
static void _thread_fd_monitor(void *p_ud);
diff --git a/platform/windows/export/export_plugin.cpp b/platform/windows/export/export_plugin.cpp
index b465bd4ecd..8d3f4bb269 100644
--- a/platform/windows/export/export_plugin.cpp
+++ b/platform/windows/export/export_plugin.cpp
@@ -167,7 +167,7 @@ Error EditorExportPlatformWindows::sign_shared_object(const Ref<EditorExportPres
}
}
-Error EditorExportPlatformWindows::modify_template(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, int p_flags) {
+Error EditorExportPlatformWindows::modify_template(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, BitField<EditorExportPlatform::DebugFlags> p_flags) {
if (p_preset->get("application/modify_resources")) {
_rcedit_add_data(p_preset, p_path, false);
String wrapper_path = p_path.get_basename() + ".console.exe";
@@ -178,7 +178,7 @@ Error EditorExportPlatformWindows::modify_template(const Ref<EditorExportPreset>
return OK;
}
-Error EditorExportPlatformWindows::export_project(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, int p_flags) {
+Error EditorExportPlatformWindows::export_project(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, BitField<EditorExportPlatform::DebugFlags> p_flags) {
String custom_debug = p_preset->get("custom_template/debug");
String custom_release = p_preset->get("custom_template/release");
String arch = p_preset->get("binary_format/architecture");
@@ -996,7 +996,7 @@ void EditorExportPlatformWindows::cleanup() {
cleanup_commands.clear();
}
-Error EditorExportPlatformWindows::run(const Ref<EditorExportPreset> &p_preset, int p_device, int p_debug_flags) {
+Error EditorExportPlatformWindows::run(const Ref<EditorExportPreset> &p_preset, int p_device, BitField<EditorExportPlatform::DebugFlags> p_debug_flags) {
cleanup();
if (p_device) { // Stop command, cleanup only.
return OK;
@@ -1050,8 +1050,7 @@ Error EditorExportPlatformWindows::run(const Ref<EditorExportPreset> &p_preset,
String cmd_args;
{
- Vector<String> cmd_args_list;
- gen_debug_flags(cmd_args_list, p_debug_flags);
+ Vector<String> cmd_args_list = gen_export_flags(p_debug_flags);
for (int i = 0; i < cmd_args_list.size(); i++) {
if (i != 0) {
cmd_args += " ";
@@ -1060,7 +1059,7 @@ Error EditorExportPlatformWindows::run(const Ref<EditorExportPreset> &p_preset,
}
}
- const bool use_remote = (p_debug_flags & DEBUG_FLAG_REMOTE_DEBUG) || (p_debug_flags & DEBUG_FLAG_DUMB_CLIENT);
+ const bool use_remote = p_debug_flags.has_flag(DEBUG_FLAG_REMOTE_DEBUG) || p_debug_flags.has_flag(DEBUG_FLAG_DUMB_CLIENT);
int dbg_port = EditorSettings::get_singleton()->get("network/debug/remote_port");
print_line("Creating temporary directory...");
diff --git a/platform/windows/export/export_plugin.h b/platform/windows/export/export_plugin.h
index 6ccb4a15a7..e86aac83d4 100644
--- a/platform/windows/export/export_plugin.h
+++ b/platform/windows/export/export_plugin.h
@@ -76,8 +76,8 @@ class EditorExportPlatformWindows : public EditorExportPlatformPC {
String _get_exe_arch(const String &p_path) const;
public:
- virtual Error export_project(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, int p_flags = 0) override;
- virtual Error modify_template(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, int p_flags) override;
+ virtual Error export_project(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, BitField<EditorExportPlatform::DebugFlags> p_flags = 0) override;
+ virtual Error modify_template(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, BitField<EditorExportPlatform::DebugFlags> p_flags) override;
virtual Error sign_shared_object(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path) override;
virtual List<String> get_binary_extensions(const Ref<EditorExportPreset> &p_preset) const override;
virtual void get_export_options(List<ExportOption> *r_options) const override;
@@ -95,7 +95,7 @@ public:
virtual int get_options_count() const override;
virtual String get_option_label(int p_index) const override;
virtual String get_option_tooltip(int p_index) const override;
- virtual Error run(const Ref<EditorExportPreset> &p_preset, int p_device, int p_debug_flags) override;
+ virtual Error run(const Ref<EditorExportPreset> &p_preset, int p_device, BitField<EditorExportPlatform::DebugFlags> p_debug_flags) override;
virtual void cleanup() override;
EditorExportPlatformWindows();
diff --git a/platform/windows/godot_res_wrap.rc b/platform/windows/godot_res_wrap.rc
index 27ad26cbc5..61e6100497 100644
--- a/platform/windows/godot_res_wrap.rc
+++ b/platform/windows/godot_res_wrap.rc
@@ -1,6 +1,11 @@
#include "core/version.h"
+#ifndef RT_MANIFEST
+#define RT_MANIFEST 24
+#endif
+
GODOT_ICON ICON platform/windows/godot_console.ico
+1 RT_MANIFEST "godot.manifest"
1 VERSIONINFO
FILEVERSION VERSION_MAJOR,VERSION_MINOR,VERSION_PATCH,0
diff --git a/platform/windows/os_windows.cpp b/platform/windows/os_windows.cpp
index fb95c69b52..f9c636a4a6 100644
--- a/platform/windows/os_windows.cpp
+++ b/platform/windows/os_windows.cpp
@@ -90,6 +90,23 @@ __declspec(dllexport) int AmdPowerXpressRequestHighPerformance = 1;
#define GetProcAddress (void *)GetProcAddress
#endif
+static String fix_path(const String &p_path) {
+ String path = p_path;
+ if (p_path.is_relative_path()) {
+ Char16String current_dir_name;
+ size_t str_len = GetCurrentDirectoryW(0, nullptr);
+ current_dir_name.resize(str_len + 1);
+ GetCurrentDirectoryW(current_dir_name.size(), (LPWSTR)current_dir_name.ptrw());
+ path = String::utf16((const char16_t *)current_dir_name.get_data()).trim_prefix(R"(\\?\)").replace("\\", "/").path_join(path);
+ }
+ path = path.simplify_path();
+ path = path.replace("/", "\\");
+ if (!path.is_network_share_path() && !path.begins_with(R"(\\?\)")) {
+ path = R"(\\?\)" + path;
+ }
+ return path;
+}
+
static String format_error_message(DWORD id) {
LPWSTR messageBuffer = nullptr;
size_t size = FormatMessageW(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
@@ -300,7 +317,7 @@ Error OS_Windows::get_entropy(uint8_t *r_buffer, int p_bytes) {
}
#ifdef DEBUG_ENABLED
-void debug_dynamic_library_check_dependencies(const String &p_root_path, const String &p_path, HashSet<String> &r_checked, HashSet<String> &r_missing) {
+void debug_dynamic_library_check_dependencies(const String &p_path, HashSet<String> &r_checked, HashSet<String> &r_missing) {
if (r_checked.has(p_path)) {
return;
}
@@ -342,15 +359,15 @@ void debug_dynamic_library_check_dependencies(const String &p_root_path, const S
const IMAGE_IMPORT_DESCRIPTOR *import_desc = (const IMAGE_IMPORT_DESCRIPTOR *)ImageDirectoryEntryToData((HMODULE)loaded_image.MappedAddress, false, IMAGE_DIRECTORY_ENTRY_IMPORT, &size);
if (import_desc) {
for (; import_desc->Name && import_desc->FirstThunk; import_desc++) {
- char16_t full_name_wc[MAX_PATH];
+ char16_t full_name_wc[32767];
const char *name_cs = (const char *)ImageRvaToVa(loaded_image.FileHeader, loaded_image.MappedAddress, import_desc->Name, nullptr);
String name = String(name_cs);
if (name.begins_with("api-ms-win-")) {
r_checked.insert(name);
- } else if (SearchPathW(nullptr, (LPCWSTR)name.utf16().get_data(), nullptr, MAX_PATH, (LPWSTR)full_name_wc, nullptr)) {
- debug_dynamic_library_check_dependencies(p_root_path, String::utf16(full_name_wc), r_checked, r_missing);
- } else if (SearchPathW((LPCWSTR)(p_path.get_base_dir().utf16().get_data()), (LPCWSTR)name.utf16().get_data(), nullptr, MAX_PATH, (LPWSTR)full_name_wc, nullptr)) {
- debug_dynamic_library_check_dependencies(p_root_path, String::utf16(full_name_wc), r_checked, r_missing);
+ } else if (SearchPathW(nullptr, (LPCWSTR)name.utf16().get_data(), nullptr, 32767, (LPWSTR)full_name_wc, nullptr)) {
+ debug_dynamic_library_check_dependencies(String::utf16(full_name_wc), r_checked, r_missing);
+ } else if (SearchPathW((LPCWSTR)(p_path.get_base_dir().utf16().get_data()), (LPCWSTR)name.utf16().get_data(), nullptr, 32767, (LPWSTR)full_name_wc, nullptr)) {
+ debug_dynamic_library_check_dependencies(String::utf16(full_name_wc), r_checked, r_missing);
} else {
r_missing.insert(name);
}
@@ -367,7 +384,7 @@ void debug_dynamic_library_check_dependencies(const String &p_root_path, const S
#endif
Error OS_Windows::open_dynamic_library(const String &p_path, void *&p_library_handle, GDExtensionData *p_data) {
- String path = p_path.replace("/", "\\");
+ String path = p_path;
if (!FileAccess::exists(path)) {
//this code exists so gdextension can load .dll files from within the executable path
@@ -413,11 +430,13 @@ Error OS_Windows::open_dynamic_library(const String &p_path, void *&p_library_ha
bool has_dll_directory_api = ((add_dll_directory != nullptr) && (remove_dll_directory != nullptr));
DLL_DIRECTORY_COOKIE cookie = nullptr;
+ String dll_path = fix_path(load_path);
+ String dll_dir = fix_path(ProjectSettings::get_singleton()->globalize_path(load_path.get_base_dir()));
if (p_data != nullptr && p_data->also_set_library_path && has_dll_directory_api) {
- cookie = add_dll_directory((LPCWSTR)(load_path.get_base_dir().utf16().get_data()));
+ cookie = add_dll_directory((LPCWSTR)(dll_dir.utf16().get_data()));
}
- p_library_handle = (void *)LoadLibraryExW((LPCWSTR)(load_path.utf16().get_data()), nullptr, (p_data != nullptr && p_data->also_set_library_path && has_dll_directory_api) ? LOAD_LIBRARY_SEARCH_DEFAULT_DIRS : 0);
+ p_library_handle = (void *)LoadLibraryExW((LPCWSTR)(dll_path.utf16().get_data()), nullptr, (p_data != nullptr && p_data->also_set_library_path && has_dll_directory_api) ? LOAD_LIBRARY_SEARCH_DEFAULT_DIRS : 0);
if (!p_library_handle) {
if (p_data != nullptr && p_data->generate_temp_files) {
DirAccess::remove_absolute(load_path);
@@ -428,7 +447,7 @@ Error OS_Windows::open_dynamic_library(const String &p_path, void *&p_library_ha
HashSet<String> checked_libs;
HashSet<String> missing_libs;
- debug_dynamic_library_check_dependencies(load_path, load_path, checked_libs, missing_libs);
+ debug_dynamic_library_check_dependencies(dll_path, checked_libs, missing_libs);
if (!missing_libs.is_empty()) {
String missing;
for (const String &E : missing_libs) {
@@ -616,6 +635,72 @@ Vector<String> OS_Windows::get_video_adapter_driver_info() const {
return info;
}
+bool OS_Windows::get_user_prefers_integrated_gpu() const {
+ // On Windows 10, the preferred GPU configured in Windows Settings is
+ // stored in the registry under the key
+ // `HKEY_CURRENT_USER\SOFTWARE\Microsoft\DirectX\UserGpuPreferences`
+ // with the name being the app ID or EXE path. The value is in the form of
+ // `GpuPreference=1;`, with the value being 1 for integrated GPU and 2
+ // for discrete GPU. On Windows 11, there may be more flags, separated
+ // by semicolons.
+
+ // If this is a packaged app, use the "application user model ID".
+ // Otherwise, use the EXE path.
+ WCHAR value_name[32768];
+ bool is_packaged = false;
+ {
+ HMODULE kernel32 = GetModuleHandleW(L"kernel32.dll");
+ if (kernel32) {
+ using GetCurrentApplicationUserModelIdPtr = LONG(WINAPI *)(UINT32 * length, PWSTR id);
+ GetCurrentApplicationUserModelIdPtr GetCurrentApplicationUserModelId = (GetCurrentApplicationUserModelIdPtr)GetProcAddress(kernel32, "GetCurrentApplicationUserModelId");
+
+ if (GetCurrentApplicationUserModelId) {
+ UINT32 length = sizeof(value_name) / sizeof(value_name[0]);
+ LONG result = GetCurrentApplicationUserModelId(&length, value_name);
+ if (result == ERROR_SUCCESS) {
+ is_packaged = true;
+ }
+ }
+ }
+ }
+ if (!is_packaged && GetModuleFileNameW(nullptr, value_name, sizeof(value_name) / sizeof(value_name[0])) >= sizeof(value_name) / sizeof(value_name[0])) {
+ // Paths should never be longer than 32767, but just in case.
+ return false;
+ }
+
+ LPCWSTR subkey = L"SOFTWARE\\Microsoft\\DirectX\\UserGpuPreferences";
+ HKEY hkey = nullptr;
+ LSTATUS result = RegOpenKeyExW(HKEY_CURRENT_USER, subkey, 0, KEY_READ, &hkey);
+ if (result != ERROR_SUCCESS) {
+ return false;
+ }
+
+ DWORD size = 0;
+ result = RegGetValueW(hkey, nullptr, value_name, RRF_RT_REG_SZ, nullptr, nullptr, &size);
+ if (result != ERROR_SUCCESS || size == 0) {
+ RegCloseKey(hkey);
+ return false;
+ }
+
+ Vector<WCHAR> buffer;
+ buffer.resize(size / sizeof(WCHAR));
+ result = RegGetValueW(hkey, nullptr, value_name, RRF_RT_REG_SZ, nullptr, (LPBYTE)buffer.ptrw(), &size);
+ if (result != ERROR_SUCCESS) {
+ RegCloseKey(hkey);
+ return false;
+ }
+
+ RegCloseKey(hkey);
+ const String flags = String::utf16((const char16_t *)buffer.ptr(), size / sizeof(WCHAR));
+
+ for (const String &flag : flags.split(";", false)) {
+ if (flag == "GpuPreference=1") {
+ return true;
+ }
+ }
+ return false;
+}
+
OS::DateTime OS_Windows::get_datetime(bool p_utc) const {
SYSTEMTIME systemtime;
if (p_utc) {
@@ -793,7 +878,7 @@ Dictionary OS_Windows::get_memory_info() const {
return meminfo;
}
-Dictionary OS_Windows::execute_with_pipe(const String &p_path, const List<String> &p_arguments) {
+Dictionary OS_Windows::execute_with_pipe(const String &p_path, const List<String> &p_arguments, bool p_blocking) {
#define CLEAN_PIPES \
if (pipe_in[0] != 0) { \
CloseHandle(pipe_in[0]); \
@@ -816,7 +901,7 @@ Dictionary OS_Windows::execute_with_pipe(const String &p_path, const List<String
Dictionary ret;
- String path = p_path.replace("/", "\\");
+ String path = p_path.is_absolute_path() ? fix_path(p_path) : p_path;
String command = _quote_command_line_argument(path);
for (const String &E : p_arguments) {
command += " " + _quote_command_line_argument(E);
@@ -865,7 +950,19 @@ Dictionary OS_Windows::execute_with_pipe(const String &p_path, const List<String
DWORD creation_flags = NORMAL_PRIORITY_CLASS | CREATE_NO_WINDOW;
- if (!CreateProcessW(nullptr, (LPWSTR)(command.utf16().ptrw()), nullptr, nullptr, true, creation_flags, nullptr, nullptr, si_w, &pi.pi)) {
+ Char16String current_dir_name;
+ size_t str_len = GetCurrentDirectoryW(0, nullptr);
+ current_dir_name.resize(str_len + 1);
+ GetCurrentDirectoryW(current_dir_name.size(), (LPWSTR)current_dir_name.ptrw());
+ if (current_dir_name.size() >= MAX_PATH) {
+ Char16String current_short_dir_name;
+ str_len = GetShortPathNameW((LPCWSTR)current_dir_name.ptr(), nullptr, 0);
+ current_short_dir_name.resize(str_len);
+ GetShortPathNameW((LPCWSTR)current_dir_name.ptr(), (LPWSTR)current_short_dir_name.ptrw(), current_short_dir_name.size());
+ current_dir_name = current_short_dir_name;
+ }
+
+ if (!CreateProcessW(nullptr, (LPWSTR)(command.utf16().ptrw()), nullptr, nullptr, true, creation_flags, nullptr, (LPWSTR)current_dir_name.ptr(), si_w, &pi.pi)) {
CLEAN_PIPES
ERR_FAIL_V_MSG(ret, "Could not create child process: " + command);
}
@@ -880,11 +977,11 @@ Dictionary OS_Windows::execute_with_pipe(const String &p_path, const List<String
Ref<FileAccessWindowsPipe> main_pipe;
main_pipe.instantiate();
- main_pipe->open_existing(pipe_out[0], pipe_in[1]);
+ main_pipe->open_existing(pipe_out[0], pipe_in[1], p_blocking);
Ref<FileAccessWindowsPipe> err_pipe;
err_pipe.instantiate();
- err_pipe->open_existing(pipe_err[0], 0);
+ err_pipe->open_existing(pipe_err[0], 0, p_blocking);
ret["stdio"] = main_pipe;
ret["stderr"] = err_pipe;
@@ -895,7 +992,7 @@ Dictionary OS_Windows::execute_with_pipe(const String &p_path, const List<String
}
Error OS_Windows::execute(const String &p_path, const List<String> &p_arguments, String *r_pipe, int *r_exitcode, bool read_stderr, Mutex *p_pipe_mutex, bool p_open_console) {
- String path = p_path.replace("/", "\\");
+ String path = p_path.is_absolute_path() ? fix_path(p_path) : p_path;
String command = _quote_command_line_argument(path);
for (const String &E : p_arguments) {
command += " " + _quote_command_line_argument(E);
@@ -933,7 +1030,19 @@ Error OS_Windows::execute(const String &p_path, const List<String> &p_arguments,
creation_flags |= CREATE_NO_WINDOW;
}
- int ret = CreateProcessW(nullptr, (LPWSTR)(command.utf16().ptrw()), nullptr, nullptr, inherit_handles, creation_flags, nullptr, nullptr, si_w, &pi.pi);
+ Char16String current_dir_name;
+ size_t str_len = GetCurrentDirectoryW(0, nullptr);
+ current_dir_name.resize(str_len + 1);
+ GetCurrentDirectoryW(current_dir_name.size(), (LPWSTR)current_dir_name.ptrw());
+ if (current_dir_name.size() >= MAX_PATH) {
+ Char16String current_short_dir_name;
+ str_len = GetShortPathNameW((LPCWSTR)current_dir_name.ptr(), nullptr, 0);
+ current_short_dir_name.resize(str_len);
+ GetShortPathNameW((LPCWSTR)current_dir_name.ptr(), (LPWSTR)current_short_dir_name.ptrw(), current_short_dir_name.size());
+ current_dir_name = current_short_dir_name;
+ }
+
+ int ret = CreateProcessW(nullptr, (LPWSTR)(command.utf16().ptrw()), nullptr, nullptr, inherit_handles, creation_flags, nullptr, (LPWSTR)current_dir_name.ptr(), si_w, &pi.pi);
if (!ret && r_pipe) {
CloseHandle(pipe[0]); // Cleanup pipe handles.
CloseHandle(pipe[1]);
@@ -997,7 +1106,7 @@ Error OS_Windows::execute(const String &p_path, const List<String> &p_arguments,
}
Error OS_Windows::create_process(const String &p_path, const List<String> &p_arguments, ProcessID *r_child_id, bool p_open_console) {
- String path = p_path.replace("/", "\\");
+ String path = p_path.is_absolute_path() ? fix_path(p_path) : p_path;
String command = _quote_command_line_argument(path);
for (const String &E : p_arguments) {
command += " " + _quote_command_line_argument(E);
@@ -1016,7 +1125,19 @@ Error OS_Windows::create_process(const String &p_path, const List<String> &p_arg
creation_flags |= CREATE_NO_WINDOW;
}
- int ret = CreateProcessW(nullptr, (LPWSTR)(command.utf16().ptrw()), nullptr, nullptr, false, creation_flags, nullptr, nullptr, si_w, &pi.pi);
+ Char16String current_dir_name;
+ size_t str_len = GetCurrentDirectoryW(0, nullptr);
+ current_dir_name.resize(str_len + 1);
+ GetCurrentDirectoryW(current_dir_name.size(), (LPWSTR)current_dir_name.ptrw());
+ if (current_dir_name.size() >= MAX_PATH) {
+ Char16String current_short_dir_name;
+ str_len = GetShortPathNameW((LPCWSTR)current_dir_name.ptr(), nullptr, 0);
+ current_short_dir_name.resize(str_len);
+ GetShortPathNameW((LPCWSTR)current_dir_name.ptr(), (LPWSTR)current_short_dir_name.ptrw(), current_short_dir_name.size());
+ current_dir_name = current_short_dir_name;
+ }
+
+ int ret = CreateProcessW(nullptr, (LPWSTR)(command.utf16().ptrw()), nullptr, nullptr, false, creation_flags, nullptr, (LPWSTR)current_dir_name.ptr(), si_w, &pi.pi);
ERR_FAIL_COND_V_MSG(ret == 0, ERR_CANT_FORK, "Could not create child process: " + command);
ProcessID pid = pi.pi.dwProcessId;
@@ -1384,8 +1505,8 @@ Vector<String> OS_Windows::get_system_font_path_for_text(const String &p_font_na
continue;
}
- WCHAR file_path[MAX_PATH];
- hr = loader->GetFilePathFromKey(reference_key, reference_key_size, &file_path[0], MAX_PATH);
+ WCHAR file_path[32767];
+ hr = loader->GetFilePathFromKey(reference_key, reference_key_size, &file_path[0], 32767);
if (FAILED(hr)) {
continue;
}
@@ -1463,8 +1584,8 @@ String OS_Windows::get_system_font_path(const String &p_font_name, int p_weight,
continue;
}
- WCHAR file_path[MAX_PATH];
- hr = loader->GetFilePathFromKey(reference_key, reference_key_size, &file_path[0], MAX_PATH);
+ WCHAR file_path[32767];
+ hr = loader->GetFilePathFromKey(reference_key, reference_key_size, &file_path[0], 32767);
if (FAILED(hr)) {
continue;
}
@@ -1493,16 +1614,7 @@ String OS_Windows::get_executable_path() const {
}
bool OS_Windows::has_environment(const String &p_var) const {
-#ifdef MINGW_ENABLED
- return _wgetenv((LPCWSTR)(p_var.utf16().get_data())) != nullptr;
-#else
- WCHAR *env;
- size_t len;
- _wdupenv_s(&env, &len, (LPCWSTR)(p_var.utf16().get_data()));
- const bool has_env = env != nullptr;
- free(env);
- return has_env;
-#endif
+ return GetEnvironmentVariableW((LPCWSTR)(p_var.utf16().get_data()), nullptr, 0) > 0;
}
String OS_Windows::get_environment(const String &p_var) const {
@@ -1570,7 +1682,7 @@ Error OS_Windows::shell_show_in_file_manager(String p_path, bool p_open_folder)
if (!p_path.is_quoted()) {
p_path = p_path.quote();
}
- p_path = p_path.replace("/", "\\");
+ p_path = fix_path(p_path);
INT_PTR ret = OK;
if (open_folder) {
@@ -1895,6 +2007,19 @@ String OS_Windows::get_system_ca_certificates() {
OS_Windows::OS_Windows(HINSTANCE _hInstance) {
hInstance = _hInstance;
+ // Reset CWD to ensure long path is used.
+ Char16String current_dir_name;
+ size_t str_len = GetCurrentDirectoryW(0, nullptr);
+ current_dir_name.resize(str_len + 1);
+ GetCurrentDirectoryW(current_dir_name.size(), (LPWSTR)current_dir_name.ptrw());
+
+ Char16String new_current_dir_name;
+ str_len = GetLongPathNameW((LPCWSTR)current_dir_name.get_data(), nullptr, 0);
+ new_current_dir_name.resize(str_len + 1);
+ GetLongPathNameW((LPCWSTR)current_dir_name.get_data(), (LPWSTR)new_current_dir_name.ptrw(), new_current_dir_name.size());
+
+ SetCurrentDirectoryW((LPCWSTR)new_current_dir_name.get_data());
+
#ifndef WINDOWS_SUBSYSTEM_CONSOLE
RedirectIOToConsole();
#endif
diff --git a/platform/windows/os_windows.h b/platform/windows/os_windows.h
index b6a21ed42d..4f9bc049ee 100644
--- a/platform/windows/os_windows.h
+++ b/platform/windows/os_windows.h
@@ -172,6 +172,7 @@ public:
virtual String get_version() const override;
virtual Vector<String> get_video_adapter_driver_info() const override;
+ virtual bool get_user_prefers_integrated_gpu() const override;
virtual void initialize_joypads() override {}
@@ -187,7 +188,7 @@ public:
virtual Dictionary get_memory_info() const override;
virtual Error execute(const String &p_path, const List<String> &p_arguments, String *r_pipe = nullptr, int *r_exitcode = nullptr, bool read_stderr = false, Mutex *p_pipe_mutex = nullptr, bool p_open_console = false) override;
- virtual Dictionary execute_with_pipe(const String &p_path, const List<String> &p_arguments) override;
+ virtual Dictionary execute_with_pipe(const String &p_path, const List<String> &p_arguments, bool p_blocking = true) override;
virtual Error create_process(const String &p_path, const List<String> &p_arguments, ProcessID *r_child_id = nullptr, bool p_open_console = false) override;
virtual Error kill(const ProcessID &p_pid) override;
virtual int get_process_id() const override;
diff --git a/platform/windows/windows_utils.cpp b/platform/windows/windows_utils.cpp
index 9e0b9eed8a..30743c6900 100644
--- a/platform/windows/windows_utils.cpp
+++ b/platform/windows/windows_utils.cpp
@@ -155,7 +155,11 @@ Error WindowsUtils::copy_and_rename_pdb(const String &p_dll_path) {
} else if (!FileAccess::exists(copy_pdb_path)) {
copy_pdb_path = dll_base_dir.path_join(copy_pdb_path.get_file());
}
- ERR_FAIL_COND_V_MSG(!FileAccess::exists(copy_pdb_path), FAILED, vformat("File '%s' does not exist.", copy_pdb_path));
+ if (!FileAccess::exists(copy_pdb_path)) {
+ // The PDB file may be distributed separately on purpose, so we don't consider this an error.
+ WARN_VERBOSE(vformat("PDB file '%s' for library '%s' was not found, skipping copy/rename.", copy_pdb_path, p_dll_path));
+ return ERR_SKIP;
+ }
String new_pdb_base_name = p_dll_path.get_file().get_basename() + "_";
diff --git a/scene/2d/animated_sprite_2d.cpp b/scene/2d/animated_sprite_2d.cpp
index 014419573c..1cab29f383 100644
--- a/scene/2d/animated_sprite_2d.cpp
+++ b/scene/2d/animated_sprite_2d.cpp
@@ -463,7 +463,7 @@ void AnimatedSprite2D::play(const StringName &p_name, float p_custom_scale, bool
name = animation;
}
- ERR_FAIL_NULL_MSG(frames, vformat("There is no animation with name '%s'.", name));
+ ERR_FAIL_COND_MSG(frames.is_null(), vformat("There is no animation with name '%s'.", name));
ERR_FAIL_COND_MSG(!frames->get_animation_names().has(name), vformat("There is no animation with name '%s'.", name));
if (frames->get_frame_count(name) == 0) {
@@ -541,7 +541,7 @@ void AnimatedSprite2D::set_animation(const StringName &p_name) {
emit_signal(SceneStringName(animation_changed));
- if (frames == nullptr) {
+ if (frames.is_null()) {
animation = StringName();
stop();
ERR_FAIL_MSG(vformat("There is no animation with name '%s'.", p_name));
diff --git a/scene/2d/audio_stream_player_2d.cpp b/scene/2d/audio_stream_player_2d.cpp
index 89a0479de3..7c60e47e64 100644
--- a/scene/2d/audio_stream_player_2d.cpp
+++ b/scene/2d/audio_stream_player_2d.cpp
@@ -276,10 +276,6 @@ void AudioStreamPlayer2D::_set_playing(bool p_enable) {
internal->set_playing(p_enable);
}
-bool AudioStreamPlayer2D::_is_active() const {
- return internal->is_active();
-}
-
void AudioStreamPlayer2D::_validate_property(PropertyInfo &p_property) const {
internal->validate_property(p_property);
}
@@ -385,8 +381,7 @@ void AudioStreamPlayer2D::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_autoplay", "enable"), &AudioStreamPlayer2D::set_autoplay);
ClassDB::bind_method(D_METHOD("is_autoplay_enabled"), &AudioStreamPlayer2D::is_autoplay_enabled);
- ClassDB::bind_method(D_METHOD("_set_playing", "enable"), &AudioStreamPlayer2D::_set_playing);
- ClassDB::bind_method(D_METHOD("_is_active"), &AudioStreamPlayer2D::_is_active);
+ ClassDB::bind_method(D_METHOD("set_playing", "enable"), &AudioStreamPlayer2D::_set_playing);
ClassDB::bind_method(D_METHOD("set_max_distance", "pixels"), &AudioStreamPlayer2D::set_max_distance);
ClassDB::bind_method(D_METHOD("get_max_distance"), &AudioStreamPlayer2D::get_max_distance);
@@ -415,7 +410,7 @@ void AudioStreamPlayer2D::_bind_methods() {
ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "stream", PROPERTY_HINT_RESOURCE_TYPE, "AudioStream"), "set_stream", "get_stream");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "volume_db", PROPERTY_HINT_RANGE, "-80,24,suffix:dB"), "set_volume_db", "get_volume_db");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "pitch_scale", PROPERTY_HINT_RANGE, "0.01,4,0.01,or_greater"), "set_pitch_scale", "get_pitch_scale");
- ADD_PROPERTY(PropertyInfo(Variant::BOOL, "playing", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_EDITOR), "_set_playing", "is_playing");
+ ADD_PROPERTY(PropertyInfo(Variant::BOOL, "playing", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_EDITOR), "set_playing", "is_playing");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "autoplay"), "set_autoplay", "is_autoplay_enabled");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "stream_paused", PROPERTY_HINT_NONE, ""), "set_stream_paused", "get_stream_paused");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "max_distance", PROPERTY_HINT_RANGE, "1,4096,1,or_greater,exp,suffix:px"), "set_max_distance", "get_max_distance");
diff --git a/scene/2d/canvas_modulate.cpp b/scene/2d/canvas_modulate.cpp
index 2c5c6a1a16..dc83775c71 100644
--- a/scene/2d/canvas_modulate.cpp
+++ b/scene/2d/canvas_modulate.cpp
@@ -114,7 +114,7 @@ Color CanvasModulate::get_color() const {
}
PackedStringArray CanvasModulate::get_configuration_warnings() const {
- PackedStringArray warnings = Node::get_configuration_warnings();
+ PackedStringArray warnings = Node2D::get_configuration_warnings();
if (is_in_canvas && is_visible_in_tree()) {
List<Node *> nodes;
diff --git a/scene/2d/light_2d.cpp b/scene/2d/light_2d.cpp
index 5ce26b3ed4..50c5873781 100644
--- a/scene/2d/light_2d.cpp
+++ b/scene/2d/light_2d.cpp
@@ -417,7 +417,7 @@ Vector2 PointLight2D::get_texture_offset() const {
}
PackedStringArray PointLight2D::get_configuration_warnings() const {
- PackedStringArray warnings = Node::get_configuration_warnings();
+ PackedStringArray warnings = Node2D::get_configuration_warnings();
if (!texture.is_valid()) {
warnings.push_back(RTR("A texture with the shape of the light must be supplied to the \"Texture\" property."));
diff --git a/scene/2d/light_occluder_2d.cpp b/scene/2d/light_occluder_2d.cpp
index 092c987ac0..7c3fb61d04 100644
--- a/scene/2d/light_occluder_2d.cpp
+++ b/scene/2d/light_occluder_2d.cpp
@@ -263,7 +263,7 @@ int LightOccluder2D::get_occluder_light_mask() const {
}
PackedStringArray LightOccluder2D::get_configuration_warnings() const {
- PackedStringArray warnings = Node::get_configuration_warnings();
+ PackedStringArray warnings = Node2D::get_configuration_warnings();
if (!occluder_polygon.is_valid()) {
warnings.push_back(RTR("An occluder polygon must be set (or drawn) for this occluder to take effect."));
diff --git a/scene/2d/navigation_link_2d.cpp b/scene/2d/navigation_link_2d.cpp
index 111f5a7b78..4961e18dc9 100644
--- a/scene/2d/navigation_link_2d.cpp
+++ b/scene/2d/navigation_link_2d.cpp
@@ -328,7 +328,7 @@ void NavigationLink2D::set_travel_cost(real_t p_travel_cost) {
}
PackedStringArray NavigationLink2D::get_configuration_warnings() const {
- PackedStringArray warnings = Node::get_configuration_warnings();
+ PackedStringArray warnings = Node2D::get_configuration_warnings();
if (start_position.is_equal_approx(end_position)) {
warnings.push_back(RTR("NavigationLink2D start position should be different than the end position to be useful."));
diff --git a/scene/2d/navigation_region_2d.cpp b/scene/2d/navigation_region_2d.cpp
index 9b3c7bb9ea..f65a3c0ecc 100644
--- a/scene/2d/navigation_region_2d.cpp
+++ b/scene/2d/navigation_region_2d.cpp
@@ -452,6 +452,7 @@ void NavigationRegion2D::_update_debug_mesh() {
const Transform2D region_gt = get_global_transform();
rs->canvas_item_set_parent(debug_instance_rid, get_world_2d()->get_canvas());
+ rs->canvas_item_set_z_index(debug_instance_rid, RS::CANVAS_ITEM_Z_MAX - 2);
rs->canvas_item_set_transform(debug_instance_rid, region_gt);
if (!debug_mesh_dirty) {
diff --git a/scene/2d/parallax_2d.cpp b/scene/2d/parallax_2d.cpp
index 9dd9d4a376..fdb2d2cdd0 100644
--- a/scene/2d/parallax_2d.cpp
+++ b/scene/2d/parallax_2d.cpp
@@ -47,9 +47,18 @@ void Parallax2D::_notification(int p_what) {
} break;
case NOTIFICATION_INTERNAL_PROCESS: {
- autoscroll_offset += autoscroll * get_process_delta_time();
- autoscroll_offset = autoscroll_offset.posmodv(repeat_size);
+ Point2 offset = scroll_offset;
+ offset += autoscroll * get_process_delta_time();
+ if (repeat_size.x) {
+ offset.x = Math::fposmod(offset.x, repeat_size.x);
+ }
+
+ if (repeat_size.y) {
+ offset.y = Math::fposmod(offset.y, repeat_size.y);
+ }
+
+ scroll_offset = offset;
_update_scroll();
} break;
@@ -106,14 +115,14 @@ void Parallax2D::_update_scroll() {
scroll_ofs *= scroll_scale;
if (repeat_size.x) {
- real_t mod = Math::fposmod(scroll_ofs.x - scroll_offset.x - autoscroll_offset.x, repeat_size.x * get_scale().x);
+ real_t mod = Math::fposmod(scroll_ofs.x - scroll_offset.x, repeat_size.x * get_scale().x);
scroll_ofs.x = screen_offset.x - mod;
} else {
scroll_ofs.x = screen_offset.x + scroll_offset.x - scroll_ofs.x;
}
if (repeat_size.y) {
- real_t mod = Math::fposmod(scroll_ofs.y - scroll_offset.y - autoscroll_offset.y, repeat_size.y * get_scale().y);
+ real_t mod = Math::fposmod(scroll_ofs.y - scroll_offset.y, repeat_size.y * get_scale().y);
scroll_ofs.y = screen_offset.y - mod;
} else {
scroll_ofs.y = screen_offset.y + scroll_offset.y - scroll_ofs.y;
@@ -193,7 +202,6 @@ void Parallax2D::set_autoscroll(const Point2 &p_autoscroll) {
}
autoscroll = p_autoscroll;
- autoscroll_offset = Point2();
_update_process();
_update_scroll();
diff --git a/scene/2d/parallax_2d.h b/scene/2d/parallax_2d.h
index 5fbc3a20c8..f15e3fa9ff 100644
--- a/scene/2d/parallax_2d.h
+++ b/scene/2d/parallax_2d.h
@@ -47,7 +47,6 @@ class Parallax2D : public Node2D {
Point2 limit_begin = Point2(-DEFAULT_LIMIT, -DEFAULT_LIMIT);
Point2 limit_end = Point2(DEFAULT_LIMIT, DEFAULT_LIMIT);
Point2 autoscroll;
- Point2 autoscroll_offset;
bool follow_viewport = true;
bool ignore_camera_scroll = false;
diff --git a/scene/2d/parallax_layer.cpp b/scene/2d/parallax_layer.cpp
index 023e9201fc..24f261deb6 100644
--- a/scene/2d/parallax_layer.cpp
+++ b/scene/2d/parallax_layer.cpp
@@ -133,7 +133,7 @@ void ParallaxLayer::set_base_offset_and_scale(const Point2 &p_offset, real_t p_s
}
PackedStringArray ParallaxLayer::get_configuration_warnings() const {
- PackedStringArray warnings = Node::get_configuration_warnings();
+ PackedStringArray warnings = Node2D::get_configuration_warnings();
if (!Object::cast_to<ParallaxBackground>(get_parent())) {
warnings.push_back(RTR("ParallaxLayer node only works when set as child of a ParallaxBackground node."));
diff --git a/scene/2d/path_2d.cpp b/scene/2d/path_2d.cpp
index 282d14da5d..5813ab02e3 100644
--- a/scene/2d/path_2d.cpp
+++ b/scene/2d/path_2d.cpp
@@ -288,7 +288,7 @@ void PathFollow2D::_validate_property(PropertyInfo &p_property) const {
}
PackedStringArray PathFollow2D::get_configuration_warnings() const {
- PackedStringArray warnings = Node::get_configuration_warnings();
+ PackedStringArray warnings = Node2D::get_configuration_warnings();
if (is_visible_in_tree() && is_inside_tree()) {
if (!Object::cast_to<Path2D>(get_parent())) {
@@ -378,9 +378,10 @@ real_t PathFollow2D::get_progress() const {
}
void PathFollow2D::set_progress_ratio(real_t p_ratio) {
- if (path && path->get_curve().is_valid() && path->get_curve()->get_baked_length()) {
- set_progress(p_ratio * path->get_curve()->get_baked_length());
- }
+ ERR_FAIL_NULL_MSG(path, "Can only set progress ratio on a PathFollow2D that is the child of a Path2D which is itself part of the scene tree.");
+ ERR_FAIL_COND_MSG(path->get_curve().is_null(), "Can't set progress ratio on a PathFollow2D that does not have a Curve.");
+ ERR_FAIL_COND_MSG(!path->get_curve()->get_baked_length(), "Can't set progress ratio on a PathFollow2D that has a 0 length curve.");
+ set_progress(p_ratio * path->get_curve()->get_baked_length());
}
real_t PathFollow2D::get_progress_ratio() const {
diff --git a/scene/2d/physics/collision_object_2d.cpp b/scene/2d/physics/collision_object_2d.cpp
index 00b6085f0c..27ee6b883c 100644
--- a/scene/2d/physics/collision_object_2d.cpp
+++ b/scene/2d/physics/collision_object_2d.cpp
@@ -582,7 +582,7 @@ void CollisionObject2D::_update_pickable() {
}
PackedStringArray CollisionObject2D::get_configuration_warnings() const {
- PackedStringArray warnings = Node::get_configuration_warnings();
+ PackedStringArray warnings = Node2D::get_configuration_warnings();
if (shapes.is_empty()) {
warnings.push_back(RTR("This node has no shape, so it can't collide or interact with other objects.\nConsider adding a CollisionShape2D or CollisionPolygon2D as a child to define its shape."));
diff --git a/scene/2d/physics/collision_polygon_2d.cpp b/scene/2d/physics/collision_polygon_2d.cpp
index a9b47ef4d4..b49badac1f 100644
--- a/scene/2d/physics/collision_polygon_2d.cpp
+++ b/scene/2d/physics/collision_polygon_2d.cpp
@@ -232,7 +232,7 @@ bool CollisionPolygon2D::_edit_is_selected_on_click(const Point2 &p_point, doubl
#endif
PackedStringArray CollisionPolygon2D::get_configuration_warnings() const {
- PackedStringArray warnings = Node::get_configuration_warnings();
+ PackedStringArray warnings = Node2D::get_configuration_warnings();
if (!Object::cast_to<CollisionObject2D>(get_parent())) {
warnings.push_back(RTR("CollisionPolygon2D only serves to provide a collision shape to a CollisionObject2D derived node. Please only use it as a child of Area2D, StaticBody2D, RigidBody2D, CharacterBody2D, etc. to give them a shape."));
diff --git a/scene/2d/physics/collision_shape_2d.cpp b/scene/2d/physics/collision_shape_2d.cpp
index 6fc29ffe63..bdd0d06b5e 100644
--- a/scene/2d/physics/collision_shape_2d.cpp
+++ b/scene/2d/physics/collision_shape_2d.cpp
@@ -174,7 +174,7 @@ bool CollisionShape2D::_edit_is_selected_on_click(const Point2 &p_point, double
}
PackedStringArray CollisionShape2D::get_configuration_warnings() const {
- PackedStringArray warnings = Node::get_configuration_warnings();
+ PackedStringArray warnings = Node2D::get_configuration_warnings();
CollisionObject2D *col_object = Object::cast_to<CollisionObject2D>(get_parent());
if (col_object == nullptr) {
diff --git a/scene/2d/physics/physical_bone_2d.cpp b/scene/2d/physics/physical_bone_2d.cpp
index 77bb8c24b8..19274c8084 100644
--- a/scene/2d/physics/physical_bone_2d.cpp
+++ b/scene/2d/physics/physical_bone_2d.cpp
@@ -107,7 +107,7 @@ void PhysicalBone2D::_find_joint_child() {
}
PackedStringArray PhysicalBone2D::get_configuration_warnings() const {
- PackedStringArray warnings = Node::get_configuration_warnings();
+ PackedStringArray warnings = RigidBody2D::get_configuration_warnings();
if (!parent_skeleton) {
warnings.push_back(RTR("A PhysicalBone2D only works with a Skeleton2D or another PhysicalBone2D as a parent node!"));
diff --git a/scene/2d/remote_transform_2d.cpp b/scene/2d/remote_transform_2d.cpp
index 5ea5098475..1816a3409b 100644
--- a/scene/2d/remote_transform_2d.cpp
+++ b/scene/2d/remote_transform_2d.cpp
@@ -114,6 +114,16 @@ void RemoteTransform2D::_notification(int p_what) {
_update_cache();
} break;
+ case NOTIFICATION_RESET_PHYSICS_INTERPOLATION: {
+ if (cache.is_valid()) {
+ _update_remote();
+ Node2D *n = Object::cast_to<Node2D>(ObjectDB::get_instance(cache));
+ if (n) {
+ n->reset_physics_interpolation();
+ }
+ }
+ } break;
+
case NOTIFICATION_LOCAL_TRANSFORM_CHANGED:
case NOTIFICATION_TRANSFORM_CHANGED: {
if (!is_inside_tree()) {
@@ -201,7 +211,7 @@ void RemoteTransform2D::force_update_cache() {
}
PackedStringArray RemoteTransform2D::get_configuration_warnings() const {
- PackedStringArray warnings = Node::get_configuration_warnings();
+ PackedStringArray warnings = Node2D::get_configuration_warnings();
if (!has_node(remote_node) || !Object::cast_to<Node2D>(get_node(remote_node))) {
warnings.push_back(RTR("Path property must point to a valid Node2D node to work."));
diff --git a/scene/2d/skeleton_2d.cpp b/scene/2d/skeleton_2d.cpp
index f9e8c831d1..90bfb4c84c 100644
--- a/scene/2d/skeleton_2d.cpp
+++ b/scene/2d/skeleton_2d.cpp
@@ -412,7 +412,7 @@ int Bone2D::get_index_in_skeleton() const {
}
PackedStringArray Bone2D::get_configuration_warnings() const {
- PackedStringArray warnings = Node::get_configuration_warnings();
+ PackedStringArray warnings = Node2D::get_configuration_warnings();
if (!skeleton) {
if (parent_bone) {
warnings.push_back(RTR("This Bone2D chain should end at a Skeleton2D node."));
diff --git a/scene/2d/tile_map.cpp b/scene/2d/tile_map.cpp
index b10f2097da..45cfb8cf33 100644
--- a/scene/2d/tile_map.cpp
+++ b/scene/2d/tile_map.cpp
@@ -827,7 +827,7 @@ TypedArray<Vector2i> TileMap::get_surrounding_cells(const Vector2i &p_coords) {
}
PackedStringArray TileMap::get_configuration_warnings() const {
- PackedStringArray warnings = Node::get_configuration_warnings();
+ PackedStringArray warnings = Node2D::get_configuration_warnings();
warnings.push_back(RTR("The TileMap node is deprecated as it is superseded by the use of multiple TileMapLayer nodes.\nTo convert a TileMap to a set of TileMapLayer nodes, open the TileMap bottom panel with this node selected, click the toolbox icon in the top-right corner and choose \"Extract TileMap layers as individual TileMapLayer nodes\"."));
diff --git a/scene/2d/tile_map_layer.cpp b/scene/2d/tile_map_layer.cpp
index 7b125a6895..ebb03e4e73 100644
--- a/scene/2d/tile_map_layer.cpp
+++ b/scene/2d/tile_map_layer.cpp
@@ -411,7 +411,7 @@ void TileMapLayer::_rendering_update(bool p_force_cleanup) {
}
// ----------- Occluders processing -----------
- if (forced_cleanup) {
+ if (forced_cleanup || !occlusion_enabled) {
// Clean everything.
for (KeyValue<Vector2i, CellData> &kv : tile_map_layer_data) {
_rendering_occluders_clear_cell(kv.value);
@@ -433,7 +433,7 @@ void TileMapLayer::_rendering_update(bool p_force_cleanup) {
// -----------
// Mark the rendering state as up to date.
- _rendering_was_cleaned_up = forced_cleanup;
+ _rendering_was_cleaned_up = forced_cleanup || !occlusion_enabled;
}
void TileMapLayer::_rendering_notification(int p_what) {
@@ -443,13 +443,15 @@ void TileMapLayer::_rendering_notification(int p_what) {
Transform2D tilemap_xform = get_global_transform();
for (KeyValue<Vector2i, CellData> &kv : tile_map_layer_data) {
const CellData &cell_data = kv.value;
- for (const RID &occluder : cell_data.occluders) {
- if (occluder.is_null()) {
- continue;
+ for (const LocalVector<RID> &polygons : cell_data.occluders) {
+ for (const RID &rid : polygons) {
+ if (rid.is_null()) {
+ continue;
+ }
+ Transform2D xform(0, tile_set->map_to_local(kv.key));
+ rs->canvas_light_occluder_attach_to_canvas(rid, get_canvas());
+ rs->canvas_light_occluder_set_transform(rid, tilemap_xform * xform);
}
- Transform2D xform(0, tile_set->map_to_local(kv.key));
- rs->canvas_light_occluder_attach_to_canvas(occluder, get_canvas());
- rs->canvas_light_occluder_set_transform(occluder, tilemap_xform * xform);
}
}
}
@@ -557,8 +559,10 @@ void TileMapLayer::_rendering_occluders_clear_cell(CellData &r_cell_data) {
RenderingServer *rs = RenderingServer::get_singleton();
// Free the occluders.
- for (const RID &rid : r_cell_data.occluders) {
- rs->free(rid);
+ for (const LocalVector<RID> &polygons : r_cell_data.occluders) {
+ for (const RID &rid : polygons) {
+ rs->free(rid);
+ }
}
r_cell_data.occluders.clear();
}
@@ -566,11 +570,12 @@ void TileMapLayer::_rendering_occluders_clear_cell(CellData &r_cell_data) {
void TileMapLayer::_rendering_occluders_update_cell(CellData &r_cell_data) {
RenderingServer *rs = RenderingServer::get_singleton();
- // Free unused occluders then resize the occluders array.
+ // Free unused occluders then resize the occluder array.
for (uint32_t i = tile_set->get_occlusion_layers_count(); i < r_cell_data.occluders.size(); i++) {
- RID occluder_id = r_cell_data.occluders[i];
- if (occluder_id.is_valid()) {
- rs->free(occluder_id);
+ for (const RID &occluder_id : r_cell_data.occluders[i]) {
+ if (occluder_id.is_valid()) {
+ rs->free(occluder_id);
+ }
}
}
r_cell_data.occluders.resize(tile_set->get_occlusion_layers_count());
@@ -598,30 +603,42 @@ void TileMapLayer::_rendering_occluders_update_cell(CellData &r_cell_data) {
// Create, update or clear occluders.
bool needs_set_not_interpolated = is_inside_tree() && get_tree()->is_physics_interpolation_enabled() && !is_physics_interpolated();
for (uint32_t occlusion_layer_index = 0; occlusion_layer_index < r_cell_data.occluders.size(); occlusion_layer_index++) {
- Ref<OccluderPolygon2D> occluder_polygon = tile_data->get_occluder(occlusion_layer_index);
-
- RID &occluder = r_cell_data.occluders[occlusion_layer_index];
+ LocalVector<RID> &occluders = r_cell_data.occluders[occlusion_layer_index];
- if (occluder_polygon.is_valid()) {
- // Create or update occluder.
- Transform2D xform;
- xform.set_origin(tile_set->map_to_local(r_cell_data.coords));
- if (!occluder.is_valid()) {
- occluder = rs->canvas_light_occluder_create();
- if (needs_set_not_interpolated) {
- rs->canvas_light_occluder_set_interpolated(occluder, false);
- }
+ // Free unused occluders then resize the occluders array.
+ for (uint32_t i = tile_data->get_occluder_polygons_count(occlusion_layer_index); i < r_cell_data.occluders[occlusion_layer_index].size(); i++) {
+ RID occluder_id = occluders[i];
+ if (occluder_id.is_valid()) {
+ rs->free(occluder_id);
}
- rs->canvas_light_occluder_set_transform(occluder, get_global_transform() * xform);
- rs->canvas_light_occluder_set_polygon(occluder, tile_data->get_occluder(occlusion_layer_index, flip_h, flip_v, transpose)->get_rid());
- rs->canvas_light_occluder_attach_to_canvas(occluder, get_canvas());
- rs->canvas_light_occluder_set_light_mask(occluder, tile_set->get_occlusion_layer_light_mask(occlusion_layer_index));
- rs->canvas_light_occluder_set_as_sdf_collision(occluder, tile_set->get_occlusion_layer_sdf_collision(occlusion_layer_index));
- } else {
- // Clear occluder.
- if (occluder.is_valid()) {
- rs->free(occluder);
- occluder = RID();
+ }
+ occluders.resize(tile_data->get_occluder_polygons_count(occlusion_layer_index));
+
+ for (uint32_t occlusion_polygon_index = 0; occlusion_polygon_index < occluders.size(); occlusion_polygon_index++) {
+ RID &occluder = occluders[occlusion_polygon_index];
+ Ref<OccluderPolygon2D> occluder_polygon = tile_data->get_occluder_polygon(occlusion_layer_index, occlusion_polygon_index);
+ if (occluder_polygon.is_valid()) {
+ // Create or update occluder.
+
+ Transform2D xform;
+ xform.set_origin(tile_set->map_to_local(r_cell_data.coords));
+ if (!occluder.is_valid()) {
+ occluder = rs->canvas_light_occluder_create();
+ if (needs_set_not_interpolated) {
+ rs->canvas_light_occluder_set_interpolated(occluder, false);
+ }
+ }
+ rs->canvas_light_occluder_set_transform(occluder, get_global_transform() * xform);
+ rs->canvas_light_occluder_set_polygon(occluder, tile_data->get_occluder_polygon(occlusion_layer_index, occlusion_polygon_index, flip_h, flip_v, transpose)->get_rid());
+ rs->canvas_light_occluder_attach_to_canvas(occluder, get_canvas());
+ rs->canvas_light_occluder_set_light_mask(occluder, tile_set->get_occlusion_layer_light_mask(occlusion_layer_index));
+ rs->canvas_light_occluder_set_as_sdf_collision(occluder, tile_set->get_occlusion_layer_sdf_collision(occlusion_layer_index));
+ } else {
+ // Clear occluder.
+ if (occluder.is_valid()) {
+ rs->free(occluder);
+ occluder = RID();
+ }
}
}
}
@@ -957,7 +974,7 @@ void TileMapLayer::_navigation_update(bool p_force_cleanup) {
// Create a dedicated map for each layer.
RID new_layer_map = ns->map_create();
// Set the default NavigationPolygon cell_size on the new map as a mismatch causes an error.
- ns->map_set_cell_size(new_layer_map, 1.0);
+ ns->map_set_cell_size(new_layer_map, NavigationDefaults2D::navmesh_cell_size);
ns->map_set_active(new_layer_map, true);
navigation_map_override = new_layer_map;
}
@@ -1709,11 +1726,13 @@ void TileMapLayer::_physics_interpolated_changed() {
}
for (const KeyValue<Vector2i, CellData> &E : tile_map_layer_data) {
- for (const RID &occluder : E.value.occluders) {
- if (occluder.is_valid()) {
- rs->canvas_light_occluder_set_interpolated(occluder, interpolated);
- if (needs_reset) {
- rs->canvas_light_occluder_reset_physics_interpolation(occluder);
+ for (const LocalVector<RID> &polygons : E.value.occluders) {
+ for (const RID &occluder_id : polygons) {
+ if (occluder_id.is_valid()) {
+ rs->canvas_light_occluder_set_interpolated(occluder_id, interpolated);
+ if (needs_reset) {
+ rs->canvas_light_occluder_reset_physics_interpolation(occluder_id);
+ }
}
}
}
@@ -1828,6 +1847,9 @@ void TileMapLayer::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_collision_visibility_mode", "visibility_mode"), &TileMapLayer::set_collision_visibility_mode);
ClassDB::bind_method(D_METHOD("get_collision_visibility_mode"), &TileMapLayer::get_collision_visibility_mode);
+ ClassDB::bind_method(D_METHOD("set_occlusion_enabled", "enabled"), &TileMapLayer::set_occlusion_enabled);
+ ClassDB::bind_method(D_METHOD("is_occlusion_enabled"), &TileMapLayer::is_occlusion_enabled);
+
ClassDB::bind_method(D_METHOD("set_navigation_enabled", "enabled"), &TileMapLayer::set_navigation_enabled);
ClassDB::bind_method(D_METHOD("is_navigation_enabled"), &TileMapLayer::is_navigation_enabled);
ClassDB::bind_method(D_METHOD("set_navigation_map", "map"), &TileMapLayer::set_navigation_map);
@@ -1843,6 +1865,7 @@ void TileMapLayer::_bind_methods() {
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "enabled"), "set_enabled", "is_enabled");
ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "tile_set", PROPERTY_HINT_RESOURCE_TYPE, "TileSet"), "set_tile_set", "get_tile_set");
ADD_GROUP("Rendering", "");
+ ADD_PROPERTY(PropertyInfo(Variant::BOOL, "occlusion_enabled"), "set_occlusion_enabled", "is_occlusion_enabled");
ADD_PROPERTY(PropertyInfo(Variant::INT, "y_sort_origin"), "set_y_sort_origin", "get_y_sort_origin");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "x_draw_order_reversed"), "set_x_draw_order_reversed", "is_x_draw_order_reversed");
ADD_PROPERTY(PropertyInfo(Variant::INT, "rendering_quadrant_size"), "set_rendering_quadrant_size", "get_rendering_quadrant_size");
@@ -2960,6 +2983,20 @@ TileMapLayer::DebugVisibilityMode TileMapLayer::get_collision_visibility_mode()
return collision_visibility_mode;
}
+void TileMapLayer::set_occlusion_enabled(bool p_enabled) {
+ if (occlusion_enabled == p_enabled) {
+ return;
+ }
+ occlusion_enabled = p_enabled;
+ dirty.flags[DIRTY_FLAGS_LAYER_OCCLUSION_ENABLED] = true;
+ _queue_internal_update();
+ emit_signal(CoreStringName(changed));
+}
+
+bool TileMapLayer::is_occlusion_enabled() const {
+ return occlusion_enabled;
+}
+
void TileMapLayer::set_navigation_enabled(bool p_enabled) {
if (navigation_enabled == p_enabled) {
return;
diff --git a/scene/2d/tile_map_layer.h b/scene/2d/tile_map_layer.h
index 1a6d182094..cc0a5b49fb 100644
--- a/scene/2d/tile_map_layer.h
+++ b/scene/2d/tile_map_layer.h
@@ -108,7 +108,7 @@ struct CellData {
// Rendering.
Ref<RenderingQuadrant> rendering_quadrant;
SelfList<CellData> rendering_quadrant_list_element;
- LocalVector<RID> occluders;
+ LocalVector<LocalVector<RID>> occluders;
// Physics.
LocalVector<RID> bodies;
@@ -254,6 +254,7 @@ public:
DIRTY_FLAGS_LAYER_COLLISION_ENABLED,
DIRTY_FLAGS_LAYER_USE_KINEMATIC_BODIES,
DIRTY_FLAGS_LAYER_COLLISION_VISIBILITY_MODE,
+ DIRTY_FLAGS_LAYER_OCCLUSION_ENABLED,
DIRTY_FLAGS_LAYER_NAVIGATION_ENABLED,
DIRTY_FLAGS_LAYER_NAVIGATION_MAP,
DIRTY_FLAGS_LAYER_NAVIGATION_VISIBILITY_MODE,
@@ -288,6 +289,8 @@ private:
bool use_kinematic_bodies = false;
DebugVisibilityMode collision_visibility_mode = DEBUG_VISIBILITY_MODE_DEFAULT;
+ bool occlusion_enabled = true;
+
bool navigation_enabled = true;
RID navigation_map_override;
DebugVisibilityMode navigation_visibility_mode = DEBUG_VISIBILITY_MODE_DEFAULT;
@@ -497,6 +500,9 @@ public:
void set_collision_visibility_mode(DebugVisibilityMode p_show_collision);
DebugVisibilityMode get_collision_visibility_mode() const;
+ void set_occlusion_enabled(bool p_enabled);
+ bool is_occlusion_enabled() const;
+
void set_navigation_enabled(bool p_enabled);
bool is_navigation_enabled() const;
void set_navigation_map(RID p_map);
diff --git a/scene/3d/audio_stream_player_3d.cpp b/scene/3d/audio_stream_player_3d.cpp
index 4d3f494ccf..591528b915 100644
--- a/scene/3d/audio_stream_player_3d.cpp
+++ b/scene/3d/audio_stream_player_3d.cpp
@@ -596,10 +596,6 @@ void AudioStreamPlayer3D::_set_playing(bool p_enable) {
internal->set_playing(p_enable);
}
-bool AudioStreamPlayer3D::_is_active() const {
- return internal->is_active();
-}
-
void AudioStreamPlayer3D::_validate_property(PropertyInfo &p_property) const {
internal->validate_property(p_property);
}
@@ -779,8 +775,7 @@ void AudioStreamPlayer3D::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_autoplay", "enable"), &AudioStreamPlayer3D::set_autoplay);
ClassDB::bind_method(D_METHOD("is_autoplay_enabled"), &AudioStreamPlayer3D::is_autoplay_enabled);
- ClassDB::bind_method(D_METHOD("_set_playing", "enable"), &AudioStreamPlayer3D::_set_playing);
- ClassDB::bind_method(D_METHOD("_is_active"), &AudioStreamPlayer3D::_is_active);
+ ClassDB::bind_method(D_METHOD("set_playing", "enable"), &AudioStreamPlayer3D::_set_playing);
ClassDB::bind_method(D_METHOD("set_max_distance", "meters"), &AudioStreamPlayer3D::set_max_distance);
ClassDB::bind_method(D_METHOD("get_max_distance"), &AudioStreamPlayer3D::get_max_distance);
@@ -830,7 +825,7 @@ void AudioStreamPlayer3D::_bind_methods() {
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "unit_size", PROPERTY_HINT_RANGE, "0.1,100,0.01,or_greater"), "set_unit_size", "get_unit_size");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "max_db", PROPERTY_HINT_RANGE, "-24,6,suffix:dB"), "set_max_db", "get_max_db");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "pitch_scale", PROPERTY_HINT_RANGE, "0.01,4,0.01,or_greater"), "set_pitch_scale", "get_pitch_scale");
- ADD_PROPERTY(PropertyInfo(Variant::BOOL, "playing", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_EDITOR), "_set_playing", "is_playing");
+ ADD_PROPERTY(PropertyInfo(Variant::BOOL, "playing", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_EDITOR), "set_playing", "is_playing");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "autoplay"), "set_autoplay", "is_autoplay_enabled");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "stream_paused", PROPERTY_HINT_NONE, ""), "set_stream_paused", "get_stream_paused");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "max_distance", PROPERTY_HINT_RANGE, "0,4096,0.01,or_greater,suffix:m"), "set_max_distance", "get_max_distance");
diff --git a/scene/3d/cpu_particles_3d.cpp b/scene/3d/cpu_particles_3d.cpp
index 03fe5e1fad..acbc443a93 100644
--- a/scene/3d/cpu_particles_3d.cpp
+++ b/scene/3d/cpu_particles_3d.cpp
@@ -448,6 +448,10 @@ void CPUParticles3D::set_emission_ring_inner_radius(real_t p_radius) {
emission_ring_inner_radius = p_radius;
}
+void CPUParticles3D::set_emission_ring_cone_angle(real_t p_angle) {
+ emission_ring_cone_angle = p_angle;
+}
+
void CPUParticles3D::set_scale_curve_x(Ref<Curve> p_scale_curve) {
scale_curve_x = p_scale_curve;
}
@@ -501,6 +505,10 @@ real_t CPUParticles3D::get_emission_ring_inner_radius() const {
return emission_ring_inner_radius;
}
+real_t CPUParticles3D::get_emission_ring_cone_angle() const {
+ return emission_ring_cone_angle;
+}
+
CPUParticles3D::EmissionShape CPUParticles3D::get_emission_shape() const {
return emission_shape;
}
@@ -878,8 +886,14 @@ void CPUParticles3D::_particles_process(double p_delta) {
}
} break;
case EMISSION_SHAPE_RING: {
+ real_t radius_clamped = MAX(0.001, emission_ring_radius);
+ real_t top_radius = MAX(radius_clamped - Math::tan(Math::deg_to_rad(90.0 - emission_ring_cone_angle)) * emission_ring_height, 0.0);
+ real_t y_pos = Math::randf();
+ real_t skew = MAX(MIN(radius_clamped, top_radius) / MAX(radius_clamped, top_radius), 0.5);
+ y_pos = radius_clamped < top_radius ? Math::pow(y_pos, skew) : 1.0 - Math::pow(y_pos, skew);
real_t ring_random_angle = Math::randf() * Math_TAU;
- real_t ring_random_radius = Math::sqrt(Math::randf() * (emission_ring_radius * emission_ring_radius - emission_ring_inner_radius * emission_ring_inner_radius) + emission_ring_inner_radius * emission_ring_inner_radius);
+ real_t ring_random_radius = Math::sqrt(Math::randf() * (radius_clamped * radius_clamped - emission_ring_inner_radius * emission_ring_inner_radius) + emission_ring_inner_radius * emission_ring_inner_radius);
+ ring_random_radius = Math::lerp(ring_random_radius, ring_random_radius * (top_radius / radius_clamped), y_pos);
Vector3 axis = emission_ring_axis == Vector3(0.0, 0.0, 0.0) ? Vector3(0.0, 0.0, 1.0) : emission_ring_axis.normalized();
Vector3 ortho_axis;
if (axis.abs() == Vector3(1.0, 0.0, 0.0)) {
@@ -890,7 +904,7 @@ void CPUParticles3D::_particles_process(double p_delta) {
ortho_axis = ortho_axis.normalized();
ortho_axis.rotate(axis, ring_random_angle);
ortho_axis = ortho_axis.normalized();
- p.transform.origin = ortho_axis * ring_random_radius + (Math::randf() * emission_ring_height - emission_ring_height / 2.0) * axis;
+ p.transform.origin = ortho_axis * ring_random_radius + (y_pos * emission_ring_height - emission_ring_height / 2.0) * axis;
} break;
case EMISSION_SHAPE_MAX: { // Max value for validity check.
break;
@@ -1550,6 +1564,9 @@ void CPUParticles3D::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_emission_ring_inner_radius", "inner_radius"), &CPUParticles3D::set_emission_ring_inner_radius);
ClassDB::bind_method(D_METHOD("get_emission_ring_inner_radius"), &CPUParticles3D::get_emission_ring_inner_radius);
+ ClassDB::bind_method(D_METHOD("set_emission_ring_cone_angle", "cone_angle"), &CPUParticles3D::set_emission_ring_cone_angle);
+ ClassDB::bind_method(D_METHOD("get_emission_ring_cone_angle"), &CPUParticles3D::get_emission_ring_cone_angle);
+
ClassDB::bind_method(D_METHOD("get_gravity"), &CPUParticles3D::get_gravity);
ClassDB::bind_method(D_METHOD("set_gravity", "accel_vec"), &CPUParticles3D::set_gravity);
@@ -1577,9 +1594,10 @@ void CPUParticles3D::_bind_methods() {
ADD_PROPERTY(PropertyInfo(Variant::PACKED_VECTOR3_ARRAY, "emission_normals"), "set_emission_normals", "get_emission_normals");
ADD_PROPERTY(PropertyInfo(Variant::PACKED_COLOR_ARRAY, "emission_colors"), "set_emission_colors", "get_emission_colors");
ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "emission_ring_axis"), "set_emission_ring_axis", "get_emission_ring_axis");
- ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "emission_ring_height"), "set_emission_ring_height", "get_emission_ring_height");
- ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "emission_ring_radius"), "set_emission_ring_radius", "get_emission_ring_radius");
- ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "emission_ring_inner_radius"), "set_emission_ring_inner_radius", "get_emission_ring_inner_radius");
+ ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "emission_ring_height", PROPERTY_HINT_RANGE, "0,1000,0.01,or_greater"), "set_emission_ring_height", "get_emission_ring_height");
+ ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "emission_ring_radius", PROPERTY_HINT_RANGE, "0,1000,0.01,or_greater"), "set_emission_ring_radius", "get_emission_ring_radius");
+ ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "emission_ring_inner_radius", PROPERTY_HINT_RANGE, "0,1000,0.01,or_greater"), "set_emission_ring_inner_radius", "get_emission_ring_inner_radius");
+ ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "emission_ring_cone_angle", PROPERTY_HINT_RANGE, "0,90,0.01,degrees"), "set_emission_ring_cone_angle", "get_emission_ring_cone_angle");
ADD_GROUP("Particle Flags", "particle_flag_");
ADD_PROPERTYI(PropertyInfo(Variant::BOOL, "particle_flag_align_y"), "set_particle_flag", "get_particle_flag", PARTICLE_FLAG_ALIGN_Y_TO_VELOCITY);
ADD_PROPERTYI(PropertyInfo(Variant::BOOL, "particle_flag_rotate_y"), "set_particle_flag", "get_particle_flag", PARTICLE_FLAG_ROTATE_Y);
@@ -1716,6 +1734,7 @@ CPUParticles3D::CPUParticles3D() {
set_emission_ring_height(1);
set_emission_ring_radius(1);
set_emission_ring_inner_radius(0);
+ set_emission_ring_cone_angle(90);
set_gravity(Vector3(0, -9.8, 0));
diff --git a/scene/3d/cpu_particles_3d.h b/scene/3d/cpu_particles_3d.h
index 82ea4bbef3..978bb64e71 100644
--- a/scene/3d/cpu_particles_3d.h
+++ b/scene/3d/cpu_particles_3d.h
@@ -178,6 +178,7 @@ private:
real_t emission_ring_height = 0.0;
real_t emission_ring_radius = 0.0;
real_t emission_ring_inner_radius = 0.0;
+ real_t emission_ring_cone_angle = 0.0;
Ref<Curve> scale_curve_x;
Ref<Curve> scale_curve_y;
@@ -282,6 +283,7 @@ public:
void set_emission_ring_height(real_t p_height);
void set_emission_ring_radius(real_t p_radius);
void set_emission_ring_inner_radius(real_t p_radius);
+ void set_emission_ring_cone_angle(real_t p_angle);
void set_scale_curve_x(Ref<Curve> p_scale_curve);
void set_scale_curve_y(Ref<Curve> p_scale_curve);
void set_scale_curve_z(Ref<Curve> p_scale_curve);
@@ -297,6 +299,7 @@ public:
real_t get_emission_ring_height() const;
real_t get_emission_ring_radius() const;
real_t get_emission_ring_inner_radius() const;
+ real_t get_emission_ring_cone_angle() const;
Ref<Curve> get_scale_curve_x() const;
Ref<Curve> get_scale_curve_y() const;
Ref<Curve> get_scale_curve_z() const;
diff --git a/scene/3d/decal.cpp b/scene/3d/decal.cpp
index 485599d0fb..8702b1d3da 100644
--- a/scene/3d/decal.cpp
+++ b/scene/3d/decal.cpp
@@ -163,7 +163,7 @@ void Decal::_validate_property(PropertyInfo &p_property) const {
}
PackedStringArray Decal::get_configuration_warnings() const {
- PackedStringArray warnings = Node::get_configuration_warnings();
+ PackedStringArray warnings = VisualInstance3D::get_configuration_warnings();
if (OS::get_singleton()->get_current_rendering_method() == "gl_compatibility") {
warnings.push_back(RTR("Decals are only available when using the Forward+ or Mobile rendering backends."));
diff --git a/scene/3d/fog_volume.cpp b/scene/3d/fog_volume.cpp
index 54631a8dff..195074ba2f 100644
--- a/scene/3d/fog_volume.cpp
+++ b/scene/3d/fog_volume.cpp
@@ -116,7 +116,7 @@ AABB FogVolume::get_aabb() const {
}
PackedStringArray FogVolume::get_configuration_warnings() const {
- PackedStringArray warnings = Node::get_configuration_warnings();
+ PackedStringArray warnings = VisualInstance3D::get_configuration_warnings();
Ref<Environment> environment = get_viewport()->find_world_3d()->get_environment();
diff --git a/scene/3d/gpu_particles_collision_3d.cpp b/scene/3d/gpu_particles_collision_3d.cpp
index 3a05ec9c9e..9791f23bc3 100644
--- a/scene/3d/gpu_particles_collision_3d.cpp
+++ b/scene/3d/gpu_particles_collision_3d.cpp
@@ -524,7 +524,7 @@ Ref<Image> GPUParticlesCollisionSDF3D::bake() {
}
PackedStringArray GPUParticlesCollisionSDF3D::get_configuration_warnings() const {
- PackedStringArray warnings = Node::get_configuration_warnings();
+ PackedStringArray warnings = GPUParticlesCollision3D::get_configuration_warnings();
if (bake_mask == 0) {
warnings.push_back(RTR("The Bake Mask has no bits enabled, which means baking will not produce any collision for this GPUParticlesCollisionSDF3D.\nTo resolve this, enable at least one bit in the Bake Mask property."));
diff --git a/scene/3d/lightmap_gi.cpp b/scene/3d/lightmap_gi.cpp
index 3f8b0dfb8e..26a574cd26 100644
--- a/scene/3d/lightmap_gi.cpp
+++ b/scene/3d/lightmap_gi.cpp
@@ -151,6 +151,14 @@ bool LightmapGIData::is_using_spherical_harmonics() const {
return uses_spherical_harmonics;
}
+void LightmapGIData::_set_uses_packed_directional(bool p_enable) {
+ _uses_packed_directional = p_enable;
+}
+
+bool LightmapGIData::_is_using_packed_directional() const {
+ return _uses_packed_directional;
+}
+
void LightmapGIData::set_capture_data(const AABB &p_bounds, bool p_interior, const PackedVector3Array &p_points, const PackedColorArray &p_point_sh, const PackedInt32Array &p_tetrahedra, const PackedInt32Array &p_bsp_tree, float p_baked_exposure) {
if (p_points.size()) {
int pc = p_points.size();
@@ -255,6 +263,9 @@ void LightmapGIData::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_uses_spherical_harmonics", "uses_spherical_harmonics"), &LightmapGIData::set_uses_spherical_harmonics);
ClassDB::bind_method(D_METHOD("is_using_spherical_harmonics"), &LightmapGIData::is_using_spherical_harmonics);
+ ClassDB::bind_method(D_METHOD("_set_uses_packed_directional", "_uses_packed_directional"), &LightmapGIData::_set_uses_packed_directional);
+ ClassDB::bind_method(D_METHOD("_is_using_packed_directional"), &LightmapGIData::_is_using_packed_directional);
+
ClassDB::bind_method(D_METHOD("add_user", "path", "uv_scale", "slice_index", "sub_instance"), &LightmapGIData::add_user);
ClassDB::bind_method(D_METHOD("get_user_count"), &LightmapGIData::get_user_count);
ClassDB::bind_method(D_METHOD("get_user_path", "user_idx"), &LightmapGIData::get_user_path);
@@ -267,6 +278,7 @@ void LightmapGIData::_bind_methods() {
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "uses_spherical_harmonics", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL), "set_uses_spherical_harmonics", "is_using_spherical_harmonics");
ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "user_data", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL), "_set_user_data", "_get_user_data");
ADD_PROPERTY(PropertyInfo(Variant::DICTIONARY, "probe_data", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL), "_set_probe_data", "_get_probe_data");
+ ADD_PROPERTY(PropertyInfo(Variant::BOOL, "_uses_packed_directional", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL), "_set_uses_packed_directional", "_is_using_packed_directional");
#ifndef DISABLE_DEPRECATED
ClassDB::bind_method(D_METHOD("set_light_texture", "light_texture"), &LightmapGIData::set_light_texture);
@@ -1187,6 +1199,7 @@ LightmapGI::BakeError LightmapGI::bake(Node *p_from_node, String p_image_data_pa
}
gi_data->set_lightmap_textures(textures);
+ gi_data->_set_uses_packed_directional(directional); // New SH lightmaps are packed automatically.
gi_data->set_uses_spherical_harmonics(directional);
for (int i = 0; i < lightmapper->get_bake_mesh_count(); i++) {
@@ -1352,6 +1365,12 @@ void LightmapGI::_notification(int p_what) {
switch (p_what) {
case NOTIFICATION_POST_ENTER_TREE: {
if (light_data.is_valid()) {
+ ERR_FAIL_COND_MSG(
+ light_data->is_using_spherical_harmonics() && !light_data->_is_using_packed_directional(),
+ vformat(
+ "%s (%s): The directional lightmap textures are stored in a format that isn't supported anymore. Please bake lightmaps again to make lightmaps display from this node again.",
+ get_light_data()->get_path(), get_name()));
+
_assign_lightmaps();
}
} break;
@@ -1581,7 +1600,7 @@ Ref<CameraAttributes> LightmapGI::get_camera_attributes() const {
}
PackedStringArray LightmapGI::get_configuration_warnings() const {
- PackedStringArray warnings = Node::get_configuration_warnings();
+ PackedStringArray warnings = VisualInstance3D::get_configuration_warnings();
if (OS::get_singleton()->get_current_rendering_method() == "gl_compatibility") {
warnings.push_back(RTR("Lightmap can only be baked from a device that supports the RD backends. Lightmap baking may fail."));
diff --git a/scene/3d/lightmap_gi.h b/scene/3d/lightmap_gi.h
index 67480132b6..6377c420d1 100644
--- a/scene/3d/lightmap_gi.h
+++ b/scene/3d/lightmap_gi.h
@@ -49,6 +49,8 @@ class LightmapGIData : public Resource {
bool uses_spherical_harmonics = false;
bool interior = false;
+ bool _uses_packed_directional = false;
+
RID lightmap;
AABB bounds;
float baked_exposure = 1.0;
@@ -92,6 +94,9 @@ public:
void set_uses_spherical_harmonics(bool p_enable);
bool is_using_spherical_harmonics() const;
+ void _set_uses_packed_directional(bool p_enable);
+ bool _is_using_packed_directional() const;
+
bool is_interior() const;
float get_baked_exposure() const;
diff --git a/scene/3d/mesh_instance_3d.cpp b/scene/3d/mesh_instance_3d.cpp
index 85bf8846b9..f551cb401c 100644
--- a/scene/3d/mesh_instance_3d.cpp
+++ b/scene/3d/mesh_instance_3d.cpp
@@ -517,12 +517,12 @@ bool MeshInstance3D::_property_get_revert(const StringName &p_name, Variant &r_p
Ref<ArrayMesh> MeshInstance3D::bake_mesh_from_current_blend_shape_mix(Ref<ArrayMesh> p_existing) {
Ref<ArrayMesh> source_mesh = get_mesh();
- ERR_FAIL_NULL_V_MSG(source_mesh, Ref<ArrayMesh>(), "The source mesh must be a valid ArrayMesh.");
+ ERR_FAIL_COND_V_MSG(source_mesh.is_null(), Ref<ArrayMesh>(), "The source mesh must be a valid ArrayMesh.");
Ref<ArrayMesh> bake_mesh;
if (p_existing.is_valid()) {
- ERR_FAIL_NULL_V_MSG(p_existing, Ref<ArrayMesh>(), "The existing mesh must be a valid ArrayMesh.");
+ ERR_FAIL_COND_V_MSG(p_existing.is_null(), Ref<ArrayMesh>(), "The existing mesh must be a valid ArrayMesh.");
ERR_FAIL_COND_V_MSG(source_mesh == p_existing, Ref<ArrayMesh>(), "The source mesh can not be the same mesh as the existing mesh.");
bake_mesh = p_existing;
@@ -671,6 +671,172 @@ Ref<ArrayMesh> MeshInstance3D::bake_mesh_from_current_blend_shape_mix(Ref<ArrayM
return bake_mesh;
}
+Ref<ArrayMesh> MeshInstance3D::bake_mesh_from_current_skeleton_pose(Ref<ArrayMesh> p_existing) {
+ Ref<ArrayMesh> source_mesh = get_mesh();
+ ERR_FAIL_COND_V_MSG(source_mesh.is_null(), Ref<ArrayMesh>(), "The source mesh must be a valid ArrayMesh.");
+
+ Ref<ArrayMesh> bake_mesh;
+
+ if (p_existing.is_valid()) {
+ ERR_FAIL_COND_V_MSG(source_mesh == p_existing, Ref<ArrayMesh>(), "The source mesh can not be the same mesh as the existing mesh.");
+
+ bake_mesh = p_existing;
+ } else {
+ bake_mesh.instantiate();
+ }
+
+ ERR_FAIL_COND_V_MSG(skin_ref.is_null(), Ref<ArrayMesh>(), "The source mesh must have a valid skin.");
+ ERR_FAIL_COND_V_MSG(skin_internal.is_null(), Ref<ArrayMesh>(), "The source mesh must have a valid skin.");
+ RID skeleton = skin_ref->get_skeleton();
+ ERR_FAIL_COND_V_MSG(!skeleton.is_valid(), Ref<ArrayMesh>(), "The source mesh must have its skin registered with a valid skeleton.");
+
+ const int bone_count = RenderingServer::get_singleton()->skeleton_get_bone_count(skeleton);
+ ERR_FAIL_COND_V(bone_count <= 0, Ref<ArrayMesh>());
+ ERR_FAIL_COND_V(bone_count < skin_internal->get_bind_count(), Ref<ArrayMesh>());
+
+ LocalVector<Transform3D> bone_transforms;
+ bone_transforms.resize(bone_count);
+ for (int bone_index = 0; bone_index < bone_count; bone_index++) {
+ bone_transforms[bone_index] = RenderingServer::get_singleton()->skeleton_bone_get_transform(skeleton, bone_index);
+ }
+
+ bake_mesh->clear_surfaces();
+
+ int mesh_surface_count = source_mesh->get_surface_count();
+
+ for (int surface_index = 0; surface_index < mesh_surface_count; surface_index++) {
+ ERR_CONTINUE(source_mesh->surface_get_primitive_type(surface_index) != Mesh::PRIMITIVE_TRIANGLES);
+
+ uint32_t surface_format = source_mesh->surface_get_format(surface_index);
+
+ ERR_CONTINUE(0 == (surface_format & Mesh::ARRAY_FORMAT_VERTEX));
+ ERR_CONTINUE(0 == (surface_format & Mesh::ARRAY_FORMAT_BONES));
+ ERR_CONTINUE(0 == (surface_format & Mesh::ARRAY_FORMAT_WEIGHTS));
+
+ unsigned int bones_per_vertex = surface_format & Mesh::ARRAY_FLAG_USE_8_BONE_WEIGHTS ? 8 : 4;
+
+ surface_format &= ~Mesh::ARRAY_FORMAT_BONES;
+ surface_format &= ~Mesh::ARRAY_FORMAT_WEIGHTS;
+
+ const Array &source_mesh_arrays = source_mesh->surface_get_arrays(surface_index);
+
+ ERR_FAIL_COND_V(source_mesh_arrays.size() != RS::ARRAY_MAX, Ref<ArrayMesh>());
+
+ const Vector<Vector3> &source_mesh_vertex_array = source_mesh_arrays[Mesh::ARRAY_VERTEX];
+ const Vector<Vector3> &source_mesh_normal_array = source_mesh_arrays[Mesh::ARRAY_NORMAL];
+ const Vector<float> &source_mesh_tangent_array = source_mesh_arrays[Mesh::ARRAY_TANGENT];
+ const Vector<int> &source_mesh_bones_array = source_mesh_arrays[Mesh::ARRAY_BONES];
+ const Vector<float> &source_mesh_weights_array = source_mesh_arrays[Mesh::ARRAY_WEIGHTS];
+
+ unsigned int vertex_count = source_mesh_vertex_array.size();
+ int expected_bone_array_size = vertex_count * bones_per_vertex;
+ ERR_CONTINUE(source_mesh_bones_array.size() != expected_bone_array_size);
+ ERR_CONTINUE(source_mesh_weights_array.size() != expected_bone_array_size);
+
+ Array new_mesh_arrays;
+ new_mesh_arrays.resize(Mesh::ARRAY_MAX);
+ for (int i = 0; i < source_mesh_arrays.size(); i++) {
+ if (i == Mesh::ARRAY_VERTEX || i == Mesh::ARRAY_NORMAL || i == Mesh::ARRAY_TANGENT || i == Mesh::ARRAY_BONES || i == Mesh::ARRAY_WEIGHTS) {
+ continue;
+ }
+ new_mesh_arrays[i] = source_mesh_arrays[i];
+ }
+
+ bool use_normal_array = source_mesh_normal_array.size() == source_mesh_vertex_array.size();
+ bool use_tangent_array = source_mesh_tangent_array.size() / 4 == source_mesh_vertex_array.size();
+
+ Vector<Vector3> lerped_vertex_array = source_mesh_vertex_array;
+ Vector<Vector3> lerped_normal_array = source_mesh_normal_array;
+ Vector<float> lerped_tangent_array = source_mesh_tangent_array;
+
+ const Vector3 *source_vertices_ptr = source_mesh_vertex_array.ptr();
+ const Vector3 *source_normals_ptr = source_mesh_normal_array.ptr();
+ const float *source_tangents_ptr = source_mesh_tangent_array.ptr();
+ const int *source_bones_ptr = source_mesh_bones_array.ptr();
+ const float *source_weights_ptr = source_mesh_weights_array.ptr();
+
+ Vector3 *lerped_vertices_ptrw = lerped_vertex_array.ptrw();
+ Vector3 *lerped_normals_ptrw = lerped_normal_array.ptrw();
+ float *lerped_tangents_ptrw = lerped_tangent_array.ptrw();
+
+ for (unsigned int vertex_index = 0; vertex_index < vertex_count; vertex_index++) {
+ Vector3 lerped_vertex;
+ Vector3 lerped_normal;
+ Vector3 lerped_tangent;
+
+ const Vector3 &source_vertex = source_vertices_ptr[vertex_index];
+
+ Vector3 source_normal;
+ if (use_normal_array) {
+ source_normal = source_normals_ptr[vertex_index];
+ }
+
+ int tangent_index = vertex_index * 4;
+ Vector4 source_tangent;
+ Vector3 source_tangent_vec3;
+ if (use_tangent_array) {
+ source_tangent = Vector4(
+ source_tangents_ptr[tangent_index],
+ source_tangents_ptr[tangent_index + 1],
+ source_tangents_ptr[tangent_index + 2],
+ source_tangents_ptr[tangent_index + 3]);
+
+ DEV_ASSERT(source_tangent.w == 1.0 || source_tangent.w == -1.0);
+
+ source_tangent_vec3 = Vector3(source_tangent.x, source_tangent.y, source_tangent.z);
+ }
+
+ for (unsigned int weight_index = 0; weight_index < bones_per_vertex; weight_index++) {
+ float bone_weight = source_weights_ptr[vertex_index * bones_per_vertex + weight_index];
+ if (bone_weight < FLT_EPSILON) {
+ continue;
+ }
+ int vertex_bone_index = source_bones_ptr[vertex_index * bones_per_vertex + weight_index];
+ const Transform3D &bone_transform = bone_transforms[vertex_bone_index];
+ const Basis bone_basis = bone_transform.basis.orthonormalized();
+
+ ERR_FAIL_INDEX_V(vertex_bone_index, static_cast<int>(bone_transforms.size()), Ref<ArrayMesh>());
+
+ lerped_vertex += source_vertex.lerp(bone_transform.xform(source_vertex), bone_weight) - source_vertex;
+ ;
+
+ if (use_normal_array) {
+ lerped_normal += source_normal.lerp(bone_basis.xform(source_normal), bone_weight) - source_normal;
+ }
+
+ if (use_tangent_array) {
+ lerped_tangent += source_tangent_vec3.lerp(bone_basis.xform(source_tangent_vec3), bone_weight) - source_tangent_vec3;
+ }
+ }
+
+ lerped_vertices_ptrw[vertex_index] += lerped_vertex;
+
+ if (use_normal_array) {
+ lerped_normals_ptrw[vertex_index] = (source_normal + lerped_normal).normalized();
+ }
+
+ if (use_tangent_array) {
+ lerped_tangent = (source_tangent_vec3 + lerped_tangent).normalized();
+ lerped_tangents_ptrw[tangent_index] = lerped_tangent.x;
+ lerped_tangents_ptrw[tangent_index + 1] = lerped_tangent.y;
+ lerped_tangents_ptrw[tangent_index + 2] = lerped_tangent.z;
+ }
+ }
+
+ new_mesh_arrays[Mesh::ARRAY_VERTEX] = lerped_vertex_array;
+ if (use_normal_array) {
+ new_mesh_arrays[Mesh::ARRAY_NORMAL] = lerped_normal_array;
+ }
+ if (use_tangent_array) {
+ new_mesh_arrays[Mesh::ARRAY_TANGENT] = lerped_tangent_array;
+ }
+
+ bake_mesh->add_surface_from_arrays(Mesh::PRIMITIVE_TRIANGLES, new_mesh_arrays, Array(), Dictionary(), surface_format);
+ }
+
+ return bake_mesh;
+}
+
void MeshInstance3D::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_mesh", "mesh"), &MeshInstance3D::set_mesh);
ClassDB::bind_method(D_METHOD("get_mesh"), &MeshInstance3D::get_mesh);
@@ -700,6 +866,7 @@ void MeshInstance3D::_bind_methods() {
ClassDB::bind_method(D_METHOD("create_debug_tangents"), &MeshInstance3D::create_debug_tangents);
ClassDB::bind_method(D_METHOD("bake_mesh_from_current_blend_shape_mix", "existing"), &MeshInstance3D::bake_mesh_from_current_blend_shape_mix, DEFVAL(Ref<ArrayMesh>()));
+ ClassDB::bind_method(D_METHOD("bake_mesh_from_current_skeleton_pose", "existing"), &MeshInstance3D::bake_mesh_from_current_skeleton_pose, DEFVAL(Ref<ArrayMesh>()));
ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "mesh", PROPERTY_HINT_RESOURCE_TYPE, "Mesh"), "set_mesh", "get_mesh");
ADD_GROUP("Skeleton", "");
diff --git a/scene/3d/mesh_instance_3d.h b/scene/3d/mesh_instance_3d.h
index 8a7e03c5b3..0eff12762d 100644
--- a/scene/3d/mesh_instance_3d.h
+++ b/scene/3d/mesh_instance_3d.h
@@ -102,6 +102,7 @@ public:
virtual AABB get_aabb() const override;
Ref<ArrayMesh> bake_mesh_from_current_blend_shape_mix(Ref<ArrayMesh> p_existing = Ref<ArrayMesh>());
+ Ref<ArrayMesh> bake_mesh_from_current_skeleton_pose(Ref<ArrayMesh> p_existing = Ref<ArrayMesh>());
MeshInstance3D();
~MeshInstance3D();
diff --git a/scene/3d/navigation_link_3d.cpp b/scene/3d/navigation_link_3d.cpp
index bebba9a6c0..0cce21b9d0 100644
--- a/scene/3d/navigation_link_3d.cpp
+++ b/scene/3d/navigation_link_3d.cpp
@@ -453,7 +453,7 @@ void NavigationLink3D::set_travel_cost(real_t p_travel_cost) {
}
PackedStringArray NavigationLink3D::get_configuration_warnings() const {
- PackedStringArray warnings = Node::get_configuration_warnings();
+ PackedStringArray warnings = Node3D::get_configuration_warnings();
if (start_position.is_equal_approx(end_position)) {
warnings.push_back(RTR("NavigationLink3D start position should be different than the end position to be useful."));
diff --git a/scene/3d/navigation_region_3d.cpp b/scene/3d/navigation_region_3d.cpp
index d7397a932d..c0c254e7ed 100644
--- a/scene/3d/navigation_region_3d.cpp
+++ b/scene/3d/navigation_region_3d.cpp
@@ -271,7 +271,7 @@ bool NavigationRegion3D::is_baking() const {
}
PackedStringArray NavigationRegion3D::get_configuration_warnings() const {
- PackedStringArray warnings = Node::get_configuration_warnings();
+ PackedStringArray warnings = Node3D::get_configuration_warnings();
if (is_visible_in_tree() && is_inside_tree()) {
if (!navigation_mesh.is_valid()) {
diff --git a/scene/3d/occluder_instance_3d.cpp b/scene/3d/occluder_instance_3d.cpp
index 6982df12f6..6d88323c76 100644
--- a/scene/3d/occluder_instance_3d.cpp
+++ b/scene/3d/occluder_instance_3d.cpp
@@ -691,7 +691,7 @@ OccluderInstance3D::BakeError OccluderInstance3D::bake_scene(Node *p_from_node,
}
PackedStringArray OccluderInstance3D::get_configuration_warnings() const {
- PackedStringArray warnings = Node::get_configuration_warnings();
+ PackedStringArray warnings = VisualInstance3D::get_configuration_warnings();
if (!bool(GLOBAL_GET("rendering/occlusion_culling/use_occlusion_culling"))) {
warnings.push_back(RTR("Occlusion culling is disabled in the Project Settings, which means occlusion culling won't be performed in the root viewport.\nTo resolve this, open the Project Settings and enable Rendering > Occlusion Culling > Use Occlusion Culling."));
diff --git a/scene/3d/path_3d.cpp b/scene/3d/path_3d.cpp
index 1f8f7cd54c..20d646fe1e 100644
--- a/scene/3d/path_3d.cpp
+++ b/scene/3d/path_3d.cpp
@@ -318,7 +318,7 @@ void PathFollow3D::_validate_property(PropertyInfo &p_property) const {
}
PackedStringArray PathFollow3D::get_configuration_warnings() const {
- PackedStringArray warnings = Node::get_configuration_warnings();
+ PackedStringArray warnings = Node3D::get_configuration_warnings();
if (is_visible_in_tree() && is_inside_tree()) {
if (!Object::cast_to<Path3D>(get_parent())) {
@@ -461,9 +461,10 @@ real_t PathFollow3D::get_progress() const {
}
void PathFollow3D::set_progress_ratio(real_t p_ratio) {
- if (path && path->get_curve().is_valid() && path->get_curve()->get_baked_length()) {
- set_progress(p_ratio * path->get_curve()->get_baked_length());
- }
+ ERR_FAIL_NULL_MSG(path, "Can only set progress ratio on a PathFollow3D that is the child of a Path3D which is itself part of the scene tree.");
+ ERR_FAIL_COND_MSG(path->get_curve().is_null(), "Can't set progress ratio on a PathFollow3D that does not have a Curve.");
+ ERR_FAIL_COND_MSG(!path->get_curve()->get_baked_length(), "Can't set progress ratio on a PathFollow3D that has a 0 length curve.");
+ set_progress(p_ratio * path->get_curve()->get_baked_length());
}
real_t PathFollow3D::get_progress_ratio() const {
diff --git a/scene/3d/physical_bone_simulator_3d.cpp b/scene/3d/physical_bone_simulator_3d.cpp
index ffe79e0892..510d887c83 100644
--- a/scene/3d/physical_bone_simulator_3d.cpp
+++ b/scene/3d/physical_bone_simulator_3d.cpp
@@ -285,11 +285,11 @@ void _pb_start_simulation(const PhysicalBoneSimulator3D *p_simulator, Node *p_no
}
void PhysicalBoneSimulator3D::physical_bones_start_simulation_on(const TypedArray<StringName> &p_bones) {
+ _pose_updated();
+
simulating = true;
_reset_physical_bones_state();
- _pose_updated();
-
Vector<int> sim_bones;
if (p_bones.size() > 0) {
sim_bones.resize(p_bones.size());
@@ -357,47 +357,16 @@ void PhysicalBoneSimulator3D::_process_modification() {
if (!skeleton) {
return;
}
- if (!enabled) {
- for (int i = 0; i < bones.size(); i++) {
- if (bones[i].physical_bone) {
- if (bones[i].physical_bone->is_simulating_physics() == false) {
- bones[i].physical_bone->reset_to_rest_position();
- }
- }
+ ERR_FAIL_COND(skeleton->get_bone_count() != bones.size());
+ for (int i = 0; i < skeleton->get_bone_count(); i++) {
+ if (!bones[i].physical_bone) {
+ continue;
}
- } else {
- ERR_FAIL_COND(skeleton->get_bone_count() != bones.size());
- for (int i = 0; i < skeleton->get_bone_count(); i++) {
- if (!bones[i].physical_bone) {
- continue;
- }
+ if (bones[i].physical_bone->is_simulating_physics() == false) {
+ bones[i].physical_bone->reset_to_rest_position();
+ } else if (simulating) {
skeleton->set_bone_global_pose(i, bones[i].global_pose);
}
-
- // TODO:
- // The above method is performance heavy and needs to be improved.
- // Ideally, the processing of set_bone_global_pose within Skeleton3D should be improved,
- // but the workaround available now is to convert the global pose to a local pose on the SkeletonModifier side.
- // However, the follow method needs recursive processing for deformations within PhysicalBoneSimulator3D to account for update order.
- /*
- ERR_FAIL_COND(skeleton->get_bone_count() != bones.size());
- LocalVector<Transform3D> local_poses;
- for (int i = 0; i < skeleton->get_bone_count(); i++) {
- Transform3D pt;
- if (skeleton->get_bone_parent(i) >= 0) {
- pt = get_bone_global_pose(skeleton->get_bone_parent(i));
- }
- local_poses.push_back(pt.affine_inverse() * bones[i].global_pose);
- }
- for (int i = 0; i < skeleton->get_bone_count(); i++) {
- if (!bones[i].physical_bone) {
- continue;
- }
- skeleton->set_bone_pose_position(i, local_poses[i].origin);
- skeleton->set_bone_pose_rotation(i, local_poses[i].basis.get_rotation_quaternion());
- skeleton->set_bone_pose_scale(i, local_poses[i].basis.get_scale());
- }
- */
}
}
diff --git a/scene/3d/physical_bone_simulator_3d.h b/scene/3d/physical_bone_simulator_3d.h
index ee900e0e77..ddc9cd569a 100644
--- a/scene/3d/physical_bone_simulator_3d.h
+++ b/scene/3d/physical_bone_simulator_3d.h
@@ -41,7 +41,6 @@ class PhysicalBoneSimulator3D : public SkeletonModifier3D {
GDCLASS(PhysicalBoneSimulator3D, SkeletonModifier3D);
bool simulating = false;
- bool enabled = true;
struct SimulatedBone {
int parent;
diff --git a/scene/3d/physics/collision_object_3d.cpp b/scene/3d/physics/collision_object_3d.cpp
index f11aa7012a..f0a5013ca2 100644
--- a/scene/3d/physics/collision_object_3d.cpp
+++ b/scene/3d/physics/collision_object_3d.cpp
@@ -731,7 +731,7 @@ bool CollisionObject3D::get_capture_input_on_drag() const {
}
PackedStringArray CollisionObject3D::get_configuration_warnings() const {
- PackedStringArray warnings = Node::get_configuration_warnings();
+ PackedStringArray warnings = Node3D::get_configuration_warnings();
if (shapes.is_empty()) {
warnings.push_back(RTR("This node has no shape, so it can't collide or interact with other objects.\nConsider adding a CollisionShape3D or CollisionPolygon3D as a child to define its shape."));
diff --git a/scene/3d/physics/collision_polygon_3d.cpp b/scene/3d/physics/collision_polygon_3d.cpp
index 76cd4db779..bf8dec7b54 100644
--- a/scene/3d/physics/collision_polygon_3d.cpp
+++ b/scene/3d/physics/collision_polygon_3d.cpp
@@ -169,7 +169,7 @@ void CollisionPolygon3D::set_margin(real_t p_margin) {
}
PackedStringArray CollisionPolygon3D::get_configuration_warnings() const {
- PackedStringArray warnings = Node::get_configuration_warnings();
+ PackedStringArray warnings = Node3D::get_configuration_warnings();
if (!Object::cast_to<CollisionObject3D>(get_parent())) {
warnings.push_back(RTR("CollisionPolygon3D only serves to provide a collision shape to a CollisionObject3D derived node.\nPlease only use it as a child of Area3D, StaticBody3D, RigidBody3D, CharacterBody3D, etc. to give them a shape."));
diff --git a/scene/3d/physics/collision_shape_3d.cpp b/scene/3d/physics/collision_shape_3d.cpp
index f3492a3cf3..304fa74b06 100644
--- a/scene/3d/physics/collision_shape_3d.cpp
+++ b/scene/3d/physics/collision_shape_3d.cpp
@@ -120,7 +120,7 @@ void CollisionShape3D::resource_changed(Ref<Resource> res) {
#endif
PackedStringArray CollisionShape3D::get_configuration_warnings() const {
- PackedStringArray warnings = Node::get_configuration_warnings();
+ PackedStringArray warnings = Node3D::get_configuration_warnings();
CollisionObject3D *col_object = Object::cast_to<CollisionObject3D>(get_parent());
if (col_object == nullptr) {
diff --git a/scene/3d/physics/vehicle_body_3d.cpp b/scene/3d/physics/vehicle_body_3d.cpp
index c23032d3b9..5073705145 100644
--- a/scene/3d/physics/vehicle_body_3d.cpp
+++ b/scene/3d/physics/vehicle_body_3d.cpp
@@ -106,7 +106,7 @@ void VehicleWheel3D::_notification(int p_what) {
}
PackedStringArray VehicleWheel3D::get_configuration_warnings() const {
- PackedStringArray warnings = Node::get_configuration_warnings();
+ PackedStringArray warnings = Node3D::get_configuration_warnings();
if (!Object::cast_to<VehicleBody3D>(get_parent())) {
warnings.push_back(RTR("VehicleWheel3D serves to provide a wheel system to a VehicleBody3D. Please use it as a child of a VehicleBody3D."));
@@ -219,6 +219,14 @@ bool VehicleWheel3D::is_in_contact() const {
return m_raycastInfo.m_isInContact;
}
+Vector3 VehicleWheel3D::get_contact_point() const {
+ return m_raycastInfo.m_contactPointWS;
+}
+
+Vector3 VehicleWheel3D::get_contact_normal() const {
+ return m_raycastInfo.m_contactNormalWS;
+}
+
Node3D *VehicleWheel3D::get_contact_body() const {
return m_raycastInfo.m_groundObject;
}
@@ -256,6 +264,8 @@ void VehicleWheel3D::_bind_methods() {
ClassDB::bind_method(D_METHOD("is_in_contact"), &VehicleWheel3D::is_in_contact);
ClassDB::bind_method(D_METHOD("get_contact_body"), &VehicleWheel3D::get_contact_body);
+ ClassDB::bind_method(D_METHOD("get_contact_point"), &VehicleWheel3D::get_contact_point);
+ ClassDB::bind_method(D_METHOD("get_contact_normal"), &VehicleWheel3D::get_contact_normal);
ClassDB::bind_method(D_METHOD("set_roll_influence", "roll_influence"), &VehicleWheel3D::set_roll_influence);
ClassDB::bind_method(D_METHOD("get_roll_influence"), &VehicleWheel3D::get_roll_influence);
diff --git a/scene/3d/physics/vehicle_body_3d.h b/scene/3d/physics/vehicle_body_3d.h
index def9984440..24f120ed26 100644
--- a/scene/3d/physics/vehicle_body_3d.h
+++ b/scene/3d/physics/vehicle_body_3d.h
@@ -130,6 +130,10 @@ public:
bool is_in_contact() const;
+ Vector3 get_contact_point() const;
+
+ Vector3 get_contact_normal() const;
+
Node3D *get_contact_body() const;
void set_roll_influence(real_t p_value);
diff --git a/scene/3d/remote_transform_3d.cpp b/scene/3d/remote_transform_3d.cpp
index 8d6e717132..f970879aa4 100644
--- a/scene/3d/remote_transform_3d.cpp
+++ b/scene/3d/remote_transform_3d.cpp
@@ -113,6 +113,16 @@ void RemoteTransform3D::_notification(int p_what) {
_update_cache();
} break;
+ case NOTIFICATION_RESET_PHYSICS_INTERPOLATION: {
+ if (cache.is_valid()) {
+ _update_remote();
+ Node3D *n = Object::cast_to<Node3D>(ObjectDB::get_instance(cache));
+ if (n) {
+ n->reset_physics_interpolation();
+ }
+ }
+ } break;
+
case NOTIFICATION_LOCAL_TRANSFORM_CHANGED:
case NOTIFICATION_TRANSFORM_CHANGED: {
if (!is_inside_tree()) {
@@ -201,7 +211,7 @@ void RemoteTransform3D::force_update_cache() {
}
PackedStringArray RemoteTransform3D::get_configuration_warnings() const {
- PackedStringArray warnings = Node::get_configuration_warnings();
+ PackedStringArray warnings = Node3D::get_configuration_warnings();
if (!has_node(remote_node) || !Object::cast_to<Node3D>(get_node(remote_node))) {
warnings.push_back(RTR("The \"Remote Path\" property must point to a valid Node3D or Node3D-derived node to work."));
diff --git a/scene/3d/soft_body_3d.cpp b/scene/3d/soft_body_3d.cpp
index 4fe5dd2385..7f67bde0cf 100644
--- a/scene/3d/soft_body_3d.cpp
+++ b/scene/3d/soft_body_3d.cpp
@@ -383,7 +383,7 @@ void SoftBody3D::_bind_methods() {
}
PackedStringArray SoftBody3D::get_configuration_warnings() const {
- PackedStringArray warnings = Node::get_configuration_warnings();
+ PackedStringArray warnings = MeshInstance3D::get_configuration_warnings();
if (mesh.is_null()) {
warnings.push_back(RTR("This body will be ignored until you set a mesh."));
diff --git a/scene/3d/sprite_3d.cpp b/scene/3d/sprite_3d.cpp
index 50218a6d86..42460eec4c 100644
--- a/scene/3d/sprite_3d.cpp
+++ b/scene/3d/sprite_3d.cpp
@@ -132,7 +132,7 @@ void SpriteBase3D::draw_texture_rect(Ref<Texture2D> p_texture, Rect2 p_dst_rect,
// Properly setup UVs for impostor textures (AtlasTexture).
Ref<AtlasTexture> atlas_tex = p_texture;
- if (atlas_tex != nullptr) {
+ if (atlas_tex.is_valid()) {
src_tsize[0] = atlas_tex->get_atlas()->get_width();
src_tsize[1] = atlas_tex->get_atlas()->get_height();
}
@@ -1324,7 +1324,7 @@ void AnimatedSprite3D::play(const StringName &p_name, float p_custom_scale, bool
name = animation;
}
- ERR_FAIL_NULL_MSG(frames, vformat("There is no animation with name '%s'.", name));
+ ERR_FAIL_COND_MSG(frames.is_null(), vformat("There is no animation with name '%s'.", name));
ERR_FAIL_COND_MSG(!frames->get_animation_names().has(name), vformat("There is no animation with name '%s'.", name));
if (frames->get_frame_count(name) == 0) {
@@ -1402,7 +1402,7 @@ void AnimatedSprite3D::set_animation(const StringName &p_name) {
emit_signal(SceneStringName(animation_changed));
- if (frames == nullptr) {
+ if (frames.is_null()) {
animation = StringName();
stop();
ERR_FAIL_MSG(vformat("There is no animation with name '%s'.", p_name));
diff --git a/scene/3d/visual_instance_3d.cpp b/scene/3d/visual_instance_3d.cpp
index 79a01450dd..a59754c8cc 100644
--- a/scene/3d/visual_instance_3d.cpp
+++ b/scene/3d/visual_instance_3d.cpp
@@ -497,7 +497,7 @@ bool GeometryInstance3D::is_ignoring_occlusion_culling() {
}
PackedStringArray GeometryInstance3D::get_configuration_warnings() const {
- PackedStringArray warnings = Node::get_configuration_warnings();
+ PackedStringArray warnings = VisualInstance3D::get_configuration_warnings();
if (!Math::is_zero_approx(visibility_range_end) && visibility_range_end <= visibility_range_begin) {
warnings.push_back(RTR("The GeometryInstance3D visibility range's End distance is set to a non-zero value, but is lower than the Begin distance.\nThis means the GeometryInstance3D will never be visible.\nTo resolve this, set the End distance to 0 or to a value greater than the Begin distance."));
diff --git a/scene/3d/voxel_gi.cpp b/scene/3d/voxel_gi.cpp
index ffca856fba..80ff176a98 100644
--- a/scene/3d/voxel_gi.cpp
+++ b/scene/3d/voxel_gi.cpp
@@ -518,7 +518,7 @@ AABB VoxelGI::get_aabb() const {
}
PackedStringArray VoxelGI::get_configuration_warnings() const {
- PackedStringArray warnings = Node::get_configuration_warnings();
+ PackedStringArray warnings = VisualInstance3D::get_configuration_warnings();
if (OS::get_singleton()->get_current_rendering_method() == "gl_compatibility") {
warnings.push_back(RTR("VoxelGI nodes are not supported when using the GL Compatibility backend yet. Support will be added in a future release."));
diff --git a/scene/3d/voxelizer.h b/scene/3d/voxelizer.h
index 6ea1cfdbb0..08d018eee9 100644
--- a/scene/3d/voxelizer.h
+++ b/scene/3d/voxelizer.h
@@ -35,9 +35,8 @@
class Voxelizer {
private:
- enum {
+ enum : uint32_t {
CHILD_EMPTY = 0xFFFFFFFF
-
};
struct Cell {
diff --git a/scene/3d/xr_hand_modifier_3d.cpp b/scene/3d/xr_hand_modifier_3d.cpp
index aa63fb623f..3b533da701 100644
--- a/scene/3d/xr_hand_modifier_3d.cpp
+++ b/scene/3d/xr_hand_modifier_3d.cpp
@@ -30,6 +30,7 @@
#include "xr_hand_modifier_3d.h"
+#include "core/config/project_settings.h"
#include "servers/xr/xr_pose.h"
#include "servers/xr_server.h"
@@ -283,6 +284,17 @@ void XRHandModifier3D::_skeleton_changed(Skeleton3D *p_old, Skeleton3D *p_new) {
_get_joint_data();
}
+PackedStringArray XRHandModifier3D::get_configuration_warnings() const {
+ PackedStringArray warnings = SkeletonModifier3D::get_configuration_warnings();
+
+ // Detect OpenXR without the Hand Tracking extension.
+ if (GLOBAL_GET("xr/openxr/enabled") && !GLOBAL_GET("xr/openxr/extensions/hand_tracking")) {
+ warnings.push_back("XRHandModifier3D requires the OpenXR Hand Tracking extension to be enabled.");
+ }
+
+ return warnings;
+}
+
void XRHandModifier3D::_notification(int p_what) {
switch (p_what) {
case NOTIFICATION_ENTER_TREE: {
diff --git a/scene/3d/xr_hand_modifier_3d.h b/scene/3d/xr_hand_modifier_3d.h
index 3d78f32b64..d58ccd0adf 100644
--- a/scene/3d/xr_hand_modifier_3d.h
+++ b/scene/3d/xr_hand_modifier_3d.h
@@ -55,6 +55,8 @@ public:
void set_bone_update(BoneUpdate p_bone_update);
BoneUpdate get_bone_update() const;
+ PackedStringArray get_configuration_warnings() const override;
+
void _notification(int p_what);
protected:
diff --git a/scene/3d/xr_nodes.cpp b/scene/3d/xr_nodes.cpp
index b71f9bc0c4..214c1f77ca 100644
--- a/scene/3d/xr_nodes.cpp
+++ b/scene/3d/xr_nodes.cpp
@@ -77,7 +77,7 @@ void XRCamera3D::_pose_changed(const Ref<XRPose> &p_pose) {
}
PackedStringArray XRCamera3D::get_configuration_warnings() const {
- PackedStringArray warnings = Node::get_configuration_warnings();
+ PackedStringArray warnings = Camera3D::get_configuration_warnings();
if (is_visible() && is_inside_tree()) {
// Warn if the node has a parent which isn't an XROrigin3D!
@@ -461,7 +461,7 @@ XRNode3D::~XRNode3D() {
}
PackedStringArray XRNode3D::get_configuration_warnings() const {
- PackedStringArray warnings = Node::get_configuration_warnings();
+ PackedStringArray warnings = Node3D::get_configuration_warnings();
if (is_visible() && is_inside_tree()) {
// Warn if the node has a parent which isn't an XROrigin3D!
@@ -644,7 +644,7 @@ Plane XRAnchor3D::get_plane() const {
Vector<XROrigin3D *> XROrigin3D::origin_nodes;
PackedStringArray XROrigin3D::get_configuration_warnings() const {
- PackedStringArray warnings = Node::get_configuration_warnings();
+ PackedStringArray warnings = Node3D::get_configuration_warnings();
if (is_visible() && is_inside_tree()) {
bool has_camera = false;
diff --git a/scene/animation/animation_mixer.cpp b/scene/animation/animation_mixer.cpp
index 20dd12f8c3..1c5f40f56e 100644
--- a/scene/animation/animation_mixer.cpp
+++ b/scene/animation/animation_mixer.cpp
@@ -81,9 +81,8 @@ bool AnimationMixer::_set(const StringName &p_name, const Variant &p_value) {
List<Variant> keys;
d.get_key_list(&keys);
for (const Variant &K : keys) {
- StringName lib_name = K;
- Ref<AnimationLibrary> lib = d[lib_name];
- add_animation_library(lib_name, lib);
+ Ref<AnimationLibrary> lib = d[K];
+ add_animation_library(K, lib);
}
emit_signal(SNAME("animation_libraries_updated"));
@@ -947,14 +946,6 @@ void AnimationMixer::_process_animation(double p_delta, bool p_update_only) {
clear_animation_instances();
}
-Variant AnimationMixer::post_process_key_value(const Ref<Animation> &p_anim, int p_track, Variant p_value, ObjectID p_object_id, int p_object_sub_idx) {
- Variant res;
- if (GDVIRTUAL_CALL(_post_process_key_value, p_anim, p_track, p_value, p_object_id, p_object_sub_idx, res)) {
- return res;
- }
- return _post_process_key_value(p_anim, p_track, p_value, p_object_id, p_object_sub_idx);
-}
-
Variant AnimationMixer::_post_process_key_value(const Ref<Animation> &p_anim, int p_track, Variant p_value, ObjectID p_object_id, int p_object_sub_idx) {
#ifndef _3D_DISABLED
switch (p_anim->track_get_type(p_track)) {
@@ -974,6 +965,17 @@ Variant AnimationMixer::_post_process_key_value(const Ref<Animation> &p_anim, in
return p_value;
}
+Variant AnimationMixer::post_process_key_value(const Ref<Animation> &p_anim, int p_track, Variant p_value, ObjectID p_object_id, int p_object_sub_idx) {
+ if (is_GDVIRTUAL_CALL_post_process_key_value) {
+ Variant res;
+ if (GDVIRTUAL_CALL(_post_process_key_value, p_anim, p_track, p_value, p_object_id, p_object_sub_idx, res)) {
+ return res;
+ }
+ is_GDVIRTUAL_CALL_post_process_key_value = false;
+ }
+ return _post_process_key_value(p_anim, p_track, p_value, p_object_id, p_object_sub_idx);
+}
+
void AnimationMixer::_blend_init() {
// Check all tracks, see if they need modification.
root_motion_position = Vector3(0, 0, 0);
@@ -1080,22 +1082,26 @@ void AnimationMixer::_blend_calc_total_weight() {
for (const AnimationInstance &ai : animation_instances) {
Ref<Animation> a = ai.animation_data.animation;
real_t weight = ai.playback_info.weight;
- Vector<real_t> track_weights = ai.playback_info.track_weights;
- Vector<Animation::TypeHash> processed_hashes;
- for (int i = 0; i < a->get_track_count(); i++) {
- if (!a->track_is_enabled(i)) {
+ const real_t *track_weights_ptr = ai.playback_info.track_weights.ptr();
+ int track_weights_count = ai.playback_info.track_weights.size();
+ static LocalVector<Animation::TypeHash> processed_hashes;
+ processed_hashes.clear();
+ const Vector<Animation::Track *> tracks = a->get_tracks();
+ for (const Animation::Track *animation_track : tracks) {
+ if (!animation_track->enabled) {
continue;
}
- Animation::TypeHash thash = a->track_get_type_hash(i);
- if (!track_cache.has(thash) || processed_hashes.has(thash)) {
+ Animation::TypeHash thash = animation_track->thash;
+ TrackCache **track_ptr = track_cache.getptr(thash);
+ if (track_ptr == nullptr || processed_hashes.has(thash)) {
// No path, but avoid error spamming.
// Or, there is the case different track type with same path; These can be distinguished by hash. So don't add the weight doubly.
continue;
}
- TrackCache *track = track_cache[thash];
+ TrackCache *track = *track_ptr;
int blend_idx = track_map[track->path];
ERR_CONTINUE(blend_idx < 0 || blend_idx >= track_count);
- real_t blend = blend_idx < track_weights.size() ? track_weights[blend_idx] * weight : weight;
+ real_t blend = blend_idx < track_weights_count ? track_weights_ptr[blend_idx] * weight : weight;
track->total_weight += blend;
processed_hashes.push_back(thash);
}
@@ -1115,26 +1121,33 @@ void AnimationMixer::_blend_process(double p_delta, bool p_update_only) {
Animation::LoopedFlag looped_flag = ai.playback_info.looped_flag;
bool is_external_seeking = ai.playback_info.is_external_seeking;
real_t weight = ai.playback_info.weight;
- Vector<real_t> track_weights = ai.playback_info.track_weights;
+ const real_t *track_weights_ptr = ai.playback_info.track_weights.ptr();
+ int track_weights_count = ai.playback_info.track_weights.size();
bool backward = signbit(delta); // This flag is used by the root motion calculates or detecting the end of audio stream.
bool seeked_backward = signbit(p_delta);
#ifndef _3D_DISABLED
bool calc_root = !seeked || is_external_seeking;
#endif // _3D_DISABLED
-
- for (int i = 0; i < a->get_track_count(); i++) {
- if (!a->track_is_enabled(i)) {
+ const Vector<Animation::Track *> tracks = a->get_tracks();
+ Animation::Track *const *tracks_ptr = tracks.ptr();
+ real_t a_length = a->get_length();
+ int count = tracks.size();
+ for (int i = 0; i < count; i++) {
+ const Animation::Track *animation_track = tracks_ptr[i];
+ if (!animation_track->enabled) {
continue;
}
- Animation::TypeHash thash = a->track_get_type_hash(i);
- if (!track_cache.has(thash)) {
+ Animation::TypeHash thash = animation_track->thash;
+ TrackCache **track_ptr = track_cache.getptr(thash);
+ if (track_ptr == nullptr) {
continue; // No path, but avoid error spamming.
}
- TrackCache *track = track_cache[thash];
- ERR_CONTINUE(!track_map.has(track->path));
- int blend_idx = track_map[track->path];
+ TrackCache *track = *track_ptr;
+ int *blend_idx_ptr = track_map.getptr(track->path);
+ ERR_CONTINUE(blend_idx_ptr == nullptr);
+ int blend_idx = *blend_idx_ptr;
ERR_CONTINUE(blend_idx < 0 || blend_idx >= track_count);
- real_t blend = blend_idx < track_weights.size() ? track_weights[blend_idx] * weight : weight;
+ real_t blend = blend_idx < track_weights_count ? track_weights_ptr[blend_idx] * weight : weight;
if (!deterministic) {
// If non-deterministic, do normalization.
// It would be better to make this if statement outside the for loop, but come here since too much code...
@@ -1143,8 +1156,8 @@ void AnimationMixer::_blend_process(double p_delta, bool p_update_only) {
}
blend = blend / track->total_weight;
}
- Animation::TrackType ttype = a->track_get_type(i);
- track->root_motion = root_motion_track == a->track_get_path(i);
+ Animation::TrackType ttype = animation_track->type;
+ track->root_motion = root_motion_track == animation_track->path;
switch (ttype) {
case Animation::TYPE_POSITION_3D: {
#ifndef _3D_DISABLED
@@ -1161,26 +1174,26 @@ void AnimationMixer::_blend_process(double p_delta, bool p_update_only) {
prev_time = 0;
} break;
case Animation::LOOP_LINEAR: {
- prev_time = Math::fposmod(prev_time, (double)a->get_length());
+ prev_time = Math::fposmod(prev_time, (double)a_length);
} break;
case Animation::LOOP_PINGPONG: {
- prev_time = Math::pingpong(prev_time, (double)a->get_length());
+ prev_time = Math::pingpong(prev_time, (double)a_length);
} break;
default:
break;
}
}
} else {
- if (Animation::is_greater_approx(prev_time, (double)a->get_length())) {
+ if (Animation::is_greater_approx(prev_time, (double)a_length)) {
switch (a->get_loop_mode()) {
case Animation::LOOP_NONE: {
- prev_time = (double)a->get_length();
+ prev_time = (double)a_length;
} break;
case Animation::LOOP_LINEAR: {
- prev_time = Math::fposmod(prev_time, (double)a->get_length());
+ prev_time = Math::fposmod(prev_time, (double)a_length);
} break;
case Animation::LOOP_PINGPONG: {
- prev_time = Math::pingpong(prev_time, (double)a->get_length());
+ prev_time = Math::pingpong(prev_time, (double)a_length);
} break;
default:
break;
@@ -1195,7 +1208,7 @@ void AnimationMixer::_blend_process(double p_delta, bool p_update_only) {
continue;
}
loc[0] = post_process_key_value(a, i, loc[0], t->object_id, t->bone_idx);
- a->try_position_track_interpolate(i, (double)a->get_length(), &loc[1]);
+ a->try_position_track_interpolate(i, (double)a_length, &loc[1]);
loc[1] = post_process_key_value(a, i, loc[1], t->object_id, t->bone_idx);
root_motion_cache.loc += (loc[1] - loc[0]) * blend;
prev_time = 0;
@@ -1210,7 +1223,7 @@ void AnimationMixer::_blend_process(double p_delta, bool p_update_only) {
a->try_position_track_interpolate(i, 0, &loc[1]);
loc[1] = post_process_key_value(a, i, loc[1], t->object_id, t->bone_idx);
root_motion_cache.loc += (loc[1] - loc[0]) * blend;
- prev_time = (double)a->get_length();
+ prev_time = (double)a_length;
}
}
Error err = a->try_position_track_interpolate(i, prev_time, &loc[0]);
@@ -1221,7 +1234,7 @@ void AnimationMixer::_blend_process(double p_delta, bool p_update_only) {
a->try_position_track_interpolate(i, time, &loc[1]);
loc[1] = post_process_key_value(a, i, loc[1], t->object_id, t->bone_idx);
root_motion_cache.loc += (loc[1] - loc[0]) * blend;
- prev_time = !backward ? 0 : (double)a->get_length();
+ prev_time = !backward ? 0 : (double)a_length;
}
{
Vector3 loc;
@@ -1249,26 +1262,26 @@ void AnimationMixer::_blend_process(double p_delta, bool p_update_only) {
prev_time = 0;
} break;
case Animation::LOOP_LINEAR: {
- prev_time = Math::fposmod(prev_time, (double)a->get_length());
+ prev_time = Math::fposmod(prev_time, (double)a_length);
} break;
case Animation::LOOP_PINGPONG: {
- prev_time = Math::pingpong(prev_time, (double)a->get_length());
+ prev_time = Math::pingpong(prev_time, (double)a_length);
} break;
default:
break;
}
}
} else {
- if (Animation::is_greater_approx(prev_time, (double)a->get_length())) {
+ if (Animation::is_greater_approx(prev_time, (double)a_length)) {
switch (a->get_loop_mode()) {
case Animation::LOOP_NONE: {
- prev_time = (double)a->get_length();
+ prev_time = (double)a_length;
} break;
case Animation::LOOP_LINEAR: {
- prev_time = Math::fposmod(prev_time, (double)a->get_length());
+ prev_time = Math::fposmod(prev_time, (double)a_length);
} break;
case Animation::LOOP_PINGPONG: {
- prev_time = Math::pingpong(prev_time, (double)a->get_length());
+ prev_time = Math::pingpong(prev_time, (double)a_length);
} break;
default:
break;
@@ -1283,7 +1296,7 @@ void AnimationMixer::_blend_process(double p_delta, bool p_update_only) {
continue;
}
rot[0] = post_process_key_value(a, i, rot[0], t->object_id, t->bone_idx);
- a->try_rotation_track_interpolate(i, (double)a->get_length(), &rot[1]);
+ a->try_rotation_track_interpolate(i, (double)a_length, &rot[1]);
rot[1] = post_process_key_value(a, i, rot[1], t->object_id, t->bone_idx);
root_motion_cache.rot = (root_motion_cache.rot * Quaternion().slerp(rot[0].inverse() * rot[1], blend)).normalized();
prev_time = 0;
@@ -1297,7 +1310,7 @@ void AnimationMixer::_blend_process(double p_delta, bool p_update_only) {
rot[0] = post_process_key_value(a, i, rot[0], t->object_id, t->bone_idx);
a->try_rotation_track_interpolate(i, 0, &rot[1]);
root_motion_cache.rot = (root_motion_cache.rot * Quaternion().slerp(rot[0].inverse() * rot[1], blend)).normalized();
- prev_time = (double)a->get_length();
+ prev_time = (double)a_length;
}
}
Error err = a->try_rotation_track_interpolate(i, prev_time, &rot[0]);
@@ -1308,7 +1321,7 @@ void AnimationMixer::_blend_process(double p_delta, bool p_update_only) {
a->try_rotation_track_interpolate(i, time, &rot[1]);
rot[1] = post_process_key_value(a, i, rot[1], t->object_id, t->bone_idx);
root_motion_cache.rot = (root_motion_cache.rot * Quaternion().slerp(rot[0].inverse() * rot[1], blend)).normalized();
- prev_time = !backward ? 0 : (double)a->get_length();
+ prev_time = !backward ? 0 : (double)a_length;
}
{
Quaternion rot;
@@ -1336,26 +1349,26 @@ void AnimationMixer::_blend_process(double p_delta, bool p_update_only) {
prev_time = 0;
} break;
case Animation::LOOP_LINEAR: {
- prev_time = Math::fposmod(prev_time, (double)a->get_length());
+ prev_time = Math::fposmod(prev_time, (double)a_length);
} break;
case Animation::LOOP_PINGPONG: {
- prev_time = Math::pingpong(prev_time, (double)a->get_length());
+ prev_time = Math::pingpong(prev_time, (double)a_length);
} break;
default:
break;
}
}
} else {
- if (Animation::is_greater_approx(prev_time, (double)a->get_length())) {
+ if (Animation::is_greater_approx(prev_time, (double)a_length)) {
switch (a->get_loop_mode()) {
case Animation::LOOP_NONE: {
- prev_time = (double)a->get_length();
+ prev_time = (double)a_length;
} break;
case Animation::LOOP_LINEAR: {
- prev_time = Math::fposmod(prev_time, (double)a->get_length());
+ prev_time = Math::fposmod(prev_time, (double)a_length);
} break;
case Animation::LOOP_PINGPONG: {
- prev_time = Math::pingpong(prev_time, (double)a->get_length());
+ prev_time = Math::pingpong(prev_time, (double)a_length);
} break;
default:
break;
@@ -1370,7 +1383,7 @@ void AnimationMixer::_blend_process(double p_delta, bool p_update_only) {
continue;
}
scale[0] = post_process_key_value(a, i, scale[0], t->object_id, t->bone_idx);
- a->try_scale_track_interpolate(i, (double)a->get_length(), &scale[1]);
+ a->try_scale_track_interpolate(i, (double)a_length, &scale[1]);
root_motion_cache.scale += (scale[1] - scale[0]) * blend;
scale[1] = post_process_key_value(a, i, scale[1], t->object_id, t->bone_idx);
prev_time = 0;
@@ -1385,7 +1398,7 @@ void AnimationMixer::_blend_process(double p_delta, bool p_update_only) {
a->try_scale_track_interpolate(i, 0, &scale[1]);
scale[1] = post_process_key_value(a, i, scale[1], t->object_id, t->bone_idx);
root_motion_cache.scale += (scale[1] - scale[0]) * blend;
- prev_time = (double)a->get_length();
+ prev_time = (double)a_length;
}
}
Error err = a->try_scale_track_interpolate(i, prev_time, &scale[0]);
@@ -1396,7 +1409,7 @@ void AnimationMixer::_blend_process(double p_delta, bool p_update_only) {
a->try_scale_track_interpolate(i, time, &scale[1]);
scale[1] = post_process_key_value(a, i, scale[1], t->object_id, t->bone_idx);
root_motion_cache.scale += (scale[1] - scale[0]) * blend;
- prev_time = !backward ? 0 : (double)a->get_length();
+ prev_time = !backward ? 0 : (double)a_length;
}
{
Vector3 scale;
@@ -1560,7 +1573,7 @@ void AnimationMixer::_blend_process(double p_delta, bool p_update_only) {
}
PlayingAudioTrackInfo &track_info = t->playing_streams[oid];
- track_info.length = a->get_length();
+ track_info.length = a_length;
track_info.time = time;
track_info.volume += blend;
track_info.loop = a->get_loop_mode() != Animation::LOOP_NONE;
@@ -1679,7 +1692,7 @@ void AnimationMixer::_blend_process(double p_delta, bool p_update_only) {
at_anim_pos = Math::fposmod(time - pos, (double)anim->get_length()); // Seek to loop.
} break;
case Animation::LOOP_PINGPONG: {
- at_anim_pos = Math::pingpong(time - pos, (double)a->get_length());
+ at_anim_pos = Math::pingpong(time - pos, (double)a_length);
} break;
default:
break;
@@ -1717,6 +1730,7 @@ void AnimationMixer::_blend_process(double p_delta, bool p_update_only) {
}
}
}
+ is_GDVIRTUAL_CALL_post_process_key_value = true;
}
void AnimationMixer::_blend_apply() {
diff --git a/scene/animation/animation_mixer.h b/scene/animation/animation_mixer.h
index c029c68ae1..5482197fbd 100644
--- a/scene/animation/animation_mixer.h
+++ b/scene/animation/animation_mixer.h
@@ -48,6 +48,7 @@ class AnimationMixer : public Node {
#endif // TOOLS_ENABLED
bool reset_on_save = true;
+ bool is_GDVIRTUAL_CALL_post_process_key_value = true;
public:
enum AnimationCallbackModeProcess {
diff --git a/scene/animation/animation_tree.cpp b/scene/animation/animation_tree.cpp
index c5a6a99d07..19080e61de 100644
--- a/scene/animation/animation_tree.cpp
+++ b/scene/animation/animation_tree.cpp
@@ -203,10 +203,11 @@ AnimationNode::NodeTimeInfo AnimationNode::_blend_node(Ref<AnimationNode> p_node
}
for (const KeyValue<NodePath, bool> &E : filter) {
- if (!process_state->track_map.has(E.key)) {
+ const HashMap<NodePath, int> &map = *process_state->track_map;
+ if (!map.has(E.key)) {
continue;
}
- int idx = process_state->track_map[E.key];
+ int idx = map[E.key];
blendw[idx] = 1.0; // Filtered goes to one.
}
@@ -618,7 +619,7 @@ bool AnimationTree::_blend_pre_process(double p_delta, int p_track_count, const
process_state.valid = true;
process_state.invalid_reasons = "";
process_state.last_pass = process_pass;
- process_state.track_map = p_track_map;
+ process_state.track_map = &p_track_map;
// Init node state for root AnimationNode.
root_animation_node->node_state.track_weights.resize(p_track_count);
@@ -680,7 +681,7 @@ uint64_t AnimationTree::get_last_process_pass() const {
}
PackedStringArray AnimationTree::get_configuration_warnings() const {
- PackedStringArray warnings = Node::get_configuration_warnings();
+ PackedStringArray warnings = AnimationMixer::get_configuration_warnings();
if (!root_animation_node.is_valid()) {
warnings.push_back(RTR("No root AnimationNode for the graph is set."));
}
diff --git a/scene/animation/animation_tree.h b/scene/animation/animation_tree.h
index aa497ff1d6..d4b7bf31c9 100644
--- a/scene/animation/animation_tree.h
+++ b/scene/animation/animation_tree.h
@@ -106,7 +106,7 @@ public:
// Temporary state for blending process which needs to be started in the AnimationTree, pass through the AnimationNodes, and then return to the AnimationTree.
struct ProcessState {
AnimationTree *tree = nullptr;
- HashMap<NodePath, int> track_map; // TODO: Is there a better way to manage filter/tracks?
+ const HashMap<NodePath, int> *track_map; // TODO: Is there a better way to manage filter/tracks?
bool is_testing = false;
bool valid = false;
String invalid_reasons;
diff --git a/scene/animation/tween.cpp b/scene/animation/tween.cpp
index f8bbd704f4..e1fd8abede 100644
--- a/scene/animation/tween.cpp
+++ b/scene/animation/tween.cpp
@@ -579,6 +579,7 @@ bool PropertyTweener::step(double &r_delta) {
Object *target_instance = ObjectDB::get_instance(target);
if (!target_instance) {
+ _finish();
return false;
}
elapsed_time += r_delta;
@@ -706,6 +707,7 @@ bool CallbackTweener::step(double &r_delta) {
}
if (!callback.is_valid()) {
+ _finish();
return false;
}
@@ -770,6 +772,7 @@ bool MethodTweener::step(double &r_delta) {
}
if (!callback.is_valid()) {
+ _finish();
return false;
}
diff --git a/scene/audio/audio_stream_player.cpp b/scene/audio/audio_stream_player.cpp
index 183c4af950..d4b44a8b69 100644
--- a/scene/audio/audio_stream_player.cpp
+++ b/scene/audio/audio_stream_player.cpp
@@ -154,10 +154,6 @@ void AudioStreamPlayer::_set_playing(bool p_enable) {
internal->set_playing(p_enable);
}
-bool AudioStreamPlayer::_is_active() const {
- return internal->is_active();
-}
-
void AudioStreamPlayer::set_stream_paused(bool p_pause) {
internal->set_stream_paused(p_pause);
}
@@ -249,8 +245,7 @@ void AudioStreamPlayer::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_mix_target", "mix_target"), &AudioStreamPlayer::set_mix_target);
ClassDB::bind_method(D_METHOD("get_mix_target"), &AudioStreamPlayer::get_mix_target);
- ClassDB::bind_method(D_METHOD("_set_playing", "enable"), &AudioStreamPlayer::_set_playing);
- ClassDB::bind_method(D_METHOD("_is_active"), &AudioStreamPlayer::_is_active);
+ ClassDB::bind_method(D_METHOD("set_playing", "enable"), &AudioStreamPlayer::_set_playing);
ClassDB::bind_method(D_METHOD("set_stream_paused", "pause"), &AudioStreamPlayer::set_stream_paused);
ClassDB::bind_method(D_METHOD("get_stream_paused"), &AudioStreamPlayer::get_stream_paused);
@@ -267,7 +262,7 @@ void AudioStreamPlayer::_bind_methods() {
ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "stream", PROPERTY_HINT_RESOURCE_TYPE, "AudioStream"), "set_stream", "get_stream");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "volume_db", PROPERTY_HINT_RANGE, "-80,24,suffix:dB"), "set_volume_db", "get_volume_db");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "pitch_scale", PROPERTY_HINT_RANGE, "0.01,4,0.01,or_greater"), "set_pitch_scale", "get_pitch_scale");
- ADD_PROPERTY(PropertyInfo(Variant::BOOL, "playing", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_EDITOR), "_set_playing", "is_playing");
+ ADD_PROPERTY(PropertyInfo(Variant::BOOL, "playing", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_EDITOR), "set_playing", "is_playing");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "autoplay"), "set_autoplay", "is_autoplay_enabled");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "stream_paused", PROPERTY_HINT_NONE, ""), "set_stream_paused", "get_stream_paused");
ADD_PROPERTY(PropertyInfo(Variant::INT, "mix_target", PROPERTY_HINT_ENUM, "Stereo,Surround,Center"), "set_mix_target", "get_mix_target");
diff --git a/scene/gui/base_button.cpp b/scene/gui/base_button.cpp
index 01e3cce78b..ed7e0de0e2 100644
--- a/scene/gui/base_button.cpp
+++ b/scene/gui/base_button.cpp
@@ -140,7 +140,7 @@ void BaseButton::_pressed() {
void BaseButton::_toggled(bool p_pressed) {
GDVIRTUAL_CALL(_toggled, p_pressed);
toggled(p_pressed);
- emit_signal(SNAME("toggled"), p_pressed);
+ emit_signal(SceneStringName(toggled), p_pressed);
}
void BaseButton::on_action_event(Ref<InputEvent> p_event) {
diff --git a/scene/gui/code_edit.cpp b/scene/gui/code_edit.cpp
index e8be38e680..c3287035ff 100644
--- a/scene/gui/code_edit.cpp
+++ b/scene/gui/code_edit.cpp
@@ -40,8 +40,18 @@ void CodeEdit::_notification(int p_what) {
switch (p_what) {
case NOTIFICATION_THEME_CHANGED: {
set_gutter_width(main_gutter, get_line_height());
- set_gutter_width(line_number_gutter, (line_number_digits + 1) * theme_cache.font->get_char_size('0', theme_cache.font_size).width);
+ _update_line_number_gutter_width();
set_gutter_width(fold_gutter, get_line_height() / 1.2);
+ _clear_line_number_text_cache();
+ } break;
+
+ case NOTIFICATION_TRANSLATION_CHANGED:
+ [[fallthrough]];
+ case NOTIFICATION_LAYOUT_DIRECTION_CHANGED:
+ [[fallthrough]];
+ case NOTIFICATION_VISIBILITY_CHANGED: {
+ // Avoid having many hidden text editors with unused cache filling up memory.
+ _clear_line_number_text_cache();
} break;
case NOTIFICATION_DRAW: {
@@ -1287,9 +1297,9 @@ bool CodeEdit::is_drawing_executing_lines_gutter() const {
}
void CodeEdit::_main_gutter_draw_callback(int p_line, int p_gutter, const Rect2 &p_region) {
+ bool hovering = get_hovered_gutter() == Vector2i(main_gutter, p_line);
if (draw_breakpoints && theme_cache.breakpoint_icon.is_valid()) {
bool breakpointed = is_line_breakpointed(p_line);
- bool hovering = p_region.has_point(get_local_mouse_pos());
bool shift_pressed = Input::get_singleton()->is_key_pressed(Key::SHIFT);
if (breakpointed || (hovering && !is_dragging_cursor() && !shift_pressed)) {
@@ -1308,7 +1318,6 @@ void CodeEdit::_main_gutter_draw_callback(int p_line, int p_gutter, const Rect2
if (draw_bookmarks && theme_cache.bookmark_icon.is_valid()) {
bool bookmarked = is_line_bookmarked(p_line);
- bool hovering = p_region.has_point(get_local_mouse_pos());
bool shift_pressed = Input::get_singleton()->is_key_pressed(Key::SHIFT);
if (bookmarked || (hovering && !is_dragging_cursor() && shift_pressed)) {
@@ -1442,7 +1451,13 @@ bool CodeEdit::is_draw_line_numbers_enabled() const {
}
void CodeEdit::set_line_numbers_zero_padded(bool p_zero_padded) {
- p_zero_padded ? line_number_padding = "0" : line_number_padding = " ";
+ String new_line_number_padding = p_zero_padded ? "0" : " ";
+ if (line_number_padding == new_line_number_padding) {
+ return;
+ }
+
+ line_number_padding = new_line_number_padding;
+ _clear_line_number_text_cache();
queue_redraw();
}
@@ -1451,19 +1466,55 @@ bool CodeEdit::is_line_numbers_zero_padded() const {
}
void CodeEdit::_line_number_draw_callback(int p_line, int p_gutter, const Rect2 &p_region) {
- String fc = String::num(p_line + 1).lpad(line_number_digits, line_number_padding);
- if (is_localizing_numeral_system()) {
- fc = TS->format_number(fc);
- }
- Ref<TextLine> tl;
- tl.instantiate();
- tl->add_string(fc, theme_cache.font, theme_cache.font_size);
- int yofs = p_region.position.y + (get_line_height() - tl->get_size().y) / 2;
+ if (!Rect2(Vector2(0, 0), get_size()).intersects(p_region)) {
+ return;
+ }
+
+ bool rtl = is_layout_rtl();
+ HashMap<int, RID>::Iterator E = line_number_text_cache.find(p_line);
+ RID text_rid;
+ if (E) {
+ text_rid = E->value;
+ } else {
+ String fc = String::num(p_line + 1).lpad(line_number_digits, line_number_padding);
+ if (is_localizing_numeral_system()) {
+ fc = TS->format_number(fc);
+ }
+
+ text_rid = TS->create_shaped_text();
+ if (theme_cache.font.is_valid()) {
+ TS->shaped_text_add_string(text_rid, fc, theme_cache.font->get_rids(), theme_cache.font_size, theme_cache.font->get_opentype_features());
+ }
+ line_number_text_cache.insert(p_line, text_rid);
+ }
+
+ Size2 text_size = TS->shaped_text_get_size(text_rid);
+ Point2 ofs = p_region.get_center() - text_size / 2;
+ ofs.y += TS->shaped_text_get_ascent(text_rid);
+
+ if (rtl) {
+ ofs.x = p_region.position.x;
+ } else {
+ ofs.x = p_region.get_end().x - text_size.width;
+ }
+
Color number_color = get_line_gutter_item_color(p_line, line_number_gutter);
if (number_color == Color(1, 1, 1)) {
number_color = theme_cache.line_number_color;
}
- tl->draw(get_canvas_item(), Point2(p_region.position.x, yofs), number_color);
+
+ TS->shaped_text_draw(text_rid, get_canvas_item(), ofs, -1, -1, number_color);
+}
+
+void CodeEdit::_clear_line_number_text_cache() {
+ for (const KeyValue<int, RID> &KV : line_number_text_cache) {
+ TS->free_rid(KV.value);
+ }
+ line_number_text_cache.clear();
+}
+
+void CodeEdit::_update_line_number_gutter_width() {
+ set_gutter_width(line_number_gutter, (line_number_digits + 1) * theme_cache.font->get_char_size('0', theme_cache.font_size).width);
}
/* Fold Gutter */
@@ -1983,12 +2034,18 @@ Point2 CodeEdit::get_delimiter_end_position(int p_line, int p_column) const {
/* Code hint */
void CodeEdit::set_code_hint(const String &p_hint) {
+ if (code_hint == p_hint) {
+ return;
+ }
code_hint = p_hint;
code_hint_xpos = -0xFFFF;
queue_redraw();
}
void CodeEdit::set_code_hint_draw_below(bool p_below) {
+ if (code_hint_draw_below == p_below) {
+ return;
+ }
code_hint_draw_below = p_below;
queue_redraw();
}
@@ -3609,16 +3666,13 @@ void CodeEdit::_text_changed() {
}
int lc = get_line_count();
- line_number_digits = 1;
- while (lc /= 10) {
- line_number_digits++;
- }
-
- if (theme_cache.font.is_valid()) {
- set_gutter_width(line_number_gutter, (line_number_digits + 1) * theme_cache.font->get_char_size('0', theme_cache.font_size).width);
+ int new_line_number_digits = log10l(lc) + 1;
+ if (line_number_digits != new_line_number_digits) {
+ _clear_line_number_text_cache();
}
+ line_number_digits = new_line_number_digits;
+ _update_line_number_gutter_width();
- lc = get_line_count();
List<int> breakpoints;
for (const KeyValue<int, bool> &E : breakpointed_lines) {
breakpoints.push_back(E.key);
@@ -3705,6 +3759,7 @@ CodeEdit::CodeEdit() {
}
CodeEdit::~CodeEdit() {
+ _clear_line_number_text_cache();
}
// Return true if l should come before r
diff --git a/scene/gui/code_edit.h b/scene/gui/code_edit.h
index 09340be035..ab443e95e1 100644
--- a/scene/gui/code_edit.h
+++ b/scene/gui/code_edit.h
@@ -113,6 +113,9 @@ private:
int line_number_gutter = -1;
int line_number_digits = 1;
String line_number_padding = " ";
+ HashMap<int, RID> line_number_text_cache;
+ void _clear_line_number_text_cache();
+ void _update_line_number_gutter_width();
void _line_number_draw_callback(int p_line, int p_gutter, const Rect2 &p_region);
/* Fold Gutter */
diff --git a/scene/gui/color_picker.cpp b/scene/gui/color_picker.cpp
index 8a3edc25b9..c92dcbc153 100644
--- a/scene/gui/color_picker.cpp
+++ b/scene/gui/color_picker.cpp
@@ -783,7 +783,7 @@ void ColorPicker::_add_recent_preset_button(int p_size, const Color &p_color) {
recent_preset_hbc->add_child(btn_preset_new);
recent_preset_hbc->move_child(btn_preset_new, 0);
btn_preset_new->set_pressed(true);
- btn_preset_new->connect("toggled", callable_mp(this, &ColorPicker::_recent_preset_pressed).bind(btn_preset_new));
+ btn_preset_new->connect(SceneStringName(toggled), callable_mp(this, &ColorPicker::_recent_preset_pressed).bind(btn_preset_new));
}
void ColorPicker::_show_hide_preset(const bool &p_is_btn_pressed, Button *p_btn_preset, Container *p_preset_container) {
@@ -2003,7 +2003,7 @@ ColorPicker::ColorPicker() {
btn_preset->set_toggle_mode(true);
btn_preset->set_focus_mode(FOCUS_NONE);
btn_preset->set_text_alignment(HORIZONTAL_ALIGNMENT_LEFT);
- btn_preset->connect("toggled", callable_mp(this, &ColorPicker::_show_hide_preset).bind(btn_preset, preset_container));
+ btn_preset->connect(SceneStringName(toggled), callable_mp(this, &ColorPicker::_show_hide_preset).bind(btn_preset, preset_container));
real_vbox->add_child(btn_preset);
real_vbox->add_child(preset_container);
@@ -2020,7 +2020,7 @@ ColorPicker::ColorPicker() {
btn_recent_preset->set_toggle_mode(true);
btn_recent_preset->set_focus_mode(FOCUS_NONE);
btn_recent_preset->set_text_alignment(HORIZONTAL_ALIGNMENT_LEFT);
- btn_recent_preset->connect("toggled", callable_mp(this, &ColorPicker::_show_hide_preset).bind(btn_recent_preset, recent_preset_hbc));
+ btn_recent_preset->connect(SceneStringName(toggled), callable_mp(this, &ColorPicker::_show_hide_preset).bind(btn_recent_preset, recent_preset_hbc));
real_vbox->add_child(btn_recent_preset);
real_vbox->add_child(recent_preset_hbc);
diff --git a/scene/gui/control.cpp b/scene/gui/control.cpp
index 2003471de0..e030828595 100644
--- a/scene/gui/control.cpp
+++ b/scene/gui/control.cpp
@@ -248,7 +248,7 @@ void Control::get_argument_options(const StringName &p_function, int p_idx, List
PackedStringArray Control::get_configuration_warnings() const {
ERR_READ_THREAD_GUARD_V(PackedStringArray());
- PackedStringArray warnings = Node::get_configuration_warnings();
+ PackedStringArray warnings = CanvasItem::get_configuration_warnings();
if (data.mouse_filter == MOUSE_FILTER_IGNORE && !data.tooltip.is_empty()) {
warnings.push_back(RTR("The Hint Tooltip won't be displayed as the control's Mouse Filter is set to \"Ignore\". To solve this, set the Mouse Filter to \"Stop\" or \"Pass\"."));
@@ -1746,10 +1746,10 @@ void Control::_size_changed() {
// so an up to date global transform could be obtained when handling these.
_notify_transform();
+ item_rect_changed(size_changed);
if (size_changed) {
notification(NOTIFICATION_RESIZED);
}
- item_rect_changed(size_changed);
}
if (pos_changed && !size_changed) {
@@ -2356,6 +2356,24 @@ void Control::_window_find_focus_neighbor(const Vector2 &p_dir, Node *p_at, cons
points[2] = xform.xform(c->get_size());
points[3] = xform.xform(Point2(0, c->get_size().y));
+ // Tie-breaking aims to address situations where a potential focus neighbor's bounding rect
+ // is right next to the currently focused control (e.g. in BoxContainer with
+ // separation overridden to 0). This needs specific handling so that the correct
+ // focus neighbor is selected.
+
+ // Calculate centers of the potential neighbor, currently focused, and closest controls.
+ Point2 center = xform.xform(0.5 * c->get_size());
+ // We only have the points, not an actual reference.
+ Point2 p_center = 0.25 * (p_points[0] + p_points[1] + p_points[2] + p_points[3]);
+ Point2 closest_center;
+ bool should_tiebreak = false;
+ if (*r_closest != nullptr) {
+ should_tiebreak = true;
+ Control *closest = *r_closest;
+ Transform2D closest_xform = closest->get_global_transform();
+ closest_center = closest_xform.xform(0.5 * closest->get_size());
+ }
+
real_t min = 1e7;
for (int i = 0; i < 4; i++) {
@@ -2376,10 +2394,15 @@ void Control::_window_find_focus_neighbor(const Vector2 &p_dir, Node *p_at, cons
Vector2 pa, pb;
real_t d = Geometry2D::get_closest_points_between_segments(la, lb, fa, fb, pa, pb);
- //real_t d = Geometry2D::get_closest_distance_between_segments(Vector3(la.x,la.y,0),Vector3(lb.x,lb.y,0),Vector3(fa.x,fa.y,0),Vector3(fb.x,fb.y,0));
if (d < r_closest_dist) {
r_closest_dist = d;
*r_closest = c;
+ } else if (should_tiebreak && d == r_closest_dist) {
+ // Tie-break in favor of the control most aligned with p_dir.
+ if (p_dir.dot((center - p_center).normalized()) > p_dir.dot((closest_center - p_center).normalized())) {
+ r_closest_dist = d;
+ *r_closest = c;
+ }
}
}
}
@@ -3606,7 +3629,7 @@ void Control::_bind_methods() {
ADD_PROPERTY(PropertyInfo(Variant::INT, "focus_mode", PROPERTY_HINT_ENUM, "None,Click,All"), "set_focus_mode", "get_focus_mode");
ADD_GROUP("Mouse", "mouse_");
- ADD_PROPERTY(PropertyInfo(Variant::INT, "mouse_filter", PROPERTY_HINT_ENUM, "Stop,Pass,Ignore"), "set_mouse_filter", "get_mouse_filter");
+ ADD_PROPERTY(PropertyInfo(Variant::INT, "mouse_filter", PROPERTY_HINT_ENUM, "Stop,Pass (Propagate Up),Ignore"), "set_mouse_filter", "get_mouse_filter");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "mouse_force_pass_scroll_events"), "set_force_pass_scroll_events", "is_force_pass_scroll_events");
ADD_PROPERTY(PropertyInfo(Variant::INT, "mouse_default_cursor_shape", PROPERTY_HINT_ENUM, "Arrow,I-Beam,Pointing Hand,Cross,Wait,Busy,Drag,Can Drop,Forbidden,Vertical Resize,Horizontal Resize,Secondary Diagonal Resize,Main Diagonal Resize,Move,Vertical Split,Horizontal Split,Help"), "set_default_cursor_shape", "get_default_cursor_shape");
diff --git a/scene/gui/file_dialog.cpp b/scene/gui/file_dialog.cpp
index 8047369ab1..373488b0fc 100644
--- a/scene/gui/file_dialog.cpp
+++ b/scene/gui/file_dialog.cpp
@@ -124,6 +124,8 @@ void FileDialog::_native_dialog_cb(bool p_ok, const Vector<String> &p_files, int
file_name = ProjectSettings::get_singleton()->localize_path(file_name);
}
}
+ selected_options = p_selected_options;
+
String f = files[0];
if (mode == FILE_MODE_OPEN_FILES) {
emit_signal(SNAME("files_selected"), files);
@@ -155,7 +157,6 @@ void FileDialog::_native_dialog_cb(bool p_ok, const Vector<String> &p_files, int
}
file->set_text(f);
dir->set_text(f.get_base_dir());
- selected_options = p_selected_options;
filter->select(p_filter);
}
@@ -1143,7 +1144,7 @@ void FileDialog::_update_option_controls() {
CheckBox *cb = memnew(CheckBox);
cb->set_pressed(opt.default_idx);
grid_options->add_child(cb);
- cb->connect("toggled", callable_mp(this, &FileDialog::_option_changed_checkbox_toggled).bind(opt.name));
+ cb->connect(SceneStringName(toggled), callable_mp(this, &FileDialog::_option_changed_checkbox_toggled).bind(opt.name));
selected_options[opt.name] = (bool)opt.default_idx;
} else {
OptionButton *ob = memnew(OptionButton);
@@ -1441,7 +1442,7 @@ FileDialog::FileDialog() {
show_hidden->set_toggle_mode(true);
show_hidden->set_pressed(is_showing_hidden_files());
show_hidden->set_tooltip_text(ETR("Toggle the visibility of hidden files."));
- show_hidden->connect("toggled", callable_mp(this, &FileDialog::set_show_hidden_files));
+ show_hidden->connect(SceneStringName(toggled), callable_mp(this, &FileDialog::set_show_hidden_files));
hbc->add_child(show_hidden);
shortcuts_container = memnew(HBoxContainer);
diff --git a/scene/gui/line_edit.cpp b/scene/gui/line_edit.cpp
index 0006204ae3..c2818edd9c 100644
--- a/scene/gui/line_edit.cpp
+++ b/scene/gui/line_edit.cpp
@@ -68,10 +68,15 @@ void LineEdit::_move_caret_left(bool p_select, bool p_move_by_word) {
int cc = caret_column;
PackedInt32Array words = TS->shaped_text_get_word_breaks(text_rid);
- for (int i = words.size() - 2; i >= 0; i = i - 2) {
- if (words[i] < cc) {
- cc = words[i];
- break;
+ if (words.is_empty() || cc <= words[0]) {
+ // Move to the start when there are no more words.
+ cc = 0;
+ } else {
+ for (int i = words.size() - 2; i >= 0; i = i - 2) {
+ if (words[i] < cc) {
+ cc = words[i];
+ break;
+ }
}
}
@@ -101,10 +106,15 @@ void LineEdit::_move_caret_right(bool p_select, bool p_move_by_word) {
int cc = caret_column;
PackedInt32Array words = TS->shaped_text_get_word_breaks(text_rid);
- for (int i = 1; i < words.size(); i = i + 2) {
- if (words[i] > cc) {
- cc = words[i];
- break;
+ if (words.is_empty() || cc >= words[words.size() - 1]) {
+ // Move to the end when there are no more words.
+ cc = text.length();
+ } else {
+ for (int i = 1; i < words.size(); i = i + 2) {
+ if (words[i] > cc) {
+ cc = words[i];
+ break;
+ }
}
}
@@ -159,10 +169,15 @@ void LineEdit::_backspace(bool p_word, bool p_all_to_left) {
int cc = caret_column;
PackedInt32Array words = TS->shaped_text_get_word_breaks(text_rid);
- for (int i = words.size() - 2; i >= 0; i = i - 2) {
- if (words[i] < cc) {
- cc = words[i];
- break;
+ if (words.is_empty() || cc <= words[0]) {
+ // Delete to the start when there are no more words.
+ cc = 0;
+ } else {
+ for (int i = words.size() - 2; i >= 0; i = i - 2) {
+ if (words[i] < cc) {
+ cc = words[i];
+ break;
+ }
}
}
@@ -198,10 +213,15 @@ void LineEdit::_delete(bool p_word, bool p_all_to_right) {
if (p_word) {
int cc = caret_column;
PackedInt32Array words = TS->shaped_text_get_word_breaks(text_rid);
- for (int i = 1; i < words.size(); i = i + 2) {
- if (words[i] > cc) {
- cc = words[i];
- break;
+ if (words.is_empty() || cc >= words[words.size() - 1]) {
+ // Delete to the end when there are no more words.
+ cc = text.length();
+ } else {
+ for (int i = 1; i < words.size(); i = i + 2) {
+ if (words[i] > cc) {
+ cc = words[i];
+ break;
+ }
}
}
@@ -698,13 +718,13 @@ bool LineEdit::can_drop_data(const Point2 &p_point, const Variant &p_data) const
return drop_override;
}
- return is_editable() && p_data.get_type() == Variant::STRING;
+ return is_editable() && p_data.is_string();
}
void LineEdit::drop_data(const Point2 &p_point, const Variant &p_data) {
Control::drop_data(p_point, p_data);
- if (p_data.get_type() == Variant::STRING && is_editable()) {
+ if (p_data.is_string() && is_editable()) {
set_caret_at_pixel_pos(p_point.x);
int caret_column_tmp = caret_column;
bool is_inside_sel = selection.enabled && caret_column >= selection.begin && caret_column <= selection.end;
diff --git a/scene/gui/menu_bar.cpp b/scene/gui/menu_bar.cpp
index 3264733548..46c9c7cccc 100644
--- a/scene/gui/menu_bar.cpp
+++ b/scene/gui/menu_bar.cpp
@@ -218,15 +218,18 @@ void MenuBar::bind_global_menu() {
int global_start_idx = -1;
int count = nmenu->get_item_count(main_menu);
String prev_tag;
- for (int i = 0; i < count; i++) {
- String tag = nmenu->get_item_tag(main_menu, i).operator String().get_slice("#", 1);
- if (!tag.is_empty() && tag != prev_tag) {
- if (i >= start_index) {
- global_start_idx = i;
- break;
+ if (start_index >= 0) {
+ for (int i = 0; i < count; i++) {
+ String tag = nmenu->get_item_tag(main_menu, i).operator String().get_slice("#", 1);
+ if (!tag.is_empty() && tag != prev_tag) {
+ MenuBar *mb = Object::cast_to<MenuBar>(ObjectDB::get_instance(ObjectID(static_cast<uint64_t>(tag.to_int()))));
+ if (mb && mb->get_start_index() >= start_index) {
+ global_start_idx = i;
+ break;
+ }
}
+ prev_tag = tag;
}
- prev_tag = tag;
}
if (global_start_idx == -1) {
global_start_idx = count;
@@ -680,10 +683,7 @@ void MenuBar::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_menu_hidden", "menu", "hidden"), &MenuBar::set_menu_hidden);
ClassDB::bind_method(D_METHOD("is_menu_hidden", "menu"), &MenuBar::is_menu_hidden);
- // TODO: Properly handle popups when advanced GUI is disabled.
-#ifndef ADVANCED_GUI_DISABLED
ClassDB::bind_method(D_METHOD("get_menu_popup", "menu"), &MenuBar::get_menu_popup);
-#endif // ADVANCED_GUI_DISABLED
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "flat"), "set_flat", "is_flat");
ADD_PROPERTY(PropertyInfo(Variant::INT, "start_index"), "set_start_index", "get_start_index");
diff --git a/scene/gui/menu_button.cpp b/scene/gui/menu_button.cpp
index 8c5bb1b33d..1069a752c4 100644
--- a/scene/gui/menu_button.cpp
+++ b/scene/gui/menu_button.cpp
@@ -173,10 +173,7 @@ bool MenuButton::_get(const StringName &p_name, Variant &r_ret) const {
}
void MenuButton::_bind_methods() {
- // TODO: Properly handle popups when advanced GUI is disabled.
-#ifndef ADVANCED_GUI_DISABLED
ClassDB::bind_method(D_METHOD("get_popup"), &MenuButton::get_popup);
-#endif // ADVANCED_GUI_DISABLED
ClassDB::bind_method(D_METHOD("show_popup"), &MenuButton::show_popup);
ClassDB::bind_method(D_METHOD("set_switch_on_hover", "enable"), &MenuButton::set_switch_on_hover);
ClassDB::bind_method(D_METHOD("is_switch_on_hover"), &MenuButton::is_switch_on_hover);
diff --git a/scene/gui/popup_menu.cpp b/scene/gui/popup_menu.cpp
index 5de4cdaf59..3c04094526 100644
--- a/scene/gui/popup_menu.cpp
+++ b/scene/gui/popup_menu.cpp
@@ -2592,14 +2592,6 @@ void PopupMenu::clear_autohide_areas() {
autohide_areas.clear();
}
-void PopupMenu::take_mouse_focus() {
- ERR_FAIL_COND(!is_inside_tree());
-
- if (get_parent()) {
- get_parent()->get_viewport()->pass_mouse_focus_to(this, control);
- }
-}
-
bool PopupMenu::_set(const StringName &p_name, const Variant &p_value) {
if (property_helper.property_set_value(p_name, p_value)) {
return true;
diff --git a/scene/gui/popup_menu.h b/scene/gui/popup_menu.h
index 5313dae404..b8aa51c1ad 100644
--- a/scene/gui/popup_menu.h
+++ b/scene/gui/popup_menu.h
@@ -369,8 +369,6 @@ public:
virtual void popup(const Rect2i &p_bounds = Rect2i()) override;
virtual void set_visible(bool p_visible) override;
- void take_mouse_focus();
-
PopupMenu();
~PopupMenu();
};
diff --git a/scene/gui/range.cpp b/scene/gui/range.cpp
index 83359653f1..d7b1a4933d 100644
--- a/scene/gui/range.cpp
+++ b/scene/gui/range.cpp
@@ -31,7 +31,7 @@
#include "range.h"
PackedStringArray Range::get_configuration_warnings() const {
- PackedStringArray warnings = Node::get_configuration_warnings();
+ PackedStringArray warnings = Control::get_configuration_warnings();
if (shared->exp_ratio && shared->min <= 0) {
warnings.push_back(RTR("If \"Exp Edit\" is enabled, \"Min Value\" must be greater than 0."));
diff --git a/scene/gui/rich_text_label.cpp b/scene/gui/rich_text_label.cpp
index 457392fb2c..715b682342 100644
--- a/scene/gui/rich_text_label.cpp
+++ b/scene/gui/rich_text_label.cpp
@@ -547,7 +547,7 @@ float RichTextLabel::_shape_line(ItemFrame *p_frame, int p_line, const Ref<Font>
if (font_size_it && font_size_it->font_size > 0) {
font_size = font_size_it->font_size;
}
- l.text_buf->add_string("\n", font, font_size);
+ l.text_buf->add_string(String::chr(0x200B), font, font_size);
txt += "\n";
l.char_count++;
remaining_characters--;
@@ -817,6 +817,8 @@ int RichTextLabel::_draw_line(ItemFrame *p_frame, int p_line, const Vector2 &p_o
}
int line_count = 0;
+ // Bottom margin for text clipping.
+ float v_limit = theme_cache.normal_style->get_margin(SIDE_BOTTOM);
Size2 ctrl_size = get_size();
// Draw text.
for (int line = 0; line < l.text_buf->get_line_count(); line++) {
@@ -824,7 +826,7 @@ int RichTextLabel::_draw_line(ItemFrame *p_frame, int p_line, const Vector2 &p_o
off.y += theme_cache.line_separation;
}
- if (p_ofs.y + off.y >= ctrl_size.height) {
+ if (p_ofs.y + off.y >= ctrl_size.height - v_limit) {
break;
}
@@ -910,84 +912,6 @@ int RichTextLabel::_draw_line(ItemFrame *p_frame, int p_line, const Vector2 &p_o
double uth = TS->shaped_text_get_underline_thickness(rid);
off.y += l_ascent;
- // Draw inlined objects.
- Array objects = TS->shaped_text_get_objects(rid);
- for (int i = 0; i < objects.size(); i++) {
- Item *it = items.get_or_null(objects[i]);
- if (it != nullptr) {
- Vector2i obj_range = TS->shaped_text_get_object_range(rid, objects[i]);
- if (trim_chars && l.char_offset + obj_range.y > visible_characters) {
- continue;
- }
- if (trim_glyphs_ltr || trim_glyphs_rtl) {
- int obj_glyph = r_processed_glyphs + TS->shaped_text_get_object_glyph(rid, objects[i]);
- if ((trim_glyphs_ltr && (obj_glyph >= visible_glyphs)) || (trim_glyphs_rtl && (obj_glyph < total_glyphs - visible_glyphs))) {
- continue;
- }
- }
- Rect2 rect = TS->shaped_text_get_object_rect(rid, objects[i]);
- switch (it->type) {
- case ITEM_IMAGE: {
- ItemImage *img = static_cast<ItemImage *>(it);
- if (img->pad) {
- Size2 pad_size = rect.size.min(img->image->get_size());
- Vector2 pad_off = (rect.size - pad_size) / 2;
- img->image->draw_rect(ci, Rect2(p_ofs + rect.position + off + pad_off, pad_size), false, img->color);
- } else {
- img->image->draw_rect(ci, Rect2(p_ofs + rect.position + off, rect.size), false, img->color);
- }
- } break;
- case ITEM_TABLE: {
- ItemTable *table = static_cast<ItemTable *>(it);
- Color odd_row_bg = theme_cache.table_odd_row_bg;
- Color even_row_bg = theme_cache.table_even_row_bg;
- Color border = theme_cache.table_border;
- float h_separation = theme_cache.table_h_separation;
- float v_separation = theme_cache.table_v_separation;
-
- int col_count = table->columns.size();
- int row_count = table->rows.size();
-
- int idx = 0;
- for (Item *E : table->subitems) {
- ItemFrame *frame = static_cast<ItemFrame *>(E);
-
- int col = idx % col_count;
- int row = idx / col_count;
-
- if (frame->lines.size() != 0 && row < row_count) {
- Vector2 coff = frame->lines[0].offset;
- if (rtl) {
- coff.x = rect.size.width - table->columns[col].width - coff.x;
- }
- if (row % 2 == 0) {
- Color c = frame->odd_row_bg != Color(0, 0, 0, 0) ? frame->odd_row_bg : odd_row_bg;
- if (c.a > 0.0) {
- draw_rect(Rect2(p_ofs + rect.position + off + coff - frame->padding.position - Vector2(h_separation * 0.5, v_separation * 0.5).floor(), Size2(table->columns[col].width + h_separation + frame->padding.position.x + frame->padding.size.x, table->rows[row])), c, true);
- }
- } else {
- Color c = frame->even_row_bg != Color(0, 0, 0, 0) ? frame->even_row_bg : even_row_bg;
- if (c.a > 0.0) {
- draw_rect(Rect2(p_ofs + rect.position + off + coff - frame->padding.position - Vector2(h_separation * 0.5, v_separation * 0.5).floor(), Size2(table->columns[col].width + h_separation + frame->padding.position.x + frame->padding.size.x, table->rows[row])), c, true);
- }
- }
- Color bc = frame->border != Color(0, 0, 0, 0) ? frame->border : border;
- if (bc.a > 0.0) {
- draw_rect(Rect2(p_ofs + rect.position + off + coff - frame->padding.position - Vector2(h_separation * 0.5, v_separation * 0.5).floor(), Size2(table->columns[col].width + h_separation + frame->padding.position.x + frame->padding.size.x, table->rows[row])), bc, false);
- }
- }
-
- for (int j = 0; j < (int)frame->lines.size(); j++) {
- _draw_line(frame, j, p_ofs + rect.position + off + Vector2(0, frame->lines[j].offset.y), rect.size.x, p_base_color, p_outline_size, p_outline_color, p_font_shadow_color, p_shadow_outline_size, p_shadow_ofs, r_processed_glyphs);
- }
- idx++;
- }
- } break;
- default:
- break;
- }
- }
- }
const Glyph *glyphs = TS->shaped_text_get_glyphs(rid);
int gl_size = TS->shaped_text_get_glyph_count(rid);
@@ -1003,6 +927,86 @@ int RichTextLabel::_draw_line(ItemFrame *p_frame, int p_line, const Vector2 &p_o
int processed_glyphs_step = 0;
for (int step = DRAW_STEP_BACKGROUND; step < DRAW_STEP_MAX; step++) {
+ if (step == DRAW_STEP_TEXT) {
+ // Draw inlined objects.
+ Array objects = TS->shaped_text_get_objects(rid);
+ for (int i = 0; i < objects.size(); i++) {
+ Item *it = items.get_or_null(objects[i]);
+ if (it != nullptr) {
+ Vector2i obj_range = TS->shaped_text_get_object_range(rid, objects[i]);
+ if (trim_chars && l.char_offset + obj_range.y > visible_characters) {
+ continue;
+ }
+ if (trim_glyphs_ltr || trim_glyphs_rtl) {
+ int obj_glyph = r_processed_glyphs + TS->shaped_text_get_object_glyph(rid, objects[i]);
+ if ((trim_glyphs_ltr && (obj_glyph >= visible_glyphs)) || (trim_glyphs_rtl && (obj_glyph < total_glyphs - visible_glyphs))) {
+ continue;
+ }
+ }
+ Rect2 rect = TS->shaped_text_get_object_rect(rid, objects[i]);
+ switch (it->type) {
+ case ITEM_IMAGE: {
+ ItemImage *img = static_cast<ItemImage *>(it);
+ if (img->pad) {
+ Size2 pad_size = rect.size.min(img->image->get_size());
+ Vector2 pad_off = (rect.size - pad_size) / 2;
+ img->image->draw_rect(ci, Rect2(p_ofs + rect.position + off + pad_off, pad_size), false, img->color);
+ } else {
+ img->image->draw_rect(ci, Rect2(p_ofs + rect.position + off, rect.size), false, img->color);
+ }
+ } break;
+ case ITEM_TABLE: {
+ ItemTable *table = static_cast<ItemTable *>(it);
+ Color odd_row_bg = theme_cache.table_odd_row_bg;
+ Color even_row_bg = theme_cache.table_even_row_bg;
+ Color border = theme_cache.table_border;
+ float h_separation = theme_cache.table_h_separation;
+ float v_separation = theme_cache.table_v_separation;
+
+ int col_count = table->columns.size();
+ int row_count = table->rows.size();
+
+ int idx = 0;
+ for (Item *E : table->subitems) {
+ ItemFrame *frame = static_cast<ItemFrame *>(E);
+
+ int col = idx % col_count;
+ int row = idx / col_count;
+
+ if (frame->lines.size() != 0 && row < row_count) {
+ Vector2 coff = frame->lines[0].offset;
+ if (rtl) {
+ coff.x = rect.size.width - table->columns[col].width - coff.x;
+ }
+ if (row % 2 == 0) {
+ Color c = frame->odd_row_bg != Color(0, 0, 0, 0) ? frame->odd_row_bg : odd_row_bg;
+ if (c.a > 0.0) {
+ draw_rect(Rect2(p_ofs + rect.position + off + coff - frame->padding.position - Vector2(h_separation * 0.5, v_separation * 0.5).floor(), Size2(table->columns[col].width + h_separation + frame->padding.position.x + frame->padding.size.x, table->rows[row])), c, true);
+ }
+ } else {
+ Color c = frame->even_row_bg != Color(0, 0, 0, 0) ? frame->even_row_bg : even_row_bg;
+ if (c.a > 0.0) {
+ draw_rect(Rect2(p_ofs + rect.position + off + coff - frame->padding.position - Vector2(h_separation * 0.5, v_separation * 0.5).floor(), Size2(table->columns[col].width + h_separation + frame->padding.position.x + frame->padding.size.x, table->rows[row])), c, true);
+ }
+ }
+ Color bc = frame->border != Color(0, 0, 0, 0) ? frame->border : border;
+ if (bc.a > 0.0) {
+ draw_rect(Rect2(p_ofs + rect.position + off + coff - frame->padding.position - Vector2(h_separation * 0.5, v_separation * 0.5).floor(), Size2(table->columns[col].width + h_separation + frame->padding.position.x + frame->padding.size.x, table->rows[row])), bc, false);
+ }
+ }
+
+ for (int j = 0; j < (int)frame->lines.size(); j++) {
+ _draw_line(frame, j, p_ofs + rect.position + off + Vector2(0, frame->lines[j].offset.y), rect.size.x, p_base_color, p_outline_size, p_outline_color, p_font_shadow_color, p_shadow_outline_size, p_shadow_ofs, r_processed_glyphs);
+ }
+ idx++;
+ }
+ } break;
+ default:
+ break;
+ }
+ }
+ }
+ }
Vector2 off_step = off;
processed_glyphs_step = r_processed_glyphs;
@@ -1800,13 +1804,13 @@ void RichTextLabel::_notification(int p_what) {
case NOTIFICATION_RESIZED: {
_stop_thread();
- main->first_resized_line.store(0); //invalidate ALL
+ main->first_resized_line.store(0); // Invalidate all lines.
queue_redraw();
} break;
case NOTIFICATION_THEME_CHANGED: {
_stop_thread();
- main->first_invalid_font_line.store(0); //invalidate ALL
+ main->first_invalid_font_line.store(0); // Invalidate all lines.
queue_redraw();
} break;
@@ -1816,7 +1820,7 @@ void RichTextLabel::_notification(int p_what) {
set_text(text);
}
- main->first_invalid_line.store(0); //invalidate ALL
+ main->first_invalid_line.store(0); // Invalidate all lines.
queue_redraw();
} break;
@@ -1890,10 +1894,12 @@ void RichTextLabel::_notification(int p_what) {
visible_paragraph_count = 0;
visible_line_count = 0;
+ // Bottom margin for text clipping.
+ float v_limit = theme_cache.normal_style->get_margin(SIDE_BOTTOM);
// New cache draw.
Point2 ofs = text_rect.get_position() + Vector2(0, main->lines[from_line].offset.y - vofs);
int processed_glyphs = 0;
- while (ofs.y < size.height && from_line < to_line) {
+ while (ofs.y < size.height - v_limit && from_line < to_line) {
MutexLock lock(main->lines[from_line].text_buf->get_mutex());
visible_paragraph_count++;
@@ -2528,7 +2534,7 @@ PackedFloat32Array RichTextLabel::_find_tab_stops(Item *p_item) {
item = item->parent;
}
- return PackedFloat32Array();
+ return default_tab_stops;
}
HorizontalAlignment RichTextLabel::_find_alignment(Item *p_item) {
@@ -3039,7 +3045,7 @@ void RichTextLabel::add_text(const String &p_text) {
int pos = 0;
while (pos < p_text.length()) {
- int end = p_text.find("\n", pos);
+ int end = p_text.find_char('\n', pos);
String line;
bool eol = false;
if (end == -1) {
@@ -4088,6 +4094,74 @@ void RichTextLabel::parse_bbcode(const String &p_bbcode) {
append_text(p_bbcode);
}
+String RichTextLabel::_get_tag_value(const String &p_tag) {
+ return p_tag.substr(p_tag.find_char('=') + 1);
+}
+
+int RichTextLabel::_find_unquoted(const String &p_src, char32_t p_chr, int p_from) {
+ if (p_from < 0) {
+ return -1;
+ }
+
+ const int len = p_src.length();
+ if (len == 0) {
+ return -1;
+ }
+
+ const char32_t *src = p_src.get_data();
+ bool in_single_quote = false;
+ bool in_double_quote = false;
+ for (int i = p_from; i < len; i++) {
+ if (in_double_quote) {
+ if (src[i] == '"') {
+ in_double_quote = false;
+ }
+ } else if (in_single_quote) {
+ if (src[i] == '\'') {
+ in_single_quote = false;
+ }
+ } else {
+ if (src[i] == '"') {
+ in_double_quote = true;
+ } else if (src[i] == '\'') {
+ in_single_quote = true;
+ } else if (src[i] == p_chr) {
+ return i;
+ }
+ }
+ }
+
+ return -1;
+}
+
+Vector<String> RichTextLabel::_split_unquoted(const String &p_src, char32_t p_splitter) {
+ Vector<String> ret;
+
+ if (p_src.is_empty()) {
+ return ret;
+ }
+
+ int from = 0;
+ int len = p_src.length();
+
+ while (true) {
+ int end = _find_unquoted(p_src, p_splitter, from);
+ if (end < 0) {
+ end = len;
+ }
+ if (end > from) {
+ ret.push_back(p_src.substr(from, end - from));
+ }
+ if (end == len) {
+ break;
+ }
+
+ from = end + 1;
+ }
+
+ return ret;
+}
+
void RichTextLabel::append_text(const String &p_bbcode) {
_stop_thread();
MutexLock data_lock(data_mutex);
@@ -4106,7 +4180,7 @@ void RichTextLabel::append_text(const String &p_bbcode) {
set_process_internal(false);
while (pos <= p_bbcode.length()) {
- int brk_pos = p_bbcode.find("[", pos);
+ int brk_pos = p_bbcode.find_char('[', pos);
if (brk_pos < 0) {
brk_pos = p_bbcode.length();
@@ -4131,7 +4205,7 @@ void RichTextLabel::append_text(const String &p_bbcode) {
break; //nothing else to add
}
- int brk_end = p_bbcode.find("]", brk_pos + 1);
+ int brk_end = _find_unquoted(p_bbcode, ']', brk_pos + 1);
if (brk_end == -1) {
//no close, add the rest
@@ -4141,7 +4215,7 @@ void RichTextLabel::append_text(const String &p_bbcode) {
}
String tag = p_bbcode.substr(brk_pos + 1, brk_end - brk_pos - 1);
- Vector<String> split_tag_block = tag.split(" ", false);
+ Vector<String> split_tag_block = _split_unquoted(tag, ' ');
// Find optional parameters.
String bbcode_name;
@@ -4151,7 +4225,7 @@ void RichTextLabel::append_text(const String &p_bbcode) {
bbcode_name = split_tag_block[0];
for (int i = 1; i < split_tag_block.size(); i++) {
const String &expr = split_tag_block[i];
- int value_pos = expr.find("=");
+ int value_pos = expr.find_char('=');
if (value_pos > -1) {
bbcode_options[expr.substr(0, value_pos)] = expr.substr(value_pos + 1).unquote();
}
@@ -4162,7 +4236,7 @@ void RichTextLabel::append_text(const String &p_bbcode) {
// Find main parameter.
String bbcode_value;
- int main_value_pos = bbcode_name.find("=");
+ int main_value_pos = bbcode_name.find_char('=');
if (main_value_pos > -1) {
bbcode_value = bbcode_name.substr(main_value_pos + 1);
bbcode_name = bbcode_name.substr(0, main_value_pos);
@@ -4261,10 +4335,10 @@ void RichTextLabel::append_text(const String &p_bbcode) {
pos = brk_end + 1;
tag_stack.push_front(tag);
} else if (tag.begins_with("table=")) {
- Vector<String> subtag = tag.substr(6, tag.length()).split(",");
+ Vector<String> subtag = _split_unquoted(_get_tag_value(tag), U',');
_normalize_subtags(subtag);
- int columns = subtag[0].to_int();
+ int columns = (subtag.is_empty()) ? 1 : subtag[0].to_int();
if (columns < 1) {
columns = 1;
}
@@ -4311,7 +4385,7 @@ void RichTextLabel::append_text(const String &p_bbcode) {
pos = brk_end + 1;
tag_stack.push_front(tag);
} else if (tag.begins_with("cell=")) {
- int ratio = tag.substr(5, tag.length()).to_int();
+ int ratio = _get_tag_value(tag).to_int();
if (ratio < 1) {
ratio = 1;
}
@@ -4322,54 +4396,45 @@ void RichTextLabel::append_text(const String &p_bbcode) {
pos = brk_end + 1;
tag_stack.push_front("cell");
} else if (tag.begins_with("cell ")) {
- Vector<String> subtag = tag.substr(5, tag.length()).split(" ");
- _normalize_subtags(subtag);
-
- for (int i = 0; i < subtag.size(); i++) {
- Vector<String> subtag_a = subtag[i].split("=");
- _normalize_subtags(subtag_a);
-
- if (subtag_a.size() == 2) {
- if (subtag_a[0] == "expand") {
- int ratio = subtag_a[1].to_int();
- if (ratio < 1) {
- ratio = 1;
- }
- set_table_column_expand(get_current_table_column(), true, ratio);
- }
+ OptionMap::Iterator expand_option = bbcode_options.find("expand");
+ if (expand_option) {
+ int ratio = expand_option->value.to_int();
+ if (ratio < 1) {
+ ratio = 1;
}
+ set_table_column_expand(get_current_table_column(), true, ratio);
}
+
push_cell();
const Color fallback_color = Color(0, 0, 0, 0);
- for (int i = 0; i < subtag.size(); i++) {
- Vector<String> subtag_a = subtag[i].split("=");
- _normalize_subtags(subtag_a);
-
- if (subtag_a.size() == 2) {
- if (subtag_a[0] == "border") {
- Color color = Color::from_string(subtag_a[1], fallback_color);
- set_cell_border_color(color);
- } else if (subtag_a[0] == "bg") {
- Vector<String> subtag_b = subtag_a[1].split(",");
- _normalize_subtags(subtag_b);
- if (subtag_b.size() == 2) {
- Color color1 = Color::from_string(subtag_b[0], fallback_color);
- Color color2 = Color::from_string(subtag_b[1], fallback_color);
- set_cell_row_background_color(color1, color2);
- }
- if (subtag_b.size() == 1) {
- Color color1 = Color::from_string(subtag_a[1], fallback_color);
- set_cell_row_background_color(color1, color1);
- }
- } else if (subtag_a[0] == "padding") {
- Vector<String> subtag_b = subtag_a[1].split(",");
- _normalize_subtags(subtag_b);
+ OptionMap::Iterator border_option = bbcode_options.find("border");
+ if (border_option) {
+ Color color = Color::from_string(border_option->value, fallback_color);
+ set_cell_border_color(color);
+ }
+ OptionMap::Iterator bg_option = bbcode_options.find("bg");
+ if (bg_option) {
+ Vector<String> subtag_b = _split_unquoted(bg_option->value, U',');
+ _normalize_subtags(subtag_b);
- if (subtag_b.size() == 4) {
- set_cell_padding(Rect2(subtag_b[0].to_float(), subtag_b[1].to_float(), subtag_b[2].to_float(), subtag_b[3].to_float()));
- }
- }
+ if (subtag_b.size() == 2) {
+ Color color1 = Color::from_string(subtag_b[0], fallback_color);
+ Color color2 = Color::from_string(subtag_b[1], fallback_color);
+ set_cell_row_background_color(color1, color2);
+ }
+ if (subtag_b.size() == 1) {
+ Color color1 = Color::from_string(bg_option->value, fallback_color);
+ set_cell_row_background_color(color1, color1);
+ }
+ }
+ OptionMap::Iterator padding_option = bbcode_options.find("padding");
+ if (padding_option) {
+ Vector<String> subtag_b = _split_unquoted(padding_option->value, U',');
+ _normalize_subtags(subtag_b);
+
+ if (subtag_b.size() == 4) {
+ set_cell_padding(Rect2(subtag_b[0].to_float(), subtag_b[1].to_float(), subtag_b[2].to_float(), subtag_b[3].to_float()));
}
}
@@ -4386,7 +4451,7 @@ void RichTextLabel::append_text(const String &p_bbcode) {
pos = brk_end + 1;
tag_stack.push_front(tag);
} else if (tag.begins_with("char=")) {
- int32_t char_code = tag.substr(5, tag.length()).hex_to_int();
+ int32_t char_code = _get_tag_value(tag).hex_to_int();
add_text(String::chr(char_code));
pos = brk_end + 1;
} else if (tag == "lb") {
@@ -4444,19 +4509,19 @@ void RichTextLabel::append_text(const String &p_bbcode) {
add_text(String::chr(0x00AD));
pos = brk_end + 1;
} else if (tag == "center") {
- push_paragraph(HORIZONTAL_ALIGNMENT_CENTER);
+ push_paragraph(HORIZONTAL_ALIGNMENT_CENTER, text_direction, language, st_parser, default_jst_flags, default_tab_stops);
pos = brk_end + 1;
tag_stack.push_front(tag);
} else if (tag == "fill") {
- push_paragraph(HORIZONTAL_ALIGNMENT_FILL);
+ push_paragraph(HORIZONTAL_ALIGNMENT_FILL, text_direction, language, st_parser, default_jst_flags, default_tab_stops);
pos = brk_end + 1;
tag_stack.push_front(tag);
} else if (tag == "left") {
- push_paragraph(HORIZONTAL_ALIGNMENT_LEFT);
+ push_paragraph(HORIZONTAL_ALIGNMENT_LEFT, text_direction, language, st_parser, default_jst_flags, default_tab_stops);
pos = brk_end + 1;
tag_stack.push_front(tag);
} else if (tag == "right") {
- push_paragraph(HORIZONTAL_ALIGNMENT_RIGHT);
+ push_paragraph(HORIZONTAL_ALIGNMENT_RIGHT, text_direction, language, st_parser, default_jst_flags, default_tab_stops);
pos = brk_end + 1;
tag_stack.push_front(tag);
} else if (tag == "ul") {
@@ -4465,7 +4530,7 @@ void RichTextLabel::append_text(const String &p_bbcode) {
pos = brk_end + 1;
tag_stack.push_front(tag);
} else if (tag.begins_with("ul bullet=")) {
- String bullet = tag.substr(10, 1);
+ String bullet = _get_tag_value(tag);
indent_level++;
push_list(indent_level, LIST_DOTS, false, bullet);
pos = brk_end + 1;
@@ -4501,7 +4566,7 @@ void RichTextLabel::append_text(const String &p_bbcode) {
pos = brk_end + 1;
tag_stack.push_front(tag);
} else if (tag.begins_with("lang=")) {
- String lang = tag.substr(5, tag.length()).unquote();
+ String lang = _get_tag_value(tag).unquote();
push_language(lang);
pos = brk_end + 1;
tag_stack.push_front("lang");
@@ -4510,89 +4575,104 @@ void RichTextLabel::append_text(const String &p_bbcode) {
pos = brk_end + 1;
tag_stack.push_front("p");
} else if (tag.begins_with("p ")) {
- Vector<String> subtag = tag.substr(2, tag.length()).split(" ");
- _normalize_subtags(subtag);
-
HorizontalAlignment alignment = HORIZONTAL_ALIGNMENT_LEFT;
Control::TextDirection dir = Control::TEXT_DIRECTION_INHERITED;
- String lang;
- PackedFloat32Array tab_stops;
+ String lang = language;
+ PackedFloat32Array tab_stops = default_tab_stops;
TextServer::StructuredTextParser st_parser_type = TextServer::STRUCTURED_TEXT_DEFAULT;
BitField<TextServer::JustificationFlag> jst_flags = default_jst_flags;
- for (int i = 0; i < subtag.size(); i++) {
- Vector<String> subtag_a = subtag[i].split("=");
- _normalize_subtags(subtag_a);
-
- if (subtag_a.size() == 2) {
- if (subtag_a[0] == "justification_flags" || subtag_a[0] == "jst") {
- Vector<String> subtag_b = subtag_a[1].split(",");
- jst_flags = 0; // Clear flags.
- for (const String &E : subtag_b) {
- if (E == "kashida" || E == "k") {
- jst_flags.set_flag(TextServer::JUSTIFICATION_KASHIDA);
- } else if (E == "word" || E == "w") {
- jst_flags.set_flag(TextServer::JUSTIFICATION_WORD_BOUND);
- } else if (E == "trim" || E == "tr") {
- jst_flags.set_flag(TextServer::JUSTIFICATION_TRIM_EDGE_SPACES);
- } else if (E == "after_last_tab" || E == "lt") {
- jst_flags.set_flag(TextServer::JUSTIFICATION_AFTER_LAST_TAB);
- } else if (E == "skip_last" || E == "sl") {
- jst_flags.set_flag(TextServer::JUSTIFICATION_SKIP_LAST_LINE);
- } else if (E == "skip_last_with_chars" || E == "sv") {
- jst_flags.set_flag(TextServer::JUSTIFICATION_SKIP_LAST_LINE_WITH_VISIBLE_CHARS);
- } else if (E == "do_not_skip_single" || E == "ns") {
- jst_flags.set_flag(TextServer::JUSTIFICATION_DO_NOT_SKIP_SINGLE_LINE);
- }
- }
- } else if (subtag_a[0] == "tab_stops") {
- Vector<String> splitters;
- splitters.push_back(",");
- splitters.push_back(";");
- tab_stops = subtag_a[1].split_floats_mk(splitters);
- } else if (subtag_a[0] == "align") {
- if (subtag_a[1] == "l" || subtag_a[1] == "left") {
- alignment = HORIZONTAL_ALIGNMENT_LEFT;
- } else if (subtag_a[1] == "c" || subtag_a[1] == "center") {
- alignment = HORIZONTAL_ALIGNMENT_CENTER;
- } else if (subtag_a[1] == "r" || subtag_a[1] == "right") {
- alignment = HORIZONTAL_ALIGNMENT_RIGHT;
- } else if (subtag_a[1] == "f" || subtag_a[1] == "fill") {
- alignment = HORIZONTAL_ALIGNMENT_FILL;
- }
- } else if (subtag_a[0] == "dir" || subtag_a[0] == "direction") {
- if (subtag_a[1] == "a" || subtag_a[1] == "auto") {
- dir = Control::TEXT_DIRECTION_AUTO;
- } else if (subtag_a[1] == "l" || subtag_a[1] == "ltr") {
- dir = Control::TEXT_DIRECTION_LTR;
- } else if (subtag_a[1] == "r" || subtag_a[1] == "rtl") {
- dir = Control::TEXT_DIRECTION_RTL;
- }
- } else if (subtag_a[0] == "lang" || subtag_a[0] == "language") {
- lang = subtag_a[1];
- } else if (subtag_a[0] == "st" || subtag_a[0] == "bidi_override") {
- if (subtag_a[1] == "d" || subtag_a[1] == "default") {
- st_parser_type = TextServer::STRUCTURED_TEXT_DEFAULT;
- } else if (subtag_a[1] == "u" || subtag_a[1] == "uri") {
- st_parser_type = TextServer::STRUCTURED_TEXT_URI;
- } else if (subtag_a[1] == "f" || subtag_a[1] == "file") {
- st_parser_type = TextServer::STRUCTURED_TEXT_FILE;
- } else if (subtag_a[1] == "e" || subtag_a[1] == "email") {
- st_parser_type = TextServer::STRUCTURED_TEXT_EMAIL;
- } else if (subtag_a[1] == "l" || subtag_a[1] == "list") {
- st_parser_type = TextServer::STRUCTURED_TEXT_LIST;
- } else if (subtag_a[1] == "n" || subtag_a[1] == "gdscript") {
- st_parser_type = TextServer::STRUCTURED_TEXT_GDSCRIPT;
- } else if (subtag_a[1] == "c" || subtag_a[1] == "custom") {
- st_parser_type = TextServer::STRUCTURED_TEXT_CUSTOM;
- }
+
+ OptionMap::Iterator justification_flags_option = bbcode_options.find("justification_flags");
+ if (!justification_flags_option) {
+ justification_flags_option = bbcode_options.find("jst");
+ }
+ if (justification_flags_option) {
+ Vector<String> subtag_b = _split_unquoted(justification_flags_option->value, U',');
+ jst_flags = 0; // Clear flags.
+ for (const String &E : subtag_b) {
+ if (E == "kashida" || E == "k") {
+ jst_flags.set_flag(TextServer::JUSTIFICATION_KASHIDA);
+ } else if (E == "word" || E == "w") {
+ jst_flags.set_flag(TextServer::JUSTIFICATION_WORD_BOUND);
+ } else if (E == "trim" || E == "tr") {
+ jst_flags.set_flag(TextServer::JUSTIFICATION_TRIM_EDGE_SPACES);
+ } else if (E == "after_last_tab" || E == "lt") {
+ jst_flags.set_flag(TextServer::JUSTIFICATION_AFTER_LAST_TAB);
+ } else if (E == "skip_last" || E == "sl") {
+ jst_flags.set_flag(TextServer::JUSTIFICATION_SKIP_LAST_LINE);
+ } else if (E == "skip_last_with_chars" || E == "sv") {
+ jst_flags.set_flag(TextServer::JUSTIFICATION_SKIP_LAST_LINE_WITH_VISIBLE_CHARS);
+ } else if (E == "do_not_skip_single" || E == "ns") {
+ jst_flags.set_flag(TextServer::JUSTIFICATION_DO_NOT_SKIP_SINGLE_LINE);
}
}
}
+ OptionMap::Iterator tab_stops_option = bbcode_options.find("tab_stops");
+ if (tab_stops_option) {
+ Vector<String> splitters;
+ splitters.push_back(",");
+ splitters.push_back(";");
+ tab_stops = tab_stops_option->value.split_floats_mk(splitters);
+ }
+ OptionMap::Iterator align_option = bbcode_options.find("align");
+ if (align_option) {
+ if (align_option->value == "l" || align_option->value == "left") {
+ alignment = HORIZONTAL_ALIGNMENT_LEFT;
+ } else if (align_option->value == "c" || align_option->value == "center") {
+ alignment = HORIZONTAL_ALIGNMENT_CENTER;
+ } else if (align_option->value == "r" || align_option->value == "right") {
+ alignment = HORIZONTAL_ALIGNMENT_RIGHT;
+ } else if (align_option->value == "f" || align_option->value == "fill") {
+ alignment = HORIZONTAL_ALIGNMENT_FILL;
+ }
+ }
+ OptionMap::Iterator direction_option = bbcode_options.find("direction");
+ if (!direction_option) {
+ direction_option = bbcode_options.find("dir");
+ }
+ if (direction_option) {
+ if (direction_option->value == "a" || direction_option->value == "auto") {
+ dir = Control::TEXT_DIRECTION_AUTO;
+ } else if (direction_option->value == "l" || direction_option->value == "ltr") {
+ dir = Control::TEXT_DIRECTION_LTR;
+ } else if (direction_option->value == "r" || direction_option->value == "rtl") {
+ dir = Control::TEXT_DIRECTION_RTL;
+ }
+ }
+ OptionMap::Iterator language_option = bbcode_options.find("language");
+ if (!language_option) {
+ language_option = bbcode_options.find("lang");
+ }
+ if (language_option) {
+ lang = language_option->value;
+ }
+ OptionMap::Iterator bidi_override_option = bbcode_options.find("bidi_override");
+ if (!bidi_override_option) {
+ bidi_override_option = bbcode_options.find("st");
+ }
+ if (bidi_override_option) {
+ if (bidi_override_option->value == "d" || bidi_override_option->value == "default") {
+ st_parser_type = TextServer::STRUCTURED_TEXT_DEFAULT;
+ } else if (bidi_override_option->value == "u" || bidi_override_option->value == "uri") {
+ st_parser_type = TextServer::STRUCTURED_TEXT_URI;
+ } else if (bidi_override_option->value == "f" || bidi_override_option->value == "file") {
+ st_parser_type = TextServer::STRUCTURED_TEXT_FILE;
+ } else if (bidi_override_option->value == "e" || bidi_override_option->value == "email") {
+ st_parser_type = TextServer::STRUCTURED_TEXT_EMAIL;
+ } else if (bidi_override_option->value == "l" || bidi_override_option->value == "list") {
+ st_parser_type = TextServer::STRUCTURED_TEXT_LIST;
+ } else if (bidi_override_option->value == "n" || bidi_override_option->value == "gdscript") {
+ st_parser_type = TextServer::STRUCTURED_TEXT_GDSCRIPT;
+ } else if (bidi_override_option->value == "c" || bidi_override_option->value == "custom") {
+ st_parser_type = TextServer::STRUCTURED_TEXT_CUSTOM;
+ }
+ }
+
push_paragraph(alignment, dir, lang, st_parser_type, jst_flags, tab_stops);
pos = brk_end + 1;
tag_stack.push_front("p");
} else if (tag == "url") {
- int end = p_bbcode.find("[", brk_end);
+ int end = p_bbcode.find_char('[', brk_end);
if (end == -1) {
end = p_bbcode.length();
}
@@ -4603,19 +4683,16 @@ void RichTextLabel::append_text(const String &p_bbcode) {
tag_stack.push_front(tag);
} else if (tag.begins_with("url=")) {
- String url = tag.substr(4, tag.length()).unquote();
+ String url = _get_tag_value(tag).unquote();
push_meta(url, META_UNDERLINE_ALWAYS);
pos = brk_end + 1;
tag_stack.push_front("url");
} else if (tag.begins_with("hint=")) {
- String description = tag.substr(5, tag.length()).unquote();
+ String description = _get_tag_value(tag).unquote();
push_hint(description);
pos = brk_end + 1;
tag_stack.push_front("hint");
} else if (tag.begins_with("dropcap")) {
- Vector<String> subtag = tag.substr(5, tag.length()).split(" ");
- _normalize_subtags(subtag);
-
int fs = theme_cache.normal_font_size * 3;
Ref<Font> f = theme_cache.normal_font;
Color color = theme_cache.default_color;
@@ -4623,39 +4700,47 @@ void RichTextLabel::append_text(const String &p_bbcode) {
int outline_size = theme_cache.outline_size;
Rect2 dropcap_margins;
- for (int i = 0; i < subtag.size(); i++) {
- Vector<String> subtag_a = subtag[i].split("=");
- _normalize_subtags(subtag_a);
-
- if (subtag_a.size() == 2) {
- if (subtag_a[0] == "font" || subtag_a[0] == "f") {
- const String &fnt = subtag_a[1];
- Ref<Font> font = ResourceLoader::load(fnt, "Font");
- if (font.is_valid()) {
- f = font;
- }
- } else if (subtag_a[0] == "font_size") {
- fs = subtag_a[1].to_int();
- } else if (subtag_a[0] == "margins") {
- Vector<String> subtag_b = subtag_a[1].split(",");
- _normalize_subtags(subtag_b);
+ OptionMap::Iterator font_option = bbcode_options.find("font");
+ if (!font_option) {
+ font_option = bbcode_options.find("f");
+ }
+ if (font_option) {
+ const String &fnt = font_option->value;
+ Ref<Font> font = ResourceLoader::load(fnt, "Font");
+ if (font.is_valid()) {
+ f = font;
+ }
+ }
+ OptionMap::Iterator font_size_option = bbcode_options.find("font_size");
+ if (font_size_option) {
+ fs = font_size_option->value.to_int();
+ }
+ OptionMap::Iterator margins_option = bbcode_options.find("margins");
+ if (margins_option) {
+ Vector<String> subtag_b = _split_unquoted(margins_option->value, U',');
+ _normalize_subtags(subtag_b);
- if (subtag_b.size() == 4) {
- dropcap_margins.position.x = subtag_b[0].to_float();
- dropcap_margins.position.y = subtag_b[1].to_float();
- dropcap_margins.size.x = subtag_b[2].to_float();
- dropcap_margins.size.y = subtag_b[3].to_float();
- }
- } else if (subtag_a[0] == "outline_size") {
- outline_size = subtag_a[1].to_int();
- } else if (subtag_a[0] == "color") {
- color = Color::from_string(subtag_a[1], color);
- } else if (subtag_a[0] == "outline_color") {
- outline_color = Color::from_string(subtag_a[1], outline_color);
- }
+ if (subtag_b.size() == 4) {
+ dropcap_margins.position.x = subtag_b[0].to_float();
+ dropcap_margins.position.y = subtag_b[1].to_float();
+ dropcap_margins.size.x = subtag_b[2].to_float();
+ dropcap_margins.size.y = subtag_b[3].to_float();
}
}
- int end = p_bbcode.find("[", brk_end);
+ OptionMap::Iterator outline_size_option = bbcode_options.find("outline_size");
+ if (outline_size_option) {
+ outline_size = outline_size_option->value.to_int();
+ }
+ OptionMap::Iterator color_option = bbcode_options.find("color");
+ if (color_option) {
+ color = Color::from_string(color_option->value, color);
+ }
+ OptionMap::Iterator outline_color_option = bbcode_options.find("outline_color");
+ if (outline_color_option) {
+ outline_color = Color::from_string(outline_color_option->value, outline_color);
+ }
+
+ int end = p_bbcode.find_char('[', brk_end);
if (end == -1) {
end = p_bbcode.length();
}
@@ -4669,7 +4754,7 @@ void RichTextLabel::append_text(const String &p_bbcode) {
} else if (tag.begins_with("img")) {
int alignment = INLINE_ALIGNMENT_CENTER;
if (tag.begins_with("img=")) {
- Vector<String> subtag = tag.substr(4, tag.length()).split(",");
+ Vector<String> subtag = _split_unquoted(_get_tag_value(tag), U',');
_normalize_subtags(subtag);
if (subtag.size() > 1) {
@@ -4700,7 +4785,7 @@ void RichTextLabel::append_text(const String &p_bbcode) {
}
}
- int end = p_bbcode.find("[", brk_end);
+ int end = p_bbcode.find_char('[', brk_end);
if (end == -1) {
end = p_bbcode.length();
}
@@ -4712,7 +4797,7 @@ void RichTextLabel::append_text(const String &p_bbcode) {
Rect2 region;
OptionMap::Iterator region_option = bbcode_options.find("region");
if (region_option) {
- Vector<String> region_values = region_option->value.split(",", false);
+ Vector<String> region_values = _split_unquoted(region_option->value, U',');
if (region_values.size() == 4) {
region.position.x = region_values[0].to_float();
region.position.y = region_values[1].to_float();
@@ -4774,27 +4859,27 @@ void RichTextLabel::append_text(const String &p_bbcode) {
pos = end;
tag_stack.push_front(bbcode_name);
} else if (tag.begins_with("color=")) {
- String color_str = tag.substr(6, tag.length()).unquote();
+ String color_str = _get_tag_value(tag).unquote();
Color color = Color::from_string(color_str, theme_cache.default_color);
push_color(color);
pos = brk_end + 1;
tag_stack.push_front("color");
} else if (tag.begins_with("outline_color=")) {
- String color_str = tag.substr(14, tag.length()).unquote();
+ String color_str = _get_tag_value(tag).unquote();
Color color = Color::from_string(color_str, theme_cache.default_color);
push_outline_color(color);
pos = brk_end + 1;
tag_stack.push_front("outline_color");
} else if (tag.begins_with("font_size=")) {
- int fnt_size = tag.substr(10, tag.length()).to_int();
+ int fnt_size = _get_tag_value(tag).to_int();
push_font_size(fnt_size);
pos = brk_end + 1;
tag_stack.push_front("font_size");
} else if (tag.begins_with("opentype_features=") || tag.begins_with("otf=")) {
- int value_pos = tag.find("=");
+ int value_pos = tag.find_char('=');
String fnt_ftr = tag.substr(value_pos + 1);
Vector<String> subtag = fnt_ftr.split(",");
_normalize_subtags(subtag);
@@ -4838,7 +4923,7 @@ void RichTextLabel::append_text(const String &p_bbcode) {
tag_stack.push_front(tag.substr(0, value_pos));
} else if (tag.begins_with("font=")) {
- String fnt = tag.substr(5, tag.length()).unquote();
+ String fnt = _get_tag_value(tag).unquote();
Ref<Font> fc = ResourceLoader::load(fnt, "Font");
if (fc.is_valid()) {
@@ -4849,11 +4934,9 @@ void RichTextLabel::append_text(const String &p_bbcode) {
tag_stack.push_front("font");
} else if (tag.begins_with("font ")) {
- Vector<String> subtag = tag.substr(2, tag.length()).split(" ");
- _normalize_subtags(subtag);
-
Ref<Font> font = theme_cache.normal_font;
DefaultFont def_font = NORMAL_FONT;
+ int fnt_size = -1;
ItemFont *font_it = _find_font(current);
if (font_it) {
@@ -4866,75 +4949,122 @@ void RichTextLabel::append_text(const String &p_bbcode) {
Ref<FontVariation> fc;
fc.instantiate();
- int fnt_size = -1;
- for (int i = 1; i < subtag.size(); i++) {
- Vector<String> subtag_a = subtag[i].split("=", true, 1);
- _normalize_subtags(subtag_a);
-
- if (subtag_a.size() == 2) {
- if (subtag_a[0] == "name" || subtag_a[0] == "n") {
- const String &fnt = subtag_a[1];
- Ref<Font> font_data = ResourceLoader::load(fnt, "Font");
- if (font_data.is_valid()) {
- font = font_data;
- def_font = CUSTOM_FONT;
- }
- } else if (subtag_a[0] == "size" || subtag_a[0] == "s") {
- fnt_size = subtag_a[1].to_int();
- } else if (subtag_a[0] == "glyph_spacing" || subtag_a[0] == "gl") {
- int spacing = subtag_a[1].to_int();
- fc->set_spacing(TextServer::SPACING_GLYPH, spacing);
- } else if (subtag_a[0] == "space_spacing" || subtag_a[0] == "sp") {
- int spacing = subtag_a[1].to_int();
- fc->set_spacing(TextServer::SPACING_SPACE, spacing);
- } else if (subtag_a[0] == "top_spacing" || subtag_a[0] == "top") {
- int spacing = subtag_a[1].to_int();
- fc->set_spacing(TextServer::SPACING_TOP, spacing);
- } else if (subtag_a[0] == "bottom_spacing" || subtag_a[0] == "bt") {
- int spacing = subtag_a[1].to_int();
- fc->set_spacing(TextServer::SPACING_BOTTOM, spacing);
- } else if (subtag_a[0] == "embolden" || subtag_a[0] == "emb") {
- float emb = subtag_a[1].to_float();
- fc->set_variation_embolden(emb);
- } else if (subtag_a[0] == "face_index" || subtag_a[0] == "fi") {
- int fi = subtag_a[1].to_int();
- fc->set_variation_face_index(fi);
- } else if (subtag_a[0] == "slant" || subtag_a[0] == "sln") {
- float slant = subtag_a[1].to_float();
- fc->set_variation_transform(Transform2D(1.0, slant, 0.0, 1.0, 0.0, 0.0));
- } else if (subtag_a[0] == "opentype_variation" || subtag_a[0] == "otv") {
- Dictionary variations;
- if (!subtag_a[1].is_empty()) {
- Vector<String> variation_tags = subtag_a[1].split(",");
- for (int j = 0; j < variation_tags.size(); j++) {
- Vector<String> subtag_b = variation_tags[j].split("=");
- _normalize_subtags(subtag_b);
-
- if (subtag_b.size() == 2) {
- variations[TS->name_to_tag(subtag_b[0])] = subtag_b[1].to_float();
- }
- }
- fc->set_variation_opentype(variations);
+ OptionMap::Iterator name_option = bbcode_options.find("name");
+ if (!name_option) {
+ name_option = bbcode_options.find("n");
+ }
+ if (name_option) {
+ const String &fnt = name_option->value;
+ Ref<Font> font_data = ResourceLoader::load(fnt, "Font");
+ if (font_data.is_valid()) {
+ font = font_data;
+ def_font = CUSTOM_FONT;
+ }
+ }
+ OptionMap::Iterator size_option = bbcode_options.find("size");
+ if (!size_option) {
+ size_option = bbcode_options.find("s");
+ }
+ if (size_option) {
+ fnt_size = size_option->value.to_int();
+ }
+ OptionMap::Iterator glyph_spacing_option = bbcode_options.find("glyph_spacing");
+ if (!glyph_spacing_option) {
+ glyph_spacing_option = bbcode_options.find("gl");
+ }
+ if (glyph_spacing_option) {
+ int spacing = glyph_spacing_option->value.to_int();
+ fc->set_spacing(TextServer::SPACING_GLYPH, spacing);
+ }
+ OptionMap::Iterator space_spacing_option = bbcode_options.find("space_spacing");
+ if (!space_spacing_option) {
+ space_spacing_option = bbcode_options.find("sp");
+ }
+ if (space_spacing_option) {
+ int spacing = space_spacing_option->value.to_int();
+ fc->set_spacing(TextServer::SPACING_SPACE, spacing);
+ }
+ OptionMap::Iterator top_spacing_option = bbcode_options.find("top_spacing");
+ if (!top_spacing_option) {
+ top_spacing_option = bbcode_options.find("top");
+ }
+ if (top_spacing_option) {
+ int spacing = top_spacing_option->value.to_int();
+ fc->set_spacing(TextServer::SPACING_TOP, spacing);
+ }
+ OptionMap::Iterator bottom_spacing_option = bbcode_options.find("bottom_spacing");
+ if (!bottom_spacing_option) {
+ bottom_spacing_option = bbcode_options.find("bt");
+ }
+ if (bottom_spacing_option) {
+ int spacing = bottom_spacing_option->value.to_int();
+ fc->set_spacing(TextServer::SPACING_BOTTOM, spacing);
+ }
+ OptionMap::Iterator embolden_option = bbcode_options.find("embolden");
+ if (!embolden_option) {
+ embolden_option = bbcode_options.find("emb");
+ }
+ if (embolden_option) {
+ float emb = embolden_option->value.to_float();
+ fc->set_variation_embolden(emb);
+ }
+ OptionMap::Iterator face_index_option = bbcode_options.find("face_index");
+ if (!face_index_option) {
+ face_index_option = bbcode_options.find("fi");
+ }
+ if (face_index_option) {
+ int fi = face_index_option->value.to_int();
+ fc->set_variation_face_index(fi);
+ }
+ OptionMap::Iterator slant_option = bbcode_options.find("slant");
+ if (!slant_option) {
+ slant_option = bbcode_options.find("sln");
+ }
+ if (slant_option) {
+ float slant = slant_option->value.to_float();
+ fc->set_variation_transform(Transform2D(1.0, slant, 0.0, 1.0, 0.0, 0.0));
+ }
+ OptionMap::Iterator opentype_variation_option = bbcode_options.find("opentype_variation");
+ if (!opentype_variation_option) {
+ opentype_variation_option = bbcode_options.find("otv");
+ }
+ if (opentype_variation_option) {
+ Dictionary variations;
+ if (!opentype_variation_option->value.is_empty()) {
+ Vector<String> variation_tags = opentype_variation_option->value.split(",");
+ for (int j = 0; j < variation_tags.size(); j++) {
+ Vector<String> subtag_b = variation_tags[j].split("=");
+ _normalize_subtags(subtag_b);
+
+ if (subtag_b.size() == 2) {
+ variations[TS->name_to_tag(subtag_b[0])] = subtag_b[1].to_float();
}
- } else if (subtag_a[0] == "opentype_features" || subtag_a[0] == "otf") {
- Dictionary features;
- if (!subtag_a[1].is_empty()) {
- Vector<String> feature_tags = subtag_a[1].split(",");
- for (int j = 0; j < feature_tags.size(); j++) {
- Vector<String> subtag_b = feature_tags[j].split("=");
- _normalize_subtags(subtag_b);
-
- if (subtag_b.size() == 2) {
- features[TS->name_to_tag(subtag_b[0])] = subtag_b[1].to_float();
- } else if (subtag_b.size() == 1) {
- features[TS->name_to_tag(subtag_b[0])] = 1;
- }
- }
- fc->set_opentype_features(features);
+ }
+ fc->set_variation_opentype(variations);
+ }
+ }
+ OptionMap::Iterator opentype_features_option = bbcode_options.find("opentype_features");
+ if (!opentype_features_option) {
+ opentype_features_option = bbcode_options.find("otf");
+ }
+ if (opentype_features_option) {
+ Dictionary features;
+ if (!opentype_features_option->value.is_empty()) {
+ Vector<String> feature_tags = opentype_features_option->value.split(",");
+ for (int j = 0; j < feature_tags.size(); j++) {
+ Vector<String> subtag_b = feature_tags[j].split("=");
+ _normalize_subtags(subtag_b);
+
+ if (subtag_b.size() == 2) {
+ features[TS->name_to_tag(subtag_b[0])] = subtag_b[1].to_float();
+ } else if (subtag_b.size() == 1) {
+ features[TS->name_to_tag(subtag_b[0])] = 1;
}
}
+ fc->set_opentype_features(features);
}
}
+
fc->set_base_font(font);
if (def_font != CUSTOM_FONT) {
@@ -4947,7 +5077,7 @@ void RichTextLabel::append_text(const String &p_bbcode) {
tag_stack.push_front("font");
} else if (tag.begins_with("outline_size=")) {
- int fnt_size = tag.substr(13, tag.length()).to_int();
+ int fnt_size = _get_tag_value(tag).to_int();
if (fnt_size > 0) {
push_outline_size(fnt_size);
}
@@ -5086,7 +5216,7 @@ void RichTextLabel::append_text(const String &p_bbcode) {
tag_stack.push_front("pulse");
set_process_internal(true);
} else if (tag.begins_with("bgcolor=")) {
- String color_str = tag.substr(8, tag.length()).unquote();
+ String color_str = _get_tag_value(tag).unquote();
Color color = Color::from_string(color_str, theme_cache.default_color);
push_bgcolor(color);
@@ -5094,7 +5224,7 @@ void RichTextLabel::append_text(const String &p_bbcode) {
tag_stack.push_front("bgcolor");
} else if (tag.begins_with("fgcolor=")) {
- String color_str = tag.substr(8, tag.length()).unquote();
+ String color_str = _get_tag_value(tag).unquote();
Color color = Color::from_string(color_str, theme_cache.default_color);
push_fgcolor(color);
@@ -5734,19 +5864,89 @@ void RichTextLabel::set_text_direction(Control::TextDirection p_text_direction)
if (text_direction != p_text_direction) {
text_direction = p_text_direction;
- main->first_invalid_line.store(0); //invalidate ALL
- _validate_line_caches();
+ if (!text.is_empty()) {
+ _apply_translation();
+ } else {
+ main->first_invalid_line.store(0); // Invalidate all lines.
+ _validate_line_caches();
+ }
+ queue_redraw();
+ }
+}
+
+Control::TextDirection RichTextLabel::get_text_direction() const {
+ return text_direction;
+}
+
+void RichTextLabel::set_horizontal_alignment(HorizontalAlignment p_alignment) {
+ ERR_FAIL_INDEX((int)p_alignment, 4);
+ _stop_thread();
+
+ if (default_alignment != p_alignment) {
+ default_alignment = p_alignment;
+ if (!text.is_empty()) {
+ _apply_translation();
+ } else {
+ main->first_invalid_line.store(0); // Invalidate all lines.
+ _validate_line_caches();
+ }
+ queue_redraw();
+ }
+}
+
+HorizontalAlignment RichTextLabel::get_horizontal_alignment() const {
+ return default_alignment;
+}
+
+void RichTextLabel::set_justification_flags(BitField<TextServer::JustificationFlag> p_flags) {
+ _stop_thread();
+
+ if (default_jst_flags != p_flags) {
+ default_jst_flags = p_flags;
+ if (!text.is_empty()) {
+ _apply_translation();
+ } else {
+ main->first_invalid_line.store(0); // Invalidate all lines.
+ _validate_line_caches();
+ }
queue_redraw();
}
}
+BitField<TextServer::JustificationFlag> RichTextLabel::get_justification_flags() const {
+ return default_jst_flags;
+}
+
+void RichTextLabel::set_tab_stops(const PackedFloat32Array &p_tab_stops) {
+ _stop_thread();
+
+ if (default_tab_stops != p_tab_stops) {
+ default_tab_stops = p_tab_stops;
+ if (!text.is_empty()) {
+ _apply_translation();
+ } else {
+ main->first_invalid_line.store(0); // Invalidate all lines.
+ _validate_line_caches();
+ }
+ queue_redraw();
+ }
+}
+
+PackedFloat32Array RichTextLabel::get_tab_stops() const {
+ return default_tab_stops;
+}
+
void RichTextLabel::set_structured_text_bidi_override(TextServer::StructuredTextParser p_parser) {
if (st_parser != p_parser) {
_stop_thread();
st_parser = p_parser;
- main->first_invalid_line.store(0); //invalidate ALL
- _validate_line_caches();
+ if (!text.is_empty()) {
+ _apply_translation();
+ } else {
+ main->first_invalid_line.store(0); // Invalidate all lines.
+ _validate_line_caches();
+ }
queue_redraw();
}
}
@@ -5760,7 +5960,7 @@ void RichTextLabel::set_structured_text_bidi_override_options(Array p_args) {
_stop_thread();
st_args = p_args;
- main->first_invalid_line.store(0); //invalidate ALL
+ main->first_invalid_line.store(0); // Invalidate all lines.
_validate_line_caches();
queue_redraw();
}
@@ -5770,17 +5970,17 @@ Array RichTextLabel::get_structured_text_bidi_override_options() const {
return st_args;
}
-Control::TextDirection RichTextLabel::get_text_direction() const {
- return text_direction;
-}
-
void RichTextLabel::set_language(const String &p_language) {
if (language != p_language) {
_stop_thread();
language = p_language;
- main->first_invalid_line.store(0); //invalidate ALL
- _validate_line_caches();
+ if (!text.is_empty()) {
+ _apply_translation();
+ } else {
+ main->first_invalid_line.store(0); // Invalidate all lines.
+ _validate_line_caches();
+ }
queue_redraw();
}
}
@@ -5794,7 +5994,7 @@ void RichTextLabel::set_autowrap_mode(TextServer::AutowrapMode p_mode) {
_stop_thread();
autowrap_mode = p_mode;
- main->first_invalid_line = 0; //invalidate ALL
+ main->first_invalid_line = 0; // Invalidate all lines.
_validate_line_caches();
queue_redraw();
}
@@ -5820,7 +6020,7 @@ void RichTextLabel::set_visible_ratio(float p_ratio) {
}
if (visible_chars_behavior == TextServer::VC_CHARS_BEFORE_SHAPING) {
- main->first_invalid_line.store(0); // Invalidate ALL.
+ main->first_invalid_line.store(0); // Invalidate all lines..
_validate_line_caches();
}
queue_redraw();
@@ -5948,6 +6148,13 @@ void RichTextLabel::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_language", "language"), &RichTextLabel::set_language);
ClassDB::bind_method(D_METHOD("get_language"), &RichTextLabel::get_language);
+ ClassDB::bind_method(D_METHOD("set_horizontal_alignment", "alignment"), &RichTextLabel::set_horizontal_alignment);
+ ClassDB::bind_method(D_METHOD("get_horizontal_alignment"), &RichTextLabel::get_horizontal_alignment);
+ ClassDB::bind_method(D_METHOD("set_justification_flags", "justification_flags"), &RichTextLabel::set_justification_flags);
+ ClassDB::bind_method(D_METHOD("get_justification_flags"), &RichTextLabel::get_justification_flags);
+ ClassDB::bind_method(D_METHOD("set_tab_stops", "tab_stops"), &RichTextLabel::set_tab_stops);
+ ClassDB::bind_method(D_METHOD("get_tab_stops"), &RichTextLabel::get_tab_stops);
+
ClassDB::bind_method(D_METHOD("set_autowrap_mode", "autowrap_mode"), &RichTextLabel::set_autowrap_mode);
ClassDB::bind_method(D_METHOD("get_autowrap_mode"), &RichTextLabel::get_autowrap_mode);
@@ -6068,6 +6275,10 @@ void RichTextLabel::_bind_methods() {
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "context_menu_enabled"), "set_context_menu_enabled", "is_context_menu_enabled");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "shortcut_keys_enabled"), "set_shortcut_keys_enabled", "is_shortcut_keys_enabled");
+ ADD_PROPERTY(PropertyInfo(Variant::INT, "horizontal_alignment", PROPERTY_HINT_ENUM, "Left,Center,Right,Fill"), "set_horizontal_alignment", "get_horizontal_alignment");
+ ADD_PROPERTY(PropertyInfo(Variant::INT, "justification_flags", PROPERTY_HINT_FLAGS, "Kashida Justification:1,Word Justification:2,Justify Only After Last Tab:8,Skip Last Line:32,Skip Last Line With Visible Characters:64,Do Not Skip Single Line:128"), "set_justification_flags", "get_justification_flags");
+ ADD_PROPERTY(PropertyInfo(Variant::PACKED_FLOAT32_ARRAY, "tab_stops"), "set_tab_stops", "get_tab_stops");
+
ADD_GROUP("Markup", "");
ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "custom_effects", PROPERTY_HINT_ARRAY_TYPE, MAKE_RESOURCE_TYPE_HINT("RichTextEffect"), (PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_SCRIPT_VARIABLE)), "set_effects", "get_effects");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "meta_underlined"), "set_meta_underline", "is_meta_underlined");
@@ -6170,7 +6381,7 @@ void RichTextLabel::set_visible_characters_behavior(TextServer::VisibleCharacter
_stop_thread();
visible_chars_behavior = p_behavior;
- main->first_invalid_line.store(0); //invalidate ALL
+ main->first_invalid_line.store(0); // Invalidate all lines.
_validate_line_caches();
queue_redraw();
}
@@ -6190,7 +6401,7 @@ void RichTextLabel::set_visible_characters(int p_visible) {
}
}
if (visible_chars_behavior == TextServer::VC_CHARS_BEFORE_SHAPING) {
- main->first_invalid_line.store(0); //invalidate ALL
+ main->first_invalid_line.store(0); // Invalidate all lines.
_validate_line_caches();
}
queue_redraw();
diff --git a/scene/gui/rich_text_label.h b/scene/gui/rich_text_label.h
index 9f81674454..a01da02b27 100644
--- a/scene/gui/rich_text_label.h
+++ b/scene/gui/rich_text_label.h
@@ -482,6 +482,7 @@ private:
HorizontalAlignment default_alignment = HORIZONTAL_ALIGNMENT_LEFT;
BitField<TextServer::JustificationFlag> default_jst_flags = TextServer::JUSTIFICATION_WORD_BOUND | TextServer::JUSTIFICATION_KASHIDA | TextServer::JUSTIFICATION_SKIP_LAST_LINE | TextServer::JUSTIFICATION_DO_NOT_SKIP_SINGLE_LINE;
+ PackedFloat32Array default_tab_stops;
ItemMeta *meta_hovering = nullptr;
Variant current_meta;
@@ -613,6 +614,10 @@ private:
String _get_prefix(Item *p_item, const Vector<int> &p_list_index, const Vector<ItemList *> &p_list_items);
+ static int _find_unquoted(const String &p_src, char32_t p_chr, int p_from);
+ static Vector<String> _split_unquoted(const String &p_src, char32_t p_splitter);
+ static String _get_tag_value(const String &p_tag);
+
#ifndef DISABLE_DEPRECATED
// Kept for compatibility from 3.x to 4.0.
bool _set(const StringName &p_name, const Variant &p_value);
@@ -808,6 +813,15 @@ public:
void set_text(const String &p_bbcode);
String get_text() const;
+ void set_horizontal_alignment(HorizontalAlignment p_alignment);
+ HorizontalAlignment get_horizontal_alignment() const;
+
+ void set_justification_flags(BitField<TextServer::JustificationFlag> p_flags);
+ BitField<TextServer::JustificationFlag> get_justification_flags() const;
+
+ void set_tab_stops(const PackedFloat32Array &p_tab_stops);
+ PackedFloat32Array get_tab_stops() const;
+
void set_text_direction(TextDirection p_text_direction);
TextDirection get_text_direction() const;
diff --git a/scene/gui/spin_box.cpp b/scene/gui/spin_box.cpp
index 4212cd709f..ac81f0de56 100644
--- a/scene/gui/spin_box.cpp
+++ b/scene/gui/spin_box.cpp
@@ -287,7 +287,12 @@ inline void SpinBox::_compute_sizes() {
int buttons_block_wanted_width = theme_cache.buttons_width + theme_cache.field_and_buttons_separation;
int buttons_block_icon_enforced_width = _get_widest_button_icon_width() + theme_cache.field_and_buttons_separation;
- int w = theme_cache.set_min_buttons_width_from_icons != 0 ? MAX(buttons_block_icon_enforced_width, buttons_block_wanted_width) : buttons_block_wanted_width;
+#ifndef DISABLE_DEPRECATED
+ const bool min_width_from_icons = theme_cache.set_min_buttons_width_from_icons || (theme_cache.buttons_width < 0);
+#else
+ const bool min_width_from_icons = theme_cache.buttons_width < 0;
+#endif
+ int w = min_width_from_icons != 0 ? MAX(buttons_block_icon_enforced_width, buttons_block_wanted_width) : buttons_block_wanted_width;
if (w != sizing_cache.buttons_block_width) {
line_edit->set_offset(SIDE_LEFT, 0);
@@ -551,7 +556,9 @@ void SpinBox::_bind_methods() {
BIND_THEME_ITEM(Theme::DATA_TYPE_CONSTANT, SpinBox, buttons_vertical_separation);
BIND_THEME_ITEM(Theme::DATA_TYPE_CONSTANT, SpinBox, field_and_buttons_separation);
BIND_THEME_ITEM(Theme::DATA_TYPE_CONSTANT, SpinBox, buttons_width);
+#ifndef DISABLE_DEPRECATED
BIND_THEME_ITEM(Theme::DATA_TYPE_CONSTANT, SpinBox, set_min_buttons_width_from_icons);
+#endif
BIND_THEME_ITEM_CUSTOM(Theme::DATA_TYPE_ICON, SpinBox, updown_icon, "updown");
BIND_THEME_ITEM_CUSTOM(Theme::DATA_TYPE_ICON, SpinBox, up_icon, "up");
diff --git a/scene/gui/spin_box.h b/scene/gui/spin_box.h
index 7c6974f6a8..592805f43a 100644
--- a/scene/gui/spin_box.h
+++ b/scene/gui/spin_box.h
@@ -127,8 +127,9 @@ class SpinBox : public Range {
int buttons_vertical_separation = 0;
int field_and_buttons_separation = 0;
int buttons_width = 0;
- int set_min_buttons_width_from_icons = 0;
-
+#ifndef DISABLE_DEPRECATED
+ bool set_min_buttons_width_from_icons = false;
+#endif
} theme_cache;
void _mouse_exited();
diff --git a/scene/gui/subviewport_container.cpp b/scene/gui/subviewport_container.cpp
index c715aceb0b..a443ae9abf 100644
--- a/scene/gui/subviewport_container.cpp
+++ b/scene/gui/subviewport_container.cpp
@@ -259,7 +259,7 @@ void SubViewportContainer::remove_child_notify(Node *p_child) {
}
PackedStringArray SubViewportContainer::get_configuration_warnings() const {
- PackedStringArray warnings = Node::get_configuration_warnings();
+ PackedStringArray warnings = Container::get_configuration_warnings();
bool has_viewport = false;
for (int i = 0; i < get_child_count(); i++) {
diff --git a/scene/gui/text_edit.cpp b/scene/gui/text_edit.cpp
index b6835541bf..ab0ad2f4b7 100644
--- a/scene/gui/text_edit.cpp
+++ b/scene/gui/text_edit.cpp
@@ -112,8 +112,34 @@ int TextEdit::Text::get_line_width(int p_line, int p_wrap_index) const {
return text[p_line].data_buf->get_size().x;
}
+int TextEdit::Text::get_max_width() const {
+ if (max_line_width_dirty) {
+ int new_max_line_width = 0;
+ for (const Line &l : text) {
+ if (l.hidden) {
+ continue;
+ }
+ new_max_line_width = MAX(new_max_line_width, l.width);
+ }
+ max_line_width = new_max_line_width;
+ }
+
+ return max_line_width;
+}
+
int TextEdit::Text::get_line_height() const {
- return line_height;
+ if (max_line_height_dirty) {
+ int new_max_line_height = 0;
+ for (const Line &l : text) {
+ if (l.hidden) {
+ continue;
+ }
+ new_max_line_height = MAX(new_max_line_height, l.height);
+ }
+ max_line_height = new_max_line_height;
+ }
+
+ return max_line_height;
}
void TextEdit::Text::set_width(float p_width) {
@@ -135,15 +161,17 @@ BitField<TextServer::LineBreakFlag> TextEdit::Text::get_brk_flags() const {
int TextEdit::Text::get_line_wrap_amount(int p_line) const {
ERR_FAIL_INDEX_V(p_line, text.size(), 0);
- return text[p_line].data_buf->get_line_count() - 1;
+ return text[p_line].line_count - 1;
}
Vector<Vector2i> TextEdit::Text::get_line_wrap_ranges(int p_line) const {
Vector<Vector2i> ret;
ERR_FAIL_INDEX_V(p_line, text.size(), ret);
- for (int i = 0; i < text[p_line].data_buf->get_line_count(); i++) {
- ret.push_back(text[p_line].data_buf->get_line_range(i));
+ Ref<TextParagraph> data_buf = text[p_line].data_buf;
+ int line_count = data_buf->get_line_count();
+ for (int i = 0; i < line_count; i++) {
+ ret.push_back(data_buf->get_line_range(i));
}
return ret;
}
@@ -153,40 +181,11 @@ const Ref<TextParagraph> TextEdit::Text::get_line_data(int p_line) const {
return text[p_line].data_buf;
}
-_FORCE_INLINE_ const String &TextEdit::Text::operator[](int p_line) const {
+_FORCE_INLINE_ String TextEdit::Text::operator[](int p_line) const {
+ ERR_FAIL_INDEX_V(p_line, text.size(), "");
return text[p_line].data;
}
-void TextEdit::Text::_calculate_line_height() {
- int height = 0;
- for (const Line &l : text) {
- // Found another line with the same height...nothing to update.
- if (l.height == line_height) {
- height = line_height;
- break;
- }
- height = MAX(height, l.height);
- }
- line_height = height;
-}
-
-void TextEdit::Text::_calculate_max_line_width() {
- int line_width = 0;
- for (const Line &l : text) {
- if (l.hidden) {
- continue;
- }
-
- // Found another line with the same width...nothing to update.
- if (l.width == max_width) {
- line_width = max_width;
- break;
- }
- line_width = MAX(line_width, l.width);
- }
- max_width = line_width;
-}
-
void TextEdit::Text::invalidate_cache(int p_line, int p_column, bool p_text_changed, const String &p_ime_text, const Array &p_bidi_override) {
ERR_FAIL_INDEX(p_line, text.size());
@@ -194,8 +193,9 @@ void TextEdit::Text::invalidate_cache(int p_line, int p_column, bool p_text_chan
return; // Not in tree?
}
+ Line &text_line = text.write[p_line];
if (p_text_changed) {
- text.write[p_line].data_buf->clear();
+ text_line.data_buf->clear();
}
BitField<TextServer::LineBreakFlag> flags = brk_flags;
@@ -203,30 +203,30 @@ void TextEdit::Text::invalidate_cache(int p_line, int p_column, bool p_text_chan
flags.set_flag(TextServer::BREAK_TRIM_INDENT);
}
- text.write[p_line].data_buf->set_width(width);
- text.write[p_line].data_buf->set_direction((TextServer::Direction)direction);
- text.write[p_line].data_buf->set_break_flags(flags);
- text.write[p_line].data_buf->set_preserve_control(draw_control_chars);
- text.write[p_line].data_buf->set_custom_punctuation(get_enabled_word_separators());
+ text_line.data_buf->set_width(width);
+ text_line.data_buf->set_direction((TextServer::Direction)direction);
+ text_line.data_buf->set_break_flags(flags);
+ text_line.data_buf->set_preserve_control(draw_control_chars);
+ text_line.data_buf->set_custom_punctuation(get_enabled_word_separators());
if (p_ime_text.length() > 0) {
if (p_text_changed) {
- text.write[p_line].data_buf->add_string(p_ime_text, font, font_size, language);
+ text_line.data_buf->add_string(p_ime_text, font, font_size, language);
}
if (!p_bidi_override.is_empty()) {
- TS->shaped_text_set_bidi_override(text.write[p_line].data_buf->get_rid(), p_bidi_override);
+ TS->shaped_text_set_bidi_override(text_line.data_buf->get_rid(), p_bidi_override);
}
} else {
if (p_text_changed) {
- text.write[p_line].data_buf->add_string(text[p_line].data, font, font_size, language);
+ text_line.data_buf->add_string(text_line.data, font, font_size, language);
}
- if (!text[p_line].bidi_override.is_empty()) {
- TS->shaped_text_set_bidi_override(text.write[p_line].data_buf->get_rid(), text[p_line].bidi_override);
+ if (!text_line.bidi_override.is_empty()) {
+ TS->shaped_text_set_bidi_override(text_line.data_buf->get_rid(), text_line.bidi_override);
}
}
if (!p_text_changed) {
- RID r = text.write[p_line].data_buf->get_rid();
+ RID r = text_line.data_buf->get_rid();
int spans = TS->shaped_get_span_count(r);
for (int i = 0; i < spans; i++) {
TS->shaped_set_span_update_font(r, i, font->get_rids(), font_size, font->get_opentype_features());
@@ -237,61 +237,58 @@ void TextEdit::Text::invalidate_cache(int p_line, int p_column, bool p_text_chan
if (tab_size > 0) {
Vector<float> tabs;
tabs.push_back(font->get_char_size(' ', font_size).width * tab_size);
- text.write[p_line].data_buf->tab_align(tabs);
+ text_line.data_buf->tab_align(tabs);
+ }
+
+ // Update wrap amount.
+ const int old_line_count = text_line.line_count;
+ text_line.line_count = text_line.data_buf->get_line_count();
+ if (!text_line.hidden && text_line.line_count != old_line_count) {
+ total_visible_line_count += text_line.line_count - old_line_count;
}
// Update height.
- const int old_height = text.write[p_line].height;
- const int wrap_amount = get_line_wrap_amount(p_line);
- int height = font_height;
- for (int i = 0; i <= wrap_amount; i++) {
- height = MAX(height, text[p_line].data_buf->get_line_size(i).y);
+ const int old_height = text_line.height;
+ text_line.height = font_height;
+ for (int i = 0; i < text_line.line_count; i++) {
+ text_line.height = MAX(text_line.height, text_line.data_buf->get_line_size(i).y);
}
- text.write[p_line].height = height;
- // If this line has shrunk, this may no longer the tallest line.
- if (old_height == line_height && height < line_height) {
- _calculate_line_height();
- } else {
- line_height = MAX(height, line_height);
+ // If this line has shrunk, this may no longer be the tallest line.
+ if (!text_line.hidden) {
+ if (old_height == max_line_height && text_line.height < old_height) {
+ max_line_height_dirty = true;
+ } else {
+ max_line_height = MAX(text_line.height, max_line_height);
+ }
}
// Update width.
- const int old_width = text.write[p_line].width;
- int line_width = get_line_width(p_line);
- text.write[p_line].width = line_width;
+ const int old_width = text_line.width;
+ text_line.width = get_line_width(p_line);
- // If this line has shrunk, this may no longer the longest line.
- if (old_width == max_width && line_width < max_width) {
- _calculate_max_line_width();
- } else if (!is_hidden(p_line)) {
- max_width = MAX(line_width, max_width);
+ if (!text_line.hidden) {
+ // If this line has shrunk, this may no longer be the longest line.
+ if (old_width == max_line_width && text_line.width < old_width) {
+ max_line_width_dirty = true;
+ } else {
+ max_line_width = MAX(text_line.width, max_line_width);
+ }
}
}
void TextEdit::Text::invalidate_all_lines() {
for (int i = 0; i < text.size(); i++) {
- BitField<TextServer::LineBreakFlag> flags = brk_flags;
- if (indent_wrapped_lines) {
- flags.set_flag(TextServer::BREAK_TRIM_INDENT);
- }
- text.write[i].data_buf->set_width(width);
- text.write[i].data_buf->set_break_flags(flags);
- text.write[i].data_buf->set_custom_punctuation(get_enabled_word_separators());
-
if (tab_size_dirty) {
if (tab_size > 0) {
Vector<float> tabs;
tabs.push_back(font->get_char_size(' ', font_size).width * tab_size);
- text.write[i].data_buf->tab_align(tabs);
+ text[i].data_buf->tab_align(tabs);
}
}
- text.write[i].width = get_line_width(i);
+ invalidate_cache(i, -1, false);
}
tab_size_dirty = false;
-
- max_width = -1;
- _calculate_max_line_width();
}
void TextEdit::Text::invalidate_font() {
@@ -299,8 +296,8 @@ void TextEdit::Text::invalidate_font() {
return;
}
- max_width = -1;
- line_height = -1;
+ max_line_width_dirty = true;
+ max_line_height_dirty = true;
if (font.is_valid() && font_size > 0) {
font_height = font->get_height(font_size);
@@ -317,8 +314,8 @@ void TextEdit::Text::invalidate_all() {
return;
}
- max_width = -1;
- line_height = -1;
+ max_line_width_dirty = true;
+ max_line_height_dirty = true;
if (font.is_valid() && font_size > 0) {
font_height = font->get_height(font_size);
@@ -333,8 +330,8 @@ void TextEdit::Text::invalidate_all() {
void TextEdit::Text::clear() {
text.clear();
- max_width = -1;
- line_height = -1;
+ max_line_width_dirty = true;
+ max_line_height_dirty = true;
Line line;
line.gutters.resize(gutter_count);
@@ -343,8 +340,8 @@ void TextEdit::Text::clear() {
invalidate_cache(0, -1, true);
}
-int TextEdit::Text::get_max_width() const {
- return max_width;
+int TextEdit::Text::get_total_visible_line_count() const {
+ return total_visible_line_count;
}
void TextEdit::Text::set(int p_line, const String &p_text, const Array &p_bidi_override) {
@@ -355,7 +352,37 @@ void TextEdit::Text::set(int p_line, const String &p_text, const Array &p_bidi_o
invalidate_cache(p_line, -1, true);
}
+void TextEdit::Text::set_hidden(int p_line, bool p_hidden) {
+ ERR_FAIL_INDEX(p_line, text.size());
+
+ Line &text_line = text.write[p_line];
+ if (text_line.hidden == p_hidden) {
+ return;
+ }
+ text_line.hidden = p_hidden;
+ if (p_hidden) {
+ total_visible_line_count -= text_line.line_count;
+ if (text_line.width == max_line_width) {
+ max_line_width_dirty = true;
+ }
+ if (text_line.height == max_line_height) {
+ max_line_height_dirty = true;
+ }
+ } else {
+ total_visible_line_count += text_line.line_count;
+ max_line_width = MAX(text_line.width, max_line_width);
+ max_line_height = MAX(text_line.height, max_line_height);
+ }
+}
+
+bool TextEdit::Text::is_hidden(int p_line) const {
+ ERR_FAIL_INDEX_V(p_line, text.size(), true);
+ return text[p_line].hidden;
+}
+
void TextEdit::Text::insert(int p_at, const Vector<String> &p_text, const Vector<Array> &p_bidi_override) {
+ ERR_FAIL_INDEX(p_at, text.size() + 1);
+
int new_line_count = p_text.size() - 1;
if (new_line_count > 0) {
text.resize(text.size() + new_line_count);
@@ -382,24 +409,25 @@ void TextEdit::Text::insert(int p_at, const Vector<String> &p_text, const Vector
}
void TextEdit::Text::remove_range(int p_from_line, int p_to_line) {
+ p_from_line = MAX(p_from_line, 0);
+ ERR_FAIL_INDEX(p_from_line, text.size());
+
+ p_to_line = MIN(p_to_line, text.size());
+ ERR_FAIL_COND(p_to_line < p_from_line);
+
if (p_from_line == p_to_line) {
return;
}
- bool dirty_height = false;
- bool dirty_width = false;
for (int i = p_from_line; i < p_to_line; i++) {
- if (!dirty_height && text[i].height == line_height) {
- dirty_height = true;
+ const Line &text_line = text[i];
+ if (text_line.height == max_line_height) {
+ max_line_height_dirty = true;
}
-
- if (!dirty_width && text[i].width == max_width) {
- dirty_width = true;
- }
-
- if (dirty_height && dirty_width) {
- break;
+ if (text_line.width == max_line_width) {
+ max_line_width_dirty = true;
}
+ total_visible_line_count -= text_line.line_count;
}
int diff = (p_to_line - p_from_line);
@@ -407,16 +435,6 @@ void TextEdit::Text::remove_range(int p_from_line, int p_to_line) {
text.write[(i - diff) + 1] = text[i + 1];
}
text.resize(text.size() - diff);
-
- if (dirty_height) {
- line_height = -1;
- _calculate_line_height();
- }
-
- if (dirty_width) {
- max_width = -1;
- _calculate_max_line_width();
- }
}
void TextEdit::Text::add_gutter(int p_at) {
@@ -431,6 +449,8 @@ void TextEdit::Text::add_gutter(int p_at) {
}
void TextEdit::Text::remove_gutter(int p_gutter) {
+ ERR_FAIL_INDEX(p_gutter, text.size());
+
for (int i = 0; i < text.size(); i++) {
text.write[i].gutters.remove_at(p_gutter);
}
@@ -438,6 +458,9 @@ void TextEdit::Text::remove_gutter(int p_gutter) {
}
void TextEdit::Text::move_gutters(int p_from_line, int p_to_line) {
+ ERR_FAIL_INDEX(p_from_line, text.size());
+ ERR_FAIL_INDEX(p_to_line, text.size());
+
text.write[p_to_line].gutters = text[p_from_line].gutters;
text.write[p_from_line].gutters.clear();
text.write[p_from_line].gutters.resize(gutter_count);
@@ -625,6 +648,8 @@ void TextEdit::_notification(int p_what) {
brace_matching.resize(get_caret_count());
for (int caret = 0; caret < get_caret_count(); caret++) {
+ BraceMatchingData &brace_match = brace_matching.write[caret];
+
if (get_caret_line(caret) < 0 || get_caret_line(caret) >= text.size() || get_caret_column(caret) < 0) {
continue;
}
@@ -678,20 +703,20 @@ void TextEdit::_notification(int p_what) {
}
if (stack == 0) {
- brace_matching.write[caret].open_match_line = i;
- brace_matching.write[caret].open_match_column = j;
- brace_matching.write[caret].open_matching = true;
+ brace_match.open_match_line = i;
+ brace_match.open_match_column = j;
+ brace_match.open_matching = true;
break;
}
}
- if (brace_matching.write[caret].open_match_line != -1) {
+ if (brace_match.open_match_line != -1) {
break;
}
}
- if (!brace_matching.write[caret].open_matching) {
- brace_matching.write[caret].open_mismatch = true;
+ if (!brace_match.open_matching) {
+ brace_match.open_mismatch = true;
}
}
}
@@ -744,20 +769,20 @@ void TextEdit::_notification(int p_what) {
}
if (stack == 0) {
- brace_matching.write[caret].close_match_line = i;
- brace_matching.write[caret].close_match_column = j;
- brace_matching.write[caret].close_matching = true;
+ brace_match.close_match_line = i;
+ brace_match.close_match_column = j;
+ brace_match.close_matching = true;
break;
}
}
- if (brace_matching.write[caret].close_match_line != -1) {
+ if (brace_match.close_match_line != -1) {
break;
}
}
- if (!brace_matching.write[caret].close_matching) {
- brace_matching.write[caret].close_mismatch = true;
+ if (!brace_match.close_matching) {
+ brace_match.close_mismatch = true;
}
}
}
@@ -772,13 +797,14 @@ void TextEdit::_notification(int p_what) {
// Check if highlighted words contain only whitespaces (tabs or spaces).
bool only_whitespaces_highlighted = highlighted_text.strip_edges().is_empty();
- HashMap<int, HashSet<int>> caret_line_wrap_index_map;
+ Vector<Pair<int, int>> highlighted_lines;
+ highlighted_lines.resize(carets.size());
Vector<int> carets_wrap_index;
carets_wrap_index.resize(carets.size());
for (int i = 0; i < carets.size(); i++) {
carets.write[i].visible = false;
int wrap_index = get_caret_wrap_index(i);
- caret_line_wrap_index_map[get_caret_line(i)].insert(wrap_index);
+ highlighted_lines.write[i] = Pair<int, int>(get_caret_line(i), wrap_index);
carets_wrap_index.write[i] = wrap_index;
}
@@ -842,7 +868,7 @@ void TextEdit::_notification(int p_what) {
break;
}
- Dictionary color_map = _get_line_syntax_highlighting(minimap_line);
+ const Vector<Pair<int64_t, Color>> color_map = _get_line_syntax_highlighting(minimap_line);
Color line_background_color = text.get_line_background_color(minimap_line);
@@ -853,12 +879,9 @@ void TextEdit::_notification(int p_what) {
line_background_color.a *= 0.6;
}
- Color current_color = theme_cache.font_color;
- if (!editable) {
- current_color = theme_cache.font_readonly_color;
- }
+ Color current_color = editable ? theme_cache.font_color : theme_cache.font_readonly_color;
- Vector<String> wrap_rows = get_line_wrapped_text(minimap_line);
+ const Vector<String> wrap_rows = get_line_wrapped_text(minimap_line);
int line_wrap_amount = get_line_wrap_count(minimap_line);
int last_wrap_column = 0;
@@ -881,13 +904,13 @@ void TextEdit::_notification(int p_what) {
last_wrap_column += wrap_rows[line_wrap_index - 1].length();
}
- if (caret_line_wrap_index_map.has(minimap_line) && caret_line_wrap_index_map[minimap_line].has(line_wrap_index) && highlight_current_line) {
+ if (highlight_current_line && highlighted_lines.has(Pair<int, int>(minimap_line, line_wrap_index))) {
if (rtl) {
RenderingServer::get_singleton()->canvas_item_add_rect(ci, Rect2(size.width - (xmargin_end + 2) - minimap_width, i * 3, minimap_width, 2), theme_cache.current_line_color);
} else {
RenderingServer::get_singleton()->canvas_item_add_rect(ci, Rect2((xmargin_end + 2), i * 3, minimap_width, 2), theme_cache.current_line_color);
}
- } else if (line_background_color != Color(0, 0, 0, 0)) {
+ } else if (line_background_color.a > 0) {
if (rtl) {
RenderingServer::get_singleton()->canvas_item_add_rect(ci, Rect2(size.width - (xmargin_end + 2) - minimap_width, i * 3, minimap_width, 2), line_background_color);
} else {
@@ -905,13 +928,17 @@ void TextEdit::_notification(int p_what) {
// Get the number of characters to draw together.
for (characters = 0; j + characters < str.length(); characters++) {
int next_char_index = j + characters;
- const Variant *color_data = color_map.getptr(last_wrap_column + next_char_index);
- if (color_data != nullptr) {
- next_color = (color_data->operator Dictionary()).get("color", theme_cache.font_color);
- if (!editable) {
- next_color.a = theme_cache.font_readonly_color.a;
+
+ for (const Pair<int64_t, Color> &color_data : color_map) {
+ if (last_wrap_column + next_char_index >= color_data.first) {
+ next_color = color_data.second;
+ if (!editable) {
+ next_color.a = theme_cache.font_readonly_color.a;
+ }
+ next_color.a *= 0.6;
+ } else {
+ break;
}
- next_color.a *= 0.6;
}
if (characters == 0) {
current_color = next_color;
@@ -1002,7 +1029,7 @@ void TextEdit::_notification(int p_what) {
LineDrawingCache cache_entry;
- Dictionary color_map = _get_line_syntax_highlighting(line);
+ const Vector<Pair<int64_t, Color>> color_map = _get_line_syntax_highlighting(line);
// Ensure we at least use the font color.
Color current_color = !editable ? theme_cache.font_readonly_color : theme_cache.font_color;
@@ -1012,7 +1039,7 @@ void TextEdit::_notification(int p_what) {
const Ref<TextParagraph> ldata = draw_placeholder ? placeholder_data_buf : text.get_line_data(line);
- Vector<String> wrap_rows = draw_placeholder ? placeholder_wraped_rows : get_line_wrapped_text(line);
+ const Vector<String> wrap_rows = draw_placeholder ? placeholder_wraped_rows : get_line_wrapped_text(line);
int line_wrap_amount = draw_placeholder ? placeholder_wraped_rows.size() - 1 : get_line_wrap_count(line);
for (int line_wrap_index = 0; line_wrap_index <= line_wrap_amount; line_wrap_index++) {
@@ -1053,20 +1080,20 @@ void TextEdit::_notification(int p_what) {
break;
}
- if (text.get_line_background_color(line) != Color(0, 0, 0, 0)) {
+ if (text.get_line_background_color(line).a > 0.0) {
if (rtl) {
- RenderingServer::get_singleton()->canvas_item_add_rect(ci, Rect2(size.width - ofs_x - xmargin_end, ofs_y, xmargin_end - xmargin_beg, row_height), text.get_line_background_color(line));
+ RS::get_singleton()->canvas_item_add_rect(ci, Rect2(size.width - ofs_x - xmargin_end, ofs_y, xmargin_end - xmargin_beg, row_height), text.get_line_background_color(line));
} else {
- RenderingServer::get_singleton()->canvas_item_add_rect(ci, Rect2(xmargin_beg + ofs_x, ofs_y, xmargin_end - xmargin_beg, row_height), text.get_line_background_color(line));
+ RS::get_singleton()->canvas_item_add_rect(ci, Rect2(xmargin_beg + ofs_x, ofs_y, xmargin_end - xmargin_beg, row_height), text.get_line_background_color(line));
}
}
// Draw current line highlight.
- if (highlight_current_line && caret_line_wrap_index_map.has(line) && caret_line_wrap_index_map[line].has(line_wrap_index)) {
+ if (highlight_current_line && highlighted_lines.has(Pair<int, int>(line, line_wrap_index))) {
if (rtl) {
- RenderingServer::get_singleton()->canvas_item_add_rect(ci, Rect2(size.width - ofs_x - xmargin_end, ofs_y, xmargin_end, row_height), theme_cache.current_line_color);
+ RS::get_singleton()->canvas_item_add_rect(ci, Rect2(size.width - ofs_x - xmargin_end, ofs_y, xmargin_end, row_height), theme_cache.current_line_color);
} else {
- RenderingServer::get_singleton()->canvas_item_add_rect(ci, Rect2(ofs_x, ofs_y, xmargin_end, row_height), theme_cache.current_line_color);
+ RS::get_singleton()->canvas_item_add_rect(ci, Rect2(ofs_x, ofs_y, xmargin_end, row_height), theme_cache.current_line_color);
}
}
@@ -1077,7 +1104,7 @@ void TextEdit::_notification(int p_what) {
int gutter_offset = theme_cache.style_normal->get_margin(SIDE_LEFT);
for (int g = 0; g < gutters.size(); g++) {
- const GutterInfo gutter = gutters[g];
+ const GutterInfo &gutter = gutters[g];
if (!gutter.draw || gutter.width <= 0) {
continue;
@@ -1184,7 +1211,7 @@ void TextEdit::_notification(int p_what) {
if (rect.position.x + rect.size.x > xmargin_end) {
rect.size.x = xmargin_end - rect.position.x;
}
- draw_rect(rect, theme_cache.selection_color, true);
+ RS::get_singleton()->canvas_item_add_rect(ci, rect, theme_cache.selection_color);
}
}
}
@@ -1194,7 +1221,7 @@ void TextEdit::_notification(int p_what) {
int search_text_col = _get_column_pos_of_word(search_text, str, search_flags, 0);
int search_text_len = search_text.length();
while (search_text_col != -1) {
- Vector<Vector2> sel = TS->shaped_text_get_selection(rid, search_text_col + start, search_text_col + search_text_len + start);
+ const Vector<Vector2> sel = TS->shaped_text_get_selection(rid, search_text_col + start, search_text_col + search_text_len + start);
for (int j = 0; j < sel.size(); j++) {
Rect2 rect = Rect2(sel[j].x + char_margin + ofs_x, ofs_y, sel[j].y - sel[j].x, row_height);
if (rect.position.x + rect.size.x <= xmargin_beg || rect.position.x > xmargin_end) {
@@ -1206,7 +1233,7 @@ void TextEdit::_notification(int p_what) {
} else if (rect.position.x + rect.size.x > xmargin_end) {
rect.size.x = xmargin_end - rect.position.x;
}
- draw_rect(rect, theme_cache.search_result_color, true);
+ RS::get_singleton()->canvas_item_add_rect(ci, rect, theme_cache.search_result_color);
draw_rect(rect, theme_cache.search_result_border_color, false);
}
@@ -1218,7 +1245,7 @@ void TextEdit::_notification(int p_what) {
int highlighted_text_col = _get_column_pos_of_word(highlighted_text, str, SEARCH_MATCH_CASE | SEARCH_WHOLE_WORDS, 0);
int highlighted_text_len = highlighted_text.length();
while (highlighted_text_col != -1) {
- Vector<Vector2> sel = TS->shaped_text_get_selection(rid, highlighted_text_col + start, highlighted_text_col + highlighted_text_len + start);
+ const Vector<Vector2> sel = TS->shaped_text_get_selection(rid, highlighted_text_col + start, highlighted_text_col + highlighted_text_len + start);
for (int j = 0; j < sel.size(); j++) {
Rect2 rect = Rect2(sel[j].x + char_margin + ofs_x, ofs_y, sel[j].y - sel[j].x, row_height);
if (rect.position.x + rect.size.x <= xmargin_beg || rect.position.x > xmargin_end) {
@@ -1230,7 +1257,7 @@ void TextEdit::_notification(int p_what) {
} else if (rect.position.x + rect.size.x > xmargin_end) {
rect.size.x = xmargin_end - rect.position.x;
}
- draw_rect(rect, theme_cache.word_highlighted_color);
+ RS::get_singleton()->canvas_item_add_rect(ci, rect, theme_cache.word_highlighted_color);
}
highlighted_text_col = _get_column_pos_of_word(highlighted_text, str, SEARCH_MATCH_CASE | SEARCH_WHOLE_WORDS, highlighted_text_col + highlighted_text_len);
@@ -1243,7 +1270,7 @@ void TextEdit::_notification(int p_what) {
int lookup_symbol_word_col = _get_column_pos_of_word(lookup_symbol_word, str, SEARCH_MATCH_CASE | SEARCH_WHOLE_WORDS, 0);
int lookup_symbol_word_len = lookup_symbol_word.length();
while (lookup_symbol_word_col != -1) {
- Vector<Vector2> sel = TS->shaped_text_get_selection(rid, lookup_symbol_word_col + start, lookup_symbol_word_col + lookup_symbol_word_len + start);
+ const Vector<Vector2> sel = TS->shaped_text_get_selection(rid, lookup_symbol_word_col + start, lookup_symbol_word_col + lookup_symbol_word_len + start);
for (int j = 0; j < sel.size(); j++) {
Rect2 rect = Rect2(sel[j].x + char_margin + ofs_x, ofs_y + (theme_cache.line_spacing / 2), sel[j].y - sel[j].x, row_height);
if (rect.position.x + rect.size.x <= xmargin_beg || rect.position.x > xmargin_end) {
@@ -1257,7 +1284,7 @@ void TextEdit::_notification(int p_what) {
}
rect.position.y += ceil(TS->shaped_text_get_ascent(rid)) + ceil(theme_cache.font->get_underline_position(theme_cache.font_size));
rect.size.y = MAX(1, theme_cache.font->get_underline_thickness(theme_cache.font_size));
- draw_rect(rect, highlight_underline_color);
+ RS::get_singleton()->canvas_item_add_rect(ci, rect, highlight_underline_color);
}
lookup_symbol_word_col = _get_column_pos_of_word(lookup_symbol_word, str, SEARCH_MATCH_CASE | SEARCH_WHOLE_WORDS, lookup_symbol_word_col + lookup_symbol_word_len);
@@ -1293,21 +1320,16 @@ void TextEdit::_notification(int p_what) {
char_ofs = 0;
}
for (int j = 0; j < gl_size; j++) {
- int64_t color_start = -1;
- for (const Variant *key = color_map.next(nullptr); key; key = color_map.next(key)) {
- if (int64_t(*key) <= glyphs[j].start) {
- color_start = *key;
+ for (const Pair<int64_t, Color> &color_data : color_map) {
+ if (color_data.first <= glyphs[j].start) {
+ current_color = color_data.second;
+ if (!editable && current_color.a > theme_cache.font_readonly_color.a) {
+ current_color.a = theme_cache.font_readonly_color.a;
+ }
} else {
break;
}
}
- const Variant *color_data = (color_start >= 0) ? color_map.getptr(color_start) : nullptr;
- if (color_data != nullptr) {
- current_color = (color_data->operator Dictionary()).get("color", theme_cache.font_color);
- if (!editable && current_color.a > theme_cache.font_readonly_color.a) {
- current_color.a = theme_cache.font_readonly_color.a;
- }
- }
Color gl_color = current_color;
for (int c = 0; c < get_caret_count(); c++) {
@@ -1325,22 +1347,23 @@ void TextEdit::_notification(int p_what) {
if (char_pos >= xmargin_beg) {
if (highlight_matching_braces_enabled) {
for (int c = 0; c < get_caret_count(); c++) {
- if ((brace_matching[c].open_match_line == line && brace_matching[c].open_match_column == glyphs[j].start) ||
- (get_caret_column(c) == glyphs[j].start && get_caret_line(c) == line && carets_wrap_index[c] == line_wrap_index && (brace_matching[c].open_matching || brace_matching[c].open_mismatch))) {
- if (brace_matching[c].open_mismatch) {
+ const BraceMatchingData &brace_match = brace_matching[c];
+ if ((brace_match.open_match_line == line && brace_match.open_match_column == glyphs[j].start) ||
+ (get_caret_column(c) == glyphs[j].start && get_caret_line(c) == line && carets_wrap_index[c] == line_wrap_index && (brace_match.open_matching || brace_match.open_mismatch))) {
+ if (brace_match.open_mismatch) {
gl_color = _get_brace_mismatch_color();
}
Rect2 rect = Rect2(char_pos, ofs_y + theme_cache.font->get_underline_position(theme_cache.font_size), glyphs[j].advance * glyphs[j].repeat, MAX(theme_cache.font->get_underline_thickness(theme_cache.font_size) * theme_cache.base_scale, 1));
- draw_rect(rect, gl_color);
+ RS::get_singleton()->canvas_item_add_rect(ci, rect, gl_color);
}
- if ((brace_matching[c].close_match_line == line && brace_matching[c].close_match_column == glyphs[j].start) ||
- (get_caret_column(c) == glyphs[j].start + 1 && get_caret_line(c) == line && carets_wrap_index[c] == line_wrap_index && (brace_matching[c].close_matching || brace_matching[c].close_mismatch))) {
- if (brace_matching[c].close_mismatch) {
+ if ((brace_match.close_match_line == line && brace_match.close_match_column == glyphs[j].start) ||
+ (get_caret_column(c) == glyphs[j].start + 1 && get_caret_line(c) == line && carets_wrap_index[c] == line_wrap_index && (brace_match.close_matching || brace_match.close_mismatch))) {
+ if (brace_match.close_mismatch) {
gl_color = _get_brace_mismatch_color();
}
Rect2 rect = Rect2(char_pos, ofs_y + theme_cache.font->get_underline_position(theme_cache.font_size), glyphs[j].advance * glyphs[j].repeat, MAX(theme_cache.font->get_underline_thickness(theme_cache.font_size) * theme_cache.base_scale, 1));
- draw_rect(rect, gl_color);
+ RS::get_singleton()->canvas_item_add_rect(ci, rect, gl_color);
}
}
}
@@ -1451,11 +1474,11 @@ void TextEdit::_notification(int p_what) {
// Draw split caret (leading part).
ts_caret.l_caret.position += Vector2(char_margin + ofs_x, ofs_y);
ts_caret.l_caret.size.x = caret_width;
- draw_rect(ts_caret.l_caret, theme_cache.caret_color);
+ RS::get_singleton()->canvas_item_add_rect(ci, ts_caret.l_caret, theme_cache.caret_color);
// Draw extra direction marker on top of split caret.
float d = (ts_caret.l_dir == TextServer::DIRECTION_LTR) ? 0.5 : -3;
Rect2 trect = Rect2(ts_caret.l_caret.position.x + d * caret_width, ts_caret.l_caret.position.y + ts_caret.l_caret.size.y - caret_width, 3 * caret_width, caret_width);
- RenderingServer::get_singleton()->canvas_item_add_rect(ci, trect, theme_cache.caret_color);
+ RS::get_singleton()->canvas_item_add_rect(ci, trect, theme_cache.caret_color);
}
} else { // End of the line.
if (gl_size > 0) {
@@ -1488,28 +1511,28 @@ void TextEdit::_notification(int p_what) {
// Draw extra marker on top of mid caret.
Rect2 trect = Rect2(ts_caret.l_caret.position.x - 2.5 * caret_width, ts_caret.l_caret.position.y, 6 * caret_width, caret_width);
trect.position += Vector2(char_margin + ofs_x, ofs_y);
- RenderingServer::get_singleton()->canvas_item_add_rect(ci, trect, theme_cache.caret_color);
+ RS::get_singleton()->canvas_item_add_rect(ci, trect, theme_cache.caret_color);
} else if (ts_caret.l_caret != Rect2() && ts_caret.t_caret != Rect2() && ts_caret.l_dir != ts_caret.t_dir) {
// Draw extra direction marker on top of split caret.
float d = (ts_caret.l_dir == TextServer::DIRECTION_LTR) ? 0.5 : -3;
Rect2 trect = Rect2(ts_caret.l_caret.position.x + d * caret_width, ts_caret.l_caret.position.y + ts_caret.l_caret.size.y - caret_width, 3 * caret_width, caret_width);
trect.position += Vector2(char_margin + ofs_x, ofs_y);
- RenderingServer::get_singleton()->canvas_item_add_rect(ci, trect, theme_cache.caret_color);
+ RS::get_singleton()->canvas_item_add_rect(ci, trect, theme_cache.caret_color);
d = (ts_caret.t_dir == TextServer::DIRECTION_LTR) ? 0.5 : -3;
trect = Rect2(ts_caret.t_caret.position.x + d * caret_width, ts_caret.t_caret.position.y, 3 * caret_width, caret_width);
trect.position += Vector2(char_margin + ofs_x, ofs_y);
- RenderingServer::get_singleton()->canvas_item_add_rect(ci, trect, theme_cache.caret_color);
+ RS::get_singleton()->canvas_item_add_rect(ci, trect, theme_cache.caret_color);
}
ts_caret.l_caret.position += Vector2(char_margin + ofs_x, ofs_y);
ts_caret.l_caret.size.x = caret_width;
- draw_rect(ts_caret.l_caret, theme_cache.caret_color);
+ RS::get_singleton()->canvas_item_add_rect(ci, ts_caret.l_caret, theme_cache.caret_color);
ts_caret.t_caret.position += Vector2(char_margin + ofs_x, ofs_y);
ts_caret.t_caret.size.x = caret_width;
- draw_rect(ts_caret.t_caret, theme_cache.caret_color);
+ RS::get_singleton()->canvas_item_add_rect(ci, ts_caret.t_caret, theme_cache.caret_color);
}
}
}
@@ -1517,7 +1540,7 @@ void TextEdit::_notification(int p_what) {
if (!ime_text.is_empty()) {
{
// IME Intermediate text range.
- Vector<Vector2> sel = TS->shaped_text_get_selection(rid, get_caret_column(c), get_caret_column(c) + ime_text.length());
+ const Vector<Vector2> sel = TS->shaped_text_get_selection(rid, get_caret_column(c), get_caret_column(c) + ime_text.length());
for (int j = 0; j < sel.size(); j++) {
Rect2 rect = Rect2(sel[j].x + char_margin + ofs_x, ofs_y, sel[j].y - sel[j].x, text_height);
if (rect.position.x + rect.size.x <= xmargin_beg || rect.position.x > xmargin_end) {
@@ -1536,7 +1559,7 @@ void TextEdit::_notification(int p_what) {
}
{
// IME caret.
- Vector<Vector2> sel = TS->shaped_text_get_selection(rid, get_caret_column(c) + ime_selection.x, get_caret_column(c) + ime_selection.x + ime_selection.y);
+ const Vector<Vector2> sel = TS->shaped_text_get_selection(rid, get_caret_column(c) + ime_selection.x, get_caret_column(c) + ime_selection.x + ime_selection.y);
for (int j = 0; j < sel.size(); j++) {
Rect2 rect = Rect2(sel[j].x + char_margin + ofs_x, ofs_y, sel[j].y - sel[j].x, text_height);
if (rect.position.x + rect.size.x <= xmargin_beg || rect.position.x > xmargin_end) {
@@ -1575,26 +1598,7 @@ void TextEdit::_notification(int p_what) {
draw_caret = true;
}
- _update_ime_window_position();
-
- if (DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_VIRTUAL_KEYBOARD) && virtual_keyboard_enabled) {
- int caret_start = -1;
- int caret_end = -1;
-
- if (!has_selection(0)) {
- String full_text = _base_get_text(0, 0, get_caret_line(), get_caret_column());
-
- caret_start = full_text.length();
- } else {
- String pre_text = _base_get_text(0, 0, get_selection_from_line(), get_selection_from_column());
- String post_text = get_selected_text(0);
-
- caret_start = pre_text.length();
- caret_end = caret_start + post_text.length();
- }
-
- DisplayServer::get_singleton()->virtual_keyboard_show(get_text(), get_global_rect(), DisplayServer::KEYBOARD_TYPE_MULTILINE, -1, caret_start, caret_end);
- }
+ _show_virtual_keyboard();
} break;
case NOTIFICATION_FOCUS_EXIT: {
@@ -1943,8 +1947,7 @@ void TextEdit::gui_input(const Ref<InputEvent> &p_gui_input) {
}
}
- // Notify to show soft keyboard.
- notification(NOTIFICATION_FOCUS_ENTER);
+ _show_virtual_keyboard();
}
}
@@ -2393,7 +2396,7 @@ void TextEdit::_move_caret_left(bool p_select, bool p_move_by_word) {
} else {
PackedInt32Array words = TS->shaped_text_get_word_breaks(text.get_line_data(get_caret_line(i))->get_rid());
if (words.is_empty() || cc <= words[0]) {
- // This solves the scenario where there are no words but glyfs that can be ignored.
+ // Move to the start when there are no more words.
cc = 0;
} else {
for (int j = words.size() - 2; j >= 0; j = j - 2) {
@@ -2450,7 +2453,7 @@ void TextEdit::_move_caret_right(bool p_select, bool p_move_by_word) {
} else {
PackedInt32Array words = TS->shaped_text_get_word_breaks(text.get_line_data(get_caret_line(i))->get_rid());
if (words.is_empty() || cc >= words[words.size() - 1]) {
- // This solves the scenario where there are no words but glyfs that can be ignored.
+ // Move to the end when there are no more words.
cc = text[get_caret_line(i)].length();
} else {
for (int j = 1; j < words.size(); j = j + 2) {
@@ -2666,7 +2669,7 @@ void TextEdit::_do_backspace(bool p_word, bool p_all_to_left) {
// Get a list with the indices of the word bounds of the given text line.
const PackedInt32Array words = TS->shaped_text_get_word_breaks(text.get_line_data(get_caret_line(caret_index))->get_rid());
if (words.is_empty() || column <= words[0]) {
- // If "words" is empty, meaning no words are left, we can remove everything until the beginning of the line.
+ // Delete to the start when there are no more words.
column = 0;
} else {
// Otherwise search for the first word break that is smaller than the index from we're currently deleting.
@@ -2731,10 +2734,15 @@ void TextEdit::_delete(bool p_word, bool p_all_to_right) {
int column = get_caret_column(caret_index);
PackedInt32Array words = TS->shaped_text_get_word_breaks(text.get_line_data(line)->get_rid());
- for (int j = 1; j < words.size(); j = j + 2) {
- if (words[j] > column) {
- column = words[j];
- break;
+ if (words.is_empty() || column >= words[words.size() - 1]) {
+ // Delete to the end when there are no more words.
+ column = text[get_caret_line(i)].length();
+ } else {
+ for (int j = 1; j < words.size(); j = j + 2) {
+ if (words[j] > column) {
+ column = words[j];
+ break;
+ }
}
}
@@ -2924,6 +2932,29 @@ void TextEdit::_update_ime_text() {
queue_redraw();
}
+void TextEdit::_show_virtual_keyboard() {
+ _update_ime_window_position();
+
+ if (virtual_keyboard_enabled && DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_VIRTUAL_KEYBOARD)) {
+ int caret_start = -1;
+ int caret_end = -1;
+
+ if (!has_selection(0)) {
+ String full_text = _base_get_text(0, 0, get_caret_line(), get_caret_column());
+
+ caret_start = full_text.length();
+ } else {
+ String pre_text = _base_get_text(0, 0, get_selection_from_line(), get_selection_from_column());
+ String post_text = get_selected_text(0);
+
+ caret_start = pre_text.length();
+ caret_end = caret_start + post_text.length();
+ }
+
+ DisplayServer::get_singleton()->virtual_keyboard_show(get_text(), get_global_rect(), DisplayServer::KEYBOARD_TYPE_MULTILINE, -1, caret_start, caret_end);
+ }
+}
+
/* General overrides. */
Size2 TextEdit::get_minimum_size() const {
Size2 size = theme_cache.style_normal->get_minimum_size();
@@ -2963,13 +2994,13 @@ bool TextEdit::can_drop_data(const Point2 &p_point, const Variant &p_data) const
return drop_override;
}
- return is_editable() && p_data.get_type() == Variant::STRING;
+ return is_editable() && p_data.is_string();
}
void TextEdit::drop_data(const Point2 &p_point, const Variant &p_data) {
Control::drop_data(p_point, p_data);
- if (p_data.get_type() == Variant::STRING && is_editable()) {
+ if (p_data.is_string() && is_editable()) {
Point2i pos = get_line_column_at_pos(get_local_mouse_pos());
int drop_at_line = pos.y;
int drop_at_column = pos.x;
@@ -5883,7 +5914,7 @@ int TextEdit::get_visible_line_count_in_range(int p_from_line, int p_to_line) co
}
int TextEdit::get_total_visible_line_count() const {
- return get_visible_line_count_in_range(0, text.size() - 1);
+ return text.get_total_visible_line_count();
}
// Auto adjust.
@@ -8157,8 +8188,36 @@ void TextEdit::_update_gutter_width() {
}
/* Syntax highlighting. */
-Dictionary TextEdit::_get_line_syntax_highlighting(int p_line) {
- return (syntax_highlighter.is_null() || setting_text) ? Dictionary() : syntax_highlighter->get_line_syntax_highlighting(p_line);
+Vector<Pair<int64_t, Color>> TextEdit::_get_line_syntax_highlighting(int p_line) {
+ if (syntax_highlighter.is_null() || setting_text) {
+ return Vector<Pair<int64_t, Color>>();
+ }
+
+ HashMap<int, Vector<Pair<int64_t, Color>>>::Iterator E = syntax_highlighting_cache.find(p_line);
+ if (E) {
+ return E->value;
+ }
+
+ Dictionary color_map = syntax_highlighter->get_line_syntax_highlighting(p_line);
+ Vector<Pair<int64_t, Color>> result;
+ result.resize(color_map.size());
+ int i = 0;
+ for (const Variant *key = color_map.next(nullptr); key; key = color_map.next(key), i++) {
+ int64_t key_data = *key;
+ const Variant *color_data = color_map.getptr(*key);
+ Color color_value = editable ? theme_cache.font_color : theme_cache.font_readonly_color;
+ if (color_data != nullptr) {
+ color_value = (color_data->operator Dictionary()).get("color", color_value);
+ }
+ result.write[i] = Pair<int64_t, Color>(key_data, color_value);
+ }
+ syntax_highlighting_cache.insert(p_line, result);
+
+ return result;
+}
+
+void TextEdit::_clear_syntax_highlighting_cache() {
+ syntax_highlighting_cache.clear();
}
/* Deprecated. */
@@ -8184,6 +8243,7 @@ int TextEdit::get_selection_column(int p_caret) const {
/*** Super internal Core API. Everything builds on it. ***/
void TextEdit::_text_changed() {
+ _clear_syntax_highlighting_cache();
_cancel_drag_and_drop_text();
queue_redraw();
diff --git a/scene/gui/text_edit.h b/scene/gui/text_edit.h
index 1f2fd6619a..a448e185b1 100644
--- a/scene/gui/text_edit.h
+++ b/scene/gui/text_edit.h
@@ -153,6 +153,7 @@ private:
Color background_color = Color(0, 0, 0, 0);
bool hidden = false;
+ int line_count = 0;
int height = 0;
int width = 0;
@@ -178,16 +179,19 @@ private:
bool use_default_word_separators = true;
bool use_custom_word_separators = false;
- int line_height = -1;
- int max_width = -1;
+ mutable bool max_line_width_dirty = true;
+ mutable bool max_line_height_dirty = true;
+ mutable int max_line_width = 0;
+ mutable int max_line_height = 0;
+ mutable int total_visible_line_count = 0;
int width = -1;
int tab_size = 4;
int gutter_count = 0;
bool indent_wrapped_lines = false;
- void _calculate_line_height();
- void _calculate_max_line_width();
+ void _calculate_line_height() const;
+ void _calculate_max_line_width() const;
public:
void set_tab_size(int p_tab_size);
@@ -203,6 +207,7 @@ private:
int get_line_height() const;
int get_line_width(int p_line, int p_wrap_index = -1) const;
int get_max_width() const;
+ int get_total_visible_line_count() const;
void set_use_default_word_separators(bool p_enabled);
bool is_default_word_separators_enabled() const;
@@ -226,18 +231,8 @@ private:
const Ref<TextParagraph> get_line_data(int p_line) const;
void set(int p_line, const String &p_text, const Array &p_bidi_override);
- void set_hidden(int p_line, bool p_hidden) {
- if (text[p_line].hidden == p_hidden) {
- return;
- }
- text.write[p_line].hidden = p_hidden;
- if (!p_hidden && text[p_line].width > max_width) {
- max_width = text[p_line].width;
- } else if (p_hidden && text[p_line].width == max_width) {
- _calculate_max_line_width();
- }
- }
- bool is_hidden(int p_line) const { return text[p_line].hidden; }
+ void set_hidden(int p_line, bool p_hidden);
+ bool is_hidden(int p_line) const;
void insert(int p_at, const Vector<String> &p_text, const Vector<Array> &p_bidi_override);
void remove_range(int p_from_line, int p_to_line);
int size() const { return text.size(); }
@@ -248,7 +243,7 @@ private:
void invalidate_all();
void invalidate_all_lines();
- _FORCE_INLINE_ const String &operator[](int p_line) const;
+ _FORCE_INLINE_ String operator[](int p_line) const;
/* Gutters. */
void add_gutter(int p_at);
@@ -453,6 +448,7 @@ private:
void _caret_changed(int p_caret = -1);
void _emit_caret_changed();
+ void _show_virtual_keyboard();
void _reset_caret_blink_timer();
void _toggle_draw_caret();
@@ -568,8 +564,10 @@ private:
/* Syntax highlighting. */
Ref<SyntaxHighlighter> syntax_highlighter;
+ HashMap<int, Vector<Pair<int64_t, Color>>> syntax_highlighting_cache;
- Dictionary _get_line_syntax_highlighting(int p_line);
+ Vector<Pair<int64_t, Color>> _get_line_syntax_highlighting(int p_line);
+ void _clear_syntax_highlighting_cache();
/* Visual. */
struct ThemeCache {
@@ -1023,6 +1021,7 @@ public:
void add_gutter(int p_at = -1);
void remove_gutter(int p_gutter);
int get_gutter_count() const;
+ Vector2i get_hovered_gutter() const { return hovered_gutter; }
void set_gutter_name(int p_gutter, const String &p_name);
String get_gutter_name(int p_gutter) const;
diff --git a/scene/gui/tree.cpp b/scene/gui/tree.cpp
index 5830bea258..edd25d1d5c 100644
--- a/scene/gui/tree.cpp
+++ b/scene/gui/tree.cpp
@@ -1564,7 +1564,7 @@ void TreeItem::_call_recursive_bind(const Variant **p_args, int p_argcount, Call
return;
}
- if (p_args[0]->get_type() != Variant::STRING && p_args[0]->get_type() != Variant::STRING_NAME) {
+ if (!p_args[0]->is_string()) {
r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT;
r_error.argument = 0;
r_error.expected = Variant::STRING_NAME;
@@ -3274,12 +3274,10 @@ void Tree::value_editor_changed(double p_value) {
return;
}
- TreeItem::Cell &c = popup_edited_item->cells.write[popup_edited_item_col];
- c.val = p_value;
+ const TreeItem::Cell &c = popup_edited_item->cells[popup_edited_item_col];
- line_editor->set_text(String::num(c.val, Math::range_step_decimals(c.step)));
+ line_editor->set_text(String::num(p_value, Math::range_step_decimals(c.step)));
- item_edited(popup_edited_item_col, popup_edited_item);
queue_redraw();
}
@@ -4490,9 +4488,16 @@ void Tree::item_edited(int p_column, TreeItem *p_item, MouseButton p_custom_mous
}
void Tree::item_changed(int p_column, TreeItem *p_item) {
- if (p_item != nullptr && p_column >= 0 && p_column < p_item->cells.size()) {
- p_item->cells.write[p_column].dirty = true;
- columns.write[p_column].cached_minimum_width_dirty = true;
+ if (p_item != nullptr) {
+ if (p_column >= 0 && p_column < p_item->cells.size()) {
+ p_item->cells.write[p_column].dirty = true;
+ columns.write[p_column].cached_minimum_width_dirty = true;
+ } else if (p_column == -1) {
+ for (int i = 0; i < p_item->cells.size(); i++) {
+ p_item->cells.write[i].dirty = true;
+ columns.write[i].cached_minimum_width_dirty = true;
+ }
+ }
}
queue_redraw();
}
diff --git a/scene/gui/tree.h b/scene/gui/tree.h
index 9b1541f4b9..4518708685 100644
--- a/scene/gui/tree.h
+++ b/scene/gui/tree.h
@@ -178,6 +178,9 @@ private:
if (parent->first_child == this) {
parent->first_child = next;
}
+ if (parent->last_child == this) {
+ parent->last_child = prev;
+ }
}
}
diff --git a/scene/main/node.cpp b/scene/main/node.cpp
index de6d49761b..50f5923e3c 100644
--- a/scene/main/node.cpp
+++ b/scene/main/node.cpp
@@ -770,8 +770,7 @@ Error Node::_rpc_bind(const Variant **p_args, int p_argcount, Callable::CallErro
return ERR_INVALID_PARAMETER;
}
- Variant::Type type = p_args[0]->get_type();
- if (type != Variant::STRING_NAME && type != Variant::STRING) {
+ if (!p_args[0]->is_string()) {
r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT;
r_error.argument = 0;
r_error.expected = Variant::STRING_NAME;
@@ -799,8 +798,7 @@ Error Node::_rpc_id_bind(const Variant **p_args, int p_argcount, Callable::CallE
return ERR_INVALID_PARAMETER;
}
- Variant::Type type = p_args[1]->get_type();
- if (type != Variant::STRING_NAME && type != Variant::STRING) {
+ if (!p_args[1]->is_string()) {
r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT;
r_error.argument = 1;
r_error.expected = Variant::STRING_NAME;
@@ -3436,7 +3434,7 @@ Variant Node::_call_deferred_thread_group_bind(const Variant **p_args, int p_arg
return Variant();
}
- if (p_args[0]->get_type() != Variant::STRING_NAME && p_args[0]->get_type() != Variant::STRING) {
+ if (!p_args[0]->is_string()) {
r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT;
r_error.argument = 0;
r_error.expected = Variant::STRING_NAME;
@@ -3459,7 +3457,7 @@ Variant Node::_call_thread_safe_bind(const Variant **p_args, int p_argcount, Cal
return Variant();
}
- if (p_args[0]->get_type() != Variant::STRING_NAME && p_args[0]->get_type() != Variant::STRING) {
+ if (!p_args[0]->is_string()) {
r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT;
r_error.argument = 0;
r_error.expected = Variant::STRING_NAME;
diff --git a/scene/main/node.h b/scene/main/node.h
index 2f6372dad5..e412459105 100644
--- a/scene/main/node.h
+++ b/scene/main/node.h
@@ -657,7 +657,7 @@ public:
return binds;
}
- void replace_by(Node *p_node, bool p_keep_data = false);
+ void replace_by(Node *p_node, bool p_keep_groups = false);
void set_process_mode(ProcessMode p_mode);
ProcessMode get_process_mode() const;
diff --git a/scene/main/scene_tree.cpp b/scene/main/scene_tree.cpp
index f0c9e8a866..106130872d 100644
--- a/scene/main/scene_tree.cpp
+++ b/scene/main/scene_tree.cpp
@@ -1315,8 +1315,8 @@ void SceneTree::_call_group_flags(const Variant **p_args, int p_argcount, Callab
ERR_FAIL_COND(p_argcount < 3);
ERR_FAIL_COND(!p_args[0]->is_num());
- ERR_FAIL_COND(p_args[1]->get_type() != Variant::STRING_NAME && p_args[1]->get_type() != Variant::STRING);
- ERR_FAIL_COND(p_args[2]->get_type() != Variant::STRING_NAME && p_args[2]->get_type() != Variant::STRING);
+ ERR_FAIL_COND(!p_args[1]->is_string());
+ ERR_FAIL_COND(!p_args[2]->is_string());
int flags = *p_args[0];
StringName group = *p_args[1];
@@ -1329,8 +1329,8 @@ void SceneTree::_call_group(const Variant **p_args, int p_argcount, Callable::Ca
r_error.error = Callable::CallError::CALL_OK;
ERR_FAIL_COND(p_argcount < 2);
- ERR_FAIL_COND(p_args[0]->get_type() != Variant::STRING_NAME && p_args[0]->get_type() != Variant::STRING);
- ERR_FAIL_COND(p_args[1]->get_type() != Variant::STRING_NAME && p_args[1]->get_type() != Variant::STRING);
+ ERR_FAIL_COND(!p_args[0]->is_string());
+ ERR_FAIL_COND(!p_args[1]->is_string());
StringName group = *p_args[0];
StringName method = *p_args[1];
diff --git a/scene/main/viewport.cpp b/scene/main/viewport.cpp
index 192427f8a6..de2332125f 100644
--- a/scene/main/viewport.cpp
+++ b/scene/main/viewport.cpp
@@ -655,9 +655,7 @@ void Viewport::_notification(int p_what) {
case NOTIFICATION_WM_WINDOW_FOCUS_OUT: {
_gui_cancel_tooltip();
_drop_physics_mouseover();
- if (gui.mouse_focus && !gui.forced_mouse_focus) {
- _drop_mouse_focus();
- }
+ _drop_mouse_focus();
// When the window focus changes, we want to end mouse_focus, but
// not the mouse_over. Note: The OS will trigger a separate mouse
// exit event if the change in focus results in the mouse exiting
@@ -884,6 +882,10 @@ void Viewport::_process_picking() {
}
#ifndef _3D_DISABLED
+ if (physics_object_picking_first_only && is_input_handled()) {
+ continue;
+ }
+
CollisionObject3D *capture_object = nullptr;
if (physics_object_capture.is_valid()) {
capture_object = Object::cast_to<CollisionObject3D>(ObjectDB::get_instance(physics_object_capture));
@@ -1831,7 +1833,6 @@ void Viewport::_gui_input_event(Ref<InputEvent> p_event) {
// as the release will never be received otherwise.
if (gui.mouse_focus_mask.is_empty()) {
gui.mouse_focus = nullptr;
- gui.forced_mouse_focus = false;
}
bool stopped = mouse_focus && mouse_focus->can_process() && _gui_call_input(mouse_focus, mb);
@@ -1860,7 +1861,6 @@ void Viewport::_gui_input_event(Ref<InputEvent> p_event) {
gui.drag_data = control->get_drag_data(control->get_global_transform_with_canvas().affine_inverse().xform(mpos - gui.drag_accum));
if (gui.drag_data.get_type() != Variant::NIL) {
gui.mouse_focus = nullptr;
- gui.forced_mouse_focus = false;
gui.mouse_focus_mask.clear();
break;
} else {
@@ -2403,7 +2403,6 @@ void Viewport::_gui_hide_control(Control *p_control) {
void Viewport::_gui_remove_control(Control *p_control) {
if (gui.mouse_focus == p_control) {
gui.mouse_focus = nullptr;
- gui.forced_mouse_focus = false;
gui.mouse_focus_mask.clear();
}
if (gui.key_focus == p_control) {
@@ -2569,9 +2568,12 @@ void Viewport::_drop_mouse_focus() {
Control *c = gui.mouse_focus;
BitField<MouseButtonMask> mask = gui.mouse_focus_mask;
gui.mouse_focus = nullptr;
- gui.forced_mouse_focus = false;
gui.mouse_focus_mask.clear();
+ if (!c) {
+ return;
+ }
+
for (int i = 0; i < 3; i++) {
if ((int)mask & (1 << i)) {
Ref<InputEventMouseButton> mb;
@@ -3898,23 +3900,6 @@ Rect2i Viewport::subwindow_get_popup_safe_rect(Window *p_window) const {
return gui.sub_windows[index].parent_safe_rect;
}
-void Viewport::pass_mouse_focus_to(Viewport *p_viewport, Control *p_control) {
- ERR_MAIN_THREAD_GUARD;
- ERR_FAIL_NULL(p_viewport);
- ERR_FAIL_NULL(p_control);
-
- if (gui.mouse_focus) {
- p_viewport->gui.mouse_focus = p_control;
- p_viewport->gui.mouse_focus_mask = gui.mouse_focus_mask;
- p_viewport->gui.key_focus = p_control;
- p_viewport->gui.forced_mouse_focus = true;
-
- gui.mouse_focus = nullptr;
- gui.forced_mouse_focus = false;
- gui.mouse_focus_mask.clear();
- }
-}
-
void Viewport::set_sdf_oversize(SDFOversize p_sdf_oversize) {
ERR_MAIN_THREAD_GUARD;
ERR_FAIL_INDEX(p_sdf_oversize, SDF_OVERSIZE_MAX);
@@ -4710,6 +4695,7 @@ void Viewport::_bind_methods() {
ClassDB::bind_method(D_METHOD("warp_mouse", "position"), &Viewport::warp_mouse);
ClassDB::bind_method(D_METHOD("update_mouse_cursor_state"), &Viewport::update_mouse_cursor_state);
+ ClassDB::bind_method(D_METHOD("gui_cancel_drag"), &Viewport::gui_cancel_drag);
ClassDB::bind_method(D_METHOD("gui_get_drag_data"), &Viewport::gui_get_drag_data);
ClassDB::bind_method(D_METHOD("gui_is_dragging"), &Viewport::gui_is_dragging);
ClassDB::bind_method(D_METHOD("gui_is_drag_successful"), &Viewport::gui_is_drag_successful);
@@ -5044,6 +5030,9 @@ Viewport::~Viewport() {
for (ViewportTexture *E : viewport_textures) {
E->vp = nullptr;
}
+ if (world_2d.is_valid()) {
+ world_2d->remove_viewport(this);
+ }
ERR_FAIL_NULL(RenderingServer::get_singleton());
RenderingServer::get_singleton()->free(viewport);
}
diff --git a/scene/main/viewport.h b/scene/main/viewport.h
index 0d31c07e57..faa36851e9 100644
--- a/scene/main/viewport.h
+++ b/scene/main/viewport.h
@@ -349,7 +349,6 @@ private:
Ref<Texture2D> vrs_texture;
struct GUI {
- bool forced_mouse_focus = false; //used for menu buttons
bool mouse_in_viewport = false;
bool key_event_accepted = false;
HashMap<int, ObjectID> touch_focus;
@@ -662,8 +661,6 @@ public:
Viewport *get_parent_viewport() const;
Window *get_base_window() const;
- void pass_mouse_focus_to(Viewport *p_viewport, Control *p_control);
-
void set_canvas_cull_mask(uint32_t p_layers);
uint32_t get_canvas_cull_mask() const;
diff --git a/scene/register_scene_types.cpp b/scene/register_scene_types.cpp
index aa8ff75c6a..76678e609a 100644
--- a/scene/register_scene_types.cpp
+++ b/scene/register_scene_types.cpp
@@ -402,8 +402,6 @@ void register_scene_types() {
GDREGISTER_CLASS(VSlider);
GDREGISTER_CLASS(Popup);
GDREGISTER_CLASS(PopupPanel);
- GDREGISTER_CLASS(MenuBar);
- GDREGISTER_CLASS(MenuButton);
GDREGISTER_CLASS(CheckBox);
GDREGISTER_CLASS(CheckButton);
GDREGISTER_CLASS(LinkButton);
@@ -458,6 +456,8 @@ void register_scene_types() {
GDREGISTER_CLASS(CodeHighlighter);
GDREGISTER_ABSTRACT_CLASS(TreeItem);
+ GDREGISTER_CLASS(MenuBar);
+ GDREGISTER_CLASS(MenuButton);
GDREGISTER_CLASS(OptionButton);
GDREGISTER_CLASS(SpinBox);
GDREGISTER_CLASS(ColorPicker);
diff --git a/scene/resources/2d/navigation_mesh_source_geometry_data_2d.cpp b/scene/resources/2d/navigation_mesh_source_geometry_data_2d.cpp
index ee6fc61af3..e3f14539a8 100644
--- a/scene/resources/2d/navigation_mesh_source_geometry_data_2d.cpp
+++ b/scene/resources/2d/navigation_mesh_source_geometry_data_2d.cpp
@@ -176,7 +176,7 @@ void NavigationMeshSourceGeometryData2D::add_obstruction_outline(const PackedVec
}
void NavigationMeshSourceGeometryData2D::merge(const Ref<NavigationMeshSourceGeometryData2D> &p_other_geometry) {
- ERR_FAIL_NULL(p_other_geometry);
+ ERR_FAIL_COND(p_other_geometry.is_null());
Vector<Vector<Vector2>> other_traversable_outlines;
Vector<Vector<Vector2>> other_obstruction_outlines;
diff --git a/scene/resources/2d/navigation_polygon.h b/scene/resources/2d/navigation_polygon.h
index 86bda47ace..ed2c606c55 100644
--- a/scene/resources/2d/navigation_polygon.h
+++ b/scene/resources/2d/navigation_polygon.h
@@ -33,6 +33,7 @@
#include "scene/2d/node_2d.h"
#include "scene/resources/navigation_mesh.h"
+#include "servers/navigation/navigation_globals.h"
class NavigationPolygon : public Resource {
GDCLASS(NavigationPolygon, Resource);
@@ -50,7 +51,7 @@ class NavigationPolygon : public Resource {
// Navigation mesh
Ref<NavigationMesh> navigation_mesh;
- real_t cell_size = 1.0f; // Must match ProjectSettings default 2D cell_size.
+ real_t cell_size = NavigationDefaults2D::navmesh_cell_size;
real_t border_size = 0.0f;
Rect2 baking_rect;
diff --git a/scene/resources/2d/tile_set.compat.inc b/scene/resources/2d/tile_set.compat.inc
index 873ae3aa93..58e33f7b35 100644
--- a/scene/resources/2d/tile_set.compat.inc
+++ b/scene/resources/2d/tile_set.compat.inc
@@ -37,7 +37,7 @@ Ref<NavigationPolygon> TileData::_get_navigation_polygon_bind_compat_84660(int p
}
Ref<OccluderPolygon2D> TileData::_get_occluder_bind_compat_84660(int p_layer_id) const {
- return get_occluder(p_layer_id, false, false, false);
+ return get_occluder_polygon(p_layer_id, 0, false, false, false);
}
void TileData::_bind_compatibility_methods() {
diff --git a/scene/resources/2d/tile_set.cpp b/scene/resources/2d/tile_set.cpp
index dd6ae5096a..229e18be23 100644
--- a/scene/resources/2d/tile_set.cpp
+++ b/scene/resources/2d/tile_set.cpp
@@ -3444,7 +3444,8 @@ void TileSet::_compatibility_conversion() {
polygon.write[index] = xform.xform(polygon[index] - ctd->region.get_size() / 2.0);
}
occluder->set_polygon(polygon);
- tile_data->set_occluder(0, occluder);
+ tile_data->add_occluder_polygon(0);
+ tile_data->set_occluder_polygon(0, 0, occluder);
}
if (ctd->navigation.is_valid()) {
if (get_navigation_layers_count() < 1) {
@@ -3558,7 +3559,8 @@ void TileSet::_compatibility_conversion() {
polygon.write[index] = xform.xform(polygon[index] - ctd->region.get_size() / 2.0);
}
occluder->set_polygon(polygon);
- tile_data->set_occluder(0, occluder);
+ tile_data->add_occluder_polygon(0);
+ tile_data->set_occluder_polygon(0, 0, occluder);
}
if (ctd->autotile_navpoly_map.has(coords)) {
if (get_navigation_layers_count() < 1) {
@@ -3920,7 +3922,7 @@ bool TileSet::_set(const StringName &p_name, const Variant &p_value) {
int terrain_index = components[1].trim_prefix("terrain_").to_int();
ERR_FAIL_COND_V(terrain_index < 0, false);
if (components[2] == "name") {
- ERR_FAIL_COND_V(p_value.get_type() != Variant::STRING, false);
+ ERR_FAIL_COND_V(!p_value.is_string(), false);
while (terrain_set_index >= terrain_sets.size()) {
add_terrain_set();
}
@@ -3958,7 +3960,7 @@ bool TileSet::_set(const StringName &p_name, const Variant &p_value) {
int index = components[0].trim_prefix("custom_data_layer_").to_int();
ERR_FAIL_COND_V(index < 0, false);
if (components[1] == "name") {
- ERR_FAIL_COND_V(p_value.get_type() != Variant::STRING, false);
+ ERR_FAIL_COND_V(!p_value.is_string(), false);
while (index >= custom_data_layers.size()) {
add_custom_data_layer();
}
@@ -6220,33 +6222,86 @@ int TileData::get_y_sort_origin() const {
return y_sort_origin;
}
+#ifndef DISABLE_DEPRECATED
void TileData::set_occluder(int p_layer_id, Ref<OccluderPolygon2D> p_occluder_polygon) {
ERR_FAIL_INDEX(p_layer_id, occluders.size());
- occluders.write[p_layer_id].occluder = p_occluder_polygon;
- occluders.write[p_layer_id].transformed_occluders.clear();
+ if (get_occluder_polygons_count(p_layer_id) == 0) {
+ add_occluder_polygon(p_layer_id);
+ }
+ set_occluder_polygon(p_layer_id, 0, p_occluder_polygon);
emit_signal(CoreStringName(changed));
}
Ref<OccluderPolygon2D> TileData::get_occluder(int p_layer_id, bool p_flip_h, bool p_flip_v, bool p_transpose) const {
ERR_FAIL_INDEX_V(p_layer_id, occluders.size(), Ref<OccluderPolygon2D>());
+ if (get_occluder_polygons_count(p_layer_id) == 0) {
+ return Ref<OccluderPolygon2D>();
+ }
+ return get_occluder_polygon(p_layer_id, 0, p_flip_h, p_flip_v, p_transpose);
+}
+#endif // DISABLE_DEPRECATED
+
+void TileData::set_occluder_polygons_count(int p_layer_id, int p_polygons_count) {
+ ERR_FAIL_INDEX(p_layer_id, occluders.size());
+ ERR_FAIL_COND(p_polygons_count < 0);
+ if (p_polygons_count == occluders.write[p_layer_id].polygons.size()) {
+ return;
+ }
+ occluders.write[p_layer_id].polygons.resize(p_polygons_count);
+ notify_property_list_changed();
+ emit_signal(CoreStringName(changed));
+}
+
+int TileData::get_occluder_polygons_count(int p_layer_id) const {
+ ERR_FAIL_INDEX_V(p_layer_id, occluders.size(), 0);
+ return occluders[p_layer_id].polygons.size();
+}
+
+void TileData::add_occluder_polygon(int p_layer_id) {
+ ERR_FAIL_INDEX(p_layer_id, occluders.size());
+ occluders.write[p_layer_id].polygons.push_back(OcclusionLayerTileData::PolygonOccluderTileData());
+ emit_signal(CoreStringName(changed));
+}
+
+void TileData::remove_occluder_polygon(int p_layer_id, int p_polygon_index) {
+ ERR_FAIL_INDEX(p_layer_id, occluders.size());
+ ERR_FAIL_INDEX(p_polygon_index, occluders[p_layer_id].polygons.size());
+ occluders.write[p_layer_id].polygons.remove_at(p_polygon_index);
+ emit_signal(CoreStringName(changed));
+}
+
+void TileData::set_occluder_polygon(int p_layer_id, int p_polygon_index, const Ref<OccluderPolygon2D> &p_occluder_polygon) {
+ ERR_FAIL_INDEX(p_layer_id, occluders.size());
+ ERR_FAIL_INDEX(p_polygon_index, occluders[p_layer_id].polygons.size());
+
+ OcclusionLayerTileData::PolygonOccluderTileData &polygon_occluder_tile_data = occluders.write[p_layer_id].polygons.write[p_polygon_index];
+ polygon_occluder_tile_data.occluder_polygon = p_occluder_polygon;
+ polygon_occluder_tile_data.transformed_polygon_occluders.clear();
+ emit_signal(CoreStringName(changed));
+}
+
+Ref<OccluderPolygon2D> TileData::get_occluder_polygon(int p_layer_id, int p_polygon_index, bool p_flip_h, bool p_flip_v, bool p_transpose) const {
+ ERR_FAIL_INDEX_V(p_layer_id, occluders.size(), Ref<OccluderPolygon2D>());
+ ERR_FAIL_INDEX_V(p_polygon_index, occluders[p_layer_id].polygons.size(), Ref<OccluderPolygon2D>());
const OcclusionLayerTileData &layer_tile_data = occluders[p_layer_id];
+ const Ref<OccluderPolygon2D> &occluder_polygon = layer_tile_data.polygons[p_polygon_index].occluder_polygon;
int key = int(p_flip_h) | int(p_flip_v) << 1 | int(p_transpose) << 2;
if (key == 0) {
- return layer_tile_data.occluder;
+ return occluder_polygon;
}
- if (layer_tile_data.occluder.is_null()) {
+ if (occluder_polygon.is_null()) {
return Ref<OccluderPolygon2D>();
}
- HashMap<int, Ref<OccluderPolygon2D>>::Iterator I = layer_tile_data.transformed_occluders.find(key);
+ HashMap<int, Ref<OccluderPolygon2D>>::Iterator I = layer_tile_data.polygons[p_polygon_index].transformed_polygon_occluders.find(key);
if (!I) {
Ref<OccluderPolygon2D> transformed_polygon;
transformed_polygon.instantiate();
- transformed_polygon->set_polygon(get_transformed_vertices(layer_tile_data.occluder->get_polygon(), p_flip_h, p_flip_v, p_transpose));
- layer_tile_data.transformed_occluders[key] = transformed_polygon;
+ transformed_polygon->set_polygon(get_transformed_vertices(occluder_polygon->get_polygon(), p_flip_h, p_flip_v, p_transpose));
+ layer_tile_data.polygons[p_polygon_index].transformed_polygon_occluders[key] = transformed_polygon;
return transformed_polygon;
} else {
return I->value;
@@ -6594,13 +6649,37 @@ bool TileData::_set(const StringName &p_name, const Variant &p_value) {
#endif
Vector<String> components = String(p_name).split("/", true, 2);
-
- if (components.size() == 2 && components[0].begins_with("occlusion_layer_") && components[0].trim_prefix("occlusion_layer_").is_valid_int()) {
+ if (components.size() >= 2 && components[0].begins_with("occlusion_layer_") && components[0].trim_prefix("occlusion_layer_").is_valid_int()) {
// Occlusion layers.
int layer_index = components[0].trim_prefix("occlusion_layer_").to_int();
ERR_FAIL_COND_V(layer_index < 0, false);
- if (components[1] == "polygon") {
- Ref<OccluderPolygon2D> polygon = p_value;
+ if (components.size() == 2) {
+ if (components[1] == "polygon") {
+ // Kept for compatibility.
+ Ref<OccluderPolygon2D> polygon = p_value;
+ if (layer_index >= occluders.size()) {
+ if (tile_set) {
+ return false;
+ } else {
+ occluders.resize(layer_index + 1);
+ }
+ }
+ if (get_occluder_polygons_count(layer_index) == 0) {
+ add_occluder_polygon(layer_index);
+ }
+ set_occluder_polygon(layer_index, 0, polygon);
+ return true;
+ } else if (components[1] == "polygons_count") {
+ if (p_value.get_type() != Variant::INT) {
+ return false;
+ }
+ set_occluder_polygons_count(layer_index, p_value);
+ return true;
+ }
+ } else if (components.size() == 3 && components[1].begins_with("polygon_") && components[1].trim_prefix("polygon_").is_valid_int()) {
+ // Polygons.
+ int polygon_index = components[1].trim_prefix("polygon_").to_int();
+ ERR_FAIL_COND_V(polygon_index < 0, false);
if (layer_index >= occluders.size()) {
if (tile_set) {
@@ -6609,8 +6688,16 @@ bool TileData::_set(const StringName &p_name, const Variant &p_value) {
occluders.resize(layer_index + 1);
}
}
- set_occluder(layer_index, polygon);
- return true;
+
+ if (polygon_index >= occluders[layer_index].polygons.size()) {
+ occluders.write[layer_index].polygons.resize(polygon_index + 1);
+ }
+
+ if (components[2] == "polygon") {
+ Ref<OccluderPolygon2D> polygon = p_value;
+ set_occluder_polygon(layer_index, polygon_index, polygon);
+ return true;
+ }
}
} else if (components.size() >= 2 && components[0].begins_with("physics_layer_") && components[0].trim_prefix("physics_layer_").is_valid_int()) {
// Physics layers.
@@ -6638,6 +6725,7 @@ bool TileData::_set(const StringName &p_name, const Variant &p_value) {
return true;
}
} else if (components.size() == 3 && components[1].begins_with("polygon_") && components[1].trim_prefix("polygon_").is_valid_int()) {
+ // Polygons.
int polygon_index = components[1].trim_prefix("polygon_").to_int();
ERR_FAIL_COND_V(polygon_index < 0, false);
@@ -6724,16 +6812,36 @@ bool TileData::_get(const StringName &p_name, Variant &r_ret) const {
Vector<String> components = String(p_name).split("/", true, 2);
if (tile_set) {
- if (components.size() == 2 && components[0].begins_with("occlusion_layer") && components[0].trim_prefix("occlusion_layer_").is_valid_int()) {
+ if (components.size() >= 2 && components[0].begins_with("occlusion_layer") && components[0].trim_prefix("occlusion_layer_").is_valid_int()) {
// Occlusion layers.
int layer_index = components[0].trim_prefix("occlusion_layer_").to_int();
ERR_FAIL_COND_V(layer_index < 0, false);
if (layer_index >= occluders.size()) {
return false;
}
- if (components[1] == "polygon") {
- r_ret = get_occluder(layer_index);
- return true;
+ if (components.size() == 2) {
+ if (components[1] == "polygon") {
+ // Kept for compatibility.
+ if (occluders[layer_index].polygons.is_empty()) {
+ return false;
+ }
+ r_ret = get_occluder_polygon(layer_index, 0);
+ return true;
+ } else if (components[1] == "polygons_count") {
+ r_ret = get_occluder_polygons_count(layer_index);
+ return true;
+ }
+ } else if (components.size() == 3 && components[1].begins_with("polygon_") && components[1].trim_prefix("polygon_").is_valid_int()) {
+ // Polygons.
+ int polygon_index = components[1].trim_prefix("polygon_").to_int();
+ ERR_FAIL_COND_V(polygon_index < 0, false);
+ if (polygon_index >= occluders[layer_index].polygons.size()) {
+ return false;
+ }
+ if (components[2] == "polygon") {
+ r_ret = get_occluder_polygon(layer_index, polygon_index);
+ return true;
+ }
}
} else if (components.size() >= 2 && components[0].begins_with("physics_layer_") && components[0].trim_prefix("physics_layer_").is_valid_int()) {
// Physics layers.
@@ -6813,12 +6921,15 @@ void TileData::_get_property_list(List<PropertyInfo> *p_list) const {
// Occlusion layers.
p_list->push_back(PropertyInfo(Variant::NIL, GNAME("Rendering", ""), PROPERTY_HINT_NONE, "", PROPERTY_USAGE_GROUP));
for (int i = 0; i < occluders.size(); i++) {
- // occlusion_layer_%d/polygon
- property_info = PropertyInfo(Variant::OBJECT, vformat("occlusion_layer_%d/%s", i, PNAME("polygon")), PROPERTY_HINT_RESOURCE_TYPE, "OccluderPolygon2D", PROPERTY_USAGE_DEFAULT);
- if (occluders[i].occluder.is_null()) {
- property_info.usage ^= PROPERTY_USAGE_STORAGE;
+ p_list->push_back(PropertyInfo(Variant::INT, vformat("occlusion_layer_%d/%s", i, PNAME("polygons_count")), PROPERTY_HINT_NONE, "", PROPERTY_USAGE_EDITOR));
+ for (int j = 0; j < occluders[i].polygons.size(); j++) {
+ // occlusion_layer_%d/polygon_%d/polygon
+ property_info = PropertyInfo(Variant::OBJECT, vformat("occlusion_layer_%d/polygon_%d/%s", i, j, PNAME("polygon")), PROPERTY_HINT_RESOURCE_TYPE, "OccluderPolygon2D", PROPERTY_USAGE_DEFAULT);
+ if (occluders[i].polygons[j].occluder_polygon.is_null()) {
+ property_info.usage ^= PROPERTY_USAGE_STORAGE;
+ }
+ p_list->push_back(property_info);
}
- p_list->push_back(property_info);
}
// Physics layers.
@@ -6923,8 +7034,17 @@ void TileData::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_y_sort_origin", "y_sort_origin"), &TileData::set_y_sort_origin);
ClassDB::bind_method(D_METHOD("get_y_sort_origin"), &TileData::get_y_sort_origin);
+ ClassDB::bind_method(D_METHOD("set_occluder_polygons_count", "layer_id", "polygons_count"), &TileData::set_occluder_polygons_count);
+ ClassDB::bind_method(D_METHOD("get_occluder_polygons_count", "layer_id"), &TileData::get_occluder_polygons_count);
+ ClassDB::bind_method(D_METHOD("add_occluder_polygon", "layer_id"), &TileData::add_occluder_polygon);
+ ClassDB::bind_method(D_METHOD("remove_occluder_polygon", "layer_id", "polygon_index"), &TileData::remove_occluder_polygon);
+ ClassDB::bind_method(D_METHOD("set_occluder_polygon", "layer_id", "polygon_index", "polygon"), &TileData::set_occluder_polygon);
+ ClassDB::bind_method(D_METHOD("get_occluder_polygon", "layer_id", "polygon_index", "flip_h", "flip_v", "transpose"), &TileData::get_occluder_polygon, DEFVAL(false), DEFVAL(false), DEFVAL(false));
+
+#ifndef DISABLE_DEPRECATED
ClassDB::bind_method(D_METHOD("set_occluder", "layer_id", "occluder_polygon"), &TileData::set_occluder);
ClassDB::bind_method(D_METHOD("get_occluder", "layer_id", "flip_h", "flip_v", "transpose"), &TileData::get_occluder, DEFVAL(false), DEFVAL(false), DEFVAL(false));
+#endif // DISABLE_DEPRECATED
// Physics.
ClassDB::bind_method(D_METHOD("set_constant_linear_velocity", "layer_id", "velocity"), &TileData::set_constant_linear_velocity);
diff --git a/scene/resources/2d/tile_set.h b/scene/resources/2d/tile_set.h
index 51df972c8d..931495d020 100644
--- a/scene/resources/2d/tile_set.h
+++ b/scene/resources/2d/tile_set.h
@@ -841,8 +841,11 @@ private:
int z_index = 0;
int y_sort_origin = 0;
struct OcclusionLayerTileData {
- Ref<OccluderPolygon2D> occluder;
- mutable HashMap<int, Ref<OccluderPolygon2D>> transformed_occluders;
+ struct PolygonOccluderTileData {
+ Ref<OccluderPolygon2D> occluder_polygon;
+ mutable HashMap<int, Ref<OccluderPolygon2D>> transformed_polygon_occluders;
+ };
+ Vector<PolygonOccluderTileData> polygons;
};
Vector<OcclusionLayerTileData> occluders;
@@ -941,8 +944,17 @@ public:
void set_y_sort_origin(int p_y_sort_origin);
int get_y_sort_origin() const;
+#ifndef DISABLE_DEPRECATED
void set_occluder(int p_layer_id, Ref<OccluderPolygon2D> p_occluder_polygon);
Ref<OccluderPolygon2D> get_occluder(int p_layer_id, bool p_flip_h = false, bool p_flip_v = false, bool p_transpose = false) const;
+#endif // DISABLE_DEPRECATED
+
+ void set_occluder_polygons_count(int p_layer_id, int p_polygons_count);
+ int get_occluder_polygons_count(int p_layer_id) const;
+ void add_occluder_polygon(int p_layer_id);
+ void remove_occluder_polygon(int p_layer_id, int p_polygon_index);
+ void set_occluder_polygon(int p_layer_id, int p_polygon_index, const Ref<OccluderPolygon2D> &p_occluder_polygon);
+ Ref<OccluderPolygon2D> get_occluder_polygon(int p_layer_id, int p_polygon_index, bool p_flip_h = false, bool p_flip_v = false, bool p_transpose = false) const;
// Physics
void set_constant_linear_velocity(int p_layer_id, const Vector2 &p_velocity);
diff --git a/scene/resources/3d/fog_material.cpp b/scene/resources/3d/fog_material.cpp
index 5e4f1970ee..92246b50db 100644
--- a/scene/resources/3d/fog_material.cpp
+++ b/scene/resources/3d/fog_material.cpp
@@ -138,7 +138,7 @@ void FogMaterial::cleanup_shader() {
}
void FogMaterial::_update_shader() {
- shader_mutex.lock();
+ MutexLock shader_lock(shader_mutex);
if (shader.is_null()) {
shader = RS::get_singleton()->shader_create();
@@ -165,7 +165,6 @@ void fog() {
}
)");
}
- shader_mutex.unlock();
}
FogMaterial::FogMaterial() {
diff --git a/scene/resources/3d/importer_mesh.cpp b/scene/resources/3d/importer_mesh.cpp
index b0633c06b9..4f4c485db3 100644
--- a/scene/resources/3d/importer_mesh.cpp
+++ b/scene/resources/3d/importer_mesh.cpp
@@ -1418,5 +1418,5 @@ void ImporterMesh::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_lightmap_size_hint", "size"), &ImporterMesh::set_lightmap_size_hint);
ClassDB::bind_method(D_METHOD("get_lightmap_size_hint"), &ImporterMesh::get_lightmap_size_hint);
- ADD_PROPERTY(PropertyInfo(Variant::DICTIONARY, "_data", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR), "_set_data", "_get_data");
+ ADD_PROPERTY(PropertyInfo(Variant::DICTIONARY, "_data", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL), "_set_data", "_get_data");
}
diff --git a/scene/resources/3d/navigation_mesh_source_geometry_data_3d.cpp b/scene/resources/3d/navigation_mesh_source_geometry_data_3d.cpp
index c55e25fcae..59366592ce 100644
--- a/scene/resources/3d/navigation_mesh_source_geometry_data_3d.cpp
+++ b/scene/resources/3d/navigation_mesh_source_geometry_data_3d.cpp
@@ -223,7 +223,7 @@ void NavigationMeshSourceGeometryData3D::add_faces(const PackedVector3Array &p_f
}
void NavigationMeshSourceGeometryData3D::merge(const Ref<NavigationMeshSourceGeometryData3D> &p_other_geometry) {
- ERR_FAIL_NULL(p_other_geometry);
+ ERR_FAIL_COND(p_other_geometry.is_null());
Vector<float> other_vertices;
Vector<int> other_indices;
diff --git a/scene/resources/3d/sky_material.cpp b/scene/resources/3d/sky_material.cpp
index 640261d615..c470db5d7f 100644
--- a/scene/resources/3d/sky_material.cpp
+++ b/scene/resources/3d/sky_material.cpp
@@ -269,7 +269,7 @@ void ProceduralSkyMaterial::cleanup_shader() {
}
void ProceduralSkyMaterial::_update_shader() {
- shader_mutex.lock();
+ MutexLock shader_lock(shader_mutex);
if (shader_cache[0].is_null()) {
for (int i = 0; i < 2; i++) {
shader_cache[i] = RS::get_singleton()->shader_create();
@@ -354,7 +354,6 @@ void sky() {
i ? "render_mode use_debanding;" : ""));
}
}
- shader_mutex.unlock();
}
ProceduralSkyMaterial::ProceduralSkyMaterial() {
@@ -463,7 +462,7 @@ void PanoramaSkyMaterial::cleanup_shader() {
}
void PanoramaSkyMaterial::_update_shader() {
- shader_mutex.lock();
+ MutexLock shader_lock(shader_mutex);
if (shader_cache[0].is_null()) {
for (int i = 0; i < 2; i++) {
shader_cache[i] = RS::get_singleton()->shader_create();
@@ -484,8 +483,6 @@ void sky() {
i ? "filter_linear" : "filter_nearest"));
}
}
-
- shader_mutex.unlock();
}
PanoramaSkyMaterial::PanoramaSkyMaterial() {
@@ -692,7 +689,7 @@ void PhysicalSkyMaterial::cleanup_shader() {
}
void PhysicalSkyMaterial::_update_shader() {
- shader_mutex.lock();
+ MutexLock shader_lock(shader_mutex);
if (shader_cache[0].is_null()) {
for (int i = 0; i < 2; i++) {
shader_cache[i] = RS::get_singleton()->shader_create();
@@ -785,8 +782,6 @@ void sky() {
i ? "render_mode use_debanding;" : ""));
}
}
-
- shader_mutex.unlock();
}
PhysicalSkyMaterial::PhysicalSkyMaterial() {
diff --git a/scene/resources/animation.cpp b/scene/resources/animation.cpp
index e00bf56939..a2ed6af23c 100644
--- a/scene/resources/animation.cpp
+++ b/scene/resources/animation.cpp
@@ -1717,7 +1717,7 @@ int Animation::track_insert_key(int p_track, double p_time, const Variant &p_key
ERR_FAIL_COND_V(p_key.get_type() != Variant::DICTIONARY, -1);
Dictionary d = p_key;
- ERR_FAIL_COND_V(!d.has("method") || (d["method"].get_type() != Variant::STRING_NAME && d["method"].get_type() != Variant::STRING), -1);
+ ERR_FAIL_COND_V(!d.has("method") || !d["method"].is_string(), -1);
ERR_FAIL_COND_V(!d.has("args") || !d["args"].is_array(), -1);
MethodKey k;
diff --git a/scene/resources/animation.h b/scene/resources/animation.h
index 9e6d34959a..0c29790ea4 100644
--- a/scene/resources/animation.h
+++ b/scene/resources/animation.h
@@ -45,7 +45,7 @@ public:
static inline String PARAMETERS_BASE_PATH = "parameters/";
- enum TrackType {
+ enum TrackType : uint8_t {
TYPE_VALUE, // Set a value in a property, can be interpolated.
TYPE_POSITION_3D, // Position 3D track, can be compressed.
TYPE_ROTATION_3D, // Rotation 3D track, can be compressed.
@@ -57,7 +57,7 @@ public:
TYPE_ANIMATION,
};
- enum InterpolationType {
+ enum InterpolationType : uint8_t {
INTERPOLATION_NEAREST,
INTERPOLATION_LINEAR,
INTERPOLATION_CUBIC,
@@ -65,26 +65,26 @@ public:
INTERPOLATION_CUBIC_ANGLE,
};
- enum UpdateMode {
+ enum UpdateMode : uint8_t {
UPDATE_CONTINUOUS,
UPDATE_DISCRETE,
UPDATE_CAPTURE,
};
- enum LoopMode {
+ enum LoopMode : uint8_t {
LOOP_NONE,
LOOP_LINEAR,
LOOP_PINGPONG,
};
// LoopedFlag is used in Animataion to "process the keys at both ends correct".
- enum LoopedFlag {
+ enum LoopedFlag : uint8_t {
LOOPED_FLAG_NONE,
LOOPED_FLAG_END,
LOOPED_FLAG_START,
};
- enum FindMode {
+ enum FindMode : uint8_t {
FIND_MODE_NEAREST,
FIND_MODE_APPROX,
FIND_MODE_EXACT,
@@ -104,7 +104,6 @@ public:
};
#endif // TOOLS_ENABLED
-private:
struct Track {
TrackType type = TrackType::TYPE_ANIMATION;
InterpolationType interpolation = INTERPOLATION_LINEAR;
@@ -117,6 +116,7 @@ private:
virtual ~Track() {}
};
+private:
struct Key {
real_t transition = 1.0;
double time = 0.0; // Time in secs.
@@ -396,6 +396,10 @@ public:
int add_track(TrackType p_type, int p_at_pos = -1);
void remove_track(int p_track);
+ _FORCE_INLINE_ const Vector<Track *> get_tracks() {
+ return tracks;
+ }
+
bool is_capture_included() const;
int get_track_count() const;
diff --git a/scene/resources/audio_stream_wav.cpp b/scene/resources/audio_stream_wav.cpp
index de6a069567..d0e0f0eef3 100644
--- a/scene/resources/audio_stream_wav.cpp
+++ b/scene/resources/audio_stream_wav.cpp
@@ -123,10 +123,8 @@ void AudioStreamPlaybackWAV::do_resample(const Depth *p_src, AudioFrame *p_dst,
int16_t nibble, diff, step;
p_ima_adpcm[i].last_nibble++;
- const uint8_t *src_ptr = (const uint8_t *)base->data;
- src_ptr += AudioStreamWAV::DATA_PAD;
- uint8_t nbb = src_ptr[(p_ima_adpcm[i].last_nibble >> 1) * (is_stereo ? 2 : 1) + i];
+ uint8_t nbb = p_src[(p_ima_adpcm[i].last_nibble >> 1) * (is_stereo ? 2 : 1) + i];
nibble = (p_ima_adpcm[i].last_nibble & 1) ? (nbb >> 4) : (nbb & 0xF);
step = _ima_adpcm_step_table[p_ima_adpcm[i].step_index];
@@ -179,17 +177,16 @@ void AudioStreamPlaybackWAV::do_resample(const Depth *p_src, AudioFrame *p_dst,
if (pos != p_qoa->cache_pos) { // Prevents triple decoding on lower mix rates.
for (int i = 0; i < 2; i++) {
// Sign operations prevent triple decoding on backward loops, maxing prevents pop.
- uint32_t interp_pos = MIN(pos + (i * sign) + (sign < 0), p_qoa->desc->samples - 1);
+ uint32_t interp_pos = MIN(pos + (i * sign) + (sign < 0), p_qoa->desc.samples - 1);
uint32_t new_data_ofs = 8 + interp_pos / QOA_FRAME_LEN * p_qoa->frame_len;
if (p_qoa->data_ofs != new_data_ofs) {
p_qoa->data_ofs = new_data_ofs;
- const uint8_t *src_ptr = (const uint8_t *)base->data;
- src_ptr += p_qoa->data_ofs + AudioStreamWAV::DATA_PAD;
- qoa_decode_frame(src_ptr, p_qoa->frame_len, p_qoa->desc, p_qoa->dec, &p_qoa->dec_len);
+ const uint8_t *ofs_src = (uint8_t *)p_src + p_qoa->data_ofs;
+ qoa_decode_frame(ofs_src, p_qoa->frame_len, &p_qoa->desc, p_qoa->dec.ptr(), &p_qoa->dec_len);
}
- uint32_t dec_idx = (interp_pos % QOA_FRAME_LEN) * p_qoa->desc->channels;
+ uint32_t dec_idx = (interp_pos % QOA_FRAME_LEN) * p_qoa->desc.channels;
if ((sign > 0 && i == 0) || (sign < 0 && i == 1)) {
final = p_qoa->dec[dec_idx];
@@ -267,7 +264,7 @@ void AudioStreamPlaybackWAV::do_resample(const Depth *p_src, AudioFrame *p_dst,
}
int AudioStreamPlaybackWAV::mix(AudioFrame *p_buffer, float p_rate_scale, int p_frames) {
- if (!base->data || !active) {
+ if (base->data.is_empty() || !active) {
for (int i = 0; i < p_frames; i++) {
p_buffer[i] = AudioFrame(0, 0);
}
@@ -286,7 +283,7 @@ int AudioStreamPlaybackWAV::mix(AudioFrame *p_buffer, float p_rate_scale, int p_
len *= 2;
break;
case AudioStreamWAV::FORMAT_QOA:
- len = qoa.desc->samples * qoa.desc->channels;
+ len = qoa.desc.samples * qoa.desc.channels;
break;
}
@@ -324,8 +321,7 @@ int AudioStreamPlaybackWAV::mix(AudioFrame *p_buffer, float p_rate_scale, int p_
/* audio data */
- uint8_t *dataptr = (uint8_t *)base->data;
- const void *data = dataptr + AudioStreamWAV::DATA_PAD;
+ const uint8_t *data = base->data.ptr() + AudioStreamWAV::DATA_PAD;
AudioFrame *dst_buff = p_buffer;
if (format == AudioStreamWAV::FORMAT_IMA_ADPCM) {
@@ -483,15 +479,7 @@ void AudioStreamPlaybackWAV::set_sample_playback(const Ref<AudioSamplePlayback>
AudioStreamPlaybackWAV::AudioStreamPlaybackWAV() {}
-AudioStreamPlaybackWAV::~AudioStreamPlaybackWAV() {
- if (qoa.desc) {
- memfree(qoa.desc);
- }
-
- if (qoa.dec) {
- memfree(qoa.dec);
- }
-}
+AudioStreamPlaybackWAV::~AudioStreamPlaybackWAV() {}
/////////////////////
@@ -557,8 +545,8 @@ double AudioStreamWAV::get_length() const {
len *= 2;
break;
case AudioStreamWAV::FORMAT_QOA:
- qoa_desc desc = { 0, 0, 0, { { { 0 }, { 0 } } } };
- qoa_decode_header((uint8_t *)data + DATA_PAD, data_bytes, &desc);
+ qoa_desc desc = {};
+ qoa_decode_header(data.ptr() + DATA_PAD, data_bytes, &desc);
len = desc.samples * desc.channels;
break;
}
@@ -576,22 +564,16 @@ bool AudioStreamWAV::is_monophonic() const {
void AudioStreamWAV::set_data(const Vector<uint8_t> &p_data) {
AudioServer::get_singleton()->lock();
- if (data) {
- memfree(data);
- data = nullptr;
- data_bytes = 0;
- }
- int datalen = p_data.size();
- if (datalen) {
- const uint8_t *r = p_data.ptr();
- int alloc_len = datalen + DATA_PAD * 2;
- data = memalloc(alloc_len); //alloc with some padding for interpolation
- memset(data, 0, alloc_len);
- uint8_t *dataptr = (uint8_t *)data;
- memcpy(dataptr + DATA_PAD, r, datalen);
- data_bytes = datalen;
- }
+ int src_data_len = p_data.size();
+
+ data.clear();
+
+ int alloc_len = src_data_len + DATA_PAD * 2;
+ data.resize(alloc_len);
+ memset(data.ptr(), 0, alloc_len);
+ memcpy(data.ptr() + DATA_PAD, p_data.ptr(), src_data_len);
+ data_bytes = src_data_len;
AudioServer::get_singleton()->unlock();
}
@@ -599,13 +581,9 @@ void AudioStreamWAV::set_data(const Vector<uint8_t> &p_data) {
Vector<uint8_t> AudioStreamWAV::get_data() const {
Vector<uint8_t> pv;
- if (data) {
+ if (!data.is_empty()) {
pv.resize(data_bytes);
- {
- uint8_t *w = pv.ptrw();
- uint8_t *dataptr = (uint8_t *)data;
- memcpy(w, dataptr + DATA_PAD, data_bytes);
- }
+ memcpy(pv.ptrw(), data.ptr() + DATA_PAD, data_bytes);
}
return pv;
@@ -697,13 +675,12 @@ Ref<AudioStreamPlayback> AudioStreamWAV::instantiate_playback() {
sample->base = Ref<AudioStreamWAV>(this);
if (format == AudioStreamWAV::FORMAT_QOA) {
- sample->qoa.desc = (qoa_desc *)memalloc(sizeof(qoa_desc));
- uint32_t ffp = qoa_decode_header((uint8_t *)data + DATA_PAD, data_bytes, sample->qoa.desc);
+ uint32_t ffp = qoa_decode_header(data.ptr() + DATA_PAD, data_bytes, &sample->qoa.desc);
ERR_FAIL_COND_V(ffp != 8, Ref<AudioStreamPlaybackWAV>());
- sample->qoa.frame_len = qoa_max_frame_size(sample->qoa.desc);
- int samples_len = (sample->qoa.desc->samples > QOA_FRAME_LEN ? QOA_FRAME_LEN : sample->qoa.desc->samples);
- int alloc_len = sample->qoa.desc->channels * samples_len * sizeof(int16_t);
- sample->qoa.dec = (int16_t *)memalloc(alloc_len);
+ sample->qoa.frame_len = qoa_max_frame_size(&sample->qoa.desc);
+ int samples_len = (sample->qoa.desc.samples > QOA_FRAME_LEN ? QOA_FRAME_LEN : sample->qoa.desc.samples);
+ int dec_len = sample->qoa.desc.channels * samples_len;
+ sample->qoa.dec.resize(dec_len);
}
return sample;
@@ -765,7 +742,7 @@ void AudioStreamWAV::_bind_methods() {
ClassDB::bind_method(D_METHOD("save_to_wav", "path"), &AudioStreamWAV::save_to_wav);
ADD_PROPERTY(PropertyInfo(Variant::PACKED_BYTE_ARRAY, "data", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR), "set_data", "get_data");
- ADD_PROPERTY(PropertyInfo(Variant::INT, "format", PROPERTY_HINT_ENUM, "8-Bit,16-Bit,IMA-ADPCM,QOA"), "set_format", "get_format");
+ ADD_PROPERTY(PropertyInfo(Variant::INT, "format", PROPERTY_HINT_ENUM, "8-Bit,16-Bit,IMA ADPCM,Quite OK Audio"), "set_format", "get_format");
ADD_PROPERTY(PropertyInfo(Variant::INT, "loop_mode", PROPERTY_HINT_ENUM, "Disabled,Forward,Ping-Pong,Backward"), "set_loop_mode", "get_loop_mode");
ADD_PROPERTY(PropertyInfo(Variant::INT, "loop_begin"), "set_loop_begin", "get_loop_begin");
ADD_PROPERTY(PropertyInfo(Variant::INT, "loop_end"), "set_loop_end", "get_loop_end");
@@ -785,10 +762,4 @@ void AudioStreamWAV::_bind_methods() {
AudioStreamWAV::AudioStreamWAV() {}
-AudioStreamWAV::~AudioStreamWAV() {
- if (data) {
- memfree(data);
- data = nullptr;
- data_bytes = 0;
- }
-}
+AudioStreamWAV::~AudioStreamWAV() {}
diff --git a/scene/resources/audio_stream_wav.h b/scene/resources/audio_stream_wav.h
index 806db675b6..bc62e8883a 100644
--- a/scene/resources/audio_stream_wav.h
+++ b/scene/resources/audio_stream_wav.h
@@ -59,10 +59,10 @@ class AudioStreamPlaybackWAV : public AudioStreamPlayback {
} ima_adpcm[2];
struct QOA_State {
- qoa_desc *desc = nullptr;
+ qoa_desc desc = {};
uint32_t data_ofs = 0;
uint32_t frame_len = 0;
- int16_t *dec = nullptr;
+ LocalVector<int16_t> dec;
uint32_t dec_len = 0;
int64_t cache_pos = -1;
int16_t cache[2] = { 0, 0 };
@@ -137,7 +137,7 @@ private:
int loop_begin = 0;
int loop_end = 0;
int mix_rate = 44100;
- void *data = nullptr;
+ LocalVector<uint8_t> data;
uint32_t data_bytes = 0;
protected:
diff --git a/scene/resources/material.cpp b/scene/resources/material.cpp
index d07dee6674..927e76e4b2 100644
--- a/scene/resources/material.cpp
+++ b/scene/resources/material.cpp
@@ -37,7 +37,7 @@
#include "scene/main/scene_tree.h"
void Material::set_next_pass(const Ref<Material> &p_pass) {
- for (Ref<Material> pass_child = p_pass; pass_child != nullptr; pass_child = pass_child->get_next_pass()) {
+ for (Ref<Material> pass_child = p_pass; pass_child.is_valid(); pass_child = pass_child->get_next_pass()) {
ERR_FAIL_COND_MSG(pass_child == this, "Can't set as next_pass one of its parents to prevent crashes due to recursive loop.");
}
diff --git a/scene/resources/mesh.cpp b/scene/resources/mesh.cpp
index 8b5e438aea..22e2e9138f 100644
--- a/scene/resources/mesh.cpp
+++ b/scene/resources/mesh.cpp
@@ -2251,6 +2251,7 @@ Error ArrayMesh::lightmap_unwrap_cached(const Transform3D &p_base_transform, flo
}
void ArrayMesh::set_shadow_mesh(const Ref<ArrayMesh> &p_mesh) {
+ ERR_FAIL_COND_MSG(p_mesh == this, "Cannot set a mesh as its own shadow mesh.");
shadow_mesh = p_mesh;
if (shadow_mesh.is_valid()) {
RS::get_singleton()->mesh_set_shadow_mesh(mesh, shadow_mesh->get_rid());
diff --git a/scene/resources/navigation_mesh.h b/scene/resources/navigation_mesh.h
index 741cea0791..1b3db5bac2 100644
--- a/scene/resources/navigation_mesh.h
+++ b/scene/resources/navigation_mesh.h
@@ -33,6 +33,7 @@
#include "core/os/rw_lock.h"
#include "scene/resources/mesh.h"
+#include "servers/navigation/navigation_globals.h"
class NavigationMesh : public Resource {
GDCLASS(NavigationMesh, Resource);
@@ -77,8 +78,8 @@ public:
};
protected:
- float cell_size = 0.25f; // Must match ProjectSettings default 3D cell_size and NavigationServer NavMap cell_size.
- float cell_height = 0.25f; // Must match ProjectSettings default 3D cell_height and NavigationServer NavMap cell_height.
+ float cell_size = NavigationDefaults3D::navmesh_cell_size;
+ float cell_height = NavigationDefaults3D::navmesh_cell_height;
float border_size = 0.0f;
float agent_height = 1.5f;
float agent_radius = 0.5f;
diff --git a/scene/resources/packed_scene.cpp b/scene/resources/packed_scene.cpp
index 900629f5f8..69dc71e414 100644
--- a/scene/resources/packed_scene.cpp
+++ b/scene/resources/packed_scene.cpp
@@ -369,6 +369,7 @@ Node *SceneState::instantiate(GenEditState p_edit_state) const {
value = make_local_resource(value, n, resources_local_to_sub_scene, node, snames[nprops[j].name], resources_local_to_scene, i, ret_nodes, p_edit_state);
}
}
+
if (value.get_type() == Variant::ARRAY) {
Array set_array = value;
value = setup_resources_in_array(set_array, n, resources_local_to_sub_scene, node, snames[nprops[j].name], resources_local_to_scene, i, ret_nodes, p_edit_state);
@@ -383,25 +384,20 @@ Node *SceneState::instantiate(GenEditState p_edit_state) const {
}
}
}
- if (value.get_type() == Variant::DICTIONARY) {
- Dictionary dictionary = value;
- const Array keys = dictionary.keys();
- const Array values = dictionary.values();
-
- if (has_local_resource(values) || has_local_resource(keys)) {
- Array duplicated_keys = keys.duplicate(true);
- Array duplicated_values = values.duplicate(true);
- duplicated_keys = setup_resources_in_array(duplicated_keys, n, resources_local_to_sub_scene, node, snames[nprops[j].name], resources_local_to_scene, i, ret_nodes, p_edit_state);
- duplicated_values = setup_resources_in_array(duplicated_values, n, resources_local_to_sub_scene, node, snames[nprops[j].name], resources_local_to_scene, i, ret_nodes, p_edit_state);
+ if (value.get_type() == Variant::DICTIONARY) {
+ Dictionary set_dict = value;
+ value = setup_resources_in_dictionary(set_dict, n, resources_local_to_sub_scene, node, snames[nprops[j].name], resources_local_to_scene, i, ret_nodes, p_edit_state);
- dictionary.clear();
+ bool is_get_valid = false;
+ Variant get_value = node->get(snames[nprops[j].name], &is_get_valid);
- for (int dictionary_index = 0; dictionary_index < keys.size(); dictionary_index++) {
- dictionary[duplicated_keys[dictionary_index]] = duplicated_values[dictionary_index];
+ if (is_get_valid && get_value.get_type() == Variant::DICTIONARY) {
+ Dictionary get_dict = get_value;
+ if (!set_dict.is_same_typed(get_dict)) {
+ value = Dictionary(set_dict, get_dict.get_typed_key_builtin(), get_dict.get_typed_key_class_name(), get_dict.get_typed_key_script(),
+ get_dict.get_typed_value_builtin(), get_dict.get_typed_value_class_name(), get_dict.get_typed_value_script());
}
-
- value = dictionary;
}
}
@@ -539,6 +535,30 @@ Node *SceneState::instantiate(GenEditState p_edit_state) const {
array.set(i, dnp.base->get_node_or_null(paths[i]));
}
dnp.base->set(dnp.property, array);
+ } else if (dnp.value.get_type() == Variant::DICTIONARY) {
+ Dictionary paths = dnp.value;
+
+ bool valid;
+ Dictionary dict = dnp.base->get(dnp.property, &valid);
+ ERR_CONTINUE(!valid);
+ dict = dict.duplicate();
+ bool convert_key = dict.get_typed_key_builtin() == Variant::OBJECT &&
+ ClassDB::is_parent_class(dict.get_typed_key_class_name(), "Node");
+ bool convert_value = dict.get_typed_value_builtin() == Variant::OBJECT &&
+ ClassDB::is_parent_class(dict.get_typed_value_class_name(), "Node");
+
+ for (int i = 0; i < paths.size(); i++) {
+ Variant key = paths.get_key_at_index(i);
+ if (convert_key) {
+ key = dnp.base->get_node_or_null(key);
+ }
+ Variant value = paths.get_value_at_index(i);
+ if (convert_value) {
+ value = dnp.base->get_node_or_null(value);
+ }
+ dict[key] = value;
+ }
+ dnp.base->set(dnp.property, dict);
} else {
dnp.base->set(dnp.property, dnp.base->get_node_or_null(dnp.value));
}
@@ -641,6 +661,26 @@ Array SceneState::setup_resources_in_array(Array &p_array_to_scan, const SceneSt
return p_array_to_scan;
}
+Dictionary SceneState::setup_resources_in_dictionary(Dictionary &p_dictionary_to_scan, const SceneState::NodeData &p_n, HashMap<Ref<Resource>, Ref<Resource>> &p_resources_local_to_sub_scene, Node *p_node, const StringName p_sname, HashMap<Ref<Resource>, Ref<Resource>> &p_resources_local_to_scene, int p_i, Node **p_ret_nodes, SceneState::GenEditState p_edit_state) const {
+ const Array keys = p_dictionary_to_scan.keys();
+ const Array values = p_dictionary_to_scan.values();
+
+ if (has_local_resource(values) || has_local_resource(keys)) {
+ Array duplicated_keys = keys.duplicate(true);
+ Array duplicated_values = values.duplicate(true);
+
+ duplicated_keys = setup_resources_in_array(duplicated_keys, p_n, p_resources_local_to_sub_scene, p_node, p_sname, p_resources_local_to_scene, p_i, p_ret_nodes, p_edit_state);
+ duplicated_values = setup_resources_in_array(duplicated_values, p_n, p_resources_local_to_sub_scene, p_node, p_sname, p_resources_local_to_scene, p_i, p_ret_nodes, p_edit_state);
+ p_dictionary_to_scan.clear();
+
+ for (int i = 0; i < keys.size(); i++) {
+ p_dictionary_to_scan[duplicated_keys[i]] = duplicated_values[i];
+ }
+ }
+
+ return p_dictionary_to_scan;
+}
+
bool SceneState::has_local_resource(const Array &p_array) const {
for (int i = 0; i < p_array.size(); i++) {
Ref<Resource> res = p_array[i];
@@ -810,6 +850,53 @@ Error SceneState::_parse_node(Node *p_owner, Node *p_node, int p_parent_idx, Has
value = new_array;
}
}
+ } else if (E.type == Variant::DICTIONARY && E.hint == PROPERTY_HINT_TYPE_STRING) {
+ int key_value_separator = E.hint_string.find(";");
+ if (key_value_separator >= 0) {
+ int key_subtype_separator = E.hint_string.find(":");
+ String key_subtype_string = E.hint_string.substr(0, key_subtype_separator);
+ int key_slash_pos = key_subtype_string.find("/");
+ PropertyHint key_subtype_hint = PropertyHint::PROPERTY_HINT_NONE;
+ if (key_slash_pos >= 0) {
+ key_subtype_hint = PropertyHint(key_subtype_string.get_slice("/", 1).to_int());
+ key_subtype_string = key_subtype_string.substr(0, key_slash_pos);
+ }
+ Variant::Type key_subtype = Variant::Type(key_subtype_string.to_int());
+ bool convert_key = key_subtype == Variant::OBJECT && key_subtype_hint == PROPERTY_HINT_NODE_TYPE;
+
+ int value_subtype_separator = E.hint_string.find(":", key_value_separator) - (key_value_separator + 1);
+ String value_subtype_string = E.hint_string.substr(key_value_separator + 1, value_subtype_separator);
+ int value_slash_pos = value_subtype_string.find("/");
+ PropertyHint value_subtype_hint = PropertyHint::PROPERTY_HINT_NONE;
+ if (value_slash_pos >= 0) {
+ value_subtype_hint = PropertyHint(value_subtype_string.get_slice("/", 1).to_int());
+ value_subtype_string = value_subtype_string.substr(0, value_slash_pos);
+ }
+ Variant::Type value_subtype = Variant::Type(value_subtype_string.to_int());
+ bool convert_value = value_subtype == Variant::OBJECT && value_subtype_hint == PROPERTY_HINT_NODE_TYPE;
+
+ if (convert_key || convert_value) {
+ use_deferred_node_path_bit = true;
+ Dictionary dict = value;
+ Dictionary new_dict;
+ for (int i = 0; i < dict.size(); i++) {
+ Variant new_key = dict.get_key_at_index(i);
+ if (convert_key && new_key.get_type() == Variant::OBJECT) {
+ if (Node *n = Object::cast_to<Node>(new_key)) {
+ new_key = p_node->get_path_to(n);
+ }
+ }
+ Variant new_value = dict.get_value_at_index(i);
+ if (convert_value && new_value.get_type() == Variant::OBJECT) {
+ if (Node *n = Object::cast_to<Node>(new_value)) {
+ new_value = p_node->get_path_to(n);
+ }
+ }
+ new_dict[new_key] = new_value;
+ }
+ value = new_dict;
+ }
+ }
}
if (!pinned_props.has(name)) {
@@ -1267,25 +1354,6 @@ Ref<SceneState> SceneState::get_base_scene_state() const {
return Ref<SceneState>();
}
-void SceneState::update_instance_resource(String p_path, Ref<PackedScene> p_packed_scene) {
- ERR_FAIL_COND(p_packed_scene.is_null());
-
- for (const NodeData &nd : nodes) {
- if (nd.instance >= 0) {
- if (!(nd.instance & FLAG_INSTANCE_IS_PLACEHOLDER)) {
- int instance_id = nd.instance & FLAG_MASK;
- Ref<PackedScene> original_packed_scene = variants[instance_id];
- if (original_packed_scene.is_valid()) {
- if (original_packed_scene->get_path() == p_path) {
- variants.remove_at(instance_id);
- variants.insert(instance_id, p_packed_scene);
- }
- }
- }
- }
- }
-}
-
int SceneState::find_node_by_path(const NodePath &p_node) const {
ERR_FAIL_COND_V_MSG(node_path_cache.is_empty(), -1, "This operation requires the node cache to have been built.");
@@ -1880,6 +1948,16 @@ Vector<NodePath> SceneState::get_editable_instances() const {
return editable_instances;
}
+Ref<Resource> SceneState::get_sub_resource(const String &p_path) {
+ for (const Variant &v : variants) {
+ const Ref<Resource> &res = v;
+ if (res.is_valid() && res->get_path() == p_path) {
+ return res;
+ }
+ }
+ return Ref<Resource>();
+}
+
//add
int SceneState::add_name(const StringName &p_name) {
@@ -2199,7 +2277,7 @@ void PackedScene::_bind_methods() {
ClassDB::bind_method(D_METHOD("_get_bundled_scene"), &PackedScene::_get_bundled_scene);
ClassDB::bind_method(D_METHOD("get_state"), &PackedScene::get_state);
- ADD_PROPERTY(PropertyInfo(Variant::DICTIONARY, "_bundled"), "_set_bundled_scene", "_get_bundled_scene");
+ ADD_PROPERTY(PropertyInfo(Variant::DICTIONARY, "_bundled", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_STORAGE | PROPERTY_USAGE_INTERNAL), "_set_bundled_scene", "_get_bundled_scene");
BIND_ENUM_CONSTANT(GEN_EDIT_STATE_DISABLED);
BIND_ENUM_CONSTANT(GEN_EDIT_STATE_INSTANCE);
diff --git a/scene/resources/packed_scene.h b/scene/resources/packed_scene.h
index e26b9f7b90..9f8088910f 100644
--- a/scene/resources/packed_scene.h
+++ b/scene/resources/packed_scene.h
@@ -158,6 +158,7 @@ public:
Node *instantiate(GenEditState p_edit_state) const;
Array setup_resources_in_array(Array &array_to_scan, const SceneState::NodeData &n, HashMap<Ref<Resource>, Ref<Resource>> &resources_local_to_sub_scene, Node *node, const StringName sname, HashMap<Ref<Resource>, Ref<Resource>> &resources_local_to_scene, int i, Node **ret_nodes, SceneState::GenEditState p_edit_state) const;
+ Dictionary setup_resources_in_dictionary(Dictionary &p_dictionary_to_scan, const SceneState::NodeData &p_n, HashMap<Ref<Resource>, Ref<Resource>> &p_resources_local_to_sub_scene, Node *p_node, const StringName p_sname, HashMap<Ref<Resource>, Ref<Resource>> &p_resources_local_to_scene, int p_i, Node **p_ret_nodes, SceneState::GenEditState p_edit_state) const;
Variant make_local_resource(Variant &value, const SceneState::NodeData &p_node_data, HashMap<Ref<Resource>, Ref<Resource>> &p_resources_local_to_sub_scene, Node *p_node, const StringName p_sname, HashMap<Ref<Resource>, Ref<Resource>> &p_resources_local_to_scene, int p_i, Node **p_ret_nodes, SceneState::GenEditState p_edit_state) const;
bool has_local_resource(const Array &p_array) const;
@@ -195,6 +196,7 @@ public:
bool has_connection(const NodePath &p_node_from, const StringName &p_signal, const NodePath &p_node_to, const StringName &p_method, bool p_no_inheritance = false);
Vector<NodePath> get_editable_instances() const;
+ Ref<Resource> get_sub_resource(const String &p_path);
//build API
diff --git a/scene/resources/particle_process_material.cpp b/scene/resources/particle_process_material.cpp
index ee986f5820..8cfe4c92b7 100644
--- a/scene/resources/particle_process_material.cpp
+++ b/scene/resources/particle_process_material.cpp
@@ -110,6 +110,7 @@ void ParticleProcessMaterial::init_shaders() {
shader_names->emission_ring_height = "emission_ring_height";
shader_names->emission_ring_radius = "emission_ring_radius";
shader_names->emission_ring_inner_radius = "emission_ring_inner_radius";
+ shader_names->emission_ring_cone_angle = "emission_ring_cone_angle";
shader_names->emission_shape_offset = "emission_shape_offset";
shader_names->emission_shape_scale = "emission_shape_scale";
@@ -269,6 +270,7 @@ void ParticleProcessMaterial::_update_shader() {
code += "uniform float " + shader_names->emission_ring_height + ";\n";
code += "uniform float " + shader_names->emission_ring_radius + ";\n";
code += "uniform float " + shader_names->emission_ring_inner_radius + ";\n";
+ code += "uniform float " + shader_names->emission_ring_cone_angle + ";\n";
} break;
case EMISSION_SHAPE_MAX: { // Max value for validity check.
break;
@@ -643,8 +645,14 @@ void ParticleProcessMaterial::_update_shader() {
code += " pos = texelFetch(emission_texture_points, emission_tex_ofs, 0).xyz;\n";
}
if (emission_shape == EMISSION_SHAPE_RING) {
+ code += " float radius_clamped = max(0.001, emission_ring_radius);\n";
+ code += " float top_radius = max(radius_clamped - tan(radians(90.0 - emission_ring_cone_angle)) * emission_ring_height, 0.0);\n";
+ code += " float y_pos = rand_from_seed(alt_seed);\n";
+ code += " float skew = max(min(radius_clamped, top_radius) / max(radius_clamped, top_radius), 0.5);\n";
+ code += " y_pos = radius_clamped < top_radius ? pow(y_pos, skew) : 1.0 - pow(y_pos, skew);\n";
code += " float ring_spawn_angle = rand_from_seed(alt_seed) * 2.0 * pi;\n";
- code += " float ring_random_radius = sqrt(rand_from_seed(alt_seed) * (emission_ring_radius * emission_ring_radius - emission_ring_inner_radius * emission_ring_inner_radius) + emission_ring_inner_radius * emission_ring_inner_radius);\n";
+ code += " float ring_random_radius = sqrt(rand_from_seed(alt_seed) * (radius_clamped * radius_clamped - emission_ring_inner_radius * emission_ring_inner_radius) + emission_ring_inner_radius * emission_ring_inner_radius);\n";
+ code += " ring_random_radius = mix(ring_random_radius, ring_random_radius * (top_radius / radius_clamped), y_pos);\n";
code += " vec3 axis = emission_ring_axis == vec3(0.0) ? vec3(0.0, 0.0, 1.0) : normalize(emission_ring_axis);\n";
code += " vec3 ortho_axis = vec3(0.0);\n";
code += " if (abs(axis) == vec3(1.0, 0.0, 0.0)) {\n";
@@ -662,7 +670,7 @@ void ParticleProcessMaterial::_update_shader() {
code += " vec3(axis.z * axis.x * oc - axis.y * s, axis.z * axis.y * oc + axis.x * s, c + axis.z * axis.z * oc)\n";
code += " ) * ortho_axis;\n";
code += " ortho_axis = normalize(ortho_axis);\n";
- code += " pos = ortho_axis * ring_random_radius + (rand_from_seed(alt_seed) * emission_ring_height - emission_ring_height / 2.0) * axis;\n";
+ code += " pos = ortho_axis * ring_random_radius + (y_pos * emission_ring_height - emission_ring_height / 2.0) * axis;\n";
}
code += " }\n";
code += " return pos * emission_shape_scale + emission_shape_offset;\n";
@@ -1615,6 +1623,11 @@ void ParticleProcessMaterial::set_emission_ring_inner_radius(real_t p_radius) {
RenderingServer::get_singleton()->material_set_param(_get_material(), shader_names->emission_ring_inner_radius, p_radius);
}
+void ParticleProcessMaterial::set_emission_ring_cone_angle(real_t p_angle) {
+ emission_ring_cone_angle = p_angle;
+ RenderingServer::get_singleton()->material_set_param(_get_material(), shader_names->emission_ring_cone_angle, p_angle);
+}
+
void ParticleProcessMaterial::set_inherit_velocity_ratio(double p_ratio) {
inherit_emitter_velocity_ratio = p_ratio;
RenderingServer::get_singleton()->material_set_param(_get_material(), shader_names->inherit_emitter_velocity_ratio, p_ratio);
@@ -1664,6 +1677,10 @@ real_t ParticleProcessMaterial::get_emission_ring_inner_radius() const {
return emission_ring_inner_radius;
}
+real_t ParticleProcessMaterial::get_emission_ring_cone_angle() const {
+ return emission_ring_cone_angle;
+}
+
void ParticleProcessMaterial::set_emission_shape_offset(const Vector3 &p_emission_shape_offset) {
emission_shape_offset = p_emission_shape_offset;
RenderingServer::get_singleton()->material_set_param(_get_material(), shader_names->emission_shape_offset, p_emission_shape_offset);
@@ -2015,6 +2032,9 @@ void ParticleProcessMaterial::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_emission_ring_inner_radius", "inner_radius"), &ParticleProcessMaterial::set_emission_ring_inner_radius);
ClassDB::bind_method(D_METHOD("get_emission_ring_inner_radius"), &ParticleProcessMaterial::get_emission_ring_inner_radius);
+ ClassDB::bind_method(D_METHOD("set_emission_ring_cone_angle", "cone_angle"), &ParticleProcessMaterial::set_emission_ring_cone_angle);
+ ClassDB::bind_method(D_METHOD("get_emission_ring_cone_angle"), &ParticleProcessMaterial::get_emission_ring_cone_angle);
+
ClassDB::bind_method(D_METHOD("set_emission_shape_offset", "emission_shape_offset"), &ParticleProcessMaterial::set_emission_shape_offset);
ClassDB::bind_method(D_METHOD("get_emission_shape_offset"), &ParticleProcessMaterial::get_emission_shape_offset);
@@ -2096,9 +2116,10 @@ void ParticleProcessMaterial::_bind_methods() {
ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "emission_color_texture", PROPERTY_HINT_RESOURCE_TYPE, "Texture2D"), "set_emission_color_texture", "get_emission_color_texture");
ADD_PROPERTY(PropertyInfo(Variant::INT, "emission_point_count", PROPERTY_HINT_RANGE, "0,1000000,1"), "set_emission_point_count", "get_emission_point_count");
ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "emission_ring_axis"), "set_emission_ring_axis", "get_emission_ring_axis");
- ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "emission_ring_height"), "set_emission_ring_height", "get_emission_ring_height");
- ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "emission_ring_radius"), "set_emission_ring_radius", "get_emission_ring_radius");
- ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "emission_ring_inner_radius"), "set_emission_ring_inner_radius", "get_emission_ring_inner_radius");
+ ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "emission_ring_height", PROPERTY_HINT_RANGE, "0,1000,0.01,or_greater"), "set_emission_ring_height", "get_emission_ring_height");
+ ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "emission_ring_radius", PROPERTY_HINT_RANGE, "0,1000,0.01,or_greater"), "set_emission_ring_radius", "get_emission_ring_radius");
+ ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "emission_ring_inner_radius", PROPERTY_HINT_RANGE, "0,1000,0.01,or_greater"), "set_emission_ring_inner_radius", "get_emission_ring_inner_radius");
+ ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "emission_ring_cone_angle", PROPERTY_HINT_RANGE, "0,90,0.01,degrees"), "set_emission_ring_cone_angle", "get_emission_ring_cone_angle");
ADD_SUBGROUP("Angle", "");
ADD_MIN_MAX_PROPERTY("angle", "-720,720,0.1,or_less,or_greater,degrees", PARAM_ANGLE);
ADD_PROPERTYI(PropertyInfo(Variant::OBJECT, "angle_curve", PROPERTY_HINT_RESOURCE_TYPE, "CurveTexture"), "set_param_texture", "get_param_texture", PARAM_ANGLE);
@@ -2276,6 +2297,7 @@ ParticleProcessMaterial::ParticleProcessMaterial() :
set_emission_ring_height(1);
set_emission_ring_radius(1);
set_emission_ring_inner_radius(0);
+ set_emission_ring_cone_angle(90);
set_emission_shape_offset(Vector3(0.0, 0.0, 0.0));
set_emission_shape_scale(Vector3(1.0, 1.0, 1.0));
diff --git a/scene/resources/particle_process_material.h b/scene/resources/particle_process_material.h
index 25046b51cd..12e3fbb64e 100644
--- a/scene/resources/particle_process_material.h
+++ b/scene/resources/particle_process_material.h
@@ -259,6 +259,7 @@ private:
StringName emission_ring_height;
StringName emission_ring_radius;
StringName emission_ring_inner_radius;
+ StringName emission_ring_cone_angle;
StringName emission_shape_offset;
StringName emission_shape_scale;
@@ -325,6 +326,7 @@ private:
real_t emission_ring_height = 0.0f;
real_t emission_ring_radius = 0.0f;
real_t emission_ring_inner_radius = 0.0f;
+ real_t emission_ring_cone_angle = 0.0f;
int emission_point_count = 1;
Vector3 emission_shape_offset;
Vector3 emission_shape_scale;
@@ -417,6 +419,7 @@ public:
void set_emission_ring_height(real_t p_height);
void set_emission_ring_radius(real_t p_radius);
void set_emission_ring_inner_radius(real_t p_radius);
+ void set_emission_ring_cone_angle(real_t p_angle);
void set_emission_point_count(int p_count);
EmissionShape get_emission_shape() const;
@@ -429,6 +432,7 @@ public:
real_t get_emission_ring_height() const;
real_t get_emission_ring_radius() const;
real_t get_emission_ring_inner_radius() const;
+ real_t get_emission_ring_cone_angle() const;
int get_emission_point_count() const;
void set_turbulence_enabled(bool p_turbulence_enabled);
diff --git a/scene/resources/portable_compressed_texture.cpp b/scene/resources/portable_compressed_texture.cpp
index 002db30379..06b5ec6d5a 100644
--- a/scene/resources/portable_compressed_texture.cpp
+++ b/scene/resources/portable_compressed_texture.cpp
@@ -345,7 +345,7 @@ void PortableCompressedTexture2D::_bind_methods() {
ClassDB::bind_static_method("PortableCompressedTexture2D", D_METHOD("set_keep_all_compressed_buffers", "keep"), &PortableCompressedTexture2D::set_keep_all_compressed_buffers);
ClassDB::bind_static_method("PortableCompressedTexture2D", D_METHOD("is_keeping_all_compressed_buffers"), &PortableCompressedTexture2D::is_keeping_all_compressed_buffers);
- ADD_PROPERTY(PropertyInfo(Variant::PACKED_BYTE_ARRAY, "_data", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR), "_set_data", "_get_data");
+ ADD_PROPERTY(PropertyInfo(Variant::PACKED_BYTE_ARRAY, "_data", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL), "_set_data", "_get_data");
ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "size_override", PROPERTY_HINT_NONE, "suffix:px"), "set_size_override", "get_size_override");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "keep_compressed_buffer"), "set_keep_compressed_buffer", "is_keeping_compressed_buffer");
diff --git a/scene/resources/resource_format_text.cpp b/scene/resources/resource_format_text.cpp
index 90102e44e4..29f9835ba9 100644
--- a/scene/resources/resource_format_text.cpp
+++ b/scene/resources/resource_format_text.cpp
@@ -35,17 +35,12 @@
#include "core/io/missing_resource.h"
#include "core/object/script_language.h"
-// Version 2: Changed names for Basis, AABB, Vectors, etc.
-// Version 3: New string ID for ext/subresources, breaks forward compat.
-// Version 4: PackedByteArray can be base64 encoded, and PackedVector4Array was added.
-#define FORMAT_VERSION 4
-// For compat, save as version 3 if not using PackedVector4Array or no big PackedByteArray.
-#define FORMAT_VERSION_COMPAT 3
-
-#define _printerr() ERR_PRINT(String(res_path + ":" + itos(lines) + " - Parse Error: " + error_text).utf8().get_data());
-
///
+void ResourceLoaderText::_printerr() {
+ ERR_PRINT(String(res_path + ":" + itos(lines) + " - Parse Error: " + error_text).utf8().get_data());
+}
+
Ref<Resource> ResourceLoaderText::get_resource() {
return resource;
}
@@ -629,6 +624,19 @@ Error ResourceLoaderText::load() {
}
}
+ if (value.get_type() == Variant::DICTIONARY) {
+ Dictionary set_dict = value;
+ bool is_get_valid = false;
+ Variant get_value = res->get(assign, &is_get_valid);
+ if (is_get_valid && get_value.get_type() == Variant::DICTIONARY) {
+ Dictionary get_dict = get_value;
+ if (!set_dict.is_same_typed(get_dict)) {
+ value = Dictionary(set_dict, get_dict.get_typed_key_builtin(), get_dict.get_typed_key_class_name(), get_dict.get_typed_key_script(),
+ get_dict.get_typed_value_builtin(), get_dict.get_typed_value_class_name(), get_dict.get_typed_value_script());
+ }
+ }
+ }
+
if (set_valid) {
res->set(assign, value);
}
@@ -756,6 +764,19 @@ Error ResourceLoaderText::load() {
}
}
+ if (value.get_type() == Variant::DICTIONARY) {
+ Dictionary set_dict = value;
+ bool is_get_valid = false;
+ Variant get_value = resource->get(assign, &is_get_valid);
+ if (is_get_valid && get_value.get_type() == Variant::DICTIONARY) {
+ Dictionary get_dict = get_value;
+ if (!set_dict.is_same_typed(get_dict)) {
+ value = Dictionary(set_dict, get_dict.get_typed_key_builtin(), get_dict.get_typed_key_class_name(), get_dict.get_typed_key_script(),
+ get_dict.get_typed_value_builtin(), get_dict.get_typed_value_class_name(), get_dict.get_typed_value_script());
+ }
+ }
+ }
+
if (set_valid) {
resource->set(assign, value);
}
@@ -1647,6 +1668,8 @@ void ResourceFormatSaverTextInstance::_find_resources(const Variant &p_variant,
} break;
case Variant::DICTIONARY: {
Dictionary d = p_variant;
+ _find_resources(d.get_typed_key_script());
+ _find_resources(d.get_typed_value_script());
List<Variant> keys;
d.get_key_list(&keys);
for (const Variant &E : keys) {
@@ -1734,7 +1757,7 @@ Error ResourceFormatSaverTextInstance::save(const String &p_path, const Ref<Reso
if (load_steps > 1) {
title += "load_steps=" + itos(load_steps) + " ";
}
- title += "format=" + itos(use_compat ? FORMAT_VERSION_COMPAT : FORMAT_VERSION) + "";
+ title += "format=" + itos(use_compat ? ResourceLoaderText::FORMAT_VERSION_COMPAT : ResourceLoaderText::FORMAT_VERSION) + "";
ResourceUID::ID uid = ResourceSaver::get_resource_id_for_path(local_path, true);
diff --git a/scene/resources/resource_format_text.h b/scene/resources/resource_format_text.h
index b5542f77ba..8397bc985f 100644
--- a/scene/resources/resource_format_text.h
+++ b/scene/resources/resource_format_text.h
@@ -38,6 +38,17 @@
#include "scene/resources/packed_scene.h"
class ResourceLoaderText {
+public:
+ enum {
+ // Version 2: Changed names for Basis, AABB, Vectors, etc.
+ // Version 3: New string ID for ext/subresources, breaks forward compat.
+ // Version 4: PackedByteArray can be base64 encoded, and PackedVector4Array was added.
+ FORMAT_VERSION = 4,
+ // For compat, save as version 3 if not using PackedVector4Array or no big PackedByteArray.
+ FORMAT_VERSION_COMPAT = 3,
+ };
+
+private:
bool translation_remapped = false;
String local_path;
String res_path;
@@ -100,6 +111,7 @@ class ResourceLoaderText {
static Error _parse_sub_resource_dummy(DummyReadData *p_data, VariantParser::Stream *p_stream, Ref<Resource> &r_res, int &line, String &r_err_str);
static Error _parse_ext_resource_dummy(DummyReadData *p_data, VariantParser::Stream *p_stream, Ref<Resource> &r_res, int &line, String &r_err_str);
+ void _printerr();
VariantParser::ResourceParser rp;
diff --git a/scene/resources/shader.cpp b/scene/resources/shader.cpp
index f343229cd8..46d38146a6 100644
--- a/scene/resources/shader.cpp
+++ b/scene/resources/shader.cpp
@@ -158,7 +158,7 @@ void Shader::get_shader_uniform_list(List<PropertyInfo> *p_params, bool p_get_gr
#ifdef MODULE_REGEX_ENABLED
const RegEx pattern("/\\*\\*\\s([^*]|[\\r\\n]|(\\*+([^*/]|[\\r\\n])))*\\*+/\\s*uniform\\s+\\w+\\s+" + pi.name + "(?=[\\s:;=])");
Ref<RegExMatch> pattern_ref = pattern.search(code);
- if (pattern_ref != nullptr) {
+ if (pattern_ref.is_valid()) {
RegExMatch *match = pattern_ref.ptr();
const RegEx pattern_tip("\\/\\*\\*([\\s\\S]*?)\\*/");
Ref<RegExMatch> pattern_tip_ref = pattern_tip.search(match->get_string(0));
diff --git a/scene/resources/sprite_frames.cpp b/scene/resources/sprite_frames.cpp
index 6e43ea9b17..dac0ceaa78 100644
--- a/scene/resources/sprite_frames.cpp
+++ b/scene/resources/sprite_frames.cpp
@@ -106,6 +106,12 @@ bool SpriteFrames::has_animation(const StringName &p_anim) const {
return animations.has(p_anim);
}
+void SpriteFrames::duplicate_animation(const StringName &p_from, const StringName &p_to) {
+ ERR_FAIL_COND_MSG(!animations.has(p_from), vformat("SpriteFrames doesn't have animation '%s'.", p_from));
+ ERR_FAIL_COND_MSG(animations.has(p_to), vformat("Animation '%s' already exists.", p_to));
+ animations[p_to] = animations[p_from];
+}
+
void SpriteFrames::remove_animation(const StringName &p_anim) {
animations.erase(p_anim);
}
@@ -246,6 +252,7 @@ void SpriteFrames::get_argument_options(const StringName &p_function, int p_idx,
void SpriteFrames::_bind_methods() {
ClassDB::bind_method(D_METHOD("add_animation", "anim"), &SpriteFrames::add_animation);
ClassDB::bind_method(D_METHOD("has_animation", "anim"), &SpriteFrames::has_animation);
+ ClassDB::bind_method(D_METHOD("duplicate_animation", "anim_from", "anim_to"), &SpriteFrames::duplicate_animation);
ClassDB::bind_method(D_METHOD("remove_animation", "anim"), &SpriteFrames::remove_animation);
ClassDB::bind_method(D_METHOD("rename_animation", "anim", "newname"), &SpriteFrames::rename_animation);
diff --git a/scene/resources/sprite_frames.h b/scene/resources/sprite_frames.h
index 0e0bb28d71..8d5b4232cf 100644
--- a/scene/resources/sprite_frames.h
+++ b/scene/resources/sprite_frames.h
@@ -60,6 +60,7 @@ protected:
public:
void add_animation(const StringName &p_anim);
bool has_animation(const StringName &p_anim) const;
+ void duplicate_animation(const StringName &p_from, const StringName &p_to);
void remove_animation(const StringName &p_anim);
void rename_animation(const StringName &p_prev, const StringName &p_next);
diff --git a/scene/resources/style_box_flat.cpp b/scene/resources/style_box_flat.cpp
index 52d02e92cb..60b91ef0cb 100644
--- a/scene/resources/style_box_flat.cpp
+++ b/scene/resources/style_box_flat.cpp
@@ -300,8 +300,8 @@ inline void draw_rounded_rectangle(Vector<Vector2> &verts, Vector<int> &indices,
const real_t x = radius * (real_t)cos((corner_index + detail / (double)adapted_corner_detail) * (Math_TAU / 4.0) + Math_PI) + corner_point.x;
const real_t y = radius * (real_t)sin((corner_index + detail / (double)adapted_corner_detail) * (Math_TAU / 4.0) + Math_PI) + corner_point.y;
- const float x_skew = -skew.x * (y - ring_rect.get_center().y);
- const float y_skew = -skew.y * (x - ring_rect.get_center().x);
+ const float x_skew = -skew.x * (y - style_rect.get_center().y);
+ const float y_skew = -skew.y * (x - style_rect.get_center().x);
verts.push_back(Vector2(x + x_skew, y + y_skew));
colors.push_back(color);
}
diff --git a/scene/resources/surface_tool.cpp b/scene/resources/surface_tool.cpp
index 3273bc3c00..6921885ee0 100644
--- a/scene/resources/surface_tool.cpp
+++ b/scene/resources/surface_tool.cpp
@@ -792,7 +792,7 @@ void SurfaceTool::deindex() {
}
void SurfaceTool::_create_list(const Ref<Mesh> &p_existing, int p_surface, LocalVector<Vertex> *r_vertex, LocalVector<int> *r_index, uint64_t &lformat) {
- ERR_FAIL_NULL_MSG(p_existing, "First argument in SurfaceTool::_create_list() must be a valid object of type Mesh");
+ ERR_FAIL_COND_MSG(p_existing.is_null(), "First argument in SurfaceTool::_create_list() must be a valid object of type Mesh");
Array arr = p_existing->surface_get_arrays(p_surface);
ERR_FAIL_COND(arr.size() != RS::ARRAY_MAX);
@@ -968,7 +968,7 @@ void SurfaceTool::create_from_triangle_arrays(const Array &p_arrays) {
}
void SurfaceTool::create_from(const Ref<Mesh> &p_existing, int p_surface) {
- ERR_FAIL_NULL_MSG(p_existing, "First argument in SurfaceTool::create_from() must be a valid object of type Mesh");
+ ERR_FAIL_COND_MSG(p_existing.is_null(), "First argument in SurfaceTool::create_from() must be a valid object of type Mesh");
clear();
primitive = p_existing->surface_get_primitive_type(p_surface);
@@ -983,7 +983,7 @@ void SurfaceTool::create_from(const Ref<Mesh> &p_existing, int p_surface) {
}
void SurfaceTool::create_from_blend_shape(const Ref<Mesh> &p_existing, int p_surface, const String &p_blend_shape_name) {
- ERR_FAIL_NULL_MSG(p_existing, "First argument in SurfaceTool::create_from_blend_shape() must be a valid object of type Mesh");
+ ERR_FAIL_COND_MSG(p_existing.is_null(), "First argument in SurfaceTool::create_from_blend_shape() must be a valid object of type Mesh");
clear();
primitive = p_existing->surface_get_primitive_type(p_surface);
@@ -1023,7 +1023,7 @@ void SurfaceTool::create_from_blend_shape(const Ref<Mesh> &p_existing, int p_sur
}
void SurfaceTool::append_from(const Ref<Mesh> &p_existing, int p_surface, const Transform3D &p_xform) {
- ERR_FAIL_NULL_MSG(p_existing, "First argument in SurfaceTool::append_from() must be a valid object of type Mesh");
+ ERR_FAIL_COND_MSG(p_existing.is_null(), "First argument in SurfaceTool::append_from() must be a valid object of type Mesh");
if (vertex_array.size() == 0) {
primitive = p_existing->surface_get_primitive_type(p_surface);
diff --git a/scene/resources/text_paragraph.cpp b/scene/resources/text_paragraph.cpp
index 5da47966dd..29a8541cb0 100644
--- a/scene/resources/text_paragraph.cpp
+++ b/scene/resources/text_paragraph.cpp
@@ -173,6 +173,7 @@ void TextParagraph::_shape_lines() {
v_offset = TS->shaped_text_get_size(dropcap_rid).x + dropcap_margins.size.x + dropcap_margins.position.x;
}
+ Size2i range = TS->shaped_text_get_range(rid);
if (h_offset > 0) {
// Dropcap, flow around.
PackedInt32Array line_breaks = TS->shaped_text_get_line_breaks(rid, width - h_offset, 0, brk_flags);
@@ -182,7 +183,7 @@ void TextParagraph::_shape_lines() {
if (!tab_stops.is_empty()) {
TS->shaped_text_tab_align(line, tab_stops);
}
- start = line_breaks[i + 1];
+ start = (i < line_breaks.size() - 2) ? line_breaks[i + 2] : range.y;
lines_rid.push_back(line);
if (v_offset < h) {
break;
@@ -192,13 +193,15 @@ void TextParagraph::_shape_lines() {
}
}
// Use fixed for the rest of lines.
- PackedInt32Array line_breaks = TS->shaped_text_get_line_breaks(rid, width, start, brk_flags);
- for (int i = 0; i < line_breaks.size(); i = i + 2) {
- RID line = TS->shaped_text_substr(rid, line_breaks[i], line_breaks[i + 1] - line_breaks[i]);
- if (!tab_stops.is_empty()) {
- TS->shaped_text_tab_align(line, tab_stops);
+ if (start == 0 || start < range.y) {
+ PackedInt32Array line_breaks = TS->shaped_text_get_line_breaks(rid, width, start, brk_flags);
+ for (int i = 0; i < line_breaks.size(); i = i + 2) {
+ RID line = TS->shaped_text_substr(rid, line_breaks[i], line_breaks[i + 1] - line_breaks[i]);
+ if (!tab_stops.is_empty()) {
+ TS->shaped_text_tab_align(line, tab_stops);
+ }
+ lines_rid.push_back(line);
}
- lines_rid.push_back(line);
}
BitField<TextServer::TextOverrunFlag> overrun_flags = TextServer::OVERRUN_NO_TRIM;
@@ -550,18 +553,42 @@ Size2 TextParagraph::get_size() const {
_THREAD_SAFE_METHOD_
const_cast<TextParagraph *>(this)->_shape_lines();
+
+ float h_offset = 0.f;
+ float v_offset = 0.f;
+ if (TS->shaped_text_get_orientation(dropcap_rid) == TextServer::ORIENTATION_HORIZONTAL) {
+ h_offset = TS->shaped_text_get_size(dropcap_rid).x + dropcap_margins.size.x + dropcap_margins.position.x;
+ v_offset = TS->shaped_text_get_size(dropcap_rid).y + dropcap_margins.size.y + dropcap_margins.position.y;
+ } else {
+ h_offset = TS->shaped_text_get_size(dropcap_rid).y + dropcap_margins.size.y + dropcap_margins.position.y;
+ v_offset = TS->shaped_text_get_size(dropcap_rid).x + dropcap_margins.size.x + dropcap_margins.position.x;
+ }
+
Size2 size;
int visible_lines = (max_lines_visible >= 0) ? MIN(max_lines_visible, (int)lines_rid.size()) : (int)lines_rid.size();
for (int i = 0; i < visible_lines; i++) {
Size2 lsize = TS->shaped_text_get_size(lines_rid[i]);
if (TS->shaped_text_get_orientation(lines_rid[i]) == TextServer::ORIENTATION_HORIZONTAL) {
+ if (h_offset > 0 && i <= dropcap_lines) {
+ lsize.x += h_offset;
+ }
size.x = MAX(size.x, lsize.x);
size.y += lsize.y;
} else {
+ if (h_offset > 0 && i <= dropcap_lines) {
+ lsize.y += h_offset;
+ }
size.x += lsize.x;
size.y = MAX(size.y, lsize.y);
}
}
+ if (h_offset > 0) {
+ if (TS->shaped_text_get_orientation(dropcap_rid) == TextServer::ORIENTATION_HORIZONTAL) {
+ size.y = MAX(size.y, v_offset);
+ } else {
+ size.x = MAX(size.x, v_offset);
+ }
+ }
return size;
}
@@ -624,7 +651,7 @@ Rect2 TextParagraph::get_line_object_rect(int p_line, Variant p_key) const {
ofs.x += TS->shaped_text_get_ascent(lines_rid[i]);
if (i <= dropcap_lines) {
if (TS->shaped_text_get_inferred_direction(dropcap_rid) == TextServer::DIRECTION_LTR) {
- ofs.x -= h_offset;
+ ofs.y -= h_offset;
}
l_width -= h_offset;
}
@@ -793,7 +820,7 @@ void TextParagraph::draw(RID p_canvas, const Vector2 &p_pos, const Color &p_colo
ofs.x += TS->shaped_text_get_ascent(lines_rid[i]);
if (i <= dropcap_lines) {
if (TS->shaped_text_get_inferred_direction(dropcap_rid) == TextServer::DIRECTION_LTR) {
- ofs.x -= h_offset;
+ ofs.y -= h_offset;
}
l_width -= h_offset;
}
@@ -895,7 +922,7 @@ void TextParagraph::draw_outline(RID p_canvas, const Vector2 &p_pos, int p_outli
ofs.x += TS->shaped_text_get_ascent(lines_rid[i]);
if (i <= dropcap_lines) {
if (TS->shaped_text_get_inferred_direction(dropcap_rid) == TextServer::DIRECTION_LTR) {
- ofs.x -= h_offset;
+ ofs.y -= h_offset;
}
l_width -= h_offset;
}
diff --git a/scene/scene_string_names.cpp b/scene/scene_string_names.cpp
index 40f3ddf048..f8a0336b37 100644
--- a/scene/scene_string_names.cpp
+++ b/scene/scene_string_names.cpp
@@ -132,6 +132,7 @@ SceneStringNames::SceneStringNames() {
pressed = StaticCString::create("pressed");
id_pressed = StaticCString::create("id_pressed");
+ toggled = StaticCString::create("toggled");
panel = StaticCString::create("panel");
diff --git a/scene/scene_string_names.h b/scene/scene_string_names.h
index 7893d213e4..381a161ad5 100644
--- a/scene/scene_string_names.h
+++ b/scene/scene_string_names.h
@@ -145,6 +145,7 @@ public:
StringName pressed;
StringName id_pressed;
+ StringName toggled;
StringName panel;
diff --git a/scene/theme/default_theme.cpp b/scene/theme/default_theme.cpp
index 8a9e784c47..749d4e3530 100644
--- a/scene/theme/default_theme.cpp
+++ b/scene/theme/default_theme.cpp
@@ -648,7 +648,9 @@ void fill_default_theme(Ref<Theme> &theme, const Ref<Font> &default_font, const
theme->set_constant("buttons_vertical_separation", "SpinBox", 0);
theme->set_constant("field_and_buttons_separation", "SpinBox", 2);
theme->set_constant("buttons_width", "SpinBox", 16);
+#ifndef DISABLE_DEPRECATED
theme->set_constant("set_min_buttons_width_from_icons", "SpinBox", 1);
+#endif
// ScrollContainer
diff --git a/scene/theme/icons/default_theme_icons_builders.py b/scene/theme/icons/default_theme_icons_builders.py
index 49c0a93191..3a673af92e 100644
--- a/scene/theme/icons/default_theme_icons_builders.py
+++ b/scene/theme/icons/default_theme_icons_builders.py
@@ -3,6 +3,8 @@
import os
from io import StringIO
+from methods import to_raw_cstring
+
# See also `editor/icons/editor_icons_builders.py`.
def make_default_theme_icons_action(target, source, env):
@@ -10,21 +12,9 @@ def make_default_theme_icons_action(target, source, env):
svg_icons = [str(x) for x in source]
with StringIO() as icons_string, StringIO() as s:
- for f in svg_icons:
- fname = str(f)
-
- icons_string.write('\t"')
-
- with open(fname, "rb") as svgf:
- b = svgf.read(1)
- while len(b) == 1:
- icons_string.write("\\" + str(hex(ord(b)))[1:])
- b = svgf.read(1)
-
- icons_string.write('"')
- if fname != svg_icons[-1]:
- icons_string.write(",")
- icons_string.write("\n")
+ for svg in svg_icons:
+ with open(svg, "r") as svgf:
+ icons_string.write("\t%s,\n" % to_raw_cstring(svgf.read()))
s.write("/* THIS FILE IS GENERATED DO NOT EDIT */\n\n")
s.write('#include "modules/modules_enabled.gen.h"\n\n')
diff --git a/servers/audio/audio_stream.cpp b/servers/audio/audio_stream.cpp
index e2c8686911..ece088a694 100644
--- a/servers/audio/audio_stream.cpp
+++ b/servers/audio/audio_stream.cpp
@@ -173,12 +173,12 @@ int AudioStreamPlaybackResampled::mix(AudioFrame *p_buffer, float p_rate_scale,
}
float mu2 = mu * mu;
- AudioFrame a0 = 3 * y1 - 3 * y2 + y3 - y0;
- AudioFrame a1 = 2 * y0 - 5 * y1 + 4 * y2 - y3;
- AudioFrame a2 = y2 - y0;
- AudioFrame a3 = 2 * y1;
+ float h11 = mu2 * (mu - 1);
+ float z = mu2 - h11;
+ float h01 = z - h11;
+ float h10 = mu - z;
- p_buffer[i] = (a0 * mu * mu2 + a1 * mu2 + a2 * mu + a3) / 2;
+ p_buffer[i] = y1 + (y2 - y1) * h01 + ((y2 - y0) * h10 + (y3 - y1) * h11) * 0.5;
mix_offset += mix_increment;
diff --git a/servers/audio/effects/audio_effect_record.cpp b/servers/audio/effects/audio_effect_record.cpp
index e30a8fa99e..6f358d54bb 100644
--- a/servers/audio/effects/audio_effect_record.cpp
+++ b/servers/audio/effects/audio_effect_record.cpp
@@ -149,7 +149,7 @@ Ref<AudioEffectInstance> AudioEffectRecord::instantiate() {
ensure_thread_stopped();
bool is_currently_recording = false;
- if (current_instance != nullptr) {
+ if (current_instance.is_valid()) {
is_currently_recording = current_instance->is_recording;
}
if (is_currently_recording) {
@@ -161,28 +161,28 @@ Ref<AudioEffectInstance> AudioEffectRecord::instantiate() {
}
void AudioEffectRecord::ensure_thread_stopped() {
- if (current_instance != nullptr) {
+ if (current_instance.is_valid()) {
current_instance->finish();
}
}
void AudioEffectRecord::set_recording_active(bool p_record) {
if (p_record) {
- if (current_instance == nullptr) {
+ if (current_instance.is_null()) {
WARN_PRINT("Recording should not be set as active before Godot has initialized.");
return;
}
ensure_thread_stopped();
current_instance->init();
} else {
- if (current_instance != nullptr) {
+ if (current_instance.is_valid()) {
current_instance->is_recording = false;
}
}
}
bool AudioEffectRecord::is_recording_active() const {
- if (current_instance == nullptr) {
+ if (current_instance.is_null()) {
return false;
} else {
return current_instance->is_recording;
@@ -283,7 +283,7 @@ void AudioEffectRecord::_bind_methods() {
ClassDB::bind_method(D_METHOD("get_format"), &AudioEffectRecord::get_format);
ClassDB::bind_method(D_METHOD("get_recording"), &AudioEffectRecord::get_recording);
- ADD_PROPERTY(PropertyInfo(Variant::INT, "format", PROPERTY_HINT_ENUM, "8-Bit,16-Bit,IMA-ADPCM"), "set_format", "get_format");
+ ADD_PROPERTY(PropertyInfo(Variant::INT, "format", PROPERTY_HINT_ENUM, "8-Bit,16-Bit,IMA ADPCM,Quite OK Audio"), "set_format", "get_format");
}
AudioEffectRecord::AudioEffectRecord() {
diff --git a/servers/audio_server.cpp b/servers/audio_server.cpp
index f0f894d03b..332f8984a2 100644
--- a/servers/audio_server.cpp
+++ b/servers/audio_server.cpp
@@ -486,10 +486,16 @@ void AudioServer::_mix_step() {
}
// Copy the bus details we mixed with to the previous bus details to maintain volume ramps.
- std::copy(std::begin(bus_details.bus_active), std::end(bus_details.bus_active), std::begin(playback->prev_bus_details->bus_active));
- std::copy(std::begin(bus_details.bus), std::end(bus_details.bus), std::begin(playback->prev_bus_details->bus));
- for (int bus_idx = 0; bus_idx < MAX_BUSES_PER_PLAYBACK; bus_idx++) {
- std::copy(std::begin(bus_details.volume[bus_idx]), std::end(bus_details.volume[bus_idx]), std::begin(playback->prev_bus_details->volume[bus_idx]));
+ for (int i = 0; i < MAX_BUSES_PER_PLAYBACK; i++) {
+ playback->prev_bus_details->bus_active[i] = bus_details.bus_active[i];
+ }
+ for (int i = 0; i < MAX_BUSES_PER_PLAYBACK; i++) {
+ playback->prev_bus_details->bus[i] = bus_details.bus[i];
+ }
+ for (int i = 0; i < MAX_BUSES_PER_PLAYBACK; i++) {
+ for (int j = 0; j < MAX_CHANNELS_PER_BUS; j++) {
+ playback->prev_bus_details->volume[i][j] = bus_details.volume[i][j];
+ }
}
switch (playback->state.load()) {
@@ -497,7 +503,7 @@ void AudioServer::_mix_step() {
case AudioStreamPlaybackListNode::FADE_OUT_TO_DELETION:
playback_list.erase(playback, [](AudioStreamPlaybackListNode *p) {
delete p->prev_bus_details;
- delete p->bus_details;
+ delete p->bus_details.load();
p->stream_playback.unref();
delete p;
});
@@ -1199,7 +1205,7 @@ void AudioServer::start_playback_stream(Ref<AudioStreamPlayback> p_playback, con
}
idx++;
}
- playback_node->bus_details = new_bus_details;
+ playback_node->bus_details.store(new_bus_details);
playback_node->prev_bus_details = new AudioStreamPlaybackBusDetails();
playback_node->pitch_scale.set(p_pitch_scale);
diff --git a/servers/display_server.cpp b/servers/display_server.cpp
index 8cb3e560ac..12456fc828 100644
--- a/servers/display_server.cpp
+++ b/servers/display_server.cpp
@@ -41,6 +41,9 @@
#if defined(D3D12_ENABLED)
#include "drivers/d3d12/rendering_context_driver_d3d12.h"
#endif
+#if defined(METAL_ENABLED)
+#include "drivers/metal/rendering_context_driver_metal.h"
+#endif
DisplayServer *DisplayServer::singleton = nullptr;
@@ -1232,6 +1235,15 @@ bool DisplayServer::can_create_rendering_device() {
rcd = memnew(RenderingContextDriverD3D12);
}
#endif
+#ifdef METAL_ENABLED
+ if (rcd == nullptr) {
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wunguarded-availability"
+ // Eliminate "RenderingContextDriverMetal is only available on iOS 14.0 or newer".
+ rcd = memnew(RenderingContextDriverMetal);
+#pragma clang diagnostic pop
+ }
+#endif
if (rcd != nullptr) {
err = rcd->initialize();
diff --git a/servers/navigation/navigation_globals.h b/servers/navigation/navigation_globals.h
new file mode 100644
index 0000000000..aa54f95519
--- /dev/null
+++ b/servers/navigation/navigation_globals.h
@@ -0,0 +1,66 @@
+/**************************************************************************/
+/* navigation_globals.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 NAVIGATION_GLOBALS_H
+#define NAVIGATION_GLOBALS_H
+
+namespace NavigationDefaults3D {
+
+// Rasterization.
+
+// To find the polygons edges the vertices are displaced in a grid where
+// each cell has the following cell_size and cell_height.
+constexpr float navmesh_cell_size{ 0.25f }; // Must match ProjectSettings default 3D cell_size and NavigationMesh cell_size.
+constexpr float navmesh_cell_height{ 0.25f }; // Must match ProjectSettings default 3D cell_height and NavigationMesh cell_height.
+constexpr auto navmesh_cell_size_hint{ "0.001,100,0.001,or_greater" };
+
+// Map.
+
+constexpr float edge_connection_margin{ 0.25f };
+constexpr float link_connection_radius{ 1.0f };
+
+} //namespace NavigationDefaults3D
+
+namespace NavigationDefaults2D {
+
+// Rasterization.
+
+// Same as in 3D but larger since 1px is treated as 1m.
+constexpr float navmesh_cell_size{ 1.0f }; // Must match ProjectSettings default 2D cell_size.
+constexpr auto navmesh_cell_size_hint{ "0.001,100,0.001,or_greater" };
+
+// Map.
+
+constexpr float edge_connection_margin{ 1.0f };
+constexpr float link_connection_radius{ 4.0f };
+
+} //namespace NavigationDefaults2D
+
+#endif // NAVIGATION_GLOBALS_H
diff --git a/servers/navigation_server_3d.cpp b/servers/navigation_server_3d.cpp
index 398ff1e1f3..f4ffcf5a3e 100644
--- a/servers/navigation_server_3d.cpp
+++ b/servers/navigation_server_3d.cpp
@@ -32,6 +32,7 @@
#include "core/config/project_settings.h"
#include "scene/main/node.h"
+#include "servers/navigation/navigation_globals.h"
NavigationServer3D *NavigationServer3D::singleton = nullptr;
@@ -227,18 +228,18 @@ NavigationServer3D::NavigationServer3D() {
ERR_FAIL_COND(singleton != nullptr);
singleton = this;
- GLOBAL_DEF_BASIC(PropertyInfo(Variant::FLOAT, "navigation/2d/default_cell_size", PROPERTY_HINT_RANGE, "0.001,100,0.001,or_greater"), 1.0);
+ GLOBAL_DEF_BASIC(PropertyInfo(Variant::FLOAT, "navigation/2d/default_cell_size", PROPERTY_HINT_RANGE, NavigationDefaults2D::navmesh_cell_size_hint), NavigationDefaults2D::navmesh_cell_size);
GLOBAL_DEF("navigation/2d/use_edge_connections", true);
- GLOBAL_DEF_BASIC("navigation/2d/default_edge_connection_margin", 1.0);
- GLOBAL_DEF_BASIC("navigation/2d/default_link_connection_radius", 4.0);
+ GLOBAL_DEF_BASIC("navigation/2d/default_edge_connection_margin", NavigationDefaults2D::edge_connection_margin);
+ GLOBAL_DEF_BASIC("navigation/2d/default_link_connection_radius", NavigationDefaults2D::link_connection_radius);
- GLOBAL_DEF_BASIC(PropertyInfo(Variant::FLOAT, "navigation/3d/default_cell_size", PROPERTY_HINT_RANGE, "0.001,100,0.001,or_greater"), 0.25);
- GLOBAL_DEF_BASIC("navigation/3d/default_cell_height", 0.25);
+ GLOBAL_DEF_BASIC(PropertyInfo(Variant::FLOAT, "navigation/3d/default_cell_size", PROPERTY_HINT_RANGE, NavigationDefaults3D::navmesh_cell_size_hint), NavigationDefaults3D::navmesh_cell_size);
+ GLOBAL_DEF_BASIC("navigation/3d/default_cell_height", NavigationDefaults3D::navmesh_cell_height);
GLOBAL_DEF("navigation/3d/default_up", Vector3(0, 1, 0));
GLOBAL_DEF(PropertyInfo(Variant::FLOAT, "navigation/3d/merge_rasterizer_cell_scale", PROPERTY_HINT_RANGE, "0.001,1,0.001,or_greater"), 1.0);
GLOBAL_DEF("navigation/3d/use_edge_connections", true);
- GLOBAL_DEF_BASIC("navigation/3d/default_edge_connection_margin", 0.25);
- GLOBAL_DEF_BASIC("navigation/3d/default_link_connection_radius", 1.0);
+ GLOBAL_DEF_BASIC("navigation/3d/default_edge_connection_margin", NavigationDefaults3D::edge_connection_margin);
+ GLOBAL_DEF_BASIC("navigation/3d/default_link_connection_radius", NavigationDefaults3D::link_connection_radius);
GLOBAL_DEF("navigation/avoidance/thread_model/avoidance_use_multiple_threads", true);
GLOBAL_DEF("navigation/avoidance/thread_model/avoidance_use_high_priority_threads", true);
diff --git a/servers/rendering/renderer_rd/effects/taa.cpp b/servers/rendering/renderer_rd/effects/taa.cpp
index c1037ec11a..4ac5552aa6 100644
--- a/servers/rendering/renderer_rd/effects/taa.cpp
+++ b/servers/rendering/renderer_rd/effects/taa.cpp
@@ -62,8 +62,8 @@ void TAA::resolve(RID p_frame, RID p_temp, RID p_depth, RID p_velocity, RID p_pr
memset(&push_constant, 0, sizeof(TAAResolvePushConstant));
push_constant.resolution_width = p_resolution.width;
push_constant.resolution_height = p_resolution.height;
- push_constant.disocclusion_threshold = 0.025f;
- push_constant.disocclusion_scale = 10.0f;
+ push_constant.disocclusion_threshold = 2.5f; // If velocity changes by less than this amount of texels we can retain the accumulation buffer.
+ push_constant.disocclusion_scale = 0.01f; // Scale the weight of this pixel calculated as (change in velocity - threshold) * scale.
RD::ComputeListID compute_list = RD::get_singleton()->compute_list_begin();
RD::get_singleton()->compute_list_bind_compute_pipeline(compute_list, pipeline);
diff --git a/servers/rendering/renderer_rd/environment/sky.cpp b/servers/rendering/renderer_rd/environment/sky.cpp
index 5dc67725f1..2087989102 100644
--- a/servers/rendering/renderer_rd/environment/sky.cpp
+++ b/servers/rendering/renderer_rd/environment/sky.cpp
@@ -545,7 +545,6 @@ void SkyRD::Sky::free() {
}
if (material.is_valid()) {
- RSG::material_storage->material_free(material);
material = RID();
}
}
@@ -1251,7 +1250,7 @@ void SkyRD::update_radiance_buffers(Ref<RenderSceneBuffersRD> p_render_buffers,
RS::SkyMode sky_mode = sky->mode;
if (sky_mode == RS::SKY_MODE_AUTOMATIC) {
- if (shader_data->uses_time || shader_data->uses_position) {
+ if ((shader_data->uses_time || shader_data->uses_position) && sky->radiance_size == 256) {
update_single_frame = true;
sky_mode = RS::SKY_MODE_REALTIME;
} else if (shader_data->uses_light || shader_data->ubo_size > 0) {
diff --git a/servers/rendering/renderer_rd/forward_clustered/render_forward_clustered.cpp b/servers/rendering/renderer_rd/forward_clustered/render_forward_clustered.cpp
index b97e38da4d..36bd22b723 100644
--- a/servers/rendering/renderer_rd/forward_clustered/render_forward_clustered.cpp
+++ b/servers/rendering/renderer_rd/forward_clustered/render_forward_clustered.cpp
@@ -2231,7 +2231,7 @@ void RenderForwardClustered::_render_scene(RenderDataRD *p_render_data, const Co
}
RID alpha_framebuffer = rb_data.is_valid() ? rb_data->get_color_pass_fb(transparent_color_pass_flags) : color_only_framebuffer;
- RenderListParameters render_list_params(render_list[RENDER_LIST_ALPHA].elements.ptr(), render_list[RENDER_LIST_ALPHA].element_info.ptr(), render_list[RENDER_LIST_ALPHA].elements.size(), false, PASS_MODE_COLOR, transparent_color_pass_flags, rb_data.is_null(), p_render_data->directional_light_soft_shadows, rp_uniform_set, get_debug_draw_mode() == RS::VIEWPORT_DEBUG_DRAW_WIREFRAME, Vector2(), p_render_data->scene_data->lod_distance_multiplier, p_render_data->scene_data->screen_mesh_lod_threshold, p_render_data->scene_data->view_count, 0, spec_constant_base_flags);
+ RenderListParameters render_list_params(render_list[RENDER_LIST_ALPHA].elements.ptr(), render_list[RENDER_LIST_ALPHA].element_info.ptr(), render_list[RENDER_LIST_ALPHA].elements.size(), reverse_cull, PASS_MODE_COLOR, transparent_color_pass_flags, rb_data.is_null(), p_render_data->directional_light_soft_shadows, rp_uniform_set, get_debug_draw_mode() == RS::VIEWPORT_DEBUG_DRAW_WIREFRAME, Vector2(), p_render_data->scene_data->lod_distance_multiplier, p_render_data->scene_data->screen_mesh_lod_threshold, p_render_data->scene_data->view_count, 0, spec_constant_base_flags);
_render_list_with_draw_list(&render_list_params, alpha_framebuffer, RD::INITIAL_ACTION_LOAD, RD::FINAL_ACTION_STORE, RD::INITIAL_ACTION_LOAD, RD::FINAL_ACTION_STORE);
}
diff --git a/servers/rendering/renderer_rd/shaders/effects/taa_resolve.glsl b/servers/rendering/renderer_rd/shaders/effects/taa_resolve.glsl
index e50397cc5d..00ac6a7919 100644
--- a/servers/rendering/renderer_rd/shaders/effects/taa_resolve.glsl
+++ b/servers/rendering/renderer_rd/shaders/effects/taa_resolve.glsl
@@ -307,6 +307,8 @@ float luminance(vec3 color) {
return max(dot(color, lumCoeff), 0.0001f);
}
+// This is "velocity disocclusion" as described by https://www.elopezr.com/temporal-aa-and-the-quest-for-the-holy-trail/.
+// We use texel space, so our scale and threshold differ.
float get_factor_disocclusion(vec2 uv_reprojected, vec2 velocity) {
vec2 velocity_previous = imageLoad(last_velocity_buffer, ivec2(uv_reprojected * params.resolution)).xy;
vec2 velocity_texels = velocity * params.resolution;
@@ -336,7 +338,7 @@ vec3 temporal_antialiasing(uvec2 pos_group_top_left, uvec2 pos_group, uvec2 pos_
// Compute blend factor
float blend_factor = RPC_16; // We want to be able to accumulate as many jitter samples as we generated, that is, 16.
{
- // If re-projected UV is out of screen, converge to current color immediatel
+ // If re-projected UV is out of screen, converge to current color immediately.
float factor_screen = any(lessThan(uv_reprojected, vec2(0.0))) || any(greaterThan(uv_reprojected, vec2(1.0))) ? 1.0 : 0.0;
// Increase blend factor when there is disocclusion (fixes a lot of the remaining ghosting).
diff --git a/servers/rendering/renderer_rd/shaders/forward_clustered/scene_forward_clustered.glsl b/servers/rendering/renderer_rd/shaders/forward_clustered/scene_forward_clustered.glsl
index 35d29fed6a..cfde97af95 100644
--- a/servers/rendering/renderer_rd/shaders/forward_clustered/scene_forward_clustered.glsl
+++ b/servers/rendering/renderer_rd/shaders/forward_clustered/scene_forward_clustered.glsl
@@ -1513,7 +1513,6 @@ void fragment_shader(in SceneData scene_data) {
if (uses_sh) {
uvw.z *= 4.0; //SH textures use 4 times more data
-
vec3 lm_light_l0;
vec3 lm_light_l1n1;
vec3 lm_light_l1_0;
@@ -1521,23 +1520,23 @@ void fragment_shader(in SceneData scene_data) {
if (sc_use_lightmap_bicubic_filter) {
lm_light_l0 = textureArray_bicubic(lightmap_textures[ofs], uvw + vec3(0.0, 0.0, 0.0), lightmaps.data[ofs].light_texture_size).rgb;
- lm_light_l1n1 = textureArray_bicubic(lightmap_textures[ofs], uvw + vec3(0.0, 0.0, 1.0), lightmaps.data[ofs].light_texture_size).rgb;
- lm_light_l1_0 = textureArray_bicubic(lightmap_textures[ofs], uvw + vec3(0.0, 0.0, 2.0), lightmaps.data[ofs].light_texture_size).rgb;
- lm_light_l1p1 = textureArray_bicubic(lightmap_textures[ofs], uvw + vec3(0.0, 0.0, 3.0), lightmaps.data[ofs].light_texture_size).rgb;
+ lm_light_l1n1 = (textureArray_bicubic(lightmap_textures[ofs], uvw + vec3(0.0, 0.0, 1.0), lightmaps.data[ofs].light_texture_size).rgb - vec3(0.5)) * 2.0;
+ lm_light_l1_0 = (textureArray_bicubic(lightmap_textures[ofs], uvw + vec3(0.0, 0.0, 2.0), lightmaps.data[ofs].light_texture_size).rgb - vec3(0.5)) * 2.0;
+ lm_light_l1p1 = (textureArray_bicubic(lightmap_textures[ofs], uvw + vec3(0.0, 0.0, 3.0), lightmaps.data[ofs].light_texture_size).rgb - vec3(0.5)) * 2.0;
} else {
lm_light_l0 = textureLod(sampler2DArray(lightmap_textures[ofs], SAMPLER_LINEAR_CLAMP), uvw + vec3(0.0, 0.0, 0.0), 0.0).rgb;
- lm_light_l1n1 = textureLod(sampler2DArray(lightmap_textures[ofs], SAMPLER_LINEAR_CLAMP), uvw + vec3(0.0, 0.0, 1.0), 0.0).rgb;
- lm_light_l1_0 = textureLod(sampler2DArray(lightmap_textures[ofs], SAMPLER_LINEAR_CLAMP), uvw + vec3(0.0, 0.0, 2.0), 0.0).rgb;
- lm_light_l1p1 = textureLod(sampler2DArray(lightmap_textures[ofs], SAMPLER_LINEAR_CLAMP), uvw + vec3(0.0, 0.0, 3.0), 0.0).rgb;
+ lm_light_l1n1 = (textureLod(sampler2DArray(lightmap_textures[ofs], SAMPLER_LINEAR_CLAMP), uvw + vec3(0.0, 0.0, 1.0), 0.0).rgb - vec3(0.5)) * 2.0;
+ lm_light_l1_0 = (textureLod(sampler2DArray(lightmap_textures[ofs], SAMPLER_LINEAR_CLAMP), uvw + vec3(0.0, 0.0, 2.0), 0.0).rgb - vec3(0.5)) * 2.0;
+ lm_light_l1p1 = (textureLod(sampler2DArray(lightmap_textures[ofs], SAMPLER_LINEAR_CLAMP), uvw + vec3(0.0, 0.0, 3.0), 0.0).rgb - vec3(0.5)) * 2.0;
}
vec3 n = normalize(lightmaps.data[ofs].normal_xform * normal);
float en = lightmaps.data[ofs].exposure_normalization;
ambient_light += lm_light_l0 * en;
- ambient_light += lm_light_l1n1 * n.y * en;
- ambient_light += lm_light_l1_0 * n.z * en;
- ambient_light += lm_light_l1p1 * n.x * en;
+ ambient_light += lm_light_l1n1 * n.y * (lm_light_l0 * en * 4.0);
+ ambient_light += lm_light_l1_0 * n.z * (lm_light_l0 * en * 4.0);
+ ambient_light += lm_light_l1p1 * n.x * (lm_light_l0 * en * 4.0);
} else {
if (sc_use_lightmap_bicubic_filter) {
@@ -2065,7 +2064,7 @@ void fragment_shader(in SceneData scene_data) {
#ifdef LIGHT_TRANSMITTANCE_USED
float transmittance_z = transmittance_depth;
-
+#ifndef SHADOWS_DISABLED
if (directional_lights.data[i].shadow_opacity > 0.001) {
float depth_z = -vertex.z;
@@ -2112,7 +2111,8 @@ void fragment_shader(in SceneData scene_data) {
transmittance_z = z - shadow_z;
}
}
-#endif
+#endif // !SHADOWS_DISABLED
+#endif // LIGHT_TRANSMITTANCE_USED
float shadow = 1.0;
#ifndef SHADOWS_DISABLED
diff --git a/servers/rendering/renderer_rd/shaders/forward_mobile/scene_forward_mobile.glsl b/servers/rendering/renderer_rd/shaders/forward_mobile/scene_forward_mobile.glsl
index c266161834..b21769f207 100644
--- a/servers/rendering/renderer_rd/shaders/forward_mobile/scene_forward_mobile.glsl
+++ b/servers/rendering/renderer_rd/shaders/forward_mobile/scene_forward_mobile.glsl
@@ -1280,23 +1280,23 @@ void main() {
if (sc_use_lightmap_bicubic_filter) {
lm_light_l0 = textureArray_bicubic(lightmap_textures[ofs], uvw + vec3(0.0, 0.0, 0.0), lightmaps.data[ofs].light_texture_size).rgb;
- lm_light_l1n1 = textureArray_bicubic(lightmap_textures[ofs], uvw + vec3(0.0, 0.0, 1.0), lightmaps.data[ofs].light_texture_size).rgb;
- lm_light_l1_0 = textureArray_bicubic(lightmap_textures[ofs], uvw + vec3(0.0, 0.0, 2.0), lightmaps.data[ofs].light_texture_size).rgb;
- lm_light_l1p1 = textureArray_bicubic(lightmap_textures[ofs], uvw + vec3(0.0, 0.0, 3.0), lightmaps.data[ofs].light_texture_size).rgb;
+ lm_light_l1n1 = (textureArray_bicubic(lightmap_textures[ofs], uvw + vec3(0.0, 0.0, 1.0), lightmaps.data[ofs].light_texture_size).rgb - vec3(0.5)) * 2.0;
+ lm_light_l1_0 = (textureArray_bicubic(lightmap_textures[ofs], uvw + vec3(0.0, 0.0, 2.0), lightmaps.data[ofs].light_texture_size).rgb - vec3(0.5)) * 2.0;
+ lm_light_l1p1 = (textureArray_bicubic(lightmap_textures[ofs], uvw + vec3(0.0, 0.0, 3.0), lightmaps.data[ofs].light_texture_size).rgb - vec3(0.5)) * 2.0;
} else {
lm_light_l0 = textureLod(sampler2DArray(lightmap_textures[ofs], SAMPLER_LINEAR_CLAMP), uvw + vec3(0.0, 0.0, 0.0), 0.0).rgb;
- lm_light_l1n1 = textureLod(sampler2DArray(lightmap_textures[ofs], SAMPLER_LINEAR_CLAMP), uvw + vec3(0.0, 0.0, 1.0), 0.0).rgb;
- lm_light_l1_0 = textureLod(sampler2DArray(lightmap_textures[ofs], SAMPLER_LINEAR_CLAMP), uvw + vec3(0.0, 0.0, 2.0), 0.0).rgb;
- lm_light_l1p1 = textureLod(sampler2DArray(lightmap_textures[ofs], SAMPLER_LINEAR_CLAMP), uvw + vec3(0.0, 0.0, 3.0), 0.0).rgb;
+ lm_light_l1n1 = (textureLod(sampler2DArray(lightmap_textures[ofs], SAMPLER_LINEAR_CLAMP), uvw + vec3(0.0, 0.0, 1.0), 0.0).rgb - vec3(0.5)) * 2.0;
+ lm_light_l1_0 = (textureLod(sampler2DArray(lightmap_textures[ofs], SAMPLER_LINEAR_CLAMP), uvw + vec3(0.0, 0.0, 2.0), 0.0).rgb - vec3(0.5)) * 2.0;
+ lm_light_l1p1 = (textureLod(sampler2DArray(lightmap_textures[ofs], SAMPLER_LINEAR_CLAMP), uvw + vec3(0.0, 0.0, 3.0), 0.0).rgb - vec3(0.5)) * 2.0;
}
vec3 n = normalize(lightmaps.data[ofs].normal_xform * normal);
float exposure_normalization = lightmaps.data[ofs].exposure_normalization;
ambient_light += lm_light_l0 * exposure_normalization;
- ambient_light += lm_light_l1n1 * n.y * exposure_normalization;
- ambient_light += lm_light_l1_0 * n.z * exposure_normalization;
- ambient_light += lm_light_l1p1 * n.x * exposure_normalization;
+ ambient_light += lm_light_l1n1 * n.y * (lm_light_l0 * exposure_normalization * 4.0);
+ ambient_light += lm_light_l1_0 * n.z * (lm_light_l0 * exposure_normalization * 4.0);
+ ambient_light += lm_light_l1p1 * n.x * (lm_light_l0 * exposure_normalization * 4.0);
} else {
if (sc_use_lightmap_bicubic_filter) {
ambient_light += textureArray_bicubic(lightmap_textures[ofs], uvw, lightmaps.data[ofs].light_texture_size).rgb * lightmaps.data[ofs].exposure_normalization;
diff --git a/servers/rendering/renderer_rd/shaders/scene_forward_lights_inc.glsl b/servers/rendering/renderer_rd/shaders/scene_forward_lights_inc.glsl
index 40ca74ae07..748fb59531 100644
--- a/servers/rendering/renderer_rd/shaders/scene_forward_lights_inc.glsl
+++ b/servers/rendering/renderer_rd/shaders/scene_forward_lights_inc.glsl
@@ -582,34 +582,39 @@ void light_process_omni(uint idx, vec3 vertex, vec3 eye_vec, vec3 normal, vec3 v
#ifdef LIGHT_TRANSMITTANCE_USED
float transmittance_z = transmittance_depth; //no transmittance by default
transmittance_color.a *= light_attenuation;
- {
- vec4 clamp_rect = omni_lights.data[idx].atlas_rect;
+#ifndef SHADOWS_DISABLED
+ if (omni_lights.data[idx].shadow_opacity > 0.001) {
+ // Redo shadowmapping, but shrink the model a bit to avoid artifacts.
+ vec2 texel_size = scene_data_block.data.shadow_atlas_pixel_size;
+ vec4 uv_rect = omni_lights.data[idx].atlas_rect;
+ uv_rect.xy += texel_size;
+ uv_rect.zw -= texel_size * 2.0;
- //redo shadowmapping, but shrink the model a bit to avoid artifacts
- vec4 splane = (omni_lights.data[idx].shadow_matrix * vec4(vertex - normalize(normal_interp) * omni_lights.data[idx].transmittance_bias, 1.0));
+ // Omni lights use direction.xy to store to store the offset between the two paraboloid regions
+ vec2 flip_offset = omni_lights.data[idx].direction.xy;
- float shadow_len = length(splane.xyz);
- splane.xyz = normalize(splane.xyz);
+ vec3 local_vert = (omni_lights.data[idx].shadow_matrix * vec4(vertex - normalize(normal) * omni_lights.data[idx].transmittance_bias, 1.0)).xyz;
- if (splane.z >= 0.0) {
- splane.z += 1.0;
- clamp_rect.y += clamp_rect.w;
- } else {
- splane.z = 1.0 - splane.z;
- }
+ float shadow_len = length(local_vert); //need to remember shadow len from here
+ vec3 shadow_sample = normalize(local_vert);
- splane.xy /= splane.z;
+ if (shadow_sample.z >= 0.0) {
+ uv_rect.xy += flip_offset;
+ flip_offset *= -1.0;
+ }
- splane.xy = splane.xy * 0.5 + 0.5;
- splane.z = shadow_len * omni_lights.data[idx].inv_radius;
- splane.xy = clamp_rect.xy + splane.xy * clamp_rect.zw;
- // splane.xy = clamp(splane.xy,clamp_rect.xy + scene_data_block.data.shadow_atlas_pixel_size,clamp_rect.xy + clamp_rect.zw - scene_data_block.data.shadow_atlas_pixel_size );
- splane.w = 1.0; //needed? i think it should be 1 already
+ shadow_sample.z = 1.0 + abs(shadow_sample.z);
+ vec2 pos = shadow_sample.xy / shadow_sample.z;
+ float depth = shadow_len * omni_lights.data[idx].inv_radius;
+ depth = 1.0 - depth;
- float shadow_z = textureLod(sampler2D(shadow_atlas, SAMPLER_LINEAR_CLAMP), splane.xy, 0.0).r;
- transmittance_z = (splane.z - shadow_z) / omni_lights.data[idx].inv_radius;
+ pos = pos * 0.5 + 0.5;
+ pos = uv_rect.xy + pos * uv_rect.zw;
+ float shadow_z = textureLod(sampler2D(shadow_atlas, SAMPLER_LINEAR_CLAMP), pos, 0.0).r;
+ transmittance_z = (depth - shadow_z) / omni_lights.data[idx].inv_radius;
}
-#endif
+#endif // !SHADOWS_DISABLED
+#endif // LIGHT_TRANSMITTANCE_USED
if (sc_use_light_projector && omni_lights.data[idx].projector_rect != vec4(0.0)) {
vec3 local_v = (omni_lights.data[idx].shadow_matrix * vec4(vertex, 1.0)).xyz;
@@ -834,12 +839,13 @@ void light_process_spot(uint idx, vec3 vertex, vec3 eye_vec, vec3 normal, vec3 v
#ifdef LIGHT_TRANSMITTANCE_USED
float transmittance_z = transmittance_depth;
transmittance_color.a *= light_attenuation;
- {
- vec4 splane = (spot_lights.data[idx].shadow_matrix * vec4(vertex - normalize(normal_interp) * spot_lights.data[idx].transmittance_bias, 1.0));
+#ifndef SHADOWS_DISABLED
+ if (spot_lights.data[idx].shadow_opacity > 0.001) {
+ vec4 splane = (spot_lights.data[idx].shadow_matrix * vec4(vertex - normalize(normal) * spot_lights.data[idx].transmittance_bias, 1.0));
splane /= splane.w;
- splane.xy = splane.xy * spot_lights.data[idx].atlas_rect.zw + spot_lights.data[idx].atlas_rect.xy;
- float shadow_z = textureLod(sampler2D(shadow_atlas, SAMPLER_LINEAR_CLAMP), splane.xy, 0.0).r;
+ vec3 shadow_uv = vec3(splane.xy * spot_lights.data[idx].atlas_rect.zw + spot_lights.data[idx].atlas_rect.xy, splane.z);
+ float shadow_z = textureLod(sampler2D(shadow_atlas, SAMPLER_LINEAR_CLAMP), shadow_uv.xy, 0.0).r;
shadow_z = shadow_z * 2.0 - 1.0;
float z_far = 1.0 / spot_lights.data[idx].inv_radius;
@@ -850,7 +856,8 @@ void light_process_spot(uint idx, vec3 vertex, vec3 eye_vec, vec3 normal, vec3 v
float z = dot(spot_dir, -light_rel_vec);
transmittance_z = z - shadow_z;
}
-#endif //LIGHT_TRANSMITTANCE_USED
+#endif // !SHADOWS_DISABLED
+#endif // LIGHT_TRANSMITTANCE_USED
if (sc_use_light_projector && spot_lights.data[idx].projector_rect != vec4(0.0)) {
vec4 splane = (spot_lights.data[idx].shadow_matrix * vec4(vertex, 1.0));
@@ -933,7 +940,7 @@ void reflection_process(uint ref_index, vec3 vertex, vec3 ref_vec, vec3 normal,
vec4 reflection;
- reflection.rgb = textureLod(samplerCubeArray(reflection_atlas, DEFAULT_SAMPLER_LINEAR_WITH_MIPMAPS_CLAMP), vec4(local_ref_vec, reflections.data[ref_index].index), roughness * MAX_ROUGHNESS_LOD).rgb * sc_luminance_multiplier;
+ reflection.rgb = textureLod(samplerCubeArray(reflection_atlas, DEFAULT_SAMPLER_LINEAR_WITH_MIPMAPS_CLAMP), vec4(local_ref_vec, reflections.data[ref_index].index), sqrt(roughness) * MAX_ROUGHNESS_LOD).rgb * sc_luminance_multiplier;
reflection.rgb *= reflections.data[ref_index].exposure_normalization;
if (reflections.data[ref_index].exterior) {
reflection.rgb = mix(specular_light, reflection.rgb, blend);
diff --git a/servers/rendering/renderer_rd/storage_rd/light_storage.cpp b/servers/rendering/renderer_rd/storage_rd/light_storage.cpp
index c217c0fa9a..b07063cfda 100644
--- a/servers/rendering/renderer_rd/storage_rd/light_storage.cpp
+++ b/servers/rendering/renderer_rd/storage_rd/light_storage.cpp
@@ -685,7 +685,7 @@ void LightStorage::update_light_buffers(RenderDataRD *p_render_data, const Paged
float bias_scale = light_instance->shadow_transform[j].bias_scale * light_data.soft_shadow_scale;
light_data.shadow_bias[j] = light->param[RS::LIGHT_PARAM_SHADOW_BIAS] / 100.0 * bias_scale;
light_data.shadow_normal_bias[j] = light->param[RS::LIGHT_PARAM_SHADOW_NORMAL_BIAS] * light_instance->shadow_transform[j].shadow_texel_size;
- light_data.shadow_transmittance_bias[j] = light->param[RS::LIGHT_PARAM_TRANSMITTANCE_BIAS] * bias_scale;
+ light_data.shadow_transmittance_bias[j] = light->param[RS::LIGHT_PARAM_TRANSMITTANCE_BIAS] / 100.0 * bias_scale;
light_data.shadow_z_range[j] = light_instance->shadow_transform[j].farplane;
light_data.shadow_range_begin[j] = light_instance->shadow_transform[j].range_begin;
RendererRD::MaterialStorage::store_camera(shadow_mtx, light_data.shadow_matrices[j]);
diff --git a/servers/rendering/renderer_rd/storage_rd/light_storage.h b/servers/rendering/renderer_rd/storage_rd/light_storage.h
index 94ab219dc2..1db58d72f9 100644
--- a/servers/rendering/renderer_rd/storage_rd/light_storage.h
+++ b/servers/rendering/renderer_rd/storage_rd/light_storage.h
@@ -49,7 +49,7 @@ namespace RendererRD {
class LightStorage : public RendererLightStorage {
public:
- enum ShadowAtlastQuadrant {
+ enum ShadowAtlastQuadrant : uint32_t {
QUADRANT_SHIFT = 27,
OMNI_LIGHT_FLAG = 1 << 26,
SHADOW_INDEX_MASK = OMNI_LIGHT_FLAG - 1,
diff --git a/servers/rendering/renderer_rd/storage_rd/mesh_storage.cpp b/servers/rendering/renderer_rd/storage_rd/mesh_storage.cpp
index 539bdcbbd0..9bd62ba065 100644
--- a/servers/rendering/renderer_rd/storage_rd/mesh_storage.cpp
+++ b/servers/rendering/renderer_rd/storage_rd/mesh_storage.cpp
@@ -783,6 +783,7 @@ String MeshStorage::mesh_get_path(RID p_mesh) const {
}
void MeshStorage::mesh_set_shadow_mesh(RID p_mesh, RID p_shadow_mesh) {
+ ERR_FAIL_COND_MSG(p_mesh == p_shadow_mesh, "Cannot set a mesh as its own shadow mesh.");
Mesh *mesh = mesh_owner.get_or_null(p_mesh);
ERR_FAIL_NULL(mesh);
diff --git a/servers/rendering/renderer_rd/storage_rd/texture_storage.cpp b/servers/rendering/renderer_rd/storage_rd/texture_storage.cpp
index 6e5e8f63e0..be29716f45 100644
--- a/servers/rendering/renderer_rd/storage_rd/texture_storage.cpp
+++ b/servers/rendering/renderer_rd/storage_rd/texture_storage.cpp
@@ -1470,8 +1470,24 @@ void TextureStorage::texture_debug_usage(List<RS::TextureInfo> *r_info) {
tinfo.format = t->format;
tinfo.width = t->width;
tinfo.height = t->height;
- tinfo.depth = t->depth;
- tinfo.bytes = Image::get_image_data_size(t->width, t->height, t->format, t->mipmaps);
+ tinfo.bytes = Image::get_image_data_size(t->width, t->height, t->format, t->mipmaps > 1);
+
+ switch (t->type) {
+ case TextureType::TYPE_3D:
+ tinfo.depth = t->depth;
+ tinfo.bytes *= t->depth;
+ break;
+
+ case TextureType::TYPE_LAYERED:
+ tinfo.depth = t->layers;
+ tinfo.bytes *= t->layers;
+ break;
+
+ default:
+ tinfo.depth = 0;
+ break;
+ }
+
r_info->push_back(tinfo);
}
}
diff --git a/servers/rendering/renderer_scene_cull.cpp b/servers/rendering/renderer_scene_cull.cpp
index 1d25dec633..286944641c 100644
--- a/servers/rendering/renderer_scene_cull.cpp
+++ b/servers/rendering/renderer_scene_cull.cpp
@@ -1783,6 +1783,8 @@ void RendererSceneCull::_update_instance(Instance *p_instance) {
if (p_instance->scenario) {
RendererSceneOcclusionCull::get_singleton()->scenario_set_instance(p_instance->scenario->self, p_instance->self, p_instance->base, *instance_xform, p_instance->visible);
}
+ } else if (p_instance->base_type == RS::INSTANCE_NONE) {
+ return;
}
if (!p_instance->aabb.has_surface()) {
diff --git a/servers/rendering/rendering_device.cpp b/servers/rendering/rendering_device.cpp
index 9e3ab5da49..f0f267c246 100644
--- a/servers/rendering/rendering_device.cpp
+++ b/servers/rendering/rendering_device.cpp
@@ -82,11 +82,12 @@ static String _get_device_type_name(const RenderingContextDriver::Device &p_devi
}
static uint32_t _get_device_type_score(const RenderingContextDriver::Device &p_device) {
+ static const bool prefer_integrated = OS::get_singleton()->get_user_prefers_integrated_gpu();
switch (p_device.type) {
case RenderingContextDriver::DEVICE_TYPE_INTEGRATED_GPU:
- return 4;
+ return prefer_integrated ? 5 : 4;
case RenderingContextDriver::DEVICE_TYPE_DISCRETE_GPU:
- return 5;
+ return prefer_integrated ? 4 : 5;
case RenderingContextDriver::DEVICE_TYPE_VIRTUAL_GPU:
return 3;
case RenderingContextDriver::DEVICE_TYPE_CPU:
@@ -3083,7 +3084,7 @@ RID RenderingDevice::uniform_set_create(const Vector<Uniform> &p_uniforms, RID p
ERR_FAIL_NULL_V_MSG(buffer, RID(), "Uniform buffer supplied (binding: " + itos(uniform.binding) + ") is invalid.");
ERR_FAIL_COND_V_MSG(buffer->size < (uint32_t)set_uniform.length, RID(),
- "Uniform buffer supplied (binding: " + itos(uniform.binding) + ") size (" + itos(buffer->size) + " is smaller than size of shader uniform: (" + itos(set_uniform.length) + ").");
+ "Uniform buffer supplied (binding: " + itos(uniform.binding) + ") size (" + itos(buffer->size) + ") is smaller than size of shader uniform: (" + itos(set_uniform.length) + ").");
if (buffer->draw_tracker != nullptr) {
draw_trackers.push_back(buffer->draw_tracker);
@@ -3112,7 +3113,7 @@ RID RenderingDevice::uniform_set_create(const Vector<Uniform> &p_uniforms, RID p
// If 0, then it's sized on link time.
ERR_FAIL_COND_V_MSG(set_uniform.length > 0 && buffer->size != (uint32_t)set_uniform.length, RID(),
- "Storage buffer supplied (binding: " + itos(uniform.binding) + ") size (" + itos(buffer->size) + " does not match size of shader uniform: (" + itos(set_uniform.length) + ").");
+ "Storage buffer supplied (binding: " + itos(uniform.binding) + ") size (" + itos(buffer->size) + ") does not match size of shader uniform: (" + itos(set_uniform.length) + ").");
if (set_uniform.writable && _buffer_make_mutable(buffer, buffer_id)) {
// The buffer must be mutable if it's used for writing.
diff --git a/servers/rendering/rendering_device.h b/servers/rendering/rendering_device.h
index d8f9e2c31a..1405f585b2 100644
--- a/servers/rendering/rendering_device.h
+++ b/servers/rendering/rendering_device.h
@@ -377,7 +377,9 @@ public:
// used for the render pipelines.
struct AttachmentFormat {
- enum { UNUSED_ATTACHMENT = 0xFFFFFFFF };
+ enum : uint32_t {
+ UNUSED_ATTACHMENT = 0xFFFFFFFF
+ };
DataFormat format;
TextureSamples samples;
uint32_t usage_flags;
diff --git a/servers/rendering/shader_language.cpp b/servers/rendering/shader_language.cpp
index 8457a09055..4eaf7fcb55 100644
--- a/servers/rendering/shader_language.cpp
+++ b/servers/rendering/shader_language.cpp
@@ -5196,7 +5196,7 @@ ShaderLanguage::Node *ShaderLanguage::_parse_array_constructor(BlockNode *p_bloc
return an;
}
-ShaderLanguage::Node *ShaderLanguage::_parse_expression(BlockNode *p_block, const FunctionInfo &p_function_info) {
+ShaderLanguage::Node *ShaderLanguage::_parse_expression(BlockNode *p_block, const FunctionInfo &p_function_info, const ExpressionInfo *p_previous_expression_info) {
Vector<Expression> expression;
//Vector<TokenType> operators;
@@ -6551,6 +6551,10 @@ ShaderLanguage::Node *ShaderLanguage::_parse_expression(BlockNode *p_block, cons
pos = _get_tkpos();
tk = _get_token();
+ if (p_previous_expression_info != nullptr && tk.type == p_previous_expression_info->tt_break && !p_previous_expression_info->is_last_expr) {
+ break;
+ }
+
if (is_token_operator(tk.type)) {
Expression o;
o.is_op = true;
@@ -6657,6 +6661,31 @@ ShaderLanguage::Node *ShaderLanguage::_parse_expression(BlockNode *p_block, cons
expression.push_back(o);
+ if (o.op == OP_SELECT_IF) {
+ ExpressionInfo info;
+ info.expression = &expression;
+ info.tt_break = TK_COLON;
+
+ expr = _parse_and_reduce_expression(p_block, p_function_info, &info);
+ if (!expr) {
+ return nullptr;
+ }
+
+ expression.push_back({ true, { OP_SELECT_ELSE } });
+
+ if (p_previous_expression_info != nullptr) {
+ info.is_last_expr = p_previous_expression_info->is_last_expr;
+ } else {
+ info.is_last_expr = true;
+ }
+
+ expr = _parse_and_reduce_expression(p_block, p_function_info, &info);
+ if (!expr) {
+ return nullptr;
+ }
+
+ break;
+ }
} else {
_set_tkpos(pos); //something else, so rollback and end
break;
@@ -6969,6 +6998,10 @@ ShaderLanguage::Node *ShaderLanguage::_parse_expression(BlockNode *p_block, cons
}
}
+ if (p_previous_expression_info != nullptr) {
+ p_previous_expression_info->expression->push_back(expression[0]);
+ }
+
return expression[0].node;
}
@@ -7081,8 +7114,8 @@ ShaderLanguage::Node *ShaderLanguage::_reduce_expression(BlockNode *p_block, Sha
return p_node;
}
-ShaderLanguage::Node *ShaderLanguage::_parse_and_reduce_expression(BlockNode *p_block, const FunctionInfo &p_function_info) {
- ShaderLanguage::Node *expr = _parse_expression(p_block, p_function_info);
+ShaderLanguage::Node *ShaderLanguage::_parse_and_reduce_expression(BlockNode *p_block, const FunctionInfo &p_function_info, const ExpressionInfo *p_previous_expression_info) {
+ ShaderLanguage::Node *expr = _parse_expression(p_block, p_function_info, p_previous_expression_info);
if (!expr) { //errored
return nullptr;
}
diff --git a/servers/rendering/shader_language.h b/servers/rendering/shader_language.h
index 1b5df7e90f..63dca99654 100644
--- a/servers/rendering/shader_language.h
+++ b/servers/rendering/shader_language.h
@@ -747,6 +747,12 @@ public:
};
};
+ struct ExpressionInfo {
+ Vector<Expression> *expression = nullptr;
+ TokenType tt_break = TK_EMPTY;
+ bool is_last_expr = false;
+ };
+
struct VarInfo {
StringName name;
DataType type;
@@ -1143,13 +1149,13 @@ private:
bool _check_restricted_func(const StringName &p_name, const StringName &p_current_function) const;
bool _validate_restricted_func(const StringName &p_call_name, const CallInfo *p_func_info, bool p_is_builtin_hint = false);
- Node *_parse_expression(BlockNode *p_block, const FunctionInfo &p_function_info);
+ Node *_parse_expression(BlockNode *p_block, const FunctionInfo &p_function_info, const ExpressionInfo *p_previous_expression_info = nullptr);
Error _parse_array_size(BlockNode *p_block, const FunctionInfo &p_function_info, bool p_forbid_unknown_size, Node **r_size_expression, int *r_array_size, bool *r_unknown_size);
Node *_parse_array_constructor(BlockNode *p_block, const FunctionInfo &p_function_info);
Node *_parse_array_constructor(BlockNode *p_block, const FunctionInfo &p_function_info, DataType p_type, const StringName &p_struct_name, int p_array_size);
ShaderLanguage::Node *_reduce_expression(BlockNode *p_block, ShaderLanguage::Node *p_node);
- Node *_parse_and_reduce_expression(BlockNode *p_block, const FunctionInfo &p_function_info);
+ Node *_parse_and_reduce_expression(BlockNode *p_block, const FunctionInfo &p_function_info, const ExpressionInfo *p_previous_expression_info = nullptr);
Error _parse_block(BlockNode *p_block, const FunctionInfo &p_function_info, bool p_just_one = false, bool p_can_break = false, bool p_can_continue = false);
String _get_shader_type_list(const HashSet<String> &p_shader_types) const;
String _get_qualifier_str(ArgumentQualifier p_qualifier) const;
diff --git a/servers/rendering_server.cpp b/servers/rendering_server.cpp
index 92f0f0dbc0..1848d5602e 100644
--- a/servers/rendering_server.cpp
+++ b/servers/rendering_server.cpp
@@ -3263,6 +3263,7 @@ void RenderingServer::_bind_methods() {
ClassDB::bind_method(D_METHOD("canvas_item_set_z_index", "item", "z_index"), &RenderingServer::canvas_item_set_z_index);
ClassDB::bind_method(D_METHOD("canvas_item_set_z_as_relative_to_parent", "item", "enabled"), &RenderingServer::canvas_item_set_z_as_relative_to_parent);
ClassDB::bind_method(D_METHOD("canvas_item_set_copy_to_backbuffer", "item", "enabled", "rect"), &RenderingServer::canvas_item_set_copy_to_backbuffer);
+ ClassDB::bind_method(D_METHOD("canvas_item_attach_skeleton", "item", "skeleton"), &RenderingServer::canvas_item_attach_skeleton);
ClassDB::bind_method(D_METHOD("canvas_item_clear", "item"), &RenderingServer::canvas_item_clear);
ClassDB::bind_method(D_METHOD("canvas_item_set_draw_index", "item", "index"), &RenderingServer::canvas_item_set_draw_index);
diff --git a/servers/rendering_server.h b/servers/rendering_server.h
index d8b6651833..62ca6b3b6d 100644
--- a/servers/rendering_server.h
+++ b/servers/rendering_server.h
@@ -176,7 +176,7 @@ public:
uint32_t height;
uint32_t depth;
Image::Format format;
- int bytes;
+ int64_t bytes;
String path;
};
diff --git a/servers/text_server.cpp b/servers/text_server.cpp
index 860cc5d75d..d2cf4674ae 100644
--- a/servers/text_server.cpp
+++ b/servers/text_server.cpp
@@ -1988,7 +1988,7 @@ TypedArray<Vector3i> TextServer::parse_structured_text(StructuredTextParser p_pa
}
} break;
case STRUCTURED_TEXT_LIST: {
- if (p_args.size() == 1 && p_args[0].get_type() == Variant::STRING) {
+ if (p_args.size() == 1 && p_args[0].is_string()) {
Vector<String> tags = p_text.split(String(p_args[0]));
int prev = 0;
for (int i = 0; i < tags.size(); i++) {
@@ -2071,8 +2071,8 @@ TypedArray<Vector3i> TextServer::parse_structured_text(StructuredTextParser p_pa
if (prev != i) {
ret.push_back(Vector3i(prev, i, TextServer::DIRECTION_AUTO));
}
- prev = i + 1;
- ret.push_back(Vector3i(i, i + 1, TextServer::DIRECTION_LTR));
+ prev = p_text.length();
+ ret.push_back(Vector3i(i, p_text.length(), TextServer::DIRECTION_AUTO));
break;
}
}
diff --git a/servers/xr_server.cpp b/servers/xr_server.cpp
index a4e68afee0..4c4781fdef 100644
--- a/servers/xr_server.cpp
+++ b/servers/xr_server.cpp
@@ -183,7 +183,7 @@ Transform3D XRServer::get_reference_frame() const {
}
void XRServer::center_on_hmd(RotationMode p_rotation_mode, bool p_keep_height) {
- if (primary_interface == nullptr) {
+ if (primary_interface.is_null()) {
return;
}
@@ -235,7 +235,7 @@ void XRServer::_set_render_reference_frame(const Transform3D &p_reference_frame)
Transform3D XRServer::get_hmd_transform() {
Transform3D hmd_transform;
- if (primary_interface != nullptr) {
+ if (primary_interface.is_valid()) {
hmd_transform = primary_interface->get_camera_transform();
}
return hmd_transform;
diff --git a/tests/core/io/test_http_client.h b/tests/core/io/test_http_client.h
index 961c653a0a..114ce3b4ed 100644
--- a/tests/core/io/test_http_client.h
+++ b/tests/core/io/test_http_client.h
@@ -41,7 +41,7 @@ namespace TestHTTPClient {
TEST_CASE("[HTTPClient] Instantiation") {
Ref<HTTPClient> client = HTTPClient::create();
- CHECK_MESSAGE(client != nullptr, "A HTTP Client created should not be a null pointer");
+ CHECK_MESSAGE(client.is_valid(), "A HTTP Client created should not be a null pointer");
}
TEST_CASE("[HTTPClient] query_string_from_dict") {
diff --git a/tests/core/io/test_json_native.h b/tests/core/io/test_json_native.h
new file mode 100644
index 0000000000..819078ac57
--- /dev/null
+++ b/tests/core/io/test_json_native.h
@@ -0,0 +1,160 @@
+/**************************************************************************/
+/* test_json_native.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_JSON_NATIVE_H
+#define TEST_JSON_NATIVE_H
+
+#include "core/io/json.h"
+
+namespace TestJSONNative {
+
+bool compare_variants(Variant variant_1, Variant variant_2, int depth = 0) {
+ if (depth > 100) {
+ return false;
+ }
+ if (variant_1.get_type() == Variant::RID && variant_2.get_type() == Variant::RID) {
+ return true;
+ }
+ if (variant_1.get_type() == Variant::CALLABLE || variant_2.get_type() == Variant::CALLABLE) {
+ return true;
+ }
+
+ List<PropertyInfo> variant_1_properties;
+ variant_1.get_property_list(&variant_1_properties);
+ List<PropertyInfo> variant_2_properties;
+ variant_2.get_property_list(&variant_2_properties);
+
+ if (variant_1_properties.size() != variant_2_properties.size()) {
+ return false;
+ }
+
+ for (List<PropertyInfo>::Element *E = variant_1_properties.front(); E; E = E->next()) {
+ String name = E->get().name;
+ Variant variant_1_value = variant_1.get(name);
+ Variant variant_2_value = variant_2.get(name);
+
+ if (!compare_variants(variant_1_value, variant_2_value, depth + 1)) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+TEST_CASE("[JSON][Native][SceneTree] Conversion between native and JSON formats") {
+ for (int variant_i = 0; variant_i < Variant::VARIANT_MAX; variant_i++) {
+ Variant::Type type = static_cast<Variant::Type>(variant_i);
+ Variant native_data;
+ Callable::CallError error;
+
+ if (type == Variant::Type::INT || type == Variant::Type::FLOAT) {
+ Variant value = int64_t(INT64_MAX);
+ const Variant *args[] = { &value };
+ Variant::construct(type, native_data, args, 1, error);
+ } else if (type == Variant::Type::OBJECT) {
+ Ref<JSON> json = memnew(JSON);
+ native_data = json;
+ } else if (type == Variant::Type::DICTIONARY) {
+ Dictionary dictionary;
+ dictionary["key"] = "value";
+ native_data = dictionary;
+ } else if (type == Variant::Type::ARRAY) {
+ Array array;
+ array.push_back("element1");
+ array.push_back("element2");
+ native_data = array;
+ } else if (type == Variant::Type::PACKED_BYTE_ARRAY) {
+ PackedByteArray packed_array;
+ packed_array.push_back(1);
+ packed_array.push_back(2);
+ native_data = packed_array;
+ } else if (type == Variant::Type::PACKED_INT32_ARRAY) {
+ PackedInt32Array packed_array;
+ packed_array.push_back(INT32_MIN);
+ packed_array.push_back(INT32_MAX);
+ native_data = packed_array;
+ } else if (type == Variant::Type::PACKED_INT64_ARRAY) {
+ PackedInt64Array packed_array;
+ packed_array.push_back(INT64_MIN);
+ packed_array.push_back(INT64_MAX);
+ native_data = packed_array;
+ } else if (type == Variant::Type::PACKED_FLOAT32_ARRAY) {
+ PackedFloat32Array packed_array;
+ packed_array.push_back(FLT_MIN);
+ packed_array.push_back(FLT_MAX);
+ native_data = packed_array;
+ } else if (type == Variant::Type::PACKED_FLOAT64_ARRAY) {
+ PackedFloat64Array packed_array;
+ packed_array.push_back(DBL_MIN);
+ packed_array.push_back(DBL_MAX);
+ native_data = packed_array;
+ } else if (type == Variant::Type::PACKED_STRING_ARRAY) {
+ PackedStringArray packed_array;
+ packed_array.push_back("string1");
+ packed_array.push_back("string2");
+ native_data = packed_array;
+ } else if (type == Variant::Type::PACKED_VECTOR2_ARRAY) {
+ PackedVector2Array packed_array;
+ Vector2 vector(1.0, 2.0);
+ packed_array.push_back(vector);
+ native_data = packed_array;
+ } else if (type == Variant::Type::PACKED_VECTOR3_ARRAY) {
+ PackedVector3Array packed_array;
+ Vector3 vector(1.0, 2.0, 3.0);
+ packed_array.push_back(vector);
+ native_data = packed_array;
+ } else if (type == Variant::Type::PACKED_COLOR_ARRAY) {
+ PackedColorArray packed_array;
+ Color color(1.0, 1.0, 1.0);
+ packed_array.push_back(color);
+ native_data = packed_array;
+ } else if (type == Variant::Type::PACKED_VECTOR4_ARRAY) {
+ PackedVector4Array packed_array;
+ Vector4 vector(1.0, 2.0, 3.0, 4.0);
+ packed_array.push_back(vector);
+ native_data = packed_array;
+ } else {
+ Variant::construct(type, native_data, nullptr, 0, error);
+ }
+ Variant json_converted_from_native = JSON::from_native(native_data, true, true);
+ Variant variant_native_converted = JSON::to_native(json_converted_from_native, true, true);
+ CHECK_MESSAGE(compare_variants(native_data, variant_native_converted),
+ vformat("Conversion from native to JSON type %s and back successful. \nNative: %s \nNative Converted: %s \nError: %s\nConversion from native to JSON type %s successful: %s",
+ Variant::get_type_name(type),
+ native_data,
+ variant_native_converted,
+ itos(error.error),
+ Variant::get_type_name(type),
+ json_converted_from_native));
+ }
+}
+} // namespace TestJSONNative
+
+#endif // TEST_JSON_NATIVE_H
diff --git a/tests/core/object/test_object.h b/tests/core/object/test_object.h
index 57bc65328a..f1bb62cb70 100644
--- a/tests/core/object/test_object.h
+++ b/tests/core/object/test_object.h
@@ -174,6 +174,31 @@ TEST_CASE("[Object] Metadata") {
CHECK_MESSAGE(
meta_list2.size() == 0,
"The metadata list should contain 0 items after removing all metadata items.");
+
+ Object other;
+ object.set_meta("conflicting_meta", "string");
+ object.set_meta("not_conflicting_meta", 123);
+ other.set_meta("conflicting_meta", Color(0, 1, 0));
+ other.set_meta("other_meta", "other");
+ object.merge_meta_from(&other);
+
+ CHECK_MESSAGE(
+ Color(object.get_meta("conflicting_meta")).is_equal_approx(Color(0, 1, 0)),
+ "String meta should be overwritten with Color after merging.");
+
+ CHECK_MESSAGE(
+ int(object.get_meta("not_conflicting_meta")) == 123,
+ "Not conflicting meta on destination should be kept intact.");
+
+ CHECK_MESSAGE(
+ object.get_meta("other_meta", String()) == "other",
+ "Not conflicting meta name on source should merged.");
+
+ List<StringName> meta_list3;
+ object.get_meta_list(&meta_list3);
+ CHECK_MESSAGE(
+ meta_list3.size() == 3,
+ "The metadata list should contain 3 items after merging meta from two objects.");
}
TEST_CASE("[Object] Construction") {
diff --git a/tests/core/string/test_string.h b/tests/core/string/test_string.h
index b47e5b1eb9..a9f615af84 100644
--- a/tests/core/string/test_string.h
+++ b/tests/core/string/test_string.h
@@ -433,6 +433,19 @@ TEST_CASE("[String] Insertion") {
String s = "Who is Frederic?";
s = s.insert(s.find("?"), " Chopin");
CHECK(s == "Who is Frederic Chopin?");
+
+ s = "foobar";
+ CHECK(s.insert(0, "X") == "Xfoobar");
+ CHECK(s.insert(-100, "X") == "foobar");
+ CHECK(s.insert(6, "X") == "foobarX");
+ CHECK(s.insert(100, "X") == "foobarX");
+ CHECK(s.insert(2, "") == "foobar");
+
+ s = "";
+ CHECK(s.insert(0, "abc") == "abc");
+ CHECK(s.insert(100, "abc") == "abc");
+ CHECK(s.insert(-100, "abc") == "");
+ CHECK(s.insert(0, "") == "");
}
TEST_CASE("[String] Erasing") {
@@ -1811,13 +1824,25 @@ TEST_CASE("[String] SHA1/SHA256/MD5") {
}
TEST_CASE("[String] Join") {
- String s = ", ";
+ String comma = ", ";
+ String empty = "";
Vector<String> parts;
+
+ CHECK(comma.join(parts) == "");
+ CHECK(empty.join(parts) == "");
+
parts.push_back("One");
+ CHECK(comma.join(parts) == "One");
+ CHECK(empty.join(parts) == "One");
+
parts.push_back("B");
parts.push_back("C");
- String t = s.join(parts);
- CHECK(t == "One, B, C");
+ CHECK(comma.join(parts) == "One, B, C");
+ CHECK(empty.join(parts) == "OneBC");
+
+ parts.push_back("");
+ CHECK(comma.join(parts) == "One, B, C, ");
+ CHECK(empty.join(parts) == "OneBC");
}
TEST_CASE("[String] Is_*") {
diff --git a/tests/core/variant/test_dictionary.h b/tests/core/variant/test_dictionary.h
index aba20972d9..48a48f6ca6 100644
--- a/tests/core/variant/test_dictionary.h
+++ b/tests/core/variant/test_dictionary.h
@@ -31,7 +31,7 @@
#ifndef TEST_DICTIONARY_H
#define TEST_DICTIONARY_H
-#include "core/variant/dictionary.h"
+#include "core/variant/typed_dictionary.h"
#include "tests/test_macros.h"
namespace TestDictionary {
@@ -66,8 +66,7 @@ TEST_CASE("[Dictionary] Assignment using bracket notation ([])") {
map[StringName("HelloName")] = 6;
CHECK(int(map[StringName("HelloName")]) == 6);
- // Check that StringName key is converted to String.
- CHECK(int(map.find_key(6).get_type()) == Variant::STRING);
+ CHECK(int(map.find_key(6).get_type()) == Variant::STRING_NAME);
map[StringName("HelloName")] = 7;
CHECK(int(map[StringName("HelloName")]) == 7);
@@ -537,6 +536,43 @@ TEST_CASE("[Dictionary] Order and find") {
CHECK_EQ(d.find_key("does not exist"), Variant());
}
+TEST_CASE("[Dictionary] Typed copying") {
+ TypedDictionary<int, int> d1;
+ d1[0] = 1;
+
+ TypedDictionary<double, double> d2;
+ d2[0] = 1.0;
+
+ Dictionary d3 = d1;
+ TypedDictionary<int, int> d4 = d3;
+
+ Dictionary d5 = d2;
+ TypedDictionary<int, int> d6 = d5;
+
+ d3[0] = 2;
+ d4[0] = 3;
+
+ // Same typed TypedDictionary should be shared.
+ CHECK_EQ(d1[0], Variant(3));
+ CHECK_EQ(d3[0], Variant(3));
+ CHECK_EQ(d4[0], Variant(3));
+
+ d5[0] = 2.0;
+ d6[0] = 3.0;
+
+ // Different typed TypedDictionary should not be shared.
+ CHECK_EQ(d2[0], Variant(2.0));
+ CHECK_EQ(d5[0], Variant(2.0));
+ CHECK_EQ(d6[0], Variant(3.0));
+
+ d1.clear();
+ d2.clear();
+ d3.clear();
+ d4.clear();
+ d5.clear();
+ d6.clear();
+}
+
} // namespace TestDictionary
#endif // TEST_DICTIONARY_H
diff --git a/tests/create_test.py b/tests/create_test.py
index deb53aca20..ad6d6b882f 100755
--- a/tests/create_test.py
+++ b/tests/create_test.py
@@ -13,12 +13,16 @@ def main():
os.chdir(os.path.dirname(os.path.realpath(__file__)))
parser = argparse.ArgumentParser(description="Creates a new unit test file.")
- parser.add_argument("name", type=str, help="The unit test name in PascalCase notation")
+ parser.add_argument(
+ "name",
+ type=str,
+ help="Specifies the class or component name to be tested, in PascalCase (e.g., MeshInstance3D). The name will be prefixed with 'test_' for the header file and 'Test' for the namespace.",
+ )
parser.add_argument(
"path",
type=str,
nargs="?",
- help="The path to the unit test file relative to the tests folder (default: .)",
+ help="The path to the unit test file relative to the tests folder (e.g. core). This should correspond to the relative path of the class or component being tested. (default: .)",
default=".",
)
parser.add_argument(
@@ -29,9 +33,10 @@ def main():
)
args = parser.parse_args()
- snake_case_regex = re.compile(r"(?<!^)(?=[A-Z])")
- name_snake_case = snake_case_regex.sub("_", args.name).lower()
-
+ snake_case_regex = re.compile(r"(?<!^)(?=[A-Z, 0-9])")
+ # Replace 2D, 3D, and 4D with 2d, 3d, and 4d, respectively. This avoids undesired splits like node_3_d.
+ prefiltered_name = re.sub(r"([234])D", lambda match: match.group(1).lower() + "d", args.name)
+ name_snake_case = snake_case_regex.sub("_", prefiltered_name).lower()
file_path = os.path.normpath(os.path.join(args.path, f"test_{name_snake_case}.h"))
print(file_path)
diff --git a/tests/python_build/fixtures/gles3/vertex_fragment_expected_full.glsl b/tests/python_build/fixtures/gles3/vertex_fragment_expected_full.glsl
index 8ad5a23eb5..db5f54e3d8 100644
--- a/tests/python_build/fixtures/gles3/vertex_fragment_expected_full.glsl
+++ b/tests/python_build/fixtures/gles3/vertex_fragment_expected_full.glsl
@@ -37,10 +37,34 @@ protected:
static const Feedback* _feedbacks=nullptr;
static const char _vertex_code[]={
-10,112,114,101,99,105,115,105,111,110,32,104,105,103,104,112,32,102,108,111,97,116,59,10,112,114,101,99,105,115,105,111,110,32,104,105,103,104,112,32,105,110,116,59,10,10,108,97,121,111,117,116,40,108,111,99,97,116,105,111,110,32,61,32,48,41,32,105,110,32,104,105,103,104,112,32,118,101,99,51,32,118,101,114,116,101,120,59,10,10,111,117,116,32,104,105,103,104,112,32,118,101,99,52,32,112,111,115,105,116,105,111,110,95,105,110,116,101,114,112,59,10,10,118,111,105,100,32,109,97,105,110,40,41,32,123,10,9,112,111,115,105,116,105,111,110,95,105,110,116,101,114,112,32,61,32,118,101,99,52,40,118,101,114,116,101,120,46,120,44,49,44,48,44,49,41,59,10,125,10,10, 0};
+R"<!>(
+precision highp float;
+precision highp int;
+
+layout(location = 0) in highp vec3 vertex;
+
+out highp vec4 position_interp;
+
+void main() {
+ position_interp = vec4(vertex.x,1,0,1);
+}
+
+)<!>"
+ };
static const char _fragment_code[]={
-10,112,114,101,99,105,115,105,111,110,32,104,105,103,104,112,32,102,108,111,97,116,59,10,112,114,101,99,105,115,105,111,110,32,104,105,103,104,112,32,105,110,116,59,10,10,105,110,32,104,105,103,104,112,32,118,101,99,52,32,112,111,115,105,116,105,111,110,95,105,110,116,101,114,112,59,10,10,118,111,105,100,32,109,97,105,110,40,41,32,123,10,9,104,105,103,104,112,32,102,108,111,97,116,32,100,101,112,116,104,32,61,32,40,40,112,111,115,105,116,105,111,110,95,105,110,116,101,114,112,46,122,32,47,32,112,111,115,105,116,105,111,110,95,105,110,116,101,114,112,46,119,41,32,43,32,49,46,48,41,59,10,9,102,114,97,103,95,99,111,108,111,114,32,61,32,118,101,99,52,40,100,101,112,116,104,41,59,10,125,10, 0};
+R"<!>(
+precision highp float;
+precision highp int;
+
+in highp vec4 position_interp;
+
+void main() {
+ highp float depth = ((position_interp.z / position_interp.w) + 1.0);
+ frag_color = vec4(depth);
+}
+)<!>"
+ };
_setup(_vertex_code,_fragment_code,"VertexFragmentShaderGLES3",0,_uniform_strings,0,_ubo_pairs,0,_feedbacks,0,_texunit_pairs,1,_spec_pairs,1,_variant_defines);
}
diff --git a/tests/python_build/fixtures/glsl/compute_expected_full.glsl b/tests/python_build/fixtures/glsl/compute_expected_full.glsl
index b937d732c8..386d14f1aa 100644
--- a/tests/python_build/fixtures/glsl/compute_expected_full.glsl
+++ b/tests/python_build/fixtures/glsl/compute_expected_full.glsl
@@ -3,6 +3,18 @@
#define COMPUTE_SHADER_GLSL_RAW_H
static const char compute_shader_glsl[] = {
- 35,91,99,111,109,112,117,116,101,93,10,10,35,118,101,114,115,105,111,110,32,52,53,48,10,10,35,86,69,82,83,73,79,78,95,68,69,70,73,78,69,83,10,10,10,35,100,101,102,105,110,101,32,77,95,80,73,32,51,46,49,52,49,53,57,50,54,53,51,53,57,10,10,118,111,105,100,32,109,97,105,110,40,41,32,123,10,9,118,101,99,51,32,115,116,97,116,105,99,95,108,105,103,104,116,32,61,32,118,101,99,51,40,48,44,32,49,44,32,48,41,59,10,125,10,0
+R"<!>(#[compute]
+
+#version 450
+
+#VERSION_DEFINES
+
+
+#define M_PI 3.14159265359
+
+void main() {
+ vec3 static_light = vec3(0, 1, 0);
+}
+)<!>"
};
#endif
diff --git a/tests/python_build/fixtures/glsl/vertex_fragment_expected_full.glsl b/tests/python_build/fixtures/glsl/vertex_fragment_expected_full.glsl
index 3f53a17fac..b7329b6a79 100644
--- a/tests/python_build/fixtures/glsl/vertex_fragment_expected_full.glsl
+++ b/tests/python_build/fixtures/glsl/vertex_fragment_expected_full.glsl
@@ -3,6 +3,38 @@
#define VERTEX_FRAGMENT_SHADER_GLSL_RAW_H
static const char vertex_fragment_shader_glsl[] = {
- 35,91,118,101,114,115,105,111,110,115,93,10,10,108,105,110,101,115,32,61,32,34,35,100,101,102,105,110,101,32,77,79,68,69,95,76,73,78,69,83,34,59,10,10,35,91,118,101,114,116,101,120,93,10,10,35,118,101,114,115,105,111,110,32,52,53,48,10,10,35,86,69,82,83,73,79,78,95,68,69,70,73,78,69,83,10,10,108,97,121,111,117,116,40,108,111,99,97,116,105,111,110,32,61,32,48,41,32,111,117,116,32,118,101,99,51,32,117,118,95,105,110,116,101,114,112,59,10,10,118,111,105,100,32,109,97,105,110,40,41,32,123,10,10,35,105,102,100,101,102,32,77,79,68,69,95,76,73,78,69,83,10,9,117,118,95,105,110,116,101,114,112,32,61,32,118,101,99,51,40,48,44,48,44,49,41,59,10,35,101,110,100,105,102,10,125,10,10,35,91,102,114,97,103,109,101,110,116,93,10,10,35,118,101,114,115,105,111,110,32,52,53,48,10,10,35,86,69,82,83,73,79,78,95,68,69,70,73,78,69,83,10,10,35,100,101,102,105,110,101,32,77,95,80,73,32,51,46,49,52,49,53,57,50,54,53,51,53,57,10,10,108,97,121,111,117,116,40,108,111,99,97,116,105,111,110,32,61,32,48,41,32,111,117,116,32,118,101,99,52,32,100,115,116,95,99,111,108,111,114,59,10,10,118,111,105,100,32,109,97,105,110,40,41,32,123,10,9,100,115,116,95,99,111,108,111,114,32,61,32,118,101,99,52,40,49,44,49,44,48,44,48,41,59,10,125,10,0
+R"<!>(#[versions]
+
+lines = "#define MODE_LINES";
+
+#[vertex]
+
+#version 450
+
+#VERSION_DEFINES
+
+layout(location = 0) out vec3 uv_interp;
+
+void main() {
+
+#ifdef MODE_LINES
+ uv_interp = vec3(0,0,1);
+#endif
+}
+
+#[fragment]
+
+#version 450
+
+#VERSION_DEFINES
+
+#define M_PI 3.14159265359
+
+layout(location = 0) out vec4 dst_color;
+
+void main() {
+ dst_color = vec4(1,1,0,0);
+}
+)<!>"
};
#endif
diff --git a/tests/python_build/fixtures/rd_glsl/compute_expected_full.glsl b/tests/python_build/fixtures/rd_glsl/compute_expected_full.glsl
index b59923e28a..1184510020 100644
--- a/tests/python_build/fixtures/rd_glsl/compute_expected_full.glsl
+++ b/tests/python_build/fixtures/rd_glsl/compute_expected_full.glsl
@@ -11,7 +11,19 @@ public:
ComputeShaderRD() {
static const char _compute_code[] = {
-10,35,118,101,114,115,105,111,110,32,52,53,48,10,10,35,86,69,82,83,73,79,78,95,68,69,70,73,78,69,83,10,10,35,100,101,102,105,110,101,32,66,76,79,67,75,95,83,73,90,69,32,56,10,10,35,100,101,102,105,110,101,32,77,95,80,73,32,51,46,49,52,49,53,57,50,54,53,51,53,57,10,10,118,111,105,100,32,109,97,105,110,40,41,32,123,10,9,117,105,110,116,32,116,32,61,32,66,76,79,67,75,95,83,73,90,69,32,43,32,49,59,10,125,10,0
+R"<!>(
+#version 450
+
+#VERSION_DEFINES
+
+#define BLOCK_SIZE 8
+
+#define M_PI 3.14159265359
+
+void main() {
+ uint t = BLOCK_SIZE + 1;
+}
+)<!>"
};
setup(nullptr, nullptr, _compute_code, "ComputeShaderRD");
}
diff --git a/tests/python_build/fixtures/rd_glsl/vertex_fragment_expected_full.glsl b/tests/python_build/fixtures/rd_glsl/vertex_fragment_expected_full.glsl
index ff804dbf89..2f809f1bfe 100644
--- a/tests/python_build/fixtures/rd_glsl/vertex_fragment_expected_full.glsl
+++ b/tests/python_build/fixtures/rd_glsl/vertex_fragment_expected_full.glsl
@@ -11,10 +11,33 @@ public:
VertexFragmentShaderRD() {
static const char _vertex_code[] = {
-10,35,118,101,114,115,105,111,110,32,52,53,48,10,10,35,86,69,82,83,73,79,78,95,68,69,70,73,78,69,83,10,10,35,100,101,102,105,110,101,32,77,95,80,73,32,51,46,49,52,49,53,57,50,54,53,51,53,57,10,10,108,97,121,111,117,116,40,108,111,99,97,116,105,111,110,32,61,32,48,41,32,111,117,116,32,118,101,99,50,32,117,118,95,105,110,116,101,114,112,59,10,10,118,111,105,100,32,109,97,105,110,40,41,32,123,10,9,117,118,95,105,110,116,101,114,112,32,61,32,118,101,99,50,40,48,44,32,49,41,59,10,125,10,10,0
+R"<!>(
+#version 450
+
+#VERSION_DEFINES
+
+#define M_PI 3.14159265359
+
+layout(location = 0) out vec2 uv_interp;
+
+void main() {
+ uv_interp = vec2(0, 1);
+}
+
+)<!>"
};
static const char _fragment_code[] = {
-10,35,118,101,114,115,105,111,110,32,52,53,48,10,10,35,86,69,82,83,73,79,78,95,68,69,70,73,78,69,83,10,10,108,97,121,111,117,116,40,108,111,99,97,116,105,111,110,32,61,32,48,41,32,105,110,32,118,101,99,50,32,117,118,95,105,110,116,101,114,112,59,10,10,118,111,105,100,32,109,97,105,110,40,41,32,123,10,9,117,118,95,105,110,116,101,114,112,32,61,32,118,101,99,50,40,49,44,32,48,41,59,10,125,10,0
+R"<!>(
+#version 450
+
+#VERSION_DEFINES
+
+layout(location = 0) in vec2 uv_interp;
+
+void main() {
+ uv_interp = vec2(1, 0);
+}
+)<!>"
};
setup(_vertex_code, _fragment_code, nullptr, "VertexFragmentShaderRD");
}
diff --git a/tests/scene/test_audio_stream_wav.h b/tests/scene/test_audio_stream_wav.h
index e8f3c9e8f5..d3d5cc8a30 100644
--- a/tests/scene/test_audio_stream_wav.h
+++ b/tests/scene/test_audio_stream_wav.h
@@ -159,6 +159,8 @@ void run_test(String file_name, AudioStreamWAV::Format data_format, bool stereo,
for (const ResourceImporter::ImportOption &E : options_list) {
options_map[E.option.name] = E.default_value;
}
+ // Compressed streams can't be saved, disable compression.
+ options_map["compress/mode"] = 0;
REQUIRE(wav_importer->import(save_path, save_path, options_map, nullptr) == OK);
@@ -179,27 +181,27 @@ void run_test(String file_name, AudioStreamWAV::Format data_format, bool stereo,
}
}
-TEST_CASE("[AudioStreamWAV] Mono PCM8 format") {
+TEST_CASE("[Audio][AudioStreamWAV] Mono PCM8 format") {
run_test("test_pcm8_mono.wav", AudioStreamWAV::FORMAT_8_BITS, false, WAV_RATE, WAV_COUNT);
}
-TEST_CASE("[AudioStreamWAV] Mono PCM16 format") {
+TEST_CASE("[Audio][AudioStreamWAV] Mono PCM16 format") {
run_test("test_pcm16_mono.wav", AudioStreamWAV::FORMAT_16_BITS, false, WAV_RATE, WAV_COUNT);
}
-TEST_CASE("[AudioStreamWAV] Stereo PCM8 format") {
+TEST_CASE("[Audio][AudioStreamWAV] Stereo PCM8 format") {
run_test("test_pcm8_stereo.wav", AudioStreamWAV::FORMAT_8_BITS, true, WAV_RATE, WAV_COUNT);
}
-TEST_CASE("[AudioStreamWAV] Stereo PCM16 format") {
+TEST_CASE("[Audio][AudioStreamWAV] Stereo PCM16 format") {
run_test("test_pcm16_stereo.wav", AudioStreamWAV::FORMAT_16_BITS, true, WAV_RATE, WAV_COUNT);
}
-TEST_CASE("[AudioStreamWAV] Alternate mix rate") {
+TEST_CASE("[Audio][AudioStreamWAV] Alternate mix rate") {
run_test("test_pcm16_stereo_38000Hz.wav", AudioStreamWAV::FORMAT_16_BITS, true, 38000, 38000);
}
-TEST_CASE("[AudioStreamWAV] save_to_wav() adds '.wav' file extension automatically") {
+TEST_CASE("[Audio][AudioStreamWAV] save_to_wav() adds '.wav' file extension automatically") {
String save_path = TestUtils::get_temp_path("test_wav_extension");
Vector<uint8_t> test_data = gen_pcm8_test(WAV_RATE, WAV_COUNT, false);
Ref<AudioStreamWAV> stream = memnew(AudioStreamWAV);
@@ -211,7 +213,7 @@ TEST_CASE("[AudioStreamWAV] save_to_wav() adds '.wav' file extension automatical
CHECK(error == OK);
}
-TEST_CASE("[AudioStreamWAV] Default values") {
+TEST_CASE("[Audio][AudioStreamWAV] Default values") {
Ref<AudioStreamWAV> stream = memnew(AudioStreamWAV);
CHECK(stream->get_format() == AudioStreamWAV::FORMAT_8_BITS);
CHECK(stream->get_loop_mode() == AudioStreamWAV::LOOP_DISABLED);
@@ -225,11 +227,11 @@ TEST_CASE("[AudioStreamWAV] Default values") {
CHECK(stream->get_stream_name() == "");
}
-TEST_CASE("[AudioStreamWAV] Save empty file") {
+TEST_CASE("[Audio][AudioStreamWAV] Save empty file") {
run_test("test_empty.wav", AudioStreamWAV::FORMAT_8_BITS, false, WAV_RATE, 0);
}
-TEST_CASE("[AudioStreamWAV] Saving IMA ADPCM is not supported") {
+TEST_CASE("[Audio][AudioStreamWAV] Saving IMA ADPCM is not supported") {
String save_path = TestUtils::get_temp_path("test_adpcm.wav");
Ref<AudioStreamWAV> stream = memnew(AudioStreamWAV);
stream->set_format(AudioStreamWAV::FORMAT_IMA_ADPCM);
diff --git a/tests/scene/test_button.h b/tests/scene/test_button.h
new file mode 100644
index 0000000000..55097edc95
--- /dev/null
+++ b/tests/scene/test_button.h
@@ -0,0 +1,66 @@
+/**************************************************************************/
+/* test_button.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_BUTTON_H
+#define TEST_BUTTON_H
+
+#include "scene/gui/button.h"
+#include "scene/main/window.h"
+
+#include "tests/test_macros.h"
+
+namespace TestButton {
+TEST_CASE("[SceneTree][Button] is_hovered()") {
+ // Create new button instance.
+ Button *button = memnew(Button);
+ CHECK(button != nullptr);
+ Window *root = SceneTree::get_singleton()->get_root();
+ root->add_child(button);
+
+ // Set up button's size and position.
+ button->set_size(Size2i(50, 50));
+ button->set_position(Size2i(10, 10));
+
+ // Button should initially be not hovered.
+ CHECK(button->is_hovered() == false);
+
+ // Simulate mouse entering the button.
+ SEND_GUI_MOUSE_MOTION_EVENT(Point2i(25, 25), MouseButtonMask::NONE, Key::NONE);
+ CHECK(button->is_hovered() == true);
+
+ // Simulate mouse exiting the button.
+ SEND_GUI_MOUSE_MOTION_EVENT(Point2i(150, 150), MouseButtonMask::NONE, Key::NONE);
+ CHECK(button->is_hovered() == false);
+
+ memdelete(button);
+}
+
+} //namespace TestButton
+#endif // TEST_BUTTON_H
diff --git a/tests/scene/test_gradient_texture.h b/tests/scene/test_gradient_texture.h
new file mode 100644
index 0000000000..16a92fbe4a
--- /dev/null
+++ b/tests/scene/test_gradient_texture.h
@@ -0,0 +1,87 @@
+/**************************************************************************/
+/* test_gradient_texture.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_GRADIENT_TEXTURE_H
+#define TEST_GRADIENT_TEXTURE_H
+
+#include "scene/resources/gradient_texture.h"
+
+#include "tests/test_macros.h"
+
+namespace TestGradientTexture {
+
+// [SceneTree] in a test case name enables initializing a mock render server,
+// which ImageTexture is dependent on.
+TEST_CASE("[SceneTree][GradientTexture1D] Create GradientTexture1D") {
+ Ref<GradientTexture1D> gradient_texture = memnew(GradientTexture1D);
+
+ Ref<Gradient> test_gradient = memnew(Gradient);
+ gradient_texture->set_gradient(test_gradient);
+ CHECK(gradient_texture->get_gradient() == test_gradient);
+
+ gradient_texture->set_width(83);
+ CHECK(gradient_texture->get_width() == 83);
+
+ gradient_texture->set_use_hdr(true);
+ CHECK(gradient_texture->is_using_hdr());
+}
+
+TEST_CASE("[SceneTree][GradientTexture2D] Create GradientTexture2D") {
+ Ref<GradientTexture2D> gradient_texture = memnew(GradientTexture2D);
+
+ Ref<Gradient> test_gradient = memnew(Gradient);
+ gradient_texture->set_gradient(test_gradient);
+ CHECK(gradient_texture->get_gradient() == test_gradient);
+
+ gradient_texture->set_width(82);
+ CHECK(gradient_texture->get_width() == 82);
+
+ gradient_texture->set_height(81);
+ CHECK(gradient_texture->get_height() == 81);
+
+ gradient_texture->set_use_hdr(true);
+ CHECK(gradient_texture->is_using_hdr());
+
+ gradient_texture->set_fill(GradientTexture2D::Fill::FILL_SQUARE);
+ CHECK(gradient_texture->get_fill() == GradientTexture2D::Fill::FILL_SQUARE);
+
+ gradient_texture->set_fill_from(Vector2(0.2, 0.25));
+ CHECK(gradient_texture->get_fill_from() == Vector2(0.2, 0.25));
+
+ gradient_texture->set_fill_to(Vector2(0.35, 0.5));
+ CHECK(gradient_texture->get_fill_to() == Vector2(0.35, 0.5));
+
+ gradient_texture->set_repeat(GradientTexture2D::Repeat::REPEAT);
+ CHECK(gradient_texture->get_repeat() == GradientTexture2D::Repeat::REPEAT);
+}
+
+} //namespace TestGradientTexture
+
+#endif // TEST_GRADIENT_TEXTURE_H
diff --git a/tests/scene/test_height_map_shape_3d.h b/tests/scene/test_height_map_shape_3d.h
new file mode 100644
index 0000000000..60a8db4e58
--- /dev/null
+++ b/tests/scene/test_height_map_shape_3d.h
@@ -0,0 +1,122 @@
+/**************************************************************************/
+/* test_height_map_shape_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 TEST_HEIGHT_MAP_SHAPE_3D_H
+#define TEST_HEIGHT_MAP_SHAPE_3D_H
+
+#include "scene/resources/3d/height_map_shape_3d.h"
+#include "scene/resources/image_texture.h"
+
+#include "tests/test_macros.h"
+#include "tests/test_utils.h"
+
+namespace TestHeightMapShape3D {
+
+TEST_CASE("[SceneTree][HeightMapShape3D] Constructor") {
+ Ref<HeightMapShape3D> height_map_shape = memnew(HeightMapShape3D);
+ CHECK(height_map_shape->get_map_width() == 2);
+ CHECK(height_map_shape->get_map_depth() == 2);
+ CHECK(height_map_shape->get_map_data().size() == 4);
+ CHECK(height_map_shape->get_min_height() == 0.0);
+ CHECK(height_map_shape->get_max_height() == 0.0);
+}
+
+TEST_CASE("[SceneTree][HeightMapShape3D] set_map_width and get_map_width") {
+ Ref<HeightMapShape3D> height_map_shape = memnew(HeightMapShape3D);
+ height_map_shape->set_map_width(10);
+ CHECK(height_map_shape->get_map_width() == 10);
+}
+
+TEST_CASE("[SceneTree][HeightMapShape3D] set_map_depth and get_map_depth") {
+ Ref<HeightMapShape3D> height_map_shape = memnew(HeightMapShape3D);
+ height_map_shape->set_map_depth(15);
+ CHECK(height_map_shape->get_map_depth() == 15);
+}
+
+TEST_CASE("[SceneTree][HeightMapShape3D] set_map_data and get_map_data") {
+ Ref<HeightMapShape3D> height_map_shape = memnew(HeightMapShape3D);
+ Vector<real_t> map_data;
+ map_data.push_back(1.0);
+ map_data.push_back(2.0);
+ height_map_shape->set_map_data(map_data);
+ CHECK(height_map_shape->get_map_data().size() == 4.0);
+ CHECK(height_map_shape->get_map_data()[0] == 0.0);
+ CHECK(height_map_shape->get_map_data()[1] == 0.0);
+}
+
+TEST_CASE("[SceneTree][HeightMapShape3D] get_min_height") {
+ Ref<HeightMapShape3D> height_map_shape = memnew(HeightMapShape3D);
+ height_map_shape->set_map_width(3);
+ height_map_shape->set_map_depth(1);
+ height_map_shape->set_map_data(Vector<real_t>{ 1.0, 2.0, 0.5 });
+ CHECK(height_map_shape->get_min_height() == 0.5);
+}
+
+TEST_CASE("[SceneTree][HeightMapShape3D] get_max_height") {
+ Ref<HeightMapShape3D> height_map_shape = memnew(HeightMapShape3D);
+ height_map_shape->set_map_width(3);
+ height_map_shape->set_map_depth(1);
+ height_map_shape->set_map_data(Vector<real_t>{ 1.0, 2.0, 0.5 });
+ CHECK(height_map_shape->get_max_height() == 2.0);
+}
+
+TEST_CASE("[SceneTree][HeightMapShape3D] update_map_data_from_image") {
+ // Create a HeightMapShape3D instance.
+ Ref<HeightMapShape3D> height_map_shape = memnew(HeightMapShape3D);
+
+ // Create a mock image with FORMAT_R8 and set its data.
+ Vector<uint8_t> image_data;
+ image_data.push_back(0);
+ image_data.push_back(128);
+ image_data.push_back(255);
+ image_data.push_back(64);
+
+ Ref<Image> image = memnew(Image);
+ image->set_data(2, 2, false, Image::FORMAT_R8, image_data);
+
+ height_map_shape->update_map_data_from_image(image, 0.0, 10.0);
+
+ // Check the map data.
+ Vector<real_t> expected_map_data = { 0.0, 5.0, 10.0, 2.5 };
+ Vector<real_t> actual_map_data = height_map_shape->get_map_data();
+ real_t tolerance = 0.1;
+
+ for (int i = 0; i < expected_map_data.size(); ++i) {
+ CHECK(Math::abs(actual_map_data[i] - expected_map_data[i]) < tolerance);
+ }
+
+ // Check the min and max heights.
+ CHECK(height_map_shape->get_min_height() == 0.0);
+ CHECK(height_map_shape->get_max_height() == 10.0);
+}
+
+} // namespace TestHeightMapShape3D
+
+#endif // TEST_HEIGHT_MAP_SHAPE_3D_H
diff --git a/tests/scene/test_option_button.h b/tests/scene/test_option_button.h
new file mode 100644
index 0000000000..56c1c7d611
--- /dev/null
+++ b/tests/scene/test_option_button.h
@@ -0,0 +1,136 @@
+/**************************************************************************/
+/* test_option_button.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_OPTION_BUTTON_H
+#define TEST_OPTION_BUTTON_H
+
+#include "scene/gui/option_button.h"
+
+#include "tests/test_macros.h"
+
+namespace TestOptionButton {
+
+TEST_CASE("[SceneTree][OptionButton] Initialization") {
+ OptionButton *test_opt = memnew(OptionButton);
+
+ SUBCASE("There should be no options right after initialization") {
+ CHECK_FALSE(test_opt->has_selectable_items());
+ CHECK(test_opt->get_item_count() == 0);
+ }
+
+ memdelete(test_opt);
+}
+
+TEST_CASE("[SceneTree][OptionButton] Single item") {
+ OptionButton *test_opt = memnew(OptionButton);
+
+ SUBCASE("There should a single item after after adding one") {
+ test_opt->add_item("single", 1013);
+
+ CHECK(test_opt->has_selectable_items());
+ CHECK(test_opt->get_item_count() == 1);
+ CHECK(test_opt->get_item_index(1013) == 0);
+ CHECK(test_opt->get_item_id(0) == 1013);
+
+ test_opt->remove_item(0);
+
+ CHECK_FALSE(test_opt->has_selectable_items());
+ CHECK(test_opt->get_item_count() == 0);
+ }
+
+ SUBCASE("There should a single item after after adding an icon") {
+ Ref<Texture2D> test_icon = memnew(Texture2D);
+ test_opt->add_icon_item(test_icon, "icon", 345);
+
+ CHECK(test_opt->has_selectable_items());
+ CHECK(test_opt->get_item_count() == 1);
+ CHECK(test_opt->get_item_index(345) == 0);
+ CHECK(test_opt->get_item_id(0) == 345);
+
+ test_opt->remove_item(0);
+
+ CHECK_FALSE(test_opt->has_selectable_items());
+ CHECK(test_opt->get_item_count() == 0);
+ }
+
+ memdelete(test_opt);
+}
+
+TEST_CASE("[SceneTree][OptionButton] Complex structure") {
+ OptionButton *test_opt = memnew(OptionButton);
+
+ SUBCASE("Creating a complex structure and checking getters") {
+ // Regular item at index 0.
+ Ref<Texture2D> test_icon1 = memnew(Texture2D);
+ Ref<Texture2D> test_icon2 = memnew(Texture2D);
+ // Regular item at index 3.
+ Ref<Texture2D> test_icon4 = memnew(Texture2D);
+
+ test_opt->add_item("first", 100);
+ test_opt->add_icon_item(test_icon1, "second_icon", 101);
+ test_opt->add_icon_item(test_icon2, "third_icon", 102);
+ test_opt->add_item("fourth", 104);
+ test_opt->add_icon_item(test_icon4, "fifth_icon", 104);
+
+ // Disable test_icon4.
+ test_opt->set_item_disabled(4, true);
+
+ CHECK(test_opt->has_selectable_items());
+ CHECK(test_opt->get_item_count() == 5);
+
+ // Check for test_icon2.
+ CHECK(test_opt->get_item_index(102) == 2);
+ CHECK(test_opt->get_item_id(2) == 102);
+
+ // Remove the two regular items.
+ test_opt->remove_item(3);
+ test_opt->remove_item(0);
+
+ CHECK(test_opt->has_selectable_items());
+ CHECK(test_opt->get_item_count() == 3);
+
+ // Check test_icon4.
+ CHECK(test_opt->get_item_index(104) == 2);
+ CHECK(test_opt->get_item_id(2) == 104);
+
+ // Remove the two non-disabled icon items.
+ test_opt->remove_item(1);
+ test_opt->remove_item(0);
+
+ CHECK_FALSE(test_opt->has_selectable_items());
+ CHECK(test_opt->get_item_count() == 1);
+ }
+
+ memdelete(test_opt);
+}
+
+} // namespace TestOptionButton
+
+#endif // TEST_OPTION_BUTTON_H
diff --git a/tests/scene/test_parallax_2d.h b/tests/scene/test_parallax_2d.h
new file mode 100644
index 0000000000..2fdbf09e09
--- /dev/null
+++ b/tests/scene/test_parallax_2d.h
@@ -0,0 +1,131 @@
+/**************************************************************************/
+/* test_parallax_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 TEST_PARALLAX_2D_H
+#define TEST_PARALLAX_2D_H
+
+#include "scene/2d/parallax_2d.h"
+#include "tests/test_macros.h"
+
+namespace TestParallax2D {
+
+// Test cases for the Parallax2D class to ensure its properties are set and retrieved correctly.
+
+TEST_CASE("[SceneTree][Parallax2D] Scroll Scale") {
+ // Test setting and getting the scroll scale.
+ Parallax2D *parallax = memnew(Parallax2D);
+ Size2 scale(2, 2);
+ parallax->set_scroll_scale(scale);
+ CHECK(parallax->get_scroll_scale() == scale);
+ memdelete(parallax);
+}
+
+TEST_CASE("[SceneTree][Parallax2D] Repeat Size") {
+ // Test setting and getting the repeat size.
+ Parallax2D *parallax = memnew(Parallax2D);
+ Size2 size(100, 100);
+ parallax->set_repeat_size(size);
+ CHECK(parallax->get_repeat_size() == size);
+ memdelete(parallax);
+}
+
+TEST_CASE("[SceneTree][Parallax2D] Repeat Times") {
+ // Test setting and getting the repeat times.
+ Parallax2D *parallax = memnew(Parallax2D);
+ int times = 5;
+ parallax->set_repeat_times(times);
+ CHECK(parallax->get_repeat_times() == times);
+ memdelete(parallax);
+}
+
+TEST_CASE("[SceneTree][Parallax2D] Autoscroll") {
+ // Test setting and getting the autoscroll values.
+ Parallax2D *parallax = memnew(Parallax2D);
+ Point2 autoscroll(1, 1);
+ parallax->set_autoscroll(autoscroll);
+ CHECK(parallax->get_autoscroll() == autoscroll);
+ memdelete(parallax);
+}
+
+TEST_CASE("[SceneTree][Parallax2D] Scroll Offset") {
+ // Test setting and getting the scroll offset.
+ Parallax2D *parallax = memnew(Parallax2D);
+ Point2 offset(10, 10);
+ parallax->set_scroll_offset(offset);
+ CHECK(parallax->get_scroll_offset() == offset);
+ memdelete(parallax);
+}
+
+TEST_CASE("[SceneTree][Parallax2D] Screen Offset") {
+ // Test setting and getting the screen offset.
+ Parallax2D *parallax = memnew(Parallax2D);
+ Point2 offset(20, 20);
+ parallax->set_screen_offset(offset);
+ CHECK(parallax->get_screen_offset() == offset);
+ memdelete(parallax);
+}
+
+TEST_CASE("[SceneTree][Parallax2D] Limit Begin") {
+ // Test setting and getting the limit begin values.
+ Parallax2D *parallax = memnew(Parallax2D);
+ Point2 limit_begin(-100, -100);
+ parallax->set_limit_begin(limit_begin);
+ CHECK(parallax->get_limit_begin() == limit_begin);
+ memdelete(parallax);
+}
+
+TEST_CASE("[SceneTree][Parallax2D] Limit End") {
+ // Test setting and getting the limit end values.
+ Parallax2D *parallax = memnew(Parallax2D);
+ Point2 limit_end(100, 100);
+ parallax->set_limit_end(limit_end);
+ CHECK(parallax->get_limit_end() == limit_end);
+ memdelete(parallax);
+}
+
+TEST_CASE("[SceneTree][Parallax2D] Follow Viewport") {
+ // Test setting and getting the follow viewport flag.
+ Parallax2D *parallax = memnew(Parallax2D);
+ parallax->set_follow_viewport(false);
+ CHECK_FALSE(parallax->get_follow_viewport());
+ memdelete(parallax);
+}
+
+TEST_CASE("[SceneTree][Parallax2D] Ignore Camera Scroll") {
+ // Test setting and getting the ignore camera scroll flag.
+ Parallax2D *parallax = memnew(Parallax2D);
+ parallax->set_ignore_camera_scroll(true);
+ CHECK(parallax->is_ignore_camera_scroll());
+ memdelete(parallax);
+}
+
+} // namespace TestParallax2D
+
+#endif // TEST_PARALLAX_2D_H
diff --git a/tests/scene/test_path_2d.h b/tests/scene/test_path_2d.h
index 7b6cec96db..4703bfa3bb 100644
--- a/tests/scene/test_path_2d.h
+++ b/tests/scene/test_path_2d.h
@@ -40,7 +40,7 @@ namespace TestPath2D {
TEST_CASE("[SceneTree][Path2D] Initialization") {
SUBCASE("Path should be empty right after initialization") {
Path2D *test_path = memnew(Path2D);
- CHECK(test_path->get_curve() == nullptr);
+ CHECK(test_path->get_curve().is_null());
memdelete(test_path);
}
}
diff --git a/tests/scene/test_path_3d.h b/tests/scene/test_path_3d.h
index f779f514a4..70c7099d48 100644
--- a/tests/scene/test_path_3d.h
+++ b/tests/scene/test_path_3d.h
@@ -40,7 +40,7 @@ namespace TestPath3D {
TEST_CASE("[Path3D] Initialization") {
SUBCASE("Path should be empty right after initialization") {
Path3D *test_path = memnew(Path3D);
- CHECK(test_path->get_curve() == nullptr);
+ CHECK(test_path->get_curve().is_null());
memdelete(test_path);
}
}
diff --git a/tests/scene/test_primitives.h b/tests/scene/test_primitives.h
index f105e1ac04..59f23983e5 100644
--- a/tests/scene/test_primitives.h
+++ b/tests/scene/test_primitives.h
@@ -609,7 +609,7 @@ TEST_CASE("[SceneTree][Primitive][TubeTrail] TubeTrail Primitive") {
CHECK(tube->get_sections() >= 0);
CHECK(tube->get_section_length() > 0);
CHECK(tube->get_section_rings() >= 0);
- CHECK(tube->get_curve() == nullptr);
+ CHECK(tube->get_curve().is_null());
CHECK(tube->get_builtin_bind_pose_count() >= 0);
}
@@ -669,7 +669,7 @@ TEST_CASE("[SceneTree][Primitive][RibbonTrail] RibbonTrail Primitive") {
CHECK(ribbon->get_section_length() > 0);
CHECK(ribbon->get_section_segments() >= 0);
CHECK(ribbon->get_builtin_bind_pose_count() >= 0);
- CHECK(ribbon->get_curve() == nullptr);
+ CHECK(ribbon->get_curve().is_null());
CHECK((ribbon->get_shape() == RibbonTrailMesh::SHAPE_CROSS ||
ribbon->get_shape() == RibbonTrailMesh::SHAPE_FLAT));
}
@@ -731,7 +731,7 @@ TEST_CASE("[SceneTree][Primitive][Text] Text Primitive") {
text->get_vertical_alignment() == VERTICAL_ALIGNMENT_TOP ||
text->get_vertical_alignment() == VERTICAL_ALIGNMENT_CENTER ||
text->get_vertical_alignment() == VERTICAL_ALIGNMENT_FILL));
- CHECK(text->get_font() == nullptr);
+ CHECK(text->get_font().is_null());
CHECK(text->get_font_size() > 0);
CHECK(text->get_line_spacing() >= 0);
CHECK((text->get_autowrap_mode() == TextServer::AUTOWRAP_OFF ||
diff --git a/tests/scene/test_text_edit.h b/tests/scene/test_text_edit.h
index 69e27fe7a0..46a5046b21 100644
--- a/tests/scene/test_text_edit.h
+++ b/tests/scene/test_text_edit.h
@@ -4232,6 +4232,18 @@ TEST_CASE("[SceneTree][TextEdit] text entry") {
CHECK(text_edit->get_caret_line(0) == 0);
CHECK(text_edit->get_caret_column(0) == 4);
text_edit->remove_secondary_carets();
+
+ // Remove when there are no words, only symbols.
+ text_edit->set_text("#{}");
+ text_edit->set_caret_line(0);
+ text_edit->set_caret_column(3);
+
+ SEND_GUI_ACTION("ui_text_backspace_word");
+ CHECK(text_edit->get_viewport()->is_input_handled());
+ CHECK_FALSE(text_edit->has_selection());
+ CHECK(text_edit->get_text() == "");
+ CHECK(text_edit->get_caret_line(0) == 0);
+ CHECK(text_edit->get_caret_column(0) == 0);
}
SUBCASE("[TextEdit] ui_text_backspace_word same line") {
@@ -4891,6 +4903,18 @@ TEST_CASE("[SceneTree][TextEdit] text entry") {
CHECK(text_edit->get_caret_line(0) == 0);
CHECK(text_edit->get_caret_column(0) == 2);
text_edit->remove_secondary_carets();
+
+ // Remove when there are no words, only symbols.
+ text_edit->set_text("#{}");
+ text_edit->set_caret_line(0);
+ text_edit->set_caret_column(0);
+
+ SEND_GUI_ACTION("ui_text_delete_word");
+ CHECK(text_edit->get_viewport()->is_input_handled());
+ CHECK_FALSE(text_edit->has_selection());
+ CHECK(text_edit->get_text() == "");
+ CHECK(text_edit->get_caret_line(0) == 0);
+ CHECK(text_edit->get_caret_column(0) == 0);
}
SUBCASE("[TextEdit] ui_text_delete_word same line") {
@@ -5301,6 +5325,16 @@ TEST_CASE("[SceneTree][TextEdit] text entry") {
SIGNAL_CHECK("caret_changed", empty_signal_args);
SIGNAL_CHECK_FALSE("text_changed");
SIGNAL_CHECK_FALSE("lines_edited_from");
+
+ // Move when there are no words, only symbols.
+ text_edit->set_text("#{}");
+ text_edit->set_caret_line(0);
+ text_edit->set_caret_column(3);
+
+ SEND_GUI_ACTION("ui_text_caret_word_left");
+ CHECK(text_edit->get_viewport()->is_input_handled());
+ CHECK(text_edit->get_caret_line(0) == 0);
+ CHECK(text_edit->get_caret_column(0) == 0);
}
SUBCASE("[TextEdit] ui_text_caret_left") {
@@ -5563,6 +5597,16 @@ TEST_CASE("[SceneTree][TextEdit] text entry") {
SIGNAL_CHECK("caret_changed", empty_signal_args);
SIGNAL_CHECK_FALSE("text_changed");
SIGNAL_CHECK_FALSE("lines_edited_from");
+
+ // Move when there are no words, only symbols.
+ text_edit->set_text("#{}");
+ text_edit->set_caret_line(0);
+ text_edit->set_caret_column(0);
+
+ SEND_GUI_ACTION("ui_text_caret_word_right");
+ CHECK(text_edit->get_viewport()->is_input_handled());
+ CHECK(text_edit->get_caret_line(0) == 0);
+ CHECK(text_edit->get_caret_column(0) == 3);
}
SUBCASE("[TextEdit] ui_text_caret_right") {
diff --git a/tests/scene/test_tree.h b/tests/scene/test_tree.h
index 41ef39d621..e19f8311e2 100644
--- a/tests/scene/test_tree.h
+++ b/tests/scene/test_tree.h
@@ -108,6 +108,30 @@ TEST_CASE("[SceneTree][Tree]") {
memdelete(tree);
}
+ // https://github.com/godotengine/godot/issues/96205
+ SUBCASE("[Tree] Get last item after removal.") {
+ Tree *tree = memnew(Tree);
+ TreeItem *root = tree->create_item();
+
+ TreeItem *child1 = tree->create_item(root);
+ TreeItem *child2 = tree->create_item(root);
+
+ CHECK_EQ(root->get_child_count(), 2);
+ CHECK_EQ(tree->get_last_item(), child2);
+
+ root->remove_child(child2);
+
+ CHECK_EQ(root->get_child_count(), 1);
+ CHECK_EQ(tree->get_last_item(), child1);
+
+ root->add_child(child2);
+
+ CHECK_EQ(root->get_child_count(), 2);
+ CHECK_EQ(tree->get_last_item(), child2);
+
+ memdelete(tree);
+ }
+
SUBCASE("[Tree] Previous and Next items.") {
Tree *tree = memnew(Tree);
TreeItem *root = tree->create_item();
diff --git a/tests/servers/test_navigation_server_3d.h b/tests/servers/test_navigation_server_3d.h
index cf6b89c330..4411b1aae5 100644
--- a/tests/servers/test_navigation_server_3d.h
+++ b/tests/servers/test_navigation_server_3d.h
@@ -31,6 +31,7 @@
#ifndef TEST_NAVIGATION_SERVER_3D_H
#define TEST_NAVIGATION_SERVER_3D_H
+#include "modules/navigation/nav_utils.h"
#include "scene/3d/mesh_instance_3d.h"
#include "scene/resources/3d/primitive_meshes.h"
#include "servers/navigation_server_3d.h"
@@ -61,6 +62,32 @@ static inline Array build_array(Variant item, Targs... Fargs) {
return a;
}
+struct GreaterThan {
+ bool operator()(int p_a, int p_b) const { return p_a > p_b; }
+};
+
+struct CompareArrayValues {
+ const int *array;
+
+ CompareArrayValues(const int *p_array) :
+ array(p_array) {}
+
+ bool operator()(uint32_t p_index_a, uint32_t p_index_b) const {
+ return array[p_index_a] < array[p_index_b];
+ }
+};
+
+struct RegisterHeapIndexes {
+ uint32_t *indexes;
+
+ RegisterHeapIndexes(uint32_t *p_indexes) :
+ indexes(p_indexes) {}
+
+ void operator()(uint32_t p_vector_index, uint32_t p_heap_index) {
+ indexes[p_vector_index] = p_heap_index;
+ }
+};
+
TEST_SUITE("[Navigation]") {
TEST_CASE("[NavigationServer3D] Server should be empty when initialized") {
NavigationServer3D *navigation_server = NavigationServer3D::get_singleton();
@@ -788,6 +815,139 @@ TEST_SUITE("[Navigation]") {
CHECK_EQ(navigation_mesh->get_vertices().size(), 0);
}
*/
+
+ TEST_CASE("[Heap] size") {
+ gd::Heap<int> heap;
+
+ CHECK(heap.size() == 0);
+
+ heap.push(0);
+ CHECK(heap.size() == 1);
+
+ heap.push(1);
+ CHECK(heap.size() == 2);
+
+ heap.pop();
+ CHECK(heap.size() == 1);
+
+ heap.pop();
+ CHECK(heap.size() == 0);
+ }
+
+ TEST_CASE("[Heap] is_empty") {
+ gd::Heap<int> heap;
+
+ CHECK(heap.is_empty() == true);
+
+ heap.push(0);
+ CHECK(heap.is_empty() == false);
+
+ heap.pop();
+ CHECK(heap.is_empty() == true);
+ }
+
+ TEST_CASE("[Heap] push/pop") {
+ SUBCASE("Default comparator") {
+ gd::Heap<int> heap;
+
+ heap.push(2);
+ heap.push(7);
+ heap.push(5);
+ heap.push(3);
+ heap.push(4);
+
+ CHECK(heap.pop() == 7);
+ CHECK(heap.pop() == 5);
+ CHECK(heap.pop() == 4);
+ CHECK(heap.pop() == 3);
+ CHECK(heap.pop() == 2);
+ }
+
+ SUBCASE("Custom comparator") {
+ GreaterThan greaterThan;
+ gd::Heap<int, GreaterThan> heap(greaterThan);
+
+ heap.push(2);
+ heap.push(7);
+ heap.push(5);
+ heap.push(3);
+ heap.push(4);
+
+ CHECK(heap.pop() == 2);
+ CHECK(heap.pop() == 3);
+ CHECK(heap.pop() == 4);
+ CHECK(heap.pop() == 5);
+ CHECK(heap.pop() == 7);
+ }
+
+ SUBCASE("Intermediate pops") {
+ gd::Heap<int> heap;
+
+ heap.push(0);
+ heap.push(3);
+ heap.pop();
+ heap.push(1);
+ heap.push(2);
+
+ CHECK(heap.pop() == 2);
+ CHECK(heap.pop() == 1);
+ CHECK(heap.pop() == 0);
+ }
+ }
+
+ TEST_CASE("[Heap] shift") {
+ int values[] = { 5, 3, 6, 7, 1 };
+ uint32_t heap_indexes[] = { 0, 0, 0, 0, 0 };
+ CompareArrayValues comparator(values);
+ RegisterHeapIndexes indexer(heap_indexes);
+ gd::Heap<uint32_t, CompareArrayValues, RegisterHeapIndexes> heap(comparator, indexer);
+
+ heap.push(0);
+ heap.push(1);
+ heap.push(2);
+ heap.push(3);
+ heap.push(4);
+
+ // Shift down: 6 -> 2
+ values[2] = 2;
+ heap.shift(heap_indexes[2]);
+
+ // Shift up: 5 -> 8
+ values[0] = 8;
+ heap.shift(heap_indexes[0]);
+
+ CHECK(heap.pop() == 0);
+ CHECK(heap.pop() == 3);
+ CHECK(heap.pop() == 1);
+ CHECK(heap.pop() == 2);
+ CHECK(heap.pop() == 4);
+
+ CHECK(heap_indexes[0] == UINT32_MAX);
+ CHECK(heap_indexes[1] == UINT32_MAX);
+ CHECK(heap_indexes[2] == UINT32_MAX);
+ CHECK(heap_indexes[3] == UINT32_MAX);
+ CHECK(heap_indexes[4] == UINT32_MAX);
+ }
+
+ TEST_CASE("[Heap] clear") {
+ uint32_t heap_indexes[] = { 0, 0, 0, 0 };
+ RegisterHeapIndexes indexer(heap_indexes);
+ gd::Heap<uint32_t, Comparator<uint32_t>, RegisterHeapIndexes> heap(indexer);
+
+ heap.push(0);
+ heap.push(2);
+ heap.push(1);
+ heap.push(3);
+
+ heap.clear();
+
+ CHECK(heap.size() == 0);
+
+ CHECK(heap_indexes[0] == UINT32_MAX);
+ CHECK(heap_indexes[1] == UINT32_MAX);
+ CHECK(heap_indexes[2] == UINT32_MAX);
+ CHECK(heap_indexes[3] == UINT32_MAX);
+ }
}
} //namespace TestNavigationServer3D
diff --git a/tests/test_main.cpp b/tests/test_main.cpp
index 5308fe5e80..2b6461e9ca 100644
--- a/tests/test_main.cpp
+++ b/tests/test_main.cpp
@@ -48,6 +48,7 @@
#include "tests/core/io/test_image.h"
#include "tests/core/io/test_ip.h"
#include "tests/core/io/test_json.h"
+#include "tests/core/io/test_json_native.h"
#include "tests/core/io/test_marshalls.h"
#include "tests/core/io/test_pck_packer.h"
#include "tests/core/io/test_resource.h"
@@ -104,18 +105,21 @@
#include "tests/scene/test_animation.h"
#include "tests/scene/test_audio_stream_wav.h"
#include "tests/scene/test_bit_map.h"
+#include "tests/scene/test_button.h"
#include "tests/scene/test_camera_2d.h"
#include "tests/scene/test_control.h"
#include "tests/scene/test_curve.h"
#include "tests/scene/test_curve_2d.h"
#include "tests/scene/test_curve_3d.h"
#include "tests/scene/test_gradient.h"
+#include "tests/scene/test_gradient_texture.h"
#include "tests/scene/test_image_texture.h"
#include "tests/scene/test_image_texture_3d.h"
#include "tests/scene/test_instance_placeholder.h"
#include "tests/scene/test_node.h"
#include "tests/scene/test_node_2d.h"
#include "tests/scene/test_packed_scene.h"
+#include "tests/scene/test_parallax_2d.h"
#include "tests/scene/test_path_2d.h"
#include "tests/scene/test_path_follow_2d.h"
#include "tests/scene/test_sprite_frames.h"
@@ -133,6 +137,7 @@
#include "tests/scene/test_code_edit.h"
#include "tests/scene/test_color_picker.h"
#include "tests/scene/test_graph_node.h"
+#include "tests/scene/test_option_button.h"
#include "tests/scene/test_text_edit.h"
#include "tests/scene/test_tree.h"
#endif // ADVANCED_GUI_DISABLED
@@ -151,6 +156,7 @@
#include "tests/scene/test_arraymesh.h"
#include "tests/scene/test_camera_3d.h"
+#include "tests/scene/test_height_map_shape_3d.h"
#include "tests/scene/test_path_3d.h"
#include "tests/scene/test_path_follow_3d.h"
#include "tests/scene/test_primitives.h"
@@ -316,7 +322,7 @@ struct GodotTestCaseListener : public doctest::IReporter {
return;
}
- if (name.contains("Audio")) {
+ if (name.contains("[Audio]")) {
// The last driver index should always be the dummy driver.
int dummy_idx = AudioDriverManager::get_driver_count() - 1;
AudioDriverManager::initialize(dummy_idx);
diff --git a/thirdparty/README.md b/thirdparty/README.md
index 799eb97cd2..dbe20ba2a5 100644
--- a/thirdparty/README.md
+++ b/thirdparty/README.md
@@ -543,7 +543,7 @@ in the MSVC debugger.
## mbedtls
- Upstream: https://github.com/Mbed-TLS/mbedtls
-- Version: 3.6.0 (2ca6c285a0dd3f33982dd57299012dacab1ff206, 2024)
+- Version: 3.6.1 (71c569d44bf3a8bd53d874c81ee8ac644dd6e9e3, 2024)
- License: Apache 2.0
File extracted from upstream release tarball:
@@ -553,8 +553,6 @@ File extracted from upstream release tarball:
- All `.c` and `.h` from `library/` to `thirdparty/mbedtls/library/` except
for the `psa_*.c` source files
- The `LICENSE` file (edited to keep only the Apache 2.0 variant)
-- Applied the patch `no-flexible-arrays.diff` to fix Windows build (see
- upstream GH-9020)
- Applied the patch `msvc-redeclaration-bug.diff` to fix a compilation error
with some MSVC versions
- Added 2 files `godot_core_mbedtls_platform.c` and `godot_core_mbedtls_config.h`
@@ -692,8 +690,8 @@ Collection of single-file libraries used in Godot components.
* License: MIT
- `qoa.h`
* Upstream: https://github.com/phoboslab/qoa
- * Version: git (5c2a86d615661f34636cf179abf4fa278d3257e0, 2024)
- * Modifications: Inlined functions, patched uninitialized variables and untyped mallocs.
+ * Version: git (e0c69447d4d3945c3c92ac1751e4cdc9803a8303, 2024)
+ * Modifications: Added a few modifiers to comply with C++ nature.
* License: MIT
- `r128.{c,h}`
* Upstream: https://github.com/fahickman/r128
@@ -909,7 +907,7 @@ instead of `miniz.h` as an external dependency.
## thorvg
- Upstream: https://github.com/thorvg/thorvg
-- Version: 0.14.7 (e3a6bf5229a9671c385ee78bc33e6e6b611a9729, 2024)
+- Version: 0.14.9 (81a0fbfd590873b21e53c3af77969c71d3d9b586, 2024)
- License: MIT
Files extracted from upstream source:
diff --git a/thirdparty/libwebp/patches/godot-clang-cl-fix.patch b/thirdparty/libwebp/patches/godot-clang-cl-fix.patch
new file mode 100644
index 0000000000..ee4f598951
--- /dev/null
+++ b/thirdparty/libwebp/patches/godot-clang-cl-fix.patch
@@ -0,0 +1,19 @@
+diff --git a/thirdparty/libwebp/src/dsp/cpu.h b/thirdparty/libwebp/src/dsp/cpu.h
+index c86540f280..4dbe607aec 100644
+--- a/thirdparty/libwebp/src/dsp/cpu.h
++++ b/thirdparty/libwebp/src/dsp/cpu.h
+@@ -47,12 +47,12 @@
+ // x86 defines.
+
+ #if !defined(HAVE_CONFIG_H)
+-#if defined(_MSC_VER) && _MSC_VER > 1310 && \
++#if defined(_MSC_VER) && !defined(__clang__) && _MSC_VER > 1310 && \
+ (defined(_M_X64) || defined(_M_IX86))
+ #define WEBP_MSC_SSE2 // Visual C++ SSE2 targets
+ #endif
+
+-#if defined(_MSC_VER) && _MSC_VER >= 1500 && \
++#if defined(_MSC_VER) && !defined(__clang__) && _MSC_VER >= 1500 && \
+ (defined(_M_X64) || defined(_M_IX86))
+ #define WEBP_MSC_SSE41 // Visual C++ SSE4.1 targets
+ #endif
diff --git a/thirdparty/libwebp/src/dsp/cpu.h b/thirdparty/libwebp/src/dsp/cpu.h
index c86540f280..4dbe607aec 100644
--- a/thirdparty/libwebp/src/dsp/cpu.h
+++ b/thirdparty/libwebp/src/dsp/cpu.h
@@ -47,12 +47,12 @@
// x86 defines.
#if !defined(HAVE_CONFIG_H)
-#if defined(_MSC_VER) && _MSC_VER > 1310 && \
+#if defined(_MSC_VER) && !defined(__clang__) && _MSC_VER > 1310 && \
(defined(_M_X64) || defined(_M_IX86))
#define WEBP_MSC_SSE2 // Visual C++ SSE2 targets
#endif
-#if defined(_MSC_VER) && _MSC_VER >= 1500 && \
+#if defined(_MSC_VER) && !defined(__clang__) && _MSC_VER >= 1500 && \
(defined(_M_X64) || defined(_M_IX86))
#define WEBP_MSC_SSE41 // Visual C++ SSE4.1 targets
#endif
diff --git a/thirdparty/mbedtls/include/mbedtls/bignum.h b/thirdparty/mbedtls/include/mbedtls/bignum.h
index 71d7b97672..8367cd34e6 100644
--- a/thirdparty/mbedtls/include/mbedtls/bignum.h
+++ b/thirdparty/mbedtls/include/mbedtls/bignum.h
@@ -880,7 +880,7 @@ int mbedtls_mpi_mod_int(mbedtls_mpi_uint *r, const mbedtls_mpi *A,
mbedtls_mpi_sint b);
/**
- * \brief Perform a sliding-window exponentiation: X = A^E mod N
+ * \brief Perform a modular exponentiation: X = A^E mod N
*
* \param X The destination MPI. This must point to an initialized MPI.
* This must not alias E or N.
diff --git a/thirdparty/mbedtls/include/mbedtls/build_info.h b/thirdparty/mbedtls/include/mbedtls/build_info.h
index eab167f383..8242ec6828 100644
--- a/thirdparty/mbedtls/include/mbedtls/build_info.h
+++ b/thirdparty/mbedtls/include/mbedtls/build_info.h
@@ -26,16 +26,16 @@
*/
#define MBEDTLS_VERSION_MAJOR 3
#define MBEDTLS_VERSION_MINOR 6
-#define MBEDTLS_VERSION_PATCH 0
+#define MBEDTLS_VERSION_PATCH 1
/**
* The single version number has the following structure:
* MMNNPP00
* Major version | Minor version | Patch version
*/
-#define MBEDTLS_VERSION_NUMBER 0x03060000
-#define MBEDTLS_VERSION_STRING "3.6.0"
-#define MBEDTLS_VERSION_STRING_FULL "Mbed TLS 3.6.0"
+#define MBEDTLS_VERSION_NUMBER 0x03060100
+#define MBEDTLS_VERSION_STRING "3.6.1"
+#define MBEDTLS_VERSION_STRING_FULL "Mbed TLS 3.6.1"
/* Macros for build-time platform detection */
@@ -101,6 +101,13 @@
#define inline __inline
#endif
+#if defined(MBEDTLS_CONFIG_FILES_READ)
+#error "Something went wrong: MBEDTLS_CONFIG_FILES_READ defined before reading the config files!"
+#endif
+#if defined(MBEDTLS_CONFIG_IS_FINALIZED)
+#error "Something went wrong: MBEDTLS_CONFIG_IS_FINALIZED defined before reading the config files!"
+#endif
+
/* X.509, TLS and non-PSA crypto configuration */
#if !defined(MBEDTLS_CONFIG_FILE)
#include "mbedtls/mbedtls_config.h"
@@ -135,6 +142,12 @@
#endif
#endif /* defined(MBEDTLS_PSA_CRYPTO_CONFIG) */
+/* Indicate that all configuration files have been read.
+ * It is now time to adjust the configuration (follow through on dependencies,
+ * make PSA and legacy crypto consistent, etc.).
+ */
+#define MBEDTLS_CONFIG_FILES_READ
+
/* Auto-enable MBEDTLS_CTR_DRBG_USE_128_BIT_KEY if
* MBEDTLS_AES_ONLY_128_BIT_KEY_LENGTH and MBEDTLS_CTR_DRBG_C defined
* to ensure a 128-bit key size in CTR_DRBG.
@@ -169,8 +182,13 @@
#include "mbedtls/config_adjust_ssl.h"
-/* Make sure all configuration symbols are set before including check_config.h,
- * even the ones that are calculated programmatically. */
+/* Indicate that all configuration symbols are set,
+ * even the ones that are calculated programmatically.
+ * It is now safe to query the configuration (to check it, to size buffers,
+ * etc.).
+ */
+#define MBEDTLS_CONFIG_IS_FINALIZED
+
#include "mbedtls/check_config.h"
#endif /* MBEDTLS_BUILD_INFO_H */
diff --git a/thirdparty/mbedtls/include/mbedtls/check_config.h b/thirdparty/mbedtls/include/mbedtls/check_config.h
index b3c038dd2e..67a05f83b8 100644
--- a/thirdparty/mbedtls/include/mbedtls/check_config.h
+++ b/thirdparty/mbedtls/include/mbedtls/check_config.h
@@ -2,6 +2,13 @@
* \file check_config.h
*
* \brief Consistency checks for configuration options
+ *
+ * This is an internal header. Do not include it directly.
+ *
+ * This header is included automatically by all public Mbed TLS headers
+ * (via mbedtls/build_info.h). Do not include it directly in a configuration
+ * file such as mbedtls/mbedtls_config.h or #MBEDTLS_USER_CONFIG_FILE!
+ * It would run at the wrong time due to missing derived symbols.
*/
/*
* Copyright The Mbed TLS Contributors
@@ -12,6 +19,13 @@
#define MBEDTLS_CHECK_CONFIG_H
/* *INDENT-OFF* */
+
+#if !defined(MBEDTLS_CONFIG_IS_FINALIZED)
+#warning "Do not include mbedtls/check_config.h manually! " \
+ "This may cause spurious errors. " \
+ "It is included automatically at the right point since Mbed TLS 3.0."
+#endif /* !MBEDTLS_CONFIG_IS_FINALIZED */
+
/*
* We assume CHAR_BIT is 8 in many places. In practice, this is true on our
* target platforms, so not an issue, but let's just be extra sure.
diff --git a/thirdparty/mbedtls/include/mbedtls/config_adjust_legacy_crypto.h b/thirdparty/mbedtls/include/mbedtls/config_adjust_legacy_crypto.h
index 9b06041228..3ba987ebb2 100644
--- a/thirdparty/mbedtls/include/mbedtls/config_adjust_legacy_crypto.h
+++ b/thirdparty/mbedtls/include/mbedtls/config_adjust_legacy_crypto.h
@@ -2,7 +2,9 @@
* \file mbedtls/config_adjust_legacy_crypto.h
* \brief Adjust legacy configuration configuration
*
- * Automatically enable certain dependencies. Generally, MBEDLTS_xxx
+ * This is an internal header. Do not include it directly.
+ *
+ * Automatically enable certain dependencies. Generally, MBEDTLS_xxx
* configurations need to be explicitly enabled by the user: enabling
* MBEDTLS_xxx_A but not MBEDTLS_xxx_B when A requires B results in a
* compilation error. However, we do automatically enable certain options
@@ -22,6 +24,14 @@
#ifndef MBEDTLS_CONFIG_ADJUST_LEGACY_CRYPTO_H
#define MBEDTLS_CONFIG_ADJUST_LEGACY_CRYPTO_H
+#if !defined(MBEDTLS_CONFIG_FILES_READ)
+#error "Do not include mbedtls/config_adjust_*.h manually! This can lead to problems, " \
+ "up to and including runtime errors such as buffer overflows. " \
+ "If you're trying to fix a complaint from check_config.h, just remove " \
+ "it from your configuration file: since Mbed TLS 3.0, it is included " \
+ "automatically at the right point."
+#endif /* */
+
/* Ideally, we'd set those as defaults in mbedtls_config.h, but
* putting an #ifdef _WIN32 in mbedtls_config.h would confuse config.py.
*
@@ -48,7 +58,8 @@
defined(MBEDTLS_PSA_BUILTIN_ALG_ECB_NO_PADDING) || \
defined(MBEDTLS_PSA_BUILTIN_ALG_CBC_NO_PADDING) || \
defined(MBEDTLS_PSA_BUILTIN_ALG_CBC_PKCS7) || \
- defined(MBEDTLS_PSA_BUILTIN_ALG_CCM_STAR_NO_TAG))
+ defined(MBEDTLS_PSA_BUILTIN_ALG_CCM_STAR_NO_TAG) || \
+ defined(MBEDTLS_PSA_BUILTIN_ALG_CMAC))
#define MBEDTLS_CIPHER_C
#endif
@@ -293,6 +304,14 @@
#define MBEDTLS_ECP_LIGHT
#endif
+/* Backward compatibility: after #8740 the RSA module offers functions to parse
+ * and write RSA private/public keys without relying on the PK one. Of course
+ * this needs ASN1 support to do so, so we enable it here. */
+#if defined(MBEDTLS_RSA_C)
+#define MBEDTLS_ASN1_PARSE_C
+#define MBEDTLS_ASN1_WRITE_C
+#endif
+
/* MBEDTLS_PK_PARSE_EC_COMPRESSED is introduced in Mbed TLS version 3.5, while
* in previous version compressed points were automatically supported as long
* as PK_PARSE_C and ECP_C were enabled. As a consequence, for backward
@@ -409,12 +428,12 @@
/* psa_util file features some ECDSA conversion functions, to convert between
* legacy's ASN.1 DER format and PSA's raw one. */
-#if defined(MBEDTLS_ECDSA_C) || (defined(MBEDTLS_PSA_CRYPTO_C) && \
+#if (defined(MBEDTLS_PSA_CRYPTO_CLIENT) && \
(defined(PSA_WANT_ALG_ECDSA) || defined(PSA_WANT_ALG_DETERMINISTIC_ECDSA)))
#define MBEDTLS_PSA_UTIL_HAVE_ECDSA
#endif
-/* Some internal helpers to determine which keys are availble. */
+/* Some internal helpers to determine which keys are available. */
#if (!defined(MBEDTLS_USE_PSA_CRYPTO) && defined(MBEDTLS_AES_C)) || \
(defined(MBEDTLS_USE_PSA_CRYPTO) && defined(PSA_WANT_KEY_TYPE_AES))
#define MBEDTLS_SSL_HAVE_AES
@@ -428,7 +447,7 @@
#define MBEDTLS_SSL_HAVE_CAMELLIA
#endif
-/* Some internal helpers to determine which operation modes are availble. */
+/* Some internal helpers to determine which operation modes are available. */
#if (!defined(MBEDTLS_USE_PSA_CRYPTO) && defined(MBEDTLS_CIPHER_MODE_CBC)) || \
(defined(MBEDTLS_USE_PSA_CRYPTO) && defined(PSA_WANT_ALG_CBC_NO_PADDING))
#define MBEDTLS_SSL_HAVE_CBC
diff --git a/thirdparty/mbedtls/include/mbedtls/config_adjust_legacy_from_psa.h b/thirdparty/mbedtls/include/mbedtls/config_adjust_legacy_from_psa.h
index 0091e246b2..04bdae61bb 100644
--- a/thirdparty/mbedtls/include/mbedtls/config_adjust_legacy_from_psa.h
+++ b/thirdparty/mbedtls/include/mbedtls/config_adjust_legacy_from_psa.h
@@ -2,6 +2,8 @@
* \file mbedtls/config_adjust_legacy_from_psa.h
* \brief Adjust PSA configuration: activate legacy implementations
*
+ * This is an internal header. Do not include it directly.
+ *
* When MBEDTLS_PSA_CRYPTO_CONFIG is enabled, activate legacy implementations
* of cryptographic mechanisms as needed to fulfill the needs of the PSA
* configuration. Generally speaking, we activate a legacy mechanism if
@@ -16,6 +18,14 @@
#ifndef MBEDTLS_CONFIG_ADJUST_LEGACY_FROM_PSA_H
#define MBEDTLS_CONFIG_ADJUST_LEGACY_FROM_PSA_H
+#if !defined(MBEDTLS_CONFIG_FILES_READ)
+#error "Do not include mbedtls/config_adjust_*.h manually! This can lead to problems, " \
+ "up to and including runtime errors such as buffer overflows. " \
+ "If you're trying to fix a complaint from check_config.h, just remove " \
+ "it from your configuration file: since Mbed TLS 3.0, it is included " \
+ "automatically at the right point."
+#endif /* */
+
/* Define appropriate ACCEL macros for the p256-m driver.
* In the future, those should be generated from the drivers JSON description.
*/
@@ -498,7 +508,6 @@
* The PSA implementation has its own implementation of HKDF, separate from
* hkdf.c. No need to enable MBEDTLS_HKDF_C here.
*/
-#define MBEDTLS_PSA_BUILTIN_ALG_HMAC 1
#define MBEDTLS_PSA_BUILTIN_ALG_HKDF 1
#endif /* !MBEDTLS_PSA_ACCEL_ALG_HKDF */
#endif /* PSA_WANT_ALG_HKDF */
@@ -509,7 +518,6 @@
* The PSA implementation has its own implementation of HKDF, separate from
* hkdf.c. No need to enable MBEDTLS_HKDF_C here.
*/
-#define MBEDTLS_PSA_BUILTIN_ALG_HMAC 1
#define MBEDTLS_PSA_BUILTIN_ALG_HKDF_EXTRACT 1
#endif /* !MBEDTLS_PSA_ACCEL_ALG_HKDF_EXTRACT */
#endif /* PSA_WANT_ALG_HKDF_EXTRACT */
@@ -520,7 +528,6 @@
* The PSA implementation has its own implementation of HKDF, separate from
* hkdf.c. No need to enable MBEDTLS_HKDF_C here.
*/
-#define MBEDTLS_PSA_BUILTIN_ALG_HMAC 1
#define MBEDTLS_PSA_BUILTIN_ALG_HKDF_EXPAND 1
#endif /* !MBEDTLS_PSA_ACCEL_ALG_HKDF_EXPAND */
#endif /* PSA_WANT_ALG_HKDF_EXPAND */
@@ -630,9 +637,6 @@
#if !defined(MBEDTLS_PSA_ACCEL_ALG_PBKDF2_HMAC)
#define MBEDTLS_PSA_BUILTIN_ALG_PBKDF2_HMAC 1
#define PSA_HAVE_SOFT_PBKDF2_HMAC 1
-#if !defined(MBEDTLS_PSA_ACCEL_ALG_HMAC)
-#define MBEDTLS_PSA_BUILTIN_ALG_HMAC 1
-#endif /* !MBEDTLS_PSA_ACCEL_ALG_HMAC */
#endif /* !MBEDTLS_PSA_BUILTIN_ALG_PBKDF2_HMAC */
#endif /* PSA_WANT_ALG_PBKDF2_HMAC */
diff --git a/thirdparty/mbedtls/include/mbedtls/config_adjust_psa_from_legacy.h b/thirdparty/mbedtls/include/mbedtls/config_adjust_psa_from_legacy.h
index 3456615943..14ca14696f 100644
--- a/thirdparty/mbedtls/include/mbedtls/config_adjust_psa_from_legacy.h
+++ b/thirdparty/mbedtls/include/mbedtls/config_adjust_psa_from_legacy.h
@@ -2,6 +2,8 @@
* \file mbedtls/config_adjust_psa_from_legacy.h
* \brief Adjust PSA configuration: construct PSA configuration from legacy
*
+ * This is an internal header. Do not include it directly.
+ *
* When MBEDTLS_PSA_CRYPTO_CONFIG is disabled, we automatically enable
* cryptographic mechanisms through the PSA interface when the corresponding
* legacy mechanism is enabled. In many cases, this just enables the PSA
@@ -18,6 +20,14 @@
#ifndef MBEDTLS_CONFIG_ADJUST_PSA_FROM_LEGACY_H
#define MBEDTLS_CONFIG_ADJUST_PSA_FROM_LEGACY_H
+#if !defined(MBEDTLS_CONFIG_FILES_READ)
+#error "Do not include mbedtls/config_adjust_*.h manually! This can lead to problems, " \
+ "up to and including runtime errors such as buffer overflows. " \
+ "If you're trying to fix a complaint from check_config.h, just remove " \
+ "it from your configuration file: since Mbed TLS 3.0, it is included " \
+ "automatically at the right point."
+#endif /* */
+
/*
* Ensure PSA_WANT_* defines are setup properly if MBEDTLS_PSA_CRYPTO_CONFIG
* is not defined
diff --git a/thirdparty/mbedtls/include/mbedtls/config_adjust_psa_superset_legacy.h b/thirdparty/mbedtls/include/mbedtls/config_adjust_psa_superset_legacy.h
index 3a55c3f6e1..ef65cce0d9 100644
--- a/thirdparty/mbedtls/include/mbedtls/config_adjust_psa_superset_legacy.h
+++ b/thirdparty/mbedtls/include/mbedtls/config_adjust_psa_superset_legacy.h
@@ -2,6 +2,8 @@
* \file mbedtls/config_adjust_psa_superset_legacy.h
* \brief Adjust PSA configuration: automatic enablement from legacy
*
+ * This is an internal header. Do not include it directly.
+ *
* To simplify some edge cases, we automatically enable certain cryptographic
* mechanisms in the PSA API if they are enabled in the legacy API. The general
* idea is that if legacy module M uses mechanism A internally, and A has
@@ -17,6 +19,14 @@
#ifndef MBEDTLS_CONFIG_ADJUST_PSA_SUPERSET_LEGACY_H
#define MBEDTLS_CONFIG_ADJUST_PSA_SUPERSET_LEGACY_H
+#if !defined(MBEDTLS_CONFIG_FILES_READ)
+#error "Do not include mbedtls/config_adjust_*.h manually! This can lead to problems, " \
+ "up to and including runtime errors such as buffer overflows. " \
+ "If you're trying to fix a complaint from check_config.h, just remove " \
+ "it from your configuration file: since Mbed TLS 3.0, it is included " \
+ "automatically at the right point."
+#endif /* */
+
/****************************************************************/
/* Hashes that are built in are also enabled in PSA.
* This simplifies dependency declarations especially
diff --git a/thirdparty/mbedtls/include/mbedtls/config_adjust_ssl.h b/thirdparty/mbedtls/include/mbedtls/config_adjust_ssl.h
index 39c7b3b117..1f82d9c006 100644
--- a/thirdparty/mbedtls/include/mbedtls/config_adjust_ssl.h
+++ b/thirdparty/mbedtls/include/mbedtls/config_adjust_ssl.h
@@ -2,7 +2,9 @@
* \file mbedtls/config_adjust_ssl.h
* \brief Adjust TLS configuration
*
- * Automatically enable certain dependencies. Generally, MBEDLTS_xxx
+ * This is an internal header. Do not include it directly.
+ *
+ * Automatically enable certain dependencies. Generally, MBEDTLS_xxx
* configurations need to be explicitly enabled by the user: enabling
* MBEDTLS_xxx_A but not MBEDTLS_xxx_B when A requires B results in a
* compilation error. However, we do automatically enable certain options
@@ -22,6 +24,14 @@
#ifndef MBEDTLS_CONFIG_ADJUST_SSL_H
#define MBEDTLS_CONFIG_ADJUST_SSL_H
+#if !defined(MBEDTLS_CONFIG_FILES_READ)
+#error "Do not include mbedtls/config_adjust_*.h manually! This can lead to problems, " \
+ "up to and including runtime errors such as buffer overflows. " \
+ "If you're trying to fix a complaint from check_config.h, just remove " \
+ "it from your configuration file: since Mbed TLS 3.0, it is included " \
+ "automatically at the right point."
+#endif /* */
+
/* The following blocks make it easier to disable all of TLS,
* or of TLS 1.2 or 1.3 or DTLS, without having to manually disable all
* key exchanges, options and extensions related to them. */
diff --git a/thirdparty/mbedtls/include/mbedtls/config_adjust_x509.h b/thirdparty/mbedtls/include/mbedtls/config_adjust_x509.h
index 346c8ae6d5..cfb2d88916 100644
--- a/thirdparty/mbedtls/include/mbedtls/config_adjust_x509.h
+++ b/thirdparty/mbedtls/include/mbedtls/config_adjust_x509.h
@@ -2,7 +2,9 @@
* \file mbedtls/config_adjust_x509.h
* \brief Adjust X.509 configuration
*
- * Automatically enable certain dependencies. Generally, MBEDLTS_xxx
+ * This is an internal header. Do not include it directly.
+ *
+ * Automatically enable certain dependencies. Generally, MBEDTLS_xxx
* configurations need to be explicitly enabled by the user: enabling
* MBEDTLS_xxx_A but not MBEDTLS_xxx_B when A requires B results in a
* compilation error. However, we do automatically enable certain options
@@ -22,4 +24,12 @@
#ifndef MBEDTLS_CONFIG_ADJUST_X509_H
#define MBEDTLS_CONFIG_ADJUST_X509_H
+#if !defined(MBEDTLS_CONFIG_FILES_READ)
+#error "Do not include mbedtls/config_adjust_*.h manually! This can lead to problems, " \
+ "up to and including runtime errors such as buffer overflows. " \
+ "If you're trying to fix a complaint from check_config.h, just remove " \
+ "it from your configuration file: since Mbed TLS 3.0, it is included " \
+ "automatically at the right point."
+#endif /* */
+
#endif /* MBEDTLS_CONFIG_ADJUST_X509_H */
diff --git a/thirdparty/mbedtls/include/mbedtls/config_psa.h b/thirdparty/mbedtls/include/mbedtls/config_psa.h
index 17da61b3e8..5f3d0f3d5d 100644
--- a/thirdparty/mbedtls/include/mbedtls/config_psa.h
+++ b/thirdparty/mbedtls/include/mbedtls/config_psa.h
@@ -22,6 +22,8 @@
#include "psa/crypto_adjust_config_synonyms.h"
+#include "psa/crypto_adjust_config_dependencies.h"
+
#include "mbedtls/config_adjust_psa_superset_legacy.h"
#if defined(MBEDTLS_PSA_CRYPTO_CONFIG)
@@ -32,7 +34,11 @@
* before we deduce what built-ins are required. */
#include "psa/crypto_adjust_config_key_pair_types.h"
+#if defined(MBEDTLS_PSA_CRYPTO_C)
+/* If we are implementing PSA crypto ourselves, then we want to enable the
+ * required built-ins. Otherwise, PSA features will be provided by the server. */
#include "mbedtls/config_adjust_legacy_from_psa.h"
+#endif
#else /* MBEDTLS_PSA_CRYPTO_CONFIG */
diff --git a/thirdparty/mbedtls/include/mbedtls/ctr_drbg.h b/thirdparty/mbedtls/include/mbedtls/ctr_drbg.h
index c00756df1b..0b7cce1923 100644
--- a/thirdparty/mbedtls/include/mbedtls/ctr_drbg.h
+++ b/thirdparty/mbedtls/include/mbedtls/ctr_drbg.h
@@ -32,12 +32,27 @@
#include "mbedtls/build_info.h"
-/* In case AES_C is defined then it is the primary option for backward
- * compatibility purposes. If that's not available, PSA is used instead */
-#if defined(MBEDTLS_AES_C)
-#include "mbedtls/aes.h"
-#else
+/* The CTR_DRBG implementation can either directly call the low-level AES
+ * module (gated by MBEDTLS_AES_C) or call the PSA API to perform AES
+ * operations. Calling the AES module directly is the default, both for
+ * maximum backward compatibility and because it's a bit more efficient
+ * (less glue code).
+ *
+ * When MBEDTLS_AES_C is disabled, the CTR_DRBG module calls PSA crypto and
+ * thus benefits from the PSA AES accelerator driver.
+ * It is technically possible to enable MBEDTLS_CTR_DRBG_USE_PSA_CRYPTO
+ * to use PSA even when MBEDTLS_AES_C is enabled, but there is very little
+ * reason to do so other than testing purposes and this is not officially
+ * supported.
+ */
+#if !defined(MBEDTLS_AES_C)
+#define MBEDTLS_CTR_DRBG_USE_PSA_CRYPTO
+#endif
+
+#if defined(MBEDTLS_CTR_DRBG_USE_PSA_CRYPTO)
#include "psa/crypto.h"
+#else
+#include "mbedtls/aes.h"
#endif
#include "entropy.h"
@@ -157,7 +172,7 @@ extern "C" {
#define MBEDTLS_CTR_DRBG_ENTROPY_NONCE_LEN (MBEDTLS_CTR_DRBG_ENTROPY_LEN + 1) / 2
#endif
-#if !defined(MBEDTLS_AES_C)
+#if defined(MBEDTLS_CTR_DRBG_USE_PSA_CRYPTO)
typedef struct mbedtls_ctr_drbg_psa_context {
mbedtls_svc_key_id_t key_id;
psa_cipher_operation_t operation;
@@ -189,10 +204,10 @@ typedef struct mbedtls_ctr_drbg_context {
* This is the maximum number of requests
* that can be made between reseedings. */
-#if defined(MBEDTLS_AES_C)
- mbedtls_aes_context MBEDTLS_PRIVATE(aes_ctx); /*!< The AES context. */
-#else
+#if defined(MBEDTLS_CTR_DRBG_USE_PSA_CRYPTO)
mbedtls_ctr_drbg_psa_context MBEDTLS_PRIVATE(psa_ctx); /*!< The PSA context. */
+#else
+ mbedtls_aes_context MBEDTLS_PRIVATE(aes_ctx); /*!< The AES context. */
#endif
/*
diff --git a/thirdparty/mbedtls/include/mbedtls/ecdh.h b/thirdparty/mbedtls/include/mbedtls/ecdh.h
index a0909d6b44..a6a5069337 100644
--- a/thirdparty/mbedtls/include/mbedtls/ecdh.h
+++ b/thirdparty/mbedtls/include/mbedtls/ecdh.h
@@ -325,7 +325,7 @@ int mbedtls_ecdh_read_params(mbedtls_ecdh_context *ctx,
* \brief This function sets up an ECDH context from an EC key.
*
* It is used by clients and servers in place of the
- * ServerKeyEchange for static ECDH, and imports ECDH
+ * ServerKeyExchange for static ECDH, and imports ECDH
* parameters from the EC key information of a certificate.
*
* \see ecp.h
diff --git a/thirdparty/mbedtls/include/mbedtls/ecp.h b/thirdparty/mbedtls/include/mbedtls/ecp.h
index d8f73ae965..623910bcbd 100644
--- a/thirdparty/mbedtls/include/mbedtls/ecp.h
+++ b/thirdparty/mbedtls/include/mbedtls/ecp.h
@@ -216,7 +216,7 @@ mbedtls_ecp_point;
* range of <code>0..2^(2*pbits)-1</code>, and transforms it in-place to an integer
* which is congruent mod \p P to the given MPI, and is close enough to \p pbits
* in size, so that it may be efficiently brought in the 0..P-1 range by a few
- * additions or subtractions. Therefore, it is only an approximative modular
+ * additions or subtractions. Therefore, it is only an approximate modular
* reduction. It must return 0 on success and non-zero on failure.
*
* \note Alternative implementations of the ECP module must obey the
diff --git a/thirdparty/mbedtls/include/mbedtls/mbedtls_config.h b/thirdparty/mbedtls/include/mbedtls/mbedtls_config.h
index 35921412c6..bd3f71d5bc 100644
--- a/thirdparty/mbedtls/include/mbedtls/mbedtls_config.h
+++ b/thirdparty/mbedtls/include/mbedtls/mbedtls_config.h
@@ -1118,7 +1118,7 @@
* MBEDTLS_ECP_DP_SECP256R1_ENABLED
*
* \warning If SHA-256 is provided only by a PSA driver, you must call
- * psa_crypto_init() before the first hanshake (even if
+ * psa_crypto_init() before the first handshake (even if
* MBEDTLS_USE_PSA_CRYPTO is disabled).
*
* This enables the following ciphersuites (if other requisites are
@@ -1415,6 +1415,23 @@
//#define MBEDTLS_PSA_CRYPTO_SPM
/**
+ * \def MBEDTLS_PSA_KEY_STORE_DYNAMIC
+ *
+ * Dynamically resize the PSA key store to accommodate any number of
+ * volatile keys (until the heap memory is exhausted).
+ *
+ * If this option is disabled, the key store has a fixed size
+ * #MBEDTLS_PSA_KEY_SLOT_COUNT for volatile keys and loaded persistent keys
+ * together.
+ *
+ * This option has no effect when #MBEDTLS_PSA_CRYPTO_C is disabled.
+ *
+ * Module: library/psa_crypto.c
+ * Requires: MBEDTLS_PSA_CRYPTO_C
+ */
+#define MBEDTLS_PSA_KEY_STORE_DYNAMIC
+
+/**
* Uncomment to enable p256-m. This is an alternative implementation of
* key generation, ECDH and (randomized) ECDSA on the curve SECP256R1.
* Compared to the default implementation:
@@ -1781,8 +1798,9 @@
* Requires: MBEDTLS_PSA_CRYPTO_C
*
* \note TLS 1.3 uses PSA crypto for cryptographic operations that are
- * directly performed by TLS 1.3 code. As a consequence, you must
- * call psa_crypto_init() before the first TLS 1.3 handshake.
+ * directly performed by TLS 1.3 code. As a consequence, when TLS 1.3
+ * is enabled, a TLS handshake may call psa_crypto_init(), even
+ * if it ends up negotiating a different TLS version.
*
* \note Cryptographic operations performed indirectly via another module
* (X.509, PK) or by code shared with TLS 1.2 (record protection,
@@ -2625,7 +2643,7 @@
* The CTR_DRBG generator uses AES-256 by default.
* To use AES-128 instead, enable \c MBEDTLS_CTR_DRBG_USE_128_BIT_KEY above.
*
- * AES support can either be achived through builtin (MBEDTLS_AES_C) or PSA.
+ * AES support can either be achieved through builtin (MBEDTLS_AES_C) or PSA.
* Builtin is the default option when MBEDTLS_AES_C is defined otherwise PSA
* is used.
*
@@ -4016,22 +4034,38 @@
* Use HMAC_DRBG with the specified hash algorithm for HMAC_DRBG for the
* PSA crypto subsystem.
*
- * If this option is unset:
- * - If CTR_DRBG is available, the PSA subsystem uses it rather than HMAC_DRBG.
- * - Otherwise, the PSA subsystem uses HMAC_DRBG with either
- * #MBEDTLS_MD_SHA512 or #MBEDTLS_MD_SHA256 based on availability and
- * on unspecified heuristics.
+ * If this option is unset, the library chooses a hash (currently between
+ * #MBEDTLS_MD_SHA512 and #MBEDTLS_MD_SHA256) based on availability and
+ * unspecified heuristics.
+ *
+ * \note The PSA crypto subsystem uses the first available mechanism amongst
+ * the following:
+ * - #MBEDTLS_PSA_CRYPTO_EXTERNAL_RNG if enabled;
+ * - Entropy from #MBEDTLS_ENTROPY_C plus CTR_DRBG with AES
+ * if #MBEDTLS_CTR_DRBG_C is enabled;
+ * - Entropy from #MBEDTLS_ENTROPY_C plus HMAC_DRBG.
+ *
+ * A future version may reevaluate the prioritization of DRBG mechanisms.
*/
//#define MBEDTLS_PSA_HMAC_DRBG_MD_TYPE MBEDTLS_MD_SHA256
/** \def MBEDTLS_PSA_KEY_SLOT_COUNT
- * Restrict the PSA library to supporting a maximum amount of simultaneously
- * loaded keys. A loaded key is a key stored by the PSA Crypto core as a
- * volatile key, or a persistent key which is loaded temporarily by the
- * library as part of a crypto operation in flight.
*
- * If this option is unset, the library will fall back to a default value of
- * 32 keys.
+ * When #MBEDTLS_PSA_KEY_STORE_DYNAMIC is disabled,
+ * the maximum amount of PSA keys simultaneously in memory. This counts all
+ * volatile keys, plus loaded persistent keys.
+ *
+ * When #MBEDTLS_PSA_KEY_STORE_DYNAMIC is enabled,
+ * the maximum number of loaded persistent keys.
+ *
+ * Currently, persistent keys do not need to be loaded all the time while
+ * a multipart operation is in progress, only while the operation is being
+ * set up. This may change in future versions of the library.
+ *
+ * Currently, the library traverses of the whole table on each access to a
+ * persistent key. Therefore large values may cause poor performance.
+ *
+ * This option has no effect when #MBEDTLS_PSA_CRYPTO_C is disabled.
*/
//#define MBEDTLS_PSA_KEY_SLOT_COUNT 32
diff --git a/thirdparty/mbedtls/include/mbedtls/pk.h b/thirdparty/mbedtls/include/mbedtls/pk.h
index fde302f872..52f4cc6c9e 100644
--- a/thirdparty/mbedtls/include/mbedtls/pk.h
+++ b/thirdparty/mbedtls/include/mbedtls/pk.h
@@ -359,32 +359,40 @@ int mbedtls_pk_setup(mbedtls_pk_context *ctx, const mbedtls_pk_info_t *info);
#if defined(MBEDTLS_USE_PSA_CRYPTO)
/**
- * \brief Initialize a PK context to wrap a PSA key.
- *
- * \note This function replaces mbedtls_pk_setup() for contexts
- * that wrap a (possibly opaque) PSA key instead of
- * storing and manipulating the key material directly.
- *
- * \param ctx The context to initialize. It must be empty (type NONE).
- * \param key The PSA key to wrap, which must hold an ECC or RSA key
- * pair (see notes below).
- *
- * \note The wrapped key must remain valid as long as the
- * wrapping PK context is in use, that is at least between
- * the point this function is called and the point
- * mbedtls_pk_free() is called on this context. The wrapped
- * key might then be independently used or destroyed.
- *
- * \note This function is currently only available for ECC or RSA
- * key pairs (that is, keys containing private key material).
- * Support for other key types may be added later.
- *
- * \return \c 0 on success.
- * \return #MBEDTLS_ERR_PK_BAD_INPUT_DATA on invalid input
- * (context already used, invalid key identifier).
- * \return #MBEDTLS_ERR_PK_FEATURE_UNAVAILABLE if the key is not an
- * ECC key pair.
- * \return #MBEDTLS_ERR_PK_ALLOC_FAILED on allocation failure.
+ * \brief Initialize a PK context to wrap a PSA key.
+ *
+ * This function creates a PK context which wraps a PSA key. The PSA wrapped
+ * key must be an EC or RSA key pair (DH is not suported in the PK module).
+ *
+ * Under the hood PSA functions will be used to perform the required
+ * operations and, based on the key type, used algorithms will be:
+ * * EC:
+ * * verify, verify_ext, sign, sign_ext: ECDSA.
+ * * RSA:
+ * * sign, decrypt: use the primary algorithm in the wrapped PSA key;
+ * * sign_ext: RSA PSS if the pk_type is #MBEDTLS_PK_RSASSA_PSS, otherwise
+ * it falls back to the sign() case;
+ * * verify, verify_ext, encrypt: not supported.
+ *
+ * In order for the above operations to succeed, the policy of the wrapped PSA
+ * key must allow the specified algorithm.
+ *
+ * Opaque PK contexts wrapping an EC keys also support \c mbedtls_pk_check_pair(),
+ * whereas RSA ones do not.
+ *
+ * \warning The PSA wrapped key must remain valid as long as the wrapping PK
+ * context is in use, that is at least between the point this function
+ * is called and the point mbedtls_pk_free() is called on this context.
+ *
+ * \param ctx The context to initialize. It must be empty (type NONE).
+ * \param key The PSA key to wrap, which must hold an ECC or RSA key pair.
+ *
+ * \return \c 0 on success.
+ * \return #MBEDTLS_ERR_PK_BAD_INPUT_DATA on invalid input (context already
+ * used, invalid key identifier).
+ * \return #MBEDTLS_ERR_PK_FEATURE_UNAVAILABLE if the key is not an ECC or
+ * RSA key pair.
+ * \return #MBEDTLS_ERR_PK_ALLOC_FAILED on allocation failure.
*/
int mbedtls_pk_setup_opaque(mbedtls_pk_context *ctx,
const mbedtls_svc_key_id_t key);
diff --git a/thirdparty/mbedtls/include/mbedtls/ssl.h b/thirdparty/mbedtls/include/mbedtls/ssl.h
index 172d4693b2..42fffbf860 100644
--- a/thirdparty/mbedtls/include/mbedtls/ssl.h
+++ b/thirdparty/mbedtls/include/mbedtls/ssl.h
@@ -83,10 +83,7 @@
/** Processing of the Certificate handshake message failed. */
#define MBEDTLS_ERR_SSL_BAD_CERTIFICATE -0x7A00
/* Error space gap */
-/**
- * Received NewSessionTicket Post Handshake Message.
- * This error code is experimental and may be changed or removed without notice.
- */
+/** A TLS 1.3 NewSessionTicket message has been received. */
#define MBEDTLS_ERR_SSL_RECEIVED_NEW_SESSION_TICKET -0x7B00
/** Not possible to read early data */
#define MBEDTLS_ERR_SSL_CANNOT_READ_EARLY_DATA -0x7B80
@@ -324,6 +321,9 @@
#define MBEDTLS_SSL_SESSION_TICKETS_DISABLED 0
#define MBEDTLS_SSL_SESSION_TICKETS_ENABLED 1
+#define MBEDTLS_SSL_TLS1_3_SIGNAL_NEW_SESSION_TICKETS_DISABLED 0
+#define MBEDTLS_SSL_TLS1_3_SIGNAL_NEW_SESSION_TICKETS_ENABLED 1
+
#define MBEDTLS_SSL_PRESET_DEFAULT 0
#define MBEDTLS_SSL_PRESET_SUITEB 2
@@ -1446,6 +1446,12 @@ struct mbedtls_ssl_config {
#endif
#if defined(MBEDTLS_SSL_SESSION_TICKETS) && \
defined(MBEDTLS_SSL_CLI_C)
+ /** Encodes two booleans, one stating whether TLS 1.2 session tickets are
+ * enabled or not, the other one whether the handling of TLS 1.3
+ * NewSessionTicket messages is enabled or not. They are respectively set
+ * by mbedtls_ssl_conf_session_tickets() and
+ * mbedtls_ssl_conf_tls13_enable_signal_new_session_tickets().
+ */
uint8_t MBEDTLS_PRIVATE(session_tickets); /*!< use session tickets? */
#endif
@@ -2364,7 +2370,7 @@ int mbedtls_ssl_set_cid(mbedtls_ssl_context *ssl,
*/
int mbedtls_ssl_get_own_cid(mbedtls_ssl_context *ssl,
int *enabled,
- unsigned char own_cid[MBEDTLS_SSL_CID_OUT_LEN_MAX],
+ unsigned char own_cid[MBEDTLS_SSL_CID_IN_LEN_MAX],
size_t *own_cid_len);
/**
@@ -3216,16 +3222,16 @@ void mbedtls_ssl_conf_session_cache(mbedtls_ssl_config *conf,
* a full handshake.
*
* \note This function can handle a variety of mechanisms for session
- * resumption: For TLS 1.2, both session ID-based resumption and
- * ticket-based resumption will be considered. For TLS 1.3,
- * once implemented, sessions equate to tickets, and loading
- * one or more sessions via this call will lead to their
- * corresponding tickets being advertised as resumption PSKs
- * by the client.
- *
- * \note Calling this function multiple times will only be useful
- * once TLS 1.3 is supported. For TLS 1.2 connections, this
- * function should be called at most once.
+ * resumption: For TLS 1.2, both session ID-based resumption
+ * and ticket-based resumption will be considered. For TLS 1.3,
+ * sessions equate to tickets, and loading one session by
+ * calling this function will lead to its corresponding ticket
+ * being advertised as resumption PSK by the client. This
+ * depends on session tickets being enabled (see
+ * #MBEDTLS_SSL_SESSION_TICKETS configuration option) though.
+ * If session tickets are disabled, a call to this function
+ * with a TLS 1.3 session, will not have any effect on the next
+ * handshake for the SSL context \p ssl.
*
* \param ssl The SSL context representing the connection which should
* be attempted to be setup using session resumption. This
@@ -3240,9 +3246,10 @@ void mbedtls_ssl_conf_session_cache(mbedtls_ssl_config *conf,
*
* \return \c 0 if successful.
* \return \c MBEDTLS_ERR_SSL_FEATURE_UNAVAILABLE if the session
- * could not be loaded because of an implementation limitation.
- * This error is non-fatal, and has no observable effect on
- * the SSL context or the session that was attempted to be loaded.
+ * could not be loaded because one session has already been
+ * loaded. This error is non-fatal, and has no observable
+ * effect on the SSL context or the session that was attempted
+ * to be loaded.
* \return Another negative error code on other kinds of failure.
*
* \sa mbedtls_ssl_get_session()
@@ -3309,8 +3316,16 @@ int mbedtls_ssl_session_load(mbedtls_ssl_session *session,
* to determine the necessary size by calling this function
* with \p buf set to \c NULL and \p buf_len to \c 0.
*
+ * \note For TLS 1.3 sessions, this feature is supported only if the
+ * MBEDTLS_SSL_SESSION_TICKETS configuration option is enabled,
+ * as in TLS 1.3 session resumption is possible only with
+ * tickets.
+ *
* \return \c 0 if successful.
* \return #MBEDTLS_ERR_SSL_BUFFER_TOO_SMALL if \p buf is too small.
+ * \return #MBEDTLS_ERR_SSL_FEATURE_UNAVAILABLE if the
+ * MBEDTLS_SSL_SESSION_TICKETS configuration option is disabled
+ * and the session is a TLS 1.3 session.
*/
int mbedtls_ssl_session_save(const mbedtls_ssl_session *session,
unsigned char *buf,
@@ -4456,21 +4471,50 @@ int mbedtls_ssl_conf_max_frag_len(mbedtls_ssl_config *conf, unsigned char mfl_co
void mbedtls_ssl_conf_preference_order(mbedtls_ssl_config *conf, int order);
#endif /* MBEDTLS_SSL_SRV_C */
-#if defined(MBEDTLS_SSL_SESSION_TICKETS) && \
- defined(MBEDTLS_SSL_CLI_C)
+#if defined(MBEDTLS_SSL_SESSION_TICKETS) && defined(MBEDTLS_SSL_CLI_C)
/**
- * \brief Enable / Disable session tickets (client only).
- * (Default: MBEDTLS_SSL_SESSION_TICKETS_ENABLED.)
+ * \brief Enable / Disable TLS 1.2 session tickets (client only,
+ * TLS 1.2 only). Enabled by default.
*
* \note On server, use \c mbedtls_ssl_conf_session_tickets_cb().
*
* \param conf SSL configuration
- * \param use_tickets Enable or disable (MBEDTLS_SSL_SESSION_TICKETS_ENABLED or
- * MBEDTLS_SSL_SESSION_TICKETS_DISABLED)
+ * \param use_tickets Enable or disable (#MBEDTLS_SSL_SESSION_TICKETS_ENABLED or
+ * #MBEDTLS_SSL_SESSION_TICKETS_DISABLED)
*/
void mbedtls_ssl_conf_session_tickets(mbedtls_ssl_config *conf, int use_tickets);
-#endif /* MBEDTLS_SSL_SESSION_TICKETS &&
- MBEDTLS_SSL_CLI_C */
+
+#if defined(MBEDTLS_SSL_PROTO_TLS1_3)
+/**
+ * \brief Enable / Disable handling of TLS 1.3 NewSessionTicket messages
+ * (client only, TLS 1.3 only).
+ *
+ * The handling of TLS 1.3 NewSessionTicket messages is disabled by
+ * default.
+ *
+ * In TLS 1.3, servers may send a NewSessionTicket message at any time,
+ * and may send multiple NewSessionTicket messages. By default, TLS 1.3
+ * clients ignore NewSessionTicket messages.
+ *
+ * To support session tickets in TLS 1.3 clients, call this function
+ * with #MBEDTLS_SSL_TLS1_3_SIGNAL_NEW_SESSION_TICKETS_ENABLED. When
+ * this is enabled, when a client receives a NewSessionTicket message,
+ * the next call to a message processing functions (notably
+ * mbedtls_ssl_handshake() and mbedtls_ssl_read()) will return
+ * #MBEDTLS_ERR_SSL_RECEIVED_NEW_SESSION_TICKET. The client should then
+ * call mbedtls_ssl_get_session() to retrieve the session ticket before
+ * calling the same message processing function again.
+ *
+ * \param conf SSL configuration
+ * \param signal_new_session_tickets Enable or disable
+ * (#MBEDTLS_SSL_TLS1_3_SIGNAL_NEW_SESSION_TICKETS_ENABLED or
+ * #MBEDTLS_SSL_TLS1_3_SIGNAL_NEW_SESSION_TICKETS_DISABLED)
+ */
+void mbedtls_ssl_conf_tls13_enable_signal_new_session_tickets(
+ mbedtls_ssl_config *conf, int signal_new_session_tickets);
+
+#endif /* MBEDTLS_SSL_PROTO_TLS1_3 */
+#endif /* MBEDTLS_SSL_SESSION_TICKETS && MBEDTLS_SSL_CLI_C */
#if defined(MBEDTLS_SSL_SESSION_TICKETS) && \
defined(MBEDTLS_SSL_SRV_C) && \
@@ -4837,23 +4881,16 @@ const mbedtls_x509_crt *mbedtls_ssl_get_peer_cert(const mbedtls_ssl_context *ssl
* \note This function can handle a variety of mechanisms for session
* resumption: For TLS 1.2, both session ID-based resumption and
* ticket-based resumption will be considered. For TLS 1.3,
- * once implemented, sessions equate to tickets, and calling
- * this function multiple times will export the available
- * tickets one a time until no further tickets are available,
- * in which case MBEDTLS_ERR_SSL_FEATURE_UNAVAILABLE will
- * be returned.
- *
- * \note Calling this function multiple times will only be useful
- * once TLS 1.3 is supported. For TLS 1.2 connections, this
- * function should be called at most once.
+ * sessions equate to tickets, and if session tickets are
+ * enabled (see #MBEDTLS_SSL_SESSION_TICKETS configuration
+ * option), this function exports the last received ticket and
+ * the exported session may be used to resume the TLS 1.3
+ * session. If session tickets are disabled, exported sessions
+ * cannot be used to resume a TLS 1.3 session.
*
* \return \c 0 if successful. In this case, \p session can be used for
* session resumption by passing it to mbedtls_ssl_set_session(),
* and serialized for storage via mbedtls_ssl_session_save().
- * \return #MBEDTLS_ERR_SSL_FEATURE_UNAVAILABLE if no further session
- * is available for export.
- * This error is a non-fatal, and has no observable effect on
- * the SSL context or the destination session.
* \return Another negative error code on other kinds of failure.
*
* \sa mbedtls_ssl_set_session()
@@ -4885,6 +4922,10 @@ int mbedtls_ssl_get_session(const mbedtls_ssl_context *ssl,
* \return #MBEDTLS_ERR_SSL_HELLO_VERIFY_REQUIRED if DTLS is in use
* and the client did not demonstrate reachability yet - in
* this case you must stop using the context (see below).
+ * \return #MBEDTLS_ERR_SSL_RECEIVED_NEW_SESSION_TICKET if a TLS 1.3
+ * NewSessionTicket message has been received. See the
+ * documentation of mbedtls_ssl_read() for more information
+ * about this error code.
* \return #MBEDTLS_ERR_SSL_RECEIVED_EARLY_DATA if early data, as
* defined in RFC 8446 (TLS 1.3 specification), has been
* received as part of the handshake. This is server specific
@@ -4901,6 +4942,7 @@ int mbedtls_ssl_get_session(const mbedtls_ssl_context *ssl,
* #MBEDTLS_ERR_SSL_WANT_WRITE,
* #MBEDTLS_ERR_SSL_ASYNC_IN_PROGRESS or
* #MBEDTLS_ERR_SSL_CRYPTO_IN_PROGRESS or
+ * #MBEDTLS_ERR_SSL_RECEIVED_NEW_SESSION_TICKET or
* #MBEDTLS_ERR_SSL_RECEIVED_EARLY_DATA,
* you must stop using the SSL context for reading or writing,
* and either free it or call \c mbedtls_ssl_session_reset()
@@ -4921,10 +4963,13 @@ int mbedtls_ssl_get_session(const mbedtls_ssl_context *ssl,
* currently being processed might or might not contain further
* DTLS records.
*
- * \note If the context is configured to allow TLS 1.3, or if
- * #MBEDTLS_USE_PSA_CRYPTO is enabled, the PSA crypto
+ * \note If #MBEDTLS_USE_PSA_CRYPTO is enabled, the PSA crypto
* subsystem must have been initialized by calling
* psa_crypto_init() before calling this function.
+ * Otherwise, the handshake may call psa_crypto_init()
+ * if a negotiation involving TLS 1.3 takes place (this may
+ * be the case even if TLS 1.3 is offered but eventually
+ * not selected).
*/
int mbedtls_ssl_handshake(mbedtls_ssl_context *ssl);
@@ -4972,6 +5017,7 @@ static inline int mbedtls_ssl_is_handshake_over(mbedtls_ssl_context *ssl)
* #MBEDTLS_ERR_SSL_WANT_READ, #MBEDTLS_ERR_SSL_WANT_WRITE,
* #MBEDTLS_ERR_SSL_ASYNC_IN_PROGRESS,
* #MBEDTLS_ERR_SSL_CRYPTO_IN_PROGRESS or
+ * #MBEDTLS_ERR_SSL_RECEIVED_NEW_SESSION_TICKET or
* #MBEDTLS_ERR_SSL_RECEIVED_EARLY_DATA, you must stop using
* the SSL context for reading or writing, and either free it
* or call \c mbedtls_ssl_session_reset() on it before
@@ -5040,6 +5086,17 @@ int mbedtls_ssl_renegotiate(mbedtls_ssl_context *ssl);
* \return #MBEDTLS_ERR_SSL_CLIENT_RECONNECT if we're at the server
* side of a DTLS connection and the client is initiating a
* new connection using the same source port. See below.
+ * \return #MBEDTLS_ERR_SSL_RECEIVED_NEW_SESSION_TICKET if a TLS 1.3
+ * NewSessionTicket message has been received.
+ * This error code is only returned on the client side. It is
+ * only returned if handling of TLS 1.3 NewSessionTicket
+ * messages has been enabled through
+ * mbedtls_ssl_conf_tls13_enable_signal_new_session_tickets().
+ * This error code indicates that a TLS 1.3 NewSessionTicket
+ * message has been received and parsed successfully by the
+ * client. The ticket data can be retrieved from the SSL
+ * context by calling mbedtls_ssl_get_session(). It remains
+ * available until the next call to mbedtls_ssl_read().
* \return #MBEDTLS_ERR_SSL_RECEIVED_EARLY_DATA if early data, as
* defined in RFC 8446 (TLS 1.3 specification), has been
* received as part of the handshake. This is server specific
@@ -5057,6 +5114,7 @@ int mbedtls_ssl_renegotiate(mbedtls_ssl_context *ssl);
* #MBEDTLS_ERR_SSL_ASYNC_IN_PROGRESS,
* #MBEDTLS_ERR_SSL_CRYPTO_IN_PROGRESS,
* #MBEDTLS_ERR_SSL_CLIENT_RECONNECT or
+ * #MBEDTLS_ERR_SSL_RECEIVED_NEW_SESSION_TICKET or
* #MBEDTLS_ERR_SSL_RECEIVED_EARLY_DATA,
* you must stop using the SSL context for reading or writing,
* and either free it or call \c mbedtls_ssl_session_reset()
@@ -5122,6 +5180,10 @@ int mbedtls_ssl_read(mbedtls_ssl_context *ssl, unsigned char *buf, size_t len);
* operation is in progress (see mbedtls_ecp_set_max_ops()) -
* in this case you must call this function again to complete
* the handshake when you're done attending other tasks.
+ * \return #MBEDTLS_ERR_SSL_RECEIVED_NEW_SESSION_TICKET if a TLS 1.3
+ * NewSessionTicket message has been received. See the
+ * documentation of mbedtls_ssl_read() for more information
+ * about this error code.
* \return #MBEDTLS_ERR_SSL_RECEIVED_EARLY_DATA if early data, as
* defined in RFC 8446 (TLS 1.3 specification), has been
* received as part of the handshake. This is server specific
@@ -5138,6 +5200,7 @@ int mbedtls_ssl_read(mbedtls_ssl_context *ssl, unsigned char *buf, size_t len);
* #MBEDTLS_ERR_SSL_WANT_WRITE,
* #MBEDTLS_ERR_SSL_ASYNC_IN_PROGRESS,
* #MBEDTLS_ERR_SSL_CRYPTO_IN_PROGRESS or
+ * #MBEDTLS_ERR_SSL_RECEIVED_NEW_SESSION_TICKET or
* #MBEDTLS_ERR_SSL_RECEIVED_EARLY_DATA,
* you must stop using the SSL context for reading or writing,
* and either free it or call \c mbedtls_ssl_session_reset()
diff --git a/thirdparty/mbedtls/include/psa/crypto.h b/thirdparty/mbedtls/include/psa/crypto.h
index 390534edf5..96baf8f3ed 100644
--- a/thirdparty/mbedtls/include/psa/crypto.h
+++ b/thirdparty/mbedtls/include/psa/crypto.h
@@ -121,8 +121,8 @@ static psa_key_attributes_t psa_key_attributes_init(void);
* value in the structure.
* The persistent key will be written to storage when the attribute
* structure is passed to a key creation function such as
- * psa_import_key(), psa_generate_key(), psa_generate_key_ext(),
- * psa_key_derivation_output_key(), psa_key_derivation_output_key_ext()
+ * psa_import_key(), psa_generate_key(), psa_generate_key_custom(),
+ * psa_key_derivation_output_key(), psa_key_derivation_output_key_custom()
* or psa_copy_key().
*
* This function may be declared as `static` (i.e. without external
@@ -131,6 +131,9 @@ static psa_key_attributes_t psa_key_attributes_init(void);
*
* \param[out] attributes The attribute structure to write to.
* \param key The persistent identifier for the key.
+ * This can be any value in the range from
+ * #PSA_KEY_ID_USER_MIN to #PSA_KEY_ID_USER_MAX
+ * inclusive.
*/
static void psa_set_key_id(psa_key_attributes_t *attributes,
mbedtls_svc_key_id_t key);
@@ -166,8 +169,8 @@ static void mbedtls_set_key_owner_id(psa_key_attributes_t *attributes,
* value in the structure.
* The persistent key will be written to storage when the attribute
* structure is passed to a key creation function such as
- * psa_import_key(), psa_generate_key(), psa_generate_key_ext(),
- * psa_key_derivation_output_key(), psa_key_derivation_output_key_ext()
+ * psa_import_key(), psa_generate_key(), psa_generate_key_custom(),
+ * psa_key_derivation_output_key(), psa_key_derivation_output_key_custom()
* or psa_copy_key().
*
* This function may be declared as `static` (i.e. without external
@@ -875,7 +878,7 @@ psa_status_t psa_hash_compute(psa_algorithm_t alg,
* such that #PSA_ALG_IS_HASH(\p alg) is true).
* \param[in] input Buffer containing the message to hash.
* \param input_length Size of the \p input buffer in bytes.
- * \param[out] hash Buffer containing the expected hash value.
+ * \param[in] hash Buffer containing the expected hash value.
* \param hash_length Size of the \p hash buffer in bytes.
*
* \retval #PSA_SUCCESS
@@ -1230,7 +1233,7 @@ psa_status_t psa_mac_compute(mbedtls_svc_key_id_t key,
* such that #PSA_ALG_IS_MAC(\p alg) is true).
* \param[in] input Buffer containing the input message.
* \param input_length Size of the \p input buffer in bytes.
- * \param[out] mac Buffer containing the expected MAC value.
+ * \param[in] mac Buffer containing the expected MAC value.
* \param mac_length Size of the \p mac buffer in bytes.
*
* \retval #PSA_SUCCESS
@@ -2922,7 +2925,7 @@ psa_status_t psa_sign_message(mbedtls_svc_key_id_t key,
* \p key.
* \param[in] input The message whose signature is to be verified.
* \param[in] input_length Size of the \p input buffer in bytes.
- * \param[out] signature Buffer containing the signature to verify.
+ * \param[in] signature Buffer containing the signature to verify.
* \param[in] signature_length Size of the \p signature buffer in bytes.
*
* \retval #PSA_SUCCESS \emptydescription
@@ -3248,7 +3251,7 @@ static psa_key_derivation_operation_t psa_key_derivation_operation_init(void);
* of or after providing inputs. For some algorithms, this step is mandatory
* because the output depends on the maximum capacity.
* -# To derive a key, call psa_key_derivation_output_key() or
- * psa_key_derivation_output_key_ext().
+ * psa_key_derivation_output_key_custom().
* To derive a byte string for a different purpose, call
* psa_key_derivation_output_bytes().
* Successive calls to these functions use successive output bytes
@@ -3471,7 +3474,7 @@ psa_status_t psa_key_derivation_input_integer(
* \note Once all inputs steps are completed, the operations will allow:
* - psa_key_derivation_output_bytes() if each input was either a direct input
* or a key with #PSA_KEY_USAGE_DERIVE set;
- * - psa_key_derivation_output_key() or psa_key_derivation_output_key_ext()
+ * - psa_key_derivation_output_key() or psa_key_derivation_output_key_custom()
* if the input for step
* #PSA_KEY_DERIVATION_INPUT_SECRET or #PSA_KEY_DERIVATION_INPUT_PASSWORD
* was from a key slot with #PSA_KEY_USAGE_DERIVE and each other input was
@@ -3721,9 +3724,9 @@ psa_status_t psa_key_derivation_output_bytes(
* on the derived key based on the attributes and strength of the secret key.
*
* \note This function is equivalent to calling
- * psa_key_derivation_output_key_ext()
- * with the production parameters #PSA_KEY_PRODUCTION_PARAMETERS_INIT
- * and `params_data_length == 0` (i.e. `params->data` is empty).
+ * psa_key_derivation_output_key_custom()
+ * with the custom production parameters #PSA_CUSTOM_KEY_PARAMETERS_INIT
+ * and `custom_data_length == 0` (i.e. `custom_data` is empty).
*
* \param[in] attributes The attributes for the new key.
* If the key type to be created is
@@ -3795,6 +3798,85 @@ psa_status_t psa_key_derivation_output_key(
* the policy must be the same as in the current
* operation.
* \param[in,out] operation The key derivation operation object to read from.
+ * \param[in] custom Customization parameters for the key generation.
+ * When this is #PSA_CUSTOM_KEY_PARAMETERS_INIT
+ * with \p custom_data_length = 0,
+ * this function is equivalent to
+ * psa_key_derivation_output_key().
+ * \param[in] custom_data Variable-length data associated with \c custom.
+ * \param custom_data_length
+ * Length of `custom_data` in bytes.
+ * \param[out] key On success, an identifier for the newly created
+ * key. For persistent keys, this is the key
+ * identifier defined in \p attributes.
+ * \c 0 on failure.
+ *
+ * \retval #PSA_SUCCESS
+ * Success.
+ * If the key is persistent, the key material and the key's metadata
+ * have been saved to persistent storage.
+ * \retval #PSA_ERROR_ALREADY_EXISTS
+ * This is an attempt to create a persistent key, and there is
+ * already a persistent key with the given identifier.
+ * \retval #PSA_ERROR_INSUFFICIENT_DATA
+ * There was not enough data to create the desired key.
+ * Note that in this case, no output is written to the output buffer.
+ * The operation's capacity is set to 0, thus subsequent calls to
+ * this function will not succeed, even with a smaller output buffer.
+ * \retval #PSA_ERROR_NOT_SUPPORTED
+ * The key type or key size is not supported, either by the
+ * implementation in general or in this particular location.
+ * \retval #PSA_ERROR_INVALID_ARGUMENT
+ * The provided key attributes are not valid for the operation.
+ * \retval #PSA_ERROR_NOT_PERMITTED
+ * The #PSA_KEY_DERIVATION_INPUT_SECRET or
+ * #PSA_KEY_DERIVATION_INPUT_PASSWORD input was not provided through a
+ * key; or one of the inputs was a key whose policy didn't allow
+ * #PSA_KEY_USAGE_DERIVE.
+ * \retval #PSA_ERROR_INSUFFICIENT_MEMORY \emptydescription
+ * \retval #PSA_ERROR_INSUFFICIENT_STORAGE \emptydescription
+ * \retval #PSA_ERROR_COMMUNICATION_FAILURE \emptydescription
+ * \retval #PSA_ERROR_HARDWARE_FAILURE \emptydescription
+ * \retval #PSA_ERROR_CORRUPTION_DETECTED \emptydescription
+ * \retval #PSA_ERROR_DATA_INVALID \emptydescription
+ * \retval #PSA_ERROR_DATA_CORRUPT \emptydescription
+ * \retval #PSA_ERROR_STORAGE_FAILURE \emptydescription
+ * \retval #PSA_ERROR_BAD_STATE
+ * The operation state is not valid (it must be active and completed
+ * all required input steps), or the library has not been previously
+ * initialized by psa_crypto_init().
+ * It is implementation-dependent whether a failure to initialize
+ * results in this error code.
+ */
+psa_status_t psa_key_derivation_output_key_custom(
+ const psa_key_attributes_t *attributes,
+ psa_key_derivation_operation_t *operation,
+ const psa_custom_key_parameters_t *custom,
+ const uint8_t *custom_data,
+ size_t custom_data_length,
+ mbedtls_svc_key_id_t *key);
+
+#ifndef __cplusplus
+/* Omitted when compiling in C++, because one of the parameters is a
+ * pointer to a struct with a flexible array member, and that is not
+ * standard C++.
+ * https://github.com/Mbed-TLS/mbedtls/issues/9020
+ */
+/** Derive a key from an ongoing key derivation operation with custom
+ * production parameters.
+ *
+ * \note
+ * This is a deprecated variant of psa_key_derivation_output_key_custom().
+ * It is equivalent except that the associated variable-length data
+ * is passed in `params->data` instead of a separate parameter.
+ * This function will be removed in a future version of Mbed TLS.
+ *
+ * \param[in] attributes The attributes for the new key.
+ * If the key type to be created is
+ * #PSA_KEY_TYPE_PASSWORD_HASH then the algorithm in
+ * the policy must be the same as in the current
+ * operation.
+ * \param[in,out] operation The key derivation operation object to read from.
* \param[in] params Customization parameters for the key derivation.
* When this is #PSA_KEY_PRODUCTION_PARAMETERS_INIT
* with \p params_data_length = 0,
@@ -3848,14 +3930,13 @@ psa_status_t psa_key_derivation_output_key(
* It is implementation-dependent whether a failure to initialize
* results in this error code.
*/
-#ifndef __cplusplus
psa_status_t psa_key_derivation_output_key_ext(
const psa_key_attributes_t *attributes,
psa_key_derivation_operation_t *operation,
const psa_key_production_parameters_t *params,
size_t params_data_length,
mbedtls_svc_key_id_t *key);
-#endif
+#endif /* !__cplusplus */
/** Compare output data from a key derivation operation to an expected value.
*
@@ -3881,8 +3962,8 @@ psa_status_t psa_key_derivation_output_key_ext(
* psa_key_derivation_abort().
*
* \param[in,out] operation The key derivation operation object to read from.
- * \param[in] expected_output Buffer containing the expected derivation output.
- * \param output_length Length of the expected output; this is also the
+ * \param[in] expected Buffer containing the expected derivation output.
+ * \param expected_length Length of the expected output; this is also the
* number of bytes that will be read.
*
* \retval #PSA_SUCCESS \emptydescription
@@ -3912,8 +3993,8 @@ psa_status_t psa_key_derivation_output_key_ext(
*/
psa_status_t psa_key_derivation_verify_bytes(
psa_key_derivation_operation_t *operation,
- const uint8_t *expected_output,
- size_t output_length);
+ const uint8_t *expected,
+ size_t expected_length);
/** Compare output data from a key derivation operation to an expected value
* stored in a key object.
@@ -3943,7 +4024,7 @@ psa_status_t psa_key_derivation_verify_bytes(
* operation. The value of this key was likely
* computed by a previous call to
* psa_key_derivation_output_key() or
- * psa_key_derivation_output_key_ext().
+ * psa_key_derivation_output_key_custom().
*
* \retval #PSA_SUCCESS \emptydescription
* \retval #PSA_ERROR_INVALID_SIGNATURE
@@ -4111,9 +4192,9 @@ psa_status_t psa_generate_random(uint8_t *output,
* between 2^{n-1} and 2^n where n is the bit size specified in the
* attributes.
*
- * \note This function is equivalent to calling psa_generate_key_ext()
- * with the production parameters #PSA_KEY_PRODUCTION_PARAMETERS_INIT
- * and `params_data_length == 0` (i.e. `params->data` is empty).
+ * \note This function is equivalent to calling psa_generate_key_custom()
+ * with the custom production parameters #PSA_CUSTOM_KEY_PARAMETERS_INIT
+ * and `custom_data_length == 0` (i.e. `custom_data` is empty).
*
* \param[in] attributes The attributes for the new key.
* \param[out] key On success, an identifier for the newly created
@@ -4153,7 +4234,7 @@ psa_status_t psa_generate_key(const psa_key_attributes_t *attributes,
* See the description of psa_generate_key() for the operation of this
* function with the default production parameters. In addition, this function
* supports the following production customizations, described in more detail
- * in the documentation of ::psa_key_production_parameters_t:
+ * in the documentation of ::psa_custom_key_parameters_t:
*
* - RSA keys: generation with a custom public exponent.
*
@@ -4161,6 +4242,64 @@ psa_status_t psa_generate_key(const psa_key_attributes_t *attributes,
* versions of Mbed TLS.
*
* \param[in] attributes The attributes for the new key.
+ * \param[in] custom Customization parameters for the key generation.
+ * When this is #PSA_CUSTOM_KEY_PARAMETERS_INIT
+ * with \p custom_data_length = 0,
+ * this function is equivalent to
+ * psa_generate_key().
+ * \param[in] custom_data Variable-length data associated with \c custom.
+ * \param custom_data_length
+ * Length of `custom_data` in bytes.
+ * \param[out] key On success, an identifier for the newly created
+ * key. For persistent keys, this is the key
+ * identifier defined in \p attributes.
+ * \c 0 on failure.
+ *
+ * \retval #PSA_SUCCESS
+ * Success.
+ * If the key is persistent, the key material and the key's metadata
+ * have been saved to persistent storage.
+ * \retval #PSA_ERROR_ALREADY_EXISTS
+ * This is an attempt to create a persistent key, and there is
+ * already a persistent key with the given identifier.
+ * \retval #PSA_ERROR_NOT_SUPPORTED \emptydescription
+ * \retval #PSA_ERROR_INVALID_ARGUMENT \emptydescription
+ * \retval #PSA_ERROR_INSUFFICIENT_MEMORY \emptydescription
+ * \retval #PSA_ERROR_INSUFFICIENT_ENTROPY \emptydescription
+ * \retval #PSA_ERROR_COMMUNICATION_FAILURE \emptydescription
+ * \retval #PSA_ERROR_HARDWARE_FAILURE \emptydescription
+ * \retval #PSA_ERROR_CORRUPTION_DETECTED \emptydescription
+ * \retval #PSA_ERROR_INSUFFICIENT_STORAGE \emptydescription
+ * \retval #PSA_ERROR_DATA_INVALID \emptydescription
+ * \retval #PSA_ERROR_DATA_CORRUPT \emptydescription
+ * \retval #PSA_ERROR_STORAGE_FAILURE \emptydescription
+ * \retval #PSA_ERROR_BAD_STATE
+ * The library has not been previously initialized by psa_crypto_init().
+ * It is implementation-dependent whether a failure to initialize
+ * results in this error code.
+ */
+psa_status_t psa_generate_key_custom(const psa_key_attributes_t *attributes,
+ const psa_custom_key_parameters_t *custom,
+ const uint8_t *custom_data,
+ size_t custom_data_length,
+ mbedtls_svc_key_id_t *key);
+
+#ifndef __cplusplus
+/* Omitted when compiling in C++, because one of the parameters is a
+ * pointer to a struct with a flexible array member, and that is not
+ * standard C++.
+ * https://github.com/Mbed-TLS/mbedtls/issues/9020
+ */
+/**
+ * \brief Generate a key or key pair using custom production parameters.
+ *
+ * \note
+ * This is a deprecated variant of psa_key_derivation_output_key_custom().
+ * It is equivalent except that the associated variable-length data
+ * is passed in `params->data` instead of a separate parameter.
+ * This function will be removed in a future version of Mbed TLS.
+ *
+ * \param[in] attributes The attributes for the new key.
* \param[in] params Customization parameters for the key generation.
* When this is #PSA_KEY_PRODUCTION_PARAMETERS_INIT
* with \p params_data_length = 0,
@@ -4196,12 +4335,11 @@ psa_status_t psa_generate_key(const psa_key_attributes_t *attributes,
* It is implementation-dependent whether a failure to initialize
* results in this error code.
*/
-#ifndef __cplusplus
psa_status_t psa_generate_key_ext(const psa_key_attributes_t *attributes,
const psa_key_production_parameters_t *params,
size_t params_data_length,
mbedtls_svc_key_id_t *key);
-#endif
+#endif /* !__cplusplus */
/**@}*/
diff --git a/thirdparty/mbedtls/include/psa/crypto_adjust_auto_enabled.h b/thirdparty/mbedtls/include/psa/crypto_adjust_auto_enabled.h
index 63fb29e85b..3a2af15180 100644
--- a/thirdparty/mbedtls/include/psa/crypto_adjust_auto_enabled.h
+++ b/thirdparty/mbedtls/include/psa/crypto_adjust_auto_enabled.h
@@ -2,6 +2,8 @@
* \file psa/crypto_adjust_auto_enabled.h
* \brief Adjust PSA configuration: enable always-on features
*
+ * This is an internal header. Do not include it directly.
+ *
* Always enable certain features which require a negligible amount of code
* to implement, to avoid some edge cases in the configuration combinatorics.
*/
@@ -13,6 +15,14 @@
#ifndef PSA_CRYPTO_ADJUST_AUTO_ENABLED_H
#define PSA_CRYPTO_ADJUST_AUTO_ENABLED_H
+#if !defined(MBEDTLS_CONFIG_FILES_READ)
+#error "Do not include psa/crypto_adjust_*.h manually! This can lead to problems, " \
+ "up to and including runtime errors such as buffer overflows. " \
+ "If you're trying to fix a complaint from check_config.h, just remove " \
+ "it from your configuration file: since Mbed TLS 3.0, it is included " \
+ "automatically at the right point."
+#endif /* */
+
#define PSA_WANT_KEY_TYPE_DERIVE 1
#define PSA_WANT_KEY_TYPE_PASSWORD 1
#define PSA_WANT_KEY_TYPE_PASSWORD_HASH 1
diff --git a/thirdparty/mbedtls/include/psa/crypto_adjust_config_dependencies.h b/thirdparty/mbedtls/include/psa/crypto_adjust_config_dependencies.h
new file mode 100644
index 0000000000..92e9c4de28
--- /dev/null
+++ b/thirdparty/mbedtls/include/psa/crypto_adjust_config_dependencies.h
@@ -0,0 +1,51 @@
+/**
+ * \file psa/crypto_adjust_config_dependencies.h
+ * \brief Adjust PSA configuration by resolving some dependencies.
+ *
+ * This is an internal header. Do not include it directly.
+ *
+ * See docs/proposed/psa-conditional-inclusion-c.md.
+ * If the Mbed TLS implementation of a cryptographic mechanism A depends on a
+ * cryptographic mechanism B then if the cryptographic mechanism A is enabled
+ * and not accelerated enable B. Note that if A is enabled and accelerated, it
+ * is not necessary to enable B for A support.
+ */
+/*
+ * Copyright The Mbed TLS Contributors
+ * SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later
+ */
+
+#ifndef PSA_CRYPTO_ADJUST_CONFIG_DEPENDENCIES_H
+#define PSA_CRYPTO_ADJUST_CONFIG_DEPENDENCIES_H
+
+#if !defined(MBEDTLS_CONFIG_FILES_READ)
+#error "Do not include psa/crypto_adjust_*.h manually! This can lead to problems, " \
+ "up to and including runtime errors such as buffer overflows. " \
+ "If you're trying to fix a complaint from check_config.h, just remove " \
+ "it from your configuration file: since Mbed TLS 3.0, it is included " \
+ "automatically at the right point."
+#endif /* */
+
+#if (defined(PSA_WANT_ALG_TLS12_PRF) && \
+ !defined(MBEDTLS_PSA_ACCEL_ALG_TLS12_PRF)) || \
+ (defined(PSA_WANT_ALG_TLS12_PSK_TO_MS) && \
+ !defined(MBEDTLS_PSA_ACCEL_ALG_TLS12_PSK_TO_MS)) || \
+ (defined(PSA_WANT_ALG_HKDF) && \
+ !defined(MBEDTLS_PSA_ACCEL_ALG_HKDF)) || \
+ (defined(PSA_WANT_ALG_HKDF_EXTRACT) && \
+ !defined(MBEDTLS_PSA_ACCEL_ALG_HKDF_EXTRACT)) || \
+ (defined(PSA_WANT_ALG_HKDF_EXPAND) && \
+ !defined(MBEDTLS_PSA_ACCEL_ALG_HKDF_EXPAND)) || \
+ (defined(PSA_WANT_ALG_PBKDF2_HMAC) && \
+ !defined(MBEDTLS_PSA_ACCEL_ALG_PBKDF2_HMAC))
+#define PSA_WANT_ALG_HMAC 1
+#define PSA_WANT_KEY_TYPE_HMAC 1
+#endif
+
+#if (defined(PSA_WANT_ALG_PBKDF2_AES_CMAC_PRF_128) && \
+ !defined(MBEDTLS_PSA_ACCEL_ALG_PBKDF2_AES_CMAC_PRF_128))
+#define PSA_WANT_KEY_TYPE_AES 1
+#define PSA_WANT_ALG_CMAC 1
+#endif
+
+#endif /* PSA_CRYPTO_ADJUST_CONFIG_DEPENDENCIES_H */
diff --git a/thirdparty/mbedtls/include/psa/crypto_adjust_config_key_pair_types.h b/thirdparty/mbedtls/include/psa/crypto_adjust_config_key_pair_types.h
index 63afc0e402..cec39e01ce 100644
--- a/thirdparty/mbedtls/include/psa/crypto_adjust_config_key_pair_types.h
+++ b/thirdparty/mbedtls/include/psa/crypto_adjust_config_key_pair_types.h
@@ -2,6 +2,8 @@
* \file psa/crypto_adjust_config_key_pair_types.h
* \brief Adjust PSA configuration for key pair types.
*
+ * This is an internal header. Do not include it directly.
+ *
* See docs/proposed/psa-conditional-inclusion-c.md.
* - Support non-basic operations in a keypair type implicitly enables basic
* support for that keypair type.
@@ -19,6 +21,14 @@
#ifndef PSA_CRYPTO_ADJUST_KEYPAIR_TYPES_H
#define PSA_CRYPTO_ADJUST_KEYPAIR_TYPES_H
+#if !defined(MBEDTLS_CONFIG_FILES_READ)
+#error "Do not include psa/crypto_adjust_*.h manually! This can lead to problems, " \
+ "up to and including runtime errors such as buffer overflows. " \
+ "If you're trying to fix a complaint from check_config.h, just remove " \
+ "it from your configuration file: since Mbed TLS 3.0, it is included " \
+ "automatically at the right point."
+#endif /* */
+
/*****************************************************************
* ANYTHING -> BASIC
****************************************************************/
diff --git a/thirdparty/mbedtls/include/psa/crypto_adjust_config_synonyms.h b/thirdparty/mbedtls/include/psa/crypto_adjust_config_synonyms.h
index 332b622c9b..54b116f434 100644
--- a/thirdparty/mbedtls/include/psa/crypto_adjust_config_synonyms.h
+++ b/thirdparty/mbedtls/include/psa/crypto_adjust_config_synonyms.h
@@ -2,6 +2,8 @@
* \file psa/crypto_adjust_config_synonyms.h
* \brief Adjust PSA configuration: enable quasi-synonyms
*
+ * This is an internal header. Do not include it directly.
+ *
* When two features require almost the same code, we automatically enable
* both when either one is requested, to reduce the combinatorics of
* possible configurations.
@@ -14,6 +16,14 @@
#ifndef PSA_CRYPTO_ADJUST_CONFIG_SYNONYMS_H
#define PSA_CRYPTO_ADJUST_CONFIG_SYNONYMS_H
+#if !defined(MBEDTLS_CONFIG_FILES_READ)
+#error "Do not include psa/crypto_adjust_*.h manually! This can lead to problems, " \
+ "up to and including runtime errors such as buffer overflows. " \
+ "If you're trying to fix a complaint from check_config.h, just remove " \
+ "it from your configuration file: since Mbed TLS 3.0, it is included " \
+ "automatically at the right point."
+#endif /* */
+
/****************************************************************/
/* De facto synonyms */
/****************************************************************/
diff --git a/thirdparty/mbedtls/include/psa/crypto_extra.h b/thirdparty/mbedtls/include/psa/crypto_extra.h
index bd985e9b78..d276cd4c7f 100644
--- a/thirdparty/mbedtls/include/psa/crypto_extra.h
+++ b/thirdparty/mbedtls/include/psa/crypto_extra.h
@@ -154,6 +154,14 @@ static inline void psa_clear_key_slot_number(
* specified in \p attributes.
*
* \param[in] attributes The attributes of the existing key.
+ * - The lifetime must be a persistent lifetime
+ * in a secure element. Volatile lifetimes are
+ * not currently supported.
+ * - The key identifier must be in the valid
+ * range for persistent keys.
+ * - The key type and size must be specified and
+ * must be consistent with the key material
+ * in the secure element.
*
* \retval #PSA_SUCCESS
* The key was successfully registered.
@@ -479,7 +487,7 @@ psa_status_t mbedtls_psa_external_get_random(
* #PSA_KEY_ID_VENDOR_MIN and #PSA_KEY_ID_VENDOR_MAX and must not intersect
* with any other set of implementation-chosen key identifiers.
*
- * This value is part of the library's ABI since changing it would invalidate
+ * This value is part of the library's API since changing it would invalidate
* the values of built-in key identifiers in applications.
*/
#define MBEDTLS_PSA_KEY_ID_BUILTIN_MIN ((psa_key_id_t) 0x7fff0000)
diff --git a/thirdparty/mbedtls/include/psa/crypto_struct.h b/thirdparty/mbedtls/include/psa/crypto_struct.h
index e2c227b2eb..362e921a36 100644
--- a/thirdparty/mbedtls/include/psa/crypto_struct.h
+++ b/thirdparty/mbedtls/include/psa/crypto_struct.h
@@ -223,13 +223,36 @@ static inline struct psa_key_derivation_s psa_key_derivation_operation_init(
return v;
}
+struct psa_custom_key_parameters_s {
+ /* Future versions may add other fields in this structure. */
+ uint32_t flags;
+};
+
+/** The default production parameters for key generation or key derivation.
+ *
+ * Calling psa_generate_key_custom() or psa_key_derivation_output_key_custom()
+ * with `custom=PSA_CUSTOM_KEY_PARAMETERS_INIT` and `custom_data_length=0` is
+ * equivalent to calling psa_generate_key() or psa_key_derivation_output_key()
+ * respectively.
+ */
+#define PSA_CUSTOM_KEY_PARAMETERS_INIT { 0 }
+
#ifndef __cplusplus
+/* Omitted when compiling in C++, because one of the parameters is a
+ * pointer to a struct with a flexible array member, and that is not
+ * standard C++.
+ * https://github.com/Mbed-TLS/mbedtls/issues/9020
+ */
+/* This is a deprecated variant of `struct psa_custom_key_parameters_s`.
+ * It has exactly the same layout, plus an extra field which is a flexible
+ * array member. Thus a `const struct psa_key_production_parameters_s *`
+ * can be passed to any function that reads a
+ * `const struct psa_custom_key_parameters_s *`.
+ */
struct psa_key_production_parameters_s {
- /* Future versions may add other fields in this structure. */
uint32_t flags;
uint8_t data[];
};
-#endif
/** The default production parameters for key generation or key derivation.
*
@@ -240,6 +263,7 @@ struct psa_key_production_parameters_s {
* respectively.
*/
#define PSA_KEY_PRODUCTION_PARAMETERS_INIT { 0 }
+#endif /* !__cplusplus */
struct psa_key_policy_s {
psa_key_usage_t MBEDTLS_PRIVATE(usage);
diff --git a/thirdparty/mbedtls/include/psa/crypto_types.h b/thirdparty/mbedtls/include/psa/crypto_types.h
index a36b6ee65d..f831486f4e 100644
--- a/thirdparty/mbedtls/include/psa/crypto_types.h
+++ b/thirdparty/mbedtls/include/psa/crypto_types.h
@@ -457,6 +457,30 @@ typedef uint16_t psa_key_derivation_step_t;
/** \brief Custom parameters for key generation or key derivation.
*
+ * This is a structure type with at least the following field:
+ *
+ * - \c flags: an unsigned integer type. 0 for the default production parameters.
+ *
+ * Functions that take such a structure as input also take an associated
+ * input buffer \c custom_data of length \c custom_data_length.
+ *
+ * The interpretation of this structure and the associated \c custom_data
+ * parameter depend on the type of the created key.
+ *
+ * - #PSA_KEY_TYPE_RSA_KEY_PAIR:
+ * - \c flags: must be 0.
+ * - \c custom_data: the public exponent, in little-endian order.
+ * This must be an odd integer and must not be 1.
+ * Implementations must support 65537, should support 3 and may
+ * support other values.
+ * When not using a driver, Mbed TLS supports values up to \c INT_MAX.
+ * If this is empty, the default value 65537 is used.
+ * - Other key types: reserved for future use. \c flags must be 0.
+ */
+typedef struct psa_custom_key_parameters_s psa_custom_key_parameters_t;
+
+/** \brief Custom parameters for key generation or key derivation.
+ *
* This is a structure type with at least the following fields:
*
* - \c flags: an unsigned integer type. 0 for the default production parameters.
@@ -477,9 +501,7 @@ typedef uint16_t psa_key_derivation_step_t;
* - Other key types: reserved for future use. \c flags must be 0.
*
*/
-#ifndef __cplusplus
typedef struct psa_key_production_parameters_s psa_key_production_parameters_t;
-#endif
/**@}*/
diff --git a/thirdparty/mbedtls/library/bignum.c b/thirdparty/mbedtls/library/bignum.c
index c45fd5bf24..424490951d 100644
--- a/thirdparty/mbedtls/library/bignum.c
+++ b/thirdparty/mbedtls/library/bignum.c
@@ -27,6 +27,7 @@
#include "mbedtls/bignum.h"
#include "bignum_core.h"
+#include "bignum_internal.h"
#include "bn_mul.h"
#include "mbedtls/platform_util.h"
#include "mbedtls/error.h"
@@ -1610,9 +1611,13 @@ int mbedtls_mpi_mod_int(mbedtls_mpi_uint *r, const mbedtls_mpi *A, mbedtls_mpi_s
return 0;
}
-int mbedtls_mpi_exp_mod(mbedtls_mpi *X, const mbedtls_mpi *A,
- const mbedtls_mpi *E, const mbedtls_mpi *N,
- mbedtls_mpi *prec_RR)
+/*
+ * Warning! If the parameter E_public has MBEDTLS_MPI_IS_PUBLIC as its value,
+ * this function is not constant time with respect to the exponent (parameter E).
+ */
+static int mbedtls_mpi_exp_mod_optionally_safe(mbedtls_mpi *X, const mbedtls_mpi *A,
+ const mbedtls_mpi *E, int E_public,
+ const mbedtls_mpi *N, mbedtls_mpi *prec_RR)
{
int ret = MBEDTLS_ERR_ERROR_CORRUPTION_DETECTED;
@@ -1695,7 +1700,11 @@ int mbedtls_mpi_exp_mod(mbedtls_mpi *X, const mbedtls_mpi *A,
{
mbedtls_mpi_uint mm = mbedtls_mpi_core_montmul_init(N->p);
mbedtls_mpi_core_to_mont_rep(X->p, X->p, N->p, N->n, mm, RR.p, T);
- mbedtls_mpi_core_exp_mod(X->p, X->p, N->p, N->n, E->p, E->n, RR.p, T);
+ if (E_public == MBEDTLS_MPI_IS_PUBLIC) {
+ mbedtls_mpi_core_exp_mod_unsafe(X->p, X->p, N->p, N->n, E->p, E->n, RR.p, T);
+ } else {
+ mbedtls_mpi_core_exp_mod(X->p, X->p, N->p, N->n, E->p, E->n, RR.p, T);
+ }
mbedtls_mpi_core_from_mont_rep(X->p, X->p, N->p, N->n, mm, T);
}
@@ -1720,6 +1729,20 @@ cleanup:
return ret;
}
+int mbedtls_mpi_exp_mod(mbedtls_mpi *X, const mbedtls_mpi *A,
+ const mbedtls_mpi *E, const mbedtls_mpi *N,
+ mbedtls_mpi *prec_RR)
+{
+ return mbedtls_mpi_exp_mod_optionally_safe(X, A, E, MBEDTLS_MPI_IS_SECRET, N, prec_RR);
+}
+
+int mbedtls_mpi_exp_mod_unsafe(mbedtls_mpi *X, const mbedtls_mpi *A,
+ const mbedtls_mpi *E, const mbedtls_mpi *N,
+ mbedtls_mpi *prec_RR)
+{
+ return mbedtls_mpi_exp_mod_optionally_safe(X, A, E, MBEDTLS_MPI_IS_PUBLIC, N, prec_RR);
+}
+
/*
* Greatest common divisor: G = gcd(A, B) (HAC 14.54)
*/
diff --git a/thirdparty/mbedtls/library/bignum_core.c b/thirdparty/mbedtls/library/bignum_core.c
index 1a3e0b9b6f..4231554b84 100644
--- a/thirdparty/mbedtls/library/bignum_core.c
+++ b/thirdparty/mbedtls/library/bignum_core.c
@@ -746,8 +746,94 @@ static void exp_mod_precompute_window(const mbedtls_mpi_uint *A,
}
}
+#if defined(MBEDTLS_TEST_HOOKS) && !defined(MBEDTLS_THREADING_C)
+// Set to a default that is neither MBEDTLS_MPI_IS_PUBLIC nor MBEDTLS_MPI_IS_SECRET
+int mbedtls_mpi_optionally_safe_codepath = MBEDTLS_MPI_IS_PUBLIC + MBEDTLS_MPI_IS_SECRET + 1;
+#endif
+
+/*
+ * This function calculates the indices of the exponent where the exponentiation algorithm should
+ * start processing.
+ *
+ * Warning! If the parameter E_public has MBEDTLS_MPI_IS_PUBLIC as its value,
+ * this function is not constant time with respect to the exponent (parameter E).
+ */
+static inline void exp_mod_calc_first_bit_optionally_safe(const mbedtls_mpi_uint *E,
+ size_t E_limbs,
+ int E_public,
+ size_t *E_limb_index,
+ size_t *E_bit_index)
+{
+ if (E_public == MBEDTLS_MPI_IS_PUBLIC) {
+ /*
+ * Skip leading zero bits.
+ */
+ size_t E_bits = mbedtls_mpi_core_bitlen(E, E_limbs);
+ if (E_bits == 0) {
+ /*
+ * If E is 0 mbedtls_mpi_core_bitlen() returns 0. Even if that is the case, we will want
+ * to represent it as a single 0 bit and as such the bitlength will be 1.
+ */
+ E_bits = 1;
+ }
+
+ *E_limb_index = E_bits / biL;
+ *E_bit_index = E_bits % biL;
+
+#if defined(MBEDTLS_TEST_HOOKS) && !defined(MBEDTLS_THREADING_C)
+ mbedtls_mpi_optionally_safe_codepath = MBEDTLS_MPI_IS_PUBLIC;
+#endif
+ } else {
+ /*
+ * Here we need to be constant time with respect to E and can't do anything better than
+ * start at the first allocated bit.
+ */
+ *E_limb_index = E_limbs;
+ *E_bit_index = 0;
+#if defined(MBEDTLS_TEST_HOOKS) && !defined(MBEDTLS_THREADING_C)
+ // Only mark the codepath safe if there wasn't an unsafe codepath before
+ if (mbedtls_mpi_optionally_safe_codepath != MBEDTLS_MPI_IS_PUBLIC) {
+ mbedtls_mpi_optionally_safe_codepath = MBEDTLS_MPI_IS_SECRET;
+ }
+#endif
+ }
+}
+
+/*
+ * Warning! If the parameter window_public has MBEDTLS_MPI_IS_PUBLIC as its value, this function is
+ * not constant time with respect to the window parameter and consequently the exponent of the
+ * exponentiation (parameter E of mbedtls_mpi_core_exp_mod_optionally_safe).
+ */
+static inline void exp_mod_table_lookup_optionally_safe(mbedtls_mpi_uint *Wselect,
+ mbedtls_mpi_uint *Wtable,
+ size_t AN_limbs, size_t welem,
+ mbedtls_mpi_uint window,
+ int window_public)
+{
+ if (window_public == MBEDTLS_MPI_IS_PUBLIC) {
+ memcpy(Wselect, Wtable + window * AN_limbs, AN_limbs * ciL);
+#if defined(MBEDTLS_TEST_HOOKS) && !defined(MBEDTLS_THREADING_C)
+ mbedtls_mpi_optionally_safe_codepath = MBEDTLS_MPI_IS_PUBLIC;
+#endif
+ } else {
+ /* Select Wtable[window] without leaking window through
+ * memory access patterns. */
+ mbedtls_mpi_core_ct_uint_table_lookup(Wselect, Wtable,
+ AN_limbs, welem, window);
+#if defined(MBEDTLS_TEST_HOOKS) && !defined(MBEDTLS_THREADING_C)
+ // Only mark the codepath safe if there wasn't an unsafe codepath before
+ if (mbedtls_mpi_optionally_safe_codepath != MBEDTLS_MPI_IS_PUBLIC) {
+ mbedtls_mpi_optionally_safe_codepath = MBEDTLS_MPI_IS_SECRET;
+ }
+#endif
+ }
+}
+
/* Exponentiation: X := A^E mod N.
*
+ * Warning! If the parameter E_public has MBEDTLS_MPI_IS_PUBLIC as its value,
+ * this function is not constant time with respect to the exponent (parameter E).
+ *
* A must already be in Montgomery form.
*
* As in other bignum functions, assume that AN_limbs and E_limbs are nonzero.
@@ -758,16 +844,25 @@ static void exp_mod_precompute_window(const mbedtls_mpi_uint *A,
* (The difference is that the body in our loop processes a single bit instead
* of a full window.)
*/
-void mbedtls_mpi_core_exp_mod(mbedtls_mpi_uint *X,
- const mbedtls_mpi_uint *A,
- const mbedtls_mpi_uint *N,
- size_t AN_limbs,
- const mbedtls_mpi_uint *E,
- size_t E_limbs,
- const mbedtls_mpi_uint *RR,
- mbedtls_mpi_uint *T)
+static void mbedtls_mpi_core_exp_mod_optionally_safe(mbedtls_mpi_uint *X,
+ const mbedtls_mpi_uint *A,
+ const mbedtls_mpi_uint *N,
+ size_t AN_limbs,
+ const mbedtls_mpi_uint *E,
+ size_t E_limbs,
+ int E_public,
+ const mbedtls_mpi_uint *RR,
+ mbedtls_mpi_uint *T)
{
- const size_t wsize = exp_mod_get_window_size(E_limbs * biL);
+ /* We'll process the bits of E from most significant
+ * (limb_index=E_limbs-1, E_bit_index=biL-1) to least significant
+ * (limb_index=0, E_bit_index=0). */
+ size_t E_limb_index;
+ size_t E_bit_index;
+ exp_mod_calc_first_bit_optionally_safe(E, E_limbs, E_public,
+ &E_limb_index, &E_bit_index);
+
+ const size_t wsize = exp_mod_get_window_size(E_limb_index * biL);
const size_t welem = ((size_t) 1) << wsize;
/* This is how we will use the temporary storage T, which must have space
@@ -786,7 +881,7 @@ void mbedtls_mpi_core_exp_mod(mbedtls_mpi_uint *X,
const mbedtls_mpi_uint mm = mbedtls_mpi_core_montmul_init(N);
- /* Set Wtable[i] = A^(2^i) (in Montgomery representation) */
+ /* Set Wtable[i] = A^i (in Montgomery representation) */
exp_mod_precompute_window(A, N, AN_limbs,
mm, RR,
welem, Wtable, temp);
@@ -798,11 +893,6 @@ void mbedtls_mpi_core_exp_mod(mbedtls_mpi_uint *X,
/* X = 1 (in Montgomery presentation) initially */
memcpy(X, Wtable, AN_limbs * ciL);
- /* We'll process the bits of E from most significant
- * (limb_index=E_limbs-1, E_bit_index=biL-1) to least significant
- * (limb_index=0, E_bit_index=0). */
- size_t E_limb_index = E_limbs;
- size_t E_bit_index = 0;
/* At any given time, window contains window_bits bits from E.
* window_bits can go up to wsize. */
size_t window_bits = 0;
@@ -828,10 +918,9 @@ void mbedtls_mpi_core_exp_mod(mbedtls_mpi_uint *X,
* when we've finished processing the exponent. */
if (window_bits == wsize ||
(E_bit_index == 0 && E_limb_index == 0)) {
- /* Select Wtable[window] without leaking window through
- * memory access patterns. */
- mbedtls_mpi_core_ct_uint_table_lookup(Wselect, Wtable,
- AN_limbs, welem, window);
+
+ exp_mod_table_lookup_optionally_safe(Wselect, Wtable, AN_limbs, welem,
+ window, E_public);
/* Multiply X by the selected element. */
mbedtls_mpi_core_montmul(X, X, Wselect, AN_limbs, N, AN_limbs, mm,
temp);
@@ -841,6 +930,42 @@ void mbedtls_mpi_core_exp_mod(mbedtls_mpi_uint *X,
} while (!(E_bit_index == 0 && E_limb_index == 0));
}
+void mbedtls_mpi_core_exp_mod(mbedtls_mpi_uint *X,
+ const mbedtls_mpi_uint *A,
+ const mbedtls_mpi_uint *N, size_t AN_limbs,
+ const mbedtls_mpi_uint *E, size_t E_limbs,
+ const mbedtls_mpi_uint *RR,
+ mbedtls_mpi_uint *T)
+{
+ mbedtls_mpi_core_exp_mod_optionally_safe(X,
+ A,
+ N,
+ AN_limbs,
+ E,
+ E_limbs,
+ MBEDTLS_MPI_IS_SECRET,
+ RR,
+ T);
+}
+
+void mbedtls_mpi_core_exp_mod_unsafe(mbedtls_mpi_uint *X,
+ const mbedtls_mpi_uint *A,
+ const mbedtls_mpi_uint *N, size_t AN_limbs,
+ const mbedtls_mpi_uint *E, size_t E_limbs,
+ const mbedtls_mpi_uint *RR,
+ mbedtls_mpi_uint *T)
+{
+ mbedtls_mpi_core_exp_mod_optionally_safe(X,
+ A,
+ N,
+ AN_limbs,
+ E,
+ E_limbs,
+ MBEDTLS_MPI_IS_PUBLIC,
+ RR,
+ T);
+}
+
mbedtls_mpi_uint mbedtls_mpi_core_sub_int(mbedtls_mpi_uint *X,
const mbedtls_mpi_uint *A,
mbedtls_mpi_uint c, /* doubles as carry */
diff --git a/thirdparty/mbedtls/library/bignum_core.h b/thirdparty/mbedtls/library/bignum_core.h
index 92c8d47db5..cf6485a148 100644
--- a/thirdparty/mbedtls/library/bignum_core.h
+++ b/thirdparty/mbedtls/library/bignum_core.h
@@ -90,6 +90,27 @@
#define GET_BYTE(X, i) \
(((X)[(i) / ciL] >> (((i) % ciL) * 8)) & 0xff)
+/* Constants to identify whether a value is public or secret. If a parameter is marked as secret by
+ * this constant, the function must be constant time with respect to the parameter.
+ *
+ * This is only needed for functions with the _optionally_safe postfix. All other functions have
+ * fixed behavior that can't be changed at runtime and are constant time with respect to their
+ * parameters as prescribed by their documentation or by conventions in their module's documentation.
+ *
+ * Parameters should be named X_public where X is the name of the
+ * corresponding input parameter.
+ *
+ * Implementation should always check using
+ * if (X_public == MBEDTLS_MPI_IS_PUBLIC) {
+ * // unsafe path
+ * } else {
+ * // safe path
+ * }
+ * not the other way round, in order to prevent misuse. (This is, if a value
+ * other than the two below is passed, default to the safe path.) */
+#define MBEDTLS_MPI_IS_PUBLIC 0x2a2a2a2a
+#define MBEDTLS_MPI_IS_SECRET 0
+
/** Count leading zero bits in a given integer.
*
* \warning The result is undefined if \p a == 0
@@ -605,6 +626,42 @@ int mbedtls_mpi_core_random(mbedtls_mpi_uint *X,
size_t mbedtls_mpi_core_exp_mod_working_limbs(size_t AN_limbs, size_t E_limbs);
/**
+ * \brief Perform a modular exponentiation with public or secret exponent:
+ * X = A^E mod N, where \p A is already in Montgomery form.
+ *
+ * \warning This function is not constant time with respect to \p E (the exponent).
+ *
+ * \p X may be aliased to \p A, but not to \p RR or \p E, even if \p E_limbs ==
+ * \p AN_limbs.
+ *
+ * \param[out] X The destination MPI, as a little endian array of length
+ * \p AN_limbs.
+ * \param[in] A The base MPI, as a little endian array of length \p AN_limbs.
+ * Must be in Montgomery form.
+ * \param[in] N The modulus, as a little endian array of length \p AN_limbs.
+ * \param AN_limbs The number of limbs in \p X, \p A, \p N, \p RR.
+ * \param[in] E The exponent, as a little endian array of length \p E_limbs.
+ * \param E_limbs The number of limbs in \p E.
+ * \param[in] RR The precomputed residue of 2^{2*biL} modulo N, as a little
+ * endian array of length \p AN_limbs.
+ * \param[in,out] T Temporary storage of at least the number of limbs returned
+ * by `mbedtls_mpi_core_exp_mod_working_limbs()`.
+ * Its initial content is unused and its final content is
+ * indeterminate.
+ * It must not alias or otherwise overlap any of the other
+ * parameters.
+ * It is up to the caller to zeroize \p T when it is no
+ * longer needed, and before freeing it if it was dynamically
+ * allocated.
+ */
+void mbedtls_mpi_core_exp_mod_unsafe(mbedtls_mpi_uint *X,
+ const mbedtls_mpi_uint *A,
+ const mbedtls_mpi_uint *N, size_t AN_limbs,
+ const mbedtls_mpi_uint *E, size_t E_limbs,
+ const mbedtls_mpi_uint *RR,
+ mbedtls_mpi_uint *T);
+
+/**
* \brief Perform a modular exponentiation with secret exponent:
* X = A^E mod N, where \p A is already in Montgomery form.
*
@@ -760,4 +817,17 @@ void mbedtls_mpi_core_from_mont_rep(mbedtls_mpi_uint *X,
mbedtls_mpi_uint mm,
mbedtls_mpi_uint *T);
+/*
+ * Can't define thread local variables with our abstraction layer: do nothing if threading is on.
+ */
+#if defined(MBEDTLS_TEST_HOOKS) && !defined(MBEDTLS_THREADING_C)
+extern int mbedtls_mpi_optionally_safe_codepath;
+
+static inline void mbedtls_mpi_optionally_safe_codepath_reset(void)
+{
+ // Set to a default that is neither MBEDTLS_MPI_IS_PUBLIC nor MBEDTLS_MPI_IS_SECRET
+ mbedtls_mpi_optionally_safe_codepath = MBEDTLS_MPI_IS_PUBLIC + MBEDTLS_MPI_IS_SECRET + 1;
+}
+#endif
+
#endif /* MBEDTLS_BIGNUM_CORE_H */
diff --git a/thirdparty/mbedtls/library/bignum_internal.h b/thirdparty/mbedtls/library/bignum_internal.h
new file mode 100644
index 0000000000..aceaf55ea2
--- /dev/null
+++ b/thirdparty/mbedtls/library/bignum_internal.h
@@ -0,0 +1,50 @@
+/**
+ * \file bignum_internal.h
+ *
+ * \brief Internal-only bignum public-key cryptosystem API.
+ *
+ * This file declares bignum-related functions that are to be used
+ * only from within the Mbed TLS library itself.
+ *
+ */
+/*
+ * Copyright The Mbed TLS Contributors
+ * SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later
+ */
+#ifndef MBEDTLS_BIGNUM_INTERNAL_H
+#define MBEDTLS_BIGNUM_INTERNAL_H
+
+/**
+ * \brief Perform a modular exponentiation: X = A^E mod N
+ *
+ * \warning This function is not constant time with respect to \p E (the exponent).
+ *
+ * \param X The destination MPI. This must point to an initialized MPI.
+ * This must not alias E or N.
+ * \param A The base of the exponentiation.
+ * This must point to an initialized MPI.
+ * \param E The exponent MPI. This must point to an initialized MPI.
+ * \param N The base for the modular reduction. This must point to an
+ * initialized MPI.
+ * \param prec_RR A helper MPI depending solely on \p N which can be used to
+ * speed-up multiple modular exponentiations for the same value
+ * of \p N. This may be \c NULL. If it is not \c NULL, it must
+ * point to an initialized MPI. If it hasn't been used after
+ * the call to mbedtls_mpi_init(), this function will compute
+ * the helper value and store it in \p prec_RR for reuse on
+ * subsequent calls to this function. Otherwise, the function
+ * will assume that \p prec_RR holds the helper value set by a
+ * previous call to mbedtls_mpi_exp_mod(), and reuse it.
+ *
+ * \return \c 0 if successful.
+ * \return #MBEDTLS_ERR_MPI_ALLOC_FAILED if a memory allocation failed.
+ * \return #MBEDTLS_ERR_MPI_BAD_INPUT_DATA if \c N is negative or
+ * even, or if \c E is negative.
+ * \return Another negative error code on different kinds of failures.
+ *
+ */
+int mbedtls_mpi_exp_mod_unsafe(mbedtls_mpi *X, const mbedtls_mpi *A,
+ const mbedtls_mpi *E, const mbedtls_mpi *N,
+ mbedtls_mpi *prec_RR);
+
+#endif /* bignum_internal.h */
diff --git a/thirdparty/mbedtls/library/block_cipher.c b/thirdparty/mbedtls/library/block_cipher.c
index 04cd7fb444..51cdcdf46b 100644
--- a/thirdparty/mbedtls/library/block_cipher.c
+++ b/thirdparty/mbedtls/library/block_cipher.c
@@ -51,6 +51,10 @@ static int mbedtls_cipher_error_from_psa(psa_status_t status)
void mbedtls_block_cipher_free(mbedtls_block_cipher_context_t *ctx)
{
+ if (ctx == NULL) {
+ return;
+ }
+
#if defined(MBEDTLS_BLOCK_CIPHER_SOME_PSA)
if (ctx->engine == MBEDTLS_BLOCK_CIPHER_ENGINE_PSA) {
psa_destroy_key(ctx->psa_key_id);
diff --git a/thirdparty/mbedtls/library/cipher.c b/thirdparty/mbedtls/library/cipher.c
index 0683677eda..7f4c121492 100644
--- a/thirdparty/mbedtls/library/cipher.c
+++ b/thirdparty/mbedtls/library/cipher.c
@@ -849,6 +849,9 @@ static int get_pkcs_padding(unsigned char *input, size_t input_len,
}
padding_len = input[input_len - 1];
+ if (padding_len == 0 || padding_len > input_len) {
+ return MBEDTLS_ERR_CIPHER_INVALID_PADDING;
+ }
*data_len = input_len - padding_len;
mbedtls_ct_condition_t bad = mbedtls_ct_uint_gt(padding_len, input_len);
diff --git a/thirdparty/mbedtls/library/common.h b/thirdparty/mbedtls/library/common.h
index 3936ffdfe1..7bb2674293 100644
--- a/thirdparty/mbedtls/library/common.h
+++ b/thirdparty/mbedtls/library/common.h
@@ -352,17 +352,19 @@ static inline void mbedtls_xor_no_simd(unsigned char *r,
#endif
/* Always provide a static assert macro, so it can be used unconditionally.
- * It will expand to nothing on some systems.
- * Can be used outside functions (but don't add a trailing ';' in that case:
- * the semicolon is included here to avoid triggering -Wextra-semi when
- * MBEDTLS_STATIC_ASSERT() expands to nothing).
- * Can't use the C11-style `defined(static_assert)` on FreeBSD, since it
+ * It does nothing on systems where we don't know how to define a static assert.
+ */
+/* Can't use the C11-style `defined(static_assert)` on FreeBSD, since it
* defines static_assert even with -std=c99, but then complains about it.
*/
#if defined(static_assert) && !defined(__FreeBSD__)
-#define MBEDTLS_STATIC_ASSERT(expr, msg) static_assert(expr, msg);
+#define MBEDTLS_STATIC_ASSERT(expr, msg) static_assert(expr, msg)
#else
-#define MBEDTLS_STATIC_ASSERT(expr, msg)
+/* Make sure `MBEDTLS_STATIC_ASSERT(expr, msg);` is valid both inside and
+ * outside a function. We choose a struct declaration, which can be repeated
+ * any number of times and does not need a matching definition. */
+#define MBEDTLS_STATIC_ASSERT(expr, msg) \
+ struct ISO_C_does_not_allow_extra_semicolon_outside_of_a_function
#endif
#if defined(__has_builtin)
diff --git a/thirdparty/mbedtls/library/ctr_drbg.c b/thirdparty/mbedtls/library/ctr_drbg.c
index 66d9d28c58..b82044eb7d 100644
--- a/thirdparty/mbedtls/library/ctr_drbg.c
+++ b/thirdparty/mbedtls/library/ctr_drbg.c
@@ -26,13 +26,13 @@
#endif
/* Using error translation functions from PSA to MbedTLS */
-#if !defined(MBEDTLS_AES_C)
+#if defined(MBEDTLS_CTR_DRBG_USE_PSA_CRYPTO)
#include "psa_util_internal.h"
#endif
#include "mbedtls/platform.h"
-#if !defined(MBEDTLS_AES_C)
+#if defined(MBEDTLS_CTR_DRBG_USE_PSA_CRYPTO)
static psa_status_t ctr_drbg_setup_psa_context(mbedtls_ctr_drbg_psa_context *psa_ctx,
unsigned char *key, size_t key_len)
{
@@ -73,11 +73,11 @@ static void ctr_drbg_destroy_psa_contex(mbedtls_ctr_drbg_psa_context *psa_ctx)
void mbedtls_ctr_drbg_init(mbedtls_ctr_drbg_context *ctx)
{
memset(ctx, 0, sizeof(mbedtls_ctr_drbg_context));
-#if defined(MBEDTLS_AES_C)
- mbedtls_aes_init(&ctx->aes_ctx);
-#else
+#if defined(MBEDTLS_CTR_DRBG_USE_PSA_CRYPTO)
ctx->psa_ctx.key_id = MBEDTLS_SVC_KEY_ID_INIT;
ctx->psa_ctx.operation = psa_cipher_operation_init();
+#else
+ mbedtls_aes_init(&ctx->aes_ctx);
#endif
/* Indicate that the entropy nonce length is not set explicitly.
* See mbedtls_ctr_drbg_set_nonce_len(). */
@@ -102,10 +102,10 @@ void mbedtls_ctr_drbg_free(mbedtls_ctr_drbg_context *ctx)
mbedtls_mutex_free(&ctx->mutex);
}
#endif
-#if defined(MBEDTLS_AES_C)
- mbedtls_aes_free(&ctx->aes_ctx);
-#else
+#if defined(MBEDTLS_CTR_DRBG_USE_PSA_CRYPTO)
ctr_drbg_destroy_psa_contex(&ctx->psa_ctx);
+#else
+ mbedtls_aes_free(&ctx->aes_ctx);
#endif
mbedtls_platform_zeroize(ctx, sizeof(mbedtls_ctr_drbg_context));
ctx->reseed_interval = MBEDTLS_CTR_DRBG_RESEED_INTERVAL;
@@ -168,15 +168,15 @@ static int block_cipher_df(unsigned char *output,
unsigned char chain[MBEDTLS_CTR_DRBG_BLOCKSIZE];
unsigned char *p, *iv;
int ret = 0;
-#if defined(MBEDTLS_AES_C)
- mbedtls_aes_context aes_ctx;
-#else
+#if defined(MBEDTLS_CTR_DRBG_USE_PSA_CRYPTO)
psa_status_t status;
size_t tmp_len;
mbedtls_ctr_drbg_psa_context psa_ctx;
psa_ctx.key_id = MBEDTLS_SVC_KEY_ID_INIT;
psa_ctx.operation = psa_cipher_operation_init();
+#else
+ mbedtls_aes_context aes_ctx;
#endif
int i, j;
@@ -209,19 +209,19 @@ static int block_cipher_df(unsigned char *output,
key[i] = i;
}
-#if defined(MBEDTLS_AES_C)
+#if defined(MBEDTLS_CTR_DRBG_USE_PSA_CRYPTO)
+ status = ctr_drbg_setup_psa_context(&psa_ctx, key, sizeof(key));
+ if (status != PSA_SUCCESS) {
+ ret = psa_generic_status_to_mbedtls(status);
+ goto exit;
+ }
+#else
mbedtls_aes_init(&aes_ctx);
if ((ret = mbedtls_aes_setkey_enc(&aes_ctx, key,
MBEDTLS_CTR_DRBG_KEYBITS)) != 0) {
goto exit;
}
-#else
- status = ctr_drbg_setup_psa_context(&psa_ctx, key, sizeof(key));
- if (status != PSA_SUCCESS) {
- ret = psa_generic_status_to_mbedtls(status);
- goto exit;
- }
#endif
/*
@@ -238,18 +238,18 @@ static int block_cipher_df(unsigned char *output,
use_len -= (use_len >= MBEDTLS_CTR_DRBG_BLOCKSIZE) ?
MBEDTLS_CTR_DRBG_BLOCKSIZE : use_len;
-#if defined(MBEDTLS_AES_C)
- if ((ret = mbedtls_aes_crypt_ecb(&aes_ctx, MBEDTLS_AES_ENCRYPT,
- chain, chain)) != 0) {
- goto exit;
- }
-#else
+#if defined(MBEDTLS_CTR_DRBG_USE_PSA_CRYPTO)
status = psa_cipher_update(&psa_ctx.operation, chain, MBEDTLS_CTR_DRBG_BLOCKSIZE,
chain, MBEDTLS_CTR_DRBG_BLOCKSIZE, &tmp_len);
if (status != PSA_SUCCESS) {
ret = psa_generic_status_to_mbedtls(status);
goto exit;
}
+#else
+ if ((ret = mbedtls_aes_crypt_ecb(&aes_ctx, MBEDTLS_AES_ENCRYPT,
+ chain, chain)) != 0) {
+ goto exit;
+ }
#endif
}
@@ -264,12 +264,7 @@ static int block_cipher_df(unsigned char *output,
/*
* Do final encryption with reduced data
*/
-#if defined(MBEDTLS_AES_C)
- if ((ret = mbedtls_aes_setkey_enc(&aes_ctx, tmp,
- MBEDTLS_CTR_DRBG_KEYBITS)) != 0) {
- goto exit;
- }
-#else
+#if defined(MBEDTLS_CTR_DRBG_USE_PSA_CRYPTO)
ctr_drbg_destroy_psa_contex(&psa_ctx);
status = ctr_drbg_setup_psa_context(&psa_ctx, tmp, MBEDTLS_CTR_DRBG_KEYSIZE);
@@ -277,32 +272,37 @@ static int block_cipher_df(unsigned char *output,
ret = psa_generic_status_to_mbedtls(status);
goto exit;
}
+#else
+ if ((ret = mbedtls_aes_setkey_enc(&aes_ctx, tmp,
+ MBEDTLS_CTR_DRBG_KEYBITS)) != 0) {
+ goto exit;
+ }
#endif
iv = tmp + MBEDTLS_CTR_DRBG_KEYSIZE;
p = output;
for (j = 0; j < MBEDTLS_CTR_DRBG_SEEDLEN; j += MBEDTLS_CTR_DRBG_BLOCKSIZE) {
-#if defined(MBEDTLS_AES_C)
- if ((ret = mbedtls_aes_crypt_ecb(&aes_ctx, MBEDTLS_AES_ENCRYPT,
- iv, iv)) != 0) {
- goto exit;
- }
-#else
+#if defined(MBEDTLS_CTR_DRBG_USE_PSA_CRYPTO)
status = psa_cipher_update(&psa_ctx.operation, iv, MBEDTLS_CTR_DRBG_BLOCKSIZE,
iv, MBEDTLS_CTR_DRBG_BLOCKSIZE, &tmp_len);
if (status != PSA_SUCCESS) {
ret = psa_generic_status_to_mbedtls(status);
goto exit;
}
+#else
+ if ((ret = mbedtls_aes_crypt_ecb(&aes_ctx, MBEDTLS_AES_ENCRYPT,
+ iv, iv)) != 0) {
+ goto exit;
+ }
#endif
memcpy(p, iv, MBEDTLS_CTR_DRBG_BLOCKSIZE);
p += MBEDTLS_CTR_DRBG_BLOCKSIZE;
}
exit:
-#if defined(MBEDTLS_AES_C)
- mbedtls_aes_free(&aes_ctx);
-#else
+#if defined(MBEDTLS_CTR_DRBG_USE_PSA_CRYPTO)
ctr_drbg_destroy_psa_contex(&psa_ctx);
+#else
+ mbedtls_aes_free(&aes_ctx);
#endif
/*
* tidy up the stack
@@ -336,7 +336,7 @@ static int ctr_drbg_update_internal(mbedtls_ctr_drbg_context *ctx,
unsigned char *p = tmp;
int j;
int ret = 0;
-#if !defined(MBEDTLS_AES_C)
+#if defined(MBEDTLS_CTR_DRBG_USE_PSA_CRYPTO)
psa_status_t status;
size_t tmp_len;
#endif
@@ -352,18 +352,18 @@ static int ctr_drbg_update_internal(mbedtls_ctr_drbg_context *ctx,
/*
* Crypt counter block
*/
-#if defined(MBEDTLS_AES_C)
- if ((ret = mbedtls_aes_crypt_ecb(&ctx->aes_ctx, MBEDTLS_AES_ENCRYPT,
- ctx->counter, p)) != 0) {
- goto exit;
- }
-#else
+#if defined(MBEDTLS_CTR_DRBG_USE_PSA_CRYPTO)
status = psa_cipher_update(&ctx->psa_ctx.operation, ctx->counter, sizeof(ctx->counter),
p, MBEDTLS_CTR_DRBG_BLOCKSIZE, &tmp_len);
if (status != PSA_SUCCESS) {
ret = psa_generic_status_to_mbedtls(status);
goto exit;
}
+#else
+ if ((ret = mbedtls_aes_crypt_ecb(&ctx->aes_ctx, MBEDTLS_AES_ENCRYPT,
+ ctx->counter, p)) != 0) {
+ goto exit;
+ }
#endif
p += MBEDTLS_CTR_DRBG_BLOCKSIZE;
@@ -374,12 +374,7 @@ static int ctr_drbg_update_internal(mbedtls_ctr_drbg_context *ctx,
/*
* Update key and counter
*/
-#if defined(MBEDTLS_AES_C)
- if ((ret = mbedtls_aes_setkey_enc(&ctx->aes_ctx, tmp,
- MBEDTLS_CTR_DRBG_KEYBITS)) != 0) {
- goto exit;
- }
-#else
+#if defined(MBEDTLS_CTR_DRBG_USE_PSA_CRYPTO)
ctr_drbg_destroy_psa_contex(&ctx->psa_ctx);
status = ctr_drbg_setup_psa_context(&ctx->psa_ctx, tmp, MBEDTLS_CTR_DRBG_KEYSIZE);
@@ -387,6 +382,11 @@ static int ctr_drbg_update_internal(mbedtls_ctr_drbg_context *ctx,
ret = psa_generic_status_to_mbedtls(status);
goto exit;
}
+#else
+ if ((ret = mbedtls_aes_setkey_enc(&ctx->aes_ctx, tmp,
+ MBEDTLS_CTR_DRBG_KEYBITS)) != 0) {
+ goto exit;
+ }
#endif
memcpy(ctx->counter, tmp + MBEDTLS_CTR_DRBG_KEYSIZE,
MBEDTLS_CTR_DRBG_BLOCKSIZE);
@@ -564,12 +564,7 @@ int mbedtls_ctr_drbg_seed(mbedtls_ctr_drbg_context *ctx,
good_nonce_len(ctx->entropy_len));
/* Initialize with an empty key. */
-#if defined(MBEDTLS_AES_C)
- if ((ret = mbedtls_aes_setkey_enc(&ctx->aes_ctx, key,
- MBEDTLS_CTR_DRBG_KEYBITS)) != 0) {
- return ret;
- }
-#else
+#if defined(MBEDTLS_CTR_DRBG_USE_PSA_CRYPTO)
psa_status_t status;
status = ctr_drbg_setup_psa_context(&ctx->psa_ctx, key, MBEDTLS_CTR_DRBG_KEYSIZE);
@@ -577,6 +572,11 @@ int mbedtls_ctr_drbg_seed(mbedtls_ctr_drbg_context *ctx,
ret = psa_generic_status_to_mbedtls(status);
return status;
}
+#else
+ if ((ret = mbedtls_aes_setkey_enc(&ctx->aes_ctx, key,
+ MBEDTLS_CTR_DRBG_KEYBITS)) != 0) {
+ return ret;
+ }
#endif
/* Do the initial seeding. */
@@ -655,12 +655,7 @@ int mbedtls_ctr_drbg_random_with_add(void *p_rng,
/*
* Crypt counter block
*/
-#if defined(MBEDTLS_AES_C)
- if ((ret = mbedtls_aes_crypt_ecb(&ctx->aes_ctx, MBEDTLS_AES_ENCRYPT,
- ctx->counter, locals.tmp)) != 0) {
- goto exit;
- }
-#else
+#if defined(MBEDTLS_CTR_DRBG_USE_PSA_CRYPTO)
psa_status_t status;
size_t tmp_len;
@@ -670,6 +665,11 @@ int mbedtls_ctr_drbg_random_with_add(void *p_rng,
ret = psa_generic_status_to_mbedtls(status);
goto exit;
}
+#else
+ if ((ret = mbedtls_aes_crypt_ecb(&ctx->aes_ctx, MBEDTLS_AES_ENCRYPT,
+ ctx->counter, locals.tmp)) != 0) {
+ goto exit;
+ }
#endif
use_len = (output_len > MBEDTLS_CTR_DRBG_BLOCKSIZE)
diff --git a/thirdparty/mbedtls/library/entropy.c b/thirdparty/mbedtls/library/entropy.c
index e3bc8516e2..7dcf067a52 100644
--- a/thirdparty/mbedtls/library/entropy.c
+++ b/thirdparty/mbedtls/library/entropy.c
@@ -61,6 +61,10 @@ void mbedtls_entropy_init(mbedtls_entropy_context *ctx)
void mbedtls_entropy_free(mbedtls_entropy_context *ctx)
{
+ if (ctx == NULL) {
+ return;
+ }
+
/* If the context was already free, don't call free() again.
* This is important for mutexes which don't allow double-free. */
if (ctx->accumulator_started == -1) {
diff --git a/thirdparty/mbedtls/library/entropy_poll.c b/thirdparty/mbedtls/library/entropy_poll.c
index 794ee03a83..611768cd85 100644
--- a/thirdparty/mbedtls/library/entropy_poll.c
+++ b/thirdparty/mbedtls/library/entropy_poll.c
@@ -5,10 +5,12 @@
* SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later
*/
-#if defined(__linux__) || defined(__midipix__) && !defined(_GNU_SOURCE)
+#if defined(__linux__) || defined(__midipix__)
/* Ensure that syscall() is available even when compiling with -std=c99 */
+#if !defined(_GNU_SOURCE)
#define _GNU_SOURCE
#endif
+#endif
#include "common.h"
diff --git a/thirdparty/mbedtls/library/error.c b/thirdparty/mbedtls/library/error.c
index 84b637aeb2..6ad7162ab5 100644
--- a/thirdparty/mbedtls/library/error.c
+++ b/thirdparty/mbedtls/library/error.c
@@ -418,7 +418,7 @@ const char *mbedtls_high_level_strerr(int error_code)
case -(MBEDTLS_ERR_SSL_BAD_CERTIFICATE):
return( "SSL - Processing of the Certificate handshake message failed" );
case -(MBEDTLS_ERR_SSL_RECEIVED_NEW_SESSION_TICKET):
- return( "SSL - * Received NewSessionTicket Post Handshake Message. This error code is experimental and may be changed or removed without notice" );
+ return( "SSL - A TLS 1.3 NewSessionTicket message has been received" );
case -(MBEDTLS_ERR_SSL_CANNOT_READ_EARLY_DATA):
return( "SSL - Not possible to read early data" );
case -(MBEDTLS_ERR_SSL_RECEIVED_EARLY_DATA):
diff --git a/thirdparty/mbedtls/library/lmots.c b/thirdparty/mbedtls/library/lmots.c
index c7091b49e1..c51cb41ece 100644
--- a/thirdparty/mbedtls/library/lmots.c
+++ b/thirdparty/mbedtls/library/lmots.c
@@ -387,6 +387,10 @@ void mbedtls_lmots_public_init(mbedtls_lmots_public_t *ctx)
void mbedtls_lmots_public_free(mbedtls_lmots_public_t *ctx)
{
+ if (ctx == NULL) {
+ return;
+ }
+
mbedtls_platform_zeroize(ctx, sizeof(*ctx));
}
@@ -556,6 +560,10 @@ void mbedtls_lmots_private_init(mbedtls_lmots_private_t *ctx)
void mbedtls_lmots_private_free(mbedtls_lmots_private_t *ctx)
{
+ if (ctx == NULL) {
+ return;
+ }
+
mbedtls_platform_zeroize(ctx,
sizeof(*ctx));
}
diff --git a/thirdparty/mbedtls/library/lms.c b/thirdparty/mbedtls/library/lms.c
index 8d3cae0524..7f7bec068b 100644
--- a/thirdparty/mbedtls/library/lms.c
+++ b/thirdparty/mbedtls/library/lms.c
@@ -229,6 +229,10 @@ void mbedtls_lms_public_init(mbedtls_lms_public_t *ctx)
void mbedtls_lms_public_free(mbedtls_lms_public_t *ctx)
{
+ if (ctx == NULL) {
+ return;
+ }
+
mbedtls_platform_zeroize(ctx, sizeof(*ctx));
}
@@ -528,6 +532,10 @@ void mbedtls_lms_private_init(mbedtls_lms_private_t *ctx)
void mbedtls_lms_private_free(mbedtls_lms_private_t *ctx)
{
+ if (ctx == NULL) {
+ return;
+ }
+
unsigned int idx;
if (ctx->have_private_key) {
diff --git a/thirdparty/mbedtls/library/md.c b/thirdparty/mbedtls/library/md.c
index 12a3ea2374..c95846aa04 100644
--- a/thirdparty/mbedtls/library/md.c
+++ b/thirdparty/mbedtls/library/md.c
@@ -41,7 +41,7 @@
#include "mbedtls/sha512.h"
#include "mbedtls/sha3.h"
-#if defined(MBEDTLS_PSA_CRYPTO_C)
+#if defined(MBEDTLS_PSA_CRYPTO_CLIENT)
#include <psa/crypto.h>
#include "md_psa.h"
#include "psa_util_internal.h"
@@ -761,13 +761,13 @@ mbedtls_md_type_t mbedtls_md_get_type(const mbedtls_md_info_t *md_info)
return md_info->type;
}
-#if defined(MBEDTLS_PSA_CRYPTO_C)
+#if defined(MBEDTLS_PSA_CRYPTO_CLIENT)
int mbedtls_md_error_from_psa(psa_status_t status)
{
return PSA_TO_MBEDTLS_ERR_LIST(status, psa_to_md_errors,
psa_generic_status_to_mbedtls);
}
-#endif /* MBEDTLS_PSA_CRYPTO_C */
+#endif /* MBEDTLS_PSA_CRYPTO_CLIENT */
/************************************************************************
diff --git a/thirdparty/mbedtls/library/net_sockets.c b/thirdparty/mbedtls/library/net_sockets.c
index edec5876ad..ef89a88ef0 100644
--- a/thirdparty/mbedtls/library/net_sockets.c
+++ b/thirdparty/mbedtls/library/net_sockets.c
@@ -683,7 +683,7 @@ void mbedtls_net_close(mbedtls_net_context *ctx)
*/
void mbedtls_net_free(mbedtls_net_context *ctx)
{
- if (ctx->fd == -1) {
+ if (ctx == NULL || ctx->fd == -1) {
return;
}
diff --git a/thirdparty/mbedtls/library/nist_kw.c b/thirdparty/mbedtls/library/nist_kw.c
index f15425b8bd..8faafe43f1 100644
--- a/thirdparty/mbedtls/library/nist_kw.c
+++ b/thirdparty/mbedtls/library/nist_kw.c
@@ -102,6 +102,10 @@ int mbedtls_nist_kw_setkey(mbedtls_nist_kw_context *ctx,
*/
void mbedtls_nist_kw_free(mbedtls_nist_kw_context *ctx)
{
+ if (ctx == NULL) {
+ return;
+ }
+
mbedtls_cipher_free(&ctx->cipher_ctx);
mbedtls_platform_zeroize(ctx, sizeof(mbedtls_nist_kw_context));
}
diff --git a/thirdparty/mbedtls/library/pem.c b/thirdparty/mbedtls/library/pem.c
index 0fee5df43a..0207601456 100644
--- a/thirdparty/mbedtls/library/pem.c
+++ b/thirdparty/mbedtls/library/pem.c
@@ -481,6 +481,10 @@ int mbedtls_pem_read_buffer(mbedtls_pem_context *ctx, const char *header, const
void mbedtls_pem_free(mbedtls_pem_context *ctx)
{
+ if (ctx == NULL) {
+ return;
+ }
+
if (ctx->buf != NULL) {
mbedtls_zeroize_and_free(ctx->buf, ctx->buflen);
}
diff --git a/thirdparty/mbedtls/library/pk.c b/thirdparty/mbedtls/library/pk.c
index 097777f2c0..3fe51ea34f 100644
--- a/thirdparty/mbedtls/library/pk.c
+++ b/thirdparty/mbedtls/library/pk.c
@@ -868,7 +868,6 @@ static int copy_from_psa(mbedtls_svc_key_id_t key_id,
psa_status_t status;
psa_key_attributes_t key_attr = PSA_KEY_ATTRIBUTES_INIT;
psa_key_type_t key_type;
- psa_algorithm_t alg_type;
size_t key_bits;
/* Use a buffer size large enough to contain either a key pair or public key. */
unsigned char exp_key[PSA_EXPORT_KEY_PAIR_OR_PUBLIC_MAX_SIZE];
@@ -899,7 +898,6 @@ static int copy_from_psa(mbedtls_svc_key_id_t key_id,
key_type = PSA_KEY_TYPE_PUBLIC_KEY_OF_KEY_PAIR(key_type);
}
key_bits = psa_get_key_bits(&key_attr);
- alg_type = psa_get_key_algorithm(&key_attr);
#if defined(MBEDTLS_RSA_C)
if ((key_type == PSA_KEY_TYPE_RSA_KEY_PAIR) ||
@@ -919,6 +917,7 @@ static int copy_from_psa(mbedtls_svc_key_id_t key_id,
goto exit;
}
+ psa_algorithm_t alg_type = psa_get_key_algorithm(&key_attr);
mbedtls_md_type_t md_type = MBEDTLS_MD_NONE;
if (PSA_ALG_GET_HASH(alg_type) != PSA_ALG_ANY_HASH) {
md_type = mbedtls_md_type_from_psa_alg(alg_type);
@@ -968,6 +967,7 @@ static int copy_from_psa(mbedtls_svc_key_id_t key_id,
} else
#endif /* MBEDTLS_PK_HAVE_ECC_KEYS */
{
+ (void) key_bits;
return MBEDTLS_ERR_PK_BAD_INPUT_DATA;
}
@@ -1327,43 +1327,19 @@ int mbedtls_pk_sign_ext(mbedtls_pk_type_t pk_type,
}
if (mbedtls_pk_get_type(ctx) == MBEDTLS_PK_OPAQUE) {
- psa_key_attributes_t key_attr = PSA_KEY_ATTRIBUTES_INIT;
- psa_algorithm_t psa_alg, sign_alg;
-#if defined(MBEDTLS_PSA_CRYPTO_C)
- psa_algorithm_t psa_enrollment_alg;
-#endif /* MBEDTLS_PSA_CRYPTO_C */
psa_status_t status;
- status = psa_get_key_attributes(ctx->priv_id, &key_attr);
- if (status != PSA_SUCCESS) {
- return PSA_PK_RSA_TO_MBEDTLS_ERR(status);
- }
- psa_alg = psa_get_key_algorithm(&key_attr);
-#if defined(MBEDTLS_PSA_CRYPTO_C)
- psa_enrollment_alg = psa_get_key_enrollment_algorithm(&key_attr);
-#endif /* MBEDTLS_PSA_CRYPTO_C */
- psa_reset_key_attributes(&key_attr);
-
- /* Since we're PK type is MBEDTLS_PK_RSASSA_PSS at least one between
- * alg and enrollment alg should be of type RSA_PSS. */
- if (PSA_ALG_IS_RSA_PSS(psa_alg)) {
- sign_alg = psa_alg;
- }
-#if defined(MBEDTLS_PSA_CRYPTO_C)
- else if (PSA_ALG_IS_RSA_PSS(psa_enrollment_alg)) {
- sign_alg = psa_enrollment_alg;
- }
-#endif /* MBEDTLS_PSA_CRYPTO_C */
- else {
- /* The opaque key has no RSA PSS algorithm associated. */
- return MBEDTLS_ERR_PK_BAD_INPUT_DATA;
- }
- /* Adjust the hashing algorithm. */
- sign_alg = (sign_alg & ~PSA_ALG_HASH_MASK) | PSA_ALG_GET_HASH(psa_md_alg);
-
- status = psa_sign_hash(ctx->priv_id, sign_alg,
+ /* PSA_ALG_RSA_PSS() behaves the same as PSA_ALG_RSA_PSS_ANY_SALT() when
+ * performing a signature, but they are encoded differently. Instead of
+ * extracting the proper one from the wrapped key policy, just try both. */
+ status = psa_sign_hash(ctx->priv_id, PSA_ALG_RSA_PSS(psa_md_alg),
hash, hash_len,
sig, sig_size, sig_len);
+ if (status == PSA_ERROR_NOT_PERMITTED) {
+ status = psa_sign_hash(ctx->priv_id, PSA_ALG_RSA_PSS_ANY_SALT(psa_md_alg),
+ hash, hash_len,
+ sig, sig_size, sig_len);
+ }
return PSA_PK_RSA_TO_MBEDTLS_ERR(status);
}
diff --git a/thirdparty/mbedtls/library/platform_util.c b/thirdparty/mbedtls/library/platform_util.c
index 0741bf575e..19ef07aead 100644
--- a/thirdparty/mbedtls/library/platform_util.c
+++ b/thirdparty/mbedtls/library/platform_util.c
@@ -149,7 +149,7 @@ void mbedtls_zeroize_and_free(void *buf, size_t len)
#include <time.h>
#if !defined(_WIN32) && (defined(unix) || \
defined(__unix) || defined(__unix__) || (defined(__APPLE__) && \
- defined(__MACH__)) || defined__midipix__)
+ defined(__MACH__)) || defined(__midipix__))
#include <unistd.h>
#endif /* !_WIN32 && (unix || __unix || __unix__ ||
* (__APPLE__ && __MACH__) || __midipix__) */
diff --git a/thirdparty/mbedtls/library/psa_crypto_core.h b/thirdparty/mbedtls/library/psa_crypto_core.h
index c059162efe..21e7559f01 100644
--- a/thirdparty/mbedtls/library/psa_crypto_core.h
+++ b/thirdparty/mbedtls/library/psa_crypto_core.h
@@ -59,6 +59,8 @@ typedef enum {
* and metadata for one key.
*/
typedef struct {
+ /* This field is accessed in a lot of places. Putting it first
+ * reduces the code size. */
psa_key_attributes_t attr;
/*
@@ -78,35 +80,77 @@ typedef struct {
* slots that are in a suitable state for the function.
* For example, psa_get_and_lock_key_slot_in_memory, which finds a slot
* containing a given key ID, will only check slots whose state variable is
- * PSA_SLOT_FULL. */
+ * PSA_SLOT_FULL.
+ */
psa_key_slot_state_t state;
- /*
- * Number of functions registered as reading the material in the key slot.
- *
- * Library functions must not write directly to registered_readers
- *
- * A function must call psa_register_read(slot) before reading the current
- * contents of the slot for an operation.
- * They then must call psa_unregister_read(slot) once they have finished
- * reading the current contents of the slot. If the key slot mutex is not
- * held (when mutexes are enabled), this call must be done via a call to
- * psa_unregister_read_under_mutex(slot).
- * A function must call psa_key_slot_has_readers(slot) to check if
- * the slot is in use for reading.
+#if defined(MBEDTLS_PSA_KEY_STORE_DYNAMIC)
+ /* The index of the slice containing this slot.
+ * This field must be filled if the slot contains a key
+ * (including keys being created or destroyed), and can be either
+ * filled or 0 when the slot is free.
*
- * This counter is used to prevent resetting the key slot while the library
- * may access it. For example, such control is needed in the following
- * scenarios:
- * . In case of key slot starvation, all key slots contain the description
- * of a key, and the library asks for the description of a persistent
- * key not present in the key slots, the key slots currently accessed by
- * the library cannot be reclaimed to free a key slot to load the
- * persistent key.
- * . In case of a multi-threaded application where one thread asks to close
- * or purge or destroy a key while it is in use by the library through
- * another thread. */
- size_t registered_readers;
+ * In most cases, the slice index can be deduced from the key identifer.
+ * We keep it in a separate field for robustness (it reduces the chance
+ * that a coding mistake in the key store will result in accessing the
+ * wrong slice), and also so that it's available even on code paths
+ * during creation or destruction where the key identifier might not be
+ * filled in.
+ * */
+ uint8_t slice_index;
+#endif /* MBEDTLS_PSA_KEY_STORE_DYNAMIC */
+
+ union {
+ struct {
+ /* The index of the next slot in the free list for this
+ * slice, relative * to the next array element.
+ *
+ * That is, 0 means the next slot, 1 means the next slot
+ * but one, etc. -1 would mean the slot itself. -2 means
+ * the previous slot, etc.
+ *
+ * If this is beyond the array length, the free list ends with the
+ * current element.
+ *
+ * The reason for this strange encoding is that 0 means the next
+ * element. This way, when we allocate a slice and initialize it
+ * to all-zero, the slice is ready for use, with a free list that
+ * consists of all the slots in order.
+ */
+ int32_t next_free_relative_to_next;
+ } free;
+
+ struct {
+ /*
+ * Number of functions registered as reading the material in the key slot.
+ *
+ * Library functions must not write directly to registered_readers
+ *
+ * A function must call psa_register_read(slot) before reading
+ * the current contents of the slot for an operation.
+ * They then must call psa_unregister_read(slot) once they have
+ * finished reading the current contents of the slot. If the key
+ * slot mutex is not held (when mutexes are enabled), this call
+ * must be done via a call to
+ * psa_unregister_read_under_mutex(slot).
+ * A function must call psa_key_slot_has_readers(slot) to check if
+ * the slot is in use for reading.
+ *
+ * This counter is used to prevent resetting the key slot while
+ * the library may access it. For example, such control is needed
+ * in the following scenarios:
+ * . In case of key slot starvation, all key slots contain the
+ * description of a key, and the library asks for the
+ * description of a persistent key not present in the
+ * key slots, the key slots currently accessed by the
+ * library cannot be reclaimed to free a key slot to load
+ * the persistent key.
+ * . In case of a multi-threaded application where one thread
+ * asks to close or purge or destroy a key while it is in use
+ * by the library through another thread. */
+ size_t registered_readers;
+ } occupied;
+ } var;
/* Dynamically allocated key data buffer.
* Format as specified in psa_export_key(). */
@@ -169,7 +213,7 @@ typedef struct {
*/
static inline int psa_key_slot_has_readers(const psa_key_slot_t *slot)
{
- return slot->registered_readers > 0;
+ return slot->var.occupied.registered_readers > 0;
}
#if defined(MBEDTLS_PSA_CRYPTO_SE_C)
@@ -343,19 +387,18 @@ psa_status_t psa_export_public_key_internal(
const uint8_t *key_buffer, size_t key_buffer_size,
uint8_t *data, size_t data_size, size_t *data_length);
-/** Whether a key production parameters structure is the default.
+/** Whether a key custom production parameters structure is the default.
*
- * Calls to a key generation driver with non-default production parameters
+ * Calls to a key generation driver with non-default custom production parameters
* require a driver supporting custom production parameters.
*
- * \param[in] params The key production parameters to check.
- * \param params_data_length Size of `params->data` in bytes.
+ * \param[in] custom The key custom production parameters to check.
+ * \param custom_data_length Size of the associated variable-length data
+ * in bytes.
*/
-#ifndef __cplusplus
-int psa_key_production_parameters_are_default(
- const psa_key_production_parameters_t *params,
- size_t params_data_length);
-#endif
+int psa_custom_key_parameters_are_default(
+ const psa_custom_key_parameters_t *custom,
+ size_t custom_data_length);
/**
* \brief Generate a key.
@@ -364,9 +407,9 @@ int psa_key_production_parameters_are_default(
* entry point.
*
* \param[in] attributes The attributes for the key to generate.
- * \param[in] params The production parameters from
- * psa_generate_key_ext().
- * \param params_data_length The size of `params->data` in bytes.
+ * \param[in] custom Custom parameters for the key generation.
+ * \param[in] custom_data Variable-length data associated with \c custom.
+ * \param custom_data_length Length of `custom_data` in bytes.
* \param[out] key_buffer Buffer where the key data is to be written.
* \param[in] key_buffer_size Size of \p key_buffer in bytes.
* \param[out] key_buffer_length On success, the number of bytes written in
@@ -380,14 +423,13 @@ int psa_key_production_parameters_are_default(
* \retval #PSA_ERROR_BUFFER_TOO_SMALL
* The size of \p key_buffer is too small.
*/
-#ifndef __cplusplus
psa_status_t psa_generate_key_internal(const psa_key_attributes_t *attributes,
- const psa_key_production_parameters_t *params,
- size_t params_data_length,
+ const psa_custom_key_parameters_t *custom,
+ const uint8_t *custom_data,
+ size_t custom_data_length,
uint8_t *key_buffer,
size_t key_buffer_size,
size_t *key_buffer_length);
-#endif
/** Sign a message with a private key. For hash-and-sign algorithms,
* this includes the hashing step.
diff --git a/thirdparty/mbedtls/library/psa_crypto_driver_wrappers.h b/thirdparty/mbedtls/library/psa_crypto_driver_wrappers.h
index 6919971aca..b901557208 100644
--- a/thirdparty/mbedtls/library/psa_crypto_driver_wrappers.h
+++ b/thirdparty/mbedtls/library/psa_crypto_driver_wrappers.h
@@ -728,10 +728,10 @@ static inline psa_status_t psa_driver_wrapper_get_key_buffer_size_from_key_data(
}
}
-#ifndef __cplusplus
static inline psa_status_t psa_driver_wrapper_generate_key(
const psa_key_attributes_t *attributes,
- const psa_key_production_parameters_t *params, size_t params_data_length,
+ const psa_custom_key_parameters_t *custom,
+ const uint8_t *custom_data, size_t custom_data_length,
uint8_t *key_buffer, size_t key_buffer_size, size_t *key_buffer_length )
{
psa_status_t status = PSA_ERROR_CORRUPTION_DETECTED;
@@ -740,7 +740,7 @@ static inline psa_status_t psa_driver_wrapper_generate_key(
#if defined(PSA_WANT_KEY_TYPE_RSA_KEY_PAIR_GENERATE)
int is_default_production =
- psa_key_production_parameters_are_default(params, params_data_length);
+ psa_custom_key_parameters_are_default(custom, custom_data_length);
if( location != PSA_KEY_LOCATION_LOCAL_STORAGE && !is_default_production )
{
/* We don't support passing custom production parameters
@@ -811,7 +811,7 @@ static inline psa_status_t psa_driver_wrapper_generate_key(
/* Software fallback */
status = psa_generate_key_internal(
- attributes, params, params_data_length,
+ attributes, custom, custom_data, custom_data_length,
key_buffer, key_buffer_size, key_buffer_length );
break;
@@ -833,7 +833,6 @@ static inline psa_status_t psa_driver_wrapper_generate_key(
return( status );
}
-#endif
static inline psa_status_t psa_driver_wrapper_import_key(
const psa_key_attributes_t *attributes,
diff --git a/thirdparty/mbedtls/library/psa_crypto_random_impl.h b/thirdparty/mbedtls/library/psa_crypto_random_impl.h
index 533fb2e940..5b5163111b 100644
--- a/thirdparty/mbedtls/library/psa_crypto_random_impl.h
+++ b/thirdparty/mbedtls/library/psa_crypto_random_impl.h
@@ -21,13 +21,10 @@ typedef mbedtls_psa_external_random_context_t mbedtls_psa_random_context_t;
#include "mbedtls/entropy.h"
/* Choose a DRBG based on configuration and availability */
-#if defined(MBEDTLS_PSA_HMAC_DRBG_MD_TYPE)
-
-#include "mbedtls/hmac_drbg.h"
-
-#elif defined(MBEDTLS_CTR_DRBG_C)
+#if defined(MBEDTLS_CTR_DRBG_C)
#include "mbedtls/ctr_drbg.h"
+#undef MBEDTLS_PSA_HMAC_DRBG_MD_TYPE
#elif defined(MBEDTLS_HMAC_DRBG_C)
@@ -49,17 +46,11 @@ typedef mbedtls_psa_external_random_context_t mbedtls_psa_random_context_t;
#error "No hash algorithm available for HMAC_DBRG."
#endif
-#else /* !MBEDTLS_PSA_HMAC_DRBG_MD_TYPE && !MBEDTLS_CTR_DRBG_C && !MBEDTLS_HMAC_DRBG_C*/
+#else /* !MBEDTLS_CTR_DRBG_C && !MBEDTLS_HMAC_DRBG_C*/
#error "No DRBG module available for the psa_crypto module."
-#endif /* !MBEDTLS_PSA_HMAC_DRBG_MD_TYPE && !MBEDTLS_CTR_DRBG_C && !MBEDTLS_HMAC_DRBG_C*/
-
-#if defined(MBEDTLS_CTR_DRBG_C)
-#include "mbedtls/ctr_drbg.h"
-#elif defined(MBEDTLS_HMAC_DRBG_C)
-#include "mbedtls/hmac_drbg.h"
-#endif /* !MBEDTLS_CTR_DRBG_C && !MBEDTLS_HMAC_DRBG_C */
+#endif /* !MBEDTLS_CTR_DRBG_C && !MBEDTLS_HMAC_DRBG_C*/
/* The maximum number of bytes that mbedtls_psa_get_random() is expected to return. */
#if defined(MBEDTLS_CTR_DRBG_C)
diff --git a/thirdparty/mbedtls/library/psa_crypto_rsa.h b/thirdparty/mbedtls/library/psa_crypto_rsa.h
index 6d695ddf50..1a780006a9 100644
--- a/thirdparty/mbedtls/library/psa_crypto_rsa.h
+++ b/thirdparty/mbedtls/library/psa_crypto_rsa.h
@@ -105,17 +105,11 @@ psa_status_t mbedtls_psa_rsa_export_public_key(
/**
* \brief Generate an RSA key.
*
- * \note The signature of the function is that of a PSA driver generate_key
- * entry point.
- *
* \param[in] attributes The attributes for the RSA key to generate.
- * \param[in] params Production parameters for the key
- * generation. This function only uses
- * `params->data`,
- * which contains the public exponent.
+ * \param[in] custom_data The public exponent to use.
* This can be a null pointer if
* \c params_data_length is 0.
- * \param params_data_length Length of `params->data` in bytes.
+ * \param custom_data_length Length of \p custom_data in bytes.
* This can be 0, in which case the
* public exponent will be 65537.
* \param[out] key_buffer Buffer where the key data is to be written.
@@ -130,12 +124,10 @@ psa_status_t mbedtls_psa_rsa_export_public_key(
* \retval #PSA_ERROR_BUFFER_TOO_SMALL
* The size of \p key_buffer is too small.
*/
-#ifndef __cplusplus
psa_status_t mbedtls_psa_rsa_generate_key(
const psa_key_attributes_t *attributes,
- const psa_key_production_parameters_t *params, size_t params_data_length,
+ const uint8_t *custom_data, size_t custom_data_length,
uint8_t *key_buffer, size_t key_buffer_size, size_t *key_buffer_length);
-#endif
/** Sign an already-calculated hash with an RSA private key.
*
diff --git a/thirdparty/mbedtls/library/psa_crypto_slot_management.h b/thirdparty/mbedtls/library/psa_crypto_slot_management.h
index bcfc9d8adc..af1208e3ae 100644
--- a/thirdparty/mbedtls/library/psa_crypto_slot_management.h
+++ b/thirdparty/mbedtls/library/psa_crypto_slot_management.h
@@ -15,20 +15,26 @@
/** Range of volatile key identifiers.
*
- * The last #MBEDTLS_PSA_KEY_SLOT_COUNT identifiers of the implementation
+ * The first #MBEDTLS_PSA_KEY_SLOT_COUNT identifiers of the implementation
* range of key identifiers are reserved for volatile key identifiers.
- * A volatile key identifier is equal to #PSA_KEY_ID_VOLATILE_MIN plus the
- * index of the key slot containing the volatile key definition.
+ *
+ * If \c id is a a volatile key identifier, #PSA_KEY_ID_VOLATILE_MIN - \c id
+ * indicates the key slot containing the volatile key definition. See
+ * psa_crypto_slot_management.c for details.
*/
/** The minimum value for a volatile key identifier.
*/
-#define PSA_KEY_ID_VOLATILE_MIN (PSA_KEY_ID_VENDOR_MAX - \
- MBEDTLS_PSA_KEY_SLOT_COUNT + 1)
+#define PSA_KEY_ID_VOLATILE_MIN PSA_KEY_ID_VENDOR_MIN
/** The maximum value for a volatile key identifier.
*/
-#define PSA_KEY_ID_VOLATILE_MAX PSA_KEY_ID_VENDOR_MAX
+#if defined(MBEDTLS_PSA_KEY_STORE_DYNAMIC)
+#define PSA_KEY_ID_VOLATILE_MAX (MBEDTLS_PSA_KEY_ID_BUILTIN_MIN - 1)
+#else /* MBEDTLS_PSA_KEY_STORE_DYNAMIC */
+#define PSA_KEY_ID_VOLATILE_MAX \
+ (PSA_KEY_ID_VOLATILE_MIN + MBEDTLS_PSA_KEY_SLOT_COUNT - 1)
+#endif /* MBEDTLS_PSA_KEY_STORE_DYNAMIC */
/** Test whether a key identifier is a volatile key identifier.
*
@@ -58,6 +64,9 @@ static inline int psa_key_id_is_volatile(psa_key_id_t key_id)
* It is the responsibility of the caller to call psa_unregister_read(slot)
* when they have finished reading the contents of the slot.
*
+ * On failure, `*p_slot` is set to NULL. This ensures that it is always valid
+ * to call psa_unregister_read on the returned slot.
+ *
* \param key Key identifier to query.
* \param[out] p_slot On success, `*p_slot` contains a pointer to the
* key slot containing the description of the key
@@ -91,6 +100,24 @@ psa_status_t psa_get_and_lock_key_slot(mbedtls_svc_key_id_t key,
*/
psa_status_t psa_initialize_key_slots(void);
+#if defined(MBEDTLS_TEST_HOOKS) && defined(MBEDTLS_PSA_KEY_STORE_DYNAMIC)
+/* Allow test code to customize the key slice length. We use this in tests
+ * that exhaust the key store to reach a full key store in reasonable time
+ * and memory.
+ *
+ * The length of each slice must be between 1 and
+ * (1 << KEY_ID_SLOT_INDEX_WIDTH) inclusive.
+ *
+ * The length for a given slice index must not change while
+ * the key store is initialized.
+ */
+extern size_t (*mbedtls_test_hook_psa_volatile_key_slice_length)(
+ size_t slice_idx);
+
+/* The number of volatile key slices. */
+size_t psa_key_slot_volatile_slice_count(void);
+#endif
+
/** Delete all data from key slots in memory.
* This function is not thread safe, it wipes every key slot regardless of
* state and reader count. It should only be called when no slot is in use.
@@ -110,13 +137,22 @@ void psa_wipe_all_key_slots(void);
* If multi-threading is enabled, the caller must hold the
* global key slot mutex.
*
- * \param[out] volatile_key_id On success, volatile key identifier
- * associated to the returned slot.
+ * \param[out] volatile_key_id - If null, reserve a cache slot for
+ * a persistent or built-in key.
+ * - If non-null, allocate a slot for
+ * a volatile key. On success,
+ * \p *volatile_key_id is the
+ * identifier corresponding to the
+ * returned slot. It is the caller's
+ * responsibility to set this key identifier
+ * in the attributes.
* \param[out] p_slot On success, a pointer to the slot.
*
* \retval #PSA_SUCCESS \emptydescription
* \retval #PSA_ERROR_INSUFFICIENT_MEMORY
* There were no free key slots.
+ * When #MBEDTLS_PSA_KEY_STORE_DYNAMIC is enabled, there was not
+ * enough memory to allocate more slots.
* \retval #PSA_ERROR_BAD_STATE \emptydescription
* \retval #PSA_ERROR_CORRUPTION_DETECTED
* This function attempted to operate on a key slot which was in an
@@ -125,6 +161,29 @@ void psa_wipe_all_key_slots(void);
psa_status_t psa_reserve_free_key_slot(psa_key_id_t *volatile_key_id,
psa_key_slot_t **p_slot);
+#if defined(MBEDTLS_PSA_KEY_STORE_DYNAMIC)
+/** Return a key slot to the free list.
+ *
+ * Call this function when a slot obtained from psa_reserve_free_key_slot()
+ * is no longer in use.
+ *
+ * If multi-threading is enabled, the caller must hold the
+ * global key slot mutex.
+ *
+ * \param slice_idx The slice containing the slot.
+ * This is `slot->slice_index` when the slot
+ * is obtained from psa_reserve_free_key_slot().
+ * \param slot The key slot.
+ *
+ * \retval #PSA_SUCCESS \emptydescription
+ * \retval #PSA_ERROR_CORRUPTION_DETECTED
+ * This function attempted to operate on a key slot which was in an
+ * unexpected state.
+ */
+psa_status_t psa_free_key_slot(size_t slice_idx,
+ psa_key_slot_t *slot);
+#endif /* MBEDTLS_PSA_KEY_STORE_DYNAMIC */
+
/** Change the state of a key slot.
*
* This function changes the state of the key slot from expected_state to
@@ -171,10 +230,10 @@ static inline psa_status_t psa_key_slot_state_transition(
static inline psa_status_t psa_register_read(psa_key_slot_t *slot)
{
if ((slot->state != PSA_SLOT_FULL) ||
- (slot->registered_readers >= SIZE_MAX)) {
+ (slot->var.occupied.registered_readers >= SIZE_MAX)) {
return PSA_ERROR_CORRUPTION_DETECTED;
}
- slot->registered_readers++;
+ slot->var.occupied.registered_readers++;
return PSA_SUCCESS;
}
diff --git a/thirdparty/mbedtls/library/rsa.c b/thirdparty/mbedtls/library/rsa.c
index 7eb4a259ea..557faaf363 100644
--- a/thirdparty/mbedtls/library/rsa.c
+++ b/thirdparty/mbedtls/library/rsa.c
@@ -29,6 +29,7 @@
#include "mbedtls/rsa.h"
#include "bignum_core.h"
+#include "bignum_internal.h"
#include "rsa_alt_helpers.h"
#include "rsa_internal.h"
#include "mbedtls/oid.h"
@@ -1259,7 +1260,7 @@ int mbedtls_rsa_public(mbedtls_rsa_context *ctx,
}
olen = ctx->len;
- MBEDTLS_MPI_CHK(mbedtls_mpi_exp_mod(&T, &T, &ctx->E, &ctx->N, &ctx->RN));
+ MBEDTLS_MPI_CHK(mbedtls_mpi_exp_mod_unsafe(&T, &T, &ctx->E, &ctx->N, &ctx->RN));
MBEDTLS_MPI_CHK(mbedtls_mpi_write_binary(&T, output, olen));
cleanup:
diff --git a/thirdparty/mbedtls/library/sha256.c b/thirdparty/mbedtls/library/sha256.c
index 87889817a4..159acccaeb 100644
--- a/thirdparty/mbedtls/library/sha256.c
+++ b/thirdparty/mbedtls/library/sha256.c
@@ -44,7 +44,9 @@
#endif /* defined(__clang__) && (__clang_major__ >= 4) */
/* Ensure that SIG_SETMASK is defined when -std=c99 is used. */
+#if !defined(_GNU_SOURCE)
#define _GNU_SOURCE
+#endif
#include "common.h"
@@ -150,7 +152,9 @@ static int mbedtls_a64_crypto_sha256_determine_support(void)
return 1;
}
#elif defined(MBEDTLS_PLATFORM_IS_WINDOWS_ON_ARM64)
+#ifndef WIN32_LEAN_AND_MEAN
#define WIN32_LEAN_AND_MEAN
+#endif
#include <Windows.h>
#include <processthreadsapi.h>
diff --git a/thirdparty/mbedtls/library/ssl_cookie.c b/thirdparty/mbedtls/library/ssl_cookie.c
index 2772cac4be..acc9e8c080 100644
--- a/thirdparty/mbedtls/library/ssl_cookie.c
+++ b/thirdparty/mbedtls/library/ssl_cookie.c
@@ -84,6 +84,10 @@ void mbedtls_ssl_cookie_set_timeout(mbedtls_ssl_cookie_ctx *ctx, unsigned long d
void mbedtls_ssl_cookie_free(mbedtls_ssl_cookie_ctx *ctx)
{
+ if (ctx == NULL) {
+ return;
+ }
+
#if defined(MBEDTLS_USE_PSA_CRYPTO)
psa_destroy_key(ctx->psa_hmac_key);
#else
diff --git a/thirdparty/mbedtls/library/ssl_debug_helpers_generated.c b/thirdparty/mbedtls/library/ssl_debug_helpers_generated.c
index f8b4448c86..734c417b8b 100644
--- a/thirdparty/mbedtls/library/ssl_debug_helpers_generated.c
+++ b/thirdparty/mbedtls/library/ssl_debug_helpers_generated.c
@@ -60,7 +60,7 @@ const char *mbedtls_ssl_named_group_to_str( uint16_t in )
return "ffdhe8192";
};
- return "UNKOWN";
+ return "UNKNOWN";
}
const char *mbedtls_ssl_sig_alg_to_str( uint16_t in )
{
diff --git a/thirdparty/mbedtls/library/ssl_misc.h b/thirdparty/mbedtls/library/ssl_misc.h
index a8807f67c6..98668798a8 100644
--- a/thirdparty/mbedtls/library/ssl_misc.h
+++ b/thirdparty/mbedtls/library/ssl_misc.h
@@ -1507,7 +1507,7 @@ int mbedtls_ssl_psk_derive_premaster(mbedtls_ssl_context *ssl,
#endif /* MBEDTLS_KEY_EXCHANGE_SOME_PSK_ENABLED */
#if defined(MBEDTLS_SSL_HANDSHAKE_WITH_PSK_ENABLED)
-#if defined(MBEDTLS_SSL_CLI_C)
+#if defined(MBEDTLS_SSL_CLI_C) || defined(MBEDTLS_SSL_SRV_C)
MBEDTLS_CHECK_RETURN_CRITICAL
int mbedtls_ssl_conf_has_static_psk(mbedtls_ssl_config const *conf);
#endif
@@ -1674,18 +1674,53 @@ static inline mbedtls_x509_crt *mbedtls_ssl_own_cert(mbedtls_ssl_context *ssl)
}
/*
- * Check usage of a certificate wrt extensions:
- * keyUsage, extendedKeyUsage (later), and nSCertType (later).
+ * Verify a certificate.
+ *
+ * [in/out] ssl: misc. things read
+ * ssl->session_negotiate->verify_result updated
+ * [in] authmode: one of MBEDTLS_SSL_VERIFY_{NONE,OPTIONAL,REQUIRED}
+ * [in] chain: the certificate chain to verify (ie the peer's chain)
+ * [in] ciphersuite_info: For TLS 1.2, this session's ciphersuite;
+ * for TLS 1.3, may be left NULL.
+ * [in] rs_ctx: restart context if restartable ECC is in use;
+ * leave NULL for no restartable behaviour.
+ *
+ * Return:
+ * - 0 if the handshake should continue. Depending on the
+ * authmode it means:
+ * - REQUIRED: the certificate was found to be valid, trusted & acceptable.
+ * ssl->session_negotiate->verify_result is 0.
+ * - OPTIONAL: the certificate may or may not be acceptable, but
+ * ssl->session_negotiate->verify_result was updated with the result.
+ * - NONE: the certificate wasn't even checked.
+ * - MBEDTLS_ERR_X509_CERT_VERIFY_FAILED or MBEDTLS_ERR_SSL_BAD_CERTIFICATE if
+ * the certificate was found to be invalid/untrusted/unacceptable and the
+ * handshake should be aborted (can only happen with REQUIRED).
+ * - another error code if another error happened (out-of-memory, etc.)
+ */
+MBEDTLS_CHECK_RETURN_CRITICAL
+int mbedtls_ssl_verify_certificate(mbedtls_ssl_context *ssl,
+ int authmode,
+ mbedtls_x509_crt *chain,
+ const mbedtls_ssl_ciphersuite_t *ciphersuite_info,
+ void *rs_ctx);
+
+/*
+ * Check usage of a certificate wrt usage extensions:
+ * keyUsage and extendedKeyUsage.
+ * (Note: nSCertType is deprecated and not standard, we don't check it.)
*
- * Warning: cert_endpoint is the endpoint of the cert (ie, of our peer when we
- * check a cert we received from them)!
+ * Note: if tls_version is 1.3, ciphersuite is ignored and can be NULL.
+ *
+ * Note: recv_endpoint is the receiver's endpoint.
*
* Return 0 if everything is OK, -1 if not.
*/
MBEDTLS_CHECK_RETURN_CRITICAL
int mbedtls_ssl_check_cert_usage(const mbedtls_x509_crt *cert,
const mbedtls_ssl_ciphersuite_t *ciphersuite,
- int cert_endpoint,
+ int recv_endpoint,
+ mbedtls_ssl_protocol_version tls_version,
uint32_t *flags);
#endif /* MBEDTLS_X509_CRT_PARSE_C */
@@ -1891,6 +1926,26 @@ static inline int mbedtls_ssl_conf_is_hybrid_tls12_tls13(const mbedtls_ssl_confi
#endif /* MBEDTLS_SSL_PROTO_TLS1_2 && MBEDTLS_SSL_PROTO_TLS1_3 */
#if defined(MBEDTLS_SSL_PROTO_TLS1_3)
+
+/** \brief Initialize the PSA crypto subsystem if necessary.
+ *
+ * Call this function before doing any cryptography in a TLS 1.3 handshake.
+ *
+ * This is necessary in Mbed TLS 3.x for backward compatibility.
+ * Up to Mbed TLS 3.5, in the default configuration, you could perform
+ * a TLS connection with default parameters without having called
+ * psa_crypto_init(), since the TLS layer only supported TLS 1.2 and
+ * did not use PSA crypto. (TLS 1.2 only uses PSA crypto if
+ * MBEDTLS_USE_PSA_CRYPTO is enabled, which is not the case in the default
+ * configuration.) Starting with Mbed TLS 3.6.0, TLS 1.3 is enabled
+ * by default, and the TLS 1.3 layer uses PSA crypto. This means that
+ * applications that are not otherwise using PSA crypto and that worked
+ * with Mbed TLS 3.5 started failing in TLS 3.6.0 if they connected to
+ * a peer that supports TLS 1.3. See
+ * https://github.com/Mbed-TLS/mbedtls/issues/9072
+ */
+int mbedtls_ssl_tls13_crypto_init(mbedtls_ssl_context *ssl);
+
extern const uint8_t mbedtls_ssl_tls13_hello_retry_request_magic[
MBEDTLS_SERVER_HELLO_RANDOM_LEN];
MBEDTLS_CHECK_RETURN_CRITICAL
@@ -2914,8 +2969,37 @@ static inline void mbedtls_ssl_tls13_session_clear_ticket_flags(
{
session->ticket_flags &= ~(flags & MBEDTLS_SSL_TLS1_3_TICKET_FLAGS_MASK);
}
+
#endif /* MBEDTLS_SSL_PROTO_TLS1_3 && MBEDTLS_SSL_SESSION_TICKETS */
+#if defined(MBEDTLS_SSL_SESSION_TICKETS) && defined(MBEDTLS_SSL_CLI_C)
+#define MBEDTLS_SSL_SESSION_TICKETS_TLS1_2_BIT 0
+#define MBEDTLS_SSL_SESSION_TICKETS_TLS1_3_BIT 1
+
+#define MBEDTLS_SSL_SESSION_TICKETS_TLS1_2_MASK \
+ (1 << MBEDTLS_SSL_SESSION_TICKETS_TLS1_2_BIT)
+#define MBEDTLS_SSL_SESSION_TICKETS_TLS1_3_MASK \
+ (1 << MBEDTLS_SSL_SESSION_TICKETS_TLS1_3_BIT)
+
+static inline int mbedtls_ssl_conf_get_session_tickets(
+ const mbedtls_ssl_config *conf)
+{
+ return conf->session_tickets & MBEDTLS_SSL_SESSION_TICKETS_TLS1_2_MASK ?
+ MBEDTLS_SSL_SESSION_TICKETS_ENABLED :
+ MBEDTLS_SSL_SESSION_TICKETS_DISABLED;
+}
+
+#if defined(MBEDTLS_SSL_PROTO_TLS1_3)
+static inline int mbedtls_ssl_conf_is_signal_new_session_tickets_enabled(
+ const mbedtls_ssl_config *conf)
+{
+ return conf->session_tickets & MBEDTLS_SSL_SESSION_TICKETS_TLS1_3_MASK ?
+ MBEDTLS_SSL_TLS1_3_SIGNAL_NEW_SESSION_TICKETS_ENABLED :
+ MBEDTLS_SSL_TLS1_3_SIGNAL_NEW_SESSION_TICKETS_DISABLED;
+}
+#endif /* MBEDTLS_SSL_PROTO_TLS1_3 */
+#endif /* MBEDTLS_SSL_SESSION_TICKETS && MBEDTLS_SSL_CLI_C */
+
#if defined(MBEDTLS_SSL_CLI_C) && defined(MBEDTLS_SSL_PROTO_TLS1_3)
int mbedtls_ssl_tls13_finalize_client_hello(mbedtls_ssl_context *ssl);
#endif
diff --git a/thirdparty/mbedtls/library/ssl_msg.c b/thirdparty/mbedtls/library/ssl_msg.c
index b07cd96f1b..ef722d7bdc 100644
--- a/thirdparty/mbedtls/library/ssl_msg.c
+++ b/thirdparty/mbedtls/library/ssl_msg.c
@@ -5570,9 +5570,9 @@ static int ssl_check_ctr_renegotiate(mbedtls_ssl_context *ssl)
#if defined(MBEDTLS_SSL_PROTO_TLS1_3)
-#if defined(MBEDTLS_SSL_SESSION_TICKETS) && defined(MBEDTLS_SSL_CLI_C)
+#if defined(MBEDTLS_SSL_CLI_C)
MBEDTLS_CHECK_RETURN_CRITICAL
-static int ssl_tls13_check_new_session_ticket(mbedtls_ssl_context *ssl)
+static int ssl_tls13_is_new_session_ticket(mbedtls_ssl_context *ssl)
{
if ((ssl->in_hslen == mbedtls_ssl_hs_hdr_len(ssl)) ||
@@ -5580,15 +5580,9 @@ static int ssl_tls13_check_new_session_ticket(mbedtls_ssl_context *ssl)
return 0;
}
- ssl->keep_current_message = 1;
-
- MBEDTLS_SSL_DEBUG_MSG(3, ("NewSessionTicket received"));
- mbedtls_ssl_handshake_set_state(ssl,
- MBEDTLS_SSL_TLS1_3_NEW_SESSION_TICKET);
-
- return MBEDTLS_ERR_SSL_WANT_READ;
+ return 1;
}
-#endif /* MBEDTLS_SSL_SESSION_TICKETS && MBEDTLS_SSL_CLI_C */
+#endif /* MBEDTLS_SSL_CLI_C */
MBEDTLS_CHECK_RETURN_CRITICAL
static int ssl_tls13_handle_hs_message_post_handshake(mbedtls_ssl_context *ssl)
@@ -5596,14 +5590,29 @@ static int ssl_tls13_handle_hs_message_post_handshake(mbedtls_ssl_context *ssl)
MBEDTLS_SSL_DEBUG_MSG(3, ("received post-handshake message"));
-#if defined(MBEDTLS_SSL_SESSION_TICKETS) && defined(MBEDTLS_SSL_CLI_C)
+#if defined(MBEDTLS_SSL_CLI_C)
if (ssl->conf->endpoint == MBEDTLS_SSL_IS_CLIENT) {
- int ret = ssl_tls13_check_new_session_ticket(ssl);
- if (ret != 0) {
- return ret;
+ if (ssl_tls13_is_new_session_ticket(ssl)) {
+#if defined(MBEDTLS_SSL_SESSION_TICKETS)
+ MBEDTLS_SSL_DEBUG_MSG(3, ("NewSessionTicket received"));
+ if (mbedtls_ssl_conf_is_signal_new_session_tickets_enabled(ssl->conf) ==
+ MBEDTLS_SSL_TLS1_3_SIGNAL_NEW_SESSION_TICKETS_ENABLED) {
+ ssl->keep_current_message = 1;
+
+ mbedtls_ssl_handshake_set_state(ssl,
+ MBEDTLS_SSL_TLS1_3_NEW_SESSION_TICKET);
+ return MBEDTLS_ERR_SSL_WANT_READ;
+ } else {
+ MBEDTLS_SSL_DEBUG_MSG(3, ("Ignoring NewSessionTicket, handling disabled."));
+ return 0;
+ }
+#else
+ MBEDTLS_SSL_DEBUG_MSG(3, ("Ignoring NewSessionTicket, not supported."));
+ return 0;
+#endif
}
}
-#endif /* MBEDTLS_SSL_SESSION_TICKETS && MBEDTLS_SSL_CLI_C */
+#endif /* MBEDTLS_SSL_CLI_C */
/* Fail in all other cases. */
return MBEDTLS_ERR_SSL_UNEXPECTED_MESSAGE;
diff --git a/thirdparty/mbedtls/library/ssl_ticket.c b/thirdparty/mbedtls/library/ssl_ticket.c
index 6a31b0bee6..bfb656cf62 100644
--- a/thirdparty/mbedtls/library/ssl_ticket.c
+++ b/thirdparty/mbedtls/library/ssl_ticket.c
@@ -534,6 +534,10 @@ cleanup:
*/
void mbedtls_ssl_ticket_free(mbedtls_ssl_ticket_context *ctx)
{
+ if (ctx == NULL) {
+ return;
+ }
+
#if defined(MBEDTLS_USE_PSA_CRYPTO)
psa_destroy_key(ctx->keys[0].key);
psa_destroy_key(ctx->keys[1].key);
diff --git a/thirdparty/mbedtls/library/ssl_tls.c b/thirdparty/mbedtls/library/ssl_tls.c
index c5e06491c1..c773365bf6 100644
--- a/thirdparty/mbedtls/library/ssl_tls.c
+++ b/thirdparty/mbedtls/library/ssl_tls.c
@@ -132,7 +132,7 @@ int mbedtls_ssl_set_cid(mbedtls_ssl_context *ssl,
int mbedtls_ssl_get_own_cid(mbedtls_ssl_context *ssl,
int *enabled,
- unsigned char own_cid[MBEDTLS_SSL_CID_OUT_LEN_MAX],
+ unsigned char own_cid[MBEDTLS_SSL_CID_IN_LEN_MAX],
size_t *own_cid_len)
{
*enabled = MBEDTLS_SSL_CID_DISABLED;
@@ -1354,29 +1354,6 @@ static int ssl_conf_check(const mbedtls_ssl_context *ssl)
return ret;
}
-#if defined(MBEDTLS_SSL_PROTO_TLS1_3)
- /* RFC 8446 section 4.4.3
- *
- * If the verification fails, the receiver MUST terminate the handshake with
- * a "decrypt_error" alert.
- *
- * If the client is configured as TLS 1.3 only with optional verify, return
- * bad config.
- *
- */
- if (mbedtls_ssl_conf_tls13_is_ephemeral_enabled(
- (mbedtls_ssl_context *) ssl) &&
- ssl->conf->endpoint == MBEDTLS_SSL_IS_CLIENT &&
- ssl->conf->max_tls_version == MBEDTLS_SSL_VERSION_TLS1_3 &&
- ssl->conf->min_tls_version == MBEDTLS_SSL_VERSION_TLS1_3 &&
- ssl->conf->authmode == MBEDTLS_SSL_VERIFY_OPTIONAL) {
- MBEDTLS_SSL_DEBUG_MSG(
- 1, ("Optional verify auth mode "
- "is not available for TLS 1.3 client"));
- return MBEDTLS_ERR_SSL_BAD_CONFIG;
- }
-#endif /* MBEDTLS_SSL_PROTO_TLS1_3 */
-
if (ssl->conf->f_rng == NULL) {
MBEDTLS_SSL_DEBUG_MSG(1, ("no RNG provided"));
return MBEDTLS_ERR_SSL_NO_RNG;
@@ -1760,6 +1737,7 @@ int mbedtls_ssl_set_session(mbedtls_ssl_context *ssl, const mbedtls_ssl_session
#if defined(MBEDTLS_SSL_PROTO_TLS1_3)
if (session->tls_version == MBEDTLS_SSL_VERSION_TLS1_3) {
+#if defined(MBEDTLS_SSL_SESSION_TICKETS)
const mbedtls_ssl_ciphersuite_t *ciphersuite_info =
mbedtls_ssl_ciphersuite_from_id(session->ciphersuite);
@@ -1770,6 +1748,14 @@ int mbedtls_ssl_set_session(mbedtls_ssl_context *ssl, const mbedtls_ssl_session
session->ciphersuite));
return MBEDTLS_ERR_SSL_BAD_INPUT_DATA;
}
+#else
+ /*
+ * If session tickets are not enabled, it is not possible to resume a
+ * TLS 1.3 session, thus do not make any change to the SSL context in
+ * the first place.
+ */
+ return 0;
+#endif
}
#endif /* MBEDTLS_SSL_PROTO_TLS1_3 */
@@ -2234,6 +2220,7 @@ static void ssl_remove_psk(mbedtls_ssl_context *ssl)
mbedtls_zeroize_and_free(ssl->handshake->psk,
ssl->handshake->psk_len);
ssl->handshake->psk_len = 0;
+ ssl->handshake->psk = NULL;
}
#endif /* MBEDTLS_USE_PSA_CRYPTO */
}
@@ -2999,11 +2986,24 @@ void mbedtls_ssl_conf_renegotiation_period(mbedtls_ssl_config *conf,
#if defined(MBEDTLS_SSL_SESSION_TICKETS)
#if defined(MBEDTLS_SSL_CLI_C)
+
void mbedtls_ssl_conf_session_tickets(mbedtls_ssl_config *conf, int use_tickets)
{
- conf->session_tickets = use_tickets;
+ conf->session_tickets &= ~MBEDTLS_SSL_SESSION_TICKETS_TLS1_2_MASK;
+ conf->session_tickets |= (use_tickets != 0) <<
+ MBEDTLS_SSL_SESSION_TICKETS_TLS1_2_BIT;
}
-#endif
+
+#if defined(MBEDTLS_SSL_PROTO_TLS1_3)
+void mbedtls_ssl_conf_tls13_enable_signal_new_session_tickets(
+ mbedtls_ssl_config *conf, int signal_new_session_tickets)
+{
+ conf->session_tickets &= ~MBEDTLS_SSL_SESSION_TICKETS_TLS1_3_MASK;
+ conf->session_tickets |= (signal_new_session_tickets != 0) <<
+ MBEDTLS_SSL_SESSION_TICKETS_TLS1_3_BIT;
+}
+#endif /* MBEDTLS_SSL_PROTO_TLS1_3 */
+#endif /* MBEDTLS_SSL_CLI_C */
#if defined(MBEDTLS_SSL_SRV_C)
@@ -4049,7 +4049,7 @@ static int ssl_tls13_session_save(const mbedtls_ssl_session *session,
}
static int ssl_tls13_session_load(const mbedtls_ssl_session *session,
- unsigned char *buf,
+ const unsigned char *buf,
size_t buf_len)
{
((void) session);
@@ -5868,7 +5868,33 @@ int mbedtls_ssl_config_defaults(mbedtls_ssl_config *conf,
if (endpoint == MBEDTLS_SSL_IS_CLIENT) {
conf->authmode = MBEDTLS_SSL_VERIFY_REQUIRED;
#if defined(MBEDTLS_SSL_SESSION_TICKETS)
- conf->session_tickets = MBEDTLS_SSL_SESSION_TICKETS_ENABLED;
+ mbedtls_ssl_conf_session_tickets(conf, MBEDTLS_SSL_SESSION_TICKETS_ENABLED);
+#if defined(MBEDTLS_SSL_PROTO_TLS1_3)
+ /* Contrary to TLS 1.2 tickets, TLS 1.3 NewSessionTicket message
+ * handling is disabled by default in Mbed TLS 3.6.x for backward
+ * compatibility with client applications developed using Mbed TLS 3.5
+ * or earlier with the default configuration.
+ *
+ * Up to Mbed TLS 3.5, in the default configuration TLS 1.3 was
+ * disabled, and a Mbed TLS client with the default configuration would
+ * establish a TLS 1.2 connection with a TLS 1.2 and TLS 1.3 capable
+ * server.
+ *
+ * Starting with Mbed TLS 3.6.0, TLS 1.3 is enabled by default, and thus
+ * an Mbed TLS client with the default configuration establishes a
+ * TLS 1.3 connection with a TLS 1.2 and TLS 1.3 capable server. If
+ * following the handshake the TLS 1.3 server sends NewSessionTicket
+ * messages and the Mbed TLS client processes them, this results in
+ * Mbed TLS high level APIs (mbedtls_ssl_read(),
+ * mbedtls_ssl_handshake(), ...) to eventually return an
+ * #MBEDTLS_ERR_SSL_RECEIVED_NEW_SESSION_TICKET non fatal error code
+ * (see the documentation of mbedtls_ssl_read() for more information on
+ * that error code). Applications unaware of that TLS 1.3 specific non
+ * fatal error code are then failing.
+ */
+ mbedtls_ssl_conf_tls13_enable_signal_new_session_tickets(
+ conf, MBEDTLS_SSL_TLS1_3_SIGNAL_NEW_SESSION_TICKETS_DISABLED);
+#endif
#endif
}
#endif
@@ -6030,6 +6056,10 @@ int mbedtls_ssl_config_defaults(mbedtls_ssl_config *conf,
*/
void mbedtls_ssl_config_free(mbedtls_ssl_config *conf)
{
+ if (conf == NULL) {
+ return;
+ }
+
#if defined(MBEDTLS_DHM_C)
mbedtls_mpi_free(&conf->dhm_P);
mbedtls_mpi_free(&conf->dhm_G);
@@ -6344,71 +6374,6 @@ const char *mbedtls_ssl_get_curve_name_from_tls_id(uint16_t tls_id)
}
#endif
-#if defined(MBEDTLS_X509_CRT_PARSE_C)
-int mbedtls_ssl_check_cert_usage(const mbedtls_x509_crt *cert,
- const mbedtls_ssl_ciphersuite_t *ciphersuite,
- int cert_endpoint,
- uint32_t *flags)
-{
- int ret = 0;
- unsigned int usage = 0;
- const char *ext_oid;
- size_t ext_len;
-
- if (cert_endpoint == MBEDTLS_SSL_IS_SERVER) {
- /* Server part of the key exchange */
- switch (ciphersuite->key_exchange) {
- case MBEDTLS_KEY_EXCHANGE_RSA:
- case MBEDTLS_KEY_EXCHANGE_RSA_PSK:
- usage = MBEDTLS_X509_KU_KEY_ENCIPHERMENT;
- break;
-
- case MBEDTLS_KEY_EXCHANGE_DHE_RSA:
- case MBEDTLS_KEY_EXCHANGE_ECDHE_RSA:
- case MBEDTLS_KEY_EXCHANGE_ECDHE_ECDSA:
- usage = MBEDTLS_X509_KU_DIGITAL_SIGNATURE;
- break;
-
- case MBEDTLS_KEY_EXCHANGE_ECDH_RSA:
- case MBEDTLS_KEY_EXCHANGE_ECDH_ECDSA:
- usage = MBEDTLS_X509_KU_KEY_AGREEMENT;
- break;
-
- /* Don't use default: we want warnings when adding new values */
- case MBEDTLS_KEY_EXCHANGE_NONE:
- case MBEDTLS_KEY_EXCHANGE_PSK:
- case MBEDTLS_KEY_EXCHANGE_DHE_PSK:
- case MBEDTLS_KEY_EXCHANGE_ECDHE_PSK:
- case MBEDTLS_KEY_EXCHANGE_ECJPAKE:
- usage = 0;
- }
- } else {
- /* Client auth: we only implement rsa_sign and mbedtls_ecdsa_sign for now */
- usage = MBEDTLS_X509_KU_DIGITAL_SIGNATURE;
- }
-
- if (mbedtls_x509_crt_check_key_usage(cert, usage) != 0) {
- *flags |= MBEDTLS_X509_BADCERT_KEY_USAGE;
- ret = -1;
- }
-
- if (cert_endpoint == MBEDTLS_SSL_IS_SERVER) {
- ext_oid = MBEDTLS_OID_SERVER_AUTH;
- ext_len = MBEDTLS_OID_SIZE(MBEDTLS_OID_SERVER_AUTH);
- } else {
- ext_oid = MBEDTLS_OID_CLIENT_AUTH;
- ext_len = MBEDTLS_OID_SIZE(MBEDTLS_OID_CLIENT_AUTH);
- }
-
- if (mbedtls_x509_crt_check_extended_key_usage(cert, ext_oid, ext_len) != 0) {
- *flags |= MBEDTLS_X509_BADCERT_EXT_KEY_USAGE;
- ret = -1;
- }
-
- return ret;
-}
-#endif /* MBEDTLS_X509_CRT_PARSE_C */
-
#if defined(MBEDTLS_USE_PSA_CRYPTO)
int mbedtls_ssl_get_handshake_transcript(mbedtls_ssl_context *ssl,
const mbedtls_md_type_t md,
@@ -7927,196 +7892,6 @@ static int ssl_parse_certificate_coordinate(mbedtls_ssl_context *ssl,
return SSL_CERTIFICATE_EXPECTED;
}
-MBEDTLS_CHECK_RETURN_CRITICAL
-static int ssl_parse_certificate_verify(mbedtls_ssl_context *ssl,
- int authmode,
- mbedtls_x509_crt *chain,
- void *rs_ctx)
-{
- int ret = 0;
- const mbedtls_ssl_ciphersuite_t *ciphersuite_info =
- ssl->handshake->ciphersuite_info;
- int have_ca_chain = 0;
-
- int (*f_vrfy)(void *, mbedtls_x509_crt *, int, uint32_t *);
- void *p_vrfy;
-
- if (authmode == MBEDTLS_SSL_VERIFY_NONE) {
- return 0;
- }
-
- if (ssl->f_vrfy != NULL) {
- MBEDTLS_SSL_DEBUG_MSG(3, ("Use context-specific verification callback"));
- f_vrfy = ssl->f_vrfy;
- p_vrfy = ssl->p_vrfy;
- } else {
- MBEDTLS_SSL_DEBUG_MSG(3, ("Use configuration-specific verification callback"));
- f_vrfy = ssl->conf->f_vrfy;
- p_vrfy = ssl->conf->p_vrfy;
- }
-
- /*
- * Main check: verify certificate
- */
-#if defined(MBEDTLS_X509_TRUSTED_CERTIFICATE_CALLBACK)
- if (ssl->conf->f_ca_cb != NULL) {
- ((void) rs_ctx);
- have_ca_chain = 1;
-
- MBEDTLS_SSL_DEBUG_MSG(3, ("use CA callback for X.509 CRT verification"));
- ret = mbedtls_x509_crt_verify_with_ca_cb(
- chain,
- ssl->conf->f_ca_cb,
- ssl->conf->p_ca_cb,
- ssl->conf->cert_profile,
- ssl->hostname,
- &ssl->session_negotiate->verify_result,
- f_vrfy, p_vrfy);
- } else
-#endif /* MBEDTLS_X509_TRUSTED_CERTIFICATE_CALLBACK */
- {
- mbedtls_x509_crt *ca_chain;
- mbedtls_x509_crl *ca_crl;
-
-#if defined(MBEDTLS_SSL_SERVER_NAME_INDICATION)
- if (ssl->handshake->sni_ca_chain != NULL) {
- ca_chain = ssl->handshake->sni_ca_chain;
- ca_crl = ssl->handshake->sni_ca_crl;
- } else
-#endif
- {
- ca_chain = ssl->conf->ca_chain;
- ca_crl = ssl->conf->ca_crl;
- }
-
- if (ca_chain != NULL) {
- have_ca_chain = 1;
- }
-
- ret = mbedtls_x509_crt_verify_restartable(
- chain,
- ca_chain, ca_crl,
- ssl->conf->cert_profile,
- ssl->hostname,
- &ssl->session_negotiate->verify_result,
- f_vrfy, p_vrfy, rs_ctx);
- }
-
- if (ret != 0) {
- MBEDTLS_SSL_DEBUG_RET(1, "x509_verify_cert", ret);
- }
-
-#if defined(MBEDTLS_SSL_ECP_RESTARTABLE_ENABLED)
- if (ret == MBEDTLS_ERR_ECP_IN_PROGRESS) {
- return MBEDTLS_ERR_SSL_CRYPTO_IN_PROGRESS;
- }
-#endif
-
- /*
- * Secondary checks: always done, but change 'ret' only if it was 0
- */
-
-#if defined(MBEDTLS_PK_HAVE_ECC_KEYS)
- {
- const mbedtls_pk_context *pk = &chain->pk;
-
- /* If certificate uses an EC key, make sure the curve is OK.
- * This is a public key, so it can't be opaque, so can_do() is a good
- * enough check to ensure pk_ec() is safe to use here. */
- if (mbedtls_pk_can_do(pk, MBEDTLS_PK_ECKEY)) {
- /* and in the unlikely case the above assumption no longer holds
- * we are making sure that pk_ec() here does not return a NULL
- */
- mbedtls_ecp_group_id grp_id = mbedtls_pk_get_ec_group_id(pk);
- if (grp_id == MBEDTLS_ECP_DP_NONE) {
- MBEDTLS_SSL_DEBUG_MSG(1, ("invalid group ID"));
- return MBEDTLS_ERR_SSL_INTERNAL_ERROR;
- }
- if (mbedtls_ssl_check_curve(ssl, grp_id) != 0) {
- ssl->session_negotiate->verify_result |=
- MBEDTLS_X509_BADCERT_BAD_KEY;
-
- MBEDTLS_SSL_DEBUG_MSG(1, ("bad certificate (EC key curve)"));
- if (ret == 0) {
- ret = MBEDTLS_ERR_SSL_BAD_CERTIFICATE;
- }
- }
- }
- }
-#endif /* MBEDTLS_PK_HAVE_ECC_KEYS */
-
- if (mbedtls_ssl_check_cert_usage(chain,
- ciphersuite_info,
- !ssl->conf->endpoint,
- &ssl->session_negotiate->verify_result) != 0) {
- MBEDTLS_SSL_DEBUG_MSG(1, ("bad certificate (usage extensions)"));
- if (ret == 0) {
- ret = MBEDTLS_ERR_SSL_BAD_CERTIFICATE;
- }
- }
-
- /* mbedtls_x509_crt_verify_with_profile is supposed to report a
- * verification failure through MBEDTLS_ERR_X509_CERT_VERIFY_FAILED,
- * with details encoded in the verification flags. All other kinds
- * of error codes, including those from the user provided f_vrfy
- * functions, are treated as fatal and lead to a failure of
- * ssl_parse_certificate even if verification was optional. */
- if (authmode == MBEDTLS_SSL_VERIFY_OPTIONAL &&
- (ret == MBEDTLS_ERR_X509_CERT_VERIFY_FAILED ||
- ret == MBEDTLS_ERR_SSL_BAD_CERTIFICATE)) {
- ret = 0;
- }
-
- if (have_ca_chain == 0 && authmode == MBEDTLS_SSL_VERIFY_REQUIRED) {
- MBEDTLS_SSL_DEBUG_MSG(1, ("got no CA chain"));
- ret = MBEDTLS_ERR_SSL_CA_CHAIN_REQUIRED;
- }
-
- if (ret != 0) {
- uint8_t alert;
-
- /* The certificate may have been rejected for several reasons.
- Pick one and send the corresponding alert. Which alert to send
- may be a subject of debate in some cases. */
- if (ssl->session_negotiate->verify_result & MBEDTLS_X509_BADCERT_OTHER) {
- alert = MBEDTLS_SSL_ALERT_MSG_ACCESS_DENIED;
- } else if (ssl->session_negotiate->verify_result & MBEDTLS_X509_BADCERT_CN_MISMATCH) {
- alert = MBEDTLS_SSL_ALERT_MSG_BAD_CERT;
- } else if (ssl->session_negotiate->verify_result & MBEDTLS_X509_BADCERT_KEY_USAGE) {
- alert = MBEDTLS_SSL_ALERT_MSG_UNSUPPORTED_CERT;
- } else if (ssl->session_negotiate->verify_result & MBEDTLS_X509_BADCERT_EXT_KEY_USAGE) {
- alert = MBEDTLS_SSL_ALERT_MSG_UNSUPPORTED_CERT;
- } else if (ssl->session_negotiate->verify_result & MBEDTLS_X509_BADCERT_NS_CERT_TYPE) {
- alert = MBEDTLS_SSL_ALERT_MSG_UNSUPPORTED_CERT;
- } else if (ssl->session_negotiate->verify_result & MBEDTLS_X509_BADCERT_BAD_PK) {
- alert = MBEDTLS_SSL_ALERT_MSG_UNSUPPORTED_CERT;
- } else if (ssl->session_negotiate->verify_result & MBEDTLS_X509_BADCERT_BAD_KEY) {
- alert = MBEDTLS_SSL_ALERT_MSG_UNSUPPORTED_CERT;
- } else if (ssl->session_negotiate->verify_result & MBEDTLS_X509_BADCERT_EXPIRED) {
- alert = MBEDTLS_SSL_ALERT_MSG_CERT_EXPIRED;
- } else if (ssl->session_negotiate->verify_result & MBEDTLS_X509_BADCERT_REVOKED) {
- alert = MBEDTLS_SSL_ALERT_MSG_CERT_REVOKED;
- } else if (ssl->session_negotiate->verify_result & MBEDTLS_X509_BADCERT_NOT_TRUSTED) {
- alert = MBEDTLS_SSL_ALERT_MSG_UNKNOWN_CA;
- } else {
- alert = MBEDTLS_SSL_ALERT_MSG_CERT_UNKNOWN;
- }
- mbedtls_ssl_send_alert_message(ssl, MBEDTLS_SSL_ALERT_LEVEL_FATAL,
- alert);
- }
-
-#if defined(MBEDTLS_DEBUG_C)
- if (ssl->session_negotiate->verify_result != 0) {
- MBEDTLS_SSL_DEBUG_MSG(3, ("! Certificate verification flags %08x",
- (unsigned int) ssl->session_negotiate->verify_result));
- } else {
- MBEDTLS_SSL_DEBUG_MSG(3, ("Certificate verification flags clear"));
- }
-#endif /* MBEDTLS_DEBUG_C */
-
- return ret;
-}
-
#if !defined(MBEDTLS_SSL_KEEP_PEER_CERTIFICATE)
MBEDTLS_CHECK_RETURN_CRITICAL
static int ssl_remember_peer_crt_digest(mbedtls_ssl_context *ssl,
@@ -8173,6 +7948,7 @@ int mbedtls_ssl_parse_certificate(mbedtls_ssl_context *ssl)
{
int ret = 0;
int crt_expected;
+ /* Authmode: precedence order is SNI if used else configuration */
#if defined(MBEDTLS_SSL_SRV_C) && defined(MBEDTLS_SSL_SERVER_NAME_INDICATION)
const int authmode = ssl->handshake->sni_authmode != MBEDTLS_SSL_VERIFY_UNSET
? ssl->handshake->sni_authmode
@@ -8252,8 +8028,9 @@ crt_verify:
}
#endif
- ret = ssl_parse_certificate_verify(ssl, authmode,
- chain, rs_ctx);
+ ret = mbedtls_ssl_verify_certificate(ssl, authmode, chain,
+ ssl->handshake->ciphersuite_info,
+ rs_ctx);
if (ret != 0) {
goto exit;
}
@@ -9919,4 +9696,274 @@ int mbedtls_ssl_session_set_ticket_alpn(mbedtls_ssl_session *session,
return 0;
}
#endif /* MBEDTLS_SSL_SRV_C && MBEDTLS_SSL_EARLY_DATA && MBEDTLS_SSL_ALPN */
+
+/*
+ * The following functions are used by 1.2 and 1.3, client and server.
+ */
+#if defined(MBEDTLS_SSL_HANDSHAKE_WITH_CERT_ENABLED)
+int mbedtls_ssl_check_cert_usage(const mbedtls_x509_crt *cert,
+ const mbedtls_ssl_ciphersuite_t *ciphersuite,
+ int recv_endpoint,
+ mbedtls_ssl_protocol_version tls_version,
+ uint32_t *flags)
+{
+ int ret = 0;
+ unsigned int usage = 0;
+ const char *ext_oid;
+ size_t ext_len;
+
+ /*
+ * keyUsage
+ */
+
+ /* Note: don't guard this with MBEDTLS_SSL_CLI_C because the server wants
+ * to check what a compliant client will think while choosing which cert
+ * to send to the client. */
+#if defined(MBEDTLS_SSL_PROTO_TLS1_2)
+ if (tls_version == MBEDTLS_SSL_VERSION_TLS1_2 &&
+ recv_endpoint == MBEDTLS_SSL_IS_CLIENT) {
+ /* TLS 1.2 server part of the key exchange */
+ switch (ciphersuite->key_exchange) {
+ case MBEDTLS_KEY_EXCHANGE_RSA:
+ case MBEDTLS_KEY_EXCHANGE_RSA_PSK:
+ usage = MBEDTLS_X509_KU_KEY_ENCIPHERMENT;
+ break;
+
+ case MBEDTLS_KEY_EXCHANGE_DHE_RSA:
+ case MBEDTLS_KEY_EXCHANGE_ECDHE_RSA:
+ case MBEDTLS_KEY_EXCHANGE_ECDHE_ECDSA:
+ usage = MBEDTLS_X509_KU_DIGITAL_SIGNATURE;
+ break;
+
+ case MBEDTLS_KEY_EXCHANGE_ECDH_RSA:
+ case MBEDTLS_KEY_EXCHANGE_ECDH_ECDSA:
+ usage = MBEDTLS_X509_KU_KEY_AGREEMENT;
+ break;
+
+ /* Don't use default: we want warnings when adding new values */
+ case MBEDTLS_KEY_EXCHANGE_NONE:
+ case MBEDTLS_KEY_EXCHANGE_PSK:
+ case MBEDTLS_KEY_EXCHANGE_DHE_PSK:
+ case MBEDTLS_KEY_EXCHANGE_ECDHE_PSK:
+ case MBEDTLS_KEY_EXCHANGE_ECJPAKE:
+ usage = 0;
+ }
+ } else
+#endif
+ {
+ /* This is either TLS 1.3 authentication, which always uses signatures,
+ * or 1.2 client auth: rsa_sign and mbedtls_ecdsa_sign are the only
+ * options we implement, both using signatures. */
+ (void) tls_version;
+ (void) ciphersuite;
+ usage = MBEDTLS_X509_KU_DIGITAL_SIGNATURE;
+ }
+
+ if (mbedtls_x509_crt_check_key_usage(cert, usage) != 0) {
+ *flags |= MBEDTLS_X509_BADCERT_KEY_USAGE;
+ ret = -1;
+ }
+
+ /*
+ * extKeyUsage
+ */
+
+ if (recv_endpoint == MBEDTLS_SSL_IS_CLIENT) {
+ ext_oid = MBEDTLS_OID_SERVER_AUTH;
+ ext_len = MBEDTLS_OID_SIZE(MBEDTLS_OID_SERVER_AUTH);
+ } else {
+ ext_oid = MBEDTLS_OID_CLIENT_AUTH;
+ ext_len = MBEDTLS_OID_SIZE(MBEDTLS_OID_CLIENT_AUTH);
+ }
+
+ if (mbedtls_x509_crt_check_extended_key_usage(cert, ext_oid, ext_len) != 0) {
+ *flags |= MBEDTLS_X509_BADCERT_EXT_KEY_USAGE;
+ ret = -1;
+ }
+
+ return ret;
+}
+
+int mbedtls_ssl_verify_certificate(mbedtls_ssl_context *ssl,
+ int authmode,
+ mbedtls_x509_crt *chain,
+ const mbedtls_ssl_ciphersuite_t *ciphersuite_info,
+ void *rs_ctx)
+{
+ if (authmode == MBEDTLS_SSL_VERIFY_NONE) {
+ return 0;
+ }
+
+ /*
+ * Primary check: use the appropriate X.509 verification function
+ */
+ int (*f_vrfy)(void *, mbedtls_x509_crt *, int, uint32_t *);
+ void *p_vrfy;
+ if (ssl->f_vrfy != NULL) {
+ MBEDTLS_SSL_DEBUG_MSG(3, ("Use context-specific verification callback"));
+ f_vrfy = ssl->f_vrfy;
+ p_vrfy = ssl->p_vrfy;
+ } else {
+ MBEDTLS_SSL_DEBUG_MSG(3, ("Use configuration-specific verification callback"));
+ f_vrfy = ssl->conf->f_vrfy;
+ p_vrfy = ssl->conf->p_vrfy;
+ }
+
+ int ret = 0;
+ int have_ca_chain_or_callback = 0;
+#if defined(MBEDTLS_X509_TRUSTED_CERTIFICATE_CALLBACK)
+ if (ssl->conf->f_ca_cb != NULL) {
+ ((void) rs_ctx);
+ have_ca_chain_or_callback = 1;
+
+ MBEDTLS_SSL_DEBUG_MSG(3, ("use CA callback for X.509 CRT verification"));
+ ret = mbedtls_x509_crt_verify_with_ca_cb(
+ chain,
+ ssl->conf->f_ca_cb,
+ ssl->conf->p_ca_cb,
+ ssl->conf->cert_profile,
+ ssl->hostname,
+ &ssl->session_negotiate->verify_result,
+ f_vrfy, p_vrfy);
+ } else
+#endif /* MBEDTLS_X509_TRUSTED_CERTIFICATE_CALLBACK */
+ {
+ mbedtls_x509_crt *ca_chain;
+ mbedtls_x509_crl *ca_crl;
+#if defined(MBEDTLS_SSL_SERVER_NAME_INDICATION)
+ if (ssl->handshake->sni_ca_chain != NULL) {
+ ca_chain = ssl->handshake->sni_ca_chain;
+ ca_crl = ssl->handshake->sni_ca_crl;
+ } else
+#endif
+ {
+ ca_chain = ssl->conf->ca_chain;
+ ca_crl = ssl->conf->ca_crl;
+ }
+
+ if (ca_chain != NULL) {
+ have_ca_chain_or_callback = 1;
+ }
+
+ ret = mbedtls_x509_crt_verify_restartable(
+ chain,
+ ca_chain, ca_crl,
+ ssl->conf->cert_profile,
+ ssl->hostname,
+ &ssl->session_negotiate->verify_result,
+ f_vrfy, p_vrfy, rs_ctx);
+ }
+
+ if (ret != 0) {
+ MBEDTLS_SSL_DEBUG_RET(1, "x509_verify_cert", ret);
+ }
+
+#if defined(MBEDTLS_SSL_ECP_RESTARTABLE_ENABLED)
+ if (ret == MBEDTLS_ERR_ECP_IN_PROGRESS) {
+ return MBEDTLS_ERR_SSL_CRYPTO_IN_PROGRESS;
+ }
+#endif
+
+ /*
+ * Secondary checks: always done, but change 'ret' only if it was 0
+ */
+
+ /* With TLS 1.2 and ECC certs, check that the curve used by the
+ * certificate is on our list of acceptable curves.
+ *
+ * With TLS 1.3 this is not needed because the curve is part of the
+ * signature algorithm (eg ecdsa_secp256r1_sha256) which is checked when
+ * we validate the signature made with the key associated to this cert.
+ */
+#if defined(MBEDTLS_SSL_PROTO_TLS1_2) && \
+ defined(MBEDTLS_PK_HAVE_ECC_KEYS)
+ if (ssl->tls_version == MBEDTLS_SSL_VERSION_TLS1_2 &&
+ mbedtls_pk_can_do(&chain->pk, MBEDTLS_PK_ECKEY)) {
+ if (mbedtls_ssl_check_curve(ssl, mbedtls_pk_get_ec_group_id(&chain->pk)) != 0) {
+ MBEDTLS_SSL_DEBUG_MSG(1, ("bad certificate (EC key curve)"));
+ ssl->session_negotiate->verify_result |= MBEDTLS_X509_BADCERT_BAD_KEY;
+ if (ret == 0) {
+ ret = MBEDTLS_ERR_SSL_BAD_CERTIFICATE;
+ }
+ }
+ }
+#endif /* MBEDTLS_SSL_PROTO_TLS1_2 && MBEDTLS_PK_HAVE_ECC_KEYS */
+
+ /* Check X.509 usage extensions (keyUsage, extKeyUsage) */
+ if (mbedtls_ssl_check_cert_usage(chain,
+ ciphersuite_info,
+ ssl->conf->endpoint,
+ ssl->tls_version,
+ &ssl->session_negotiate->verify_result) != 0) {
+ MBEDTLS_SSL_DEBUG_MSG(1, ("bad certificate (usage extensions)"));
+ if (ret == 0) {
+ ret = MBEDTLS_ERR_SSL_BAD_CERTIFICATE;
+ }
+ }
+
+ /* With authmode optional, we want to keep going if the certificate was
+ * unacceptable, but still fail on other errors (out of memory etc),
+ * including fatal errors from the f_vrfy callback.
+ *
+ * The only acceptable errors are:
+ * - MBEDTLS_ERR_X509_CERT_VERIFY_FAILED: cert rejected by primary check;
+ * - MBEDTLS_ERR_SSL_BAD_CERTIFICATE: cert rejected by secondary checks.
+ * Anything else is a fatal error. */
+ if (authmode == MBEDTLS_SSL_VERIFY_OPTIONAL &&
+ (ret == MBEDTLS_ERR_X509_CERT_VERIFY_FAILED ||
+ ret == MBEDTLS_ERR_SSL_BAD_CERTIFICATE)) {
+ ret = 0;
+ }
+
+ /* Return a specific error as this is a user error: inconsistent
+ * configuration - can't verify without trust anchors. */
+ if (have_ca_chain_or_callback == 0 && authmode == MBEDTLS_SSL_VERIFY_REQUIRED) {
+ MBEDTLS_SSL_DEBUG_MSG(1, ("got no CA chain"));
+ ret = MBEDTLS_ERR_SSL_CA_CHAIN_REQUIRED;
+ }
+
+ if (ret != 0) {
+ uint8_t alert;
+
+ /* The certificate may have been rejected for several reasons.
+ Pick one and send the corresponding alert. Which alert to send
+ may be a subject of debate in some cases. */
+ if (ssl->session_negotiate->verify_result & MBEDTLS_X509_BADCERT_OTHER) {
+ alert = MBEDTLS_SSL_ALERT_MSG_ACCESS_DENIED;
+ } else if (ssl->session_negotiate->verify_result & MBEDTLS_X509_BADCERT_CN_MISMATCH) {
+ alert = MBEDTLS_SSL_ALERT_MSG_BAD_CERT;
+ } else if (ssl->session_negotiate->verify_result & MBEDTLS_X509_BADCERT_KEY_USAGE) {
+ alert = MBEDTLS_SSL_ALERT_MSG_UNSUPPORTED_CERT;
+ } else if (ssl->session_negotiate->verify_result & MBEDTLS_X509_BADCERT_EXT_KEY_USAGE) {
+ alert = MBEDTLS_SSL_ALERT_MSG_UNSUPPORTED_CERT;
+ } else if (ssl->session_negotiate->verify_result & MBEDTLS_X509_BADCERT_BAD_PK) {
+ alert = MBEDTLS_SSL_ALERT_MSG_UNSUPPORTED_CERT;
+ } else if (ssl->session_negotiate->verify_result & MBEDTLS_X509_BADCERT_BAD_KEY) {
+ alert = MBEDTLS_SSL_ALERT_MSG_UNSUPPORTED_CERT;
+ } else if (ssl->session_negotiate->verify_result & MBEDTLS_X509_BADCERT_EXPIRED) {
+ alert = MBEDTLS_SSL_ALERT_MSG_CERT_EXPIRED;
+ } else if (ssl->session_negotiate->verify_result & MBEDTLS_X509_BADCERT_REVOKED) {
+ alert = MBEDTLS_SSL_ALERT_MSG_CERT_REVOKED;
+ } else if (ssl->session_negotiate->verify_result & MBEDTLS_X509_BADCERT_NOT_TRUSTED) {
+ alert = MBEDTLS_SSL_ALERT_MSG_UNKNOWN_CA;
+ } else {
+ alert = MBEDTLS_SSL_ALERT_MSG_CERT_UNKNOWN;
+ }
+ mbedtls_ssl_send_alert_message(ssl, MBEDTLS_SSL_ALERT_LEVEL_FATAL,
+ alert);
+ }
+
+#if defined(MBEDTLS_DEBUG_C)
+ if (ssl->session_negotiate->verify_result != 0) {
+ MBEDTLS_SSL_DEBUG_MSG(3, ("! Certificate verification flags %08x",
+ (unsigned int) ssl->session_negotiate->verify_result));
+ } else {
+ MBEDTLS_SSL_DEBUG_MSG(3, ("Certificate verification flags clear"));
+ }
+#endif /* MBEDTLS_DEBUG_C */
+
+ return ret;
+}
+#endif /* MBEDTLS_SSL_HANDSHAKE_WITH_CERT_ENABLED */
+
#endif /* MBEDTLS_SSL_TLS_C */
diff --git a/thirdparty/mbedtls/library/ssl_tls12_client.c b/thirdparty/mbedtls/library/ssl_tls12_client.c
index eac6a3aadd..9b2da5a39d 100644
--- a/thirdparty/mbedtls/library/ssl_tls12_client.c
+++ b/thirdparty/mbedtls/library/ssl_tls12_client.c
@@ -364,7 +364,8 @@ static int ssl_write_session_ticket_ext(mbedtls_ssl_context *ssl,
*olen = 0;
- if (ssl->conf->session_tickets == MBEDTLS_SSL_SESSION_TICKETS_DISABLED) {
+ if (mbedtls_ssl_conf_get_session_tickets(ssl->conf) ==
+ MBEDTLS_SSL_SESSION_TICKETS_DISABLED) {
return 0;
}
@@ -787,7 +788,8 @@ static int ssl_parse_session_ticket_ext(mbedtls_ssl_context *ssl,
const unsigned char *buf,
size_t len)
{
- if (ssl->conf->session_tickets == MBEDTLS_SSL_SESSION_TICKETS_DISABLED ||
+ if ((mbedtls_ssl_conf_get_session_tickets(ssl->conf) ==
+ MBEDTLS_SSL_SESSION_TICKETS_DISABLED) ||
len != 0) {
MBEDTLS_SSL_DEBUG_MSG(1,
("non-matching session ticket extension"));
diff --git a/thirdparty/mbedtls/library/ssl_tls12_server.c b/thirdparty/mbedtls/library/ssl_tls12_server.c
index b49a8ae6a6..03722ac33c 100644
--- a/thirdparty/mbedtls/library/ssl_tls12_server.c
+++ b/thirdparty/mbedtls/library/ssl_tls12_server.c
@@ -756,7 +756,9 @@ static int ssl_pick_cert(mbedtls_ssl_context *ssl,
* and decrypting with the same RSA key.
*/
if (mbedtls_ssl_check_cert_usage(cur->cert, ciphersuite_info,
- MBEDTLS_SSL_IS_SERVER, &flags) != 0) {
+ MBEDTLS_SSL_IS_CLIENT,
+ MBEDTLS_SSL_VERSION_TLS1_2,
+ &flags) != 0) {
MBEDTLS_SSL_DEBUG_MSG(3, ("certificate mismatch: "
"(extended) key usage extension"));
continue;
@@ -2631,13 +2633,8 @@ static int ssl_get_ecdh_params_from_cert(mbedtls_ssl_context *ssl)
ssl->handshake->xxdh_psa_type = psa_get_key_type(&key_attributes);
ssl->handshake->xxdh_psa_bits = psa_get_key_bits(&key_attributes);
- if (pk_type == MBEDTLS_PK_OPAQUE) {
- /* Opaque key is created by the user (externally from Mbed TLS)
- * so we assume it already has the right algorithm and flags
- * set. Just copy its ID as reference. */
- ssl->handshake->xxdh_psa_privkey = pk->priv_id;
- ssl->handshake->xxdh_psa_privkey_is_external = 1;
- } else {
+#if defined(MBEDTLS_PK_USE_PSA_EC_DATA)
+ if (pk_type != MBEDTLS_PK_OPAQUE) {
/* PK_ECKEY[_DH] and PK_ECDSA instead as parsed from the PK
* module and only have ECDSA capabilities. Since we need
* them for ECDH later, we export and then re-import them with
@@ -2665,10 +2662,20 @@ static int ssl_get_ecdh_params_from_cert(mbedtls_ssl_context *ssl)
/* Set this key as owned by the TLS library: it will be its duty
* to clear it exit. */
ssl->handshake->xxdh_psa_privkey_is_external = 0;
+
+ ret = 0;
+ break;
}
+#endif /* MBEDTLS_PK_USE_PSA_EC_DATA */
+ /* Opaque key is created by the user (externally from Mbed TLS)
+ * so we assume it already has the right algorithm and flags
+ * set. Just copy its ID as reference. */
+ ssl->handshake->xxdh_psa_privkey = pk->priv_id;
+ ssl->handshake->xxdh_psa_privkey_is_external = 1;
ret = 0;
break;
+
#if !defined(MBEDTLS_PK_USE_PSA_EC_DATA)
case MBEDTLS_PK_ECKEY:
case MBEDTLS_PK_ECKEY_DH:
@@ -3916,7 +3923,7 @@ static int ssl_parse_client_key_exchange(mbedtls_ssl_context *ssl)
#if defined(MBEDTLS_USE_PSA_CRYPTO)
psa_status_t status = PSA_ERROR_CORRUPTION_DETECTED;
psa_status_t destruction_status = PSA_ERROR_CORRUPTION_DETECTED;
- uint8_t ecpoint_len;
+ size_t ecpoint_len;
mbedtls_ssl_handshake_params *handshake = ssl->handshake;
diff --git a/thirdparty/mbedtls/library/ssl_tls13_client.c b/thirdparty/mbedtls/library/ssl_tls13_client.c
index 7fcc394319..b63b5e63c5 100644
--- a/thirdparty/mbedtls/library/ssl_tls13_client.c
+++ b/thirdparty/mbedtls/library/ssl_tls13_client.c
@@ -666,6 +666,7 @@ static int ssl_tls13_write_psk_key_exchange_modes_ext(mbedtls_ssl_context *ssl,
return 0;
}
+#if defined(MBEDTLS_SSL_SESSION_TICKETS)
static psa_algorithm_t ssl_tls13_get_ciphersuite_hash_alg(int ciphersuite)
{
const mbedtls_ssl_ciphersuite_t *ciphersuite_info = NULL;
@@ -678,7 +679,6 @@ static psa_algorithm_t ssl_tls13_get_ciphersuite_hash_alg(int ciphersuite)
return PSA_ALG_NONE;
}
-#if defined(MBEDTLS_SSL_SESSION_TICKETS)
static int ssl_tls13_has_configured_ticket(mbedtls_ssl_context *ssl)
{
mbedtls_ssl_session *session = ssl->session_negotiate;
@@ -1141,6 +1141,11 @@ int mbedtls_ssl_tls13_write_client_hello_exts(mbedtls_ssl_context *ssl,
*out_len = 0;
+ ret = mbedtls_ssl_tls13_crypto_init(ssl);
+ if (ret != 0) {
+ return ret;
+ }
+
/* Write supported_versions extension
*
* Supported Versions Extension is mandatory with TLS 1.3.
diff --git a/thirdparty/mbedtls/library/ssl_tls13_generic.c b/thirdparty/mbedtls/library/ssl_tls13_generic.c
index d448a054a9..b6d09788ba 100644
--- a/thirdparty/mbedtls/library/ssl_tls13_generic.c
+++ b/thirdparty/mbedtls/library/ssl_tls13_generic.c
@@ -27,7 +27,6 @@
#include "psa/crypto.h"
#include "psa_util_internal.h"
-#if defined(MBEDTLS_SSL_TLS1_3_KEY_EXCHANGE_MODE_SOME_EPHEMERAL_ENABLED)
/* Define a local translating function to save code size by not using too many
* arguments in each translating place. */
static int local_err_translation(psa_status_t status)
@@ -37,7 +36,16 @@ static int local_err_translation(psa_status_t status)
psa_generic_status_to_mbedtls);
}
#define PSA_TO_MBEDTLS_ERR(status) local_err_translation(status)
-#endif
+
+int mbedtls_ssl_tls13_crypto_init(mbedtls_ssl_context *ssl)
+{
+ psa_status_t status = psa_crypto_init();
+ if (status != PSA_SUCCESS) {
+ (void) ssl; // unused when debugging is disabled
+ MBEDTLS_SSL_DEBUG_RET(1, "psa_crypto_init", status);
+ }
+ return PSA_TO_MBEDTLS_ERR(status);
+}
const uint8_t mbedtls_ssl_tls13_hello_retry_request_magic[
MBEDTLS_SERVER_HELLO_RANDOM_LEN] =
@@ -193,10 +201,12 @@ static void ssl_tls13_create_verify_structure(const unsigned char *transcript_ha
idx = 64;
if (from == MBEDTLS_SSL_IS_CLIENT) {
- memcpy(verify_buffer + idx, MBEDTLS_SSL_TLS1_3_LBL_WITH_LEN(client_cv));
+ memcpy(verify_buffer + idx, mbedtls_ssl_tls13_labels.client_cv,
+ MBEDTLS_SSL_TLS1_3_LBL_LEN(client_cv));
idx += MBEDTLS_SSL_TLS1_3_LBL_LEN(client_cv);
} else { /* from == MBEDTLS_SSL_IS_SERVER */
- memcpy(verify_buffer + idx, MBEDTLS_SSL_TLS1_3_LBL_WITH_LEN(server_cv));
+ memcpy(verify_buffer + idx, mbedtls_ssl_tls13_labels.server_cv,
+ MBEDTLS_SSL_TLS1_3_LBL_LEN(server_cv));
idx += MBEDTLS_SSL_TLS1_3_LBL_LEN(server_cv);
}
@@ -470,6 +480,7 @@ int mbedtls_ssl_tls13_parse_certificate(mbedtls_ssl_context *ssl,
mbedtls_free(ssl->session_negotiate->peer_cert);
}
+ /* This is used by ssl_tls13_validate_certificate() */
if (certificate_list_len == 0) {
ssl->session_negotiate->peer_cert = NULL;
ret = 0;
@@ -625,25 +636,13 @@ int mbedtls_ssl_tls13_parse_certificate(mbedtls_ssl_context *ssl,
MBEDTLS_CHECK_RETURN_CRITICAL
static int ssl_tls13_validate_certificate(mbedtls_ssl_context *ssl)
{
- int ret = 0;
- int authmode = MBEDTLS_SSL_VERIFY_REQUIRED;
- mbedtls_x509_crt *ca_chain;
- mbedtls_x509_crl *ca_crl;
- const char *ext_oid;
- size_t ext_len;
- uint32_t verify_result = 0;
-
- /* If SNI was used, overwrite authentication mode
- * from the configuration. */
-#if defined(MBEDTLS_SSL_SRV_C)
- if (ssl->conf->endpoint == MBEDTLS_SSL_IS_SERVER) {
-#if defined(MBEDTLS_SSL_SERVER_NAME_INDICATION)
- if (ssl->handshake->sni_authmode != MBEDTLS_SSL_VERIFY_UNSET) {
- authmode = ssl->handshake->sni_authmode;
- } else
-#endif
- authmode = ssl->conf->authmode;
- }
+ /* Authmode: precedence order is SNI if used else configuration */
+#if defined(MBEDTLS_SSL_SRV_C) && defined(MBEDTLS_SSL_SERVER_NAME_INDICATION)
+ const int authmode = ssl->handshake->sni_authmode != MBEDTLS_SSL_VERIFY_UNSET
+ ? ssl->handshake->sni_authmode
+ : ssl->conf->authmode;
+#else
+ const int authmode = ssl->conf->authmode;
#endif
/*
@@ -675,6 +674,11 @@ static int ssl_tls13_validate_certificate(mbedtls_ssl_context *ssl)
#endif /* MBEDTLS_SSL_SRV_C */
#if defined(MBEDTLS_SSL_CLI_C)
+ /* Regardless of authmode, the server is not allowed to send an empty
+ * certificate chain. (Last paragraph before 4.4.2.1 in RFC 8446: "The
+ * server's certificate_list MUST always be non-empty.") With authmode
+ * optional/none, we continue the handshake if we can't validate the
+ * server's cert, but we still break it if no certificate was sent. */
if (ssl->conf->endpoint == MBEDTLS_SSL_IS_CLIENT) {
MBEDTLS_SSL_PEND_FATAL_ALERT(MBEDTLS_SSL_ALERT_MSG_NO_CERT,
MBEDTLS_ERR_SSL_FATAL_ALERT_MESSAGE);
@@ -683,114 +687,9 @@ static int ssl_tls13_validate_certificate(mbedtls_ssl_context *ssl)
#endif /* MBEDTLS_SSL_CLI_C */
}
-#if defined(MBEDTLS_SSL_SERVER_NAME_INDICATION)
- if (ssl->handshake->sni_ca_chain != NULL) {
- ca_chain = ssl->handshake->sni_ca_chain;
- ca_crl = ssl->handshake->sni_ca_crl;
- } else
-#endif /* MBEDTLS_SSL_SERVER_NAME_INDICATION */
- {
- ca_chain = ssl->conf->ca_chain;
- ca_crl = ssl->conf->ca_crl;
- }
-
- /*
- * Main check: verify certificate
- */
- ret = mbedtls_x509_crt_verify_with_profile(
- ssl->session_negotiate->peer_cert,
- ca_chain, ca_crl,
- ssl->conf->cert_profile,
- ssl->hostname,
- &verify_result,
- ssl->conf->f_vrfy, ssl->conf->p_vrfy);
-
- if (ret != 0) {
- MBEDTLS_SSL_DEBUG_RET(1, "x509_verify_cert", ret);
- }
-
- /*
- * Secondary checks: always done, but change 'ret' only if it was 0
- */
- if (ssl->conf->endpoint == MBEDTLS_SSL_IS_CLIENT) {
- ext_oid = MBEDTLS_OID_SERVER_AUTH;
- ext_len = MBEDTLS_OID_SIZE(MBEDTLS_OID_SERVER_AUTH);
- } else {
- ext_oid = MBEDTLS_OID_CLIENT_AUTH;
- ext_len = MBEDTLS_OID_SIZE(MBEDTLS_OID_CLIENT_AUTH);
- }
-
- if ((mbedtls_x509_crt_check_key_usage(
- ssl->session_negotiate->peer_cert,
- MBEDTLS_X509_KU_DIGITAL_SIGNATURE) != 0) ||
- (mbedtls_x509_crt_check_extended_key_usage(
- ssl->session_negotiate->peer_cert,
- ext_oid, ext_len) != 0)) {
- MBEDTLS_SSL_DEBUG_MSG(1, ("bad certificate (usage extensions)"));
- if (ret == 0) {
- ret = MBEDTLS_ERR_SSL_BAD_CERTIFICATE;
- }
- }
-
- /* mbedtls_x509_crt_verify_with_profile is supposed to report a
- * verification failure through MBEDTLS_ERR_X509_CERT_VERIFY_FAILED,
- * with details encoded in the verification flags. All other kinds
- * of error codes, including those from the user provided f_vrfy
- * functions, are treated as fatal and lead to a failure of
- * mbedtls_ssl_tls13_parse_certificate even if verification was optional.
- */
- if (authmode == MBEDTLS_SSL_VERIFY_OPTIONAL &&
- (ret == MBEDTLS_ERR_X509_CERT_VERIFY_FAILED ||
- ret == MBEDTLS_ERR_SSL_BAD_CERTIFICATE)) {
- ret = 0;
- }
-
- if (ca_chain == NULL && authmode == MBEDTLS_SSL_VERIFY_REQUIRED) {
- MBEDTLS_SSL_DEBUG_MSG(1, ("got no CA chain"));
- ret = MBEDTLS_ERR_SSL_CA_CHAIN_REQUIRED;
- }
-
- if (ret != 0) {
- /* The certificate may have been rejected for several reasons.
- Pick one and send the corresponding alert. Which alert to send
- may be a subject of debate in some cases. */
- if (verify_result & MBEDTLS_X509_BADCERT_OTHER) {
- MBEDTLS_SSL_PEND_FATAL_ALERT(
- MBEDTLS_SSL_ALERT_MSG_ACCESS_DENIED, ret);
- } else if (verify_result & MBEDTLS_X509_BADCERT_CN_MISMATCH) {
- MBEDTLS_SSL_PEND_FATAL_ALERT(MBEDTLS_SSL_ALERT_MSG_BAD_CERT, ret);
- } else if (verify_result & (MBEDTLS_X509_BADCERT_KEY_USAGE |
- MBEDTLS_X509_BADCERT_EXT_KEY_USAGE |
- MBEDTLS_X509_BADCERT_NS_CERT_TYPE |
- MBEDTLS_X509_BADCERT_BAD_PK |
- MBEDTLS_X509_BADCERT_BAD_KEY)) {
- MBEDTLS_SSL_PEND_FATAL_ALERT(
- MBEDTLS_SSL_ALERT_MSG_UNSUPPORTED_CERT, ret);
- } else if (verify_result & MBEDTLS_X509_BADCERT_EXPIRED) {
- MBEDTLS_SSL_PEND_FATAL_ALERT(
- MBEDTLS_SSL_ALERT_MSG_CERT_EXPIRED, ret);
- } else if (verify_result & MBEDTLS_X509_BADCERT_REVOKED) {
- MBEDTLS_SSL_PEND_FATAL_ALERT(
- MBEDTLS_SSL_ALERT_MSG_CERT_REVOKED, ret);
- } else if (verify_result & MBEDTLS_X509_BADCERT_NOT_TRUSTED) {
- MBEDTLS_SSL_PEND_FATAL_ALERT(MBEDTLS_SSL_ALERT_MSG_UNKNOWN_CA, ret);
- } else {
- MBEDTLS_SSL_PEND_FATAL_ALERT(
- MBEDTLS_SSL_ALERT_MSG_CERT_UNKNOWN, ret);
- }
- }
-
-#if defined(MBEDTLS_DEBUG_C)
- if (verify_result != 0) {
- MBEDTLS_SSL_DEBUG_MSG(3, ("! Certificate verification flags %08x",
- (unsigned int) verify_result));
- } else {
- MBEDTLS_SSL_DEBUG_MSG(3, ("Certificate verification flags clear"));
- }
-#endif /* MBEDTLS_DEBUG_C */
-
- ssl->session_negotiate->verify_result = verify_result;
- return ret;
+ return mbedtls_ssl_verify_certificate(ssl, authmode,
+ ssl->session_negotiate->peer_cert,
+ NULL, NULL);
}
#else /* MBEDTLS_SSL_KEEP_PEER_CERTIFICATE */
MBEDTLS_CHECK_RETURN_CRITICAL
@@ -1482,9 +1381,11 @@ int mbedtls_ssl_tls13_check_early_data_len(mbedtls_ssl_context *ssl,
ssl->total_early_data_size)) {
MBEDTLS_SSL_DEBUG_MSG(
- 2, ("EarlyData: Too much early data received, %u + %" MBEDTLS_PRINTF_SIZET " > %u",
- ssl->total_early_data_size, early_data_len,
- ssl->session_negotiate->max_early_data_size));
+ 2, ("EarlyData: Too much early data received, "
+ "%lu + %" MBEDTLS_PRINTF_SIZET " > %lu",
+ (unsigned long) ssl->total_early_data_size,
+ early_data_len,
+ (unsigned long) ssl->session_negotiate->max_early_data_size));
MBEDTLS_SSL_PEND_FATAL_ALERT(
MBEDTLS_SSL_ALERT_MSG_UNEXPECTED_MESSAGE,
diff --git a/thirdparty/mbedtls/library/ssl_tls13_server.c b/thirdparty/mbedtls/library/ssl_tls13_server.c
index 2760d76a5d..693edc7b0b 100644
--- a/thirdparty/mbedtls/library/ssl_tls13_server.c
+++ b/thirdparty/mbedtls/library/ssl_tls13_server.c
@@ -92,8 +92,9 @@ static void ssl_tls13_select_ciphersuite(
return;
}
- MBEDTLS_SSL_DEBUG_MSG(2, ("No matched ciphersuite, psk_ciphersuite_id=%x, psk_hash_alg=%x",
- (unsigned) psk_ciphersuite_id, psk_hash_alg));
+ MBEDTLS_SSL_DEBUG_MSG(2, ("No matched ciphersuite, psk_ciphersuite_id=%x, psk_hash_alg=%lx",
+ (unsigned) psk_ciphersuite_id,
+ (unsigned long) psk_hash_alg));
}
#if defined(MBEDTLS_SSL_TLS1_3_KEY_EXCHANGE_MODE_SOME_PSK_ENABLED)
@@ -172,12 +173,12 @@ static int ssl_tls13_parse_key_exchange_modes_ext(mbedtls_ssl_context *ssl,
#define SSL_TLS1_3_PSK_IDENTITY_MATCH_BUT_PSK_NOT_USABLE 1
#define SSL_TLS1_3_PSK_IDENTITY_MATCH 0
-#if defined(MBEDTLS_SSL_SESSION_TICKETS)
MBEDTLS_CHECK_RETURN_CRITICAL
static int ssl_tls13_key_exchange_is_psk_available(mbedtls_ssl_context *ssl);
MBEDTLS_CHECK_RETURN_CRITICAL
static int ssl_tls13_key_exchange_is_psk_ephemeral_available(mbedtls_ssl_context *ssl);
+#if defined(MBEDTLS_SSL_SESSION_TICKETS)
MBEDTLS_CHECK_RETURN_CRITICAL
static int ssl_tls13_offered_psks_check_identity_match_ticket(
mbedtls_ssl_context *ssl,
@@ -575,10 +576,8 @@ static int ssl_tls13_parse_pre_shared_key_ext(
psa_algorithm_t psk_hash_alg;
int allowed_key_exchange_modes;
-#if defined(MBEDTLS_SSL_SESSION_TICKETS)
mbedtls_ssl_session session;
mbedtls_ssl_session_init(&session);
-#endif
MBEDTLS_SSL_CHK_BUF_READ_PTR(p_identity_len, identities_end, 2 + 1 + 4);
identity_len = MBEDTLS_GET_UINT16_BE(p_identity_len, 0);
@@ -1356,19 +1355,23 @@ static int ssl_tls13_parse_client_hello(mbedtls_ssl_context *ssl,
* compression methods and the length of the extensions.
*
* cipher_suites cipher_suites_len bytes
- * legacy_compression_methods 2 bytes
- * extensions_len 2 bytes
+ * legacy_compression_methods length 1 byte
*/
- MBEDTLS_SSL_CHK_BUF_READ_PTR(p, end, cipher_suites_len + 2 + 2);
+ MBEDTLS_SSL_CHK_BUF_READ_PTR(p, end, cipher_suites_len + 1);
p += cipher_suites_len;
cipher_suites_end = p;
+ /* Check if we have enough data for legacy_compression_methods
+ * and the length of the extensions (2 bytes).
+ */
+ MBEDTLS_SSL_CHK_BUF_READ_PTR(p + 1, end, p[0] + 2);
+
/*
* Search for the supported versions extension and parse it to determine
* if the client supports TLS 1.3.
*/
ret = mbedtls_ssl_tls13_is_supported_versions_ext_present_in_exts(
- ssl, p + 2, end,
+ ssl, p + 1 + p[0], end,
&supported_versions_data, &supported_versions_data_end);
if (ret < 0) {
MBEDTLS_SSL_DEBUG_RET(1,
@@ -1409,6 +1412,12 @@ static int ssl_tls13_parse_client_hello(mbedtls_ssl_context *ssl,
ssl->session_negotiate->tls_version = MBEDTLS_SSL_VERSION_TLS1_3;
ssl->session_negotiate->endpoint = ssl->conf->endpoint;
+ /* Before doing any crypto, make sure we can. */
+ ret = mbedtls_ssl_tls13_crypto_init(ssl);
+ if (ret != 0) {
+ return ret;
+ }
+
/*
* We are negotiating the version 1.3 of the protocol. Do what we have
* postponed: copy of the client random bytes, copy of the legacy session
@@ -3109,6 +3118,7 @@ static int ssl_tls13_handshake_wrapup(mbedtls_ssl_context *ssl)
return 0;
}
+#if defined(MBEDTLS_SSL_SESSION_TICKETS)
/*
* Handler for MBEDTLS_SSL_TLS1_3_NEW_SESSION_TICKET
*/
@@ -3138,7 +3148,6 @@ static int ssl_tls13_write_new_session_ticket_coordinate(mbedtls_ssl_context *ss
return SSL_NEW_SESSION_TICKET_WRITE;
}
-#if defined(MBEDTLS_SSL_SESSION_TICKETS)
MBEDTLS_CHECK_RETURN_CRITICAL
static int ssl_tls13_prepare_new_session_ticket(mbedtls_ssl_context *ssl,
unsigned char *ticket_nonce,
diff --git a/thirdparty/mbedtls/library/version_features.c b/thirdparty/mbedtls/library/version_features.c
index 406161d4c7..f542d9808f 100644
--- a/thirdparty/mbedtls/library/version_features.c
+++ b/thirdparty/mbedtls/library/version_features.c
@@ -423,6 +423,9 @@ static const char * const features[] = {
#if defined(MBEDTLS_PSA_CRYPTO_SPM)
"PSA_CRYPTO_SPM", //no-check-names
#endif /* MBEDTLS_PSA_CRYPTO_SPM */
+#if defined(MBEDTLS_PSA_KEY_STORE_DYNAMIC)
+ "PSA_KEY_STORE_DYNAMIC", //no-check-names
+#endif /* MBEDTLS_PSA_KEY_STORE_DYNAMIC */
#if defined(MBEDTLS_PSA_P256M_DRIVER_ENABLED)
"PSA_P256M_DRIVER_ENABLED", //no-check-names
#endif /* MBEDTLS_PSA_P256M_DRIVER_ENABLED */
diff --git a/thirdparty/mbedtls/library/x509_crt.c b/thirdparty/mbedtls/library/x509_crt.c
index 2fd56fbd79..53cdcf0266 100644
--- a/thirdparty/mbedtls/library/x509_crt.c
+++ b/thirdparty/mbedtls/library/x509_crt.c
@@ -48,7 +48,9 @@
#if defined(MBEDTLS_HAVE_TIME)
#if defined(_WIN32) && !defined(EFIX64) && !defined(EFI32)
+#ifndef WIN32_LEAN_AND_MEAN
#define WIN32_LEAN_AND_MEAN
+#endif
#include <windows.h>
#else
#include <time.h>
diff --git a/thirdparty/mbedtls/library/x509write_crt.c b/thirdparty/mbedtls/library/x509write_crt.c
index 72f5a10a17..56f23c9fab 100644
--- a/thirdparty/mbedtls/library/x509write_crt.c
+++ b/thirdparty/mbedtls/library/x509write_crt.c
@@ -46,6 +46,10 @@ void mbedtls_x509write_crt_init(mbedtls_x509write_cert *ctx)
void mbedtls_x509write_crt_free(mbedtls_x509write_cert *ctx)
{
+ if (ctx == NULL) {
+ return;
+ }
+
mbedtls_asn1_free_named_data_list(&ctx->subject);
mbedtls_asn1_free_named_data_list(&ctx->issuer);
mbedtls_asn1_free_named_data_list(&ctx->extensions);
diff --git a/thirdparty/mbedtls/library/x509write_csr.c b/thirdparty/mbedtls/library/x509write_csr.c
index d3ddbcc03d..0d6f6bb1d3 100644
--- a/thirdparty/mbedtls/library/x509write_csr.c
+++ b/thirdparty/mbedtls/library/x509write_csr.c
@@ -43,6 +43,10 @@ void mbedtls_x509write_csr_init(mbedtls_x509write_csr *ctx)
void mbedtls_x509write_csr_free(mbedtls_x509write_csr *ctx)
{
+ if (ctx == NULL) {
+ return;
+ }
+
mbedtls_asn1_free_named_data_list(&ctx->subject);
mbedtls_asn1_free_named_data_list(&ctx->extensions);
diff --git a/thirdparty/mbedtls/patches/msvc-redeclaration-bug.diff b/thirdparty/mbedtls/patches/msvc-redeclaration-bug.diff
index c5f1970223..3a15928fe1 100644
--- a/thirdparty/mbedtls/patches/msvc-redeclaration-bug.diff
+++ b/thirdparty/mbedtls/patches/msvc-redeclaration-bug.diff
@@ -1,5 +1,5 @@
diff --git a/thirdparty/mbedtls/include/psa/crypto.h b/thirdparty/mbedtls/include/psa/crypto.h
-index 92f9c824e9..1cc2e7e729 100644
+index 2bbcea3ee0..96baf8f3ed 100644
--- a/thirdparty/mbedtls/include/psa/crypto.h
+++ b/thirdparty/mbedtls/include/psa/crypto.h
@@ -107,7 +107,9 @@ psa_status_t psa_crypto_init(void);
@@ -12,7 +12,7 @@ index 92f9c824e9..1cc2e7e729 100644
/** Declare a key as persistent and set its key identifier.
*
-@@ -333,7 +335,9 @@ static void psa_set_key_bits(psa_key_attributes_t *attributes,
+@@ -336,7 +338,9 @@ static void psa_set_key_bits(psa_key_attributes_t *attributes,
*
* \return The key type stored in the attribute structure.
*/
@@ -22,7 +22,7 @@ index 92f9c824e9..1cc2e7e729 100644
/** Retrieve the key size from key attributes.
*
-@@ -936,7 +940,9 @@ typedef struct psa_hash_operation_s psa_hash_operation_t;
+@@ -939,7 +943,9 @@ typedef struct psa_hash_operation_s psa_hash_operation_t;
/** Return an initial value for a hash operation object.
*/
@@ -32,7 +32,7 @@ index 92f9c824e9..1cc2e7e729 100644
/** Set up a multipart hash operation.
*
-@@ -1295,7 +1301,9 @@ typedef struct psa_mac_operation_s psa_mac_operation_t;
+@@ -1298,7 +1304,9 @@ typedef struct psa_mac_operation_s psa_mac_operation_t;
/** Return an initial value for a MAC operation object.
*/
@@ -42,7 +42,7 @@ index 92f9c824e9..1cc2e7e729 100644
/** Set up a multipart MAC calculation operation.
*
-@@ -1708,7 +1716,9 @@ typedef struct psa_cipher_operation_s psa_cipher_operation_t;
+@@ -1711,7 +1719,9 @@ typedef struct psa_cipher_operation_s psa_cipher_operation_t;
/** Return an initial value for a cipher operation object.
*/
@@ -52,7 +52,7 @@ index 92f9c824e9..1cc2e7e729 100644
/** Set the key for a multipart symmetric encryption operation.
*
-@@ -2226,7 +2236,9 @@ typedef struct psa_aead_operation_s psa_aead_operation_t;
+@@ -2229,7 +2239,9 @@ typedef struct psa_aead_operation_s psa_aead_operation_t;
/** Return an initial value for an AEAD operation object.
*/
@@ -62,7 +62,7 @@ index 92f9c824e9..1cc2e7e729 100644
/** Set the key for a multipart authenticated encryption operation.
*
-@@ -3213,7 +3225,9 @@ typedef struct psa_key_derivation_s psa_key_derivation_operation_t;
+@@ -3216,7 +3228,9 @@ typedef struct psa_key_derivation_s psa_key_derivation_operation_t;
/** Return an initial value for a key derivation operation object.
*/
@@ -73,10 +73,10 @@ index 92f9c824e9..1cc2e7e729 100644
/** Set up a key derivation operation.
*
diff --git a/thirdparty/mbedtls/include/psa/crypto_extra.h b/thirdparty/mbedtls/include/psa/crypto_extra.h
-index 6ed1f6c43a..2686b9d74d 100644
+index 0cf42c6055..d276cd4c7f 100644
--- a/thirdparty/mbedtls/include/psa/crypto_extra.h
+++ b/thirdparty/mbedtls/include/psa/crypto_extra.h
-@@ -915,7 +915,9 @@ typedef struct psa_pake_cipher_suite_s psa_pake_cipher_suite_t;
+@@ -923,7 +923,9 @@ typedef struct psa_pake_cipher_suite_s psa_pake_cipher_suite_t;
/** Return an initial value for a PAKE cipher suite object.
*/
@@ -86,7 +86,7 @@ index 6ed1f6c43a..2686b9d74d 100644
/** Retrieve the PAKE algorithm from a PAKE cipher suite.
*
-@@ -1048,7 +1050,9 @@ typedef struct psa_jpake_computation_stage_s psa_jpake_computation_stage_t;
+@@ -1056,7 +1058,9 @@ typedef struct psa_jpake_computation_stage_s psa_jpake_computation_stage_t;
/** Return an initial value for a PAKE operation object.
*/
diff --git a/thirdparty/mbedtls/patches/no-flexible-arrays.diff b/thirdparty/mbedtls/patches/no-flexible-arrays.diff
deleted file mode 100644
index 87fd06f1e3..0000000000
--- a/thirdparty/mbedtls/patches/no-flexible-arrays.diff
+++ /dev/null
@@ -1,132 +0,0 @@
-diff --git a/thirdparty/mbedtls/include/psa/crypto.h b/thirdparty/mbedtls/include/psa/crypto.h
-index 7083bd911b..92f9c824e9 100644
---- a/thirdparty/mbedtls/include/psa/crypto.h
-+++ b/thirdparty/mbedtls/include/psa/crypto.h
-@@ -3834,12 +3834,14 @@ psa_status_t psa_key_derivation_output_key(
- * It is implementation-dependent whether a failure to initialize
- * results in this error code.
- */
-+#ifndef __cplusplus
- psa_status_t psa_key_derivation_output_key_ext(
- const psa_key_attributes_t *attributes,
- psa_key_derivation_operation_t *operation,
- const psa_key_production_parameters_t *params,
- size_t params_data_length,
- mbedtls_svc_key_id_t *key);
-+#endif
-
- /** Compare output data from a key derivation operation to an expected value.
- *
-@@ -4180,10 +4182,12 @@ psa_status_t psa_generate_key(const psa_key_attributes_t *attributes,
- * It is implementation-dependent whether a failure to initialize
- * results in this error code.
- */
-+#ifndef __cplusplus
- psa_status_t psa_generate_key_ext(const psa_key_attributes_t *attributes,
- const psa_key_production_parameters_t *params,
- size_t params_data_length,
- mbedtls_svc_key_id_t *key);
-+#endif
-
- /**@}*/
-
-diff --git a/thirdparty/mbedtls/include/psa/crypto_struct.h b/thirdparty/mbedtls/include/psa/crypto_struct.h
-index 3913551aa8..e2c227b2eb 100644
---- a/thirdparty/mbedtls/include/psa/crypto_struct.h
-+++ b/thirdparty/mbedtls/include/psa/crypto_struct.h
-@@ -223,11 +223,13 @@ static inline struct psa_key_derivation_s psa_key_derivation_operation_init(
- return v;
- }
-
-+#ifndef __cplusplus
- struct psa_key_production_parameters_s {
- /* Future versions may add other fields in this structure. */
- uint32_t flags;
- uint8_t data[];
- };
-+#endif
-
- /** The default production parameters for key generation or key derivation.
- *
-diff --git a/thirdparty/mbedtls/include/psa/crypto_types.h b/thirdparty/mbedtls/include/psa/crypto_types.h
-index c21bad86cc..a36b6ee65d 100644
---- a/thirdparty/mbedtls/include/psa/crypto_types.h
-+++ b/thirdparty/mbedtls/include/psa/crypto_types.h
-@@ -477,7 +477,9 @@ typedef uint16_t psa_key_derivation_step_t;
- * - Other key types: reserved for future use. \c flags must be 0.
- *
- */
-+#ifndef __cplusplus
- typedef struct psa_key_production_parameters_s psa_key_production_parameters_t;
-+#endif
-
- /**@}*/
-
-diff --git a/thirdparty/mbedtls/library/psa_crypto_core.h b/thirdparty/mbedtls/library/psa_crypto_core.h
-index 9462d2e8be..c059162efe 100644
---- a/thirdparty/mbedtls/library/psa_crypto_core.h
-+++ b/thirdparty/mbedtls/library/psa_crypto_core.h
-@@ -351,9 +351,11 @@ psa_status_t psa_export_public_key_internal(
- * \param[in] params The key production parameters to check.
- * \param params_data_length Size of `params->data` in bytes.
- */
-+#ifndef __cplusplus
- int psa_key_production_parameters_are_default(
- const psa_key_production_parameters_t *params,
- size_t params_data_length);
-+#endif
-
- /**
- * \brief Generate a key.
-@@ -378,12 +380,14 @@ int psa_key_production_parameters_are_default(
- * \retval #PSA_ERROR_BUFFER_TOO_SMALL
- * The size of \p key_buffer is too small.
- */
-+#ifndef __cplusplus
- psa_status_t psa_generate_key_internal(const psa_key_attributes_t *attributes,
- const psa_key_production_parameters_t *params,
- size_t params_data_length,
- uint8_t *key_buffer,
- size_t key_buffer_size,
- size_t *key_buffer_length);
-+#endif
-
- /** Sign a message with a private key. For hash-and-sign algorithms,
- * this includes the hashing step.
-diff --git a/thirdparty/mbedtls/library/psa_crypto_driver_wrappers.h b/thirdparty/mbedtls/library/psa_crypto_driver_wrappers.h
-index ea6aee32eb..6919971aca 100644
---- a/thirdparty/mbedtls/library/psa_crypto_driver_wrappers.h
-+++ b/thirdparty/mbedtls/library/psa_crypto_driver_wrappers.h
-@@ -728,6 +728,7 @@ static inline psa_status_t psa_driver_wrapper_get_key_buffer_size_from_key_data(
- }
- }
-
-+#ifndef __cplusplus
- static inline psa_status_t psa_driver_wrapper_generate_key(
- const psa_key_attributes_t *attributes,
- const psa_key_production_parameters_t *params, size_t params_data_length,
-@@ -832,6 +833,7 @@ static inline psa_status_t psa_driver_wrapper_generate_key(
-
- return( status );
- }
-+#endif
-
- static inline psa_status_t psa_driver_wrapper_import_key(
- const psa_key_attributes_t *attributes,
-diff --git a/thirdparty/mbedtls/library/psa_crypto_rsa.h b/thirdparty/mbedtls/library/psa_crypto_rsa.h
-index ffeef26be1..6d695ddf50 100644
---- a/thirdparty/mbedtls/library/psa_crypto_rsa.h
-+++ b/thirdparty/mbedtls/library/psa_crypto_rsa.h
-@@ -130,10 +130,12 @@ psa_status_t mbedtls_psa_rsa_export_public_key(
- * \retval #PSA_ERROR_BUFFER_TOO_SMALL
- * The size of \p key_buffer is too small.
- */
-+#ifndef __cplusplus
- psa_status_t mbedtls_psa_rsa_generate_key(
- const psa_key_attributes_t *attributes,
- const psa_key_production_parameters_t *params, size_t params_data_length,
- uint8_t *key_buffer, size_t key_buffer_size, size_t *key_buffer_length);
-+#endif
-
- /** Sign an already-calculated hash with an RSA private key.
- *
diff --git a/thirdparty/misc/patches/qoa-min-fix.patch b/thirdparty/misc/patches/qoa-min-fix.patch
index 38303a1521..6008b5f8bc 100644
--- a/thirdparty/misc/patches/qoa-min-fix.patch
+++ b/thirdparty/misc/patches/qoa-min-fix.patch
@@ -1,5 +1,5 @@
diff --git a/qoa.h b/qoa.h
-index 592082933a..c890b88bd6 100644
+index cfed266bef..23612bb0bf 100644
--- a/qoa.h
+++ b/qoa.h
@@ -140,14 +140,14 @@ typedef struct {
@@ -24,19 +24,15 @@ index 592082933a..c890b88bd6 100644
#ifndef QOA_NO_STDIO
-@@ -394,9 +394,9 @@ unsigned int qoa_encode_frame(const short *sample_data, qoa_desc *qoa, unsigned
- #ifdef QOA_RECORD_TOTAL_ERROR
+@@ -395,7 +395,7 @@ unsigned int qoa_encode_frame(const short *sample_data, qoa_desc *qoa, unsigned
qoa_uint64_t best_error = -1;
#endif
-- qoa_uint64_t best_slice;
+ qoa_uint64_t best_slice = 0;
- qoa_lms_t best_lms;
-- int best_scalefactor;
-+ qoa_uint64_t best_slice = -1;
-+ qoa_lms_t best_lms = {{-1, -1, -1, -1}, {-1, -1, -1, -1}};
-+ int best_scalefactor = -1;
++ qoa_lms_t best_lms = {};
+ int best_scalefactor = 0;
for (int sfi = 0; sfi < 16; sfi++) {
- /* There is a strong correlation between the scalefactors of
@@ -500,7 +500,7 @@ void *qoa_encode(const short *sample_data, qoa_desc *qoa, unsigned int *out_len)
num_frames * QOA_LMS_LEN * 4 * qoa->channels + /* 4 * 4 bytes lms state per channel */
num_slices * 8 * qoa->channels; /* 8 byte slices */
diff --git a/thirdparty/misc/qoa.h b/thirdparty/misc/qoa.h
index c890b88bd6..23612bb0bf 100644
--- a/thirdparty/misc/qoa.h
+++ b/thirdparty/misc/qoa.h
@@ -394,9 +394,9 @@ unsigned int qoa_encode_frame(const short *sample_data, qoa_desc *qoa, unsigned
#ifdef QOA_RECORD_TOTAL_ERROR
qoa_uint64_t best_error = -1;
#endif
- qoa_uint64_t best_slice = -1;
- qoa_lms_t best_lms = {{-1, -1, -1, -1}, {-1, -1, -1, -1}};
- int best_scalefactor = -1;
+ qoa_uint64_t best_slice = 0;
+ qoa_lms_t best_lms = {};
+ int best_scalefactor = 0;
for (int sfi = 0; sfi < 16; sfi++) {
/* There is a strong correlation between the scalefactors of
diff --git a/thirdparty/thorvg/inc/config.h b/thirdparty/thorvg/inc/config.h
index 67ebc9381e..654d70f918 100644
--- a/thirdparty/thorvg/inc/config.h
+++ b/thirdparty/thorvg/inc/config.h
@@ -15,5 +15,5 @@
// For internal debugging:
//#define THORVG_LOG_ENABLED
-#define THORVG_VERSION_STRING "0.14.7"
+#define THORVG_VERSION_STRING "0.14.9"
#endif
diff --git a/thirdparty/thorvg/patches/pr2716-text-drawing-reliability.patch b/thirdparty/thorvg/patches/pr2716-text-drawing-reliability.patch
new file mode 100644
index 0000000000..c79f7c63d6
--- /dev/null
+++ b/thirdparty/thorvg/patches/pr2716-text-drawing-reliability.patch
@@ -0,0 +1,25 @@
+From 41d67213607e7ff20b7a3ca833f1cfde9780da65 Mon Sep 17 00:00:00 2001
+From: Hermet Park <hermet@lottiefiles.com>
+Date: Sat, 7 Sep 2024 01:35:09 +0900
+Subject: [PATCH] renderer: ++reliability in text drawing
+
+Allow the canvas to pass through
+even if text elements are not properly supported.
+
+issue: https://github.com/thorvg/thorvg/issues/2715
+---
+ src/renderer/tvgText.h | 1 +
+ 1 file changed, 1 insertion(+)
+
+diff --git a/thirdparty/thorvg/src/renderer/tvgText.h b/thirdparty/thorvg/src/renderer/tvgText.h
+index 746b85bea6..55d33ffd4b 100644
+--- a/thirdparty/thorvg/src/renderer/tvgText.h
++++ b/thirdparty/thorvg/src/renderer/tvgText.h
+@@ -89,6 +89,7 @@ struct Text::Impl
+
+ bool render(RenderMethod* renderer)
+ {
++ if (!loader) return true;
+ renderer->blend(paint->blend(), true);
+ return PP(shape)->render(renderer);
+ }
diff --git a/thirdparty/thorvg/patches/revert-tvgLines-bezier-precision-change.patch b/thirdparty/thorvg/patches/revert-tvgLines-bezier-precision-change.patch
new file mode 100644
index 0000000000..dd6c8ba5e7
--- /dev/null
+++ b/thirdparty/thorvg/patches/revert-tvgLines-bezier-precision-change.patch
@@ -0,0 +1,13 @@
+diff --git a/thirdparty/thorvg/src/common/tvgLines.cpp b/thirdparty/thorvg/src/common/tvgLines.cpp
+index 49d992f127..9d704900a5 100644
+--- a/thirdparty/thorvg/src/common/tvgLines.cpp
++++ b/thirdparty/thorvg/src/common/tvgLines.cpp
+@@ -79,7 +79,7 @@ float _bezAt(const Bezier& bz, float at, float length, LengthFunc lineLengthFunc
+ Bezier left;
+ bezSplitLeft(right, t, left);
+ length = _bezLength(left, lineLengthFunc);
+- if (fabsf(length - at) < BEZIER_EPSILON || fabsf(smallest - biggest) < 1e-3f) {
++ if (fabsf(length - at) < BEZIER_EPSILON || fabsf(smallest - biggest) < BEZIER_EPSILON) {
+ break;
+ }
+ if (length < at) {
diff --git a/thirdparty/thorvg/src/common/tvgLines.cpp b/thirdparty/thorvg/src/common/tvgLines.cpp
index 49d992f127..9d704900a5 100644
--- a/thirdparty/thorvg/src/common/tvgLines.cpp
+++ b/thirdparty/thorvg/src/common/tvgLines.cpp
@@ -79,7 +79,7 @@ float _bezAt(const Bezier& bz, float at, float length, LengthFunc lineLengthFunc
Bezier left;
bezSplitLeft(right, t, left);
length = _bezLength(left, lineLengthFunc);
- if (fabsf(length - at) < BEZIER_EPSILON || fabsf(smallest - biggest) < 1e-3f) {
+ if (fabsf(length - at) < BEZIER_EPSILON || fabsf(smallest - biggest) < BEZIER_EPSILON) {
break;
}
if (length < at) {
diff --git a/thirdparty/thorvg/src/common/tvgMath.cpp b/thirdparty/thorvg/src/common/tvgMath.cpp
index c56b32249f..c03b54e5f8 100644
--- a/thirdparty/thorvg/src/common/tvgMath.cpp
+++ b/thirdparty/thorvg/src/common/tvgMath.cpp
@@ -43,9 +43,8 @@ bool mathInverse(const Matrix* m, Matrix* out)
m->e12 * (m->e21 * m->e33 - m->e23 * m->e31) +
m->e13 * (m->e21 * m->e32 - m->e22 * m->e31);
- if (mathZero(det)) return false;
-
- auto invDet = 1 / det;
+ auto invDet = 1.0f / det;
+ if (std::isinf(invDet)) return false;
out->e11 = (m->e22 * m->e33 - m->e32 * m->e23) * invDet;
out->e12 = (m->e13 * m->e32 - m->e12 * m->e33) * invDet;
@@ -133,3 +132,10 @@ Point operator*(const Point& pt, const Matrix& m)
auto ty = pt.x * m.e21 + pt.y * m.e22 + m.e23;
return {tx, ty};
}
+
+uint8_t mathLerp(const uint8_t &start, const uint8_t &end, float t)
+{
+ auto result = static_cast<int>(start + (end - start) * t);
+ mathClamp(result, 0, 255);
+ return static_cast<uint8_t>(result);
+}
diff --git a/thirdparty/thorvg/src/common/tvgMath.h b/thirdparty/thorvg/src/common/tvgMath.h
index 556ed410ff..50786754a1 100644
--- a/thirdparty/thorvg/src/common/tvgMath.h
+++ b/thirdparty/thorvg/src/common/tvgMath.h
@@ -26,7 +26,7 @@
#define _USE_MATH_DEFINES
#include <float.h>
-#include <math.h>
+#include <cmath>
#include "tvgCommon.h"
#define MATH_PI 3.14159265358979323846f
@@ -68,6 +68,13 @@ static inline bool mathEqual(float a, float b)
}
+template <typename T>
+static inline void mathClamp(T& v, const T& min, const T& max)
+{
+ if (v < min) v = min;
+ else if (v > max) v = max;
+}
+
/************************************************************************/
/* Matrix functions */
/************************************************************************/
@@ -259,5 +266,6 @@ static inline T mathLerp(const T &start, const T &end, float t)
return static_cast<T>(start + (end - start) * t);
}
+uint8_t mathLerp(const uint8_t &start, const uint8_t &end, float t);
#endif //_TVG_MATH_H_
diff --git a/thirdparty/thorvg/src/loaders/svg/tvgSvgLoader.cpp b/thirdparty/thorvg/src/loaders/svg/tvgSvgLoader.cpp
index 197d3cc13a..cccd056a13 100644
--- a/thirdparty/thorvg/src/loaders/svg/tvgSvgLoader.cpp
+++ b/thirdparty/thorvg/src/loaders/svg/tvgSvgLoader.cpp
@@ -588,6 +588,11 @@ static bool _hslToRgb(float hue, float saturation, float brightness, uint8_t* re
float _red = 0, _green = 0, _blue = 0;
uint32_t i = 0;
+ while (hue < 0) hue += 360.0f;
+ hue = fmod(hue, 360.0f);
+ saturation = saturation > 0 ? std::min(saturation, 1.0f) : 0.0f;
+ brightness = brightness > 0 ? std::min(brightness, 1.0f) : 0.0f;
+
if (mathZero(saturation)) _red = _green = _blue = brightness;
else {
if (mathEqual(hue, 360.0)) hue = 0.0f;
@@ -714,7 +719,6 @@ static bool _toColor(const char* str, uint8_t* r, uint8_t* g, uint8_t* b, char**
const char* hue = nullptr;
if (_parseNumber(&content, &hue, &th) && hue) {
const char* saturation = nullptr;
- th = float(uint32_t(th) % 360);
hue = _skipSpace(hue, nullptr);
hue = (char*)_skipComma(hue);
hue = _skipSpace(hue, nullptr);
@@ -3284,6 +3288,7 @@ static void _svgLoaderParserXmlClose(SvgLoaderData* loader, const char* content,
for (unsigned int i = 0; i < sizeof(graphicsTags) / sizeof(graphicsTags[0]); i++) {
if (!strncmp(tagName, graphicsTags[i].tag, sz)) {
loader->currentGraphicsNode = nullptr;
+ if (!strncmp(tagName, "text", 4)) loader->openedTag = OpenedTagType::Other;
loader->stack.pop();
break;
}
@@ -3357,11 +3362,9 @@ static void _svgLoaderParserXmlOpen(SvgLoaderData* loader, const char* content,
node = method(loader, parent, attrs, attrsLength, simpleXmlParseAttributes);
if (node && !empty) {
if (!strcmp(tagName, "text")) loader->openedTag = OpenedTagType::Text;
- else {
- auto defs = _createDefsNode(loader, nullptr, nullptr, 0, nullptr);
- loader->stack.push(defs);
- loader->currentGraphicsNode = node;
- }
+ auto defs = _createDefsNode(loader, nullptr, nullptr, 0, nullptr);
+ loader->stack.push(defs);
+ loader->currentGraphicsNode = node;
}
} else if ((gradientMethod = _findGradientFactory(tagName))) {
SvgStyleGradient* gradient;
@@ -3399,7 +3402,6 @@ static void _svgLoaderParserText(SvgLoaderData* loader, const char* content, uns
auto text = &loader->svgParse->node->node.text;
if (text->text) free(text->text);
text->text = strDuplicate(content, length);
- loader->openedTag = OpenedTagType::Other;
}
diff --git a/thirdparty/thorvg/src/renderer/sw_engine/tvgSwCommon.h b/thirdparty/thorvg/src/renderer/sw_engine/tvgSwCommon.h
index 3229eb39ba..20442c1c20 100644
--- a/thirdparty/thorvg/src/renderer/sw_engine/tvgSwCommon.h
+++ b/thirdparty/thorvg/src/renderer/sw_engine/tvgSwCommon.h
@@ -367,7 +367,7 @@ static inline uint32_t opBlendSrcOver(uint32_t s, TVG_UNUSED uint32_t d, TVG_UNU
}
//TODO: BlendMethod could remove the alpha parameter.
-static inline uint32_t opBlendDifference(uint32_t s, uint32_t d, TVG_UNUSED uint8_t a)
+static inline uint32_t opBlendDifference(uint32_t s, uint32_t d, uint8_t a)
{
//if (s > d) => s - d
//else => d - s
@@ -404,8 +404,7 @@ static inline uint32_t opBlendScreen(uint32_t s, uint32_t d, TVG_UNUSED uint8_t
return JOIN(255, c1, c2, c3);
}
-
-static inline uint32_t opBlendMultiply(uint32_t s, uint32_t d, TVG_UNUSED uint8_t a)
+static inline uint32_t opBlendDirectMultiply(uint32_t s, uint32_t d, uint8_t a)
{
// s * d
auto c1 = MULTIPLY(C1(s), C1(d));
@@ -414,6 +413,10 @@ static inline uint32_t opBlendMultiply(uint32_t s, uint32_t d, TVG_UNUSED uint8_
return JOIN(255, c1, c2, c3);
}
+static inline uint32_t opBlendMultiply(uint32_t s, uint32_t d, uint8_t a)
+{
+ return opBlendDirectMultiply(s, d, a) + ALPHA_BLEND(d, IA(s));
+}
static inline uint32_t opBlendOverlay(uint32_t s, uint32_t d, TVG_UNUSED uint8_t a)
{
diff --git a/thirdparty/thorvg/src/renderer/sw_engine/tvgSwRaster.cpp b/thirdparty/thorvg/src/renderer/sw_engine/tvgSwRaster.cpp
index a6f9d8745a..90d91e17e0 100644
--- a/thirdparty/thorvg/src/renderer/sw_engine/tvgSwRaster.cpp
+++ b/thirdparty/thorvg/src/renderer/sw_engine/tvgSwRaster.cpp
@@ -1383,7 +1383,7 @@ static bool _rasterDirectBlendingImage(SwSurface* surface, const SwImage* image,
*dst = INTERPOLATE(tmp, *dst, A(*src));
}
} else {
- for (auto x = region.min.x; x < region.max.x; ++x, ++dst, ++src) {
+ for (auto x = region.min.x; x < region.max.x; x++, dst++, src++) {
auto tmp = ALPHA_BLEND(*src, opacity);
auto tmp2 = surface->blender(tmp, *dst, 255);
*dst = INTERPOLATE(tmp2, *dst, A(tmp));
diff --git a/thirdparty/thorvg/src/renderer/sw_engine/tvgSwRenderer.cpp b/thirdparty/thorvg/src/renderer/sw_engine/tvgSwRenderer.cpp
index 47a7166115..6470089c7c 100644
--- a/thirdparty/thorvg/src/renderer/sw_engine/tvgSwRenderer.cpp
+++ b/thirdparty/thorvg/src/renderer/sw_engine/tvgSwRenderer.cpp
@@ -100,7 +100,6 @@ struct SwShapeTask : SwTask
return (width * sqrt(transform.e11 * transform.e11 + transform.e12 * transform.e12));
}
-
bool clip(SwRleData* target) override
{
if (shape.fastTrack) rleClipRect(target, &bbox);
@@ -447,7 +446,7 @@ bool SwRenderer::renderShape(RenderData data)
}
-bool SwRenderer::blend(BlendMethod method)
+bool SwRenderer::blend(BlendMethod method, bool direct)
{
if (surface->blendMethod == method) return true;
surface->blendMethod = method;
@@ -460,7 +459,7 @@ bool SwRenderer::blend(BlendMethod method)
surface->blender = opBlendScreen;
break;
case BlendMethod::Multiply:
- surface->blender = opBlendMultiply;
+ surface->blender = direct ? opBlendDirectMultiply : opBlendMultiply;
break;
case BlendMethod::Overlay:
surface->blender = opBlendOverlay;
diff --git a/thirdparty/thorvg/src/renderer/sw_engine/tvgSwRenderer.h b/thirdparty/thorvg/src/renderer/sw_engine/tvgSwRenderer.h
index fcd8ad4620..7aa6d89aaa 100644
--- a/thirdparty/thorvg/src/renderer/sw_engine/tvgSwRenderer.h
+++ b/thirdparty/thorvg/src/renderer/sw_engine/tvgSwRenderer.h
@@ -46,7 +46,7 @@ public:
RenderRegion region(RenderData data) override;
RenderRegion viewport() override;
bool viewport(const RenderRegion& vp) override;
- bool blend(BlendMethod method) override;
+ bool blend(BlendMethod method, bool direct) override;
ColorSpace colorSpace() override;
const Surface* mainSurface() override;
diff --git a/thirdparty/thorvg/src/renderer/sw_engine/tvgSwShape.cpp b/thirdparty/thorvg/src/renderer/sw_engine/tvgSwShape.cpp
index 96c3bc28b9..24c4a9e372 100644
--- a/thirdparty/thorvg/src/renderer/sw_engine/tvgSwShape.cpp
+++ b/thirdparty/thorvg/src/renderer/sw_engine/tvgSwShape.cpp
@@ -210,7 +210,7 @@ static void _dashCubicTo(SwDashStroke& dash, const Point* ctrl1, const Point* ct
}
_outlineCubicTo(*dash.outline, &cur.ctrl1, &cur.ctrl2, &cur.end, transform);
}
- if (dash.curLen < 1 && TO_SWCOORD(len) > 1) {
+ if (dash.curLen < 0.1f && TO_SWCOORD(len) > 1) {
//move to next dash
dash.curIdx = (dash.curIdx + 1) % dash.cnt;
dash.curLen = dash.pattern[dash.curIdx];
diff --git a/thirdparty/thorvg/src/renderer/tvgLoadModule.h b/thirdparty/thorvg/src/renderer/tvgLoadModule.h
index c750683771..1b81d81a4f 100644
--- a/thirdparty/thorvg/src/renderer/tvgLoadModule.h
+++ b/thirdparty/thorvg/src/renderer/tvgLoadModule.h
@@ -101,7 +101,8 @@ struct FontLoader : LoadModule
FontLoader(FileType type) : LoadModule(type) {}
- virtual bool request(Shape* shape, char* text, bool italic = false) = 0;
+ virtual bool request(Shape* shape, char* text) = 0;
+ virtual bool transform(Paint* paint, float fontSize, bool italic) = 0;
};
#endif //_TVG_LOAD_MODULE_H_
diff --git a/thirdparty/thorvg/src/renderer/tvgPaint.cpp b/thirdparty/thorvg/src/renderer/tvgPaint.cpp
index 11cee0c54a..37813b19ef 100644
--- a/thirdparty/thorvg/src/renderer/tvgPaint.cpp
+++ b/thirdparty/thorvg/src/renderer/tvgPaint.cpp
@@ -225,8 +225,6 @@ bool Paint::Impl::render(RenderMethod* renderer)
if (cmp) renderer->beginComposite(cmp, compData->method, compData->target->pImpl->opacity);
- renderer->blend(blendMethod);
-
bool ret;
PAINT_METHOD(ret, render(renderer));
diff --git a/thirdparty/thorvg/src/renderer/tvgPicture.cpp b/thirdparty/thorvg/src/renderer/tvgPicture.cpp
index 68c7a41032..e6653b1c99 100644
--- a/thirdparty/thorvg/src/renderer/tvgPicture.cpp
+++ b/thirdparty/thorvg/src/renderer/tvgPicture.cpp
@@ -73,6 +73,8 @@ bool Picture::Impl::needComposition(uint8_t opacity)
bool Picture::Impl::render(RenderMethod* renderer)
{
bool ret = false;
+ renderer->blend(picture->blend(), true);
+
if (surface) return renderer->renderImage(rd);
else if (paint) {
Compositor* cmp = nullptr;
diff --git a/thirdparty/thorvg/src/renderer/tvgRender.h b/thirdparty/thorvg/src/renderer/tvgRender.h
index b0ee42db8d..f1042884ec 100644
--- a/thirdparty/thorvg/src/renderer/tvgRender.h
+++ b/thirdparty/thorvg/src/renderer/tvgRender.h
@@ -296,7 +296,7 @@ public:
virtual RenderRegion region(RenderData data) = 0;
virtual RenderRegion viewport() = 0;
virtual bool viewport(const RenderRegion& vp) = 0;
- virtual bool blend(BlendMethod method) = 0;
+ virtual bool blend(BlendMethod method, bool direct = false) = 0;
virtual ColorSpace colorSpace() = 0;
virtual const Surface* mainSurface() = 0;
diff --git a/thirdparty/thorvg/src/renderer/tvgScene.h b/thirdparty/thorvg/src/renderer/tvgScene.h
index cb6d179326..190ecd31b9 100644
--- a/thirdparty/thorvg/src/renderer/tvgScene.h
+++ b/thirdparty/thorvg/src/renderer/tvgScene.h
@@ -120,6 +120,8 @@ struct Scene::Impl
Compositor* cmp = nullptr;
auto ret = true;
+ renderer->blend(scene->blend());
+
if (needComp) {
cmp = renderer->target(bounds(renderer), renderer->colorSpace());
renderer->beginComposite(cmp, CompositeMethod::None, opacity);
diff --git a/thirdparty/thorvg/src/renderer/tvgShape.h b/thirdparty/thorvg/src/renderer/tvgShape.h
index b76fde7ced..94704aee67 100644
--- a/thirdparty/thorvg/src/renderer/tvgShape.h
+++ b/thirdparty/thorvg/src/renderer/tvgShape.h
@@ -54,10 +54,13 @@ struct Shape::Impl
Compositor* cmp = nullptr;
bool ret;
+ renderer->blend(shape->blend(), !needComp);
+
if (needComp) {
cmp = renderer->target(bounds(renderer), renderer->colorSpace());
renderer->beginComposite(cmp, CompositeMethod::None, opacity);
}
+
ret = renderer->renderShape(rd);
if (cmp) renderer->endComposite(cmp);
return ret;
diff --git a/thirdparty/thorvg/src/renderer/tvgText.h b/thirdparty/thorvg/src/renderer/tvgText.h
index f6faec2d53..55d33ffd4b 100644
--- a/thirdparty/thorvg/src/renderer/tvgText.h
+++ b/thirdparty/thorvg/src/renderer/tvgText.h
@@ -26,12 +26,7 @@
#include <cstring>
#include "tvgShape.h"
#include "tvgFill.h"
-
-#ifdef THORVG_TTF_LOADER_SUPPORT
- #include "tvgTtfLoader.h"
-#else
- #include "tvgLoader.h"
-#endif
+#include "tvgLoader.h"
struct Text::Impl
{
@@ -69,6 +64,11 @@ struct Text::Impl
auto loader = LoaderMgr::loader(name);
if (!loader) return Result::InsufficientCondition;
+ if (style && strstr(style, "italic")) italic = true;
+ else italic = false;
+
+ fontSize = size;
+
//Same resource has been loaded.
if (this->loader == loader) {
this->loader->sharing--; //make it sure the reference counting.
@@ -78,8 +78,6 @@ struct Text::Impl
}
this->loader = static_cast<FontLoader*>(loader);
- fontSize = size;
- if (style && strstr(style, "italic")) italic = true;
changed = true;
return Result::Success;
}
@@ -91,6 +89,8 @@ struct Text::Impl
bool render(RenderMethod* renderer)
{
+ if (!loader) return true;
+ renderer->blend(paint->blend(), true);
return PP(shape)->render(renderer);
}
@@ -98,13 +98,13 @@ struct Text::Impl
{
if (!loader) return false;
+ loader->request(shape, utf8);
//reload
if (changed) {
- loader->request(shape, utf8, italic);
loader->read();
changed = false;
}
- return loader->resize(shape, fontSize, fontSize);
+ return loader->transform(shape, fontSize, italic);
}
RenderData update(RenderMethod* renderer, const Matrix& transform, Array<RenderData>& clips, uint8_t opacity, RenderUpdateFlag pFlag, TVG_UNUSED bool clipper)
diff --git a/thirdparty/thorvg/update-thorvg.sh b/thirdparty/thorvg/update-thorvg.sh
index 7ff07fe4c2..1ec21f3dd8 100755
--- a/thirdparty/thorvg/update-thorvg.sh
+++ b/thirdparty/thorvg/update-thorvg.sh
@@ -1,16 +1,22 @@
#!/bin/bash -e
-VERSION=0.14.7
+VERSION=0.14.9
+# Uncomment and set a git hash to use specific commit instead of tag.
+#GIT_COMMIT=
-cd thirdparty/thorvg/ || true
+pushd "$(dirname "$0")"
rm -rf AUTHORS LICENSE inc/ src/ *.zip *.tar.gz tmp/
mkdir tmp/ && pushd tmp/
# Release
-curl -L -O https://github.com/thorvg/thorvg/archive/v$VERSION.tar.gz
-# Current Github main branch tip
-#curl -L -O https://github.com/thorvg/thorvg/archive/refs/heads/main.tar.gz
+if [ ! -z "$GIT_COMMIT" ]; then
+ echo "Updating ThorVG to commit:" $GIT_COMMIT
+ curl -L -O https://github.com/thorvg/thorvg/archive/$GIT_COMMIT.tar.gz
+else
+ echo "Updating ThorVG to tagged release:" $VERSION
+ curl -L -O https://github.com/thorvg/thorvg/archive/v$VERSION.tar.gz
+fi
tar --strip-components=1 -xvf *.tar.gz
rm *.tar.gz
@@ -70,4 +76,4 @@ cp -rv src/loaders/jpg ../src/loaders/
popd
rm -rf tmp
-
+popd