diff --git a/.clang-format b/.clang-format index 1df6c35bfb5b..eda00dd8ddce 100644 --- a/.clang-format +++ b/.clang-format @@ -1,29 +1,59 @@ # Commented out parameters are those with the same value as base LLVM style. # We can uncomment them if we want to change their value, or enforce the -# chosen value in case the base style changes (last sync: Clang 14.0). ---- -### General config, applies to all languages ### -BasedOnStyle: LLVM +# chosen value in case the base style changes (last sync: Clang 18.1.8). +BasedOnStyle: LLVM AccessModifierOffset: -4 AlignAfterOpenBracket: DontAlign # AlignArrayOfStructures: None -# AlignConsecutiveMacros: None -# AlignConsecutiveAssignments: None -# AlignConsecutiveBitFields: None -# AlignConsecutiveDeclarations: None +# AlignConsecutiveAssignments: +# Enabled: false +# AcrossEmptyLines: false +# AcrossComments: false +# AlignCompound: false +# AlignFunctionPointers: false +# PadOperators: true +# AlignConsecutiveBitFields: +# Enabled: false +# AcrossEmptyLines: false +# AcrossComments: false +# AlignCompound: false +# AlignFunctionPointers: false +# PadOperators: false +# AlignConsecutiveDeclarations: +# Enabled: false +# AcrossEmptyLines: false +# AcrossComments: false +# AlignCompound: false +# AlignFunctionPointers: false +# PadOperators: false +# AlignConsecutiveMacros: +# Enabled: false +# AcrossEmptyLines: false +# AcrossComments: false +# AlignCompound: false +# AlignFunctionPointers: false +# PadOperators: false +# AlignConsecutiveShortCaseStatements: +# Enabled: false +# AcrossEmptyLines: false +# AcrossComments: false +# AlignCaseColons: false # AlignEscapedNewlines: Right -AlignOperands: DontAlign -AlignTrailingComments: false +AlignOperands: DontAlign +AlignTrailingComments: + Kind: Never + OverEmptyLines: 0 # AllowAllArgumentsOnNextLine: true AllowAllParametersOfDeclarationOnNextLine: false -# AllowShortEnumsOnASingleLine: true +# AllowBreakBeforeNoexceptSpecifier: Never # AllowShortBlocksOnASingleLine: Never # AllowShortCaseLabelsOnASingleLine: false +# AllowShortCompoundRequirementOnASingleLine: true +# AllowShortEnumsOnASingleLine: true # AllowShortFunctionsOnASingleLine: All -# AllowShortLambdasOnASingleLine: All # AllowShortIfStatementsOnASingleLine: Never +# AllowShortLambdasOnASingleLine: All # AllowShortLoopsOnASingleLine: false -# AlwaysBreakAfterDefinitionReturnType: None # AlwaysBreakAfterReturnType: None # AlwaysBreakBeforeMultilineStrings: false # AlwaysBreakTemplateDeclarations: MultiLine @@ -31,50 +61,49 @@ AllowAllParametersOfDeclarationOnNextLine: false # - __capability # BinPackArguments: true # BinPackParameters: true +# BitFieldColonSpacing: Both # BraceWrapping: -# AfterCaseLabel: false -# AfterClass: false +# AfterCaseLabel: false +# AfterClass: false # AfterControlStatement: Never -# AfterEnum: false -# AfterFunction: false -# AfterNamespace: false +# AfterEnum: false +# AfterFunction: false +# AfterNamespace: false # AfterObjCDeclaration: false -# AfterStruct: false -# AfterUnion: false +# AfterStruct: false +# AfterUnion: false # AfterExternBlock: false -# BeforeCatch: false -# BeforeElse: false +# BeforeCatch: false +# BeforeElse: false # BeforeLambdaBody: false -# BeforeWhile: false -# IndentBraces: false +# BeforeWhile: false +# IndentBraces: false # SplitEmptyFunction: true # SplitEmptyRecord: true # SplitEmptyNamespace: true +# BreakAdjacentStringLiterals: true +# BreakAfterAttributes: Leave +# BreakAfterJavaFieldAnnotations: false +# BreakArrays: true # BreakBeforeBinaryOperators: None -# BreakBeforeConceptDeclarations: true # BreakBeforeBraces: Attach -# BreakBeforeInheritanceComma: false -# BreakInheritanceList: BeforeColon +# BreakBeforeConceptDeclarations: Always +# BreakBeforeInlineASMColon: OnlyMultiline # BreakBeforeTernaryOperators: true -# BreakConstructorInitializersBeforeComma: false BreakConstructorInitializers: AfterColon +# BreakInheritanceList: BeforeColon # BreakStringLiterals: true -ColumnLimit: 0 -# CommentPragmas: '^ IWYU pragma:' -# QualifierAlignment: Leave +ColumnLimit: 0 +# CommentPragmas: '^ IWYU pragma:' # CompactNamespaces: false ConstructorInitializerIndentWidth: 8 ContinuationIndentWidth: 8 Cpp11BracedListStyle: false -# DeriveLineEnding: true # DerivePointerAlignment: false -# DisableFormat: false +# DisableFormat: false # EmptyLineAfterAccessModifier: Never # EmptyLineBeforeAccessModifier: LogicalBlock # ExperimentalAutoDetectBinPacking: false -# PackConstructorInitializers: BinPack -ConstructorInitializerAllOnOneLineOrOnePerLine: true -# AllowAllConstructorInitializersOnNextLine: true # FixNamespaceComments: true # ForEachMacros: # - foreach @@ -82,118 +111,138 @@ ConstructorInitializerAllOnOneLineOrOnePerLine: true # - BOOST_FOREACH # IfMacros: # - KJ_IF_MAYBE -# IncludeBlocks: Preserve +# IncludeBlocks: Preserve IncludeCategories: - - Regex: '".*"' - Priority: 1 - - Regex: '^<.*\.h>' - Priority: 2 - - Regex: '^<.*' - Priority: 3 -# IncludeIsMainRegex: '(Test)?$' + - Regex: ^".*"$ + Priority: 1 + - Regex: ^<.*\.h>$ + Priority: 2 + - Regex: ^<.*>$ + Priority: 3 +# IncludeIsMainRegex: (Test)?$ # IncludeIsMainSourceRegex: '' # IndentAccessModifiers: false -IndentCaseLabels: true # IndentCaseBlocks: false +IndentCaseLabels: true +# IndentExternBlock: AfterExternBlock # IndentGotoLabels: true # IndentPPDirectives: None -# IndentExternBlock: AfterExternBlock -# IndentRequires: false -IndentWidth: 4 +# IndentRequiresClause: true +IndentWidth: 4 # IndentWrappedFunctionNames: false +# InsertBraces: false +# InsertNewlineAtEOF: false # InsertTrailingCommas: None +# IntegerLiteralSeparator: +# Binary: 0 +# BinaryMinDigits: 0 +# Decimal: 0 +# DecimalMinDigits: 0 +# Hex: 0 +# HexMinDigits: 0 +JavaImportGroups: + - org.godotengine + - android + - androidx + - com.android + - com.google + - java + - javax # JavaScriptQuotes: Leave # JavaScriptWrapImports: true +# KeepEmptyLinesAtEOF: false KeepEmptyLinesAtTheStartOfBlocks: false # LambdaBodyIndentation: Signature +# Language: Cpp +# LineEnding: DeriveLF # MacroBlockBegin: '' -# MacroBlockEnd: '' +# MacroBlockEnd: '' # MaxEmptyLinesToKeep: 1 # NamespaceIndentation: None +# ObjCBinPackProtocolList: Auto +ObjCBlockIndentWidth: 4 +# ObjCBreakBeforeNestedBlockParam: true +# ObjCSpaceAfterProperty: false +# ObjCSpaceBeforeProtocolList: true +# PPIndentWidth: -1 +PackConstructorInitializers: NextLine # PenaltyBreakAssignment: 2 # PenaltyBreakBeforeFirstCallParameter: 19 # PenaltyBreakComment: 300 # PenaltyBreakFirstLessLess: 120 # PenaltyBreakOpenParenthesis: 0 +# PenaltyBreakScopeResolution: 500 # PenaltyBreakString: 1000 # PenaltyBreakTemplateDeclaration: 10 # PenaltyExcessCharacter: 1000000 -# PenaltyReturnTypeOnItsOwnLine: 60 # PenaltyIndentedWhitespace: 0 +# PenaltyReturnTypeOnItsOwnLine: 60 # PointerAlignment: Right -# PPIndentWidth: -1 +# QualifierAlignment: Leave # ReferenceAlignment: Pointer -# ReflowComments: true +# ReflowComments: true # RemoveBracesLLVM: false +# RemoveParentheses: Leave +# RemoveSemicolon: false +# RequiresClausePosition: OwnLine +# RequiresExpressionIndentation: OuterScope # SeparateDefinitionBlocks: Leave # ShortNamespaceLines: 1 -# SortIncludes: CaseSensitive +# SkipMacroDefinitionBody: false +# SortIncludes: CaseSensitive # SortJavaStaticImport: Before -# SortUsingDeclarations: true +# SortUsingDeclarations: LexicographicNumeric # SpaceAfterCStyleCast: false # SpaceAfterLogicalNot: false # SpaceAfterTemplateKeyword: true +# SpaceAroundPointerQualifiers: Default # SpaceBeforeAssignmentOperators: true # SpaceBeforeCaseColon: false # SpaceBeforeCpp11BracedList: false # SpaceBeforeCtorInitializerColon: true # SpaceBeforeInheritanceColon: true -# SpaceBeforeParens: ControlStatements +# SpaceBeforeJsonColon: false # SpaceBeforeParensOptions: # AfterControlStatements: true # AfterForeachMacros: true -# AfterFunctionDefinitionName: false # AfterFunctionDeclarationName: false -# AfterIfMacros: true +# AfterFunctionDefinitionName: false +# AfterIfMacros: true # AfterOverloadedOperator: false +# AfterPlacementOperator: true +# AfterRequiresInClause: false +# AfterRequiresInExpression: false # BeforeNonEmptyParentheses: false -# SpaceAroundPointerQualifiers: Default # SpaceBeforeRangeBasedForLoopColon: true +# SpaceBeforeSquareBrackets: false # SpaceInEmptyBlock: false -# SpaceInEmptyParentheses: false # SpacesBeforeTrailingComments: 1 -# SpacesInAngles: Never -# SpacesInConditionalStatement: false +# SpacesInAngles: Never # SpacesInContainerLiterals: true -# SpacesInCStyleCastParentheses: false ## Godot TODO: We'll want to use a min of 1, but we need to see how to fix ## our comment capitalization at the same time. SpacesInLineCommentPrefix: - Minimum: 0 - Maximum: -1 -# SpacesInParentheses: false + Minimum: 0 + Maximum: -1 +# SpacesInParens: Never +# SpacesInParensOptions: +# InConditionalStatements: false +# InCStyleCasts: false +# InEmptyParentheses: false +# Other: false # SpacesInSquareBrackets: false -# SpaceBeforeSquareBrackets: false -# BitFieldColonSpacing: Both +Standard: c++17 # StatementAttributeLikeMacros: # - Q_EMIT # StatementMacros: # - Q_UNUSED # - QT_REQUIRE_VERSION -TabWidth: 4 -# UseCRLF: false -UseTab: Always +TabWidth: 4 +UseTab: Always +# VerilogBreakBetweenInstancePorts: true # WhitespaceSensitiveMacros: -# - STRINGIZE -# - PP_STRINGIZE # - BOOST_PP_STRINGIZE -# - NS_SWIFT_NAME # - CF_SWIFT_NAME ---- -### C++ specific config ### -Language: Cpp -Standard: c++17 ---- -### ObjC specific config ### -Language: ObjC -# ObjCBinPackProtocolList: Auto -ObjCBlockIndentWidth: 4 -# ObjCBreakBeforeNestedBlockParam: true -# ObjCSpaceAfterProperty: false -# ObjCSpaceBeforeProtocolList: true ---- -### Java specific config ### -Language: Java -# BreakAfterJavaFieldAnnotations: false -JavaImportGroups: ['org.godotengine', 'android', 'androidx', 'com.android', 'com.google', 'java', 'javax'] -... +# - NS_SWIFT_NAME +# - PP_STRINGIZE +# - STRINGIZE diff --git a/.git-blame-ignore-revs b/.git-blame-ignore-revs index ebfd3b515af3..1c78dde7a4e9 100644 --- a/.git-blame-ignore-revs +++ b/.git-blame-ignore-revs @@ -54,3 +54,6 @@ df61dc4b2bd54a5a40c515493c76f5a458e5b541 # Enforce template syntax `typename` over `class` 9903e6779b70fc03aae70a37b9cf053f4f355b91 + +# Style: Apply new `clang-format` fixes +b37fc1014abf7adda70dc30b0822d775b3a4433f diff --git a/.gitattributes b/.gitattributes index 5af3e121a8cc..30d1acb49737 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/CODEOWNERS b/.github/CODEOWNERS index a1d406ff92de..748d787b86ea 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -4,180 +4,234 @@ # Buildsystem -.* @godotengine/buildsystem -.github/ @godotengine/buildsystem -*.py @godotengine/buildsystem -SConstruct @godotengine/buildsystem -SCsub @godotengine/buildsystem +* @godotengine/buildsystem # Core -/core/ @godotengine/core -/core/crypto/ @godotengine/network -/core/debugger/ @godotengine/debugger -/core/extension/ @godotengine/gdextension -/core/input/ @godotengine/input +/core/ @godotengine/core +/core/crypto/ @godotengine/network +/core/debugger/ @godotengine/debugger +/core/extension/ @godotengine/gdextension +/core/input/ @godotengine/input # Doc -/doc/ @godotengine/documentation -doc_classes/* @godotengine/documentation +/doc/ @godotengine/documentation +**/doc_classes/ @godotengine/documentation # Drivers ## Audio -/drivers/alsa/ @godotengine/audio -/drivers/alsamidi/ @godotengine/audio -/drivers/coreaudio/ @godotengine/audio -/drivers/coremidi/ @godotengine/audio -/drivers/pulseaudio/ @godotengine/audio -/drivers/wasapi/ @godotengine/audio -/drivers/winmidi/ @godotengine/audio -/drivers/xaudio2/ @godotengine/audio +/drivers/alsa/ @godotengine/audio +/drivers/alsamidi/ @godotengine/audio +/drivers/coreaudio/ @godotengine/audio +/drivers/coremidi/ @godotengine/audio +/drivers/pulseaudio/ @godotengine/audio +/drivers/wasapi/ @godotengine/audio +/drivers/winmidi/ @godotengine/audio +/drivers/xaudio2/ @godotengine/audio ## Rendering -/drivers/d3d12/ @godotengine/rendering -/drivers/dummy/ @godotengine/rendering -/drivers/gles3/ @godotengine/rendering -/drivers/spirv-reflect/ @godotengine/rendering -/drivers/vulkan/ @godotengine/rendering +/drivers/d3d12/ @godotengine/rendering +/drivers/dummy/ @godotengine/rendering +/drivers/gles3/ @godotengine/rendering +/drivers/spirv-reflect/ @godotengine/rendering +/drivers/vulkan/ @godotengine/rendering ## OS -/drivers/unix/ @godotengine/_platforms -/drivers/windows/ @godotengine/windows +/drivers/unix/ @godotengine/_platforms +/drivers/windows/ @godotengine/windows ## Misc -/drivers/png/ @godotengine/import +/drivers/png/ @godotengine/import # Editor -/editor/*debugger* @godotengine/debugger -/editor/gui/ @godotengine/usability @godotengine/gui-nodes -/editor/icons/ @godotengine/usability -/editor/import/ @godotengine/import -/editor/plugins/*2d_*.* @godotengine/2d-editor -/editor/plugins/*3d_*.* @godotengine/3d-editor -/editor/plugins/script_*.* @godotengine/script-editor -/editor/plugins/*shader*.* @godotengine/shaders -/editor/themes/ @godotengine/usability @godotengine/gui-nodes -/editor/code_editor.* @godotengine/script-editor -/editor/*dock*.* @godotengine/docks -/editor/*shader*.* @godotengine/shaders +/editor/*debugger* @godotengine/debugger +/editor/gui/ @godotengine/usability @godotengine/gui-nodes +/editor/icons/ @godotengine/usability +/editor/import/ @godotengine/import +/editor/plugins/*2d_*.* @godotengine/2d-editor +/editor/plugins/*3d_*.* @godotengine/3d-editor +/editor/plugins/script_*.* @godotengine/script-editor +/editor/plugins/*shader*.* @godotengine/shaders +/editor/themes/ @godotengine/usability @godotengine/gui-nodes +/editor/code_editor.* @godotengine/script-editor +/editor/*dock*.* @godotengine/docks +/editor/*shader*.* @godotengine/shaders # Main -/main/ @godotengine/core +/main/ @godotengine/core # Misc -/misc/ @godotengine/buildsystem -/misc/extension_api_validation/ @godotengine/gdextension @godotengine/dotnet +/misc/ @godotengine/buildsystem +/misc/extension_api_validation/ @godotengine/gdextension @godotengine/dotnet # Modules ## Audio (+ video) -/modules/minimp3/ @godotengine/audio -/modules/ogg/ @godotengine/audio -/modules/opus/ @godotengine/audio -/modules/theora/ @godotengine/audio -/modules/vorbis/ @godotengine/audio -/modules/webm/ @godotengine/audio +/modules/interactive_music/ @godotengine/audio +/modules/interactive_music/doc_classes/ @godotengine/audio @godotengine/documentation +/modules/minimp3/ @godotengine/audio +/modules/minimp3/doc_classes/ @godotengine/audio @godotengine/documentation +/modules/ogg/ @godotengine/audio +/modules/ogg/doc_classes/ @godotengine/audio @godotengine/documentation +/modules/theora/ @godotengine/audio +/modules/theora/doc_classes/ @godotengine/audio @godotengine/documentation +/modules/vorbis/ @godotengine/audio +/modules/vorbis/doc_classes/ @godotengine/audio @godotengine/documentation ## Import -/modules/basis_universal/ @godotengine/import -/modules/bmp/ @godotengine/import -/modules/cvtt/ @godotengine/import -/modules/dds/ @godotengine/import -/modules/etc/ @godotengine/import -/modules/fbx/ @godotengine/import -/modules/gltf/ @godotengine/import -/modules/hdr/ @godotengine/import -/modules/jpg/ @godotengine/import -/modules/pvr/ @godotengine/import -/modules/squish/ @godotengine/import -/modules/svg/ @godotengine/import -/modules/tga/ @godotengine/import -/modules/tinyexr/ @godotengine/import -/modules/webp/ @godotengine/import +/modules/astcenc/ @godotengine/import +/modules/basis_universal/ @godotengine/import +/modules/betsy/ @godotengine/import +/modules/bmp/ @godotengine/import +/modules/cvtt/ @godotengine/import +/modules/dds/ @godotengine/import +/modules/etcpak/ @godotengine/import +/modules/fbx/ @godotengine/import +/modules/fbx/doc_classes/ @godotengine/import @godotengine/documentation +/modules/gltf/ @godotengine/import +/modules/gltf/doc_classes/ @godotengine/import @godotengine/documentation +/modules/gltf/tests/ @godotengine/import @godotengine/tests +/modules/hdr/ @godotengine/import +/modules/jpg/ @godotengine/import +/modules/ktx/ @godotengine/import +/modules/squish/ @godotengine/import +/modules/svg/ @godotengine/import +/modules/tga/ @godotengine/import +/modules/tinyexr/ @godotengine/import +/modules/webp/ @godotengine/import ## Network -/modules/enet/ @godotengine/network -/modules/mbedtls/ @godotengine/network -/modules/upnp/ @godotengine/network -/modules/webrtc/ @godotengine/network -/modules/websocket/ @godotengine/network +/modules/enet/ @godotengine/network +/modules/enet/doc_classes/ @godotengine/network @godotengine/documentation +/modules/mbedtls/ @godotengine/network +/modules/mbedtls/tests/ @godotengine/network @godotengine/tests +/modules/multiplayer/ @godotengine/network +/modules/multiplayer/doc_classes/ @godotengine/network @godotengine/documentation +/modules/upnp/ @godotengine/network +/modules/upnp/doc_classes/ @godotengine/network @godotengine/documentation +/modules/webrtc/ @godotengine/network +/modules/webrtc/doc_classes/ @godotengine/network @godotengine/documentation +/modules/websocket/ @godotengine/network +/modules/websocket/doc_classes/ @godotengine/network @godotengine/documentation + +## Physics +/modules/godot_physics_2d/ @godotengine/physics +/modules/godot_physics_3d/ @godotengine/physics ## Rendering -/modules/denoise/ @godotengine/rendering -/modules/glslang/ @godotengine/rendering -/modules/lightmapper_rd/ @godotengine/rendering -/modules/meshoptimizer/ @godotengine/rendering -/modules/vhacd/ @godotengine/rendering -/modules/xatlas_unwrap/ @godotengine/rendering +/modules/glslang/ @godotengine/rendering +/modules/lightmapper_rd/ @godotengine/rendering +/modules/meshoptimizer/ @godotengine/rendering +/modules/raycast/ @godotengine/rendering +/modules/vhacd/ @godotengine/rendering +/modules/xatlas_unwrap/ @godotengine/rendering ## Scripting -/modules/gdscript/ @godotengine/gdscript -/modules/jsonrpc/ @godotengine/gdscript -/modules/mono/ @godotengine/dotnet +/modules/gdscript/ @godotengine/gdscript +/modules/gdscript/doc_classes/ @godotengine/gdscript @godotengine/documentation +/modules/gdscript/icons/ @godotengine/gdscript @godotengine/usability +/modules/gdscript/tests/ @godotengine/gdscript @godotengine/tests +/modules/jsonrpc/ @godotengine/gdscript @godotengine/network +/modules/jsonrpc/tests @godotengine/gdscript @godotengine/network @godotengine/tests +/modules/mono/ @godotengine/dotnet +/modules/mono/doc_classes/ @godotengine/dotnet @godotengine/documentation +/modules/mono/icons/ @godotengine/dotnet @godotengine/usability ## Text -/modules/freetype/ @godotengine/buildsystem -/modules/text_server_adv/ @godotengine/gui-nodes -/modules/text_server_fb/ @godotengine/gui-nodes +/modules/freetype/ @godotengine/buildsystem +/modules/msdfgen/ @godotengine/buildsystem +/modules/text_server_adv/ @godotengine/gui-nodes +/modules/text_server_adv/doc_classes/ @godotengine/gui-nodes @godotengine/documentation +/modules/text_server_fb/ @godotengine/gui-nodes +/modules/text_server_fb/doc_classes/ @godotengine/gui-nodes @godotengine/documentation ## XR -/modules/camera/ @godotengine/xr -/modules/gdextension/xr/ @godotengine/xr -/modules/mobile_vr/ @godotengine/xr -/modules/webxr/ @godotengine/xr +/modules/camera/ @godotengine/xr +/modules/mobile_vr/ @godotengine/xr +/modules/mobile_vr/doc_classes/ @godotengine/xr @godotengine/documentation +/modules/openxr/ @godotengine/xr +/modules/openxr/doc_classes/ @godotengine/xr @godotengine/documentation +/modules/webxr/ @godotengine/xr +/modules/webxr/doc_classes/ @godotengine/xr @godotengine/documentation ## Misc -/modules/bullet/ @godotengine/physics -/modules/csg/ @godotengine/3d-nodes -/modules/gdnavigation/ @godotengine/navigation -/modules/gridmap/ @godotengine/3d-nodes -/modules/opensimplex/ @godotengine/3d-nodes -/modules/regex/ @godotengine/core +/modules/csg/ @godotengine/3d-nodes +/modules/csg/doc_classes/ @godotengine/3d-nodes @godotengine/documentation +/modules/csg/icons/ @godotengine/3d-nodes @godotengine/usability +/modules/navigation/ @godotengine/navigation +/modules/gridmap/ @godotengine/3d-nodes +/modules/gridmap/doc_classes/ @godotengine/3d-nodes @godotengine/documentation +/modules/gridmap/icons/ @godotengine/3d-nodes @godotengine/usability +/modules/noise/ @godotengine/core +/modules/noise/doc_classes/ @godotengine/core @godotengine/documentation +/modules/noise/icons/ @godotengine/core @godotengine/usability +/modules/noise/tests/ @godotengine/core @godotengine/tests +/modules/regex/ @godotengine/core +/modules/regex/doc_classes/ @godotengine/core @godotengine/documentation +/modules/regex/icons/ @godotengine/core @godotengine/usability +/modules/regex/tests/ @godotengine/core @godotengine/tests +/modules/zip/ @godotengine/core +/modules/zip/doc_classes/ @godotengine/core @godotengine/documentation # Platform -/platform/android/ @godotengine/android -/platform/ios/ @godotengine/ios -/platform/linuxbsd/ @godotengine/linux-bsd -/platform/macos/ @godotengine/macos -/platform/web/ @godotengine/web -/platform/windows/ @godotengine/windows +/platform/android/ @godotengine/android +/platform/android/doc_classes/ @godotengine/android @godotengine/documentation +/platform/ios/ @godotengine/ios +/platform/ios/doc_classes/ @godotengine/ios @godotengine/documentation +/platform/linuxbsd/ @godotengine/linux-bsd +/platform/linuxbsd/doc_classes/ @godotengine/linux-bsd @godotengine/documentation +/platform/macos/ @godotengine/macos +/platform/macos/doc_classes/ @godotengine/macos @godotengine/documentation +/platform/web/ @godotengine/web +/platform/web/doc_classes/ @godotengine/web @godotengine/documentation +/platform/windows/ @godotengine/windows +/platform/windows/doc_classes/ @godotengine/windows @godotengine/documentation # Scene -/scene/2d/ @godotengine/2d-nodes -/scene/3d/ @godotengine/3d-nodes -/scene/animation/ @godotengine/animation -/scene/audio/ @godotengine/audio -/scene/debugger/ @godotengine/debugger -/scene/gui/ @godotengine/gui-nodes -/scene/main/ @godotengine/core -/scene/resources/font.* @godotengine/gui-nodes -/scene/resources/text_line.* @godotengine/gui-nodes -/scene/resources/text_paragraph.* @godotengine/gui-nodes -/scene/resources/visual_shader*.* @godotengine/shaders -/scene/theme/ @godotengine/gui-nodes +/scene/2d/ @godotengine/2d-nodes +/scene/2d/physics/ @godotengine/2d-nodes @godotengine/physics +/scene/3d/ @godotengine/3d-nodes +/scene/3d/physics/ @godotengine/3d-nodes @godotengine/physics +/scene/animation/ @godotengine/animation +/scene/audio/ @godotengine/audio +/scene/debugger/ @godotengine/debugger +/scene/gui/ @godotengine/gui-nodes +/scene/main/ @godotengine/core +/scene/resources/font.* @godotengine/gui-nodes +/scene/resources/text_line.* @godotengine/gui-nodes +/scene/resources/text_paragraph.* @godotengine/gui-nodes +/scene/resources/visual_shader*.* @godotengine/shaders +/scene/theme/ @godotengine/gui-nodes +/scene/theme/icons/ @godotengine/gui-nodes @godotengine/usability # Servers -/servers/audio* @godotengine/audio -/servers/camera* @godotengine/xr -/servers/display_server.* @godotengine/_platforms -/servers/navigation_server*.* @godotengine/navigation -/servers/physics* @godotengine/physics -/servers/rendering* @godotengine/rendering -/servers/text_server.* @godotengine/gui-nodes -/servers/xr* @godotengine/xr +/servers/audio* @godotengine/audio +/servers/camera* @godotengine/xr +/servers/display_server.* @godotengine/_platforms +/servers/navigation_server*.* @godotengine/navigation +/servers/physics* @godotengine/physics +/servers/rendering* @godotengine/rendering +/servers/text_server.* @godotengine/gui-nodes +/servers/xr* @godotengine/xr # Tests -/tests/ @godotengine/tests +/tests/ @godotengine/tests # Thirdparty -/thirdparty/ @godotengine/buildsystem +/thirdparty/ @godotengine/buildsystem + +# Buildsystem (After everything to catch all) + +*.py @godotengine/buildsystem +SConstruct @godotengine/buildsystem +SCsub @godotengine/buildsystem diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index 7ffae10a2aab..6803d9afd52b 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -1,72 +1,72 @@ name: Bug report description: Report a bug in Godot -body: -- type: markdown - attributes: - value: | - When reporting bugs, please follow the guidelines in this template. This helps identify the problem precisely and thus enables contributors to fix it faster. - - Write a descriptive issue title above. - - The golden rule is to **always open *one* issue for *one* bug**. If you notice several bugs and want to report them, make sure to create one new issue for each of them. - - Search [open](https://github.com/godotengine/godot/issues) and [closed](https://github.com/godotengine/godot/issues?q=is%3Aissue+is%3Aclosed) issues to ensure it has not already been reported. If you don't find a relevant match or if you're unsure, don't hesitate to **open a new issue**. The bugsquad will handle it from there if it's a duplicate. - - Verify that you are using a [supported Godot version](https://docs.godotengine.org/en/latest/about/release_policy.html). Please always check if your issue is reproducible in the latest version – it may already have been fixed! - - If you use a custom build, please test if your issue is reproducible in official builds too. Likewise if you use any C++ modules, GDExtensions, or editor plugins, you should check if the bug is reproducible in a project without these. +body: + - type: markdown + attributes: + value: | + When reporting bugs, please follow the guidelines in this template. This helps identify the problem precisely and thus enables contributors to fix it faster. + - Write a descriptive issue title above. + - The golden rule is to **always open *one* issue for *one* bug**. If you notice several bugs and want to report them, make sure to create one new issue for each of them. + - Search [open](https://github.com/godotengine/godot/issues) and [closed](https://github.com/godotengine/godot/issues?q=is%3Aissue+is%3Aclosed) issues to ensure it has not already been reported. If you don't find a relevant match or if you're unsure, don't hesitate to **open a new issue**. The bugsquad will handle it from there if it's a duplicate. + - Verify that you are using a [supported Godot version](https://docs.godotengine.org/en/latest/about/release_policy.html). Please always check if your issue is reproducible in the latest version – it may already have been fixed! + - If you use a custom build, please test if your issue is reproducible in official builds too. Likewise if you use any C++ modules, GDExtensions, or editor plugins, you should check if the bug is reproducible in a project without these. -- type: textarea - attributes: - label: Tested versions - description: | - To properly fix a bug, we need to identify if the bug was recently introduced in the engine, or if it was always present. - - Please specify the Godot version you found the issue in, including the **Git commit hash** if using a development or non-official build. The exact Godot version (including the commit hash) can be copied by clicking the version shown in the editor (bottom bar) or in the project manager (top bar). - - If you can, **please test earlier Godot versions** (previous stable branch, and development snapshots of the current feature release) and, if applicable, newer versions (development snapshots for the next feature release). Mention whether the bug is reproducible or not in the versions you tested. You can find all Godot releases in our [download archive](https://godotengine.org/download/archive/). - - The aim is for us to identify whether a bug is a **regression**, i.e. an issue that didn't exist in a previous version, but was introduced later on, breaking existing functionality. For example, if a bug is reproducible in 4.2.stable but not in 4.1.stable, we would like you to test intermediate 4.2 dev and beta snapshots to find which snapshot is the first one where the issue can be reproduced. - placeholder: | + - type: textarea + attributes: + label: Tested versions + description: | + To properly fix a bug, we need to identify if the bug was recently introduced in the engine, or if it was always present. + - Please specify the Godot version you found the issue in, including the **Git commit hash** if using a development or non-official build. The exact Godot version (including the commit hash) can be copied by clicking the version shown in the editor (bottom bar) or in the project manager (top bar). + - If you can, **please test earlier Godot versions** (previous stable branch, and development snapshots of the current feature release) and, if applicable, newer versions (development snapshots for the next feature release). Mention whether the bug is reproducible or not in the versions you tested. You can find all Godot releases in our [download archive](https://godotengine.org/download/archive/). + - The aim is for us to identify whether a bug is a **regression**, i.e. an issue that didn't exist in a previous version, but was introduced later on, breaking existing functionality. For example, if a bug is reproducible in 4.2.stable but not in 4.1.stable, we would like you to test intermediate 4.2 dev and beta snapshots to find which snapshot is the first one where the issue can be reproduced. + placeholder: | - - Reproducible in: 4.3.dev [d76c1d0e5], 4.2.stable, 4.2.dev5 and later 4.2 snapshots. - - Not reproducible in: 4.1.3.stable, 4.2.dev4 and earlier 4.2 snapshots. - validations: - required: true + - Reproducible in: 4.3.dev [d76c1d0e5], 4.2.stable, 4.2.dev5 and later 4.2 snapshots. + - Not reproducible in: 4.1.3.stable, 4.2.dev4 and earlier 4.2 snapshots. + validations: + required: true -- type: input - attributes: - label: System information - description: | - - Specify the OS version, and when relevant hardware information. - - For issues that are likely OS-specific and/or graphics-related, please specify the CPU model and architecture. - - For graphics-related issues, specify the GPU model, driver version, and the rendering backend (GLES2, GLES3, Vulkan). - - **Bug reports not including the required information may be closed at the maintainers' discretion.** If in doubt, always include all the requested information; it's better to include too much information than not enough information. - - **Starting from Godot 4.1, you can copy this information to your clipboard by using *Help > Copy System Info* at the top of the editor window.** - placeholder: Windows 10 - Godot v4.0.3.stable - Vulkan (Forward+) - dedicated NVIDIA GeForce GTX 970 (nvidia, 510.85.02) - Intel Core i7-10700KF CPU @ 3.80GHz (16 Threads) - validations: - required: true + - type: input + attributes: + label: System information + description: | + - Specify the OS version, and when relevant hardware information. + - For issues that are likely OS-specific and/or graphics-related, please specify the CPU model and architecture. + - For graphics-related issues, specify the GPU model, driver version, and the rendering backend (GLES2, GLES3, Vulkan). + - **Bug reports not including the required information may be closed at the maintainers' discretion.** If in doubt, always include all the requested information; it's better to include too much information than not enough information. + - **Starting from Godot 4.1, you can copy this information to your clipboard by using *Help > Copy System Info* at the top of the editor window.** + placeholder: Windows 10 - Godot v4.0.3.stable - Vulkan (Forward+) - dedicated NVIDIA GeForce GTX 970 (nvidia, 510.85.02) - Intel Core i7-10700KF CPU @ 3.80GHz (16 Threads) + validations: + required: true -- type: textarea - attributes: - label: Issue description - description: | - Describe your issue briefly. What doesn't work, and how do you expect it to work instead? - You can include images or videos with drag and drop, and format code blocks or logs with \`\`\` tags, on separate lines before and after the text. (Use \`\`\`gdscript to add GDScript syntax highlighting.) - Please do not add code examples or error messages as screenshots, but as text, this helps searching for issues and testing the code. If you are reporting a bug in the editor interface, like the script editor, please provide both a screenshot *and* the text of the code to help with testing. - validations: - required: true + - type: textarea + attributes: + label: Issue description + description: | + Describe your issue briefly. What doesn't work, and how do you expect it to work instead? + You can include images or videos with drag and drop, and format code blocks or logs with \`\`\` tags, on separate lines before and after the text. (Use \`\`\`gdscript to add GDScript syntax highlighting.) + Please do not add code examples or error messages as screenshots, but as text, this helps searching for issues and testing the code. If you are reporting a bug in the editor interface, like the script editor, please provide both a screenshot *and* the text of the code to help with testing. + validations: + required: true -- type: textarea - attributes: - label: Steps to reproduce - description: | - List of steps or sample code that reproduces the issue. Having reproducible issues is a prerequisite for contributors to be able to solve them. - If you include a minimal reproduction project below, you can detail how to use it here. - validations: - required: true + - type: textarea + attributes: + label: Steps to reproduce + description: | + List of steps or sample code that reproduces the issue. Having reproducible issues is a prerequisite for contributors to be able to solve them. + If you include a minimal reproduction project below, you can detail how to use it here. + validations: + required: true -- type: textarea - attributes: - label: Minimal reproduction project (MRP) - description: | - - A small Godot project which reproduces the issue, with no unnecessary files included. Be sure to not include the `.godot` folder in the archive (but keep `project.godot`). - - Having an MRP is very important for contributors to be able to reproduce the bug in the same way that you are experiencing it. When testing a potential fix for the issue, contributors will use the MRP to validate that the fix is working as intended. - - If the reproduction steps are not project dependent (e.g. the bug is visible in a brand new project), you can write "N/A" in the field. - - Drag and drop a ZIP archive to upload it (max 10 MB). **Do not select another field until the project is done uploading.** - - **Note for C# users:** If your issue is *not* C#-specific, please upload a minimal reproduction project written in GDScript. This will make it easier for contributors to reproduce the issue locally as not everyone has a .NET setup available. - validations: - required: true + - type: textarea + attributes: + label: Minimal reproduction project (MRP) + description: | + - A small Godot project which reproduces the issue, with no unnecessary files included. Be sure to not include the `.godot` folder in the archive (but keep `project.godot`). + - Having an MRP is very important for contributors to be able to reproduce the bug in the same way that you are experiencing it. When testing a potential fix for the issue, contributors will use the MRP to validate that the fix is working as intended. + - If the reproduction steps are not project dependent (e.g. the bug is visible in a brand new project), you can write "N/A" in the field. + - Drag and drop a ZIP archive to upload it (max 10 MB). **Do not select another field until the project is done uploading.** + - **Note for C# users:** If your issue is *not* C#-specific, please upload a minimal reproduction project written in GDScript. This will make it easier for contributors to reproduce the issue locally as not everyone has a .NET setup available. + validations: + required: true diff --git a/.github/actions/download-artifact/action.yml b/.github/actions/download-artifact/action.yml index 534b3251c5c0..c2a3d777c41b 100644 --- a/.github/actions/download-artifact/action.yml +++ b/.github/actions/download-artifact/action.yml @@ -1,15 +1,17 @@ name: Download Godot artifact description: Download the Godot artifact. + inputs: name: description: The artifact name. - default: "${{ github.job }}" + default: ${{ github.job }} path: description: The path to download and extract to. required: true - default: "./" + default: ./ + runs: - using: "composite" + using: composite steps: - name: Download Godot Artifact uses: actions/download-artifact@v4 diff --git a/.github/actions/godot-api-dump/action.yml b/.github/actions/godot-api-dump/action.yml index 773068866170..dee70298c4b5 100644 --- a/.github/actions/godot-api-dump/action.yml +++ b/.github/actions/godot-api-dump/action.yml @@ -1,11 +1,13 @@ name: Dump Godot API description: Dump Godot API for GDExtension + inputs: bin: description: The path to the Godot executable required: true + runs: - using: "composite" + using: composite steps: # Dump GDExtension interface and API - name: Dump GDExtension interface and API for godot-cpp build @@ -19,5 +21,5 @@ runs: - name: Upload API dump uses: ./.github/actions/upload-artifact with: - name: 'godot-api-dump' - path: './godot-api/*' + name: godot-api-dump + path: ./godot-api/* diff --git a/.github/actions/godot-build/action.yml b/.github/actions/godot-build/action.yml index bf29b7e430ad..93d6f076b728 100644 --- a/.github/actions/godot-build/action.yml +++ b/.github/actions/godot-build/action.yml @@ -1,34 +1,39 @@ name: Build Godot description: Build Godot with the provided options. + inputs: target: description: Build target (editor, template_release, template_debug). - default: "editor" + default: editor tests: description: Unit tests. default: false + required: false platform: description: Target platform. required: false sconsflags: - default: "" + description: Additional SCons flags. + default: '' + required: false scons-cache: description: The SCons cache path. - default: "${{ github.workspace }}/.scons-cache/" + default: ${{ github.workspace }}/.scons-cache/ scons-cache-limit: description: The SCons cache size limit. # actions/cache has 10 GiB limit, and GitHub runners have a 14 GiB disk. # Limit to 7 GiB to avoid having the extracted cache fill the disk. default: 7168 + runs: - using: "composite" + using: composite steps: - - name: Scons Build + - name: SCons Build shell: sh env: - SCONSFLAGS: ${{ inputs.sconsflags }} - SCONS_CACHE: ${{ inputs.scons-cache }} - SCONS_CACHE_LIMIT: ${{ inputs.scons-cache-limit }} + SCONSFLAGS: ${{ inputs.sconsflags }} + SCONS_CACHE: ${{ inputs.scons-cache }} + SCONS_CACHE_LIMIT: ${{ inputs.scons-cache-limit }} run: | echo "Building with flags:" platform=${{ inputs.platform }} target=${{ inputs.target }} tests=${{ inputs.tests }} ${{ env.SCONSFLAGS }} diff --git a/.github/actions/godot-cache-restore/action.yml b/.github/actions/godot-cache-restore/action.yml index eb955affef97..7abec20a2821 100644 --- a/.github/actions/godot-cache-restore/action.yml +++ b/.github/actions/godot-cache-restore/action.yml @@ -3,18 +3,19 @@ description: Restore Godot build cache. inputs: cache-name: description: The cache base name (job name by default). - default: "${{github.job}}" + default: ${{ github.job }} scons-cache: description: The SCons cache path. - default: "${{github.workspace}}/.scons-cache/" + default: ${{ github.workspace }}/.scons-cache/ + runs: - using: "composite" + using: composite steps: - name: Restore SCons cache directory uses: actions/cache/restore@v4 with: - path: ${{inputs.scons-cache}} - key: ${{inputs.cache-name}}-${{env.GODOT_BASE_BRANCH}}-${{github.ref}}-${{github.sha}} + path: ${{ inputs.scons-cache }} + key: ${{ inputs.cache-name }}-${{ env.GODOT_BASE_BRANCH }}-${{ github.ref }}-${{ github.sha }} # We try to match an existing cache to restore from it. Each potential key is checked against # all existing caches as a prefix. E.g. 'linux-template-minimal' would match any cache that @@ -28,7 +29,7 @@ runs: # 4. A partial match for the same base branch only (not ideal, matches any PR with the same base branch). restore-keys: | - ${{inputs.cache-name}}-${{env.GODOT_BASE_BRANCH}}-${{github.ref}}-${{github.sha}} - ${{inputs.cache-name}}-${{env.GODOT_BASE_BRANCH}}-${{github.ref}} - ${{inputs.cache-name}}-${{env.GODOT_BASE_BRANCH}}-refs/heads/${{env.GODOT_BASE_BRANCH}} - ${{inputs.cache-name}}-${{env.GODOT_BASE_BRANCH}} + ${{ inputs.cache-name }}-${{ env.GODOT_BASE_BRANCH }}-${{ github.ref }}-${{ github.sha }} + ${{ inputs.cache-name }}-${{ env.GODOT_BASE_BRANCH }}-${{ github.ref }} + ${{ inputs.cache-name }}-${{ env.GODOT_BASE_BRANCH }}-refs/heads/${{ env.GODOT_BASE_BRANCH }} + ${{ inputs.cache-name }}-${{ env.GODOT_BASE_BRANCH }} diff --git a/.github/actions/godot-cache-save/action.yml b/.github/actions/godot-cache-save/action.yml index b7cbf91f94ff..df877cec67bd 100644 --- a/.github/actions/godot-cache-save/action.yml +++ b/.github/actions/godot-cache-save/action.yml @@ -3,15 +3,16 @@ description: Save Godot build cache. inputs: cache-name: description: The cache base name (job name by default). - default: "${{github.job}}" + default: ${{ github.job }} scons-cache: description: The SCons cache path. - default: "${{github.workspace}}/.scons-cache/" + default: ${{ github.workspace }}/.scons-cache/ + runs: - using: "composite" + using: composite steps: - name: Save SCons cache directory uses: actions/cache/save@v4 with: - path: ${{inputs.scons-cache}} - key: ${{inputs.cache-name}}-${{env.GODOT_BASE_BRANCH}}-${{github.ref}}-${{github.sha}} + path: ${{ inputs.scons-cache }} + key: ${{ inputs.cache-name }}-${{ env.GODOT_BASE_BRANCH }}-${{ github.ref }}-${{ github.sha }} diff --git a/.github/actions/godot-converter-test/action.yml b/.github/actions/godot-converter-test/action.yml index 919a76e69375..ffd558d98b1a 100644 --- a/.github/actions/godot-converter-test/action.yml +++ b/.github/actions/godot-converter-test/action.yml @@ -1,11 +1,13 @@ name: Test Godot project converter description: Test the Godot project converter. + inputs: bin: description: The path to the Godot executable required: true + runs: - using: "composite" + using: composite steps: - name: Test 3-to-4 conversion shell: sh diff --git a/.github/actions/godot-deps/action.yml b/.github/actions/godot-deps/action.yml index 99404657c63f..eb9bdef1e774 100644 --- a/.github/actions/godot-deps/action.yml +++ b/.github/actions/godot-deps/action.yml @@ -1,17 +1,19 @@ name: Setup Python and SCons description: Setup Python, install the pip version of SCons. + inputs: python-version: description: The Python version to use. - default: "3.x" + default: 3.x python-arch: description: The Python architecture. - default: "x64" + default: x64 scons-version: description: The SCons version to use. - default: "4.8.0" + default: 4.8.0 + runs: - using: "composite" + using: composite steps: - name: Set up Python 3.x uses: actions/setup-python@v5 diff --git a/.github/actions/godot-project-test/action.yml b/.github/actions/godot-project-test/action.yml index fd8c024a37df..36448cd50aec 100644 --- a/.github/actions/godot-project-test/action.yml +++ b/.github/actions/godot-project-test/action.yml @@ -1,11 +1,13 @@ name: Test Godot project description: Run the test Godot project. + inputs: bin: description: The path to the Godot executable required: true + runs: - using: "composite" + using: composite steps: # Download and extract zip archive with project, folder is renamed to be able to easy change used project - name: Download test project diff --git a/.github/actions/upload-artifact/action.yml b/.github/actions/upload-artifact/action.yml index 80c680103d30..8524afdf593a 100644 --- a/.github/actions/upload-artifact/action.yml +++ b/.github/actions/upload-artifact/action.yml @@ -1,15 +1,17 @@ name: Upload Godot artifact description: Upload the Godot artifact. + inputs: name: description: The artifact name. - default: "${{ github.job }}" + default: ${{ github.job }} path: description: The path to upload. required: true - default: "bin/*" + default: bin/* + runs: - using: "composite" + using: composite steps: - name: Upload Godot Artifact uses: actions/upload-artifact@v4 diff --git a/.github/workflows/android_builds.yml b/.github/workflows/android_builds.yml index ee75d532828d..7ff5502137c3 100644 --- a/.github/workflows/android_builds.yml +++ b/.github/workflows/android_builds.yml @@ -6,15 +6,15 @@ on: env: # Used for the cache key. Add version suffix to force clean build. GODOT_BASE_BRANCH: master - SCONSFLAGS: verbose=yes warnings=extra werror=yes debug_symbols=no module_text_server_fb_enabled=yes + SCONSFLAGS: verbose=yes warnings=extra werror=yes debug_symbols=no module_text_server_fb_enabled=yes strict_checks=yes concurrency: - group: ci-${{github.actor}}-${{github.head_ref || github.run_number}}-${{github.ref}}-android + group: ci-${{ github.actor }}-${{ github.head_ref || github.run_number }}-${{ github.ref }}-android cancel-in-progress: true jobs: build-android: - runs-on: "ubuntu-20.04" + runs-on: ubuntu-20.04 name: ${{ matrix.name }} strategy: fail-fast: false @@ -39,7 +39,8 @@ jobs: sconsflags: arch=arm64 steps: - - uses: actions/checkout@v4 + - name: Checkout + uses: actions/checkout@v4 with: submodules: recursive @@ -85,6 +86,7 @@ jobs: run: | cd platform/android/java ./gradlew generateGodotEditor + ./gradlew generateGodotHorizonOSEditor cd ../../.. ls -l bin/android_editor_builds/ diff --git a/.github/workflows/godot_cpp_test.yml b/.github/workflows/godot_cpp_test.yml index 57114dacfc20..e26c109d75c4 100644 --- a/.github/workflows/godot_cpp_test.yml +++ b/.github/workflows/godot_cpp_test.yml @@ -7,52 +7,58 @@ env: # Used for the cache key. Add version suffix to force clean build. GODOT_BASE_BRANCH: master # Used for the godot-cpp checkout. - GODOT_CPP_BRANCH: '4.2' + GODOT_CPP_BRANCH: 4.3 concurrency: - group: ci-${{github.actor}}-${{github.head_ref || github.run_number}}-${{github.ref}}-cpp-tests + group: ci-${{ github.actor }}-${{ github.head_ref || github.run_number }}-${{ github.ref }}-cpp-tests cancel-in-progress: true jobs: godot-cpp-tests: - runs-on: "ubuntu-20.04" - name: "Build and test Godot CPP" + runs-on: ubuntu-20.04 + name: Build and test Godot CPP steps: - - uses: actions/checkout@v4 + - name: Checkout + uses: actions/checkout@v4 with: - submodules: recursive - - - name: Setup Python and SCons - uses: ./.github/actions/godot-deps + sparse-checkout: .github - # Checkout godot-cpp - name: Checkout godot-cpp uses: actions/checkout@v4 with: + submodules: recursive repository: godotengine/godot-cpp ref: ${{ env.GODOT_CPP_BRANCH }} - submodules: 'recursive' - path: 'godot-cpp' + path: godot-cpp + + - name: Setup Python and SCons + uses: ./.github/actions/godot-deps + + - name: Setup GCC problem matcher + uses: ammaraskar/gcc-problem-matcher@master - # Download generated API dump - name: Download GDExtension interface and API dump uses: ./.github/actions/download-artifact with: - name: 'godot-api-dump' - path: './godot-api' + name: godot-api-dump + path: ./godot-cpp/gdextension - # Extract and override existing files with generated files - - name: Extract GDExtension interface and API dump - run: | - cp -f godot-api/gdextension_interface.h godot-cpp/gdextension/ - cp -f godot-api/extension_api.json godot-cpp/gdextension/ + # TODO: Enable caching when godot-cpp has proper cache limiting. - # TODO: Add caching to the SCons build and store it for CI via the godot-cache - # action. + # - name: Restore Godot build cache + # uses: ./.github/actions/godot-cache-restore + # with: + # cache-name: godot-cpp + # continue-on-error: true - # Build godot-cpp test extension - name: Build godot-cpp test extension - run: | - cd godot-cpp/test - scons target=template_debug dev_build=yes - cd ../.. + env: # Keep synced with godot-build. + SCONS_CACHE: ${{ github.workspace }}/.scons-cache/ + SCONS_CACHE_LIMIT: 7168 + run: scons --directory=./godot-cpp/test target=template_debug dev_build=yes verbose=yes + + # - name: Save Godot build cache + # uses: ./.github/actions/godot-cache-save + # with: + # cache-name: godot-cpp + # continue-on-error: true diff --git a/.github/workflows/ios_builds.yml b/.github/workflows/ios_builds.yml index 8da6d1311e6e..8513429f9a63 100644 --- a/.github/workflows/ios_builds.yml +++ b/.github/workflows/ios_builds.yml @@ -6,19 +6,20 @@ on: env: # Used for the cache key. Add version suffix to force clean build. GODOT_BASE_BRANCH: master - SCONSFLAGS: verbose=yes warnings=extra werror=yes debug_symbols=no module_text_server_fb_enabled=yes + SCONSFLAGS: verbose=yes warnings=extra werror=yes debug_symbols=no module_text_server_fb_enabled=yes strict_checks=yes concurrency: - group: ci-${{github.actor}}-${{github.head_ref || github.run_number}}-${{github.ref}}-ios + group: ci-${{ github.actor }}-${{ github.head_ref || github.run_number }}-${{ github.ref }}-ios cancel-in-progress: true jobs: ios-template: - runs-on: "macos-latest" + runs-on: macos-latest name: Template (target=template_release) steps: - - uses: actions/checkout@v4 + - name: Checkout + uses: actions/checkout@v4 with: submodules: recursive diff --git a/.github/workflows/linux_builds.yml b/.github/workflows/linux_builds.yml index 10389dc9da0d..dc3d9f378624 100644 --- a/.github/workflows/linux_builds.yml +++ b/.github/workflows/linux_builds.yml @@ -6,18 +6,18 @@ on: env: # Used for the cache key. Add version suffix to force clean build. GODOT_BASE_BRANCH: master - SCONSFLAGS: verbose=yes warnings=extra werror=yes module_text_server_fb_enabled=yes + SCONSFLAGS: verbose=yes warnings=extra werror=yes module_text_server_fb_enabled=yes strict_checks=yes DOTNET_NOLOGO: true DOTNET_CLI_TELEMETRY_OPTOUT: true TSAN_OPTIONS: suppressions=misc/error_suppressions/tsan.txt concurrency: - group: ci-${{github.actor}}-${{github.head_ref || github.run_number}}-${{github.ref}}-linux + group: ci-${{ github.actor }}-${{ github.head_ref || github.run_number }}-${{ github.ref }}-linux cancel-in-progress: true jobs: build-linux: - runs-on: "ubuntu-20.04" + runs-on: ubuntu-20.04 name: ${{ matrix.name }} strategy: fail-fast: false @@ -27,7 +27,7 @@ jobs: cache-name: linux-editor-mono target: editor sconsflags: module_mono_enabled=yes - bin: "./bin/godot.linuxbsd.editor.x86_64.mono" + bin: ./bin/godot.linuxbsd.editor.x86_64.mono build-mono: true tests: false # Disabled due freeze caused by mix Mono build and CI doc-test: true @@ -40,7 +40,7 @@ jobs: target: editor # Debug symbols disabled as they're huge on this build and we hit the 14 GB limit for runners. sconsflags: dev_build=yes scu_build=yes debug_symbols=no precision=double use_asan=yes use_ubsan=yes linker=gold - bin: "./bin/godot.linuxbsd.editor.dev.double.x86_64.san" + bin: ./bin/godot.linuxbsd.editor.dev.double.x86_64.san build-mono: false tests: true proj-test: true @@ -53,7 +53,7 @@ jobs: cache-name: linux-editor-llvm-sanitizers target: editor sconsflags: dev_build=yes use_asan=yes use_ubsan=yes use_llvm=yes linker=lld - bin: "./bin/godot.linuxbsd.editor.dev.x86_64.llvm.san" + bin: ./bin/godot.linuxbsd.editor.dev.x86_64.llvm.san build-mono: false tests: true # Skip 2GiB artifact speeding up action. @@ -66,36 +66,37 @@ jobs: target: editor tests: true sconsflags: dev_build=yes use_tsan=yes use_llvm=yes linker=lld - bin: "./bin/godot.linuxbsd.editor.dev.x86_64.llvm.san" + bin: ./bin/godot.linuxbsd.editor.dev.x86_64.llvm.san build-mono: false # Skip 2GiB artifact speeding up action. artifact: false - - name: Template w/ Mono (target=template_release) + - name: Template w/ Mono (target=template_release, tests=yes) cache-name: linux-template-mono target: template_release - sconsflags: module_mono_enabled=yes tests=yes - bin: "./bin/godot.linuxbsd.template_release.x86_64.mono" + sconsflags: module_mono_enabled=yes + bin: ./bin/godot.linuxbsd.template_release.x86_64.mono build-mono: false tests: true artifact: true - - name: Minimal template (target=template_release, everything disabled) + - name: Minimal template (target=template_release, tests=yes, everything disabled) cache-name: linux-template-minimal target: template_release - sconsflags: modules_enabled_by_default=no disable_3d=yes disable_advanced_gui=yes deprecated=no minizip=no tests=yes - bin: "./bin/godot.linuxbsd.template_release.x86_64" + sconsflags: modules_enabled_by_default=no disable_3d=yes disable_advanced_gui=yes deprecated=no minizip=no + bin: ./bin/godot.linuxbsd.template_release.x86_64 tests: true artifact: true steps: - - uses: actions/checkout@v4 + - name: Checkout + uses: actions/checkout@v4 with: submodules: recursive # Need newer mesa for lavapipe to work properly. - name: Linux dependencies for tests - if: ${{ matrix.proj-test }} + if: matrix.proj-test run: | sudo rm -f /etc/apt/sources.list.d/microsoft-prod.list sudo apt-key adv --keyserver keyserver.ubuntu.com --recv-keys EB8B81E14DA65431D7504EA8F63F0F2B90935439 @@ -120,11 +121,11 @@ jobs: continue-on-error: true - name: Setup Python and SCons - if: ${{ ! matrix.legacy-scons }} + if: '!matrix.legacy-scons' uses: ./.github/actions/godot-deps - name: Setup Python and SCons (legacy versions) - if: ${{ matrix.legacy-scons }} + if: matrix.legacy-scons uses: ./.github/actions/godot-deps with: # Sync with Ensure*Version in SConstruct. @@ -149,48 +150,48 @@ jobs: continue-on-error: true - name: Generate C# glue - if: ${{ matrix.build-mono }} + if: matrix.build-mono run: | ${{ matrix.bin }} --headless --generate-mono-glue ./modules/mono/glue - name: Build .NET solutions - if: ${{ matrix.build-mono }} + if: matrix.build-mono run: | ./modules/mono/build_scripts/build_assemblies.py --godot-output-dir=./bin --godot-platform=linuxbsd - name: Prepare artifact - if: ${{ matrix.artifact }} + if: matrix.artifact run: | strip bin/godot.* chmod +x bin/godot.* - name: Upload artifact uses: ./.github/actions/upload-artifact - if: ${{ matrix.artifact }} + if: matrix.artifact with: name: ${{ matrix.cache-name }} - name: Dump Godot API uses: ./.github/actions/godot-api-dump - if: ${{ matrix.api-dump }} + if: matrix.api-dump with: bin: ${{ matrix.bin }} - name: Unit tests - if: ${{ matrix.tests }} + if: matrix.tests run: | ${{ matrix.bin }} --version ${{ matrix.bin }} --help ${{ matrix.bin }} --headless --test --force-colors - name: .NET source generators tests - if: ${{ matrix.build-mono }} + if: matrix.build-mono run: | dotnet test modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests # Check class reference - name: Check for class reference updates - if: ${{ matrix.doc-test }} + if: matrix.doc-test run: | echo "Running --doctool to see if this changes the public API without updating the documentation." echo -e "If a diff is shown, it means that your code/doc changes are incomplete and you should update the class reference with --doctool.\n\n" @@ -199,20 +200,20 @@ jobs: # Check API backwards compatibility - name: Check for GDExtension compatibility - if: ${{ matrix.api-compat }} + if: matrix.api-compat run: | ./misc/scripts/validate_extension_api.sh "${{ matrix.bin }}" # Download and run the test project - name: Test Godot project uses: ./.github/actions/godot-project-test - if: ${{ matrix.proj-test }} + if: matrix.proj-test with: bin: ${{ matrix.bin }} # Test the project converter - name: Test project converter uses: ./.github/actions/godot-converter-test - if: ${{ matrix.proj-conv }} + if: matrix.proj-conv with: bin: ${{ matrix.bin }} diff --git a/.github/workflows/macos_builds.yml b/.github/workflows/macos_builds.yml index 4db7462b3a85..fcf4b00afb18 100644 --- a/.github/workflows/macos_builds.yml +++ b/.github/workflows/macos_builds.yml @@ -6,15 +6,15 @@ on: env: # Used for the cache key. Add version suffix to force clean build. GODOT_BASE_BRANCH: master - SCONSFLAGS: verbose=yes warnings=extra werror=yes module_text_server_fb_enabled=yes + SCONSFLAGS: verbose=yes warnings=extra werror=yes module_text_server_fb_enabled=yes strict_checks=yes concurrency: - group: ci-${{github.actor}}-${{github.head_ref || github.run_number}}-${{github.ref}}-macos + group: ci-${{ github.actor }}-${{ github.head_ref || github.run_number }}-${{ github.ref }}-macos cancel-in-progress: true jobs: build-macos: - runs-on: "macos-latest" + runs-on: macos-latest name: ${{ matrix.name }} strategy: fail-fast: false @@ -24,17 +24,18 @@ jobs: cache-name: macos-editor target: editor tests: true - bin: "./bin/godot.macos.editor.universal" + bin: ./bin/godot.macos.editor.universal - - name: Template (target=template_release) + - name: Template (target=template_release, tests=yes) cache-name: macos-template target: template_release tests: true - sconsflags: debug_symbols=no tests=yes - bin: "./bin/godot.macos.template_release.universal" + sconsflags: debug_symbols=no + bin: ./bin/godot.macos.template_release.universal steps: - - uses: actions/checkout@v4 + - name: Checkout + uses: actions/checkout@v4 with: submodules: recursive @@ -86,7 +87,7 @@ jobs: name: ${{ matrix.cache-name }} - name: Unit tests - if: ${{ matrix.tests }} + if: matrix.tests run: | ${{ matrix.bin }} --version ${{ matrix.bin }} --help diff --git a/.github/workflows/runner.yml b/.github/workflows/runner.yml index 34b6af43074d..fd5e74b914ad 100644 --- a/.github/workflows/runner.yml +++ b/.github/workflows/runner.yml @@ -1,52 +1,46 @@ name: 🔗 GHA -on: [push, pull_request] +on: [push, pull_request, merge_group] concurrency: - group: ci-${{github.actor}}-${{github.head_ref || github.run_number}}-${{github.ref}}-runner + group: ci-${{ github.actor }}-${{ github.head_ref || github.run_number }}-${{ github.ref }}-runner cancel-in-progress: true jobs: # First stage: Only static checks, fast and prevent expensive builds from running. static-checks: - if: ${{ vars.DISABLE_GODOT_CI == '' }} + if: '!vars.DISABLE_GODOT_CI' name: 📊 Static checks uses: ./.github/workflows/static_checks.yml # Second stage: Run all the builds and some of the tests. android-build: - if: ${{ vars.DISABLE_GODOT_CI == '' }} name: 🤖 Android needs: static-checks uses: ./.github/workflows/android_builds.yml ios-build: - if: ${{ vars.DISABLE_GODOT_CI == '' }} name: 🍏 iOS needs: static-checks uses: ./.github/workflows/ios_builds.yml linux-build: - if: ${{ vars.DISABLE_GODOT_CI == '' }} name: 🐧 Linux needs: static-checks uses: ./.github/workflows/linux_builds.yml macos-build: - if: ${{ vars.DISABLE_GODOT_CI == '' }} name: 🍎 macOS needs: static-checks uses: ./.github/workflows/macos_builds.yml windows-build: - if: ${{ vars.DISABLE_GODOT_CI == '' }} name: 🏁 Windows needs: static-checks uses: ./.github/workflows/windows_builds.yml web-build: - if: ${{ vars.DISABLE_GODOT_CI == '' }} name: 🌐 Web needs: static-checks uses: ./.github/workflows/web_builds.yml @@ -56,7 +50,6 @@ jobs: # Can be turned off for PRs that intentionally break compat with godot-cpp, # until both the upstream PR and the matching godot-cpp changes are merged. godot-cpp-test: - if: ${{ vars.DISABLE_GODOT_CI == '' }} name: 🪲 Godot CPP # This can be changed to depend on another platform, if we decide to use it for # godot-cpp instead. Make sure to move the .github/actions/godot-api-dump step diff --git a/.github/workflows/static_checks.yml b/.github/workflows/static_checks.yml index 9a8a4a8f1912..ff102a06cc57 100644 --- a/.github/workflows/static_checks.yml +++ b/.github/workflows/static_checks.yml @@ -3,7 +3,7 @@ on: workflow_call: concurrency: - group: ci-${{github.actor}}-${{github.head_ref || github.run_number}}-${{github.ref}}-static + group: ci-${{ github.actor }}-${{ github.head_ref || github.run_number }}-${{ github.ref }}-static cancel-in-progress: true jobs: @@ -48,7 +48,7 @@ jobs: - name: Style checks via pre-commit uses: pre-commit/action@v3.0.1 with: - extra_args: --verbose --files ${{ env.CHANGED_FILES }} + extra_args: --files ${{ env.CHANGED_FILES }} - name: Python builders checks via pytest run: | @@ -57,3 +57,7 @@ jobs: - name: Class reference schema checks run: | xmllint --noout --schema doc/class.xsd doc/classes/*.xml modules/*/doc_classes/*.xml platform/*/doc_classes/*.xml + + - name: Run C compiler on `gdextension_interface.h` + run: | + gcc -c core/extension/gdextension_interface.h diff --git a/.github/workflows/web_builds.yml b/.github/workflows/web_builds.yml index d3a6b5b8b828..8e30c99fbc65 100644 --- a/.github/workflows/web_builds.yml +++ b/.github/workflows/web_builds.yml @@ -6,17 +6,17 @@ on: env: # Used for the cache key. Add version suffix to force clean build. GODOT_BASE_BRANCH: master - SCONSFLAGS: verbose=yes warnings=extra werror=yes debug_symbols=no use_closure_compiler=yes + SCONSFLAGS: verbose=yes warnings=extra werror=yes debug_symbols=no use_closure_compiler=yes strict_checks=yes EM_VERSION: 3.1.64 - EM_CACHE_FOLDER: "emsdk-cache" + EM_CACHE_FOLDER: emsdk-cache concurrency: - group: ci-${{github.actor}}-${{github.head_ref || github.run_number}}-${{github.ref}}-web + group: ci-${{ github.actor }}-${{ github.head_ref || github.run_number }}-${{ github.ref }}-web cancel-in-progress: true jobs: web-template: - runs-on: "ubuntu-22.04" + runs-on: ubuntu-22.04 name: ${{ matrix.name }} strategy: fail-fast: false @@ -37,16 +37,17 @@ jobs: artifact: true steps: - - uses: actions/checkout@v4 + - name: Checkout + uses: actions/checkout@v4 with: submodules: recursive - name: Set up Emscripten latest uses: mymindstorm/setup-emsdk@v14 with: - version: ${{env.EM_VERSION}} - actions-cache-folder: ${{env.EM_CACHE_FOLDER}} - cache-key: emsdk-${{ matrix.cache-name }}-${{env.GODOT_BASE_BRANCH}}-${{github.ref}}-${{github.sha}} + version: ${{ env.EM_VERSION }} + actions-cache-folder: ${{ env.EM_CACHE_FOLDER }} + cache-key: emsdk-${{ matrix.cache-name }}-${{ env.GODOT_BASE_BRANCH }}-${{ github.ref }}-${{ github.sha }} - name: Verify Emscripten setup run: | @@ -77,6 +78,6 @@ jobs: - name: Upload artifact uses: ./.github/actions/upload-artifact - if: ${{ matrix.artifact }} + if: matrix.artifact with: name: ${{ matrix.cache-name }} diff --git a/.github/workflows/windows_builds.yml b/.github/workflows/windows_builds.yml index 90629204e6a9..95e3d4a553d1 100644 --- a/.github/workflows/windows_builds.yml +++ b/.github/workflows/windows_builds.yml @@ -7,17 +7,17 @@ on: env: # Used for the cache key. Add version suffix to force clean build. GODOT_BASE_BRANCH: master - SCONSFLAGS: verbose=yes warnings=extra werror=yes module_text_server_fb_enabled=yes d3d12=yes "angle_libs=${{github.workspace}}/" + SCONSFLAGS: verbose=yes warnings=extra werror=yes module_text_server_fb_enabled=yes d3d12=yes strict_checks=yes "angle_libs=${{ github.workspace }}/" SCONS_CACHE_MSVC_CONFIG: true concurrency: - group: ci-${{github.actor}}-${{github.head_ref || github.run_number}}-${{github.ref}}-windows + group: ci-${{ github.actor }}-${{ github.head_ref || github.run_number }}-${{ github.ref }}-windows cancel-in-progress: true jobs: build-windows: # Windows 10 with latest image - runs-on: "windows-latest" + runs-on: windows-latest name: ${{ matrix.name }} strategy: fail-fast: false @@ -29,17 +29,27 @@ jobs: tests: true # 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" + bin: ./bin/godot.windows.editor.x86_64.exe + artifact: true - - name: Template (target=template_release) + - 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, tests=yes) cache-name: windows-template target: template_release tests: true - sconsflags: debug_symbols=no tests=yes - bin: "./bin/godot.windows.template_release.x86_64.console.exe" + sconsflags: debug_symbols=no + bin: ./bin/godot.windows.template_release.x86_64.console.exe + artifact: true steps: - - uses: actions/checkout@v4 + - name: Checkout + uses: actions/checkout@v4 with: submodules: recursive @@ -64,7 +74,7 @@ jobs: target: angle/angle.zip - name: Extract pre-built ANGLE static libraries - run: Expand-Archive -Force angle/angle.zip ${{github.workspace}}/ + run: Expand-Archive -Force angle/angle.zip ${{ github.workspace }}/ - name: Setup MSVC problem matcher uses: ammaraskar/msvc-problem-matcher@master @@ -84,16 +94,18 @@ 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 }} - name: Unit tests - if: ${{ matrix.tests }} + if: matrix.tests run: | ${{ matrix.bin }} --version ${{ matrix.bin }} --help diff --git a/.mailmap b/.mailmap index 078d6c606b11..825e72bfd08f 100644 --- a/.mailmap +++ b/.mailmap @@ -84,6 +84,7 @@ Jean-Michel Bernard Jérôme Gully JFonS jitspoe +Johan Aires Rastén Juan Linietsky Juan Linietsky Juan Linietsky @@ -97,6 +98,7 @@ karroffel Kelly Thomas Kongfa Waroros K. S. Ernest (iFire) Lee +K. S. Ernest (iFire) Lee kleonc <9283098+kleonc@users.noreply.github.com> Leon Krause Leon Krause @@ -142,6 +144,7 @@ Pieter-Jan Briers Pieter-Jan Briers Poommetee Ketson Przemysław Gołąb (n-pigeon) +Radiant <69520693+RadiantUwU@users.noreply.github.com> Rafał Mikrut Rafał Mikrut Ralf Hölzemer diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 46f29d0d5fc9..572eaf67912f 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -9,7 +9,7 @@ exclude: | repos: - repo: https://github.com/pre-commit/mirrors-clang-format - rev: v17.0.6 + rev: v19.1.0 hooks: - id: clang-format files: \.(c|h|cpp|hpp|cc|hh|cxx|hxx|m|mm|inc|java|glsl)$ @@ -17,6 +17,7 @@ repos: exclude: | (?x)^( tests/python_build/.*| + platform/android/java/editor/src/main/java/com/android/.*| platform/android/java/lib/src/com/.* ) @@ -30,6 +31,7 @@ repos: exclude: | (?x)^( tests/python_build/.*| + platform/android/java/editor/src/main/java/com/android/.*| platform/android/java/lib/src/com/.* ) additional_dependencies: [clang-tidy==18.1.1] @@ -37,14 +39,14 @@ repos: stages: [manual] # Not automatically triggered, invoked via `pre-commit run --hook-stage manual clang-tidy` - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.4.4 + rev: v0.6.6 hooks: - id: ruff args: [--fix] - id: ruff-format - repo: https://github.com/pre-commit/mirrors-mypy - rev: v0.971 + rev: v1.11.2 hooks: - id: mypy files: \.py$ @@ -54,6 +56,11 @@ repos: rev: v2.3.0 hooks: - id: codespell + exclude: | + (?x)^( + platform/android/java/editor/src/main/java/com/android/.*| + platform/android/java/lib/src/com/.* + ) additional_dependencies: [tomli] ### Requires Docker; look into alternative implementation. @@ -89,16 +96,21 @@ repos: language: node entry: eslint files: ^(platform/web/js/|modules/|misc/dist/html/).*\.(js|html)$ - args: [--fix, --no-warn-ignored, --no-config-lookup, --config, platform/web/eslint.config.cjs] + args: + - --fix + - --no-warn-ignored + - --no-config-lookup + - --config + - platform/web/eslint.config.cjs additional_dependencies: - '@eslint/js@^9.3.0' - '@html-eslint/eslint-plugin@^0.24.1' - '@html-eslint/parser@^0.24.1' - '@stylistic/eslint-plugin@^2.1.0' - - 'eslint@^9.3.0' - - 'eslint-plugin-html@^8.1.1' - - 'globals@^15.3.0' - - 'espree@^10.0.1' + - eslint@^9.3.0 + - eslint-plugin-html@^8.1.1 + - globals@^15.3.0 + - espree@^10.0.1 - id: jsdoc name: jsdoc @@ -116,7 +128,7 @@ repos: - -d - dry-run pass_filenames: false - additional_dependencies: ['jsdoc@^4.0.3'] + additional_dependencies: [jsdoc@^4.0.3] - id: svgo name: svgo @@ -124,7 +136,7 @@ repos: entry: svgo files: \.svg$ args: [--quiet, --config, misc/utility/svgo.config.mjs] - additional_dependencies: ["svgo@3.3.2"] + additional_dependencies: [svgo@3.3.2] - id: copyright-headers name: copyright-headers @@ -135,6 +147,7 @@ repos: (?x)^( core/math/bvh_.*\.inc$| platform/(?!android|ios|linuxbsd|macos|web|windows)\w+/.*| + platform/android/java/editor/src/main/java/com/android/.*| platform/android/java/lib/src/com/.*| platform/android/java/lib/src/org/godotengine/godot/gl/GLSurfaceView\.java$| platform/android/java/lib/src/org/godotengine/godot/gl/EGLLogWrapper\.java$| @@ -162,6 +175,7 @@ repos: modules/gdscript/tests/scripts/parser/features/mixed_indentation_on_blank_lines\.gd$| modules/gdscript/tests/scripts/parser/warnings/empty_file_newline_comment\.notest\.gd$| modules/gdscript/tests/scripts/parser/warnings/empty_file_newline\.notest\.gd$| + platform/android/java/editor/src/main/java/com/android/.*| platform/android/java/lib/src/com/google/.* ) @@ -170,7 +184,7 @@ repos: language: python entry: python misc/scripts/dotnet_format.py types_or: [c#] - +# # End of upstream Godot pre-commit hooks. # # Keep this separation to let downstream forks add their own hooks to this file, diff --git a/AUTHORS.md b/AUTHORS.md index 76d78dcefa29..b21270b3f700 100644 --- a/AUTHORS.md +++ b/AUTHORS.md @@ -1,11 +1,17 @@ # Godot Engine authors Godot Engine is developed by a community of voluntary contributors who -contribute code, bug reports, documentation, artwork, support, etc. +contribute code, bug reports, documentation, translations, support, etc., +across multiple repositories. It is impossible to list them all; nevertheless, this file aims at listing -the developers who contributed significant patches to this MIT licensed -source code. "Significant" is arbitrarily decided, but should be fair :) +the developers who contributed significant improvements to the engine code. + +To keep the list curated, we use a threshold of a minimum of 11 commits. + +This file does not strictly reflect copyright ownership for the engine +source code. For this, refer to the Git history to know which contributor +wrote which part of the codebase. GitHub usernames are indicated in parentheses, or as sole entry when no other name is available. @@ -25,8 +31,6 @@ name is available. ## Developers -(in alphabetical order, with over 10 commits excluding merges) - Aaron Franke (aaronfranke) Aaron Pagano (aaronp64) Aaron Record (LightningAA) @@ -38,6 +42,7 @@ name is available. Alfred Reinold Baudisch (alfredbaudisch) Alistair Leslie-Hughes (alesliehughes) Alket Rexhepi (alketii) + Alvin Wong (alvinhochun) Andrea Catania (AndreaCatania) Andreia Gaita (shana) Andrii Doroshenko (Xrayez) @@ -46,11 +51,13 @@ name is available. Angad Kambli (angad-k) Anilforextra (AnilBK) Anish Bhobe (KidRigger) + Anni Ryynänen (anniryynanen) Anton Yabchinskiy (a12n) Anutrix Aren Villanueva (kurikaesu) Ariel Manzur (punto-) Arman Elgudzhyan (puchik) + Arseny Kapoulkine (zeux) AThousandShips aXu-AP Bartłomiej T. Listwon (Listwon) @@ -72,6 +79,7 @@ name is available. Carter Anderson (cart) ChibiDenDen Chris Bradfield (cbscribe) + Chris Cranford (Naros) Christian Kaiser (ckaiser) Clay John (clayjohn) ConteZero @@ -86,7 +94,9 @@ name is available. David Cambré (Gallilus) David Sichma (DavidSichma) David Snopek (dsnopek) + derammo Dharkael (lupoDharkael) + Dirk Steinmetz (rsjtdrjgfuzkfg) Dmitry Koteroff (Krakean) Dmitry Maganov (vonagam) Dominik Jasiński (dreamsComeTrue) @@ -117,6 +127,7 @@ name is available. Geequlim George Marques (vnen) Gerrit Großkopf (Grosskopf) + Giganzo Gilles Roudiere (groud) Gordon MacPherson (RevoluPowered) Guilherme Felipe de C. G. da Silva (guilhermefelipecgs) @@ -126,7 +137,6 @@ name is available. Hein-Pieter van Braam-Stewart (hpvb) Hendrik Brucker (Geometror) Hilderin - hilfazer Hiroshi Ogawa (hi-ogawa) HolonProduction homer666 @@ -151,6 +161,7 @@ name is available. Jia Jun Chai (SkyLucilfer) jitspoe Joan Fons Sanchez (JFonS) + Johan Aires Rastén (JohanAR) Johan Manuel (29jm) Johannes Witt (HaSa1002) Jonathan Nicholl (jtnicholl) @@ -163,11 +174,13 @@ name is available. Jummit Justo Delgado (mrcdk) karroffel + Kassandra Pucher (PucklaJ) Kelly Thomas (KellyThomas) kleonc Kongfa Waroros (gongpha) Kostadin Damyanov (Max-Might) K. S. Ernest (iFire) Lee (fire) + Kyle Eichlin (likeich) lawnjelly Leon Krause (leonkrause) Liz Haas (27thLiz) @@ -185,6 +198,7 @@ name is available. Marcus Brummer (mbrlabs) Marcus Elg (MCrafterzz) Mariano Javier Suligoy (MarianoGnu) + Mario Liebisch (MarioLiebisch) Mario Schlack (hurikhan) Marios Staikopoulos (marstaik) Marius Hanl (Maran23) @@ -198,6 +212,7 @@ name is available. Masoud BH (masoudbh3) Mateo Kuruk Miccino (kuruk-mm) Matias N. Goldberg (darksylinc) + Matthew Murphy (mashumafi) Matthew (skyace65) Matthias Hölzl (hoelzl) Max Hilbrunner (mhilbrunner) @@ -209,9 +224,10 @@ name is available. MichiRecRoom (LikeLakers2) Micky (Mickeon) Mikael Hermansson (mihe) + Mika Viskari (miv391) MinusKube MJacred - Morris "Tabor" Arroad (mortarroad) + Mounir Tohami (WhalesState) mrezai Muhammad Huri (CakHuri) muiroc @@ -234,6 +250,7 @@ name is available. Patrick Dawson (pkdawson) Patrick Exner (FlameLizard) Patrick (firefly2442) + patwork Paul Batty (Paulb23) Paul Joannon (paulloz) Paul Trojahn (ptrojahn) @@ -245,6 +262,7 @@ name is available. Pieter-Jan Briers (PJB3005) Poommetee Ketson (Noshyaar) Przemysław Gołąb (n-pigeon) + Radiant (RadiantUwU) Rafael M. G. (rafallus) Rafał Mikrut (qarmin) Raffaele Picca (RPicster) @@ -282,6 +300,7 @@ name is available. Stanislav Labzyuk (DarkMessiah) Stijn Hinlopen (hinlopen) stmSi + Stuart Carnie (stuartcarnie) Swarnim Arun (minraws) TC (floppyhammer) TechnoPorg @@ -289,6 +308,7 @@ name is available. Thakee Nathees (ThakeeNathees) thebestnom Theo Hallenius (TheoXD) + Thomas ten Cate (ttencate) Timo Schwarzer (timoschwarzer) Timothé Bonhoure (ajreckof) Timo (toger5) diff --git a/COPYRIGHT.txt b/COPYRIGHT.txt index 1e32c87c0167..6d227d6615a7 100644 --- a/COPYRIGHT.txt +++ b/COPYRIGHT.txt @@ -63,6 +63,44 @@ Copyright: 2011, Ole Kniemeyer, MAXON, www.maxon.net 2007-2014, Juan Linietsky, Ariel Manzur License: Expat and Zlib +Files: ./modules/godot_physics_2d/godot_joints_2d.cpp +Comment: Chipmunk2D Joint Constraints +Copyright: 2007, Scott Lembcke +License: Expat + +Files: ./modules/godot_physics_3d/gjk_epa.cpp + ./modules/godot_physics_3d/joints/godot_generic_6dof_joint_3d.cpp + ./modules/godot_physics_3d/joints/godot_generic_6dof_joint_3d.h + ./modules/godot_physics_3d/joints/godot_hinge_joint_3d.cpp + ./modules/godot_physics_3d/joints/godot_hinge_joint_3d_sw.h + ./modules/godot_physics_3d/joints/godot_jacobian_entry_3d_sw.h + ./modules/godot_physics_3d/joints/godot_pin_joint_3d.cpp + ./modules/godot_physics_3d/joints/godot_pin_joint_3d.h + ./modules/godot_physics_3d/joints/godot_slider_joint_3d.cpp + ./modules/godot_physics_3d/joints/godot_slider_joint_3d.h + ./modules/godot_physics_3d/godot_soft_body_3d.cpp + ./modules/godot_physics_3d/godot_soft_body_3d.h + ./modules/godot_physics_3d/godot_shape_3d.cpp + ./modules/godot_physics_3d/godot_shape_3d.h +Comment: Bullet Continuous Collision Detection and Physics Library +Copyright: 2003-2008, Erwin Coumans + 2014-present, Godot Engine contributors + 2007-2014, Juan Linietsky, Ariel Manzur +License: Expat and Zlib + +Files: ./modules/godot_physics_3d/godot_collision_solver_3d_sat.cpp +Comment: Open Dynamics Engine +Copyright: 2001-2003, Russell L. Smith, Alen Ladavac, Nguyen Binh +License: BSD-3-clause + +Files: ./modules/godot_physics_3d/joints/godot_cone_twist_joint_3d.cpp + ./modules/godot_physics_3d/joints/godot_cone_twist_joint_3d.h +Comment: Bullet Continuous Collision Detection and Physics Library +Copyright: 2007, Starbreeze Studios + 2014-present, Godot Engine contributors + 2007-2014, Juan Linietsky, Ariel Manzur +License: Expat and Zlib + Files: ./modules/lightmapper_rd/lm_compute.glsl Comment: Joint Non-Local Means (JNLM) denoiser Copyright: 2020, Manuel Prandini @@ -70,7 +108,8 @@ Copyright: 2020, Manuel Prandini 2007-2014, Juan Linietsky, Ariel Manzur License: Expat -Files: ./platform/android/java/lib/aidl/com/android/* +Files: ./platform/android/java/editor/src/main/java/com/android/* + ./platform/android/java/lib/aidl/com/android/* ./platform/android/java/lib/res/layout/status_bar_ongoing_event_progress_bar.xml ./platform/android/java/lib/src/com/google/android/* ./platform/android/java/lib/src/org/godotengine/godot/input/InputManagerCompat.java @@ -92,44 +131,6 @@ Copyright: 2001, Robert Penner 2007-2014, Juan Linietsky, Ariel Manzur License: Expat -Files: ./servers/physics_2d/godot_joints_2d.cpp -Comment: Chipmunk2D Joint Constraints -Copyright: 2007, Scott Lembcke -License: Expat - -Files: ./servers/physics_3d/collision_solver_3d_sat.cpp -Comment: Open Dynamics Engine -Copyright: 2001-2003, Russell L. Smith, Alen Ladavac, Nguyen Binh -License: BSD-3-clause - -Files: ./servers/physics_3d/gjk_epa.cpp - ./servers/physics_3d/joints/generic_6dof_joint_3d_sw.cpp - ./servers/physics_3d/joints/generic_6dof_joint_3d_sw.h - ./servers/physics_3d/joints/hinge_joint_3d_sw.cpp - ./servers/physics_3d/joints/hinge_joint_3d_sw.h - ./servers/physics_3d/joints/jacobian_entry_3d_sw.h - ./servers/physics_3d/joints/pin_joint_3d_sw.cpp - ./servers/physics_3d/joints/pin_joint_3d_sw.h - ./servers/physics_3d/joints/slider_joint_3d_sw.cpp - ./servers/physics_3d/joints/slider_joint_3d_sw.h - ./servers/physics_3d/soft_body_3d_sw.cpp - ./servers/physics_3d/soft_body_3d_sw.h - ./servers/physics_3d/shape_3d_sw.cpp - ./servers/physics_3d/shape_3d_sw.h -Comment: Bullet Continuous Collision Detection and Physics Library -Copyright: 2003-2008, Erwin Coumans - 2014-present, Godot Engine contributors - 2007-2014, Juan Linietsky, Ariel Manzur -License: Expat and Zlib - -Files: ./servers/physics_3d/joints/cone_twist_joint_3d_sw.cpp - ./servers/physics_3d/joints/cone_twist_joint_3d_sw.h -Comment: Bullet Continuous Collision Detection and Physics Library -Copyright: 2007, Starbreeze Studios - 2014-present, Godot Engine contributors - 2007-2014, Juan Linietsky, Ariel Manzur -License: Expat and Zlib - Files: ./servers/rendering/renderer_rd/shaders/ss_effects_downsample.glsl ./servers/rendering/renderer_rd/shaders/ssao_blur.glsl ./servers/rendering/renderer_rd/shaders/ssao_importance_map.glsl @@ -475,6 +476,11 @@ Comment: RVO2 Copyright: 2016, University of North Carolina at Chapel Hill License: Apache-2.0 +Files: ./thirdparty/spirv-cross/ +Comment: SPIRV-Cross +Copyright: 2015-2021, Arm Limited +License: Apache-2.0 or Expat + Files: ./thirdparty/spirv-reflect/ Comment: SPIRV-Reflect Copyright: 2017-2022, Google Inc. diff --git a/DONORS.md b/DONORS.md index 899c44306cd4..314a0f649637 100644 --- a/DONORS.md +++ b/DONORS.md @@ -12,12 +12,12 @@ generous deed immortalized in the next stable release of Godot Engine. ## Patrons + Khronos® Group OSS Capital - Re-Logic ## Platinum sponsors - Google Play + Google Play Ramatak V-Sekai W4 Games @@ -25,53 +25,56 @@ generous deed immortalized in the next stable release of Godot Engine. ## Gold sponsors Mega Crit - Pirate Software - Prehensile Tales + Pirate Software + Prehensile Tales Robot Gentleman ## Silver sponsors - Broken Rules - Chasing Carrots + Broken Rules + Chasing Carrots Copia Wealth Studios Indoor Astronaut - Load Complete + LoadComplete Null Orbital Knight Playful Studios + Re-Logic ## Diamond members Bippinbits - Sealow - And 5 anonymous donors + Sylv + And 3 anonymous donors ## Titanium members - Adriaan de Jongh - Anitya Space + Adriaan de Jongh + Anitya Space Basically Games - FDG Entertainment - Game Dev Artisan + FDG Entertainment + Game Dev Artisan Garry Newman - Isaiah Smith - Libretrend + Kenney + Libretrend Life Art Studios Lucid Silence Games Matthew Campbell PolyMars - RPG in a Box Razenpok - Smirk Software + RPG in a Box + Smirk Software + Studio Sunshower + TrampolineTales 粟二华 (Su Erhua) - And 6 anonymous donors + And 4 anonymous donors ## Platinum members Andy Touch BlockImperiumGames (BIG) - Christoph Woinke Christopher Shifflett + Christoph Woinke Cody Bentley Darrin Massena Edward Flick @@ -79,8 +82,8 @@ generous deed immortalized in the next stable release of Godot Engine. HP van Braam iCommitGames Jonah Stich + Justo Delgado Baudí katnamag - Marek Belski Matthew Ekenstedt Memories in 8Bit Mike King @@ -97,13 +100,14 @@ generous deed immortalized in the next stable release of Godot Engine. TigerJ Violin Iliev Vladimír Chvátil - And 16 anonymous donors + And 13 anonymous donors ## Gold members 80px afreytes alMoo Games + alMoo Games Alva Majo Antti Vesanen Asher Glick @@ -119,6 +123,8 @@ generous deed immortalized in the next stable release of Godot Engine. Bryce Dixon c64cosmin Carlo del Mundo + Carl van der Geest + Chocolate Software Cindy Trieu ClarkThyLord Codex404 @@ -135,17 +141,21 @@ generous deed immortalized in the next stable release of Godot Engine. dgehrig dhanielk Distorted Realities + Donkung Dono Don't You Know Who I Am? Inc. Dustuu + Dylan P. Edelweiss Ends Eren Ogrul Eric Brand Eric Phy Faisal Al-Kubaisi (QatariGameDev) + Felix Adam FeralBytes Festzeltgaming.de + Frozen Fractal Gaudipern GlassBrick Grau @@ -153,6 +163,7 @@ generous deed immortalized in the next stable release of Godot Engine. Hayden Oliver hiulit Illyan + Immaculate Lift Studio Ivan Tabashki Jacob (HACKhalo2 Studios) Jam @@ -160,13 +171,12 @@ generous deed immortalized in the next stable release of Godot Engine. Javier Roman Jeff Hungerford Jeronimo Schreyer - Joel Martinez Johannes Wuensch John Gabriel Jonas Yamazaki + Jonathan José Canepa Joshua Stelly - Justin Sasso Kalydi Balázs KAR Games Kiri "ExpiredPopsicle" Artemis @@ -181,7 +191,9 @@ generous deed immortalized in the next stable release of Godot Engine. m1n1ster Manuel Requena Mara Huldra + Marek Belski Martin Šenkeřík + MHDante Michael Gooch Modus Ponens Moshe Harris @@ -190,6 +202,7 @@ generous deed immortalized in the next stable release of Godot Engine. Nassor Paulino da Silva nezticle Niklas Wahrman + Nitzan Bueno Niwl Games NotNet Oathbringer @@ -207,6 +220,7 @@ generous deed immortalized in the next stable release of Godot Engine. re:thinc Richard Ivánek Rudi P + Sam Leathers Samuel Judd ScoreSpace Shiny Shinken @@ -238,12 +252,12 @@ generous deed immortalized in the next stable release of Godot Engine. Zhu Li zikes 嗯大爷 + 潘彦圣 Alex Khayrullin Algebrute Andriy Antanas Paskauskas - anti666 Ari Arisaka Mayuki Arthur S. Muszynski @@ -269,13 +283,11 @@ generous deed immortalized in the next stable release of Godot Engine. Liam Smyth LoparPanda Martin Gulliksson - Martin Soucek Michael Dürwald Michael Policastro n00sh Nicolás Monner Sans Nikita Rotskov - Nikola Whallon Oliver Dick Patrick Wuttke Pete Goodwin @@ -298,7 +310,6 @@ generous deed immortalized in the next stable release of Godot Engine. VoidPointer Yifan Lai - Aaron Mayfield Adam Carr Adam Smeltzer Adisibio @@ -315,7 +326,6 @@ generous deed immortalized in the next stable release of Godot Engine. Ano Nim Arch Toasty Arda Erol - A Really Tall Horse Arturo Rosales Ash K Aubrey Falconer @@ -343,6 +353,7 @@ generous deed immortalized in the next stable release of Godot Engine. Dakota Watkins Daniele Tolomelli Daniel Ramos + Daren Scot Wilson Dave Jansen Davesnothere David Baker @@ -361,12 +372,11 @@ generous deed immortalized in the next stable release of Godot Engine. Eric Stokes Eric Williams Erkki Seppälä - Ewan Holmes - Felix Adam Frank Frying☆Pan Game Endeavor gamerminstrel + Garrett S Gary Thomas gebba Greyson Richey @@ -385,7 +395,6 @@ generous deed immortalized in the next stable release of Godot Engine. Jamie Massey JARKKO PARVIAINEN Jason Evans - Joakim Askenbäck Jonas Jonas Arndt Jonas Yamazaki @@ -421,7 +430,6 @@ generous deed immortalized in the next stable release of Godot Engine. Martin Holas Martin Liška Martin Trbola - Matěj Drábek Mathieu Matt Edwards Maverick @@ -441,7 +449,6 @@ generous deed immortalized in the next stable release of Godot Engine. Neofytos Chimonas Nerdforge Nerdyninja - Nick Eldrenkamp Nik Rudenko Noel Billig ozrk @@ -450,7 +457,6 @@ generous deed immortalized in the next stable release of Godot Engine. Patrick Nafarrete Paul Black Paul Gieske - Paul Mozet Pete Phoenix Jauregui Pierre Caye @@ -462,7 +468,6 @@ generous deed immortalized in the next stable release of Godot Engine. Raghava Kovvali Ragnar Pettersson Rammeow - Rebecca H Richard Hayes Riley RobotCritter @@ -508,7 +513,7 @@ generous deed immortalized in the next stable release of Godot Engine. ケルベロス 貴宏 小松 - And 181 anonymous donors + And 176 anonymous donors ## Silver and bronze donors diff --git a/SConstruct b/SConstruct index 0ae8f1a38729..63cff4fe1672 100644 --- a/SConstruct +++ b/SConstruct @@ -1,4 +1,5 @@ #!/usr/bin/env python +from misc.utility.scons_hints import * EnsureSConsVersion(3, 1, 2) EnsurePythonVersion(3, 6) @@ -218,10 +219,11 @@ opts.Add(BoolVariable("deprecated", "Enable compatibility code for deprecated an opts.Add(EnumVariable("precision", "Set the floating-point precision level", "single", ("single", "double"))) opts.Add(BoolVariable("minizip", "Enable ZIP archive support using minizip", True)) opts.Add(BoolVariable("brotli", "Enable Brotli for decompresson and WOFF2 fonts support", True)) -opts.Add(BoolVariable("xaudio2", "Enable the XAudio2 audio driver", False)) +opts.Add(BoolVariable("xaudio2", "Enable the XAudio2 audio driver on supported platforms", False)) opts.Add(BoolVariable("vulkan", "Enable the vulkan rendering driver", True)) opts.Add(BoolVariable("opengl3", "Enable the OpenGL/GLES3 rendering driver", True)) -opts.Add(BoolVariable("d3d12", "Enable the Direct3D 12 rendering driver", False)) +opts.Add(BoolVariable("d3d12", "Enable the Direct3D 12 rendering driver on supported platforms", False)) +opts.Add(BoolVariable("metal", "Enable the Metal rendering driver on supported platforms (Apple arm64 only)", False)) opts.Add(BoolVariable("openxr", "Enable the OpenXR driver", True)) opts.Add(BoolVariable("use_volk", "Use the volk library to load the Vulkan loader dynamically", True)) opts.Add(BoolVariable("disable_exceptions", "Force disabling exception handling code", True)) @@ -229,11 +231,22 @@ opts.Add("custom_modules", "A list of comma-separated directory paths containing opts.Add(BoolVariable("custom_modules_recursive", "Detect custom modules recursively for each specified path.", True)) # Advanced options -opts.Add(BoolVariable("dev_mode", "Alias for dev options: verbose=yes warnings=extra werror=yes tests=yes", False)) +opts.Add( + BoolVariable( + "dev_mode", "Alias for dev options: verbose=yes warnings=extra werror=yes tests=yes strict_checks=yes", False + ) +) opts.Add(BoolVariable("tests", "Build the unit tests", False)) opts.Add(BoolVariable("fast_unsafe", "Enable unsafe options for faster rebuilds", False)) opts.Add(BoolVariable("ninja", "Use the ninja backend for faster rebuilds", False)) +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"))) @@ -254,6 +267,7 @@ opts.Add( "", ) opts.Add(BoolVariable("use_precise_math_checks", "Math checks use very precise epsilon (debug option)", False)) +opts.Add(BoolVariable("strict_checks", "Enforce stricter checks (debug option)", False)) opts.Add(BoolVariable("scu_build", "Use single compilation unit build", False)) opts.Add("scu_limit", "Max includes per SCU file when using scu_build (determines RAM use)", "0") opts.Add(BoolVariable("engine_update_check", "Enable engine update checks in the Project Manager", True)) @@ -285,7 +299,6 @@ opts.Add(BoolVariable("builtin_pcre2_with_jit", "Use JIT compiler for the built- opts.Add(BoolVariable("builtin_recastnavigation", "Use the built-in Recast navigation library", True)) opts.Add(BoolVariable("builtin_rvo2_2d", "Use the built-in RVO2 2D library", True)) opts.Add(BoolVariable("builtin_rvo2_3d", "Use the built-in RVO2 3D library", True)) -opts.Add(BoolVariable("builtin_squish", "Use the built-in squish library", True)) opts.Add(BoolVariable("builtin_xatlas", "Use the built-in xatlas library", True)) opts.Add(BoolVariable("builtin_zlib", "Use the built-in zlib library", True)) opts.Add(BoolVariable("builtin_zstd", "Use the built-in Zstd library", True)) @@ -537,16 +550,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 = "" @@ -588,12 +607,16 @@ if env["dev_mode"]: env["warnings"] = ARGUMENTS.get("warnings", "extra") env["werror"] = methods.get_cmdline_bool("werror", True) env["tests"] = methods.get_cmdline_bool("tests", True) + env["strict_checks"] = methods.get_cmdline_bool("strict_checks", True) if env["production"]: env["use_static_cpp"] = methods.get_cmdline_bool("use_static_cpp", True) env["debug_symbols"] = methods.get_cmdline_bool("debug_symbols", False) # LTO "auto" means we handle the preferred option in each platform detect.py. env["lto"] = ARGUMENTS.get("lto", "auto") +if env["strict_checks"]: + env.Append(CPPDEFINES=["STRICT_CHECKS"]) + # Run SCU file generation script if in a SCU build. if env["scu_build"]: max_includes_per_scu = 8 @@ -616,25 +639,18 @@ if env.dev_build: print("NOTE: Developer build, with debug optimization level and debug symbols (unless overridden).") # Enforce our minimal compiler version requirements -cc_version = methods.get_compiler_version(env) or { - "major": None, - "minor": None, - "patch": None, - "metadata1": None, - "metadata2": None, - "date": None, -} -cc_version_major = int(cc_version["major"] or -1) -cc_version_minor = int(cc_version["minor"] or -1) -cc_version_metadata1 = cc_version["metadata1"] or "" - -if methods.using_gcc(env): - if cc_version_major == -1: - print_warning( - "Couldn't detect compiler version, skipping version checks. " - "Build may fail if the compiler doesn't support C++17 fully." - ) - elif cc_version_major < 9: +cc_version = methods.get_compiler_version(env) +cc_version_major = cc_version["major"] +cc_version_minor = cc_version["minor"] +cc_version_metadata1 = cc_version["metadata1"] + +if cc_version_major == -1: + print_warning( + "Couldn't detect compiler version, skipping version checks. " + "Build may fail if the compiler doesn't support C++17 fully." + ) +elif methods.using_gcc(env): + if cc_version_major < 9: print_error( "Detected GCC version older than 9, which does not fully support " "C++17, or has bugs when compiling Godot. Supported versions are 9 " @@ -654,17 +670,12 @@ if methods.using_gcc(env): print_warning("GCC < 8 doesn't support -ffile-prefix-map, disabling `debug_paths_relative` option.") env["debug_paths_relative"] = False elif methods.using_clang(env): - if cc_version_major == -1: - print_warning( - "Couldn't detect compiler version, skipping version checks. " - "Build may fail if the compiler doesn't support C++17 fully." - ) # Apple LLVM versions differ from upstream LLVM version \o/, compare # in https://en.wikipedia.org/wiki/Xcode#Toolchain_versions - elif env["platform"] == "macos" or env["platform"] == "ios": + if env["platform"] == "macos" or env["platform"] == "ios": vanilla = methods.is_vanilla_clang(env) if vanilla and cc_version_major < 6: - print_warning( + print_error( "Detected Clang version older than 6, which does not fully support " "C++17. Supported versions are Clang 6 and later." ) @@ -689,6 +700,28 @@ elif methods.using_clang(env): if env["debug_paths_relative"] and cc_version_major < 10: print_warning("Clang < 10 doesn't support -ffile-prefix-map, disabling `debug_paths_relative` option.") env["debug_paths_relative"] = False +elif env.msvc: + # Ensure latest minor builds of Visual Studio 2017/2019. + # https://github.com/godotengine/godot/pull/94995#issuecomment-2336464574 + if cc_version_major == 16 and cc_version_minor < 11: + print_error( + "Detected Visual Studio 2019 version older than 16.11, which has bugs " + "when compiling Godot. Use a newer VS2019 version, or VS2022." + ) + Exit(255) + if cc_version_major == 15 and cc_version_minor < 9: + print_error( + "Detected Visual Studio 2017 version older than 15.9, which has bugs " + "when compiling Godot. Use a newer VS2017 version, or VS2019/VS2022." + ) + Exit(255) + if cc_version_major < 15: + print_error( + "Detected Visual Studio 2015 or earlier, which is unsupported in Godot. " + "Supported versions are Visual Studio 2017 and later." + ) + Exit(255) + # Set optimize and debug_symbols flags. # "custom" means do nothing and let users set their own optimization flags. @@ -766,13 +799,23 @@ if env["lto"] != "none": # This needs to come after `configure`, otherwise we don't have env.msvc. if not env.msvc: # Specifying GNU extensions support explicitly, which are supported by - # both GCC and Clang. Both currently default to gnu11 and gnu++14. + # both GCC and Clang. Both currently default to gnu11 and gnu++17. env.Prepend(CFLAGS=["-std=gnu11"]) env.Prepend(CXXFLAGS=["-std=gnu++17"]) else: - # MSVC doesn't have clear C standard support, /std only covers C++. - # We apply it to CCFLAGS (both C and C++ code) in case it impacts C features. - env.Prepend(CCFLAGS=["/std:c++17"]) + # MSVC started offering C standard support with Visual Studio 2019 16.8, which covers all + # of our supported VS2019 & VS2022 versions; VS2017 will only pass the C++ standard. + env.Prepend(CXXFLAGS=["/std:c++17"]) + if cc_version_major < 16: + print_warning("Visual Studio 2017 cannot specify a C-Standard.") + else: + env.Prepend(CFLAGS=["/std:c11"]) + # MSVC is non-conforming with the C++ standard by default, so we enable more conformance. + # 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). @@ -785,7 +828,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: @@ -835,8 +878,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( @@ -858,9 +904,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"]) @@ -1014,7 +1060,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") @@ -1026,12 +1074,9 @@ if env["ninja"]: Exit(255) SetOption("experimental", "ninja") - env.Tool("ninja") - - # By setting this we allow the user to run ninja by themselves with all - # the flags they need, as apparently automatically running from scons - # is way slower. - SetOption("disable_execute_ninja", True) + env["NINJA_FILE_NAME"] = env["ninja_file"] + env["NINJA_DISABLE_AUTO_RUN"] = not env["ninja_auto_run"] + env.Tool("ninja", "build.ninja") # Threads if env["threads"]: @@ -1070,7 +1115,6 @@ if "check_c_headers" in env: env.AppendUnique(CPPDEFINES=[headers[header]]) -# FIXME: This method mixes both cosmetic progress stuff and cache handling... methods.show_progress(env) # TODO: replace this with `env.Dump(format="json")` # once we start requiring SCons 4.0 as min version. @@ -1094,7 +1138,7 @@ atexit.register(print_elapsed_time) def purge_flaky_files(): - paths_to_keep = ["ninja.build"] + paths_to_keep = ["build.ninja"] for build_failure in GetBuildFailures(): path = build_failure.node.path if os.path.isfile(path) and path not in paths_to_keep: @@ -1102,3 +1146,5 @@ def purge_flaky_files(): atexit.register(purge_flaky_files) + +methods.clean_cache(env) diff --git a/core/SCsub b/core/SCsub index 1bd4eae16c4c..8bda230b870c 100644 --- a/core/SCsub +++ b/core/SCsub @@ -1,4 +1,5 @@ #!/usr/bin/env python +from misc.utility.scons_hints import * Import("env") @@ -140,7 +141,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/config/SCsub b/core/config/SCsub index bf702854905f..1417a258c1a7 100644 --- a/core/config/SCsub +++ b/core/config/SCsub @@ -1,4 +1,5 @@ #!/usr/bin/env python +from misc.utility.scons_hints import * Import("env") diff --git a/core/config/engine.cpp b/core/config/engine.cpp index 3574430cf752..d77c91331426 100644 --- a/core/config/engine.cpp +++ b/core/config/engine.cpp @@ -263,6 +263,18 @@ bool Engine::is_generate_spirv_debug_info_enabled() const { return generate_spirv_debug_info; } +bool Engine::is_extra_gpu_memory_tracking_enabled() const { + return extra_gpu_memory_tracking; +} + +void Engine::set_print_to_stdout(bool p_enabled) { + CoreGlobals::print_line_enabled = p_enabled; +} + +bool Engine::is_printing_to_stdout() const { + return CoreGlobals::print_line_enabled; +} + void Engine::set_print_error_messages(bool p_enabled) { CoreGlobals::print_error_enabled = p_enabled; } diff --git a/core/config/engine.h b/core/config/engine.h index 7e617d8773a0..a0b1ffa98123 100644 --- a/core/config/engine.h +++ b/core/config/engine.h @@ -72,6 +72,7 @@ class Engine { bool abort_on_gpu_errors = false; bool use_validation_layers = false; bool generate_spirv_debug_info = false; + bool extra_gpu_memory_tracking = false; int32_t gpu_idx = -1; uint64_t _process_frames = 0; @@ -127,6 +128,9 @@ class Engine { void set_time_scale(double p_scale); double get_time_scale() const; + void set_print_to_stdout(bool p_enabled); + bool is_printing_to_stdout() const; + void set_print_error_messages(bool p_enabled); bool is_printing_error_messages() const; void print_header(const String &p_string) const; @@ -181,6 +185,7 @@ class Engine { bool is_abort_on_gpu_errors_enabled() const; bool is_validation_layers_enabled() const; bool is_generate_spirv_debug_info_enabled() const; + bool is_extra_gpu_memory_tracking_enabled() const; int32_t get_gpu_index() const; void increment_frames_drawn(); diff --git a/core/config/project_settings.cpp b/core/config/project_settings.cpp index 37a2608c102c..9fe54e57a7a7 100644 --- a/core/config/project_settings.cpp +++ b/core/config/project_settings.cpp @@ -348,7 +348,6 @@ bool ProjectSettings::_get(const StringName &p_name, Variant &r_ret) const { _THREAD_SAFE_METHOD_ if (!props.has(p_name)) { - WARN_PRINT("Property not found: " + String(p_name)); return false; } r_ret = props[p_name].variant; @@ -1016,7 +1015,7 @@ Error ProjectSettings::save_custom(const String &p_path, const CustomMap &p_cust } } // Check for the existence of a csproj file. - if (_csproj_exists(p_path.get_base_dir())) { + if (_csproj_exists(get_resource_path())) { // If there is a csproj file, add the C# feature if it doesn't already exist. if (!project_features.has("C#")) { project_features.append("C#"); @@ -1398,7 +1397,7 @@ void ProjectSettings::_add_builtin_input_map() { } Dictionary action; - action["deadzone"] = Variant(0.5f); + action["deadzone"] = Variant(0.2f); action["events"] = events; String action_name = "input/" + E.key; @@ -1472,10 +1471,6 @@ ProjectSettings::ProjectSettings() { GLOBAL_DEF(PropertyInfo(Variant::INT, "display/window/size/window_height_override", PROPERTY_HINT_RANGE, "0,4320,1,or_greater"), 0); // 8K resolution GLOBAL_DEF("display/window/energy_saving/keep_screen_on", true); -#ifdef TOOLS_ENABLED - GLOBAL_DEF("display/window/energy_saving/keep_screen_on.editor_hint", false); -#endif - GLOBAL_DEF("animation/warnings/check_invalid_track_paths", true); GLOBAL_DEF("animation/warnings/check_angle_interpolation_type_conflicting", true); @@ -1489,15 +1484,6 @@ ProjectSettings::ProjectSettings() { GLOBAL_DEF(PropertyInfo(Variant::INT, "audio/general/ios/session_category", PROPERTY_HINT_ENUM, "Ambient,Multi Route,Play and Record,Playback,Record,Solo Ambient"), 0); GLOBAL_DEF("audio/general/ios/mix_with_others", false); - PackedStringArray extensions; - extensions.push_back("gd"); - if (ClassDB::class_exists("CSharpScript")) { - extensions.push_back("cs"); - } - extensions.push_back("gdshader"); - - GLOBAL_DEF(PropertyInfo(Variant::PACKED_STRING_ARRAY, "editor/script/search_in_file_extensions"), extensions); - _add_builtin_input_map(); // Keep the enum values in sync with the `DisplayServer::ScreenOrientation` enum. @@ -1570,6 +1556,11 @@ ProjectSettings::ProjectSettings() { GLOBAL_DEF("collada/use_ambient", false); + // Input settings + GLOBAL_DEF_BASIC("input_devices/pointing/android/enable_long_press_as_right_click", false); + GLOBAL_DEF_BASIC("input_devices/pointing/android/enable_pan_and_scale_gestures", false); + GLOBAL_DEF_BASIC(PropertyInfo(Variant::INT, "input_devices/pointing/android/rotary_input_scroll_axis", PROPERTY_HINT_ENUM, "Horizontal,Vertical"), 1); + // These properties will not show up in the dialog. If you want to exclude whole groups, use add_hidden_prefix(). GLOBAL_DEF_INTERNAL("application/config/features", PackedStringArray()); GLOBAL_DEF_INTERNAL("internationalization/locale/translation_remaps", PackedStringArray()); @@ -1582,6 +1573,7 @@ ProjectSettings::ProjectSettings() { ProjectSettings::ProjectSettings(const String &p_path) { if (load_custom(p_path) == OK) { + resource_path = p_path.get_base_dir(); project_loaded = true; } } diff --git a/core/core_bind.compat.inc b/core/core_bind.compat.inc new file mode 100644 index 000000000000..3e8ac3c5de6a --- /dev/null +++ b/core/core_bind.compat.inc @@ -0,0 +1,57 @@ +/**************************************************************************/ +/* core_bind.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 + +namespace core_bind { + +// Semaphore + +void Semaphore::_post_bind_compat_93605() { + post(1); +} + +void Semaphore::_bind_compatibility_methods() { + ClassDB::bind_compatibility_method(D_METHOD("post"), &Semaphore::_post_bind_compat_93605); +} + +// OS + +Dictionary OS::_execute_with_pipe_bind_compat_94434(const String &p_path, const Vector &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 // DISABLE_DEPRECATED diff --git a/core/core_bind.cpp b/core/core_bind.cpp index a1b7b81111df..891e3a28c9ed 100644 --- a/core/core_bind.cpp +++ b/core/core_bind.cpp @@ -29,6 +29,7 @@ /**************************************************************************/ #include "core_bind.h" +#include "core_bind.compat.inc" #include "core/config/project_settings.h" #include "core/crypto/crypto_core.h" @@ -56,8 +57,11 @@ Error ResourceLoader::load_threaded_request(const String &p_path, const String & ResourceLoader::ThreadLoadStatus ResourceLoader::load_threaded_get_status(const String &p_path, Array r_progress) { float progress = 0; ::ResourceLoader::ThreadLoadStatus tls = ::ResourceLoader::load_threaded_get_status(p_path, &progress); - r_progress.resize(1); - r_progress[0] = progress; + // Default array should never be modified, it causes the hash of the method to change. + if (!ClassDB::is_default_array_arg(r_progress)) { + r_progress.resize(1); + r_progress[0] = progress; + } return (ThreadLoadStatus)tls; } @@ -115,6 +119,11 @@ bool ResourceLoader::has_cached(const String &p_path) { return ResourceCache::has(local_path); } +Ref 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); } @@ -125,7 +134,7 @@ ResourceUID::ID ResourceLoader::get_resource_uid(const String &p_path) { void ResourceLoader::_bind_methods() { ClassDB::bind_method(D_METHOD("load_threaded_request", "path", "type_hint", "use_sub_threads", "cache_mode"), &ResourceLoader::load_threaded_request, DEFVAL(""), DEFVAL(false), DEFVAL(CACHE_MODE_REUSE)); - ClassDB::bind_method(D_METHOD("load_threaded_get_status", "path", "progress"), &ResourceLoader::load_threaded_get_status, DEFVAL(Array())); + ClassDB::bind_method(D_METHOD("load_threaded_get_status", "path", "progress"), &ResourceLoader::load_threaded_get_status, DEFVAL_ARRAY); ClassDB::bind_method(D_METHOD("load_threaded_get", "path"), &ResourceLoader::load_threaded_get); ClassDB::bind_method(D_METHOD("load", "path", "type_hint", "cache_mode"), &ResourceLoader::load, DEFVAL(""), DEFVAL(CACHE_MODE_REUSE)); @@ -135,6 +144,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); @@ -174,6 +184,10 @@ void ResourceSaver::remove_resource_format_saver(Ref p_form ::ResourceSaver::remove_resource_format_saver(p_format_saver); } +ResourceUID::ID ResourceSaver::get_resource_id_for_path(const String &p_path, bool p_generate) { + return ::ResourceSaver::get_resource_id_for_path(p_path, p_generate); +} + ResourceSaver *ResourceSaver::singleton = nullptr; void ResourceSaver::_bind_methods() { @@ -181,6 +195,7 @@ void ResourceSaver::_bind_methods() { ClassDB::bind_method(D_METHOD("get_recognized_extensions", "type"), &ResourceSaver::get_recognized_extensions); ClassDB::bind_method(D_METHOD("add_resource_format_saver", "format_saver", "at_front"), &ResourceSaver::add_resource_format_saver, DEFVAL(false)); ClassDB::bind_method(D_METHOD("remove_resource_format_saver", "format_saver"), &ResourceSaver::remove_resource_format_saver); + ClassDB::bind_method(D_METHOD("get_resource_id_for_path", "path", "generate"), &ResourceSaver::get_resource_id_for_path, DEFVAL(false)); BIND_BITFIELD_FLAG(FLAG_NONE); BIND_BITFIELD_FLAG(FLAG_RELATIVE_PATHS); @@ -300,19 +315,22 @@ int OS::execute(const String &p_path, const Vector &p_arguments, Array r String pipe; int exitcode = 0; Error err = ::OS::get_singleton()->execute(p_path, args, &pipe, &exitcode, p_read_stderr, nullptr, p_open_console); - r_output.push_back(pipe); + // Default array should never be modified, it causes the hash of the method to change. + if (!ClassDB::is_default_array_arg(r_output)) { + r_output.push_back(pipe); + } if (err != OK) { return -1; } return exitcode; } -Dictionary OS::execute_with_pipe(const String &p_path, const Vector &p_arguments) { +Dictionary OS::execute_with_pipe(const String &p_path, const Vector &p_arguments, bool p_blocking) { List 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 &p_arguments) { @@ -611,8 +629,8 @@ void OS::_bind_methods() { ClassDB::bind_method(D_METHOD("get_system_font_path_for_text", "font_name", "text", "locale", "script", "weight", "stretch", "italic"), &OS::get_system_font_path_for_text, DEFVAL(String()), DEFVAL(String()), DEFVAL(400), DEFVAL(100), DEFVAL(false)); 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", "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", "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); @@ -692,6 +710,7 @@ void OS::_bind_methods() { BIND_ENUM_CONSTANT(RENDERING_DRIVER_VULKAN); BIND_ENUM_CONSTANT(RENDERING_DRIVER_OPENGL3); BIND_ENUM_CONSTANT(RENDERING_DRIVER_D3D12); + BIND_ENUM_CONSTANT(RENDERING_DRIVER_METAL); BIND_ENUM_CONSTANT(SYSTEM_DIR_DESKTOP); BIND_ENUM_CONSTANT(SYSTEM_DIR_DCIM); @@ -1209,14 +1228,15 @@ bool Semaphore::try_wait() { return semaphore.try_wait(); } -void Semaphore::post() { - semaphore.post(); +void Semaphore::post(int p_count) { + ERR_FAIL_COND(p_count <= 0); + semaphore.post(p_count); } void Semaphore::_bind_methods() { ClassDB::bind_method(D_METHOD("wait"), &Semaphore::wait); ClassDB::bind_method(D_METHOD("try_wait"), &Semaphore::try_wait); - ClassDB::bind_method(D_METHOD("post"), &Semaphore::post); + ClassDB::bind_method(D_METHOD("post", "count"), &Semaphore::post, DEFVAL(1)); } ////// Mutex ////// @@ -1404,6 +1424,11 @@ Variant ClassDB::instantiate(const StringName &p_class) const { } } +ClassDB::APIType ClassDB::class_get_api_type(const StringName &p_class) const { + ::ClassDB::APIType api_type = ::ClassDB::get_api_type(p_class); + return (APIType)api_type; +} + bool ClassDB::class_has_signal(const StringName &p_class, const StringName &p_signal) const { return ::ClassDB::has_signal(p_class, p_signal); } @@ -1440,6 +1465,14 @@ TypedArray ClassDB::class_get_property_list(const StringName &p_clas return ret; } +StringName ClassDB::class_get_property_getter(const StringName &p_class, const StringName &p_property) { + return ::ClassDB::get_property_getter(p_class, p_property); +} + +StringName ClassDB::class_get_property_setter(const StringName &p_class, const StringName &p_property) { + return ::ClassDB::get_property_setter(p_class, p_property); +} + Variant ClassDB::class_get_property(Object *p_object, const StringName &p_property) const { Variant ret; ::ClassDB::get_property(p_object, p_property, ret); @@ -1492,6 +1525,23 @@ TypedArray ClassDB::class_get_method_list(const StringName &p_class, return ret; } +Variant ClassDB::class_call_static_method(const Variant **p_arguments, int p_argcount, Callable::CallError &r_call_error) { + if (p_argcount < 2) { + r_call_error.error = Callable::CallError::CALL_ERROR_TOO_FEW_ARGUMENTS; + return Variant::NIL; + } + if (!p_arguments[0]->is_string() || !p_arguments[1]->is_string()) { + r_call_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT; + return Variant::NIL; + } + StringName class_ = *p_arguments[0]; + StringName method = *p_arguments[1]; + const MethodBind *bind = ::ClassDB::get_method(class_, method); + ERR_FAIL_NULL_V_MSG(bind, Variant::NIL, "Cannot find static method."); + ERR_FAIL_COND_V_MSG(!bind->is_static(), Variant::NIL, "Method is not static."); + return bind->call(nullptr, p_arguments + 2, p_argcount - 2, r_call_error); +} + PackedStringArray ClassDB::class_get_integer_constant_list(const StringName &p_class, bool p_no_inheritance) const { List constants; ::ClassDB::get_integer_constant_list(p_class, &constants, p_no_inheritance); @@ -1575,7 +1625,7 @@ void ClassDB::get_argument_options(const StringName &p_function, int p_idx, List pf == "class_has_method" || pf == "class_get_method_list" || pf == "class_get_integer_constant_list" || pf == "class_has_integer_constant" || pf == "class_get_integer_constant" || pf == "class_has_enum" || pf == "class_get_enum_list" || pf == "class_get_enum_constants" || pf == "class_get_integer_constant_enum" || - pf == "is_class_enabled" || pf == "is_class_enum_bitfield"); + pf == "is_class_enabled" || pf == "is_class_enum_bitfield" || pf == "class_get_api_type"); } if (first_argument_is_class || pf == "is_parent_class") { for (const String &E : get_class_list()) { @@ -1596,11 +1646,15 @@ void ClassDB::_bind_methods() { ::ClassDB::bind_method(D_METHOD("can_instantiate", "class"), &ClassDB::can_instantiate); ::ClassDB::bind_method(D_METHOD("instantiate", "class"), &ClassDB::instantiate); + ::ClassDB::bind_method(D_METHOD("class_get_api_type", "class"), &ClassDB::class_get_api_type); + ::ClassDB::bind_method(D_METHOD("class_has_signal", "class", "signal"), &ClassDB::class_has_signal); ::ClassDB::bind_method(D_METHOD("class_get_signal", "class", "signal"), &ClassDB::class_get_signal); ::ClassDB::bind_method(D_METHOD("class_get_signal_list", "class", "no_inheritance"), &ClassDB::class_get_signal_list, DEFVAL(false)); ::ClassDB::bind_method(D_METHOD("class_get_property_list", "class", "no_inheritance"), &ClassDB::class_get_property_list, DEFVAL(false)); + ::ClassDB::bind_method(D_METHOD("class_get_property_getter", "class", "property"), &ClassDB::class_get_property_getter); + ::ClassDB::bind_method(D_METHOD("class_get_property_setter", "class", "property"), &ClassDB::class_get_property_setter); ::ClassDB::bind_method(D_METHOD("class_get_property", "object", "property"), &ClassDB::class_get_property); ::ClassDB::bind_method(D_METHOD("class_set_property", "object", "property", "value"), &ClassDB::class_set_property); @@ -1612,6 +1666,8 @@ void ClassDB::_bind_methods() { ::ClassDB::bind_method(D_METHOD("class_get_method_list", "class", "no_inheritance"), &ClassDB::class_get_method_list, DEFVAL(false)); + ::ClassDB::bind_vararg_method(METHOD_FLAGS_DEFAULT, "class_call_static_method", &ClassDB::class_call_static_method, MethodInfo("class_call_static_method", PropertyInfo(Variant::STRING_NAME, "class"), PropertyInfo(Variant::STRING_NAME, "method"))); + ::ClassDB::bind_method(D_METHOD("class_get_integer_constant_list", "class", "no_inheritance"), &ClassDB::class_get_integer_constant_list, DEFVAL(false)); ::ClassDB::bind_method(D_METHOD("class_has_integer_constant", "class", "name"), &ClassDB::class_has_integer_constant); @@ -1625,6 +1681,12 @@ void ClassDB::_bind_methods() { ::ClassDB::bind_method(D_METHOD("is_class_enum_bitfield", "class", "enum", "no_inheritance"), &ClassDB::is_class_enum_bitfield, DEFVAL(false)); ::ClassDB::bind_method(D_METHOD("is_class_enabled", "class"), &ClassDB::is_class_enabled); + + BIND_ENUM_CONSTANT(API_CORE); + BIND_ENUM_CONSTANT(API_EDITOR); + BIND_ENUM_CONSTANT(API_EXTENSION); + BIND_ENUM_CONSTANT(API_EDITOR_EXTENSION); + BIND_ENUM_CONSTANT(API_NONE); } } // namespace special @@ -1738,7 +1800,7 @@ Object *Engine::get_singleton_object(const StringName &p_name) const { void Engine::register_singleton(const StringName &p_name, Object *p_object) { ERR_FAIL_COND_MSG(has_singleton(p_name), "Singleton already registered: " + String(p_name)); - ERR_FAIL_COND_MSG(!String(p_name).is_valid_identifier(), "Singleton name is not a valid identifier: " + p_name); + ERR_FAIL_COND_MSG(!String(p_name).is_valid_ascii_identifier(), "Singleton name is not a valid identifier: " + p_name); ::Engine::Singleton s; s.class_name = p_name; s.name = p_name; @@ -1791,6 +1853,14 @@ String Engine::get_write_movie_path() const { return ::Engine::get_singleton()->get_write_movie_path(); } +void Engine::set_print_to_stdout(bool p_enabled) { + ::Engine::get_singleton()->set_print_to_stdout(p_enabled); +} + +bool Engine::is_printing_to_stdout() const { + return ::Engine::get_singleton()->is_printing_to_stdout(); +} + void Engine::set_print_error_messages(bool p_enabled) { ::Engine::get_singleton()->set_print_error_messages(p_enabled); } @@ -1859,10 +1929,14 @@ void Engine::_bind_methods() { ClassDB::bind_method(D_METHOD("get_write_movie_path"), &Engine::get_write_movie_path); + ClassDB::bind_method(D_METHOD("set_print_to_stdout", "enabled"), &Engine::set_print_to_stdout); + ClassDB::bind_method(D_METHOD("is_printing_to_stdout"), &Engine::is_printing_to_stdout); + ClassDB::bind_method(D_METHOD("set_print_error_messages", "enabled"), &Engine::set_print_error_messages); ClassDB::bind_method(D_METHOD("is_printing_error_messages"), &Engine::is_printing_error_messages); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "print_error_messages"), "set_print_error_messages", "is_printing_error_messages"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "print_to_stdout"), "set_print_to_stdout", "is_printing_to_stdout"); ADD_PROPERTY(PropertyInfo(Variant::INT, "physics_ticks_per_second"), "set_physics_ticks_per_second", "get_physics_ticks_per_second"); ADD_PROPERTY(PropertyInfo(Variant::INT, "max_physics_steps_per_frame"), "set_max_physics_steps_per_frame", "get_max_physics_steps_per_frame"); ADD_PROPERTY(PropertyInfo(Variant::INT, "max_fps"), "set_max_fps", "get_max_fps"); diff --git a/core/core_bind.h b/core/core_bind.h index b142a2fbbd9c..ce0bde3c05b8 100644 --- a/core/core_bind.h +++ b/core/core_bind.h @@ -73,7 +73,7 @@ class ResourceLoader : public Object { static ResourceLoader *get_singleton() { return singleton; } Error load_threaded_request(const String &p_path, const String &p_type_hint = "", bool p_use_sub_threads = false, CacheMode p_cache_mode = CACHE_MODE_REUSE); - ThreadLoadStatus load_threaded_get_status(const String &p_path, Array r_progress = Array()); + ThreadLoadStatus load_threaded_get_status(const String &p_path, Array r_progress = ClassDB::default_array_arg); Ref load_threaded_get(const String &p_path); Ref load(const String &p_path, const String &p_type_hint = "", CacheMode p_cache_mode = CACHE_MODE_REUSE); @@ -83,6 +83,7 @@ class ResourceLoader : public Object { void set_abort_on_missing_resources(bool p_abort); PackedStringArray get_dependencies(const String &p_path); bool has_cached(const String &p_path); + Ref 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); @@ -115,6 +116,8 @@ class ResourceSaver : public Object { void add_resource_format_saver(Ref p_format_saver, bool p_at_front); void remove_resource_format_saver(Ref p_format_saver); + ResourceUID::ID get_resource_id_for_path(const String &p_path, bool p_generate = false); + ResourceSaver() { singleton = this; } }; @@ -127,11 +130,18 @@ class OS : public Object { static void _bind_methods(); static OS *singleton; +#ifndef DISABLE_DEPRECATED + Dictionary _execute_with_pipe_bind_compat_94434(const String &p_path, const Vector &p_arguments); + + static void _bind_compatibility_methods(); +#endif + public: enum RenderingDriver { RENDERING_DRIVER_VULKAN, RENDERING_DRIVER_OPENGL3, RENDERING_DRIVER_D3D12, + RENDERING_DRIVER_METAL, }; PackedByteArray get_entropy(int p_bytes); @@ -158,8 +168,8 @@ class OS : public Object { Vector 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; String get_executable_path() const; String read_string_from_stdin(); - int execute(const String &p_path, const Vector &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 &p_arguments); + int execute(const String &p_path, const Vector &p_arguments, Array r_output = ClassDB::default_array_arg, bool p_read_stderr = false, bool p_open_console = false); + Dictionary execute_with_pipe(const String &p_path, const Vector &p_arguments, bool p_blocking = true); int create_process(const String &p_path, const Vector &p_arguments, bool p_open_console = false); int create_instance(const Vector &p_arguments); Error kill(int p_pid); @@ -389,12 +399,17 @@ class Semaphore : public RefCounted { GDCLASS(Semaphore, RefCounted); ::Semaphore semaphore; +protected: static void _bind_methods(); +#ifndef DISABLE_DEPRECATED + void _post_bind_compat_93605(); + static void _bind_compatibility_methods(); +#endif // DISABLE_DEPRECATED public: void wait(); bool try_wait(); - void post(); + void post(int p_count = 1); }; class Thread : public RefCounted { @@ -434,6 +449,14 @@ class ClassDB : public Object { static void _bind_methods(); public: + enum APIType { + API_CORE, + API_EDITOR, + API_EXTENSION, + API_EDITOR_EXTENSION, + API_NONE, + }; + PackedStringArray get_class_list() const; PackedStringArray get_inheriters_from_class(const StringName &p_class) const; StringName get_parent_class(const StringName &p_class) const; @@ -442,11 +465,14 @@ class ClassDB : public Object { bool can_instantiate(const StringName &p_class) const; Variant instantiate(const StringName &p_class) const; + APIType class_get_api_type(const StringName &p_class) const; bool class_has_signal(const StringName &p_class, const StringName &p_signal) const; Dictionary class_get_signal(const StringName &p_class, const StringName &p_signal) const; TypedArray class_get_signal_list(const StringName &p_class, bool p_no_inheritance = false) const; TypedArray class_get_property_list(const StringName &p_class, bool p_no_inheritance = false) const; + StringName class_get_property_getter(const StringName &p_class, const StringName &p_property); + StringName class_get_property_setter(const StringName &p_class, const StringName &p_property); Variant class_get_property(Object *p_object, const StringName &p_property) const; Error class_set_property(Object *p_object, const StringName &p_property, const Variant &p_value) const; @@ -457,6 +483,7 @@ class ClassDB : public Object { int class_get_method_argument_count(const StringName &p_class, const StringName &p_method, bool p_no_inheritance = false) const; TypedArray class_get_method_list(const StringName &p_class, bool p_no_inheritance = false) const; + Variant class_call_static_method(const Variant **p_arguments, int p_argcount, Callable::CallError &r_call_error); PackedStringArray class_get_integer_constant_list(const StringName &p_class, bool p_no_inheritance = false) const; bool class_has_integer_constant(const StringName &p_class, const StringName &p_name) const; @@ -542,6 +569,9 @@ class Engine : public Object { // `set_write_movie_path()` is not exposed to the scripting API as changing it at run-time has no effect. String get_write_movie_path() const; + void set_print_to_stdout(bool p_enabled); + bool is_printing_to_stdout() const; + void set_print_error_messages(bool p_enabled); bool is_printing_error_messages() const; @@ -618,4 +648,6 @@ VARIANT_ENUM_CAST(core_bind::Geometry2D::PolyEndType); VARIANT_ENUM_CAST(core_bind::Thread::Priority); +VARIANT_ENUM_CAST(core_bind::special::ClassDB::APIType); + #endif // CORE_BIND_H diff --git a/core/core_constants.cpp b/core/core_constants.cpp index 5322e39ec0de..25da49fa5c3e 100644 --- a/core/core_constants.cpp +++ b/core/core_constants.cpp @@ -671,11 +671,13 @@ 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); BIND_CORE_ENUM_CONSTANT(PROPERTY_HINT_HIDE_QUATERNION_EDIT); BIND_CORE_ENUM_CONSTANT(PROPERTY_HINT_PASSWORD); + BIND_CORE_ENUM_CONSTANT(PROPERTY_HINT_TOOL_BUTTON); BIND_CORE_ENUM_CONSTANT(PROPERTY_HINT_MAX); BIND_CORE_BITFIELD_FLAG(PROPERTY_USAGE_NONE); diff --git a/core/crypto/SCsub b/core/crypto/SCsub index 8cff3cf679cb..3cea6bfb4711 100644 --- a/core/crypto/SCsub +++ b/core/crypto/SCsub @@ -1,4 +1,5 @@ #!/usr/bin/env python +from misc.utility.scons_hints import * Import("env") diff --git a/core/crypto/crypto.cpp b/core/crypto/crypto.cpp index d3d007941057..62bacadf91aa 100644 --- a/core/crypto/crypto.cpp +++ b/core/crypto/crypto.cpp @@ -36,10 +36,10 @@ /// Resources -CryptoKey *(*CryptoKey::_create)() = nullptr; -CryptoKey *CryptoKey::create() { +CryptoKey *(*CryptoKey::_create)(bool p_notify_postinitialize) = nullptr; +CryptoKey *CryptoKey::create(bool p_notify_postinitialize) { if (_create) { - return _create(); + return _create(p_notify_postinitialize); } return nullptr; } @@ -52,10 +52,10 @@ void CryptoKey::_bind_methods() { ClassDB::bind_method(D_METHOD("load_from_string", "string_key", "public_only"), &CryptoKey::load_from_string, DEFVAL(false)); } -X509Certificate *(*X509Certificate::_create)() = nullptr; -X509Certificate *X509Certificate::create() { +X509Certificate *(*X509Certificate::_create)(bool p_notify_postinitialize) = nullptr; +X509Certificate *X509Certificate::create(bool p_notify_postinitialize) { if (_create) { - return _create(); + return _create(p_notify_postinitialize); } return nullptr; } @@ -116,10 +116,10 @@ void HMACContext::_bind_methods() { ClassDB::bind_method(D_METHOD("finish"), &HMACContext::finish); } -HMACContext *(*HMACContext::_create)() = nullptr; -HMACContext *HMACContext::create() { +HMACContext *(*HMACContext::_create)(bool p_notify_postinitialize) = nullptr; +HMACContext *HMACContext::create(bool p_notify_postinitialize) { if (_create) { - return _create(); + return _create(p_notify_postinitialize); } ERR_FAIL_V_MSG(nullptr, "HMACContext is not available when the mbedtls module is disabled."); } @@ -127,10 +127,10 @@ HMACContext *HMACContext::create() { /// Crypto void (*Crypto::_load_default_certificates)(const String &p_path) = nullptr; -Crypto *(*Crypto::_create)() = nullptr; -Crypto *Crypto::create() { +Crypto *(*Crypto::_create)(bool p_notify_postinitialize) = nullptr; +Crypto *Crypto::create(bool p_notify_postinitialize) { if (_create) { - return _create(); + return _create(p_notify_postinitialize); } ERR_FAIL_V_MSG(nullptr, "Crypto is not available when the mbedtls module is disabled."); } diff --git a/core/crypto/crypto.h b/core/crypto/crypto.h index 16649422cffb..c19e6b6773bb 100644 --- a/core/crypto/crypto.h +++ b/core/crypto/crypto.h @@ -42,10 +42,10 @@ class CryptoKey : public Resource { protected: static void _bind_methods(); - static CryptoKey *(*_create)(); + static CryptoKey *(*_create)(bool p_notify_postinitialize); public: - static CryptoKey *create(); + static CryptoKey *create(bool p_notify_postinitialize = true); virtual Error load(const String &p_path, bool p_public_only = false) = 0; virtual Error save(const String &p_path, bool p_public_only = false) = 0; virtual String save_to_string(bool p_public_only = false) = 0; @@ -58,10 +58,10 @@ class X509Certificate : public Resource { protected: static void _bind_methods(); - static X509Certificate *(*_create)(); + static X509Certificate *(*_create)(bool p_notify_postinitialize); public: - static X509Certificate *create(); + static X509Certificate *create(bool p_notify_postinitialize = true); virtual Error load(const String &p_path) = 0; virtual Error load_from_memory(const uint8_t *p_buffer, int p_len) = 0; virtual Error save(const String &p_path) = 0; @@ -106,10 +106,10 @@ class HMACContext : public RefCounted { protected: static void _bind_methods(); - static HMACContext *(*_create)(); + static HMACContext *(*_create)(bool p_notify_postinitialize); public: - static HMACContext *create(); + static HMACContext *create(bool p_notify_postinitialize = true); virtual Error start(HashingContext::HashType p_hash_type, const PackedByteArray &p_key) = 0; virtual Error update(const PackedByteArray &p_data) = 0; @@ -124,11 +124,11 @@ class Crypto : public RefCounted { protected: static void _bind_methods(); - static Crypto *(*_create)(); + static Crypto *(*_create)(bool p_notify_postinitialize); static void (*_load_default_certificates)(const String &p_path); public: - static Crypto *create(); + static Crypto *create(bool p_notify_postinitialize = true); static void load_default_certificates(const String &p_path); virtual PackedByteArray generate_random_bytes(int p_bytes) = 0; diff --git a/core/debugger/SCsub b/core/debugger/SCsub index 19a65492252d..ab811758940f 100644 --- a/core/debugger/SCsub +++ b/core/debugger/SCsub @@ -1,4 +1,5 @@ #!/usr/bin/env python +from misc.utility.scons_hints import * Import("env") diff --git a/core/debugger/remote_debugger.cpp b/core/debugger/remote_debugger.cpp index e2ed7245a245..fc1b7b74f992 100644 --- a/core/debugger/remote_debugger.cpp +++ b/core/debugger/remote_debugger.cpp @@ -37,6 +37,7 @@ #include "core/debugger/script_debugger.h" #include "core/input/input.h" #include "core/io/resource_loader.h" +#include "core/math/expression.h" #include "core/object/script_language.h" #include "core/os/os.h" #include "servers/display_server.h" @@ -529,6 +530,41 @@ void RemoteDebugger::debug(bool p_can_continue, bool p_is_error_breakpoint) { } else if (command == "set_skip_breakpoints") { ERR_FAIL_COND(data.is_empty()); script_debugger->set_skip_breakpoints(data[0]); + } else if (command == "evaluate") { + String expression_str = data[0]; + int frame = data[1]; + + ScriptInstance *breaked_instance = script_debugger->get_break_language()->debug_get_stack_level_instance(frame); + if (!breaked_instance) { + break; + } + + List locals; + List local_vals; + + script_debugger->get_break_language()->debug_get_stack_level_locals(frame, &locals, &local_vals); + ERR_FAIL_COND(locals.size() != local_vals.size()); + + PackedStringArray locals_vector; + for (const String &S : locals) { + locals_vector.append(S); + } + + Array local_vals_array; + for (const Variant &V : local_vals) { + local_vals_array.append(V); + } + + Expression expression; + expression.parse(expression_str, locals_vector); + const Variant return_val = expression.execute(local_vals_array, breaked_instance->get_owner()); + + DebuggerMarshalls::ScriptStackVariable stvar; + stvar.name = expression_str; + stvar.value = return_val; + stvar.type = 3; + + send_message("evaluation_return", stvar.serialize()); } else { bool captured = false; ERR_CONTINUE(_try_capture(command, data, captured) != OK); diff --git a/core/debugger/remote_debugger_peer.cpp b/core/debugger/remote_debugger_peer.cpp index 21a9014626db..9dca47a0b4f2 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 672a36c35c19..f40e878d52b9 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 04bd55eabae9..6a7f4355db54 100644 --- a/core/doc_data.h +++ b/core/doc_data.h @@ -522,6 +522,10 @@ class DocData { 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 @@ class DocData { 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 @@ class DocData { 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/error/SCsub b/core/error/SCsub index dfd6248a941c..08089d31b075 100644 --- a/core/error/SCsub +++ b/core/error/SCsub @@ -1,4 +1,5 @@ #!/usr/bin/env python +from misc.utility.scons_hints import * Import("env") diff --git a/core/error/error_list.h b/core/error/error_list.h index abc637106a57..cdf06eb06d7f 100644 --- a/core/error/error_list.h +++ b/core/error/error_list.h @@ -41,6 +41,7 @@ * - Are added to the Error enum in core/error/error_list.h * - Have a description added to error_names in core/error/error_list.cpp * - Are bound with BIND_CORE_ENUM_CONSTANT() in core/core_constants.cpp + * - Have a matching Android version in platform/android/java/lib/src/org/godotengine/godot/error/Error.kt */ enum Error { diff --git a/core/error/error_macros.cpp b/core/error/error_macros.cpp index 8376c0aaf84c..813ee7684ff4 100644 --- a/core/error/error_macros.cpp +++ b/core/error/error_macros.cpp @@ -34,6 +34,12 @@ #include "core/os/os.h" #include "core/string/ustring.h" +// Optional physics interpolation warnings try to include the path to the relevant node. +#if defined(DEBUG_ENABLED) && defined(TOOLS_ENABLED) +#include "core/config/project_settings.h" +#include "scene/main/node.h" +#endif + static ErrorHandlerList *error_handler_list = nullptr; void add_error_handler(ErrorHandlerList *p_handler) { @@ -128,3 +134,48 @@ void _err_print_index_error(const char *p_function, const char *p_file, int p_li void _err_flush_stdout() { fflush(stdout); } + +// Prevent error spam by limiting the warnings to a certain frequency. +void _physics_interpolation_warning(const char *p_function, const char *p_file, int p_line, ObjectID p_id, const char *p_warn_string) { +#if defined(DEBUG_ENABLED) && defined(TOOLS_ENABLED) + const uint32_t warn_max = 2048; + const uint32_t warn_timeout_seconds = 15; + + static uint32_t warn_count = warn_max; + static uint32_t warn_timeout = warn_timeout_seconds; + + uint32_t time_now = UINT32_MAX; + + if (warn_count) { + warn_count--; + } + + if (!warn_count) { + time_now = OS::get_singleton()->get_ticks_msec() / 1000; + } + + if ((warn_count == 0) && (time_now >= warn_timeout)) { + warn_count = warn_max; + warn_timeout = time_now + warn_timeout_seconds; + + if (GLOBAL_GET("debug/settings/physics_interpolation/enable_warnings")) { + // UINT64_MAX means unused. + if (p_id.operator uint64_t() == UINT64_MAX) { + _err_print_error(p_function, p_file, p_line, "[Physics interpolation] " + String(p_warn_string) + " (possibly benign).", false, ERR_HANDLER_WARNING); + } else { + String node_name; + if (p_id.is_valid()) { + Node *node = Object::cast_to(ObjectDB::get_instance(p_id)); + if (node && node->is_inside_tree()) { + node_name = "\"" + String(node->get_path()) + "\""; + } else { + node_name = "\"unknown\""; + } + } + + _err_print_error(p_function, p_file, p_line, "[Physics interpolation] " + String(p_warn_string) + ": " + node_name + " (possibly benign).", false, ERR_HANDLER_WARNING); + } + } + } +#endif +} diff --git a/core/error/error_macros.h b/core/error/error_macros.h index ab7dbcbd44b7..19c16667d01b 100644 --- a/core/error/error_macros.h +++ b/core/error/error_macros.h @@ -31,6 +31,7 @@ #ifndef ERROR_MACROS_H #define ERROR_MACROS_H +#include "core/object/object_id.h" #include "core/typedefs.h" #include // We'd normally use safe_refcount.h, but that would cause circular includes. @@ -71,6 +72,8 @@ void _err_print_index_error(const char *p_function, const char *p_file, int p_li void _err_print_index_error(const char *p_function, const char *p_file, int p_line, int64_t p_index, int64_t p_size, const char *p_index_str, const char *p_size_str, const String &p_message, bool p_editor_notify = false, bool fatal = false); void _err_flush_stdout(); +void _physics_interpolation_warning(const char *p_function, const char *p_file, int p_line, ObjectID p_id, const char *p_warn_string); + #ifdef __GNUC__ //#define FUNCTION_STR __PRETTY_FUNCTION__ - too annoying #define FUNCTION_STR __FUNCTION__ @@ -832,4 +835,14 @@ void _err_flush_stdout(); #define DEV_CHECK_ONCE(m_cond) #endif +/** + * Physics Interpolation warnings. + * These are spam protection warnings. + */ +#define PHYSICS_INTERPOLATION_NODE_WARNING(m_object_id, m_string) \ + _physics_interpolation_warning(FUNCTION_STR, __FILE__, __LINE__, m_object_id, m_string) + +#define PHYSICS_INTERPOLATION_WARNING(m_string) \ + _physics_interpolation_warning(FUNCTION_STR, __FILE__, __LINE__, ObjectID(UINT64_MAX), m_string) + #endif // ERROR_MACROS_H diff --git a/core/extension/SCsub b/core/extension/SCsub index 6ab2d2b0a672..8688ca5b6e7c 100644 --- a/core/extension/SCsub +++ b/core/extension/SCsub @@ -1,4 +1,5 @@ #!/usr/bin/env python +from misc.utility.scons_hints import * Import("env") diff --git a/core/extension/extension_api_dump.cpp b/core/extension/extension_api_dump.cpp index 848b6f3886e5..c5f7502c12f7 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); } @@ -85,7 +88,7 @@ static String get_property_info_type_name(const PropertyInfo &p_info) { } static String get_type_meta_name(const GodotTypeInfo::Metadata metadata) { - static const char *argmeta[11] = { "none", "int8", "int16", "int32", "int64", "uint8", "uint16", "uint32", "uint64", "float", "double" }; + static const char *argmeta[13] = { "none", "int8", "int16", "int32", "int64", "uint8", "uint16", "uint32", "uint64", "float", "double", "char16", "char32" }; return argmeta[metadata]; } @@ -1014,6 +1017,7 @@ Dictionary GDExtensionAPIDump::generate_extension_api(bool p_include_docs) { d2["name"] = String(method_name); d2["is_const"] = (F.flags & METHOD_FLAG_CONST) ? true : false; d2["is_static"] = (F.flags & METHOD_FLAG_STATIC) ? true : false; + d2["is_required"] = (F.flags & METHOD_FLAG_VIRTUAL_REQUIRED) ? true : false; d2["is_vararg"] = false; d2["is_virtual"] = true; // virtual functions have no hash since no MethodBind is involved diff --git a/core/extension/gdextension.cpp b/core/extension/gdextension.cpp index 8e2366fc95e1..7cba5cb161ef 100644 --- a/core/extension/gdextension.cpp +++ b/core/extension/gdextension.cpp @@ -32,11 +32,9 @@ #include "gdextension.compat.inc" #include "core/config/project_settings.h" -#include "core/io/dir_access.h" #include "core/object/class_db.h" #include "core/object/method_bind.h" -#include "core/os/os.h" -#include "core/version.h" +#include "gdextension_library_loader.h" #include "gdextension_manager.h" extern void gdextension_setup_interface(); @@ -48,146 +46,6 @@ String GDExtension::get_extension_list_config_file() { return ProjectSettings::get_singleton()->get_project_data_path().path_join("extension_list.cfg"); } -Vector GDExtension::find_extension_dependencies(const String &p_path, Ref p_config, std::function p_has_feature) { - Vector dependencies_shared_objects; - if (p_config->has_section("dependencies")) { - List config_dependencies; - p_config->get_section_keys("dependencies", &config_dependencies); - - for (const String &dependency : config_dependencies) { - Vector dependency_tags = dependency.split("."); - bool all_tags_met = true; - for (int i = 0; i < dependency_tags.size(); i++) { - String tag = dependency_tags[i].strip_edges(); - if (!p_has_feature(tag)) { - all_tags_met = false; - break; - } - } - - if (all_tags_met) { - Dictionary dependency_value = p_config->get_value("dependencies", dependency); - for (const Variant *key = dependency_value.next(nullptr); key; key = dependency_value.next(key)) { - String dependency_path = *key; - String target_path = dependency_value[*key]; - if (dependency_path.is_relative_path()) { - dependency_path = p_path.get_base_dir().path_join(dependency_path); - } - dependencies_shared_objects.push_back(SharedObject(dependency_path, dependency_tags, target_path)); - } - break; - } - } - } - - return dependencies_shared_objects; -} - -String GDExtension::find_extension_library(const String &p_path, Ref p_config, std::function p_has_feature, PackedStringArray *r_tags) { - // First, check the explicit libraries. - if (p_config->has_section("libraries")) { - List libraries; - p_config->get_section_keys("libraries", &libraries); - - // Iterate the libraries, finding the best matching tags. - String best_library_path; - Vector best_library_tags; - for (const String &E : libraries) { - Vector tags = E.split("."); - bool all_tags_met = true; - for (int i = 0; i < tags.size(); i++) { - String tag = tags[i].strip_edges(); - if (!p_has_feature(tag)) { - all_tags_met = false; - break; - } - } - - if (all_tags_met && tags.size() > best_library_tags.size()) { - best_library_path = p_config->get_value("libraries", E); - best_library_tags = tags; - } - } - - if (!best_library_path.is_empty()) { - if (best_library_path.is_relative_path()) { - best_library_path = p_path.get_base_dir().path_join(best_library_path); - } - if (r_tags != nullptr) { - r_tags->append_array(best_library_tags); - } - return best_library_path; - } - } - - // Second, try to autodetect - String autodetect_library_prefix; - if (p_config->has_section_key("configuration", "autodetect_library_prefix")) { - autodetect_library_prefix = p_config->get_value("configuration", "autodetect_library_prefix"); - } - if (!autodetect_library_prefix.is_empty()) { - String autodetect_path = autodetect_library_prefix; - if (autodetect_path.is_relative_path()) { - autodetect_path = p_path.get_base_dir().path_join(autodetect_path); - } - - // Find the folder and file parts of the prefix. - String folder; - String file_prefix; - if (DirAccess::dir_exists_absolute(autodetect_path)) { - folder = autodetect_path; - } else if (DirAccess::dir_exists_absolute(autodetect_path.get_base_dir())) { - folder = autodetect_path.get_base_dir(); - file_prefix = autodetect_path.get_file(); - } else { - ERR_FAIL_V_MSG(String(), vformat("Error in extension: %s. Could not find folder for automatic detection of libraries files. autodetect_library_prefix=\"%s\"", p_path, autodetect_library_prefix)); - } - - // Open the folder. - Ref dir = DirAccess::open(folder); - ERR_FAIL_COND_V_MSG(!dir.is_valid(), String(), vformat("Error in extension: %s. Could not open folder for automatic detection of libraries files. autodetect_library_prefix=\"%s\"", p_path, autodetect_library_prefix)); - - // Iterate the files and check the prefixes, finding the best matching file. - String best_file; - Vector best_file_tags; - dir->list_dir_begin(); - String file_name = dir->_get_next(); - while (file_name != "") { - if (!dir->current_is_dir() && file_name.begins_with(file_prefix)) { - // Check if the files matches all requested feature tags. - String tags_str = file_name.trim_prefix(file_prefix); - tags_str = tags_str.trim_suffix(tags_str.get_extension()); - - Vector tags = tags_str.split(".", false); - bool all_tags_met = true; - for (int i = 0; i < tags.size(); i++) { - String tag = tags[i].strip_edges(); - if (!p_has_feature(tag)) { - all_tags_met = false; - break; - } - } - - // If all tags are found in the feature list, and we found more tags than before, use this file. - if (all_tags_met && tags.size() > best_file_tags.size()) { - best_file_tags = tags; - best_file = file_name; - } - } - file_name = dir->_get_next(); - } - - if (!best_file.is_empty()) { - String library_path = folder.path_join(best_file); - if (r_tags != nullptr) { - r_tags->append_array(best_file_tags); - } - return library_path; - } - } - return String(); -} - class GDExtensionMethodBind : public MethodBind { GDExtensionClassMethodCall call_func; GDExtensionClassMethodValidatedCall validated_call_func; @@ -382,7 +240,7 @@ class GDExtensionMethodBind : public MethodBind { #ifndef DISABLE_DEPRECATED void GDExtension::_register_extension_class(GDExtensionClassLibraryPtr p_library, GDExtensionConstStringNamePtr p_class_name, GDExtensionConstStringNamePtr p_parent_class_name, const GDExtensionClassCreationInfo *p_extension_funcs) { - const GDExtensionClassCreationInfo3 class_info3 = { + const GDExtensionClassCreationInfo4 class_info4 = { p_extension_funcs->is_virtual, // GDExtensionBool is_virtual; p_extension_funcs->is_abstract, // GDExtensionBool is_abstract; true, // GDExtensionBool is_exposed; @@ -398,25 +256,26 @@ void GDExtension::_register_extension_class(GDExtensionClassLibraryPtr p_library p_extension_funcs->to_string_func, // GDExtensionClassToString to_string_func; p_extension_funcs->reference_func, // GDExtensionClassReference reference_func; p_extension_funcs->unreference_func, // GDExtensionClassUnreference unreference_func; - p_extension_funcs->create_instance_func, // GDExtensionClassCreateInstance create_instance_func; /* this one is mandatory */ + nullptr, // GDExtensionClassCreateInstance2 create_instance_func; /* this one is mandatory */ p_extension_funcs->free_instance_func, // GDExtensionClassFreeInstance free_instance_func; /* this one is mandatory */ nullptr, // GDExtensionClassRecreateInstance recreate_instance_func; p_extension_funcs->get_virtual_func, // GDExtensionClassGetVirtual get_virtual_func; nullptr, // GDExtensionClassGetVirtualCallData get_virtual_call_data_func; nullptr, // GDExtensionClassCallVirtualWithData call_virtual_func; - p_extension_funcs->get_rid_func, // GDExtensionClassGetRID get_rid; p_extension_funcs->class_userdata, // void *class_userdata; }; const ClassCreationDeprecatedInfo legacy = { p_extension_funcs->notification_func, // GDExtensionClassNotification notification_func; p_extension_funcs->free_property_list_func, // GDExtensionClassFreePropertyList free_property_list_func; + p_extension_funcs->create_instance_func, // GDExtensionClassCreateInstance create_instance_func; + p_extension_funcs->get_rid_func, // GDExtensionClassGetRID get_rid; }; - _register_extension_class_internal(p_library, p_class_name, p_parent_class_name, &class_info3, &legacy); + _register_extension_class_internal(p_library, p_class_name, p_parent_class_name, &class_info4, &legacy); } void GDExtension::_register_extension_class2(GDExtensionClassLibraryPtr p_library, GDExtensionConstStringNamePtr p_class_name, GDExtensionConstStringNamePtr p_parent_class_name, const GDExtensionClassCreationInfo2 *p_extension_funcs) { - const GDExtensionClassCreationInfo3 class_info3 = { + const GDExtensionClassCreationInfo4 class_info4 = { p_extension_funcs->is_virtual, // GDExtensionBool is_virtual; p_extension_funcs->is_abstract, // GDExtensionBool is_abstract; p_extension_funcs->is_exposed, // GDExtensionBool is_exposed; @@ -432,34 +291,71 @@ void GDExtension::_register_extension_class2(GDExtensionClassLibraryPtr p_librar p_extension_funcs->to_string_func, // GDExtensionClassToString to_string_func; p_extension_funcs->reference_func, // GDExtensionClassReference reference_func; p_extension_funcs->unreference_func, // GDExtensionClassUnreference unreference_func; - p_extension_funcs->create_instance_func, // GDExtensionClassCreateInstance create_instance_func; /* this one is mandatory */ + nullptr, // GDExtensionClassCreateInstance2 create_instance_func; /* this one is mandatory */ p_extension_funcs->free_instance_func, // GDExtensionClassFreeInstance free_instance_func; /* this one is mandatory */ p_extension_funcs->recreate_instance_func, // GDExtensionClassRecreateInstance recreate_instance_func; p_extension_funcs->get_virtual_func, // GDExtensionClassGetVirtual get_virtual_func; p_extension_funcs->get_virtual_call_data_func, // GDExtensionClassGetVirtualCallData get_virtual_call_data_func; p_extension_funcs->call_virtual_with_data_func, // GDExtensionClassCallVirtualWithData call_virtual_func; - p_extension_funcs->get_rid_func, // GDExtensionClassGetRID get_rid; p_extension_funcs->class_userdata, // void *class_userdata; }; const ClassCreationDeprecatedInfo legacy = { nullptr, // GDExtensionClassNotification notification_func; p_extension_funcs->free_property_list_func, // GDExtensionClassFreePropertyList free_property_list_func; + p_extension_funcs->create_instance_func, // GDExtensionClassCreateInstance create_instance_func; + p_extension_funcs->get_rid_func, // GDExtensionClassGetRID get_rid; }; - _register_extension_class_internal(p_library, p_class_name, p_parent_class_name, &class_info3, &legacy); + _register_extension_class_internal(p_library, p_class_name, p_parent_class_name, &class_info4, &legacy); } -#endif // DISABLE_DEPRECATED void GDExtension::_register_extension_class3(GDExtensionClassLibraryPtr p_library, GDExtensionConstStringNamePtr p_class_name, GDExtensionConstStringNamePtr p_parent_class_name, const GDExtensionClassCreationInfo3 *p_extension_funcs) { + const GDExtensionClassCreationInfo4 class_info4 = { + p_extension_funcs->is_virtual, // GDExtensionBool is_virtual; + p_extension_funcs->is_abstract, // GDExtensionBool is_abstract; + p_extension_funcs->is_exposed, // GDExtensionBool is_exposed; + p_extension_funcs->is_runtime, // GDExtensionBool is_runtime; + p_extension_funcs->set_func, // GDExtensionClassSet set_func; + p_extension_funcs->get_func, // GDExtensionClassGet get_func; + p_extension_funcs->get_property_list_func, // GDExtensionClassGetPropertyList get_property_list_func; + p_extension_funcs->free_property_list_func, // GDExtensionClassFreePropertyList free_property_list_func; + p_extension_funcs->property_can_revert_func, // GDExtensionClassPropertyCanRevert property_can_revert_func; + p_extension_funcs->property_get_revert_func, // GDExtensionClassPropertyGetRevert property_get_revert_func; + p_extension_funcs->validate_property_func, // GDExtensionClassValidateProperty validate_property_func; + p_extension_funcs->notification_func, // GDExtensionClassNotification2 notification_func; + p_extension_funcs->to_string_func, // GDExtensionClassToString to_string_func; + p_extension_funcs->reference_func, // GDExtensionClassReference reference_func; + p_extension_funcs->unreference_func, // GDExtensionClassUnreference unreference_func; + nullptr, // GDExtensionClassCreateInstance2 create_instance_func; /* this one is mandatory */ + p_extension_funcs->free_instance_func, // GDExtensionClassFreeInstance free_instance_func; /* this one is mandatory */ + p_extension_funcs->recreate_instance_func, // GDExtensionClassRecreateInstance recreate_instance_func; + p_extension_funcs->get_virtual_func, // GDExtensionClassGetVirtual get_virtual_func; + p_extension_funcs->get_virtual_call_data_func, // GDExtensionClassGetVirtualCallData get_virtual_call_data_func; + p_extension_funcs->call_virtual_with_data_func, // GDExtensionClassCallVirtualWithData call_virtual_func; + p_extension_funcs->class_userdata, // void *class_userdata; + }; + + const ClassCreationDeprecatedInfo legacy = { + nullptr, // GDExtensionClassNotification notification_func; + nullptr, // GDExtensionClassFreePropertyList free_property_list_func; + p_extension_funcs->create_instance_func, // GDExtensionClassCreateInstance2 create_instance_func; + p_extension_funcs->get_rid_func, // GDExtensionClassGetRID get_rid; + }; + _register_extension_class_internal(p_library, p_class_name, p_parent_class_name, &class_info4, &legacy); +} + +#endif // DISABLE_DEPRECATED + +void GDExtension::_register_extension_class4(GDExtensionClassLibraryPtr p_library, GDExtensionConstStringNamePtr p_class_name, GDExtensionConstStringNamePtr p_parent_class_name, const GDExtensionClassCreationInfo4 *p_extension_funcs) { _register_extension_class_internal(p_library, p_class_name, p_parent_class_name, p_extension_funcs); } -void GDExtension::_register_extension_class_internal(GDExtensionClassLibraryPtr p_library, GDExtensionConstStringNamePtr p_class_name, GDExtensionConstStringNamePtr p_parent_class_name, const GDExtensionClassCreationInfo3 *p_extension_funcs, const ClassCreationDeprecatedInfo *p_deprecated_funcs) { +void GDExtension::_register_extension_class_internal(GDExtensionClassLibraryPtr p_library, GDExtensionConstStringNamePtr p_class_name, GDExtensionConstStringNamePtr p_parent_class_name, const GDExtensionClassCreationInfo4 *p_extension_funcs, const ClassCreationDeprecatedInfo *p_deprecated_funcs) { GDExtension *self = reinterpret_cast(p_library); StringName class_name = *reinterpret_cast(p_class_name); StringName parent_class_name = *reinterpret_cast(p_parent_class_name); - ERR_FAIL_COND_MSG(!String(class_name).is_valid_identifier(), "Attempt to register extension class '" + class_name + "', which is not a valid class identifier."); + ERR_FAIL_COND_MSG(!String(class_name).is_valid_unicode_identifier(), "Attempt to register extension class '" + class_name + "', which is not a valid class identifier."); ERR_FAIL_COND_MSG(ClassDB::class_exists(class_name), "Attempt to register extension class '" + class_name + "', which appears to be already registered."); Extension *parent_extension = nullptr; @@ -530,6 +426,8 @@ void GDExtension::_register_extension_class_internal(GDExtensionClassLibraryPtr if (p_deprecated_funcs) { extension->gdextension.notification = p_deprecated_funcs->notification_func; extension->gdextension.free_property_list = p_deprecated_funcs->free_property_list_func; + extension->gdextension.create_instance = p_deprecated_funcs->create_instance_func; + extension->gdextension.get_rid = p_deprecated_funcs->get_rid_func; } #endif // DISABLE_DEPRECATED extension->gdextension.notification2 = p_extension_funcs->notification_func; @@ -537,13 +435,12 @@ void GDExtension::_register_extension_class_internal(GDExtensionClassLibraryPtr extension->gdextension.reference = p_extension_funcs->reference_func; extension->gdextension.unreference = p_extension_funcs->unreference_func; extension->gdextension.class_userdata = p_extension_funcs->class_userdata; - extension->gdextension.create_instance = p_extension_funcs->create_instance_func; + extension->gdextension.create_instance2 = p_extension_funcs->create_instance_func; extension->gdextension.free_instance = p_extension_funcs->free_instance_func; extension->gdextension.recreate_instance = p_extension_funcs->recreate_instance_func; extension->gdextension.get_virtual = p_extension_funcs->get_virtual_func; extension->gdextension.get_virtual_call_data = p_extension_funcs->get_virtual_call_data_func; extension->gdextension.call_virtual_with_data = p_extension_funcs->call_virtual_with_data_func; - extension->gdextension.get_rid = p_extension_funcs->get_rid_func; extension->gdextension.reloadable = self->reloadable; #ifdef TOOLS_ENABLED @@ -755,7 +652,13 @@ void GDExtension::_unregister_extension_class(GDExtensionClassLibraryPtr p_libra void GDExtension::_get_library_path(GDExtensionClassLibraryPtr p_library, GDExtensionUninitializedStringPtr r_path) { GDExtension *self = reinterpret_cast(p_library); - memnew_placement(r_path, String(self->library_path)); + Ref library_loader = self->loader; + String library_path; + if (library_loader.is_valid()) { + library_path = library_loader->library_path; + } + + memnew_placement(r_path, String(library_path)); } HashMap GDExtension::gdextension_interface_functions; @@ -771,64 +674,32 @@ GDExtensionInterfaceFunctionPtr GDExtension::get_interface_function(const String return *function; } -Error GDExtension::open_library(const String &p_path, const String &p_entry_symbol, Vector *p_dependencies) { - String abs_path = ProjectSettings::get_singleton()->globalize_path(p_path); - - Vector abs_dependencies_paths; - if (p_dependencies != nullptr && !p_dependencies->is_empty()) { - for (const SharedObject &dependency : *p_dependencies) { - abs_dependencies_paths.push_back(ProjectSettings::get_singleton()->globalize_path(dependency.path)); - } - } - - String actual_lib_path; - OS::GDExtensionData data = { - true, // also_set_library_path - &actual_lib_path, // r_resolved_path - Engine::get_singleton()->is_editor_hint(), // generate_temp_files - &abs_dependencies_paths, // library_dependencies - }; - Error err = OS::get_singleton()->open_dynamic_library(abs_path, library, &data); - - if (actual_lib_path.get_file() != abs_path.get_file()) { - // If temporary files are generated, let's change the library path to point at the original, - // because that's what we want to check to see if it's changed. - library_path = actual_lib_path.get_base_dir().path_join(p_path.get_file()); - } else { - library_path = actual_lib_path; - } +Error GDExtension::open_library(const String &p_path, const Ref &p_loader) { + ERR_FAIL_COND_V_MSG(p_loader.is_null(), FAILED, "Can't open GDExtension without a loader."); + loader = p_loader; - 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); + Error err = loader->open_library(p_path); - void *entry_funcptr = nullptr; + 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 = OS::get_singleton()->get_dynamic_library_symbol_handle(library, p_entry_symbol, entry_funcptr, false); + err = loader->initialize(&gdextension_get_proc_address, this, &initialization); if (err != OK) { - ERR_PRINT("GDExtension entry point '" + p_entry_symbol + "' not found in library " + abs_path); - OS::get_singleton()->close_dynamic_library(library); + // Errors already logged in initialize(). + loader->close_library(); return err; } - GDExtensionInitializationFunction initialization_function = (GDExtensionInitializationFunction)entry_funcptr; - GDExtensionBool ret = initialization_function(&gdextension_get_proc_address, this, &initialization); + level_initialized = -1; - if (ret) { - level_initialized = -1; - return OK; - } else { - ERR_PRINT("GDExtension initialization function '" + p_entry_symbol + "' returned an error."); - OS::get_singleton()->close_dynamic_library(library); - return FAILED; - } + return OK; } void GDExtension::close_library() { - ERR_FAIL_NULL(library); - OS::get_singleton()->close_dynamic_library(library); + ERR_FAIL_COND(!is_library_open()); + loader->close_library(); - library = nullptr; class_icon_paths.clear(); #ifdef TOOLS_ENABLED @@ -837,16 +708,16 @@ void GDExtension::close_library() { } bool GDExtension::is_library_open() const { - return library != nullptr; + return loader.is_valid() && loader->is_library_open(); } GDExtension::InitializationLevel GDExtension::get_minimum_library_initialization_level() const { - ERR_FAIL_NULL_V(library, INITIALIZATION_LEVEL_CORE); + ERR_FAIL_COND_V(!is_library_open(), INITIALIZATION_LEVEL_CORE); return InitializationLevel(initialization.minimum_initialization_level); } void GDExtension::initialize_library(InitializationLevel p_level) { - ERR_FAIL_NULL(library); + ERR_FAIL_COND(!is_library_open()); ERR_FAIL_COND_MSG(p_level <= int32_t(level_initialized), vformat("Level '%d' must be higher than the current level '%d'", p_level, level_initialized)); level_initialized = int32_t(p_level); @@ -856,7 +727,7 @@ void GDExtension::initialize_library(InitializationLevel p_level) { initialization.initialize(initialization.userdata, GDExtensionInitializationLevel(p_level)); } void GDExtension::deinitialize_library(InitializationLevel p_level) { - ERR_FAIL_NULL(library); + ERR_FAIL_COND(!is_library_open()); ERR_FAIL_COND(p_level > int32_t(level_initialized)); level_initialized = int32_t(p_level) - 1; @@ -880,7 +751,7 @@ GDExtension::GDExtension() { } GDExtension::~GDExtension() { - if (library != nullptr) { + if (is_library_open()) { close_library(); } #ifdef TOOLS_ENABLED @@ -897,8 +768,9 @@ void GDExtension::initialize_gdextensions() { #ifndef DISABLE_DEPRECATED register_interface_function("classdb_register_extension_class", (GDExtensionInterfaceFunctionPtr)&GDExtension::_register_extension_class); register_interface_function("classdb_register_extension_class2", (GDExtensionInterfaceFunctionPtr)&GDExtension::_register_extension_class2); -#endif // DISABLE_DEPRECATED register_interface_function("classdb_register_extension_class3", (GDExtensionInterfaceFunctionPtr)&GDExtension::_register_extension_class3); +#endif // DISABLE_DEPRECATED + register_interface_function("classdb_register_extension_class4", (GDExtensionInterfaceFunctionPtr)&GDExtension::_register_extension_class4); register_interface_function("classdb_register_extension_class_method", (GDExtensionInterfaceFunctionPtr)&GDExtension::_register_extension_class_method); register_interface_function("classdb_register_extension_class_virtual_method", (GDExtensionInterfaceFunctionPtr)&GDExtension::_register_extension_class_virtual_method); register_interface_function("classdb_register_extension_class_integer_constant", (GDExtensionInterfaceFunctionPtr)&GDExtension::_register_extension_class_integer_constant); @@ -918,142 +790,15 @@ void GDExtension::finalize_gdextensions() { Error GDExtensionResourceLoader::load_gdextension_resource(const String &p_path, Ref &p_extension) { ERR_FAIL_COND_V_MSG(p_extension.is_valid() && p_extension->is_library_open(), ERR_ALREADY_IN_USE, "Cannot load GDExtension resource into already opened library."); - Ref config; - config.instantiate(); - - Error err = config->load(p_path); - - if (err != OK) { - ERR_PRINT("Error loading GDExtension configuration file: " + p_path); - return err; - } - - if (!config->has_section_key("configuration", "entry_symbol")) { - ERR_PRINT("GDExtension configuration file must contain a \"configuration/entry_symbol\" key: " + p_path); - return ERR_INVALID_DATA; - } - - String entry_symbol = config->get_value("configuration", "entry_symbol"); - - uint32_t compatibility_minimum[3] = { 0, 0, 0 }; - if (config->has_section_key("configuration", "compatibility_minimum")) { - String compat_string = config->get_value("configuration", "compatibility_minimum"); - Vector parts = compat_string.split_ints("."); - for (int i = 0; i < parts.size(); i++) { - if (i >= 3) { - break; - } - if (parts[i] >= 0) { - compatibility_minimum[i] = parts[i]; - } - } - } else { - ERR_PRINT("GDExtension configuration file must contain a \"configuration/compatibility_minimum\" key: " + p_path); - return ERR_INVALID_DATA; - } - - if (compatibility_minimum[0] < 4 || (compatibility_minimum[0] == 4 && compatibility_minimum[1] == 0)) { - ERR_PRINT(vformat("GDExtension's compatibility_minimum (%d.%d.%d) must be at least 4.1.0: %s", compatibility_minimum[0], compatibility_minimum[1], compatibility_minimum[2], p_path)); - return ERR_INVALID_DATA; - } - - bool compatible = true; - // Check version lexicographically. - if (VERSION_MAJOR != compatibility_minimum[0]) { - compatible = VERSION_MAJOR > compatibility_minimum[0]; - } else if (VERSION_MINOR != compatibility_minimum[1]) { - compatible = VERSION_MINOR > compatibility_minimum[1]; - } else { - compatible = VERSION_PATCH >= compatibility_minimum[2]; - } - if (!compatible) { - ERR_PRINT(vformat("GDExtension only compatible with Godot version %d.%d.%d or later: %s", compatibility_minimum[0], compatibility_minimum[1], compatibility_minimum[2], p_path)); - return ERR_INVALID_DATA; - } - - // Optionally check maximum compatibility. - if (config->has_section_key("configuration", "compatibility_maximum")) { - uint32_t compatibility_maximum[3] = { 0, 0, 0 }; - String compat_string = config->get_value("configuration", "compatibility_maximum"); - Vector parts = compat_string.split_ints("."); - for (int i = 0; i < 3; i++) { - if (i < parts.size() && parts[i] >= 0) { - compatibility_maximum[i] = parts[i]; - } else { - // If a version part is missing, set the maximum to an arbitrary high value. - compatibility_maximum[i] = 9999; - } - } - - compatible = true; - if (VERSION_MAJOR != compatibility_maximum[0]) { - compatible = VERSION_MAJOR < compatibility_maximum[0]; - } else if (VERSION_MINOR != compatibility_maximum[1]) { - compatible = VERSION_MINOR < compatibility_maximum[1]; - } -#if VERSION_PATCH - // #if check to avoid -Wtype-limits warning when 0. - else { - compatible = VERSION_PATCH <= compatibility_maximum[2]; - } -#endif - - if (!compatible) { - ERR_PRINT(vformat("GDExtension only compatible with Godot version %s or earlier: %s", compat_string, p_path)); - return ERR_INVALID_DATA; - } - } - - String library_path = GDExtension::find_extension_library(p_path, config, [](const String &p_feature) { return OS::get_singleton()->has_feature(p_feature); }); - - if (library_path.is_empty()) { - const String os_arch = OS::get_singleton()->get_name().to_lower() + "." + Engine::get_singleton()->get_architecture_name(); - ERR_PRINT(vformat("No GDExtension library found for current OS and architecture (%s) in configuration file: %s", os_arch, p_path)); - return ERR_FILE_NOT_FOUND; - } - - bool is_static_library = library_path.ends_with(".a") || library_path.ends_with(".xcframework"); - - if (!library_path.is_resource_file() && !library_path.is_absolute_path()) { - library_path = p_path.get_base_dir().path_join(library_path); - } - - if (p_extension.is_null()) { - p_extension.instantiate(); - } - -#ifdef TOOLS_ENABLED - p_extension->set_reloadable(config->get_value("configuration", "reloadable", false) && Engine::get_singleton()->is_extension_reloading_enabled()); - - p_extension->update_last_modified_time( - FileAccess::get_modified_time(p_path), - FileAccess::get_modified_time(library_path)); -#endif - - Vector library_dependencies = GDExtension::find_extension_dependencies(p_path, config, [](const String &p_feature) { return OS::get_singleton()->has_feature(p_feature); }); - err = p_extension->open_library(is_static_library ? String() : library_path, entry_symbol, &library_dependencies); - if (err != OK) { - // Unreference the extension so that this loading can be considered a failure. - p_extension.unref(); - - // Errors already logged in open_library() - return err; - } - - // Handle icons if any are specified. - if (config->has_section("icons")) { - List keys; - config->get_section_keys("icons", &keys); - for (const String &key : keys) { - String icon_path = config->get_value("icons", key); - if (icon_path.is_relative_path()) { - icon_path = p_path.get_base_dir().path_join(icon_path); - } + GDExtensionManager *extension_manager = GDExtensionManager::get_singleton(); - p_extension->class_icon_paths[key] = icon_path; - } + GDExtensionManager::LoadStatus status = extension_manager->load_extension(p_path); + if (status != GDExtensionManager::LOAD_STATUS_OK && status != GDExtensionManager::LOAD_STATUS_ALREADY_LOADED) { + // Errors already logged in load_extension(). + return FAILED; } + p_extension = extension_manager->get_extension(p_path); return OK; } @@ -1094,16 +839,7 @@ String GDExtensionResourceLoader::get_resource_type(const String &p_path) const #ifdef TOOLS_ENABLED bool GDExtension::has_library_changed() const { - // Check only that the last modified time is different (rather than checking - // that it's newer) since some OS's (namely Windows) will preserve the modified - // time by default when copying files. - if (FileAccess::get_modified_time(get_path()) != resource_last_modified_time) { - return true; - } - if (FileAccess::get_modified_time(library_path) != library_last_modified_time) { - return true; - } - return false; + return loader->has_library_changed(); } void GDExtension::prepare_reload() { diff --git a/core/extension/gdextension.h b/core/extension/gdextension.h index 9393e7399ba2..706bc7e18961 100644 --- a/core/extension/gdextension.h +++ b/core/extension/gdextension.h @@ -31,13 +31,11 @@ #ifndef GDEXTENSION_H #define GDEXTENSION_H -#include - #include "core/extension/gdextension_interface.h" +#include "core/extension/gdextension_loader.h" #include "core/io/config_file.h" #include "core/io/resource_loader.h" #include "core/object/ref_counted.h" -#include "core/os/shared_object.h" class GDExtensionMethodBind; @@ -46,8 +44,8 @@ class GDExtension : public Resource { friend class GDExtensionManager; - void *library = nullptr; // pointer if valid, - String library_path; + Ref loader; + bool reloadable = false; struct Extension { @@ -72,15 +70,18 @@ class GDExtension : public Resource { #ifndef DISABLE_DEPRECATED GDExtensionClassNotification notification_func = nullptr; GDExtensionClassFreePropertyList free_property_list_func = nullptr; + GDExtensionClassCreateInstance create_instance_func = nullptr; + GDExtensionClassGetRID get_rid_func = nullptr; #endif // DISABLE_DEPRECATED }; #ifndef DISABLE_DEPRECATED static void _register_extension_class(GDExtensionClassLibraryPtr p_library, GDExtensionConstStringNamePtr p_class_name, GDExtensionConstStringNamePtr p_parent_class_name, const GDExtensionClassCreationInfo *p_extension_funcs); static void _register_extension_class2(GDExtensionClassLibraryPtr p_library, GDExtensionConstStringNamePtr p_class_name, GDExtensionConstStringNamePtr p_parent_class_name, const GDExtensionClassCreationInfo2 *p_extension_funcs); -#endif // DISABLE_DEPRECATED static void _register_extension_class3(GDExtensionClassLibraryPtr p_library, GDExtensionConstStringNamePtr p_class_name, GDExtensionConstStringNamePtr p_parent_class_name, const GDExtensionClassCreationInfo3 *p_extension_funcs); - static void _register_extension_class_internal(GDExtensionClassLibraryPtr p_library, GDExtensionConstStringNamePtr p_class_name, GDExtensionConstStringNamePtr p_parent_class_name, const GDExtensionClassCreationInfo3 *p_extension_funcs, const ClassCreationDeprecatedInfo *p_deprecated_funcs = nullptr); +#endif // DISABLE_DEPRECATED + static void _register_extension_class4(GDExtensionClassLibraryPtr p_library, GDExtensionConstStringNamePtr p_class_name, GDExtensionConstStringNamePtr p_parent_class_name, const GDExtensionClassCreationInfo4 *p_extension_funcs); + static void _register_extension_class_internal(GDExtensionClassLibraryPtr p_library, GDExtensionConstStringNamePtr p_class_name, GDExtensionConstStringNamePtr p_parent_class_name, const GDExtensionClassCreationInfo4 *p_extension_funcs, const ClassCreationDeprecatedInfo *p_deprecated_funcs = nullptr); static void _register_extension_class_method(GDExtensionClassLibraryPtr p_library, GDExtensionConstStringNamePtr p_class_name, const GDExtensionClassMethodInfo *p_method_info); static void _register_extension_class_virtual_method(GDExtensionClassLibraryPtr p_library, GDExtensionConstStringNamePtr p_class_name, const GDExtensionClassVirtualMethodInfo *p_method_info); static void _register_extension_class_integer_constant(GDExtensionClassLibraryPtr p_library, GDExtensionConstStringNamePtr p_class_name, GDExtensionConstStringNamePtr p_enum_name, GDExtensionConstStringNamePtr p_constant_name, GDExtensionInt p_constant_value, GDExtensionBool p_is_bitfield); @@ -96,8 +97,6 @@ class GDExtension : public Resource { int32_t level_initialized = -1; #ifdef TOOLS_ENABLED - uint64_t resource_last_modified_time = 0; - uint64_t library_last_modified_time = 0; bool is_reloading = false; Vector invalid_methods; Vector instance_bindings; @@ -124,11 +123,12 @@ class GDExtension : public Resource { virtual bool editor_can_reload_from_file() override { return false; } // Reloading is handled in a special way. static String get_extension_list_config_file(); - static String find_extension_library(const String &p_path, Ref p_config, std::function p_has_feature, PackedStringArray *r_tags = nullptr); - static Vector find_extension_dependencies(const String &p_path, Ref p_config, std::function p_has_feature); - Error open_library(const String &p_path, const String &p_entry_symbol, Vector *p_dependencies = nullptr); + const Ref get_loader() const { return loader; } + + Error open_library(const String &p_path, const Ref &p_loader); void close_library(); + bool is_library_open() const; enum InitializationLevel { INITIALIZATION_LEVEL_CORE = GDEXTENSION_INITIALIZATION_CORE, @@ -146,17 +146,11 @@ class GDExtension : public Resource { #endif public: - bool is_library_open() const; - #ifdef TOOLS_ENABLED bool is_reloadable() const { return reloadable; } void set_reloadable(bool p_reloadable) { reloadable = p_reloadable; } bool has_library_changed() const; - void update_last_modified_time(uint64_t p_resource_last_modified_time, uint64_t p_library_last_modified_time) { - resource_last_modified_time = p_resource_last_modified_time; - library_last_modified_time = p_library_last_modified_time; - } void track_instance_binding(Object *p_object); void untrack_instance_binding(Object *p_object); diff --git a/core/extension/gdextension_compat_hashes.cpp b/core/extension/gdextension_compat_hashes.cpp index ebbf795070f7..b07f5b185893 100644 --- a/core/extension/gdextension_compat_hashes.cpp +++ b/core/extension/gdextension_compat_hashes.cpp @@ -103,6 +103,14 @@ void GDExtensionCompatHashes::initialize() { mappings.insert("AcceptDialog", { { "add_button", 4158837846, 3328440682 }, }); + mappings.insert("AnimatedSprite2D", { + { "play", 2372066587, 3269405555 }, + { "play_backwards", 1421762485, 3323268493 }, + }); + mappings.insert("AnimatedSprite3D", { + { "play", 2372066587, 3269405555 }, + { "play_backwards", 1421762485, 3323268493 }, + }); mappings.insert("Animation", { { "add_track", 2393815928, 3843682357 }, { "track_insert_key", 1985425300, 808952278 }, @@ -146,6 +154,12 @@ void GDExtensionCompatHashes::initialize() { { "travel", 3683006648, 3823612587 }, { "start", 3683006648, 3823612587 }, }); + mappings.insert("AnimationPlayer", { + { "play", 3697947785, 3118260607 }, + { "play", 2221377757, 3118260607 }, + { "play_backwards", 3890664824, 2787282401 }, + { "play_with_capture", 3180464118, 1572969103 }, + }); mappings.insert("ArrayMesh", { { "add_surface_from_arrays", 172284304, 1796411378 }, }); @@ -247,13 +261,20 @@ void GDExtensionCompatHashes::initialize() { }); mappings.insert("DisplayServer", { { "global_menu_add_submenu_item", 3806306913, 2828985934 }, - { "global_menu_add_item", 3415468211, 3401266716 }, - { "global_menu_add_check_item", 3415468211, 3401266716 }, - { "global_menu_add_icon_item", 1700867534, 4245856523 }, - { "global_menu_add_icon_check_item", 1700867534, 4245856523 }, - { "global_menu_add_radio_check_item", 3415468211, 3401266716 }, - { "global_menu_add_icon_radio_check_item", 1700867534, 4245856523 }, - { "global_menu_add_multistate_item", 635750054, 3431222859 }, + { "global_menu_add_item", 3415468211, 3616842746 }, + { "global_menu_add_item", 3401266716, 3616842746 }, + { "global_menu_add_check_item", 3415468211, 3616842746 }, + { "global_menu_add_check_item", 3401266716, 3616842746 }, + { "global_menu_add_icon_item", 1700867534, 3867083847 }, + { "global_menu_add_icon_item", 4245856523, 3867083847 }, + { "global_menu_add_icon_check_item", 1700867534, 3867083847 }, + { "global_menu_add_icon_check_item", 4245856523, 3867083847 }, + { "global_menu_add_radio_check_item", 3415468211, 3616842746 }, + { "global_menu_add_radio_check_item", 3401266716, 3616842746 }, + { "global_menu_add_icon_radio_check_item", 1700867534, 3867083847 }, + { "global_menu_add_icon_radio_check_item", 4245856523, 3867083847 }, + { "global_menu_add_multistate_item", 635750054, 3297554655 }, + { "global_menu_add_multistate_item", 3431222859, 3297554655 }, { "global_menu_add_separator", 1041533178, 3214812433 }, { "tts_speak", 3741216677, 903992738 }, { "is_touchscreen_available", 4162880507, 3323674545 }, @@ -286,6 +307,12 @@ void GDExtensionCompatHashes::initialize() { { "virtual_keyboard_show", 860410478, 3042891259 }, #endif }); + mappings.insert("EditorExportPlatform", { + { "export_project_files", 425454869, 1063735070 }, + }); + mappings.insert("EditorProperty", { + { "emit_changed", 3069422438, 1822500399 }, + }); mappings.insert("ENetConnection", { { "create_host_bound", 866250949, 1515002313 }, { "connect_to_host", 385984708, 2171300490 }, @@ -453,18 +480,35 @@ void GDExtensionCompatHashes::initialize() { mappings.insert("MultiplayerAPI", { { "rpc", 1833408346, 2077486355 }, }); + mappings.insert("NativeMenu", { + { "add_item", 2553375659, 980552939 }, + { "add_check_item", 2553375659, 980552939 }, + { "add_icon_item", 2987595282, 1372188274 }, + { "add_icon_check_item", 2987595282, 1372188274 }, + { "add_radio_check_item", 2553375659, 980552939 }, + { "add_icon_radio_check_item", 2987595282, 1372188274 }, + { "add_multistate_item", 1558592568, 2674635658 }, + }); mappings.insert("NavigationMeshGenerator", { - { "parse_source_geometry_data", 3703028813, 685862123 }, - { "bake_from_source_geometry_data", 3669016597, 2469318639 }, + { "parse_source_geometry_data", 3703028813, 3172802542 }, + { "parse_source_geometry_data", 685862123, 3172802542 }, + { "bake_from_source_geometry_data", 3669016597, 1286748856 }, + { "bake_from_source_geometry_data", 2469318639, 1286748856 }, }); mappings.insert("NavigationServer2D", { { "map_get_path", 56240621, 3146466012 }, + { "parse_source_geometry_data", 1176164995, 1766905497 }, + { "bake_from_source_geometry_data", 2909414286, 2179660022 }, + { "bake_from_source_geometry_data_async", 2909414286, 2179660022 }, }); mappings.insert("NavigationServer3D", { { "map_get_path", 2121045993, 1187418690 }, - { "parse_source_geometry_data", 3703028813, 685862123 }, - { "bake_from_source_geometry_data", 3669016597, 2469318639 }, - { "bake_from_source_geometry_data_async", 3669016597, 2469318639 }, + { "parse_source_geometry_data", 3703028813, 3172802542 }, + { "parse_source_geometry_data", 685862123, 3172802542 }, + { "bake_from_source_geometry_data", 3669016597, 1286748856 }, + { "bake_from_source_geometry_data", 2469318639, 1286748856 }, + { "bake_from_source_geometry_data_async", 3669016597, 1286748856 }, + { "bake_from_source_geometry_data_async", 2469318639, 1286748856 }, }); mappings.insert("Node", { { "add_child", 3070154285, 3863233950 }, @@ -631,6 +675,11 @@ void GDExtensionCompatHashes::initialize() { mappings.insert("ProjectSettings", { { "load_resource_pack", 3001721055, 708980503 }, }); + mappings.insert("RDShaderFile", { + { "bake_from_source_geometry_data_async", 2469318639, 1286748856 }, + { "set_bytecode", 1558064255, 1526857008 }, + { "get_spirv", 3340165340, 2689310080 }, + }); mappings.insert("RegEx", { { "search", 4087180739, 3365977994 }, { "search_all", 3354100289, 849021363 }, diff --git a/core/extension/gdextension_interface.cpp b/core/extension/gdextension_interface.cpp index 85f83eecfddf..66b016116020 100644 --- a/core/extension/gdextension_interface.cpp +++ b/core/extension/gdextension_interface.cpp @@ -507,6 +507,14 @@ static GDExtensionBool gdextension_variant_has_key(GDExtensionConstVariantPtr p_ return ret; } +static GDObjectInstanceID gdextension_variant_get_object_instance_id(GDExtensionConstVariantPtr p_self) { + const Variant *self = (const Variant *)p_self; + if (likely(self->get_type() == Variant::OBJECT)) { + return self->operator ObjectID(); + } + return 0; +} + static void gdextension_variant_get_type_name(GDExtensionVariantType p_type, GDExtensionUninitializedVariantPtr r_ret) { String name = Variant::get_type_name((Variant::Type)p_type); memnew_placement(r_ret, String(name)); @@ -1199,6 +1207,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(p_self); + const StringName *key_class_name = reinterpret_cast(p_key_class_name); + const Variant *key_script = reinterpret_cast(p_key_script); + const StringName *value_class_name = reinterpret_cast(p_value_class_name); + const Variant *value_script = reinterpret_cast(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 +1316,7 @@ static void gdextension_object_call_script_method(GDExtensionObjectPtr p_object, const StringName method = *reinterpret_cast(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); @@ -1515,10 +1532,17 @@ static GDExtensionMethodBindPtr gdextension_classdb_get_method_bind(GDExtensionC return (GDExtensionMethodBindPtr)mb; } +#ifndef DISABLE_DEPRECATED static GDExtensionObjectPtr gdextension_classdb_construct_object(GDExtensionConstStringNamePtr p_classname) { const StringName classname = *reinterpret_cast(p_classname); return (GDExtensionObjectPtr)ClassDB::instantiate_no_placeholders(classname); } +#endif + +static GDExtensionObjectPtr gdextension_classdb_construct_object2(GDExtensionConstStringNamePtr p_classname) { + const StringName classname = *reinterpret_cast(p_classname); + return (GDExtensionObjectPtr)ClassDB::instantiate_without_postinitialization(classname); +} static void *gdextension_classdb_get_class_tag(GDExtensionConstStringNamePtr p_classname) { const StringName classname = *reinterpret_cast(p_classname); @@ -1594,6 +1618,7 @@ void gdextension_setup_interface() { REGISTER_INTERFACE_FUNC(variant_has_method); REGISTER_INTERFACE_FUNC(variant_has_member); REGISTER_INTERFACE_FUNC(variant_has_key); + REGISTER_INTERFACE_FUNC(variant_get_object_instance_id); REGISTER_INTERFACE_FUNC(variant_get_type_name); REGISTER_INTERFACE_FUNC(variant_can_convert); REGISTER_INTERFACE_FUNC(variant_can_convert_strict); @@ -1672,6 +1697,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); @@ -1701,7 +1727,10 @@ void gdextension_setup_interface() { #endif // DISABLE_DEPRECATED REGISTER_INTERFACE_FUNC(callable_custom_create2); REGISTER_INTERFACE_FUNC(callable_custom_get_userdata); +#ifndef DISABLE_DEPRECATED REGISTER_INTERFACE_FUNC(classdb_construct_object); +#endif // DISABLE_DEPRECATED + REGISTER_INTERFACE_FUNC(classdb_construct_object2); REGISTER_INTERFACE_FUNC(classdb_get_method_bind); REGISTER_INTERFACE_FUNC(classdb_get_class_tag); REGISTER_INTERFACE_FUNC(editor_add_plugin); diff --git a/core/extension/gdextension_interface.h b/core/extension/gdextension_interface.h index fce377f96738..374dbfd07115 100644 --- a/core/extension/gdextension_interface.h +++ b/core/extension/gdextension_interface.h @@ -268,6 +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, 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); @@ -292,7 +293,7 @@ typedef struct { GDExtensionClassGetVirtual get_virtual_func; // Queries a virtual function by name and returns a callback to invoke the requested virtual function. GDExtensionClassGetRID get_rid_func; void *class_userdata; // Per-class user data, later accessible in instance bindings. -} GDExtensionClassCreationInfo; // Deprecated. Use GDExtensionClassCreationInfo3 instead. +} GDExtensionClassCreationInfo; // Deprecated. Use GDExtensionClassCreationInfo4 instead. typedef struct { GDExtensionBool is_virtual; @@ -325,7 +326,7 @@ typedef struct { GDExtensionClassCallVirtualWithData call_virtual_with_data_func; GDExtensionClassGetRID get_rid_func; void *class_userdata; // Per-class user data, later accessible in instance bindings. -} GDExtensionClassCreationInfo2; // Deprecated. Use GDExtensionClassCreationInfo3 instead. +} GDExtensionClassCreationInfo2; // Deprecated. Use GDExtensionClassCreationInfo4 instead. typedef struct { GDExtensionBool is_virtual; @@ -359,7 +360,40 @@ typedef struct { GDExtensionClassCallVirtualWithData call_virtual_with_data_func; GDExtensionClassGetRID get_rid_func; void *class_userdata; // Per-class user data, later accessible in instance bindings. -} GDExtensionClassCreationInfo3; +} GDExtensionClassCreationInfo3; // Deprecated. Use GDExtensionClassCreationInfo4 instead. + +typedef struct { + GDExtensionBool is_virtual; + GDExtensionBool is_abstract; + GDExtensionBool is_exposed; + GDExtensionBool is_runtime; + GDExtensionClassSet set_func; + GDExtensionClassGet get_func; + GDExtensionClassGetPropertyList get_property_list_func; + GDExtensionClassFreePropertyList2 free_property_list_func; + GDExtensionClassPropertyCanRevert property_can_revert_func; + GDExtensionClassPropertyGetRevert property_get_revert_func; + GDExtensionClassValidateProperty validate_property_func; + GDExtensionClassNotification2 notification_func; + GDExtensionClassToString to_string_func; + GDExtensionClassReference reference_func; + GDExtensionClassUnreference unreference_func; + GDExtensionClassCreateInstance2 create_instance_func; // (Default) constructor; mandatory. If the class is not instantiable, consider making it virtual or abstract. + GDExtensionClassFreeInstance free_instance_func; // Destructor; mandatory. + GDExtensionClassRecreateInstance recreate_instance_func; + // Queries a virtual function by name and returns a callback to invoke the requested virtual function. + GDExtensionClassGetVirtual get_virtual_func; + // Paired with `call_virtual_with_data_func`, this is an alternative to `get_virtual_func` for extensions that + // need or benefit from extra data when calling virtual functions. + // Returns user data that will be passed to `call_virtual_with_data_func`. + // Returning `NULL` from this function signals to Godot that the virtual function is not overridden. + // Data returned from this function should be managed by the extension and must be valid until the extension is deinitialized. + // You should supply either `get_virtual_func`, or `get_virtual_call_data_func` with `call_virtual_with_data_func`. + GDExtensionClassGetVirtualCallData get_virtual_call_data_func; + // Used to call virtual functions when `get_virtual_call_data_func` is not null. + GDExtensionClassCallVirtualWithData call_virtual_with_data_func; + void *class_userdata; // Per-class user data, later accessible in instance bindings. +} GDExtensionClassCreationInfo4; typedef void *GDExtensionClassLibraryPtr; @@ -386,7 +420,9 @@ typedef enum { GDEXTENSION_METHOD_ARGUMENT_METADATA_INT_IS_UINT32, GDEXTENSION_METHOD_ARGUMENT_METADATA_INT_IS_UINT64, GDEXTENSION_METHOD_ARGUMENT_METADATA_REAL_IS_FLOAT, - GDEXTENSION_METHOD_ARGUMENT_METADATA_REAL_IS_DOUBLE + GDEXTENSION_METHOD_ARGUMENT_METADATA_REAL_IS_DOUBLE, + GDEXTENSION_METHOD_ARGUMENT_METADATA_INT_IS_CHAR16, + GDEXTENSION_METHOD_ARGUMENT_METADATA_INT_IS_CHAR32, } GDExtensionClassMethodArgumentMetadata; typedef void (*GDExtensionClassMethodCall)(void *method_userdata, GDExtensionClassInstancePtr p_instance, const GDExtensionConstVariantPtr *p_args, GDExtensionInt p_argument_count, GDExtensionVariantPtr r_return, GDExtensionCallError *r_error); @@ -1271,6 +1307,21 @@ typedef GDExtensionBool (*GDExtensionInterfaceVariantHasMember)(GDExtensionVaria */ typedef GDExtensionBool (*GDExtensionInterfaceVariantHasKey)(GDExtensionConstVariantPtr p_self, GDExtensionConstVariantPtr p_key, GDExtensionBool *r_valid); +/** + * @name variant_get_object_instance_id + * @since 4.4 + * + * Gets the object instance ID from a variant of type GDEXTENSION_VARIANT_TYPE_OBJECT. + * + * If the variant isn't of type GDEXTENSION_VARIANT_TYPE_OBJECT, then zero will be returned. + * The instance ID will be returned even if the object is no longer valid - use `object_get_instance_by_id()` to check if the object is still valid. + * + * @param p_self A pointer to the Variant. + * + * @return The instance ID for the contained object. + */ +typedef GDObjectInstanceID (*GDExtensionInterfaceVariantGetObjectInstanceId)(GDExtensionConstVariantPtr p_self); + /** * @name variant_get_type_name * @since 4.1 @@ -2337,6 +2388,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 */ /** @@ -2680,6 +2747,7 @@ typedef void *(*GDExtensionInterfaceCallableCustomGetUserData)(GDExtensionConstT /** * @name classdb_construct_object * @since 4.1 + * @deprecated in Godot 4.4. Use `classdb_construct_object2` instead. * * Constructs an Object of the requested class. * @@ -2691,6 +2759,22 @@ typedef void *(*GDExtensionInterfaceCallableCustomGetUserData)(GDExtensionConstT */ typedef GDExtensionObjectPtr (*GDExtensionInterfaceClassdbConstructObject)(GDExtensionConstStringNamePtr p_classname); +/** + * @name classdb_construct_object2 + * @since 4.4 + * + * Constructs an Object of the requested class. + * + * The passed class must be a built-in godot class, or an already-registered extension class. In both cases, object_set_instance() should be called to fully initialize the object. + * + * "NOTIFICATION_POSTINITIALIZE" must be sent after construction. + * + * @param p_classname A pointer to a StringName with the class name. + * + * @return A pointer to the newly created Object. + */ +typedef GDExtensionObjectPtr (*GDExtensionInterfaceClassdbConstructObject2)(GDExtensionConstStringNamePtr p_classname); + /** * @name classdb_get_method_bind * @since 4.1 @@ -2722,7 +2806,7 @@ typedef void *(*GDExtensionInterfaceClassdbGetClassTag)(GDExtensionConstStringNa /** * @name classdb_register_extension_class * @since 4.1 - * @deprecated in Godot 4.2. Use `classdb_register_extension_class3` instead. + * @deprecated in Godot 4.2. Use `classdb_register_extension_class4` instead. * * Registers an extension class in the ClassDB. * @@ -2738,7 +2822,7 @@ typedef void (*GDExtensionInterfaceClassdbRegisterExtensionClass)(GDExtensionCla /** * @name classdb_register_extension_class2 * @since 4.2 - * @deprecated in Godot 4.3. Use `classdb_register_extension_class3` instead. + * @deprecated in Godot 4.3. Use `classdb_register_extension_class4` instead. * * Registers an extension class in the ClassDB. * @@ -2754,6 +2838,7 @@ typedef void (*GDExtensionInterfaceClassdbRegisterExtensionClass2)(GDExtensionCl /** * @name classdb_register_extension_class3 * @since 4.3 + * @deprecated in Godot 4.4. Use `classdb_register_extension_class4` instead. * * Registers an extension class in the ClassDB. * @@ -2766,6 +2851,21 @@ typedef void (*GDExtensionInterfaceClassdbRegisterExtensionClass2)(GDExtensionCl */ typedef void (*GDExtensionInterfaceClassdbRegisterExtensionClass3)(GDExtensionClassLibraryPtr p_library, GDExtensionConstStringNamePtr p_class_name, GDExtensionConstStringNamePtr p_parent_class_name, const GDExtensionClassCreationInfo3 *p_extension_funcs); +/** + * @name classdb_register_extension_class4 + * @since 4.4 + * + * Registers an extension class in the ClassDB. + * + * Provided struct can be safely freed once the function returns. + * + * @param p_library A pointer the library received by the GDExtension's entry point function. + * @param p_class_name A pointer to a StringName with the class name. + * @param p_parent_class_name A pointer to a StringName with the parent class name. + * @param p_extension_funcs A pointer to a GDExtensionClassCreationInfo2 struct. + */ +typedef void (*GDExtensionInterfaceClassdbRegisterExtensionClass4)(GDExtensionClassLibraryPtr p_library, GDExtensionConstStringNamePtr p_class_name, GDExtensionConstStringNamePtr p_parent_class_name, const GDExtensionClassCreationInfo4 *p_extension_funcs); + /** * @name classdb_register_extension_class_method * @since 4.1 diff --git a/core/extension/gdextension_library_loader.cpp b/core/extension/gdextension_library_loader.cpp new file mode 100644 index 000000000000..d5f2eb668f55 --- /dev/null +++ b/core/extension/gdextension_library_loader.cpp @@ -0,0 +1,394 @@ +/**************************************************************************/ +/* gdextension_library_loader.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 "gdextension_library_loader.h" + +#include "core/config/project_settings.h" +#include "core/io/dir_access.h" +#include "core/version.h" +#include "gdextension.h" + +Vector GDExtensionLibraryLoader::find_extension_dependencies(const String &p_path, Ref p_config, std::function p_has_feature) { + Vector dependencies_shared_objects; + if (p_config->has_section("dependencies")) { + List config_dependencies; + p_config->get_section_keys("dependencies", &config_dependencies); + + for (const String &dependency : config_dependencies) { + Vector dependency_tags = dependency.split("."); + bool all_tags_met = true; + for (int i = 0; i < dependency_tags.size(); i++) { + String tag = dependency_tags[i].strip_edges(); + if (!p_has_feature(tag)) { + all_tags_met = false; + break; + } + } + + if (all_tags_met) { + Dictionary dependency_value = p_config->get_value("dependencies", dependency); + for (const Variant *key = dependency_value.next(nullptr); key; key = dependency_value.next(key)) { + String dependency_path = *key; + String target_path = dependency_value[*key]; + if (dependency_path.is_relative_path()) { + dependency_path = p_path.get_base_dir().path_join(dependency_path); + } + dependencies_shared_objects.push_back(SharedObject(dependency_path, dependency_tags, target_path)); + } + break; + } + } + } + + return dependencies_shared_objects; +} + +String GDExtensionLibraryLoader::find_extension_library(const String &p_path, Ref p_config, std::function p_has_feature, PackedStringArray *r_tags) { + // First, check the explicit libraries. + if (p_config->has_section("libraries")) { + List libraries; + p_config->get_section_keys("libraries", &libraries); + + // Iterate the libraries, finding the best matching tags. + String best_library_path; + Vector best_library_tags; + for (const String &E : libraries) { + Vector tags = E.split("."); + bool all_tags_met = true; + for (int i = 0; i < tags.size(); i++) { + String tag = tags[i].strip_edges(); + if (!p_has_feature(tag)) { + all_tags_met = false; + break; + } + } + + if (all_tags_met && tags.size() > best_library_tags.size()) { + best_library_path = p_config->get_value("libraries", E); + best_library_tags = tags; + } + } + + if (!best_library_path.is_empty()) { + if (best_library_path.is_relative_path()) { + best_library_path = p_path.get_base_dir().path_join(best_library_path); + } + if (r_tags != nullptr) { + r_tags->append_array(best_library_tags); + } + return best_library_path; + } + } + + // Second, try to autodetect. + String autodetect_library_prefix; + if (p_config->has_section_key("configuration", "autodetect_library_prefix")) { + autodetect_library_prefix = p_config->get_value("configuration", "autodetect_library_prefix"); + } + if (!autodetect_library_prefix.is_empty()) { + String autodetect_path = autodetect_library_prefix; + if (autodetect_path.is_relative_path()) { + autodetect_path = p_path.get_base_dir().path_join(autodetect_path); + } + + // Find the folder and file parts of the prefix. + String folder; + String file_prefix; + if (DirAccess::dir_exists_absolute(autodetect_path)) { + folder = autodetect_path; + } else if (DirAccess::dir_exists_absolute(autodetect_path.get_base_dir())) { + folder = autodetect_path.get_base_dir(); + file_prefix = autodetect_path.get_file(); + } else { + ERR_FAIL_V_MSG(String(), vformat("Error in extension: %s. Could not find folder for automatic detection of libraries files. autodetect_library_prefix=\"%s\"", p_path, autodetect_library_prefix)); + } + + // Open the folder. + Ref dir = DirAccess::open(folder); + ERR_FAIL_COND_V_MSG(dir.is_null(), String(), vformat("Error in extension: %s. Could not open folder for automatic detection of libraries files. autodetect_library_prefix=\"%s\"", p_path, autodetect_library_prefix)); + + // Iterate the files and check the prefixes, finding the best matching file. + String best_file; + Vector best_file_tags; + dir->list_dir_begin(); + String file_name = dir->_get_next(); + while (file_name != "") { + if (!dir->current_is_dir() && file_name.begins_with(file_prefix)) { + // Check if the files matches all requested feature tags. + String tags_str = file_name.trim_prefix(file_prefix); + tags_str = tags_str.trim_suffix(tags_str.get_extension()); + + Vector tags = tags_str.split(".", false); + bool all_tags_met = true; + for (int i = 0; i < tags.size(); i++) { + String tag = tags[i].strip_edges(); + if (!p_has_feature(tag)) { + all_tags_met = false; + break; + } + } + + // If all tags are found in the feature list, and we found more tags than before, use this file. + if (all_tags_met && tags.size() > best_file_tags.size()) { + best_file_tags = tags; + best_file = file_name; + } + } + file_name = dir->_get_next(); + } + + if (!best_file.is_empty()) { + String library_path = folder.path_join(best_file); + if (r_tags != nullptr) { + r_tags->append_array(best_file_tags); + } + return library_path; + } + } + return String(); +} + +Error GDExtensionLibraryLoader::open_library(const String &p_path) { + Error err = parse_gdextension_file(p_path); + if (err != OK) { + return err; + } + + String abs_path = ProjectSettings::get_singleton()->globalize_path(library_path); + + Vector abs_dependencies_paths; + if (!library_dependencies.is_empty()) { + for (const SharedObject &dependency : library_dependencies) { + abs_dependencies_paths.push_back(ProjectSettings::get_singleton()->globalize_path(dependency.path)); + } + } + + OS::GDExtensionData data = { + true, // also_set_library_path + &library_path, // r_resolved_path + Engine::get_singleton()->is_editor_hint(), // generate_temp_files + &abs_dependencies_paths, // library_dependencies + }; + + err = OS::get_singleton()->open_dynamic_library(is_static_library ? String() : abs_path, library, &data); + if (err != OK) { + return err; + } + + return OK; +} + +Error GDExtensionLibraryLoader::initialize(GDExtensionInterfaceGetProcAddress p_get_proc_address, const Ref &p_extension, GDExtensionInitialization *r_initialization) { +#ifdef TOOLS_ENABLED + p_extension->set_reloadable(is_reloadable && Engine::get_singleton()->is_extension_reloading_enabled()); +#endif + + for (const KeyValue &icon : class_icon_paths) { + p_extension->class_icon_paths[icon.key] = icon.value; + } + + void *entry_funcptr = nullptr; + + Error err = OS::get_singleton()->get_dynamic_library_symbol_handle(library, entry_symbol, entry_funcptr, false); + + if (err != OK) { + ERR_PRINT("GDExtension entry point '" + entry_symbol + "' not found in library " + library_path); + return err; + } + + GDExtensionInitializationFunction initialization_function = (GDExtensionInitializationFunction)entry_funcptr; + + GDExtensionBool ret = initialization_function(p_get_proc_address, p_extension.ptr(), r_initialization); + + if (ret) { + return OK; + } else { + ERR_PRINT("GDExtension initialization function '" + entry_symbol + "' returned an error."); + return FAILED; + } +} + +void GDExtensionLibraryLoader::close_library() { + OS::get_singleton()->close_dynamic_library(library); + library = nullptr; +} + +bool GDExtensionLibraryLoader::is_library_open() const { + return library != nullptr; +} + +bool GDExtensionLibraryLoader::has_library_changed() const { +#ifdef TOOLS_ENABLED + // Check only that the last modified time is different (rather than checking + // that it's newer) since some OS's (namely Windows) will preserve the modified + // time by default when copying files. + if (FileAccess::get_modified_time(resource_path) != resource_last_modified_time) { + return true; + } + if (FileAccess::get_modified_time(library_path) != library_last_modified_time) { + return true; + } +#endif + return false; +} + +bool GDExtensionLibraryLoader::library_exists() const { + return FileAccess::exists(resource_path); +} + +Error GDExtensionLibraryLoader::parse_gdextension_file(const String &p_path) { + resource_path = p_path; + + Ref config; + config.instantiate(); + + Error err = config->load(p_path); + + if (err != OK) { + ERR_PRINT("Error loading GDExtension configuration file: " + p_path); + return err; + } + + if (!config->has_section_key("configuration", "entry_symbol")) { + ERR_PRINT("GDExtension configuration file must contain a \"configuration/entry_symbol\" key: " + p_path); + return ERR_INVALID_DATA; + } + + entry_symbol = config->get_value("configuration", "entry_symbol"); + + uint32_t compatibility_minimum[3] = { 0, 0, 0 }; + if (config->has_section_key("configuration", "compatibility_minimum")) { + String compat_string = config->get_value("configuration", "compatibility_minimum"); + Vector parts = compat_string.split_ints("."); + for (int i = 0; i < parts.size(); i++) { + if (i >= 3) { + break; + } + if (parts[i] >= 0) { + compatibility_minimum[i] = parts[i]; + } + } + } else { + ERR_PRINT("GDExtension configuration file must contain a \"configuration/compatibility_minimum\" key: " + p_path); + return ERR_INVALID_DATA; + } + + if (compatibility_minimum[0] < 4 || (compatibility_minimum[0] == 4 && compatibility_minimum[1] == 0)) { + ERR_PRINT(vformat("GDExtension's compatibility_minimum (%d.%d.%d) must be at least 4.1.0: %s", compatibility_minimum[0], compatibility_minimum[1], compatibility_minimum[2], p_path)); + return ERR_INVALID_DATA; + } + + bool compatible = true; + // Check version lexicographically. + if (VERSION_MAJOR != compatibility_minimum[0]) { + compatible = VERSION_MAJOR > compatibility_minimum[0]; + } else if (VERSION_MINOR != compatibility_minimum[1]) { + compatible = VERSION_MINOR > compatibility_minimum[1]; + } else { + compatible = VERSION_PATCH >= compatibility_minimum[2]; + } + if (!compatible) { + ERR_PRINT(vformat("GDExtension only compatible with Godot version %d.%d.%d or later: %s", compatibility_minimum[0], compatibility_minimum[1], compatibility_minimum[2], p_path)); + return ERR_INVALID_DATA; + } + + // Optionally check maximum compatibility. + if (config->has_section_key("configuration", "compatibility_maximum")) { + uint32_t compatibility_maximum[3] = { 0, 0, 0 }; + String compat_string = config->get_value("configuration", "compatibility_maximum"); + Vector parts = compat_string.split_ints("."); + for (int i = 0; i < 3; i++) { + if (i < parts.size() && parts[i] >= 0) { + compatibility_maximum[i] = parts[i]; + } else { + // If a version part is missing, set the maximum to an arbitrary high value. + compatibility_maximum[i] = 9999; + } + } + + compatible = true; + if (VERSION_MAJOR != compatibility_maximum[0]) { + compatible = VERSION_MAJOR < compatibility_maximum[0]; + } else if (VERSION_MINOR != compatibility_maximum[1]) { + compatible = VERSION_MINOR < compatibility_maximum[1]; + } +#if VERSION_PATCH + // #if check to avoid -Wtype-limits warning when 0. + else { + compatible = VERSION_PATCH <= compatibility_maximum[2]; + } +#endif + + if (!compatible) { + ERR_PRINT(vformat("GDExtension only compatible with Godot version %s or earlier: %s", compat_string, p_path)); + return ERR_INVALID_DATA; + } + } + + library_path = find_extension_library(p_path, config, [](const String &p_feature) { return OS::get_singleton()->has_feature(p_feature); }); + + if (library_path.is_empty()) { + const String os_arch = OS::get_singleton()->get_name().to_lower() + "." + Engine::get_singleton()->get_architecture_name(); + ERR_PRINT(vformat("No GDExtension library found for current OS and architecture (%s) in configuration file: %s", os_arch, p_path)); + return ERR_FILE_NOT_FOUND; + } + + is_static_library = library_path.ends_with(".a") || library_path.ends_with(".xcframework"); + + if (!library_path.is_resource_file() && !library_path.is_absolute_path()) { + library_path = p_path.get_base_dir().path_join(library_path); + } + +#ifdef TOOLS_ENABLED + is_reloadable = config->get_value("configuration", "reloadable", false); + + update_last_modified_time( + FileAccess::get_modified_time(resource_path), + FileAccess::get_modified_time(library_path)); +#endif + + library_dependencies = find_extension_dependencies(p_path, config, [](const String &p_feature) { return OS::get_singleton()->has_feature(p_feature); }); + + // Handle icons if any are specified. + if (config->has_section("icons")) { + List keys; + config->get_section_keys("icons", &keys); + for (const String &key : keys) { + String icon_path = config->get_value("icons", key); + if (icon_path.is_relative_path()) { + icon_path = p_path.get_base_dir().path_join(icon_path); + } + + class_icon_paths[key] = icon_path; + } + } + + return OK; +} diff --git a/core/extension/gdextension_library_loader.h b/core/extension/gdextension_library_loader.h new file mode 100644 index 000000000000..f781611b3058 --- /dev/null +++ b/core/extension/gdextension_library_loader.h @@ -0,0 +1,85 @@ +/**************************************************************************/ +/* gdextension_library_loader.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 GDEXTENSION_LIBRARY_LOADER_H +#define GDEXTENSION_LIBRARY_LOADER_H + +#include + +#include "core/extension/gdextension_loader.h" +#include "core/io/config_file.h" +#include "core/os/shared_object.h" + +class GDExtensionLibraryLoader : public GDExtensionLoader { + friend class GDExtensionManager; + friend class GDExtension; + +private: + String resource_path; + + void *library = nullptr; // pointer if valid. + String library_path; + String entry_symbol; + + bool is_static_library = false; + +#ifdef TOOLS_ENABLED + bool is_reloadable = false; +#endif + + Vector library_dependencies; + + HashMap class_icon_paths; + +#ifdef TOOLS_ENABLED + uint64_t resource_last_modified_time = 0; + uint64_t library_last_modified_time = 0; + + void update_last_modified_time(uint64_t p_resource_last_modified_time, uint64_t p_library_last_modified_time) { + resource_last_modified_time = p_resource_last_modified_time; + library_last_modified_time = p_library_last_modified_time; + } +#endif + +public: + static String find_extension_library(const String &p_path, Ref p_config, std::function p_has_feature, PackedStringArray *r_tags = nullptr); + static Vector find_extension_dependencies(const String &p_path, Ref p_config, std::function p_has_feature); + + virtual Error open_library(const String &p_path) override; + virtual Error initialize(GDExtensionInterfaceGetProcAddress p_get_proc_address, const Ref &p_extension, GDExtensionInitialization *r_initialization) override; + virtual void close_library() override; + virtual bool is_library_open() const override; + virtual bool has_library_changed() const override; + virtual bool library_exists() const override; + + Error parse_gdextension_file(const String &p_path); +}; + +#endif // GDEXTENSION_LIBRARY_LOADER_H diff --git a/core/extension/gdextension_loader.h b/core/extension/gdextension_loader.h new file mode 100644 index 000000000000..22895503291d --- /dev/null +++ b/core/extension/gdextension_loader.h @@ -0,0 +1,48 @@ +/**************************************************************************/ +/* gdextension_loader.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 GDEXTENSION_LOADER_H +#define GDEXTENSION_LOADER_H + +#include "core/object/ref_counted.h" + +class GDExtension; + +class GDExtensionLoader : public RefCounted { +public: + virtual Error open_library(const String &p_path) = 0; + virtual Error initialize(GDExtensionInterfaceGetProcAddress p_get_proc_address, const Ref &p_extension, GDExtensionInitialization *r_initialization) = 0; + virtual void close_library() = 0; + virtual bool is_library_open() const = 0; + virtual bool has_library_changed() const = 0; + virtual bool library_exists() const = 0; +}; + +#endif // GDEXTENSION_LOADER_H diff --git a/core/extension/gdextension_manager.cpp b/core/extension/gdextension_manager.cpp index 1ee9de077689..fff938858fd3 100644 --- a/core/extension/gdextension_manager.cpp +++ b/core/extension/gdextension_manager.cpp @@ -31,14 +31,19 @@ #include "gdextension_manager.h" #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 &p_extension) { +GDExtensionManager::LoadStatus GDExtensionManager::_load_extension_internal(const Ref &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++) { @@ -50,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 &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--) { @@ -69,19 +84,31 @@ GDExtensionManager::LoadStatus GDExtensionManager::_unload_extension_internal(co } GDExtensionManager::LoadStatus GDExtensionManager::load_extension(const String &p_path) { + Ref loader; + loader.instantiate(); + return GDExtensionManager::get_singleton()->load_extension_with_loader(p_path, loader); +} + +GDExtensionManager::LoadStatus GDExtensionManager::load_extension_with_loader(const String &p_path, const Ref &p_loader) { + DEV_ASSERT(p_loader.is_valid()); + if (gdextension_map.has(p_path)) { return LOAD_STATUS_ALREADY_LOADED; } - Ref extension = ResourceLoader::load(p_path); - if (extension.is_null()) { + + Ref extension; + extension.instantiate(); + Error err = extension->open_library(p_path, p_loader); + if (err != OK) { return LOAD_STATUS_FAILED; } - LoadStatus status = _load_extension_internal(extension); + LoadStatus status = _load_extension_internal(extension, true); if (status != LOAD_STATUS_OK) { return status; } + extension->set_path(p_path); gdextension_map[p_path] = extension; return LOAD_STATUS_OK; } @@ -117,12 +144,12 @@ GDExtensionManager::LoadStatus GDExtensionManager::reload_extension(const String extension->close_library(); } - Error err = GDExtensionResourceLoader::load_gdextension_resource(p_path, extension); + Error err = extension->open_library(p_path, extension->loader); if (err != OK) { return LOAD_STATUS_FAILED; } - status = _load_extension_internal(extension); + status = _load_extension_internal(extension, false); if (status != LOAD_STATUS_OK) { return status; } @@ -261,6 +288,72 @@ void GDExtensionManager::reload_extensions() { #endif } +bool GDExtensionManager::ensure_extensions_loaded(const HashSet &p_extensions) { + Vector extensions_added; + Vector extensions_removed; + + for (const String &E : p_extensions) { + if (!is_extension_loaded(E)) { + extensions_added.push_back(E); + } + } + + Vector 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. + const Ref extension = GDExtensionManager::get_singleton()->get_extension(loaded_extension); + if (!extension->get_loader()->library_exists()) { + 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 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 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; } @@ -281,6 +374,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 9386e356bbec..39a600474cda 100644 --- a/core/extension/gdextension_manager.h +++ b/core/extension/gdextension_manager.h @@ -54,7 +54,7 @@ class GDExtensionManager : public Object { }; private: - LoadStatus _load_extension_internal(const Ref &p_extension); + LoadStatus _load_extension_internal(const Ref &p_extension, bool p_first_load); LoadStatus _unload_extension_internal(const Ref &p_extension); #ifdef TOOLS_ENABLED @@ -63,6 +63,7 @@ class GDExtensionManager : public Object { public: LoadStatus load_extension(const String &p_path); + LoadStatus load_extension_with_loader(const String &p_path, const Ref &p_loader); LoadStatus reload_extension(const String &p_path); LoadStatus unload_extension(const String &p_path); bool is_extension_loaded(const String &p_path) const; @@ -84,6 +85,7 @@ class GDExtensionManager : public Object { void load_extensions(); void reload_extensions(); + bool ensure_extensions_loaded(const HashSet &p_extensions); GDExtensionManager(); ~GDExtensionManager(); diff --git a/core/extension/make_wrappers.py b/core/extension/make_wrappers.py index 54f4fd5579d2..665b6f0f91d5 100644 --- a/core/extension/make_wrappers.py +++ b/core/extension/make_wrappers.py @@ -55,10 +55,10 @@ def generate_mod_version(argcount, const=False, returns=False): proto_ex = """ #define EXBIND$VER($RETTYPE m_name$ARG) \\ -GDVIRTUAL$VER($RETTYPE_##m_name$ARG)\\ +GDVIRTUAL$VER_REQUIRED($RETTYPE_##m_name$ARG)\\ virtual $RETVAL m_name($FUNCARGS) $CONST override { \\ $RETPRE\\ - GDVIRTUAL_REQUIRED_CALL(_##m_name$CALLARGS$RETREF);\\ + GDVIRTUAL_CALL(_##m_name$CALLARGS$RETREF);\\ $RETPOST\\ } """ diff --git a/core/input/SCsub b/core/input/SCsub index d8e6f33156b2..521f7702e4e2 100644 --- a/core/input/SCsub +++ b/core/input/SCsub @@ -1,4 +1,5 @@ #!/usr/bin/env python +from misc.utility.scons_hints import * Import("env") diff --git a/core/input/godotcontrollerdb.txt b/core/input/godotcontrollerdb.txt index f5158bfabbfb..8e8ec4c7186a 100644 --- a/core/input/godotcontrollerdb.txt +++ b/core/input/godotcontrollerdb.txt @@ -8,7 +8,7 @@ __XINPUT_DEVICE__,XInput Gamepad,a:b12,b:b13,x:b14,y:b15,start:b4,guide:b10,back Default Android Gamepad,Default Controller,leftx:a0,lefty:a1,dpdown:h0.4,rightstick:b8,rightshoulder:b10,rightx:a2,start:b6,righty:a3,dpleft:h0.8,lefttrigger:a4,x:b2,dpup:h0.1,back:b4,leftstick:b7,leftshoulder:b9,y:b3,a:b0,dpright:h0.2,righttrigger:a5,b:b1,platform:Android, # Web -standard,Standard Gamepad Mapping,leftx:a0,lefty:a1,rightx:a2,righty:a3,lefttrigger:b6,righttrigger:b7,a:b0,b:b1,x:b2,y:b3,leftshoulder:b4,rightshoulder:b5,back:b8,start:b9,leftstick:b10,rightstick:b11,dpup:b12,dpdown:b13,dpleft:b14,dpright:b15,guide:b16,leftstick:b10,rightstick:b11,platform:Web, +standard,Standard Gamepad Mapping,leftx:a0,lefty:a1,rightx:a2,righty:a3,lefttrigger:+a4,righttrigger:+a5,a:b0,b:b1,x:b2,y:b3,leftshoulder:b4,rightshoulder:b5,back:b8,start:b9,leftstick:b10,rightstick:b11,dpup:b12,dpdown:b13,dpleft:b14,dpright:b15,guide:b16,leftstick:b10,rightstick:b11,platform:Web, Linux24c6581a,PowerA Xbox One Cabled,a:b0,b:b1,y:b3,x:b2,start:b7,guide:b8,back:b6,leftstick:b9,rightstick:b10,leftshoulder:b4,rightshoulder:b5,dpup:-a7,dpleft:-a6,dpdown:+a7,dpright:+a6,leftx:a0,lefty:a1,rightx:a3,righty:a4,lefttrigger:a2,righttrigger:a5,platform:Web, Linux0e6f0301,Logic 3 Controller (xbox compatible),a:b0,b:b1,y:b3,x:b2,start:b7,guide:b8,back:b6,leftstick:b9,rightstick:b10,leftshoulder:b4,rightshoulder:b5,dpup:-a7,dpleft:-a6,dpdown:+a7,dpright:+a6,leftx:a0,lefty:a1,rightx:a3,righty:a4,lefttrigger:a2,righttrigger:a5,platform:Web, Linux045e028e,Microsoft X-Box 360 pad,a:b0,b:b1,y:b3,x:b2,start:b7,guide:b8,back:b6,leftstick:b9,rightstick:b10,leftshoulder:b4,rightshoulder:b5,dpup:-a7,dpleft:-a6,dpdown:+a7,dpright:+a6,leftx:a0,lefty:a1,rightx:a3,righty:a4,lefttrigger:a2,righttrigger:a5,platform:Web, diff --git a/core/input/input.cpp b/core/input/input.cpp index 91378591b0a0..eba7ded267ba 100644 --- a/core/input/input.cpp +++ b/core/input/input.cpp @@ -513,21 +513,49 @@ void Input::joy_connection_changed(int p_idx, bool p_connected, const String &p_ Vector3 Input::get_gravity() const { _THREAD_SAFE_METHOD_ + +#ifdef DEBUG_ENABLED + if (!gravity_enabled) { + WARN_PRINT_ONCE("`input_devices/sensors/enable_gravity` is not enabled in project settings."); + } +#endif + return gravity; } Vector3 Input::get_accelerometer() const { _THREAD_SAFE_METHOD_ + +#ifdef DEBUG_ENABLED + if (!accelerometer_enabled) { + WARN_PRINT_ONCE("`input_devices/sensors/enable_accelerometer` is not enabled in project settings."); + } +#endif + return accelerometer; } Vector3 Input::get_magnetometer() const { _THREAD_SAFE_METHOD_ + +#ifdef DEBUG_ENABLED + if (!magnetometer_enabled) { + WARN_PRINT_ONCE("`input_devices/sensors/enable_magnetometer` is not enabled in project settings."); + } +#endif + return magnetometer; } Vector3 Input::get_gyroscope() const { _THREAD_SAFE_METHOD_ + +#ifdef DEBUG_ENABLED + if (!gyroscope_enabled) { + WARN_PRINT_ONCE("`input_devices/sensors/enable_gyroscope` is not enabled in project settings."); + } +#endif + return gyroscope; } @@ -1683,6 +1711,11 @@ Input::Input() { // Always use standard behavior in the editor. legacy_just_pressed_behavior = false; } + + accelerometer_enabled = GLOBAL_DEF_RST_BASIC("input_devices/sensors/enable_accelerometer", false); + gravity_enabled = GLOBAL_DEF_RST_BASIC("input_devices/sensors/enable_gravity", false); + gyroscope_enabled = GLOBAL_DEF_RST_BASIC("input_devices/sensors/enable_gyroscope", false); + magnetometer_enabled = GLOBAL_DEF_RST_BASIC("input_devices/sensors/enable_magnetometer", false); } Input::~Input() { diff --git a/core/input/input.h b/core/input/input.h index 89e48f53d740..95dd623cc032 100644 --- a/core/input/input.h +++ b/core/input/input.h @@ -92,9 +92,13 @@ class Input : public Object { RBSet joy_buttons_pressed; RBMap _joy_axis; //RBMap custom_action_press; + bool gravity_enabled = false; Vector3 gravity; + bool accelerometer_enabled = false; Vector3 accelerometer; + bool magnetometer_enabled = false; Vector3 magnetometer; + bool gyroscope_enabled = false; Vector3 gyroscope; Vector2 mouse_pos; int64_t mouse_window = 0; diff --git a/core/input/input_builders.py b/core/input/input_builders.py index ae848f4e7cd5..3685e726b468 100644 --- a/core/input/input_builders.py +++ b/core/input/input_builders.py @@ -33,7 +33,7 @@ def make_default_controller_mappings(target, source, env): guid = line_parts[0] if guid in platform_mappings[current_platform]: g.write( - "// WARNING - DATABASE {} OVERWROTE PRIOR MAPPING: {} {}\n".format( + "// WARNING: DATABASE {} OVERWROTE PRIOR MAPPING: {} {}\n".format( src_path, current_platform, platform_mappings[current_platform][guid] ) ) diff --git a/core/input/input_event.cpp b/core/input/input_event.cpp index de3efa7a3a4f..905526bbbd88 100644 --- a/core/input/input_event.cpp +++ b/core/input/input_event.cpp @@ -1088,7 +1088,7 @@ void InputEventMouseMotion::_bind_methods() { /////////////////////////////////// void InputEventJoypadMotion::set_axis(JoyAxis p_axis) { - ERR_FAIL_COND(p_axis < JoyAxis::LEFT_X || p_axis > JoyAxis::MAX); + ERR_FAIL_COND(p_axis < JoyAxis::INVALID || p_axis > JoyAxis::MAX); axis = p_axis; emit_changed(); diff --git a/core/object/object.compat.inc b/core/input/input_map.compat.inc similarity index 84% rename from core/object/object.compat.inc rename to core/input/input_map.compat.inc index bf1e99fc9b3c..da4bd962b6fb 100644 --- a/core/object/object.compat.inc +++ b/core/input/input_map.compat.inc @@ -1,5 +1,5 @@ /**************************************************************************/ -/* object.compat.inc */ +/* input_map.compat.inc */ /**************************************************************************/ /* This file is part of: */ /* GODOT ENGINE */ @@ -30,11 +30,12 @@ #ifndef DISABLE_DEPRECATED -#include "core/object/class_db.h" +void InputMap::_add_action_bind_compat_97281(const StringName &p_action, float p_deadzone) { + add_action(p_action, p_deadzone); +} -void Object::_bind_compatibility_methods() { - ClassDB::bind_compatibility_method(D_METHOD("tr", "message", "context"), &Object::tr, DEFVAL("")); - ClassDB::bind_compatibility_method(D_METHOD("tr_n", "message", "plural_message", "n", "context"), &Object::tr_n, DEFVAL("")); +void InputMap::_bind_compatibility_methods() { + ClassDB::bind_compatibility_method(D_METHOD("add_action", "action", "deadzone"), &InputMap::_add_action_bind_compat_97281, DEFVAL(0.5f)); } -#endif +#endif // DISABLE_DEPRECATED diff --git a/core/input/input_map.cpp b/core/input/input_map.cpp index ddeee9d765a2..27a50c79f670 100644 --- a/core/input/input_map.cpp +++ b/core/input/input_map.cpp @@ -29,6 +29,7 @@ /**************************************************************************/ #include "input_map.h" +#include "input_map.compat.inc" #include "core/config/project_settings.h" #include "core/input/input.h" @@ -43,7 +44,7 @@ int InputMap::ALL_DEVICES = -1; void InputMap::_bind_methods() { ClassDB::bind_method(D_METHOD("has_action", "action"), &InputMap::has_action); ClassDB::bind_method(D_METHOD("get_actions"), &InputMap::_get_actions); - ClassDB::bind_method(D_METHOD("add_action", "action", "deadzone"), &InputMap::add_action, DEFVAL(0.5f)); + ClassDB::bind_method(D_METHOD("add_action", "action", "deadzone"), &InputMap::add_action, DEFVAL(0.2f)); ClassDB::bind_method(D_METHOD("erase_action", "action"), &InputMap::erase_action); ClassDB::bind_method(D_METHOD("action_set_deadzone", "action", "deadzone"), &InputMap::action_set_deadzone); @@ -306,7 +307,7 @@ void InputMap::load_from_project_settings() { String name = pi.name.substr(pi.name.find("/") + 1, pi.name.length()); Dictionary action = GLOBAL_GET(pi.name); - float deadzone = action.has("deadzone") ? (float)action["deadzone"] : 0.5f; + float deadzone = action.has("deadzone") ? (float)action["deadzone"] : 0.2f; Array events = action["events"]; add_action(name, deadzone); @@ -400,6 +401,7 @@ static const _BuiltinActionDisplayName _builtin_action_display_names[] = { { "ui_filedialog_refresh", TTRC("Refresh") }, { "ui_filedialog_show_hidden", TTRC("Show Hidden") }, { "ui_swap_input_direction ", TTRC("Swap Input Direction") }, + { "ui_unicode_start", TTRC("Start Unicode Character Input") }, { "", ""} /* clang-format on */ }; @@ -754,6 +756,10 @@ const HashMap>> &InputMap::get_builtins() { inputs.push_back(InputEventKey::create_reference(Key::KP_ENTER)); default_builtin_cache.insert("ui_text_submit", inputs); + inputs = List>(); + inputs.push_back(InputEventKey::create_reference(Key::U | KeyModifierMask::CTRL | KeyModifierMask::SHIFT)); + default_builtin_cache.insert("ui_unicode_start", inputs); + // ///// UI Graph Shortcuts ///// inputs = List>(); diff --git a/core/input/input_map.h b/core/input/input_map.h index 3774a131e6fe..b29687d144f5 100644 --- a/core/input/input_map.h +++ b/core/input/input_map.h @@ -69,12 +69,17 @@ class InputMap : public Object { protected: static void _bind_methods(); +#ifndef DISABLE_DEPRECATED + void _add_action_bind_compat_97281(const StringName &p_action, float p_deadzone = 0.5); + static void _bind_compatibility_methods(); +#endif // DISABLE_DEPRECATED + public: static _FORCE_INLINE_ InputMap *get_singleton() { return singleton; } bool has_action(const StringName &p_action) const; List get_actions() const; - void add_action(const StringName &p_action, float p_deadzone = 0.5); + void add_action(const StringName &p_action, float p_deadzone = 0.2); void erase_action(const StringName &p_action); float action_get_deadzone(const StringName &p_action); diff --git a/core/io/SCsub b/core/io/SCsub index 19a65492252d..ab811758940f 100644 --- a/core/io/SCsub +++ b/core/io/SCsub @@ -1,4 +1,5 @@ #!/usr/bin/env python +from misc.utility.scons_hints import * Import("env") diff --git a/core/io/dtls_server.cpp b/core/io/dtls_server.cpp index 07d62d3a8d47..7638328dc33e 100644 --- a/core/io/dtls_server.cpp +++ b/core/io/dtls_server.cpp @@ -33,12 +33,12 @@ #include "core/config/project_settings.h" #include "core/io/file_access.h" -DTLSServer *(*DTLSServer::_create)() = nullptr; +DTLSServer *(*DTLSServer::_create)(bool p_notify_postinitialize) = nullptr; bool DTLSServer::available = false; -DTLSServer *DTLSServer::create() { +DTLSServer *DTLSServer::create(bool p_notify_postinitialize) { if (_create) { - return _create(); + return _create(p_notify_postinitialize); } return nullptr; } diff --git a/core/io/dtls_server.h b/core/io/dtls_server.h index f3fbde3c152a..5ffed1ecc381 100644 --- a/core/io/dtls_server.h +++ b/core/io/dtls_server.h @@ -38,14 +38,14 @@ class DTLSServer : public RefCounted { GDCLASS(DTLSServer, RefCounted); protected: - static DTLSServer *(*_create)(); + static DTLSServer *(*_create)(bool p_notify_postinitialize); static void _bind_methods(); static bool available; public: static bool is_available(); - static DTLSServer *create(); + static DTLSServer *create(bool p_notify_postinitialize = true); virtual Error setup(Ref p_options) = 0; virtual void stop() = 0; diff --git a/core/io/file_access.cpp b/core/io/file_access.cpp index 1cf388b33a79..d919243e6b7a 100644 --- a/core/io/file_access.cpp +++ b/core/io/file_access.cpp @@ -59,11 +59,9 @@ bool FileAccess::exists(const String &p_name) { return true; } - Ref f = open(p_name, READ); - if (f.is_null()) { - return false; - } - return true; + // Using file_exists because it's faster than trying to open the file. + Ref ret = create_for_path(p_name); + return ret->file_exists(p_name); } void FileAccess::_set_access_type(AccessType p_access) { @@ -225,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(&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(&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(&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 { @@ -467,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 FileAccess::get_buffer(int64_t p_length) const { Vector data; @@ -490,7 +462,7 @@ Vector 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); @@ -514,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(&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(&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(&p_dest), sizeof(uint64_t)); } void FileAccess::store_real(real_t p_real) { @@ -710,22 +668,11 @@ void FileAccess::store_csv_line(const Vector &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 &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 2ab84db4b63d..2f4d1a860435 100644 --- a/core/io/file_access.h +++ b/core/io/file_access.h @@ -137,7 +137,7 @@ class FileAccess : public RefCounted { 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 @@ class FileAccess : public RefCounted { 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 get_buffer(int64_t p_length) const; virtual String get_line() const; virtual String get_token() const; @@ -168,7 +168,7 @@ class FileAccess : public RefCounted { 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 @@ class FileAccess : public RefCounted { 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 &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 0f00bd292cdd..3602baf8c5cc 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 f706c82f8e6d..ea9837dd0394 100644 --- a/core/io/file_access_compressed.h +++ b/core/io/file_access_compressed.h @@ -83,14 +83,13 @@ class FileAccessCompressed : public FileAccess { 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 b689f5b6284e..13d1e0c8fc31 100644 --- a/core/io/file_access_encrypted.cpp +++ b/core/io/file_access_encrypted.cpp @@ -37,7 +37,7 @@ #include Error FileAccessEncrypted::open_and_parse(Ref p_base, const Vector &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 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 42afe49a5e7b..5f8c803d6022 100644 --- a/core/io/file_access_encrypted.h +++ b/core/io/file_access_encrypted.h @@ -73,14 +73,12 @@ class FileAccessEncrypted : public FileAccess { 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 9521a4f666d1..1541a5ed4a4d 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 e9fbc26d7515..39e1528d9782 100644 --- a/core/io/file_access_memory.h +++ b/core/io/file_access_memory.h @@ -55,15 +55,12 @@ class FileAccessMemory : public FileAccess { 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 02bf0a603935..1340382eaa44 100644 --- a/core/io/file_access_pack.cpp +++ b/core/io/file_access_pack.cpp @@ -102,6 +102,22 @@ void PackedData::add_pack_source(PackSource *p_source) { } } +uint8_t *PackedData::get_file_hash(const String &p_path) { + PathMD5 pmd5(p_path.md5_buffer()); + HashMap::Iterator E = files.find(pmd5); + if (!E || E->value.offset == 0) { + return nullptr; + } + + return E->value.md5; +} + +void PackedData::clear() { + files.clear(); + _free_packed_dirs(root); + root = memnew(PackedDir); +} + PackedData *PackedData::singleton = nullptr; PackedData::PackedData() { @@ -313,17 +329,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 +371,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 594ac8f089b3..57b7a5f87f5d 100644 --- a/core/io/file_access_pack.h +++ b/core/io/file_access_pack.h @@ -111,6 +111,7 @@ class PackedData { public: void add_pack_source(PackSource *p_source); void add_path(const String &p_pkg_path, const String &p_path, uint64_t p_ofs, uint64_t p_size, const uint8_t *p_md5, PackSource *p_src, bool p_replace_files, bool p_encrypted = false); // for PackSource + uint8_t *get_file_hash(const String &p_path); void set_disabled(bool p_disabled) { disabled = p_disabled; } _FORCE_INLINE_ bool is_disabled() const { return disabled; } @@ -118,6 +119,8 @@ class PackedData { static PackedData *get_singleton() { return singleton; } Error add_pack(const String &p_path, bool p_replace_files, uint64_t p_offset); + void clear(); + _FORCE_INLINE_ Ref try_open_path(const String &p_path); _FORCE_INLINE_ bool has_path(const String &p_path); @@ -169,8 +172,6 @@ class FileAccessPack : public FileAccess { 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 +180,6 @@ class FileAccessPack : public FileAccess { 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 c0d1afc8e187..b33b7b35c31d 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 88b63e93e2dc..1e11e050dfb9 100644 --- a/core/io/file_access_zip.h +++ b/core/io/file_access_zip.h @@ -95,14 +95,13 @@ class FileAccessZip : public FileAccess { 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/http_client.cpp b/core/io/http_client.cpp index 833fd1adc381..fc91341bedda 100644 --- a/core/io/http_client.cpp +++ b/core/io/http_client.cpp @@ -42,9 +42,9 @@ const char *HTTPClient::_methods[METHOD_MAX] = { "PATCH" }; -HTTPClient *HTTPClient::create() { +HTTPClient *HTTPClient::create(bool p_notify_postinitialize) { if (_create) { - return _create(); + return _create(p_notify_postinitialize); } return nullptr; } diff --git a/core/io/http_client.h b/core/io/http_client.h index 9e018182e379..594529112213 100644 --- a/core/io/http_client.h +++ b/core/io/http_client.h @@ -158,12 +158,12 @@ class HTTPClient : public RefCounted { Error _request_raw(Method p_method, const String &p_url, const Vector &p_headers, const Vector &p_body); Error _request(Method p_method, const String &p_url, const Vector &p_headers, const String &p_body = String()); - static HTTPClient *(*_create)(); + static HTTPClient *(*_create)(bool p_notify_postinitialize); static void _bind_methods(); public: - static HTTPClient *create(); + static HTTPClient *create(bool p_notify_postinitialize = true); String query_string_from_dict(const Dictionary &p_dict); Error verify_headers(const Vector &p_headers); diff --git a/core/io/http_client_tcp.cpp b/core/io/http_client_tcp.cpp index 2f45238951be..70fcad543a1f 100644 --- a/core/io/http_client_tcp.cpp +++ b/core/io/http_client_tcp.cpp @@ -35,8 +35,8 @@ #include "core/io/stream_peer_tls.h" #include "core/version.h" -HTTPClient *HTTPClientTCP::_create_func() { - return memnew(HTTPClientTCP); +HTTPClient *HTTPClientTCP::_create_func(bool p_notify_postinitialize) { + return static_cast(ClassDB::creator(p_notify_postinitialize)); } Error HTTPClientTCP::connect_to_host(const String &p_host, int p_port, Ref p_options) { @@ -792,6 +792,6 @@ HTTPClientTCP::HTTPClientTCP() { request_buffer.instantiate(); } -HTTPClient *(*HTTPClient::_create)() = HTTPClientTCP::_create_func; +HTTPClient *(*HTTPClient::_create)(bool p_notify_postinitialize) = HTTPClientTCP::_create_func; #endif // WEB_ENABLED diff --git a/core/io/http_client_tcp.h b/core/io/http_client_tcp.h index 6060c975bcb8..dd6cc6b84f04 100644 --- a/core/io/http_client_tcp.h +++ b/core/io/http_client_tcp.h @@ -76,7 +76,7 @@ class HTTPClientTCP : public HTTPClient { Error _get_http_data(uint8_t *p_buffer, int p_bytes, int &r_received); public: - static HTTPClient *_create_func(); + static HTTPClient *_create_func(bool p_notify_postinitialize); Error request(Method p_method, const String &p_url, const Vector &p_headers, const uint8_t *p_body, int p_body_size) override; diff --git a/core/io/image.cpp b/core/io/image.cpp index d0598e4dc6c7..aa391b77dd43 100644 --- a/core/io/image.cpp +++ b/core/io/image.cpp @@ -30,6 +30,7 @@ #include "image.h" +#include "core/config/project_settings.h" #include "core/error/error_list.h" #include "core/error/error_macros.h" #include "core/io/image_loader.h" @@ -501,6 +502,38 @@ static void _convert(int p_width, int p_height, const uint8_t *p_src, uint8_t *p } } +template +static void _convert_fast(int p_width, int p_height, const T *p_src, T *p_dst) { + uint32_t dst_count = 0; + uint32_t src_count = 0; + + const int resolution = p_width * p_height; + + for (int i = 0; i < resolution; i++) { + memcpy(p_dst + dst_count, p_src + src_count, MIN(read_channels, write_channels) * sizeof(T)); + + if constexpr (write_channels > read_channels) { + const T def_value[4] = { def_zero, def_zero, def_zero, def_one }; + memcpy(p_dst + dst_count + read_channels, &def_value[read_channels], (write_channels - read_channels) * sizeof(T)); + } + + dst_count += write_channels; + src_count += read_channels; + } +} + +static bool _are_formats_compatible(Image::Format p_format0, Image::Format p_format1) { + if (p_format0 <= Image::FORMAT_RGBA8 && p_format1 <= Image::FORMAT_RGBA8) { + return true; + } else if (p_format0 <= Image::FORMAT_RGBAH && p_format0 >= Image::FORMAT_RH && p_format1 <= Image::FORMAT_RGBAH && p_format1 >= Image::FORMAT_RH) { + return true; + } else if (p_format0 <= Image::FORMAT_RGBAF && p_format0 >= Image::FORMAT_RF && p_format1 <= Image::FORMAT_RGBAF && p_format1 >= Image::FORMAT_RF) { + return true; + } + + return false; +} + void Image::convert(Format p_new_format) { ERR_FAIL_INDEX_MSG(p_new_format, FORMAT_MAX, "The Image format specified (" + itos(p_new_format) + ") is out of range. See Image's Format enum."); if (data.size() == 0) { @@ -517,7 +550,7 @@ void Image::convert(Format p_new_format) { if (Image::is_format_compressed(format) || Image::is_format_compressed(p_new_format)) { ERR_FAIL_MSG("Cannot convert to <-> from compressed formats. Use compress() and decompress() instead."); - } else if (format > FORMAT_RGBA8 || p_new_format > FORMAT_RGBA8) { + } else if (!_are_formats_compatible(format, p_new_format)) { //use put/set pixel which is slower but works with non byte formats Image new_img(width, height, mipmaps, p_new_format); @@ -648,6 +681,78 @@ void Image::convert(Format p_new_format) { case FORMAT_RGBA8 | (FORMAT_RGB8 << 8): _convert<3, true, 3, false, false, false>(mip_width, mip_height, rptr, wptr); break; + case FORMAT_RH | (FORMAT_RGH << 8): + _convert_fast(mip_width, mip_height, (const uint16_t *)rptr, (uint16_t *)wptr); + break; + case FORMAT_RH | (FORMAT_RGBH << 8): + _convert_fast(mip_width, mip_height, (const uint16_t *)rptr, (uint16_t *)wptr); + break; + case FORMAT_RH | (FORMAT_RGBAH << 8): + _convert_fast(mip_width, mip_height, (const uint16_t *)rptr, (uint16_t *)wptr); + break; + case FORMAT_RGH | (FORMAT_RH << 8): + _convert_fast(mip_width, mip_height, (const uint16_t *)rptr, (uint16_t *)wptr); + break; + case FORMAT_RGH | (FORMAT_RGBH << 8): + _convert_fast(mip_width, mip_height, (const uint16_t *)rptr, (uint16_t *)wptr); + break; + case FORMAT_RGH | (FORMAT_RGBAH << 8): + _convert_fast(mip_width, mip_height, (const uint16_t *)rptr, (uint16_t *)wptr); + break; + case FORMAT_RGBH | (FORMAT_RH << 8): + _convert_fast(mip_width, mip_height, (const uint16_t *)rptr, (uint16_t *)wptr); + break; + case FORMAT_RGBH | (FORMAT_RGH << 8): + _convert_fast(mip_width, mip_height, (const uint16_t *)rptr, (uint16_t *)wptr); + break; + case FORMAT_RGBH | (FORMAT_RGBAH << 8): + _convert_fast(mip_width, mip_height, (const uint16_t *)rptr, (uint16_t *)wptr); + break; + case FORMAT_RGBAH | (FORMAT_RH << 8): + _convert_fast(mip_width, mip_height, (const uint16_t *)rptr, (uint16_t *)wptr); + break; + case FORMAT_RGBAH | (FORMAT_RGH << 8): + _convert_fast(mip_width, mip_height, (const uint16_t *)rptr, (uint16_t *)wptr); + break; + case FORMAT_RGBAH | (FORMAT_RGBH << 8): + _convert_fast(mip_width, mip_height, (const uint16_t *)rptr, (uint16_t *)wptr); + break; + case FORMAT_RF | (FORMAT_RGF << 8): + _convert_fast(mip_width, mip_height, (const uint32_t *)rptr, (uint32_t *)wptr); + break; + case FORMAT_RF | (FORMAT_RGBF << 8): + _convert_fast(mip_width, mip_height, (const uint32_t *)rptr, (uint32_t *)wptr); + break; + case FORMAT_RF | (FORMAT_RGBAF << 8): + _convert_fast(mip_width, mip_height, (const uint32_t *)rptr, (uint32_t *)wptr); + break; + case FORMAT_RGF | (FORMAT_RF << 8): + _convert_fast(mip_width, mip_height, (const uint32_t *)rptr, (uint32_t *)wptr); + break; + case FORMAT_RGF | (FORMAT_RGBF << 8): + _convert_fast(mip_width, mip_height, (const uint32_t *)rptr, (uint32_t *)wptr); + break; + case FORMAT_RGF | (FORMAT_RGBAF << 8): + _convert_fast(mip_width, mip_height, (const uint32_t *)rptr, (uint32_t *)wptr); + break; + case FORMAT_RGBF | (FORMAT_RF << 8): + _convert_fast(mip_width, mip_height, (const uint32_t *)rptr, (uint32_t *)wptr); + break; + case FORMAT_RGBF | (FORMAT_RGF << 8): + _convert_fast(mip_width, mip_height, (const uint32_t *)rptr, (uint32_t *)wptr); + break; + case FORMAT_RGBF | (FORMAT_RGBAF << 8): + _convert_fast(mip_width, mip_height, (const uint32_t *)rptr, (uint32_t *)wptr); + break; + case FORMAT_RGBAF | (FORMAT_RF << 8): + _convert_fast(mip_width, mip_height, (const uint32_t *)rptr, (uint32_t *)wptr); + break; + case FORMAT_RGBAF | (FORMAT_RGF << 8): + _convert_fast(mip_width, mip_height, (const uint32_t *)rptr, (uint32_t *)wptr); + break; + case FORMAT_RGBAF | (FORMAT_RGBF << 8): + _convert_fast(mip_width, mip_height, (const uint32_t *)rptr, (uint32_t *)wptr); + break; } } @@ -761,12 +866,10 @@ static void _scale_cubic(const uint8_t *__restrict p_src, uint8_t *__restrict p_ template static void _scale_bilinear(const uint8_t *__restrict p_src, uint8_t *__restrict p_dst, uint32_t p_src_width, uint32_t p_src_height, uint32_t p_dst_width, uint32_t p_dst_height) { - enum { - FRAC_BITS = 8, - FRAC_LEN = (1 << FRAC_BITS), - FRAC_HALF = (FRAC_LEN >> 1), - FRAC_MASK = FRAC_LEN - 1 - }; + constexpr uint32_t FRAC_BITS = 8; + constexpr uint32_t FRAC_LEN = (1 << FRAC_BITS); + constexpr uint32_t FRAC_HALF = (FRAC_LEN >> 1); + constexpr uint32_t FRAC_MASK = FRAC_LEN - 1; for (uint32_t i = 0; i < p_dst_height; i++) { // Add 0.5 in order to interpolate based on pixel center @@ -2630,6 +2733,40 @@ Error Image::compress(CompressMode p_mode, CompressSource p_source, ASTCFormat p Error Image::compress_from_channels(CompressMode p_mode, UsedChannels p_channels, ASTCFormat p_astc_format) { ERR_FAIL_COND_V(data.is_empty(), ERR_INVALID_DATA); + // RenderingDevice only. + if (GLOBAL_GET("rendering/textures/vram_compression/compress_with_gpu")) { + switch (p_mode) { + case COMPRESS_BPTC: { + // BC7 is unsupported currently. + if ((format >= FORMAT_RF && format <= FORMAT_RGBE9995) && _image_compress_bptc_rd_func) { + Error result = _image_compress_bptc_rd_func(this, p_channels); + + // If the image was compressed successfully, we return here. If not, we fall back to the default compression scheme. + if (result == OK) { + return OK; + } + } + + } break; + + case COMPRESS_S3TC: { + // BC3 is unsupported currently. + if ((p_channels == USED_CHANNELS_RGB || p_channels == USED_CHANNELS_L) && _image_compress_bc_rd_func) { + Error result = _image_compress_bc_rd_func(this, p_channels); + + // If the image was compressed successfully, we return here. If not, we fall back to the default compression scheme. + if (result == OK) { + return OK; + } + } + + } break; + + default: { + } + } + } + switch (p_mode) { case COMPRESS_S3TC: { ERR_FAIL_NULL_V(_image_compress_bc_func, ERR_UNAVAILABLE); @@ -3011,6 +3148,8 @@ void (*Image::_image_compress_bptc_func)(Image *, Image::UsedChannels) = nullptr void (*Image::_image_compress_etc1_func)(Image *) = nullptr; void (*Image::_image_compress_etc2_func)(Image *, Image::UsedChannels) = nullptr; void (*Image::_image_compress_astc_func)(Image *, Image::ASTCFormat) = nullptr; +Error (*Image::_image_compress_bptc_rd_func)(Image *, Image::UsedChannels) = nullptr; +Error (*Image::_image_compress_bc_rd_func)(Image *, Image::UsedChannels) = nullptr; void (*Image::_image_decompress_bc)(Image *) = nullptr; void (*Image::_image_decompress_bptc)(Image *) = nullptr; void (*Image::_image_decompress_etc1)(Image *) = nullptr; @@ -3460,6 +3599,7 @@ void Image::_bind_methods() { ClassDB::bind_method(D_METHOD("fix_alpha_edges"), &Image::fix_alpha_edges); ClassDB::bind_method(D_METHOD("premultiply_alpha"), &Image::premultiply_alpha); ClassDB::bind_method(D_METHOD("srgb_to_linear"), &Image::srgb_to_linear); + ClassDB::bind_method(D_METHOD("linear_to_srgb"), &Image::linear_to_srgb); ClassDB::bind_method(D_METHOD("normal_map_to_xy"), &Image::normal_map_to_xy); ClassDB::bind_method(D_METHOD("rgbe_to_srgb"), &Image::rgbe_to_srgb); ClassDB::bind_method(D_METHOD("bump_map_to_normal_map", "bump_scale"), &Image::bump_map_to_normal_map, DEFVAL(1.0)); @@ -3696,6 +3836,33 @@ void Image::bump_map_to_normal_map(float bump_scale) { data = result_image; } +bool Image::detect_signed(bool p_include_mips) const { + ERR_FAIL_COND_V(is_compressed(), false); + + if (format >= Image::FORMAT_RH && format <= Image::FORMAT_RGBAH) { + const uint16_t *img_data = reinterpret_cast(data.ptr()); + const uint64_t img_size = p_include_mips ? (data.size() / 2) : (width * height * get_format_pixel_size(format) / 2); + + for (uint64_t i = 0; i < img_size; i++) { + if ((img_data[i] & 0x8000) != 0 && (img_data[i] & 0x7fff) != 0) { + return true; + } + } + + } else if (format >= Image::FORMAT_RF && format <= Image::FORMAT_RGBAF) { + const uint32_t *img_data = reinterpret_cast(data.ptr()); + const uint64_t img_size = p_include_mips ? (data.size() / 4) : (width * height * get_format_pixel_size(format) / 4); + + for (uint64_t i = 0; i < img_size; i++) { + if ((img_data[i] & 0x80000000) != 0 && (img_data[i] & 0x7fffffff) != 0) { + return true; + } + } + } + + return false; +} + void Image::srgb_to_linear() { if (data.size() == 0) { return; @@ -3727,6 +3894,37 @@ void Image::srgb_to_linear() { } } +void Image::linear_to_srgb() { + if (data.size() == 0) { + return; + } + + static const uint8_t lin2srgb[256] = { 0, 12, 21, 28, 33, 38, 42, 46, 49, 52, 55, 58, 61, 63, 66, 68, 70, 73, 75, 77, 79, 81, 82, 84, 86, 88, 89, 91, 93, 94, 96, 97, 99, 100, 102, 103, 104, 106, 107, 109, 110, 111, 112, 114, 115, 116, 117, 118, 120, 121, 122, 123, 124, 125, 126, 127, 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 142, 143, 144, 145, 146, 147, 148, 149, 150, 151, 151, 152, 153, 154, 155, 156, 157, 157, 158, 159, 160, 161, 161, 162, 163, 164, 165, 165, 166, 167, 168, 168, 169, 170, 171, 171, 172, 173, 174, 174, 175, 176, 176, 177, 178, 179, 179, 180, 181, 181, 182, 183, 183, 184, 185, 185, 186, 187, 187, 188, 189, 189, 190, 191, 191, 192, 193, 193, 194, 194, 195, 196, 196, 197, 197, 198, 199, 199, 200, 201, 201, 202, 202, 203, 204, 204, 205, 205, 206, 206, 207, 208, 208, 209, 209, 210, 210, 211, 212, 212, 213, 213, 214, 214, 215, 215, 216, 217, 217, 218, 218, 219, 219, 220, 220, 221, 221, 222, 222, 223, 223, 224, 224, 225, 226, 226, 227, 227, 228, 228, 229, 229, 230, 230, 231, 231, 232, 232, 233, 233, 234, 234, 235, 235, 236, 236, 237, 237, 237, 238, 238, 239, 239, 240, 240, 241, 241, 242, 242, 243, 243, 244, 244, 245, 245, 245, 246, 246, 247, 247, 248, 248, 249, 249, 250, 250, 251, 251, 251, 252, 252, 253, 253, 254, 254, 255 }; + + ERR_FAIL_COND(format != FORMAT_RGB8 && format != FORMAT_RGBA8); + + if (format == FORMAT_RGBA8) { + int len = data.size() / 4; + uint8_t *data_ptr = data.ptrw(); + + for (int i = 0; i < len; i++) { + data_ptr[(i << 2) + 0] = lin2srgb[data_ptr[(i << 2) + 0]]; + data_ptr[(i << 2) + 1] = lin2srgb[data_ptr[(i << 2) + 1]]; + data_ptr[(i << 2) + 2] = lin2srgb[data_ptr[(i << 2) + 2]]; + } + + } else if (format == FORMAT_RGB8) { + int len = data.size() / 3; + uint8_t *data_ptr = data.ptrw(); + + for (int i = 0; i < len; i++) { + data_ptr[(i * 3) + 0] = lin2srgb[data_ptr[(i * 3) + 0]]; + data_ptr[(i * 3) + 1] = lin2srgb[data_ptr[(i * 3) + 1]]; + data_ptr[(i * 3) + 2] = lin2srgb[data_ptr[(i * 3) + 2]]; + } + } +} + void Image::premultiply_alpha() { if (data.size() == 0) { return; @@ -4039,7 +4237,7 @@ Dictionary Image::compute_image_metrics(const Ref 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 compared_image = duplicate(true); if (compared_image->is_compressed()) { diff --git a/core/io/image.h b/core/io/image.h index d55cc39dbb57..78757246e06a 100644 --- a/core/io/image.h +++ b/core/io/image.h @@ -159,6 +159,9 @@ class Image : public Resource { static void (*_image_compress_etc2_func)(Image *, UsedChannels p_channels); static void (*_image_compress_astc_func)(Image *, ASTCFormat p_format); + static Error (*_image_compress_bptc_rd_func)(Image *, UsedChannels p_channels); + static Error (*_image_compress_bc_rd_func)(Image *, UsedChannels p_channels); + static void (*_image_decompress_bc)(Image *); static void (*_image_decompress_bptc)(Image *); static void (*_image_decompress_etc1)(Image *); @@ -383,11 +386,14 @@ class Image : public Resource { void fix_alpha_edges(); void premultiply_alpha(); void srgb_to_linear(); + void linear_to_srgb(); void normal_map_to_xy(); Ref rgbe_to_srgb(); Ref get_image_from_mipmap(int p_mipmap) const; void bump_map_to_normal_map(float bump_scale = 1.0); + bool detect_signed(bool p_include_mips = true) const; + void blit_rect(const Ref &p_src, const Rect2i &p_src_rect, const Point2i &p_dest); void blit_rect_mask(const Ref &p_src, const Ref &p_mask, const Rect2i &p_src_rect, const Point2i &p_dest); void blend_rect(const Ref &p_src, const Rect2i &p_src_rect, const Point2i &p_dest); diff --git a/core/io/ip.cpp b/core/io/ip.cpp index f20d65bef901..38c71b19fa31 100644 --- a/core/io/ip.cpp +++ b/core/io/ip.cpp @@ -81,17 +81,17 @@ struct _IP_ResolverPrivate { continue; } - mutex.lock(); + MutexLock lock(mutex); List response; String hostname = queue[i].hostname; IP::Type type = queue[i].type; - mutex.unlock(); + lock.temp_unlock(); // We should not lock while resolving the hostname, // only when modifying the queue. IP::get_singleton()->_resolve_hostname(response, hostname, type); - MutexLock lock(mutex); + lock.temp_relock(); // Could have been completed by another function, or deleted. if (queue[i].status.get() != IP::RESOLVER_STATUS_WAITING) { continue; @@ -131,21 +131,22 @@ PackedStringArray IP::resolve_hostname_addresses(const String &p_hostname, Type List res; String key = _IP_ResolverPrivate::get_cache_key(p_hostname, p_type); - resolver->mutex.lock(); - if (resolver->cache.has(key)) { - res = resolver->cache[key]; - } else { - // This should be run unlocked so the resolver thread can keep resolving - // other requests. - resolver->mutex.unlock(); - _resolve_hostname(res, p_hostname, p_type); - resolver->mutex.lock(); - // We might be overriding another result, but we don't care as long as the result is valid. - if (res.size()) { - resolver->cache[key] = res; + { + MutexLock lock(resolver->mutex); + if (resolver->cache.has(key)) { + res = resolver->cache[key]; + } else { + // This should be run unlocked so the resolver thread can keep resolving + // other requests. + lock.temp_unlock(); + _resolve_hostname(res, p_hostname, p_type); + lock.temp_relock(); + // We might be overriding another result, but we don't care as long as the result is valid. + if (res.size()) { + resolver->cache[key] = res; + } } } - resolver->mutex.unlock(); PackedStringArray result; for (const IPAddress &E : res) { diff --git a/core/io/json.cpp b/core/io/json.cpp index 61051727c181..664ff7857b6b 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 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 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 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 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 801fa29b4bbe..67b5e09afa0e 100644 --- a/core/io/json.h +++ b/core/io/json.h @@ -94,6 +94,9 @@ class JSON : public Resource { 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/logger.cpp b/core/io/logger.cpp index a24277fe7247..26b60f6738e9 100644 --- a/core/io/logger.cpp +++ b/core/io/logger.cpp @@ -84,11 +84,7 @@ void Logger::log_error(const char *p_function, const char *p_file, int p_line, c err_details = p_code; } - if (p_editor_notify) { - logf_error("%s: %s\n", err_type, err_details); - } else { - logf_error("USER %s: %s\n", err_type, err_details); - } + logf_error("%s: %s\n", err_type, err_details); logf_error(" at: %s (%s:%i)\n", p_function, p_file, p_line); } diff --git a/core/io/packed_data_container.h b/core/io/packed_data_container.h index cc9996101e2a..f4ffa090223a 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/packet_peer.cpp b/core/io/packet_peer.cpp index 0329ace31305..614f81c42a1b 100644 --- a/core/io/packet_peer.cpp +++ b/core/io/packet_peer.cpp @@ -299,7 +299,7 @@ Ref PacketPeerStream::get_stream_peer() const { void PacketPeerStream::set_input_buffer_max_size(int p_max_size) { ERR_FAIL_COND_MSG(p_max_size < 0, "Max size of input buffer size cannot be smaller than 0."); - //warning may lose packets + // WARNING: May lose packets. ERR_FAIL_COND_MSG(ring_buffer.data_left(), "Buffer in use, resizing would cause loss of data."); ring_buffer.resize(nearest_shift(next_power_of_2(p_max_size + 4)) - 1); input_buffer.resize(next_power_of_2(p_max_size + 4)); diff --git a/core/io/packet_peer_dtls.cpp b/core/io/packet_peer_dtls.cpp index 18bef3ff3cca..231c48d88765 100644 --- a/core/io/packet_peer_dtls.cpp +++ b/core/io/packet_peer_dtls.cpp @@ -32,12 +32,12 @@ #include "core/config/project_settings.h" #include "core/io/file_access.h" -PacketPeerDTLS *(*PacketPeerDTLS::_create)() = nullptr; +PacketPeerDTLS *(*PacketPeerDTLS::_create)(bool p_notify_postinitialize) = nullptr; bool PacketPeerDTLS::available = false; -PacketPeerDTLS *PacketPeerDTLS::create() { +PacketPeerDTLS *PacketPeerDTLS::create(bool p_notify_postinitialize) { if (_create) { - return _create(); + return _create(p_notify_postinitialize); } return nullptr; } diff --git a/core/io/packet_peer_dtls.h b/core/io/packet_peer_dtls.h index 3990a851f744..03d97a59036c 100644 --- a/core/io/packet_peer_dtls.h +++ b/core/io/packet_peer_dtls.h @@ -38,7 +38,7 @@ class PacketPeerDTLS : public PacketPeer { GDCLASS(PacketPeerDTLS, PacketPeer); protected: - static PacketPeerDTLS *(*_create)(); + static PacketPeerDTLS *(*_create)(bool p_notify_postinitialize); static void _bind_methods(); static bool available; @@ -57,7 +57,7 @@ class PacketPeerDTLS : public PacketPeer { virtual void disconnect_from_peer() = 0; virtual Status get_status() const = 0; - static PacketPeerDTLS *create(); + static PacketPeerDTLS *create(bool p_notify_postinitialize = true); static bool is_available(); PacketPeerDTLS() {} diff --git a/core/io/pck_packer.cpp b/core/io/pck_packer.cpp index a7c715c31892..93179d9a11f4 100644 --- a/core/io/pck_packer.cpp +++ b/core/io/pck_packer.cpp @@ -47,12 +47,12 @@ static int _get_pad(int p_alignment, int p_n) { } void PCKPacker::_bind_methods() { - ClassDB::bind_method(D_METHOD("pck_start", "pck_name", "alignment", "key", "encrypt_directory"), &PCKPacker::pck_start, DEFVAL(32), DEFVAL("0000000000000000000000000000000000000000000000000000000000000000"), DEFVAL(false)); + ClassDB::bind_method(D_METHOD("pck_start", "pck_path", "alignment", "key", "encrypt_directory"), &PCKPacker::pck_start, DEFVAL(32), DEFVAL("0000000000000000000000000000000000000000000000000000000000000000"), DEFVAL(false)); ClassDB::bind_method(D_METHOD("add_file", "pck_path", "source_path", "encrypt"), &PCKPacker::add_file, DEFVAL(false)); ClassDB::bind_method(D_METHOD("flush", "verbose"), &PCKPacker::flush, DEFVAL(false)); } -Error PCKPacker::pck_start(const String &p_file, int p_alignment, const String &p_key, bool p_encrypt_directory) { +Error PCKPacker::pck_start(const String &p_pck_path, int p_alignment, const String &p_key, bool p_encrypt_directory) { ERR_FAIL_COND_V_MSG((p_key.is_empty() || !p_key.is_valid_hex_number(false) || p_key.length() != 64), ERR_CANT_CREATE, "Invalid Encryption Key (must be 64 characters long)."); ERR_FAIL_COND_V_MSG(p_alignment <= 0, ERR_CANT_CREATE, "Invalid alignment, must be greater then 0."); @@ -83,8 +83,8 @@ Error PCKPacker::pck_start(const String &p_file, int p_alignment, const String & } enc_dir = p_encrypt_directory; - file = FileAccess::open(p_file, FileAccess::WRITE); - ERR_FAIL_COND_V_MSG(file.is_null(), ERR_CANT_CREATE, "Can't open file to write: " + String(p_file) + "."); + file = FileAccess::open(p_pck_path, FileAccess::WRITE); + ERR_FAIL_COND_V_MSG(file.is_null(), ERR_CANT_CREATE, "Can't open file to write: " + String(p_pck_path) + "."); alignment = p_alignment; @@ -106,7 +106,7 @@ Error PCKPacker::pck_start(const String &p_file, int p_alignment, const String & return OK; } -Error PCKPacker::add_file(const String &p_file, const String &p_src, bool p_encrypt) { +Error PCKPacker::add_file(const String &p_pck_path, const String &p_src, bool p_encrypt) { ERR_FAIL_COND_V_MSG(file.is_null(), ERR_INVALID_PARAMETER, "File must be opened before use."); Ref f = FileAccess::open(p_src, FileAccess::READ); @@ -117,7 +117,7 @@ Error PCKPacker::add_file(const String &p_file, const String &p_src, bool p_encr File pf; // Simplify path here and on every 'files' access so that paths that have extra '/' // symbols in them still match to the MD5 hash for the saved path. - pf.path = p_file.simplify_path(); + pf.path = p_pck_path.simplify_path(); pf.src_path = p_src; pf.ofs = ofs; pf.size = f->get_length(); diff --git a/core/io/pck_packer.h b/core/io/pck_packer.h index 8764fc90a015..5aac833532dd 100644 --- a/core/io/pck_packer.h +++ b/core/io/pck_packer.h @@ -58,8 +58,8 @@ class PCKPacker : public RefCounted { Vector files; public: - Error pck_start(const String &p_file, int p_alignment = 32, const String &p_key = "0000000000000000000000000000000000000000000000000000000000000000", bool p_encrypt_directory = false); - Error add_file(const String &p_file, const String &p_src, bool p_encrypt = false); + Error pck_start(const String &p_pck_path, int p_alignment = 32, const String &p_key = "0000000000000000000000000000000000000000000000000000000000000000", bool p_encrypt_directory = false); + Error add_file(const String &p_pck_path, const String &p_src, bool p_encrypt = false); Error flush(bool p_verbose = false); PCKPacker() {} diff --git a/core/io/plist.cpp b/core/io/plist.cpp index 86737609bf56..8d91e6dec294 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 432adb88da9f..0ff4fbe490c5 100644 --- a/core/io/resource.cpp +++ b/core/io/resource.cpp @@ -40,12 +40,12 @@ #include 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 existing = ResourceCache::get_ref(p_path); + Ref 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(); } @@ -96,33 +96,45 @@ String Resource::get_path() const { void Resource::set_path_cache(const String &p_path) { path_cache = p_path; + GDVIRTUAL_CALL(_set_path_cache, p_path); +} + +static thread_local RandomPCG unique_id_gen(0, RandomPCG::DEFAULT_INC); + +void Resource::seed_scene_unique_id(uint32_t p_seed) { + unique_id_gen.seed(p_seed); } String Resource::generate_scene_unique_id() { // Generate a unique enough hash, but still user-readable. // If it's not unique it does not matter because the saver will try again. - OS::DateTime dt = OS::get_singleton()->get_datetime(); - uint32_t hash = hash_murmur3_one_32(OS::get_singleton()->get_ticks_usec()); - hash = hash_murmur3_one_32(dt.year, hash); - hash = hash_murmur3_one_32(dt.month, hash); - hash = hash_murmur3_one_32(dt.day, hash); - hash = hash_murmur3_one_32(dt.hour, hash); - hash = hash_murmur3_one_32(dt.minute, hash); - hash = hash_murmur3_one_32(dt.second, hash); - hash = hash_murmur3_one_32(Math::rand(), hash); + if (unique_id_gen.get_seed() == 0) { + OS::DateTime dt = OS::get_singleton()->get_datetime(); + uint32_t hash = hash_murmur3_one_32(OS::get_singleton()->get_ticks_usec()); + hash = hash_murmur3_one_32(dt.year, hash); + hash = hash_murmur3_one_32(dt.month, hash); + hash = hash_murmur3_one_32(dt.day, hash); + hash = hash_murmur3_one_32(dt.hour, hash); + hash = hash_murmur3_one_32(dt.minute, hash); + hash = hash_murmur3_one_32(dt.second, hash); + hash = hash_murmur3_one_32(Math::rand(), hash); + unique_id_gen.seed(hash); + } + + uint32_t random_num = unique_id_gen.rand(); static constexpr uint32_t characters = 5; static constexpr uint32_t char_count = ('z' - 'a'); static constexpr uint32_t base = char_count + ('9' - '0'); String id; for (uint32_t i = 0; i < characters; i++) { - uint32_t c = hash % base; + uint32_t c = random_num % base; if (c < char_count) { id += String::chr('a' + c); } else { id += String::chr('0' + (c - char_count)); } - hash /= base; + random_num /= base; } return id; @@ -166,28 +178,29 @@ 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); } } void Resource::reset_state() { + GDVIRTUAL_CALL(_reset_state); } Error Resource::copy_from(const Ref &p_resource) { @@ -416,21 +429,15 @@ void Resource::_take_over_path(const String &p_path) { } RID Resource::get_rid() const { - if (get_script_instance()) { - Callable::CallError ce; - RID ret = get_script_instance()->callp(SNAME("_get_rid"), nullptr, 0, ce); - if (ce.error == Callable::CallError::CALL_OK && ret.is_valid()) { - return ret; - } - } - if (_get_extension() && _get_extension()->get_rid) { - RID ret = RID::from_uint64(_get_extension()->get_rid(_get_extension_instance())); - if (ret.is_valid()) { - return ret; + RID ret; + if (!GDVIRTUAL_CALL(_get_rid, ret)) { +#ifndef DISABLE_DEPRECATED + if (_get_extension() && _get_extension()->get_rid) { + ret = RID::from_uint64(_get_extension()->get_rid(_get_extension_instance())); } +#endif } - - return RID(); + return ret; } #ifdef TOOLS_ENABLED @@ -492,20 +499,18 @@ 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 //helps keep IDs same number when loading/saving scenes. -1 clears ID and it Returns -1 when no id stored void Resource::set_id_for_path(const String &p_path, const String &p_id) { +#ifdef TOOLS_ENABLED if (p_id.is_empty()) { ResourceCache::path_cache_lock.write_lock(); ResourceCache::resource_path_cache[p_path].erase(get_path()); @@ -515,9 +520,11 @@ void Resource::set_id_for_path(const String &p_path, const String &p_id) { ResourceCache::resource_path_cache[p_path][get_path()] = p_id; ResourceCache::path_cache_lock.write_unlock(); } +#endif } String Resource::get_id_for_path(const String &p_path) const { +#ifdef TOOLS_ENABLED ResourceCache::path_cache_lock.read_lock(); if (ResourceCache::resource_path_cache[p_path].has(get_path())) { String result = ResourceCache::resource_path_cache[p_path][get_path()]; @@ -527,13 +534,16 @@ String Resource::get_id_for_path(const String &p_path) const { ResourceCache::path_cache_lock.read_unlock(); return ""; } -} +#else + return ""; #endif +} void Resource::_bind_methods() { ClassDB::bind_method(D_METHOD("set_path", "path"), &Resource::_set_path); ClassDB::bind_method(D_METHOD("take_over_path", "path"), &Resource::_take_over_path); ClassDB::bind_method(D_METHOD("get_path"), &Resource::get_path); + ClassDB::bind_method(D_METHOD("set_path_cache", "path"), &Resource::set_path_cache); ClassDB::bind_method(D_METHOD("set_name", "name"), &Resource::set_name); ClassDB::bind_method(D_METHOD("get_name"), &Resource::get_name); ClassDB::bind_method(D_METHOD("get_rid"), &Resource::get_rid); @@ -541,6 +551,12 @@ void Resource::_bind_methods() { ClassDB::bind_method(D_METHOD("is_local_to_scene"), &Resource::is_local_to_scene); ClassDB::bind_method(D_METHOD("get_local_scene"), &Resource::get_local_scene); ClassDB::bind_method(D_METHOD("setup_local_to_scene"), &Resource::setup_local_to_scene); + ClassDB::bind_method(D_METHOD("reset_state"), &Resource::reset_state); + + ClassDB::bind_method(D_METHOD("set_id_for_path", "path", "id"), &Resource::set_id_for_path); + ClassDB::bind_method(D_METHOD("get_id_for_path", "path"), &Resource::get_id_for_path); + + ClassDB::bind_method(D_METHOD("is_built_in"), &Resource::is_built_in); ClassDB::bind_static_method("Resource", D_METHOD("generate_scene_unique_id"), &Resource::generate_scene_unique_id); ClassDB::bind_method(D_METHOD("set_scene_unique_id", "id"), &Resource::set_scene_unique_id); @@ -558,11 +574,10 @@ void Resource::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::STRING, "resource_name"), "set_name", "get_name"); ADD_PROPERTY(PropertyInfo(Variant::STRING, "resource_scene_unique_id", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NONE), "set_scene_unique_id", "get_scene_unique_id"); - MethodInfo get_rid_bind("_get_rid"); - get_rid_bind.return_val.type = Variant::RID; - - ::ClassDB::add_virtual_method(get_class_static(), get_rid_bind, true, Vector(), true); GDVIRTUAL_BIND(_setup_local_to_scene); + GDVIRTUAL_BIND(_get_rid); + GDVIRTUAL_BIND(_reset_state); + GDVIRTUAL_BIND(_set_path_cache, "path"); } Resource::Resource() : @@ -573,14 +588,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::Iterator E = ResourceCache::resources.find(path_cache); if (likely(E && E->value == this)) { ResourceCache::resources.remove(E); } - ResourceCache::lock.unlock(); } HashMap ResourceCache::resources; @@ -609,18 +623,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; @@ -631,28 +647,27 @@ bool ResourceCache::has(const String &p_path) { Ref ResourceCache::get_ref(const String &p_path) { Ref ref; - lock.lock(); - - Resource **res = resources.getptr(p_path); + { + MutexLock mutex_lock(lock); + Resource **res = resources.getptr(p_path); - if (res) { - ref = Ref(*res); - } + if (res) { + ref = Ref(*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> *p_resources) { - lock.lock(); + MutexLock mutex_lock(lock); LocalVector to_remove; @@ -672,14 +687,9 @@ void ResourceCache::get_cached_resources(List> *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.h b/core/io/resource.h index cc8a0d4387d8..015f7ad197b0 100644 --- a/core/io/resource.h +++ b/core/io/resource.h @@ -87,6 +87,11 @@ class Resource : public RefCounted { virtual void reset_local_to_scene(); GDVIRTUAL0(_setup_local_to_scene); + GDVIRTUAL0RC(RID, _get_rid); + + GDVIRTUAL1C(_set_path_cache, String); + GDVIRTUAL0(_reset_state); + public: static Node *(*_get_local_scene_func)(); //used by editor static void (*_update_configuration_warning)(); //used by editor @@ -109,6 +114,7 @@ class Resource : public RefCounted { virtual void set_path_cache(const String &p_path); // Set raw path without involving resource cache. _FORCE_INLINE_ bool is_built_in() const { return path_cache.is_empty() || path_cache.contains("::") || path_cache.begins_with("local://"); } + static void seed_scene_unique_id(uint32_t p_seed); static String generate_scene_unique_id(); void set_scene_unique_id(const String &p_id); String get_scene_unique_id() const; @@ -142,11 +148,9 @@ class Resource : public RefCounted { virtual RID get_rid() const; // some resources may offer conversion to RID -#ifdef TOOLS_ENABLED //helps keep IDs same number when loading/saving scenes. -1 clears ID and it Returns -1 when no id stored void set_id_for_path(const String &p_path, const String &p_id); String get_id_for_path(const String &p_path) const; -#endif Resource(); ~Resource(); diff --git a/core/io/resource_format_binary.cpp b/core/io/resource_format_binary.cpp index f71257fa7644..109999d61253 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 keys; d.get_key_list(&keys); for (const Variant &E : keys) { @@ -2119,6 +2134,8 @@ static String _resource_get_class(Ref p_resource) { } Error ResourceFormatSaverBinaryInstance::save(const String &p_path, const Ref &p_resource, uint32_t p_flags) { + Resource::seed_scene_unique_id(p_path.hash()); + Error err; Ref f; if (p_flags & ResourceSaver::FLAG_COMPRESS) { diff --git a/core/io/resource_importer.cpp b/core/io/resource_importer.cpp index 9e6f3ba31427..1ae50d2d0d93 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 &p_a, const Ref &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 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 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 ResourceFormatImporter::load(const String &p_path, const String &p return Ref(); } + 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(); + } + } + Ref res = ResourceLoader::_load(pat.path, p_path, pat.type, p_cache_mode, r_error, p_use_sub_threads, r_progress); #ifdef TOOLS_ENABLED @@ -364,6 +387,23 @@ ResourceUID::ID ResourceFormatImporter::get_resource_uid(const String &p_path) c return pat.uid; } +Error ResourceFormatImporter::get_resource_import_info(const String &p_path, StringName &r_type, ResourceUID::ID &r_uid, String &r_import_group_file) const { + PathAndType pat; + Error err = _get_path_and_type(p_path, pat); + + if (err == OK) { + r_type = pat.type; + r_uid = pat.uid; + r_import_group_file = pat.group_file; + } else { + r_type = ""; + r_uid = ResourceUID::INVALID_ID; + r_import_group_file = ""; + } + + return err; +} + Variant ResourceFormatImporter::get_resource_metadata(const String &p_path) const { PathAndType pat; Error err = _get_path_and_type(p_path, pat); @@ -467,7 +507,7 @@ bool ResourceFormatImporter::are_import_settings_valid(const String &p_path) con for (int i = 0; i < importers.size(); i++) { if (importers[i]->get_importer_name() == pat.importer) { - if (!importers[i]->are_import_settings_valid(p_path)) { //importer thinks this is not valid + if (!importers[i]->are_import_settings_valid(p_path, pat.metadata)) { //importer thinks this is not valid return false; } } diff --git a/core/io/resource_importer.h b/core/io/resource_importer.h index dbd9e70d16be..221f38494b3c 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 (*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 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 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 *p_extensions) const override; virtual void get_recognized_extensions_for_type(const String &p_type, List *p_extensions) const override; virtual bool recognize_path(const String &p_path, const String &p_for_type = String()) const override; @@ -93,6 +97,8 @@ class ResourceFormatImporter : public ResourceFormatLoader { String get_import_settings_hash() const; 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(); }; @@ -103,6 +109,8 @@ class ResourceImporter : public RefCounted { 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 *p_extensions) const = 0; @@ -145,7 +153,7 @@ class ResourceImporter : public RefCounted { virtual void import_threaded_end() {} virtual Error import_group_file(const String &p_group_file, const HashMap> &p_source_file_options, const HashMap &p_base_paths) { return ERR_UNAVAILABLE; } - virtual bool are_import_settings_valid(const String &p_path) const { return true; } + virtual bool are_import_settings_valid(const String &p_path, const Dictionary &p_meta) const { return true; } virtual String get_import_settings_string() const { return String(); } }; diff --git a/core/io/resource_loader.cpp b/core/io/resource_loader.cpp index 928bb95de3a8..f026d5416caf 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" @@ -38,7 +39,7 @@ #include "core/os/os.h" #include "core/os/safe_binary_mutex.h" #include "core/string/print_string.h" -#include "core/string/translation.h" +#include "core/string/translation_server.h" #include "core/variant/variant_parser.h" #include "servers/rendering_server.h" @@ -207,34 +208,57 @@ void ResourceFormatLoader::_bind_methods() { /////////////////////////////////// +// These are used before and after a wait for a WorkerThreadPool task +// because that can lead to another load started in the same thread, +// something we must treat as a different stack for the purposes +// of tracking nesting. + +#define PREPARE_FOR_WTP_WAIT \ + int load_nesting_backup = ResourceLoader::load_nesting; \ + Vector load_paths_stack_backup = ResourceLoader::load_paths_stack; \ + ResourceLoader::load_nesting = 0; \ + ResourceLoader::load_paths_stack.clear(); + +#define RESTORE_AFTER_WTP_WAIT \ + DEV_ASSERT(ResourceLoader::load_nesting == 0); \ + DEV_ASSERT(ResourceLoader::load_paths_stack.is_empty()); \ + ResourceLoader::load_nesting = load_nesting_backup; \ + ResourceLoader::load_paths_stack = load_paths_stack_backup; \ + load_paths_stack_backup.clear(); + // 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; - 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.awaited) { - task_to_await = load_task.task_id; - load_task.awaited = true; - } - thread_load_tasks.erase(local_path); - local_path.clear(); - } + { + 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 (!user_path.is_empty()) { - DEV_ASSERT(user_load_tokens.has(user_path)); - user_load_tokens.erase(user_path); - user_path.clear(); + 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. + } } - 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 WorkerThreadPool::get_singleton()->wait_for_task_completion(task_to_await); + RESTORE_AFTER_WTP_WAIT } } @@ -245,18 +269,17 @@ ResourceLoader::LoadToken::~LoadToken() { Ref ResourceLoader::_load(const String &p_path, const String &p_original_path, const String &p_type_hint, ResourceFormatLoader::CacheMode p_cache_mode, Error *r_error, bool p_use_sub_threads, float *r_progress) { 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(); - const String &parent_task_path = load_paths_stack->get(load_paths_stack->size() - 1); + if (load_paths_stack.size()) { + MutexLock thread_load_lock(thread_load_mutex); + const String &parent_task_path = load_paths_stack.get(load_paths_stack.size() - 1); HashMap::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). bool is_remapped_load = original_path == parent_task_path; if (E && !is_remapped_load) { E->value.sub_tasks.insert(p_original_path); } - thread_load_mutex.unlock(); } - load_paths_stack->push_back(original_path); + load_paths_stack.push_back(original_path); // Try all loaders and pick the first match for the type hint bool found = false; @@ -272,7 +295,7 @@ Ref ResourceLoader::_load(const String &p_path, const String &p_origin } } - load_paths_stack->resize(load_paths_stack->size() - 1); + load_paths_stack.resize(load_paths_stack.size() - 1); res_ref_overrides.erase(load_nesting); load_nesting--; @@ -291,23 +314,26 @@ Ref ResourceLoader::_load(const String &p_path, const String &p_origin ERR_FAIL_V_MSG(Ref(), vformat("No loader found for resource: %s (expected type: %s)", p_path, p_type_hint)); } -void ResourceLoader::_thread_load_function(void *p_userdata) { +// 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(); - caller_task_id = load_task.task_id; - 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; if (load_nesting == 0) { - load_paths_stack = memnew(Vector); - + DEV_ASSERT(load_paths_stack.is_empty()); if (!Thread::is_main_thread()) { // Let the caller thread use its own, for added flexibility. Provide one otherwise. if (MessageQueue::get_singleton() == MessageQueue::get_main_singleton()) { @@ -319,12 +345,21 @@ void ResourceLoader::_thread_load_function(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 res = _load(load_task.remapped_path, load_task.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); + Ref 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; @@ -337,11 +372,10 @@ void ResourceLoader::_thread_load_function(void *p_userdata) { load_task.status = THREAD_LOAD_LOADED; } - if (load_task.cond_var) { + if (load_task.cond_var && load_task.need_wait) { load_task.cond_var->notify_all(); - memdelete(load_task.cond_var); - load_task.cond_var = nullptr; } + load_task.need_wait = false; bool ignoring = load_task.cache_mode == ResourceFormatLoader::CACHE_MODE_IGNORE || load_task.cache_mode == ResourceFormatLoader::CACHE_MODE_IGNORE_DEEP; bool replacing = load_task.cache_mode == ResourceFormatLoader::CACHE_MODE_REPLACE || load_task.cache_mode == ResourceFormatLoader::CACHE_MODE_REPLACE_DEEP; @@ -354,27 +388,40 @@ void ResourceLoader::_thread_load_function(void *p_userdata) { unlock_pending = false; if (!ignoring) { - if (replacing) { - Ref old_res = ResourceCache::get_ref(load_task.local_path); - if (old_res.is_valid() && old_res != load_task.resource) { - // If resource is already loaded, only replace its data, to avoid existing invalidating instances. - old_res->copy_from(load_task.resource); + ResourceCache::lock.lock(); // Check and operations must happen atomically. + bool pending_unlock = true; + Ref old_res = ResourceCache::get_ref(load_task.local_path); + if (old_res.is_valid()) { + if (old_res != load_task.resource) { + // Resource can already exists at this point for two reasons: + // a) The load uses replace mode. + // b) There were more than one load in flight for the same path because of deadlock prevention. + // Either case, we want to keep the resource that was already there. + ResourceCache::lock.unlock(); + pending_unlock = false; + if (replacing) { + old_res->copy_from(load_task.resource); + } load_task.resource = old_res; } + } else { + load_task.resource->set_path(load_task.local_path); + } + if (pending_unlock) { + ResourceCache::lock.unlock(); } - load_task.resource->set_path(load_task.local_path, replacing); } else { load_task.resource->set_path_cache(load_task.local_path); } - if (load_task.xl_remapped) { + if (xl_remapped) { load_task.resource->set_as_translation_remapped(true); } #ifdef TOOLS_ENABLED load_task.resource->set_edited(false); if (timestamp_on_load) { - uint64_t mt = FileAccess::get_modified_time(load_task.remapped_path); + uint64_t mt = FileAccess::get_modified_time(remapped_path); //printf("mt %s: %lli\n",remapped_path.utf8().get_data(),mt); load_task.resource->set_last_modified_time(mt); } @@ -399,6 +446,9 @@ void ResourceLoader::_thread_load_function(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(); } @@ -408,11 +458,10 @@ void ResourceLoader::_thread_load_function(void *p_userdata) { MessageQueue::set_thread_singleton_override(nullptr); memdelete(own_mq_override); } - if (load_paths_stack) { - memdelete(load_paths_stack); - load_paths_stack = nullptr; - } + DEV_ASSERT(load_paths_stack.is_empty()); } + + curr_load_task = curr_load_task_backup; } static String _validate_local_path(const String &p_path) { @@ -427,36 +476,44 @@ static String _validate_local_path(const String &p_path) { } Error ResourceLoader::load_threaded_request(const String &p_path, const String &p_type_hint, bool p_use_sub_threads, ResourceFormatLoader::CacheMode p_cache_mode) { - thread_load_mutex.lock(); - if (user_load_tokens.has(p_path)) { + Ref token = _load_start(p_path, p_type_hint, p_use_sub_threads ? LOAD_THREAD_DISTRIBUTE : LOAD_THREAD_SPAWN_SINGLE, p_cache_mode, true); + return token.is_valid() ? OK : FAILED; +} + +ResourceLoader::LoadToken *ResourceLoader::_load_threaded_request_reuse_user_token(const String &p_path) { + HashMap::Iterator E = user_load_tokens.find(p_path); + if (E) { print_verbose("load_threaded_request(): Another threaded load for resource path '" + p_path + "' has been initiated. Not an error."); - user_load_tokens[p_path]->reference(); // Additional request. - thread_load_mutex.unlock(); - return OK; - } - user_load_tokens[p_path] = nullptr; - thread_load_mutex.unlock(); - - Ref token = _load_start(p_path, p_type_hint, p_use_sub_threads ? LOAD_THREAD_DISTRIBUTE : LOAD_THREAD_SPAWN_SINGLE, p_cache_mode); - if (token.is_valid()) { - thread_load_mutex.lock(); - token->user_path = p_path; - token->reference(); // First request. - user_load_tokens[p_path] = token.ptr(); - print_lt("REQUEST: user load tokens: " + itos(user_load_tokens.size())); - thread_load_mutex.unlock(); - return OK; + LoadToken *token = E->value; + token->user_rc++; + return token; } else { - return FAILED; + return nullptr; } } +void ResourceLoader::_load_threaded_request_setup_user_token(LoadToken *p_token, const String &p_path) { + p_token->user_path = p_path; + p_token->reference(); // Extra RC until all user requests have been gotten. + p_token->user_rc = 1; + user_load_tokens[p_path] = p_token; + print_lt("REQUEST: user load tokens: " + itos(user_load_tokens.size())); +} + Ref ResourceLoader::load(const String &p_path, const String &p_type_hint, ResourceFormatLoader::CacheMode p_cache_mode, Error *r_error) { if (r_error) { *r_error = OK; } - Ref load_token = _load_start(p_path, p_type_hint, LOAD_THREAD_FROM_CURRENT, p_cache_mode); + LoadThreadMode thread_mode = LOAD_THREAD_FROM_CURRENT; + if (WorkerThreadPool::get_singleton()->get_caller_task_id() != WorkerThreadPool::INVALID_TASK_ID) { + // If user is initiating a single-threaded load from a WorkerThreadPool task, + // we instead spawn a new task so there's a precondition that a load in a pool task + // is always initiated by the engine. That makes certain aspects simpler, such as + // cyclic load detection and awaiting. + thread_mode = LOAD_THREAD_SPAWN_SINGLE; + } + Ref load_token = _load_start(p_path, p_type_hint, thread_mode, p_cache_mode); if (!load_token.is_valid()) { if (r_error) { *r_error = FAILED; @@ -468,22 +525,32 @@ Ref ResourceLoader::load(const String &p_path, const String &p_type_hi return res; } -Ref ResourceLoader::_load_start(const String &p_path, const String &p_type_hint, LoadThreadMode p_thread_mode, ResourceFormatLoader::CacheMode p_cache_mode) { +Ref ResourceLoader::_load_start(const String &p_path, const String &p_type_hint, LoadThreadMode p_thread_mode, ResourceFormatLoader::CacheMode p_cache_mode, bool p_for_user) { String local_path = _validate_local_path(p_path); bool ignoring_cache = p_cache_mode == ResourceFormatLoader::CACHE_MODE_IGNORE || p_cache_mode == ResourceFormatLoader::CACHE_MODE_IGNORE_DEEP; Ref 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); + if (p_for_user) { + LoadToken *existing_token = _load_threaded_request_reuse_user_token(p_path); + if (existing_token) { + return Ref(existing_token); + } + } + if (!ignoring_cache && thread_load_tasks.has(local_path)) { load_token = Ref(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). @@ -494,12 +561,14 @@ Ref ResourceLoader::_load_start(const String &p_path, load_token.instantiate(); load_token->local_path = local_path; + if (p_for_user) { + _load_threaded_request_setup_user_token(load_token.ptr(), p_path); + } //create load task { ThreadLoadTask load_task; - load_task.remapped_path = _path_remap(local_path, &load_task.xl_remapped); load_task.load_token = load_token.ptr(); load_task.local_path = local_path; load_task.type_hint = p_type_hint; @@ -512,17 +581,17 @@ Ref ResourceLoader::_load_start(const String &p_path, load_task.resource = existing; load_task.status = THREAD_LOAD_LOADED; load_task.progress = 1.0; + DEV_ASSERT(!thread_load_tasks.has(local_path)); thread_load_tasks[local_path] = load_task; return load_token; } } - // 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::Iterator E = thread_load_tasks.insert(local_path, load_task); @@ -530,20 +599,26 @@ Ref 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) { - load_task_ptr->thread_id = Thread::get_caller_id(); + 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) { + load_task_ptr->task_id = tid; + } else { + load_task_ptr->thread_id = Thread::get_caller_id(); + } } else { - load_task_ptr->task_id = WorkerThreadPool::get_singleton()->add_native_task(&ResourceLoader::_thread_load_function, load_task_ptr); + load_task_ptr->task_id = WorkerThreadPool::get_singleton()->add_native_task(&ResourceLoader::_run_load_task, load_task_ptr); } - } + } // MutexLock(thread_load_mutex). - if (run_on_current_thread) { - _thread_load_function(load_task_ptr); - if (must_not_register) { - load_token->res_if_unregistered = load_task_ptr->resource; - } + if (p_thread_mode == LOAD_THREAD_FROM_CURRENT) { + _run_load_task(load_task_ptr); } return load_token; @@ -583,13 +658,7 @@ ResourceLoader::ThreadLoadStatus ResourceLoader::load_threaded_get_status(const } String local_path = _validate_local_path(p_path); - if (!thread_load_tasks.has(local_path)) { -#ifdef DEV_ENABLED - CRASH_NOW(); -#endif - // On non-dev, be defensive and at least avoid crashing (at this point at least). - return THREAD_LOAD_INVALID_RESOURCE; - } + ERR_FAIL_COND_V_MSG(!thread_load_tasks.has(local_path), THREAD_LOAD_INVALID_RESOURCE, "Bug in ResourceLoader logic, please report."); ThreadLoadTask &load_task = thread_load_tasks[local_path]; status = load_task.status; @@ -633,22 +702,16 @@ Ref ResourceLoader::load_threaded_get(const String &p_path, Error *r_e } LoadToken *load_token = user_load_tokens[p_path]; - if (!load_token) { - // This happens if requested from one thread and rapidly querying from another. - if (r_error) { - *r_error = ERR_BUSY; - } - return Ref(); - } + DEV_ASSERT(load_token->user_rc >= 1); // Support userland requesting on the main thread before the load is reported to be complete. if (Thread::is_main_thread() && !load_token->local_path.is_empty()) { const ThreadLoadTask &load_task = thread_load_tasks[load_token->local_path]; while (load_task.status == THREAD_LOAD_IN_PROGRESS) { - thread_load_lock.~MutexLock(); + thread_load_lock.temp_unlock(); bool exit = !_ensure_load_progress(); OS::get_singleton()->delay_usec(1000); - new (&thread_load_lock) MutexLock(thread_load_mutex); + thread_load_lock.temp_relock(); if (exit) { break; } @@ -656,8 +719,15 @@ Ref ResourceLoader::load_threaded_get(const String &p_path, Error *r_e } res = _load_complete_inner(*load_token, r_error, thread_load_lock); - if (load_token->unreference()) { - memdelete(load_token); + + load_token->user_rc--; + if (load_token->user_rc == 0) { + load_token->user_path.clear(); + user_load_tokens.erase(p_path); + if (load_token->unreference()) { + memdelete(load_token); + load_token = nullptr; + } } } @@ -676,16 +746,15 @@ Ref 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)) { -#ifdef DEV_ENABLED - CRASH_NOW(); -#endif - // On non-dev, be defensive and at least avoid crashing (at this point at least). if (r_error) { *r_error = ERR_BUG; } - return Ref(); + ERR_FAIL_V_MSG(Ref(), "Bug in ResourceLoader logic, please report."); } ThreadLoadTask &load_task = thread_load_tasks[p_load_token.local_path]; @@ -693,7 +762,7 @@ Ref ResourceLoader::_load_complete_inner(LoadToken &p_load_token, Erro if (load_task.status == THREAD_LOAD_IN_PROGRESS) { DEV_ASSERT((load_task.task_id == 0) != (load_task.thread_id == 0)); - if ((load_task.task_id != 0 && load_task.task_id == caller_task_id) || + if ((load_task.task_id != 0 && load_task.task_id == WorkerThreadPool::get_singleton()->get_caller_task_id()) || (load_task.thread_id != 0 && load_task.thread_id == Thread::get_caller_id())) { // Load is in progress, but it's precisely this thread the one in charge. // That means this is a cyclic load. @@ -704,49 +773,46 @@ Ref ResourceLoader::_load_complete_inner(LoadToken &p_load_token, Erro } bool loader_is_wtp = load_task.task_id != 0; - Error wtp_task_err = FAILED; if (loader_is_wtp) { // Loading thread is in the worker pool. + p_thread_load_lock.temp_unlock(); + + PREPARE_FOR_WTP_WAIT + Error wait_err = WorkerThreadPool::get_singleton()->wait_for_task_completion(load_task.task_id); + RESTORE_AFTER_WTP_WAIT + + DEV_ASSERT(!wait_err || wait_err == ERR_BUSY); + if (wait_err == ERR_BUSY) { + // The WorkerThreadPool has reported that the current task wants to await on an older one. + // That't not allowed for safety, to avoid deadlocks. Fortunately, though, in the context of + // 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); + } + + p_thread_load_lock.temp_relock(); load_task.awaited = true; - thread_load_mutex.unlock(); - wtp_task_err = WorkerThreadPool::get_singleton()->wait_for_task_completion(load_task.task_id); - } - if (load_task.status == THREAD_LOAD_IN_PROGRESS) { // If early errored, awaiting would deadlock. - if (loader_is_wtp) { - if (wtp_task_err == ERR_BUSY) { - // The WorkerThreadPool has reported that the current task wants to await on an older one. - // That't not allowed for safety, to avoid deadlocks. Fortunately, though, in the context of - // 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. - // CACHE_MODE_IGNORE is needed because, otherwise, the new request would just see there's - // an ongoing load for that resource and wait for it again. This value forces a new load. - Ref token = _load_start(load_task.local_path, load_task.type_hint, LOAD_THREAD_DISTRIBUTE, ResourceFormatLoader::CACHE_MODE_IGNORE); - Ref resource = _load_complete(*token.ptr(), &wtp_task_err); - if (r_error) { - *r_error = wtp_task_err; - } - thread_load_mutex.lock(); - return resource; - } else { - DEV_ASSERT(wtp_task_err == OK); - thread_load_mutex.lock(); - } - } else { - // Loading thread is main or user thread. - if (!load_task.cond_var) { - load_task.cond_var = memnew(ConditionVariable); - } - do { - load_task.cond_var->wait(p_thread_load_lock); - DEV_ASSERT(thread_load_tasks.has(p_load_token.local_path) && p_load_token.get_reference_count()); - } while (load_task.cond_var); + DEV_ASSERT(load_task.status == THREAD_LOAD_FAILED || load_task.status == THREAD_LOAD_LOADED); + } else if (load_task.need_wait) { + // Loading thread is main or user thread. + if (!load_task.cond_var) { + load_task.cond_var = memnew(ConditionVariable); } - } else { - if (loader_is_wtp) { - thread_load_mutex.lock(); + load_task.awaiters_count++; + do { + load_task.cond_var->wait(p_thread_load_lock); + DEV_ASSERT(thread_load_tasks.has(p_load_token.local_path) && p_load_token.get_reference_count()); + } while (load_task.need_wait); + load_task.awaiters_count--; + if (load_task.awaiters_count == 0) { + memdelete(load_task.cond_var); + load_task.cond_var = nullptr; } + + DEV_ASSERT(load_task.status == THREAD_LOAD_FAILED || load_task.status == THREAD_LOAD_LOADED); } } @@ -755,22 +821,51 @@ Ref ResourceLoader::_load_complete_inner(LoadToken &p_load_token, Erro load_task.error = FAILED; } - Ref 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 = 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 = 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).bind(1)); + done.wait(); + } } } - return resource; } + + p_thread_load_lock.temp_relock(); + + return resource; } bool ResourceLoader::_ensure_load_progress() { @@ -784,6 +879,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 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()); // It makes no sense to use this from nesting level 0. const String &local_path = _validate_local_path(p_path); @@ -1060,36 +1199,39 @@ String ResourceLoader::_path_remap(const String &p_path, bool *r_translation_rem new_path = path_remaps[new_path]; } else { // Try file remap. - Error err; - Ref f = FileAccess::open(new_path + ".remap", FileAccess::READ, &err); - if (f.is_valid()) { - VariantParser::StreamFile stream; - stream.f = f; - - String assign; - Variant value; - VariantParser::Tag next_tag; - - int lines = 0; - String error_text; - while (true) { - assign = Variant(); - next_tag.fields.clear(); - next_tag.name = String(); - - err = VariantParser::parse_tag_assign_eof(&stream, lines, error_text, next_tag, assign, value, nullptr, true); - if (err == ERR_FILE_EOF) { - break; - } else if (err != OK) { - ERR_PRINT("Parse error: " + p_path + ".remap:" + itos(lines) + " error: " + error_text + "."); - break; - } + // Usually, there's no remap file and FileAccess::exists() is faster than FileAccess::open(). + if (FileAccess::exists(new_path + ".remap")) { + Error err; + Ref f = FileAccess::open(new_path + ".remap", FileAccess::READ, &err); + if (f.is_valid()) { + VariantParser::StreamFile stream; + stream.f = f; + + String assign; + Variant value; + VariantParser::Tag next_tag; + + int lines = 0; + String error_text; + while (true) { + assign = Variant(); + next_tag.fields.clear(); + next_tag.name = String(); + + err = VariantParser::parse_tag_assign_eof(&stream, lines, error_text, next_tag, assign, value, nullptr, true); + if (err == ERR_FILE_EOF) { + break; + } else if (err != OK) { + ERR_PRINT("Parse error: " + p_path + ".remap:" + itos(lines) + " error: " + error_text + "."); + break; + } - if (assign == "path") { - new_path = value; - break; - } else if (next_tag.name != "remap") { - break; + if (assign == "path") { + new_path = value; + break; + } else if (next_tag.name != "remap") { + break; + } } } } @@ -1111,17 +1253,17 @@ String ResourceLoader::path_remap(const String &p_path) { } void ResourceLoader::reload_translation_remaps() { - ResourceCache::lock.lock(); - List to_reload; - SelfList *E = remapped_list.first(); - while (E) { - to_reload.push_back(E->self()); - E = E->next(); - } + { + MutexLock lock(ResourceCache::lock); + SelfList *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()) { @@ -1161,7 +1303,7 @@ void ResourceLoader::clear_translation_remaps() { void ResourceLoader::clear_thread_load_tasks() { // Bring the thing down as quickly as possible without causing deadlocks or leaks. - thread_load_mutex.lock(); + MutexLock thread_load_lock(thread_load_mutex); cleaning_tasks = true; while (true) { @@ -1169,11 +1311,10 @@ void ResourceLoader::clear_thread_load_tasks() { if (thread_load_tasks.size()) { for (KeyValue &E : thread_load_tasks) { if (E.value.status == THREAD_LOAD_IN_PROGRESS) { - if (E.value.cond_var) { + if (E.value.cond_var && E.value.need_wait) { E.value.cond_var->notify_all(); - memdelete(E.value.cond_var); - E.value.cond_var = nullptr; } + E.value.need_wait = false; none_running = false; } } @@ -1181,21 +1322,23 @@ void ResourceLoader::clear_thread_load_tasks() { if (none_running) { break; } - thread_load_mutex.unlock(); + thread_load_lock.temp_unlock(); OS::get_singleton()->delay_usec(1000); - thread_load_mutex.lock(); + thread_load_lock.temp_relock(); } while (user_load_tokens.begin()) { - // User load tokens remove themselves from the map on destruction. - memdelete(user_load_tokens.begin()->value); + LoadToken *user_token = user_load_tokens.begin()->value; + user_load_tokens.remove(user_load_tokens.begin()); + DEV_ASSERT(user_token->user_rc > 0 && !user_token->user_path.is_empty()); + user_token->user_path.clear(); + user_token->user_rc = 0; + user_token->unreference(); } - user_load_tokens.clear(); thread_load_tasks.clear(); cleaning_tasks = false; - thread_load_mutex.unlock(); } void ResourceLoader::load_path_remaps() { @@ -1308,12 +1451,16 @@ bool ResourceLoader::abort_on_missing_resource = true; bool ResourceLoader::timestamp_on_load = false; thread_local int ResourceLoader::load_nesting = 0; -thread_local WorkerThreadPool::TaskID ResourceLoader::caller_task_id = 0; -thread_local Vector *ResourceLoader::load_paths_stack = nullptr; +thread_local Vector ResourceLoader::load_paths_stack; thread_local HashMap>> ResourceLoader::res_ref_overrides; +thread_local ResourceLoader::ThreadLoadTask *ResourceLoader::curr_load_task = nullptr; + +SafeBinaryMutex &_get_res_loader_mutex() { + return ResourceLoader::thread_load_mutex; +} template <> -thread_local uint32_t SafeBinaryMutex::count = 0; +thread_local SafeBinaryMutex::TLSData SafeBinaryMutex::tls_data(_get_res_loader_mutex()); SafeBinaryMutex ResourceLoader::thread_load_mutex; HashMap ResourceLoader::thread_load_tasks; bool ResourceLoader::cleaning_tasks = false; diff --git a/core/io/resource_loader.h b/core/io/resource_loader.h index 5f1831f0d988..caaf9f8f45dd 100644 --- a/core/io/resource_loader.h +++ b/core/io/resource_loader.h @@ -100,10 +100,14 @@ typedef Error (*ResourceLoaderImport)(const String &p_path); typedef void (*ResourceLoadedCallback)(Ref p_resource, const String &p_path); class ResourceLoader { + friend class LoadToken; + enum { MAX_LOADERS = 64 }; + struct ThreadLoadTask; + public: enum ThreadLoadStatus { THREAD_LOAD_INVALID_RESOURCE, @@ -121,7 +125,8 @@ class ResourceLoader { struct LoadToken : public RefCounted { String local_path; String user_path; - Ref res_if_unregistered; + uint32_t user_rc = 0; // Having user RC implies regular RC incremented in one, until the user RC reaches zero. + ThreadLoadTask *task_if_unregistered = nullptr; void clear(); @@ -130,10 +135,13 @@ class ResourceLoader { static const int BINARY_MUTEX_TAG = 1; - static Ref _load_start(const String &p_path, const String &p_type_hint, LoadThreadMode p_thread_mode, ResourceFormatLoader::CacheMode p_cache_mode); + static Ref _load_start(const String &p_path, const String &p_type_hint, LoadThreadMode p_thread_mode, ResourceFormatLoader::CacheMode p_cache_mode, bool p_for_user = false); static Ref _load_complete(LoadToken &p_load_token, Error *r_error); private: + static LoadToken *_load_threaded_request_reuse_user_token(const String &p_path); + static void _load_threaded_request_setup_user_token(LoadToken *p_token, const String &p_path); + static Ref _load_complete_inner(LoadToken &p_load_token, Error *r_error, MutexLock> &p_thread_load_lock); static Ref loader[MAX_LOADERS]; @@ -167,9 +175,10 @@ class ResourceLoader { Thread::ID thread_id = 0; // Used if running on an user thread (e.g., simple non-threaded load). bool awaited = false; // If it's in the pool, this helps not awaiting from more than one dependent thread. ConditionVariable *cond_var = nullptr; // In not in the worker pool or already awaiting, this is used as a secondary awaiting mechanism. + uint32_t awaiters_count = 0; + bool need_wait = true; LoadToken *load_token = nullptr; String local_path; - String remapped_path; String type_hint; float progress = 0.0f; float max_reported_progress = 0.0f; @@ -178,18 +187,27 @@ class ResourceLoader { ResourceFormatLoader::CacheMode cache_mode = ResourceFormatLoader::CACHE_MODE_REUSE; Error error = OK; Ref resource; - bool xl_remapped = false; bool use_sub_threads = false; HashSet sub_tasks; + + struct ResourceChangedConnection { + Resource *source = nullptr; + Callable callable; + uint32_t flags = 0; + }; + LocalVector resource_changed_connections; }; - static void _thread_load_function(void *p_userdata); + static void _run_load_task(void *p_userdata); static thread_local int load_nesting; - static thread_local WorkerThreadPool::TaskID caller_task_id; static thread_local HashMap>> res_ref_overrides; // Outermost key is nesting level. - static thread_local Vector *load_paths_stack; // A pointer to avoid broken TLS implementations from double-running the destructor. + static thread_local Vector load_paths_stack; + static thread_local ThreadLoadTask *curr_load_task; + static SafeBinaryMutex thread_load_mutex; + friend SafeBinaryMutex &_get_res_loader_mutex(); + static HashMap thread_load_tasks; static bool cleaning_tasks; @@ -206,6 +224,10 @@ class ResourceLoader { 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 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/io/stream_peer_tls.cpp b/core/io/stream_peer_tls.cpp index 69877974e685..f04e217a266b 100644 --- a/core/io/stream_peer_tls.cpp +++ b/core/io/stream_peer_tls.cpp @@ -32,11 +32,11 @@ #include "core/config/engine.h" -StreamPeerTLS *(*StreamPeerTLS::_create)() = nullptr; +StreamPeerTLS *(*StreamPeerTLS::_create)(bool p_notify_postinitialize) = nullptr; -StreamPeerTLS *StreamPeerTLS::create() { +StreamPeerTLS *StreamPeerTLS::create(bool p_notify_postinitialize) { if (_create) { - return _create(); + return _create(p_notify_postinitialize); } return nullptr; } diff --git a/core/io/stream_peer_tls.h b/core/io/stream_peer_tls.h index 5894abb7a465..3e03e25a2d42 100644 --- a/core/io/stream_peer_tls.h +++ b/core/io/stream_peer_tls.h @@ -38,7 +38,7 @@ class StreamPeerTLS : public StreamPeer { GDCLASS(StreamPeerTLS, StreamPeer); protected: - static StreamPeerTLS *(*_create)(); + static StreamPeerTLS *(*_create)(bool p_notify_postinitialize); static void _bind_methods(); public: @@ -58,7 +58,7 @@ class StreamPeerTLS : public StreamPeer { virtual void disconnect_from_stream() = 0; - static StreamPeerTLS *create(); + static StreamPeerTLS *create(bool p_notify_postinitialize = true); static bool is_available(); diff --git a/core/math/SCsub b/core/math/SCsub index c8fdac207ec7..6ea3ab6b125e 100644 --- a/core/math/SCsub +++ b/core/math/SCsub @@ -1,4 +1,5 @@ #!/usr/bin/env python +from misc.utility.scons_hints import * Import("env") diff --git a/core/math/a_star.cpp b/core/math/a_star.cpp index 449760494735..c85201a97393 100644 --- a/core/math/a_star.cpp +++ b/core/math/a_star.cpp @@ -319,11 +319,11 @@ Vector3 AStar3D::get_closest_position_in_segment(const Vector3 &p_point) const { return closest_point; } -bool AStar3D::_solve(Point *begin_point, Point *end_point) { +bool AStar3D::_solve(Point *begin_point, Point *end_point, bool p_allow_partial_path) { last_closest_point = nullptr; pass++; - if (!end_point->enabled) { + if (!end_point->enabled && !p_allow_partial_path) { return false; } @@ -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) { @@ -443,7 +443,7 @@ Vector AStar3D::get_point_path(int64_t p_from_id, int64_t p_to_id, bool Point *begin_point = a; Point *end_point = b; - bool found_route = _solve(begin_point, end_point); + bool found_route = _solve(begin_point, end_point, p_allow_partial_path); if (!found_route) { if (!p_allow_partial_path || last_closest_point == nullptr) { return Vector(); @@ -497,7 +497,7 @@ Vector AStar3D::get_id_path(int64_t p_from_id, int64_t p_to_id, bool p_ Point *begin_point = a; Point *end_point = b; - bool found_route = _solve(begin_point, end_point); + bool found_route = _solve(begin_point, end_point, p_allow_partial_path); if (!found_route) { if (!p_allow_partial_path || last_closest_point == nullptr) { return Vector(); @@ -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) { @@ -726,7 +726,7 @@ Vector AStar2D::get_point_path(int64_t p_from_id, int64_t p_to_id, bool AStar3D::Point *begin_point = a; AStar3D::Point *end_point = b; - bool found_route = _solve(begin_point, end_point); + bool found_route = _solve(begin_point, end_point, p_allow_partial_path); if (!found_route) { if (!p_allow_partial_path || astar.last_closest_point == nullptr) { return Vector(); @@ -780,7 +780,7 @@ Vector AStar2D::get_id_path(int64_t p_from_id, int64_t p_to_id, bool p_ AStar3D::Point *begin_point = a; AStar3D::Point *end_point = b; - bool found_route = _solve(begin_point, end_point); + bool found_route = _solve(begin_point, end_point, p_allow_partial_path); if (!found_route) { if (!p_allow_partial_path || astar.last_closest_point == nullptr) { return Vector(); @@ -816,11 +816,11 @@ Vector AStar2D::get_id_path(int64_t p_from_id, int64_t p_to_id, bool p_ return path; } -bool AStar2D::_solve(AStar3D::Point *begin_point, AStar3D::Point *end_point) { +bool AStar2D::_solve(AStar3D::Point *begin_point, AStar3D::Point *end_point, bool p_allow_partial_path) { astar.last_closest_point = nullptr; astar.pass++; - if (!end_point->enabled) { + if (!end_point->enabled && !p_allow_partial_path) { return false; } @@ -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 8e054c478989..cbaafc101836 100644 --- a/core/math/a_star.h +++ b/core/math/a_star.h @@ -115,12 +115,12 @@ class AStar3D : public RefCounted { HashSet segments; Point *last_closest_point = nullptr; - bool _solve(Point *begin_point, Point *end_point); + bool _solve(Point *begin_point, Point *end_point, bool p_allow_partial_path); 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) @@ -171,12 +171,12 @@ class AStar2D : public RefCounted { GDCLASS(AStar2D, RefCounted); AStar3D astar; - bool _solve(AStar3D::Point *begin_point, AStar3D::Point *end_point); + bool _solve(AStar3D::Point *begin_point, AStar3D::Point *end_point, bool p_allow_partial_path); 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 984bb1c9c18b..7e0e982c6244 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 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 &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,25 +477,25 @@ void AStarGrid2D::_get_nbors(Point *p_point, LocalVector &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); } } -bool AStarGrid2D::_solve(Point *p_begin_point, Point *p_end_point) { +bool AStarGrid2D::_solve(Point *p_begin_point, Point *p_end_point, bool p_allow_partial_path) { last_closest_point = nullptr; pass++; - if (p_end_point->solid) { + if (_get_solid_unchecked(p_end_point->id) && !p_allow_partial_path) { 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 AStarGrid2D::get_point_data_in_region(const Rect2i &p_region) const { + ERR_FAIL_COND_V_MSG(dirty, TypedArray(), "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 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 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(), "Grid is not initialized. Call the update method."); ERR_FAIL_COND_V_MSG(!is_in_boundsv(p_from_id), Vector(), vformat("Can't get id path. Point %s out of bounds %s.", p_from_id, region)); @@ -579,7 +647,7 @@ Vector AStarGrid2D::get_point_path(const Vector2i &p_from_id, const Vec Point *begin_point = a; Point *end_point = b; - bool found_route = _solve(begin_point, end_point); + bool found_route = _solve(begin_point, end_point, p_allow_partial_path); if (!found_route) { if (!p_allow_partial_path || last_closest_point == nullptr) { return Vector(); @@ -632,7 +700,7 @@ TypedArray AStarGrid2D::get_id_path(const Vector2i &p_from_id, const V Point *begin_point = a; Point *end_point = b; - bool found_route = _solve(begin_point, end_point); + bool found_route = _solve(begin_point, end_point, p_allow_partial_path); if (!found_route) { if (!p_allow_partial_path || last_closest_point == nullptr) { return TypedArray(); @@ -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 1a9f6dcc11fa..0536b8109b73 100644 --- a/core/math/a_star_grid_2d.h +++ b/core/math/a_star_grid_2d.h @@ -78,7 +78,6 @@ class AStarGrid2D : public RefCounted { struct Point { Vector2i id; - bool solid = false; Vector2 pos; real_t weight_scale = 1.0; @@ -111,6 +110,7 @@ class AStarGrid2D : public RefCounted { } }; + LocalVector solid_mask; LocalVector> points; Point *end = nullptr; Point *last_closest_point = nullptr; @@ -118,11 +118,12 @@ class AStarGrid2D : public RefCounted { 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 @@ class AStarGrid2D : public RefCounted { 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 @@ class AStarGrid2D : public RefCounted { void _get_nbors(Point *p_point, LocalVector &r_nbors); Point *_jump(Point *p_from, Point *p_to); - bool _solve(Point *p_begin_point, Point *p_end_point); + bool _solve(Point *p_begin_point, Point *p_end_point, bool p_allow_partial_path); + Point *_forced_successor(int32_t p_x, int32_t p_y, int32_t p_dx, int32_t p_dy, bool p_inclusive = false); 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 @@ class AStarGrid2D : public RefCounted { void clear(); Vector2 get_point_position(const Vector2i &p_id) const; + TypedArray get_point_data_in_region(const Rect2i &p_region) const; Vector get_point_path(const Vector2i &p_from, const Vector2i &p_to, bool p_allow_partial_path = false); TypedArray get_id_path(const Vector2i &p_from, const Vector2i &p_to, bool p_allow_partial_path = false); }; diff --git a/core/math/aabb.h b/core/math/aabb.h index cb358ca7efc2..7a5581b5d453 100644 --- a/core/math/aabb.h +++ b/core/math/aabb.h @@ -85,7 +85,7 @@ struct [[nodiscard]] AABB { bool intersects_plane(const Plane &p_plane) const; _FORCE_INLINE_ bool has_point(const Vector3 &p_point) const; - _FORCE_INLINE_ Vector3 get_support(const Vector3 &p_normal) const; + _FORCE_INLINE_ Vector3 get_support(const Vector3 &p_direction) const; Vector3 get_longest_axis() const; int get_longest_axis_index() const; @@ -212,15 +212,18 @@ inline bool AABB::encloses(const AABB &p_aabb) const { (src_max.z >= dst_max.z)); } -Vector3 AABB::get_support(const Vector3 &p_normal) const { - Vector3 half_extents = size * 0.5f; - Vector3 ofs = position + half_extents; - - return Vector3( - (p_normal.x > 0) ? half_extents.x : -half_extents.x, - (p_normal.y > 0) ? half_extents.y : -half_extents.y, - (p_normal.z > 0) ? half_extents.z : -half_extents.z) + - ofs; +Vector3 AABB::get_support(const Vector3 &p_direction) const { + Vector3 support = position; + if (p_direction.x > 0.0f) { + support.x += size.x; + } + if (p_direction.y > 0.0f) { + support.y += size.y; + } + if (p_direction.z > 0.0f) { + support.z += size.z; + } + return support; } Vector3 AABB::get_endpoint(int p_point) const { diff --git a/core/math/basis.h b/core/math/basis.h index 5c1a5fbdda71..236d6661033e 100644 --- a/core/math/basis.h +++ b/core/math/basis.h @@ -41,11 +41,11 @@ struct [[nodiscard]] Basis { Vector3(0, 0, 1) }; - _FORCE_INLINE_ const Vector3 &operator[](int p_axis) const { - return rows[p_axis]; + _FORCE_INLINE_ const Vector3 &operator[](int p_row) const { + return rows[p_row]; } - _FORCE_INLINE_ Vector3 &operator[](int p_axis) { - return rows[p_axis]; + _FORCE_INLINE_ Vector3 &operator[](int p_row) { + return rows[p_row]; } void invert(); diff --git a/core/math/color.h b/core/math/color.h index e17b8c9fd703..70fad78acbdd 100644 --- a/core/math/color.h +++ b/core/math/color.h @@ -129,33 +129,46 @@ struct [[nodiscard]] Color { } _FORCE_INLINE_ uint32_t to_rgbe9995() const { - const float pow2to9 = 512.0f; - const float B = 15.0f; - const float N = 9.0f; - - float sharedexp = 65408.000f; // Result of: ((pow2to9 - 1.0f) / pow2to9) * powf(2.0f, 31.0f - 15.0f) - - float cRed = MAX(0.0f, MIN(sharedexp, r)); - float cGreen = MAX(0.0f, MIN(sharedexp, g)); - float cBlue = MAX(0.0f, MIN(sharedexp, b)); - - float cMax = MAX(cRed, MAX(cGreen, cBlue)); - - float expp = MAX(-B - 1.0f, floor(Math::log(cMax) / (real_t)Math_LN2)) + 1.0f + B; - - float sMax = (float)floor((cMax / Math::pow(2.0f, expp - B - N)) + 0.5f); - - float exps = expp + 1.0f; - - if (0.0f <= sMax && sMax < pow2to9) { - exps = expp; - } - - float sRed = Math::floor((cRed / pow(2.0f, exps - B - N)) + 0.5f); - float sGreen = Math::floor((cGreen / pow(2.0f, exps - B - N)) + 0.5f); - float sBlue = Math::floor((cBlue / pow(2.0f, exps - B - N)) + 0.5f); - - return (uint32_t(Math::fast_ftoi(sRed)) & 0x1FF) | ((uint32_t(Math::fast_ftoi(sGreen)) & 0x1FF) << 9) | ((uint32_t(Math::fast_ftoi(sBlue)) & 0x1FF) << 18) | ((uint32_t(Math::fast_ftoi(exps)) & 0x1F) << 27); + // https://github.com/microsoft/DirectX-Graphics-Samples/blob/v10.0.19041.0/MiniEngine/Core/Color.cpp + static const float kMaxVal = float(0x1FF << 7); + static const float kMinVal = float(1.f / (1 << 16)); + + // Clamp RGB to [0, 1.FF*2^16] + const float _r = CLAMP(r, 0.0f, kMaxVal); + const float _g = CLAMP(g, 0.0f, kMaxVal); + const float _b = CLAMP(b, 0.0f, kMaxVal); + + // Compute the maximum channel, no less than 1.0*2^-15 + const float MaxChannel = MAX(MAX(_r, _g), MAX(_b, kMinVal)); + + // Take the exponent of the maximum channel (rounding up the 9th bit) and + // add 15 to it. When added to the channels, it causes the implicit '1.0' + // bit and the first 8 mantissa bits to be shifted down to the low 9 bits + // of the mantissa, rounding the truncated bits. + union { + float f; + int32_t i; + } R, G, B, E; + + E.f = MaxChannel; + E.i += 0x07804000; // Add 15 to the exponent and 0x4000 to the mantissa + E.i &= 0x7F800000; // Zero the mantissa + + // This shifts the 9-bit values we need into the lowest bits, rounding as + // needed. Note that if the channel has a smaller exponent than the max + // channel, it will shift even more. This is intentional. + R.f = _r + E.f; + G.f = _g + E.f; + B.f = _b + E.f; + + // Convert the Bias to the correct exponent in the upper 5 bits. + E.i <<= 4; + E.i += 0x10000000; + + // Combine the fields. RGB floats have unwanted data in the upper 9 + // bits. Only red needs to mask them off because green and blue shift + // it out to the left. + return E.i | (B.i << 18) | (G.i << 9) | (R.i & 511); } _FORCE_INLINE_ Color blend(const Color &p_over) const { diff --git a/core/math/convex_hull.cpp b/core/math/convex_hull.cpp index 80662c1b07b7..620a7541e45a 100644 --- a/core/math/convex_hull.cpp +++ b/core/math/convex_hull.cpp @@ -204,7 +204,7 @@ class ConvexHullInternal { static Int128 mul(uint64_t a, uint64_t b); Int128 operator-() const { - return Int128((uint64_t) - (int64_t)low, ~high + (low == 0)); + return Int128(uint64_t(-int64_t(low)), ~high + (low == 0)); } Int128 operator+(const Int128 &b) const { diff --git a/core/math/expression.cpp b/core/math/expression.cpp index 636c2c16bf36..0692ece1e6fa 100644 --- a/core/math/expression.cpp +++ b/core/math/expression.cpp @@ -30,12 +30,7 @@ #include "expression.h" -#include "core/io/marshalls.h" -#include "core/math/math_funcs.h" #include "core/object/class_db.h" -#include "core/object/ref_counted.h" -#include "core/os/os.h" -#include "core/variant/variant_parser.h" Error Expression::_get_token(Token &r_token) { while (true) { @@ -392,7 +387,6 @@ Error Expression::_get_token(Token &r_token) { if (is_digit(c)) { } else if (c == 'e') { reading = READING_EXP; - } else { reading = READING_DONE; } @@ -419,7 +413,9 @@ Error Expression::_get_token(Token &r_token) { is_first_char = false; } - str_ofs--; + if (c != 0) { + str_ofs--; + } r_token.type = TK_CONSTANT; diff --git a/core/math/math_funcs.h b/core/math/math_funcs.h index 3060f31970c3..1afc5f4bbbdf 100644 --- a/core/math/math_funcs.h +++ b/core/math/math_funcs.h @@ -105,6 +105,9 @@ class Math { 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); } @@ -447,14 +450,22 @@ class Math { static _ALWAYS_INLINE_ double smoothstep(double p_from, double p_to, double p_s) { if (is_equal_approx(p_from, p_to)) { - return p_from; + if (likely(p_from <= p_to)) { + return p_s <= p_from ? 0.0 : 1.0; + } else { + return p_s <= p_to ? 1.0 : 0.0; + } } double s = CLAMP((p_s - p_from) / (p_to - p_from), 0.0, 1.0); return s * s * (3.0 - 2.0 * s); } static _ALWAYS_INLINE_ float smoothstep(float p_from, float p_to, float p_s) { if (is_equal_approx(p_from, p_to)) { - return p_from; + if (likely(p_from <= p_to)) { + return p_s <= p_from ? 0.0f : 1.0f; + } else { + return p_s <= p_to ? 1.0f : 0.0f; + } } float s = CLAMP((p_s - p_from) / (p_to - p_from), 0.0f, 1.0f); return s * s * (3.0f - 2.0f * s); diff --git a/core/math/random_pcg.cpp b/core/math/random_pcg.cpp index 55787a0b57e9..c286a6042182 100644 --- a/core/math/random_pcg.cpp +++ b/core/math/random_pcg.cpp @@ -60,6 +60,11 @@ int64_t RandomPCG::rand_weighted(const Vector &p_weights) { } } + for (int64_t i = weights_size - 1; i >= 0; --i) { + if (weights[i] > 0) { + return i; + } + } return -1; } diff --git a/core/math/rect2.h b/core/math/rect2.h index 9cb341b6894a..817923c13488 100644 --- a/core/math/rect2.h +++ b/core/math/rect2.h @@ -285,13 +285,15 @@ struct [[nodiscard]] Rect2 { return Rect2(position.round(), size.round()); } - Vector2 get_support(const Vector2 &p_normal) const { - Vector2 half_extents = size * 0.5f; - Vector2 ofs = position + half_extents; - return Vector2( - (p_normal.x > 0) ? -half_extents.x : half_extents.x, - (p_normal.y > 0) ? -half_extents.y : half_extents.y) + - ofs; + Vector2 get_support(const Vector2 &p_direction) const { + Vector2 support = position; + if (p_direction.x > 0.0f) { + support.x += size.x; + } + if (p_direction.y > 0.0f) { + support.y += size.y; + } + return support; } _FORCE_INLINE_ bool intersects_filled_polygon(const Vector2 *p_points, int p_point_count) const { diff --git a/core/math/transform_2d.h b/core/math/transform_2d.h index 476577508f2f..1ee7d3d84f78 100644 --- a/core/math/transform_2d.h +++ b/core/math/transform_2d.h @@ -39,16 +39,19 @@ class String; struct [[nodiscard]] Transform2D { - // Warning #1: basis of Transform2D is stored differently from Basis. In terms of columns array, the basis matrix looks like "on paper": + // WARNING: The basis of Transform2D is stored differently from Basis. + // In terms of columns array, the basis matrix looks like "on paper": // M = (columns[0][0] columns[1][0]) // (columns[0][1] columns[1][1]) - // This is such that the columns, which can be interpreted as basis vectors of the coordinate system "painted" on the object, can be accessed as columns[i]. - // Note that this is the opposite of the indices in mathematical texts, meaning: $M_{12}$ in a math book corresponds to columns[1][0] here. + // This is such that the columns, which can be interpreted as basis vectors + // of the coordinate system "painted" on the object, can be accessed as columns[i]. + // NOTE: This is the opposite of the indices in mathematical texts, + // meaning: $M_{12}$ in a math book corresponds to columns[1][0] here. // This requires additional care when working with explicit indices. // See https://en.wikipedia.org/wiki/Row-_and_column-major_order for further reading. - // Warning #2: 2D be aware that unlike 3D code, 2D code uses a left-handed coordinate system: Y-axis points down, - // and angle is measure from +X to +Y in a clockwise-fashion. + // WARNING: Be aware that unlike 3D code, 2D code uses a left-handed coordinate system: + // Y-axis points down, and angle is measure from +X to +Y in a clockwise-fashion. Vector2 columns[3]; diff --git a/core/math/transform_interpolator.cpp b/core/math/transform_interpolator.cpp index 6a564b0ca7f6..1cd35b3d1aee 100644 --- a/core/math/transform_interpolator.cpp +++ b/core/math/transform_interpolator.cpp @@ -31,6 +31,7 @@ #include "transform_interpolator.h" #include "core/math/transform_2d.h" +#include "core/math/transform_3d.h" void TransformInterpolator::interpolate_transform_2d(const Transform2D &p_prev, const Transform2D &p_curr, Transform2D &r_result, real_t p_fraction) { // Special case for physics interpolation, if flipping, don't interpolate basis. @@ -44,3 +45,340 @@ void TransformInterpolator::interpolate_transform_2d(const Transform2D &p_prev, r_result = p_prev.interpolate_with(p_curr, p_fraction); } + +void TransformInterpolator::interpolate_transform_3d(const Transform3D &p_prev, const Transform3D &p_curr, Transform3D &r_result, real_t p_fraction) { + r_result.origin = p_prev.origin + ((p_curr.origin - p_prev.origin) * p_fraction); + interpolate_basis(p_prev.basis, p_curr.basis, r_result.basis, p_fraction); +} + +void TransformInterpolator::interpolate_basis(const Basis &p_prev, const Basis &p_curr, Basis &r_result, real_t p_fraction) { + Method method = find_method(p_prev, p_curr); + interpolate_basis_via_method(p_prev, p_curr, r_result, p_fraction, method); +} + +void TransformInterpolator::interpolate_transform_3d_via_method(const Transform3D &p_prev, const Transform3D &p_curr, Transform3D &r_result, real_t p_fraction, Method p_method) { + r_result.origin = p_prev.origin + ((p_curr.origin - p_prev.origin) * p_fraction); + interpolate_basis_via_method(p_prev.basis, p_curr.basis, r_result.basis, p_fraction, p_method); +} + +void TransformInterpolator::interpolate_basis_via_method(const Basis &p_prev, const Basis &p_curr, Basis &r_result, real_t p_fraction, Method p_method) { + switch (p_method) { + default: { + interpolate_basis_linear(p_prev, p_curr, r_result, p_fraction); + } break; + case INTERP_SLERP: { + r_result = _basis_slerp_unchecked(p_prev, p_curr, p_fraction); + } break; + case INTERP_SCALED_SLERP: { + interpolate_basis_scaled_slerp(p_prev, p_curr, r_result, p_fraction); + } break; + } +} + +Quaternion TransformInterpolator::_basis_to_quat_unchecked(const Basis &p_basis) { + Basis m = p_basis; + real_t trace = m.rows[0][0] + m.rows[1][1] + m.rows[2][2]; + real_t temp[4]; + + if (trace > 0.0) { + real_t s = Math::sqrt(trace + 1.0f); + temp[3] = (s * 0.5f); + s = 0.5f / s; + + temp[0] = ((m.rows[2][1] - m.rows[1][2]) * s); + temp[1] = ((m.rows[0][2] - m.rows[2][0]) * s); + temp[2] = ((m.rows[1][0] - m.rows[0][1]) * s); + } else { + int i = m.rows[0][0] < m.rows[1][1] + ? (m.rows[1][1] < m.rows[2][2] ? 2 : 1) + : (m.rows[0][0] < m.rows[2][2] ? 2 : 0); + int j = (i + 1) % 3; + int k = (i + 2) % 3; + + real_t s = Math::sqrt(m.rows[i][i] - m.rows[j][j] - m.rows[k][k] + 1.0f); + temp[i] = s * 0.5f; + s = 0.5f / s; + + temp[3] = (m.rows[k][j] - m.rows[j][k]) * s; + temp[j] = (m.rows[j][i] + m.rows[i][j]) * s; + temp[k] = (m.rows[k][i] + m.rows[i][k]) * s; + } + + return Quaternion(temp[0], temp[1], temp[2], temp[3]); +} + +Quaternion TransformInterpolator::_quat_slerp_unchecked(const Quaternion &p_from, const Quaternion &p_to, real_t p_fraction) { + Quaternion to1; + real_t omega, cosom, sinom, scale0, scale1; + + // Calculate cosine. + cosom = p_from.dot(p_to); + + // Adjust signs (if necessary) + if (cosom < 0.0f) { + cosom = -cosom; + to1.x = -p_to.x; + to1.y = -p_to.y; + to1.z = -p_to.z; + to1.w = -p_to.w; + } else { + to1.x = p_to.x; + to1.y = p_to.y; + to1.z = p_to.z; + to1.w = p_to.w; + } + + // Calculate coefficients. + + // This check could possibly be removed as we dealt with this + // case in the find_method() function, but is left for safety, it probably + // isn't a bottleneck. + if ((1.0f - cosom) > (real_t)CMP_EPSILON) { + // standard case (slerp) + omega = Math::acos(cosom); + sinom = Math::sin(omega); + scale0 = Math::sin((1.0f - p_fraction) * omega) / sinom; + scale1 = Math::sin(p_fraction * omega) / sinom; + } else { + // "from" and "to" quaternions are very close + // ... so we can do a linear interpolation + scale0 = 1.0f - p_fraction; + scale1 = p_fraction; + } + // Calculate final values. + return Quaternion( + scale0 * p_from.x + scale1 * to1.x, + scale0 * p_from.y + scale1 * to1.y, + scale0 * p_from.z + scale1 * to1.z, + scale0 * p_from.w + scale1 * to1.w); +} + +Basis TransformInterpolator::_basis_slerp_unchecked(Basis p_from, Basis p_to, real_t p_fraction) { + Quaternion from = _basis_to_quat_unchecked(p_from); + Quaternion to = _basis_to_quat_unchecked(p_to); + + Basis b(_quat_slerp_unchecked(from, to, p_fraction)); + return b; +} + +void TransformInterpolator::interpolate_basis_scaled_slerp(Basis p_prev, Basis p_curr, Basis &r_result, real_t p_fraction) { + // Normalize both and find lengths. + Vector3 lengths_prev = _basis_orthonormalize(p_prev); + Vector3 lengths_curr = _basis_orthonormalize(p_curr); + + r_result = _basis_slerp_unchecked(p_prev, p_curr, p_fraction); + + // Now the result is unit length basis, we need to scale. + Vector3 lengths_lerped = lengths_prev + ((lengths_curr - lengths_prev) * p_fraction); + + // Keep a note that the column / row order of the basis is weird, + // so keep an eye for bugs with this. + r_result[0] *= lengths_lerped; + r_result[1] *= lengths_lerped; + r_result[2] *= lengths_lerped; +} + +void TransformInterpolator::interpolate_basis_linear(const Basis &p_prev, const Basis &p_curr, Basis &r_result, real_t p_fraction) { + // Interpolate basis. + r_result = p_prev.lerp(p_curr, p_fraction); + + // It turns out we need to guard against zero scale basis. + // This is kind of silly, as we should probably fix the bugs elsewhere in Godot that can't deal with + // zero scale, but until that time... + for (int n = 0; n < 3; n++) { + Vector3 &axis = r_result[n]; + + // Not ok, this could cause errors due to bugs elsewhere, + // so we will bodge set this to a small value. + const real_t smallest = 0.0001f; + const real_t smallest_squared = smallest * smallest; + if (axis.length_squared() < smallest_squared) { + // Setting a different component to the smallest + // helps prevent the situation where all the axes are pointing in the same direction, + // which could be a problem for e.g. cross products... + axis[n] = smallest; + } + } +} + +// Returns length. +real_t TransformInterpolator::_vec3_normalize(Vector3 &p_vec) { + real_t lengthsq = p_vec.length_squared(); + if (lengthsq == 0.0f) { + p_vec.x = p_vec.y = p_vec.z = 0.0f; + return 0.0f; + } + real_t length = Math::sqrt(lengthsq); + p_vec.x /= length; + p_vec.y /= length; + p_vec.z /= length; + return length; +} + +// Returns lengths. +Vector3 TransformInterpolator::_basis_orthonormalize(Basis &r_basis) { + // Gram-Schmidt Process. + + Vector3 x = r_basis.get_column(0); + Vector3 y = r_basis.get_column(1); + Vector3 z = r_basis.get_column(2); + + Vector3 lengths; + + lengths.x = _vec3_normalize(x); + y = (y - x * (x.dot(y))); + lengths.y = _vec3_normalize(y); + z = (z - x * (x.dot(z)) - y * (y.dot(z))); + lengths.z = _vec3_normalize(z); + + r_basis.set_column(0, x); + r_basis.set_column(1, y); + r_basis.set_column(2, z); + + return lengths; +} + +TransformInterpolator::Method TransformInterpolator::_test_basis(Basis p_basis, bool r_needed_normalize, Quaternion &r_quat) { + // Axis lengths. + Vector3 al = Vector3(p_basis.get_column(0).length_squared(), + p_basis.get_column(1).length_squared(), + p_basis.get_column(2).length_squared()); + + // Non unit scale? + if (r_needed_normalize || !_vec3_is_equal_approx(al, Vector3(1.0, 1.0, 1.0), (real_t)0.001f)) { + // If the basis is not normalized (at least approximately), it will fail the checks needed for slerp. + // So we try to detect a scaled (but not sheared) basis, which we *can* slerp by normalizing first, + // and lerping the scales separately. + + // If any of the axes are really small, it is unlikely to be a valid rotation, or is scaled too small to deal with float error. + const real_t sl_epsilon = 0.00001f; + if ((al.x < sl_epsilon) || + (al.y < sl_epsilon) || + (al.z < sl_epsilon)) { + return INTERP_LERP; + } + + // Normalize the basis. + Basis norm_basis = p_basis; + + al.x = Math::sqrt(al.x); + al.y = Math::sqrt(al.y); + al.z = Math::sqrt(al.z); + + norm_basis.set_column(0, norm_basis.get_column(0) / al.x); + norm_basis.set_column(1, norm_basis.get_column(1) / al.y); + norm_basis.set_column(2, norm_basis.get_column(2) / al.z); + + // This doesn't appear necessary, as the later checks will catch it. + // if (!_basis_is_orthogonal_any_scale(norm_basis)) { + // return INTERP_LERP; + // } + + p_basis = norm_basis; + + // Orthonormalize not necessary as normal normalization(!) works if the + // axes are orthonormal. + // p_basis.orthonormalize(); + + // If we needed to normalize one of the two bases, we will need to normalize both, + // regardless of whether the 2nd needs it, just to make sure it takes the path to return + // INTERP_SCALED_LERP on the 2nd call of _test_basis. + r_needed_normalize = true; + } + + // Apply less stringent tests than the built in slerp, the standard Godot slerp + // is too susceptible to float error to be useful. + real_t det = p_basis.determinant(); + if (!Math::is_equal_approx(det, 1, (real_t)0.01f)) { + return INTERP_LERP; + } + + if (!_basis_is_orthogonal(p_basis)) { + return INTERP_LERP; + } + + // TODO: This could possibly be less stringent too, check this. + r_quat = _basis_to_quat_unchecked(p_basis); + if (!r_quat.is_normalized()) { + return INTERP_LERP; + } + + return r_needed_normalize ? INTERP_SCALED_SLERP : INTERP_SLERP; +} + +// This check doesn't seem to be needed but is preserved in case of bugs. +bool TransformInterpolator::_basis_is_orthogonal_any_scale(const Basis &p_basis) { + Vector3 cross = p_basis.get_column(0).cross(p_basis.get_column(1)); + real_t l = _vec3_normalize(cross); + // Too small numbers, revert to lerp. + if (l < 0.001f) { + return false; + } + + const real_t epsilon = 0.9995f; + + real_t dot = cross.dot(p_basis.get_column(2)); + if (dot < epsilon) { + return false; + } + + cross = p_basis.get_column(1).cross(p_basis.get_column(2)); + l = _vec3_normalize(cross); + // Too small numbers, revert to lerp. + if (l < 0.001f) { + return false; + } + + dot = cross.dot(p_basis.get_column(0)); + if (dot < epsilon) { + return false; + } + + return true; +} + +bool TransformInterpolator::_basis_is_orthogonal(const Basis &p_basis, real_t p_epsilon) { + Basis identity; + Basis m = p_basis * p_basis.transposed(); + + // Less stringent tests than the standard Godot slerp. + if (!_vec3_is_equal_approx(m[0], identity[0], p_epsilon) || !_vec3_is_equal_approx(m[1], identity[1], p_epsilon) || !_vec3_is_equal_approx(m[2], identity[2], p_epsilon)) { + return false; + } + return true; +} + +real_t TransformInterpolator::checksum_transform_3d(const Transform3D &p_transform) { + // just a really basic checksum, this can probably be improved + real_t sum = _vec3_sum(p_transform.origin); + sum -= _vec3_sum(p_transform.basis.rows[0]); + sum += _vec3_sum(p_transform.basis.rows[1]); + sum -= _vec3_sum(p_transform.basis.rows[2]); + return sum; +} + +TransformInterpolator::Method TransformInterpolator::find_method(const Basis &p_a, const Basis &p_b) { + bool needed_normalize = false; + + Quaternion q0; + Method method = _test_basis(p_a, needed_normalize, q0); + if (method == INTERP_LERP) { + return method; + } + + Quaternion q1; + method = _test_basis(p_b, needed_normalize, q1); + if (method == INTERP_LERP) { + return method; + } + + // Are they close together? + // Apply the same test that will revert to lerp as is present in the slerp routine. + // Calculate cosine. + real_t cosom = Math::abs(q0.dot(q1)); + if ((1.0f - cosom) <= (real_t)CMP_EPSILON) { + return INTERP_LERP; + } + + return method; +} diff --git a/core/math/transform_interpolator.h b/core/math/transform_interpolator.h index a9bce2bd7fea..cc556707e4c6 100644 --- a/core/math/transform_interpolator.h +++ b/core/math/transform_interpolator.h @@ -32,15 +32,64 @@ #define TRANSFORM_INTERPOLATOR_H #include "core/math/math_defs.h" +#include "core/math/vector3.h" + +// Keep all the functions for fixed timestep interpolation together. +// There are two stages involved: +// Finding a method, for determining the interpolation method between two +// keyframes (which are physics ticks). +// And applying that pre-determined method. + +// Pre-determining the method makes sense because it is expensive and often +// several frames may occur between each physics tick, which will make it cheaper +// than performing every frame. struct Transform2D; +struct Transform3D; +struct Basis; +struct Quaternion; class TransformInterpolator { +public: + enum Method { + INTERP_LERP, + INTERP_SLERP, + INTERP_SCALED_SLERP, + }; + private: - static bool _sign(real_t p_val) { return p_val >= 0; } + _FORCE_INLINE_ static bool _sign(real_t p_val) { return p_val >= 0; } + static real_t _vec3_sum(const Vector3 &p_pt) { return p_pt.x + p_pt.y + p_pt.z; } + static real_t _vec3_normalize(Vector3 &p_vec); + _FORCE_INLINE_ static bool _vec3_is_equal_approx(const Vector3 &p_a, const Vector3 &p_b, real_t p_tolerance) { + return Math::is_equal_approx(p_a.x, p_b.x, p_tolerance) && Math::is_equal_approx(p_a.y, p_b.y, p_tolerance) && Math::is_equal_approx(p_a.z, p_b.z, p_tolerance); + } + static Vector3 _basis_orthonormalize(Basis &r_basis); + static Method _test_basis(Basis p_basis, bool r_needed_normalize, Quaternion &r_quat); + static Basis _basis_slerp_unchecked(Basis p_from, Basis p_to, real_t p_fraction); + static Quaternion _quat_slerp_unchecked(const Quaternion &p_from, const Quaternion &p_to, real_t p_fraction); + static Quaternion _basis_to_quat_unchecked(const Basis &p_basis); + static bool _basis_is_orthogonal(const Basis &p_basis, real_t p_epsilon = 0.01f); + static bool _basis_is_orthogonal_any_scale(const Basis &p_basis); + + static void interpolate_basis_linear(const Basis &p_prev, const Basis &p_curr, Basis &r_result, real_t p_fraction); + static void interpolate_basis_scaled_slerp(Basis p_prev, Basis p_curr, Basis &r_result, real_t p_fraction); public: static void interpolate_transform_2d(const Transform2D &p_prev, const Transform2D &p_curr, Transform2D &r_result, real_t p_fraction); + + // Generic functions, use when you don't know what method should be used, e.g. from GDScript. + // These will be slower. + static void interpolate_transform_3d(const Transform3D &p_prev, const Transform3D &p_curr, Transform3D &r_result, real_t p_fraction); + static void interpolate_basis(const Basis &p_prev, const Basis &p_curr, Basis &r_result, real_t p_fraction); + + // Optimized function when you know ahead of time the method. + static void interpolate_transform_3d_via_method(const Transform3D &p_prev, const Transform3D &p_curr, Transform3D &r_result, real_t p_fraction, Method p_method); + static void interpolate_basis_via_method(const Basis &p_prev, const Basis &p_curr, Basis &r_result, real_t p_fraction, Method p_method); + + static real_t checksum_transform_3d(const Transform3D &p_transform); + + static Method find_method(const Basis &p_a, const Basis &p_b); }; #endif // TRANSFORM_INTERPOLATOR_H diff --git a/core/math/vector4.h b/core/math/vector4.h index 8632f69f5791..9197e3587ab6 100644 --- a/core/math/vector4.h +++ b/core/math/vector4.h @@ -55,16 +55,16 @@ struct [[nodiscard]] Vector4 { real_t z; real_t w; }; - real_t components[4] = { 0, 0, 0, 0 }; + real_t coord[4] = { 0, 0, 0, 0 }; }; _FORCE_INLINE_ real_t &operator[](int p_axis) { DEV_ASSERT((unsigned int)p_axis < 4); - return components[p_axis]; + return coord[p_axis]; } _FORCE_INLINE_ const real_t &operator[](int p_axis) const { DEV_ASSERT((unsigned int)p_axis < 4); - return components[p_axis]; + return coord[p_axis]; } Vector4::Axis min_axis_index() const; diff --git a/core/object/SCsub b/core/object/SCsub index 7c00bb719e3e..3d0d2c14dd27 100644 --- a/core/object/SCsub +++ b/core/object/SCsub @@ -1,4 +1,5 @@ #!/usr/bin/env python +from misc.utility.scons_hints import * Import("env") diff --git a/core/object/callable_method_pointer.h b/core/object/callable_method_pointer.h index 1b29e1778ad4..86c66593bd3a 100644 --- a/core/object/callable_method_pointer.h +++ b/core/object/callable_method_pointer.h @@ -37,6 +37,8 @@ #include "core/variant/binder_common.h" #include "core/variant/callable.h" +#include + class CallableCustomMethodPointerBase : public CallableCustom { uint32_t *comp_ptr = nullptr; uint32_t comp_size; @@ -77,12 +79,13 @@ class CallableCustomMethodPointerBase : public CallableCustom { virtual uint32_t hash() const; }; -template +template class CallableCustomMethodPointer : public CallableCustomMethodPointerBase { struct Data { T *instance; uint64_t object_id; - void (T::*method)(P...); + R(T::*method) + (P...); } data; public: @@ -100,10 +103,14 @@ class CallableCustomMethodPointer : public CallableCustomMethodPointerBase { virtual void call(const Variant **p_arguments, int p_argcount, Variant &r_return_value, Callable::CallError &r_call_error) const { ERR_FAIL_NULL_MSG(ObjectDB::get_instance(ObjectID(data.object_id)), "Invalid Object id '" + uitos(data.object_id) + "', can't call method."); - call_with_variant_args(data.instance, data.method, p_arguments, p_argcount, r_call_error); + if constexpr (std::is_same::value) { + call_with_variant_args(data.instance, data.method, p_arguments, p_argcount, r_call_error); + } else { + call_with_variant_args_ret(data.instance, data.method, p_arguments, p_argcount, r_return_value, r_call_error); + } } - CallableCustomMethodPointer(T *p_instance, void (T::*p_method)(P...)) { + CallableCustomMethodPointer(T *p_instance, R (T::*p_method)(P...)) { memset(&data, 0, sizeof(Data)); // Clear beforehand, may have padding bytes. data.instance = p_instance; data.object_id = p_instance->get_instance_id(); @@ -118,7 +125,7 @@ Callable create_custom_callable_function_pointer(T *p_instance, const char *p_func_text, #endif void (T::*p_method)(P...)) { - typedef CallableCustomMethodPointer CCMP; // Messes with memnew otherwise. + typedef CallableCustomMethodPointer CCMP; // Messes with memnew otherwise. CCMP *ccmp = memnew(CCMP(p_instance, p_method)); #ifdef DEBUG_METHODS_ENABLED ccmp->set_text(p_func_text + 1); // Try to get rid of the ampersand. @@ -126,51 +133,13 @@ Callable create_custom_callable_function_pointer(T *p_instance, return Callable(ccmp); } -// VERSION WITH RETURN - -template -class CallableCustomMethodPointerRet : public CallableCustomMethodPointerBase { - struct Data { - T *instance; - uint64_t object_id; - R(T::*method) - (P...); - } data; - -public: - virtual ObjectID get_object() const { - if (ObjectDB::get_instance(ObjectID(data.object_id)) == nullptr) { - return ObjectID(); - } - return data.instance->get_instance_id(); - } - - virtual int get_argument_count(bool &r_is_valid) const { - r_is_valid = true; - return sizeof...(P); - } - - virtual void call(const Variant **p_arguments, int p_argcount, Variant &r_return_value, Callable::CallError &r_call_error) const { - ERR_FAIL_NULL_MSG(ObjectDB::get_instance(ObjectID(data.object_id)), "Invalid Object id '" + uitos(data.object_id) + "', can't call method."); - call_with_variant_args_ret(data.instance, data.method, p_arguments, p_argcount, r_return_value, r_call_error); - } - - CallableCustomMethodPointerRet(T *p_instance, R (T::*p_method)(P...)) { - memset(&data, 0, sizeof(Data)); // Clear beforehand, may have padding bytes. - data.instance = p_instance; - data.object_id = p_instance->get_instance_id(); - data.method = p_method; - _setup((uint32_t *)&data, sizeof(Data)); - } -}; - template Callable create_custom_callable_function_pointer(T *p_instance, #ifdef DEBUG_METHODS_ENABLED const char *p_func_text, #endif R (T::*p_method)(P...)) { - typedef CallableCustomMethodPointerRet CCMP; // Messes with memnew otherwise. + typedef CallableCustomMethodPointer CCMP; // Messes with memnew otherwise. CCMP *ccmp = memnew(CCMP(p_instance, p_method)); #ifdef DEBUG_METHODS_ENABLED ccmp->set_text(p_func_text + 1); // Try to get rid of the ampersand. @@ -178,10 +147,10 @@ Callable create_custom_callable_function_pointer(T *p_instance, return Callable(ccmp); } -// CONST VERSION WITH RETURN +// CONST VERSION template -class CallableCustomMethodPointerRetC : public CallableCustomMethodPointerBase { +class CallableCustomMethodPointerC : public CallableCustomMethodPointerBase { struct Data { T *instance; uint64_t object_id; @@ -204,10 +173,14 @@ class CallableCustomMethodPointerRetC : public CallableCustomMethodPointerBase { virtual void call(const Variant **p_arguments, int p_argcount, Variant &r_return_value, Callable::CallError &r_call_error) const override { ERR_FAIL_NULL_MSG(ObjectDB::get_instance(ObjectID(data.object_id)), "Invalid Object id '" + uitos(data.object_id) + "', can't call method."); - call_with_variant_args_retc(data.instance, data.method, p_arguments, p_argcount, r_return_value, r_call_error); + if constexpr (std::is_same::value) { + call_with_variant_argsc(data.instance, data.method, p_arguments, p_argcount, r_call_error); + } else { + call_with_variant_args_retc(data.instance, data.method, p_arguments, p_argcount, r_return_value, r_call_error); + } } - CallableCustomMethodPointerRetC(T *p_instance, R (T::*p_method)(P...) const) { + CallableCustomMethodPointerC(T *p_instance, R (T::*p_method)(P...) const) { memset(&data, 0, sizeof(Data)); // Clear beforehand, may have padding bytes. data.instance = p_instance; data.object_id = p_instance->get_instance_id(); @@ -216,13 +189,27 @@ class CallableCustomMethodPointerRetC : public CallableCustomMethodPointerBase { } }; +template +Callable create_custom_callable_function_pointer(T *p_instance, +#ifdef DEBUG_METHODS_ENABLED + const char *p_func_text, +#endif + void (T::*p_method)(P...) const) { + typedef CallableCustomMethodPointerC CCMP; // Messes with memnew otherwise. + CCMP *ccmp = memnew(CCMP(p_instance, p_method)); +#ifdef DEBUG_METHODS_ENABLED + ccmp->set_text(p_func_text + 1); // Try to get rid of the ampersand. +#endif + return Callable(ccmp); +} + template Callable create_custom_callable_function_pointer(T *p_instance, #ifdef DEBUG_METHODS_ENABLED const char *p_func_text, #endif R (T::*p_method)(P...) const) { - typedef CallableCustomMethodPointerRetC CCMP; // Messes with memnew otherwise. + typedef CallableCustomMethodPointerC CCMP; // Messes with memnew otherwise. CCMP *ccmp = memnew(CCMP(p_instance, p_method)); #ifdef DEBUG_METHODS_ENABLED ccmp->set_text(p_func_text + 1); // Try to get rid of the ampersand. @@ -238,10 +225,11 @@ Callable create_custom_callable_function_pointer(T *p_instance, // STATIC VERSIONS -template +template class CallableCustomStaticMethodPointer : public CallableCustomMethodPointerBase { struct Data { - void (*method)(P...); + R(*method) + (P...); } data; public: @@ -259,24 +247,27 @@ class CallableCustomStaticMethodPointer : public CallableCustomMethodPointerBase } virtual void call(const Variant **p_arguments, int p_argcount, Variant &r_return_value, Callable::CallError &r_call_error) const override { - call_with_variant_args_static_ret(data.method, p_arguments, p_argcount, r_return_value, r_call_error); - r_return_value = Variant(); + if constexpr (std::is_same::value) { + call_with_variant_args_static(data.method, p_arguments, p_argcount, r_call_error); + } else { + call_with_variant_args_static_ret(data.method, p_arguments, p_argcount, r_return_value, r_call_error); + } } - CallableCustomStaticMethodPointer(void (*p_method)(P...)) { + CallableCustomStaticMethodPointer(R (*p_method)(P...)) { memset(&data, 0, sizeof(Data)); // Clear beforehand, may have padding bytes. data.method = p_method; _setup((uint32_t *)&data, sizeof(Data)); } }; -template +template Callable create_custom_callable_static_function_pointer( #ifdef DEBUG_METHODS_ENABLED const char *p_func_text, #endif void (*p_method)(P...)) { - typedef CallableCustomStaticMethodPointer CCMP; // Messes with memnew otherwise. + typedef CallableCustomStaticMethodPointer CCMP; // Messes with memnew otherwise. CCMP *ccmp = memnew(CCMP(p_method)); #ifdef DEBUG_METHODS_ENABLED ccmp->set_text(p_func_text + 1); // Try to get rid of the ampersand. @@ -284,45 +275,13 @@ Callable create_custom_callable_static_function_pointer( return Callable(ccmp); } -template -class CallableCustomStaticMethodPointerRet : public CallableCustomMethodPointerBase { - struct Data { - R(*method) - (P...); - } data; - -public: - virtual bool is_valid() const override { - return true; - } - - virtual ObjectID get_object() const override { - return ObjectID(); - } - - virtual int get_argument_count(bool &r_is_valid) const override { - r_is_valid = true; - return sizeof...(P); - } - - virtual void call(const Variant **p_arguments, int p_argcount, Variant &r_return_value, Callable::CallError &r_call_error) const override { - call_with_variant_args_static_ret(data.method, p_arguments, p_argcount, r_return_value, r_call_error); - } - - CallableCustomStaticMethodPointerRet(R (*p_method)(P...)) { - memset(&data, 0, sizeof(Data)); // Clear beforehand, may have padding bytes. - data.method = p_method; - _setup((uint32_t *)&data, sizeof(Data)); - } -}; - template Callable create_custom_callable_static_function_pointer( #ifdef DEBUG_METHODS_ENABLED const char *p_func_text, #endif R (*p_method)(P...)) { - typedef CallableCustomStaticMethodPointerRet CCMP; // Messes with memnew otherwise. + typedef CallableCustomStaticMethodPointer CCMP; // Messes with memnew otherwise. CCMP *ccmp = memnew(CCMP(p_method)); #ifdef DEBUG_METHODS_ENABLED ccmp->set_text(p_func_text + 1); // Try to get rid of the ampersand. diff --git a/core/object/class_db.cpp b/core/object/class_db.cpp index ceeb04b8eae3..9826d73a9dda 100644 --- a/core/object/class_db.cpp +++ b/core/object/class_db.cpp @@ -181,7 +181,7 @@ class PlaceholderExtensionInstance { return 0; } - static GDExtensionObjectPtr placeholder_class_create_instance(void *p_class_userdata) { + 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 @@ class PlaceholderExtensionInstance { ERR_FAIL_NULL_V(native_parent->creation_func, nullptr); // Construct a placeholder. - Object *obj = native_parent->creation_func(); + Object *obj = native_parent->creation_func(static_cast(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 @@ -227,16 +227,12 @@ class PlaceholderExtensionInstance { #endif bool ClassDB::_is_parent_class(const StringName &p_class, const StringName &p_inherits) { - if (!classes.has(p_class)) { - return false; - } - - StringName inherits = p_class; - while (inherits.operator String().length()) { - if (inherits == p_inherits) { + ClassInfo *c = classes.getptr(p_class); + while (c) { + if (c->name == p_inherits) { return true; } - inherits = _get_parent_class(inherits); + c = c->inherits_ptr; } return false; @@ -271,6 +267,22 @@ void ClassDB::get_extensions_class_list(List *p_classes) { p_classes->sort_custom(); } + +void ClassDB::get_extension_class_list(const Ref &p_extension, List *p_classes) { + OBJTYPE_RLOCK; + + for (const KeyValue &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(); +} #endif void ClassDB::get_inheriters_from_class(const StringName &p_class, List *p_classes) { @@ -303,6 +315,29 @@ StringName ClassDB::get_parent_class_nocheck(const StringName &p_class) { return ti->inherits; } +bool ClassDB::get_inheritance_chain_nocheck(const StringName &p_class, Vector &r_result) { + OBJTYPE_RLOCK; + + ClassInfo *start = classes.getptr(p_class); + if (!start) { + return false; + } + + int classes_to_add = 0; + for (ClassInfo *ti = start; ti; ti = ti->inherits_ptr) { + classes_to_add++; + } + + int64_t old_size = r_result.size(); + r_result.resize(old_size + classes_to_add); + StringName *w = r_result.ptrw() + old_size; + for (ClassInfo *ti = start; ti; ti = ti->inherits_ptr) { + *w++ = ti->name; + } + + return true; +} + StringName ClassDB::get_compatibility_remapped_class(const StringName &p_class) { if (classes.has(p_class)) { return p_class; @@ -502,12 +537,12 @@ StringName ClassDB::get_compatibility_class(const StringName &p_class) { return StringName(); } -Object *ClassDB::_instantiate_internal(const StringName &p_class, bool p_require_real_class) { +Object *ClassDB::_instantiate_internal(const StringName &p_class, bool p_require_real_class, bool p_notify_postinitialize) { ClassInfo *ti; { OBJTYPE_RLOCK; ti = classes.getptr(p_class); - if (!ti || ti->disabled || !ti->creation_func || (ti->gdextension && !ti->gdextension->create_instance)) { + if (!_can_instantiate(ti)) { if (compat_classes.has(p_class)) { ti = classes.getptr(compat_classes[p_class]); } @@ -516,34 +551,78 @@ Object *ClassDB::_instantiate_internal(const StringName &p_class, bool p_require ERR_FAIL_COND_V_MSG(ti->disabled, nullptr, "Class '" + String(p_class) + "' is disabled."); ERR_FAIL_NULL_V_MSG(ti->creation_func, nullptr, "Class '" + String(p_class) + "' or its base class cannot be instantiated."); } + #ifdef TOOLS_ENABLED if ((ti->api == API_EDITOR || ti->api == API_EDITOR_EXTENSION) && !Engine::get_singleton()->is_editor_hint()) { ERR_PRINT("Class '" + String(p_class) + "' can only be instantiated by editor."); return nullptr; } #endif - if (ti->gdextension && ti->gdextension->create_instance) { - ObjectGDExtension *extension = ti->gdextension; -#ifdef TOOLS_ENABLED - if (!p_require_real_class && ti->is_runtime && Engine::get_singleton()->is_editor_hint()) { - extension = get_placeholder_extension(ti->name); - } -#endif - return (Object *)extension->create_instance(extension->class_userdata); - } else { + #ifdef TOOLS_ENABLED - if (!p_require_real_class && ti->is_runtime && Engine::get_singleton()->is_editor_hint()) { - if (!ti->inherits_ptr || !ti->inherits_ptr->creation_func) { - ERR_PRINT(vformat("Cannot make a placeholder instance of runtime class %s because its parent cannot be constructed.", ti->name)); - } else { - ObjectGDExtension *extension = get_placeholder_extension(ti->name); - return (Object *)extension->create_instance(extension->class_userdata); + // Try to create placeholder. + if (!p_require_real_class && ti->is_runtime && Engine::get_singleton()->is_editor_hint()) { + bool can_create_placeholder = false; + if (ti->gdextension) { + if (ti->gdextension->create_instance2) { + can_create_placeholder = true; } +#ifndef DISABLE_DEPRECATED + else if (ti->gdextension->create_instance) { + can_create_placeholder = true; + } +#endif // DISABLE_DEPRECATED + } else if (!ti->inherits_ptr || !ti->inherits_ptr->creation_func) { + ERR_PRINT(vformat("Cannot make a placeholder instance of runtime class %s because its parent cannot be constructed.", ti->name)); + } else { + can_create_placeholder = true; } -#endif - return ti->creation_func(); + if (can_create_placeholder) { + ObjectGDExtension *extension = get_placeholder_extension(ti->name); + return (Object *)extension->create_instance2(extension->class_userdata, p_notify_postinitialize); + } + } +#endif // TOOLS_ENABLED + + if (ti->gdextension && ti->gdextension->create_instance2) { + ObjectGDExtension *extension = ti->gdextension; + return (Object *)extension->create_instance2(extension->class_userdata, p_notify_postinitialize); + } +#ifndef DISABLE_DEPRECATED + else if (ti->gdextension && ti->gdextension->create_instance) { + ObjectGDExtension *extension = ti->gdextension; + return (Object *)extension->create_instance(extension->class_userdata); + } +#endif // DISABLE_DEPRECATED + else { + return ti->creation_func(p_notify_postinitialize); + } +} + +bool ClassDB::_can_instantiate(ClassInfo *p_class_info) { + if (!p_class_info) { + return false; + } + + if (p_class_info->disabled || !p_class_info->creation_func) { + return false; + } + + if (!p_class_info->gdextension) { + return true; + } + + if (p_class_info->gdextension->create_instance2) { + return true; + } + +#ifndef DISABLE_DEPRECATED + if (p_class_info->gdextension->create_instance) { + return true; } +#endif // DISABLE_DEPRECATED + return false; } Object *ClassDB::instantiate(const StringName &p_class) { @@ -554,6 +633,10 @@ Object *ClassDB::instantiate_no_placeholders(const StringName &p_class) { return _instantiate_internal(p_class, true); } +Object *ClassDB::instantiate_without_postinitialization(const StringName &p_class) { + return _instantiate_internal(p_class, true, false); +} + #ifdef TOOLS_ENABLED ObjectGDExtension *ClassDB::get_placeholder_extension(const StringName &p_class) { ObjectGDExtension *placeholder_extension = placeholder_extensions.getptr(p_class); @@ -565,7 +648,7 @@ ObjectGDExtension *ClassDB::get_placeholder_extension(const StringName &p_class) { OBJTYPE_RLOCK; ti = classes.getptr(p_class); - if (!ti || ti->disabled || !ti->creation_func || (ti->gdextension && !ti->gdextension->create_instance)) { + if (!_can_instantiate(ti)) { if (compat_classes.has(p_class)) { ti = classes.getptr(compat_classes[p_class]); } @@ -626,7 +709,10 @@ ObjectGDExtension *ClassDB::get_placeholder_extension(const StringName &p_class) placeholder_extension->get_rid = &PlaceholderExtensionInstance::placeholder_instance_get_rid; placeholder_extension->class_userdata = ti; - placeholder_extension->create_instance = &PlaceholderExtensionInstance::placeholder_class_create_instance; +#ifndef DISABLE_DEPRECATED + placeholder_extension->create_instance = nullptr; +#endif // DISABLE_DEPRECATED + placeholder_extension->create_instance2 = &PlaceholderExtensionInstance::placeholder_class_create_instance; placeholder_extension->free_instance = &PlaceholderExtensionInstance::placeholder_class_free_instance; placeholder_extension->get_virtual = &PlaceholderExtensionInstance::placeholder_class_get_virtual; placeholder_extension->get_virtual_call_data = nullptr; @@ -643,7 +729,7 @@ void ClassDB::set_object_extension_instance(Object *p_object, const StringName & { OBJTYPE_RLOCK; ti = classes.getptr(p_class); - if (!ti || ti->disabled || !ti->creation_func || (ti->gdextension && !ti->gdextension->create_instance)) { + if (!_can_instantiate(ti)) { if (compat_classes.has(p_class)) { ti = classes.getptr(compat_classes[p_class]); } @@ -680,7 +766,7 @@ bool ClassDB::can_instantiate(const StringName &p_class) { return false; } #endif - return (!ti->disabled && ti->creation_func != nullptr && !(ti->gdextension && !ti->gdextension->create_instance)); + return _can_instantiate(ti); } bool ClassDB::is_abstract(const StringName &p_class) { @@ -695,7 +781,18 @@ bool ClassDB::is_abstract(const StringName &p_class) { Ref