diff --git a/.github/workflows/build_android.yml b/.github/workflows/build_android.yml
index 2c99ad118..568b4f35e 100644
--- a/.github/workflows/build_android.yml
+++ b/.github/workflows/build_android.yml
@@ -1,21 +1,36 @@
name: Build Android
on: [push, pull_request]
+env:
+ DOTNET_NOLOGO: true
+ DOTNET_CLI_TELEMETRY_OPTOUT: false
+
jobs:
# Game
game-windows:
name: Game (Android, Release ARM64)
- runs-on: "windows-2019"
+ runs-on: "windows-2022"
steps:
- name: Checkout repo
uses: actions/checkout@v3
- name: Setup Vulkan
uses: ./.github/actions/vulkan
+ - name: Setup .NET
+ uses: actions/setup-dotnet@v3
+ with:
+ dotnet-version: 7.0.x
+ - name: Setup .NET Workload
+ run: |
+ dotnet workload install android
+ - name: Print .NET info
+ run: |
+ dotnet --info
+ dotnet workload --info
- name: Checkout LFS
run: |
git lfs version
git lfs pull
- name: Build
run: |
- .\Development\Scripts\Windows\CallBuildTool.bat -build -log -arch=ARM64 -platform=Android -configuration=Release -buildtargets=FlaxGame
+ .\Development\Scripts\Windows\CallBuildTool.bat -build -log -printSDKs -arch=ARM64 -platform=Android -configuration=Release -buildtargets=FlaxGame
diff --git a/.github/workflows/build_ios.yml b/.github/workflows/build_ios.yml
new file mode 100644
index 000000000..f4b5d8147
--- /dev/null
+++ b/.github/workflows/build_ios.yml
@@ -0,0 +1,36 @@
+name: Build iOS
+on: [push, pull_request]
+
+env:
+ DOTNET_NOLOGO: true
+ DOTNET_CLI_TELEMETRY_OPTOUT: false
+
+jobs:
+
+ # Game
+ game-windows:
+ name: Game (iOS, Release ARM64)
+ runs-on: "macos-latest"
+ steps:
+ - name: Checkout repo
+ uses: actions/checkout@v3
+ - name: Setup Vulkan
+ uses: ./.github/actions/vulkan
+ - name: Setup .NET
+ uses: actions/setup-dotnet@v3
+ with:
+ dotnet-version: 7.0.x
+ - name: Setup .NET Workload
+ run: |
+ dotnet workload install ios
+ - name: Print .NET info
+ run: |
+ dotnet --info
+ dotnet workload --info
+ - name: Checkout LFS
+ run: |
+ git lfs version
+ git lfs pull
+ - name: Build
+ run: |
+ ./Development/Scripts/Mac/CallBuildTool.sh -build -log -arch=ARM64 -platform=iOS -configuration=Release -buildtargets=FlaxGame
diff --git a/.github/workflows/build_linux.yml b/.github/workflows/build_linux.yml
index a860010fd..bd1726737 100644
--- a/.github/workflows/build_linux.yml
+++ b/.github/workflows/build_linux.yml
@@ -1,6 +1,10 @@
name: Build Linux
on: [push, pull_request]
+env:
+ DOTNET_NOLOGO: true
+ DOTNET_CLI_TELEMETRY_OPTOUT: false
+
jobs:
# Editor
@@ -18,13 +22,21 @@ jobs:
sudo apt-get install libx11-dev libxcursor-dev libxinerama-dev build-essential gettext libtool libtool-bin libpulse-dev libasound2-dev libjack-dev portaudio19-dev
- name: Setup Vulkan
uses: ./.github/actions/vulkan
+ - name: Setup .NET
+ uses: actions/setup-dotnet@v3
+ with:
+ dotnet-version: 7.0.x
+ - name: Print .NET info
+ run: |
+ dotnet --info
+ dotnet workload --info
- name: Checkout LFS
run: |
git lfs version
git lfs pull
- name: Build
run: |
- ./Development/Scripts/Linux/CallBuildTool.sh -build -log -arch=x64 -platform=Linux -configuration=Development -buildtargets=FlaxEditor
+ ./Development/Scripts/Linux/CallBuildTool.sh -build -log -printSDKs -arch=x64 -platform=Linux -configuration=Development -buildtargets=FlaxEditor
# Game
game-linux:
@@ -38,10 +50,18 @@ jobs:
sudo apt-get install libx11-dev libxcursor-dev libxinerama-dev
- name: Setup Vulkan
uses: ./.github/actions/vulkan
+ - name: Setup .NET
+ uses: actions/setup-dotnet@v3
+ with:
+ dotnet-version: 7.0.x
+ - name: Print .NET info
+ run: |
+ dotnet --info
+ dotnet workload --info
- name: Checkout LFS
run: |
git lfs version
git lfs pull
- name: Build
run: |
- ./Development/Scripts/Linux/CallBuildTool.sh -build -log -arch=x64 -platform=Linux -configuration=Release -buildtargets=FlaxGame
+ ./Development/Scripts/Linux/CallBuildTool.sh -build -log -printSDKs -arch=x64 -platform=Linux -configuration=Release -buildtargets=FlaxGame
diff --git a/.github/workflows/build_mac.yml b/.github/workflows/build_mac.yml
index 33e2668ae..88cb9b7a8 100644
--- a/.github/workflows/build_mac.yml
+++ b/.github/workflows/build_mac.yml
@@ -1,6 +1,10 @@
name: Build macOS
on: [push, pull_request]
+env:
+ DOTNET_NOLOGO: true
+ DOTNET_CLI_TELEMETRY_OPTOUT: false
+
jobs:
# Editor
@@ -12,13 +16,21 @@ jobs:
uses: actions/checkout@v3
- name: Setup Vulkan
uses: ./.github/actions/vulkan
+ - name: Setup .NET
+ uses: actions/setup-dotnet@v3
+ with:
+ dotnet-version: 7.0.x
+ - name: Print .NET info
+ run: |
+ dotnet --info
+ dotnet workload --info
- name: Checkout LFS
run: |
git lfs version
git lfs pull
- name: Build
run: |
- ./Development/Scripts/Mac/CallBuildTool.sh -build -log -arch=x64 -platform=Mac -configuration=Development -buildtargets=FlaxEditor
+ ./Development/Scripts/Mac/CallBuildTool.sh -build -log -printSDKs -arch=x64 -platform=Mac -configuration=Development -buildtargets=FlaxEditor
# Game
game-mac:
@@ -29,10 +41,18 @@ jobs:
uses: actions/checkout@v3
- name: Setup Vulkan
uses: ./.github/actions/vulkan
+ - name: Setup .NET
+ uses: actions/setup-dotnet@v3
+ with:
+ dotnet-version: 7.0.x
+ - name: Print .NET info
+ run: |
+ dotnet --info
+ dotnet workload --info
- name: Checkout LFS
run: |
git lfs version
git lfs pull
- name: Build
run: |
- ./Development/Scripts/Mac/CallBuildTool.sh -build -log -arch=x64 -platform=Mac -configuration=Release -buildtargets=FlaxGame
+ ./Development/Scripts/Mac/CallBuildTool.sh -build -log -printSDKs -arch=x64 -platform=Mac -configuration=Release -buildtargets=FlaxGame
diff --git a/.github/workflows/build_windows.yml b/.github/workflows/build_windows.yml
index ac88b30f4..513cbfd08 100644
--- a/.github/workflows/build_windows.yml
+++ b/.github/workflows/build_windows.yml
@@ -1,38 +1,58 @@
name: Build Windows
on: [push, pull_request]
+env:
+ DOTNET_NOLOGO: true
+ DOTNET_CLI_TELEMETRY_OPTOUT: false
+
jobs:
# Editor
editor-windows:
name: Editor (Windows, Development x64)
- runs-on: "windows-2019"
+ runs-on: "windows-2022"
steps:
- name: Checkout repo
uses: actions/checkout@v3
- name: Setup Vulkan
uses: ./.github/actions/vulkan
+ - name: Setup .NET
+ uses: actions/setup-dotnet@v3
+ with:
+ dotnet-version: 7.0.x
+ - name: Print .NET info
+ run: |
+ dotnet --info
+ dotnet workload --info
- name: Checkout LFS
run: |
git lfs version
git lfs pull
- name: Build
run: |
- .\Development\Scripts\Windows\CallBuildTool.bat -build -log -arch=x64 -platform=Windows -configuration=Development -buildtargets=FlaxEditor
+ .\Development\Scripts\Windows\CallBuildTool.bat -build -log -printSDKs -arch=x64 -platform=Windows -configuration=Development -buildtargets=FlaxEditor
# Game
game-windows:
name: Game (Windows, Release x64)
- runs-on: "windows-2019"
+ runs-on: "windows-2022"
steps:
- name: Checkout repo
uses: actions/checkout@v3
- name: Setup Vulkan
uses: ./.github/actions/vulkan
+ - name: Setup .NET
+ uses: actions/setup-dotnet@v3
+ with:
+ dotnet-version: 7.0.x
+ - name: Print .NET info
+ run: |
+ dotnet --info
+ dotnet workload --info
- name: Checkout LFS
run: |
git lfs version
git lfs pull
- name: Build
run: |
- .\Development\Scripts\Windows\CallBuildTool.bat -build -log -arch=x64 -platform=Windows -configuration=Release -buildtargets=FlaxGame
+ .\Development\Scripts\Windows\CallBuildTool.bat -build -log -printSDKs -arch=x64 -platform=Windows -configuration=Release -buildtargets=FlaxGame
diff --git a/.github/workflows/cd.yml b/.github/workflows/cd.yml
index d5149a652..a5d8bc043 100644
--- a/.github/workflows/cd.yml
+++ b/.github/workflows/cd.yml
@@ -4,12 +4,16 @@ on:
- cron: '15 4 * * *'
workflow_dispatch:
+env:
+ DOTNET_NOLOGO: true
+ DOTNET_CLI_TELEMETRY_OPTOUT: false
+
jobs:
# Windows
package-windows-editor:
name: Editor (Windows)
- runs-on: "windows-2019"
+ runs-on: "windows-2022"
steps:
- name: Checkout repo
uses: actions/checkout@v3
@@ -19,6 +23,14 @@ jobs:
git lfs pull
- name: Setup Vulkan
uses: ./.github/actions/vulkan
+ - name: Setup .NET
+ uses: actions/setup-dotnet@v3
+ with:
+ dotnet-version: 7.0.x
+ - name: Print .NET info
+ run: |
+ dotnet --info
+ dotnet workload --info
- name: Build
run: |
.\PackageEditor.bat -arch=x64 -platform=Windows -deployOutput=Output
@@ -34,7 +46,7 @@ jobs:
path: Output/EditorDebugSymbols.zip
package-windows-game:
name: Game (Windows)
- runs-on: "windows-2019"
+ runs-on: "windows-2022"
steps:
- name: Checkout repo
uses: actions/checkout@v3
@@ -44,6 +56,14 @@ jobs:
git lfs pull
- name: Setup Vulkan
uses: ./.github/actions/vulkan
+ - name: Setup .NET
+ uses: actions/setup-dotnet@v3
+ with:
+ dotnet-version: 7.0.x
+ - name: Print .NET info
+ run: |
+ dotnet --info
+ dotnet workload --info
- name: Build
run: |
.\PackagePlatforms.bat -arch=x64 -platform=Windows -deployOutput=Output
@@ -72,6 +92,14 @@ jobs:
sudo apt-get install libx11-dev libxcursor-dev libxinerama-dev build-essential gettext libtool libtool-bin libpulse-dev libasound2-dev libjack-dev portaudio19-dev
- name: Setup Vulkan
uses: ./.github/actions/vulkan
+ - name: Setup .NET
+ uses: actions/setup-dotnet@v3
+ with:
+ dotnet-version: 7.0.x
+ - name: Print .NET info
+ run: |
+ dotnet --info
+ dotnet workload --info
- name: Build
run: |
./PackageEditor.sh -arch=x64 -platform=Linux -deployOutput=Output
@@ -98,6 +126,14 @@ jobs:
sudo apt-get install libx11-dev libxcursor-dev libxinerama-dev build-essential gettext libtool libtool-bin libpulse-dev libasound2-dev libjack-dev portaudio19-dev
- name: Setup Vulkan
uses: ./.github/actions/vulkan
+ - name: Setup .NET
+ uses: actions/setup-dotnet@v3
+ with:
+ dotnet-version: 7.0.x
+ - name: Print .NET info
+ run: |
+ dotnet --info
+ dotnet workload --info
- name: Build
run: |
./PackagePlatforms.sh -arch=x64 -platform=Linux -deployOutput=Output
@@ -120,6 +156,14 @@ jobs:
git lfs pull
- name: Setup Vulkan
uses: ./.github/actions/vulkan
+ - name: Setup .NET
+ uses: actions/setup-dotnet@v3
+ with:
+ dotnet-version: 7.0.x
+ - name: Print .NET info
+ run: |
+ dotnet --info
+ dotnet workload --info
- name: Build
run: |
./PackageEditor.command -arch=x64 -platform=Mac -deployOutput=Output
@@ -140,6 +184,14 @@ jobs:
git lfs pull
- name: Setup Vulkan
uses: ./.github/actions/vulkan
+ - name: Setup .NET
+ uses: actions/setup-dotnet@v3
+ with:
+ dotnet-version: 7.0.x
+ - name: Print .NET info
+ run: |
+ dotnet --info
+ dotnet workload --info
- name: Build
run: |
./PackagePlatforms.command -arch=x64 -platform=Mac -deployOutput=Output
diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml
index 6cbe46bfe..f9d18bcfc 100644
--- a/.github/workflows/tests.yml
+++ b/.github/workflows/tests.yml
@@ -1,6 +1,10 @@
name: Tests
on: [push, pull_request]
+env:
+ DOTNET_NOLOGO: true
+ DOTNET_CLI_TELEMETRY_OPTOUT: false
+
jobs:
# Tests on Linux
@@ -10,6 +14,14 @@ jobs:
steps:
- name: Checkout repo
uses: actions/checkout@v3
+ - name: Setup .NET
+ uses: actions/setup-dotnet@v3
+ with:
+ dotnet-version: 7.0.x
+ - name: Print .NET info
+ run: |
+ dotnet --info
+ dotnet workload --info
- name: Checkout LFS
run: |
git lfs version
@@ -22,39 +34,52 @@ jobs:
sudo apt-get install libx11-dev libxcursor-dev libxinerama-dev build-essential gettext libtool libtool-bin libpulse-dev libasound2-dev libjack-dev portaudio19-dev
- name: Build
run: |
- ./GenerateProjectFiles.sh -vs2019
+ ./GenerateProjectFiles.sh -vs2022 -log -verbose -printSDKs
./Development/Scripts/Linux/CallBuildTool.sh -build -log -arch=x64 -platform=Linux -configuration=Development -buildtargets=FlaxTestsTarget
- ./Development/Scripts/Linux/CallBuildTool.sh -build -log -arch=x64 -platform=Linux -configuration=Debug -buildtargets=FlaxEditor -BuildBindingsOnly
- ./Development/Scripts/Linux/CallBuildTool.sh -build -log -arch=x64 -platform=Linux -configuration=Debug -buildtargets="Flax.Build.Tests"
+ dotnet msbuild Source/Tools/Flax.Build.Tests/Flax.Build.Tests.csproj /m /t:Restore,Build /p:Configuration=Debug /p:Platform=AnyCPU /nologo
+ dotnet msbuild Source/Tools/Flax.Build.Tests/Flax.Build.Tests.csproj /m /t:Restore,Build /p:Configuration=Debug /p:Platform=AnyCPU /nologo
- name: Test
run: |
- Binaries/Editor/Linux/Development/FlaxTests
- mono Source/Platforms/DotNet/NUnit/nunit3-console.exe Binaries/Tools/Flax.Build.Tests.dll --framework=mono-4.0
+ ${GITHUB_WORKSPACE}/Binaries/Editor/Linux/Development/FlaxTests
+ dotnet test -f net7.0 Binaries/Tests/Flax.Build.Tests.dll
+ cp Binaries/Editor/Linux/Development/FlaxEngine.CSharp.dll Binaries/Tests
+ cp Binaries/Editor/Linux/Development/FlaxEngine.CSharp.runtimeconfig.json Binaries/Tests
+ cp Binaries/Editor/Linux/Development/Newtonsoft.Json.dll Binaries/Tests
+ dotnet test -f net7.0 Binaries/Tests/FlaxEngine.CSharp.dll
- name: Test UseLargeWorlds
run: |
./Development/Scripts/Linux/CallBuildTool.sh -build -log -arch=x64 -platform=Linux -configuration=Development -buildtargets=FlaxTestsTarget -UseLargeWorlds=true
- Binaries/Editor/Linux/Development/FlaxTests
+ ${GITHUB_WORKSPACE}/Binaries/Editor/Linux/Development/FlaxTests
# Tests on Windows
tests-windows:
name: Tests (Windows)
- runs-on: "windows-2019"
+ runs-on: "windows-2022"
steps:
- name: Checkout repo
uses: actions/checkout@v3
+ - name: Setup .NET
+ uses: actions/setup-dotnet@v3
+ with:
+ dotnet-version: 7.0.x
+ - name: Print .NET info
+ run: |
+ dotnet --info
+ dotnet workload --info
- name: Checkout LFS
run: |
git lfs version
git lfs pull
- name: Build
run: |
- .\GenerateProjectFiles.bat -vs2019
+ .\GenerateProjectFiles.bat -vs2022 -log -verbose -printSDKs
.\Development\Scripts\Windows\CallBuildTool.bat -build -log -arch=x64 -platform=Windows -configuration=Development -buildtargets=FlaxTestsTarget
- .\Development\Scripts\Windows\CallBuildTool.bat -build -log -arch=x64 -platform=Windows -configuration=Debug -buildtargets=FlaxEditor -BuildBindingsOnly
- .\Development\Scripts\Windows\CallBuildTool.bat -build -log -arch=x64 -platform=Windows -configuration=Debug -buildtargets="FlaxEngine.Tests"
- .\Development\Scripts\Windows\CallBuildTool.bat -build -log -arch=x64 -platform=Windows -configuration=Debug -buildtargets="Flax.Build.Tests"
+ dotnet msbuild Source\Tools\Flax.Build.Tests\Flax.Build.Tests.csproj /m /t:Restore,Build /p:Configuration=Debug /p:Platform=AnyCPU /nologo
- name: Test
run: |
- Binaries\Editor\Win64\Development\FlaxTests.exe
- Source\Platforms\DotNet\NUnit\nunit3-console.exe Binaries\Tools\FlaxEngine.Tests.dll --framework=net-4.5.2
- Source\Platforms\DotNet\NUnit\nunit3-console.exe Binaries\Tools\Flax.Build.Tests.dll --framework=net-4.5.2
+ .\Binaries\Editor\Win64\Development\FlaxTests.exe
+ dotnet test -f net7.0 Binaries\Tests\Flax.Build.Tests.dll
+ xcopy /y Binaries\Editor\Win64\Development\FlaxEngine.CSharp.dll Binaries\Tests
+ xcopy /y Binaries\Editor\Win64\Development\FlaxEngine.CSharp.runtimeconfig.json Binaries\Tests
+ xcopy /y Binaries\Editor\Win64\Development\Newtonsoft.Json.dll Binaries\Tests
+ dotnet test -f net7.0 Binaries\Tests\FlaxEngine.CSharp.dll
diff --git a/.gitignore b/.gitignore
index 1e9aef426..54907892f 100644
--- a/.gitignore
+++ b/.gitignore
@@ -25,6 +25,7 @@ PackagePlatforms_Cert.bat
*.opendb
*.DS_Store
*.xcodeproj
+launchSettings.json
# NUNIT
*.VisualState.xml
diff --git a/Content/Editor/IconsAtlas.flax b/Content/Editor/IconsAtlas.flax
index 8fa3e3ee9..3318b811b 100644
--- a/Content/Editor/IconsAtlas.flax
+++ b/Content/Editor/IconsAtlas.flax
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:ff5774c1073e5d4e5de362f7f8a0a74822cacb0b074272d4bba341be0c92d4ed
-size 5611572
+oid sha256:d867a994701f09964335ddf51cd4060a54728b9de4420ad3f0bfbd8847bc7602
+size 5611659
diff --git a/Content/Editor/MaterialTemplates/Features/DeferredShading.hlsl b/Content/Editor/MaterialTemplates/Features/DeferredShading.hlsl
index fd0023a91..97c71c26f 100644
--- a/Content/Editor/MaterialTemplates/Features/DeferredShading.hlsl
+++ b/Content/Editor/MaterialTemplates/Features/DeferredShading.hlsl
@@ -1,4 +1,4 @@
-// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved.
+// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.
@0// Deferred Shading: Defines
@1// Deferred Shading: Includes
diff --git a/Content/Editor/MaterialTemplates/Features/Distortion.hlsl b/Content/Editor/MaterialTemplates/Features/Distortion.hlsl
index c74014cca..97faee8fb 100644
--- a/Content/Editor/MaterialTemplates/Features/Distortion.hlsl
+++ b/Content/Editor/MaterialTemplates/Features/Distortion.hlsl
@@ -1,4 +1,4 @@
-// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved.
+// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.
@0// Distortion: Defines
@1// Distortion: Includes
diff --git a/Content/Editor/MaterialTemplates/Features/ForwardShading.hlsl b/Content/Editor/MaterialTemplates/Features/ForwardShading.hlsl
index cf596bb30..066815dfe 100644
--- a/Content/Editor/MaterialTemplates/Features/ForwardShading.hlsl
+++ b/Content/Editor/MaterialTemplates/Features/ForwardShading.hlsl
@@ -1,4 +1,4 @@
-// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved.
+// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.
@0// Forward Shading: Defines
#define MAX_LOCAL_LIGHTS 4
diff --git a/Content/Editor/MaterialTemplates/Features/GlobalIllumination.hlsl b/Content/Editor/MaterialTemplates/Features/GlobalIllumination.hlsl
index 7159f1423..b660b7288 100644
--- a/Content/Editor/MaterialTemplates/Features/GlobalIllumination.hlsl
+++ b/Content/Editor/MaterialTemplates/Features/GlobalIllumination.hlsl
@@ -1,4 +1,4 @@
-// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved.
+// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.
@0// Global Illumination: Defines
#define USE_GI 1
diff --git a/Content/Editor/MaterialTemplates/Features/Lightmap.hlsl b/Content/Editor/MaterialTemplates/Features/Lightmap.hlsl
index f4583fa7f..d0edf8f30 100644
--- a/Content/Editor/MaterialTemplates/Features/Lightmap.hlsl
+++ b/Content/Editor/MaterialTemplates/Features/Lightmap.hlsl
@@ -1,4 +1,4 @@
-// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved.
+// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.
@0// Lightmap: Defines
#define CAN_USE_LIGHTMAP 1
diff --git a/Content/Editor/MaterialTemplates/Features/MotionVectors.hlsl b/Content/Editor/MaterialTemplates/Features/MotionVectors.hlsl
index 992eb6805..592d6c2b8 100644
--- a/Content/Editor/MaterialTemplates/Features/MotionVectors.hlsl
+++ b/Content/Editor/MaterialTemplates/Features/MotionVectors.hlsl
@@ -1,4 +1,4 @@
-// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved.
+// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.
@0// Motion Vectors: Defines
@1// Motion Vectors: Includes
diff --git a/Content/Editor/MaterialTemplates/Features/Tessellation.hlsl b/Content/Editor/MaterialTemplates/Features/Tessellation.hlsl
index e2e0b54ea..40314a4c2 100644
--- a/Content/Editor/MaterialTemplates/Features/Tessellation.hlsl
+++ b/Content/Editor/MaterialTemplates/Features/Tessellation.hlsl
@@ -1,4 +1,4 @@
-// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved.
+// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.
@0// Tessellation: Defines
#define TessalationProjectOntoPlane(planeNormal, planePosition, pointToProject) pointToProject - dot(pointToProject - planePosition, planeNormal) * planeNormal
diff --git a/Content/Shaders/Lights.flax b/Content/Shaders/Lights.flax
index 34e8c9a8c..ea30cd9f7 100644
--- a/Content/Shaders/Lights.flax
+++ b/Content/Shaders/Lights.flax
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:b405f5698304e87e6f510c49a0cea3817c9a1a00d7ba63ef4027ac49ba7cc7a4
-size 5299
+oid sha256:83cb261770aba2813f85063f987032f9914387f4011474b0fe347c42522116b4
+size 5122
diff --git a/Content/Shaders/VolumetricFog.flax b/Content/Shaders/VolumetricFog.flax
index 18eddae16..eb3ca9f2c 100644
--- a/Content/Shaders/VolumetricFog.flax
+++ b/Content/Shaders/VolumetricFog.flax
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:72415ba69a685e9a739d1a08d08c6eb9efd3cab31d8cc0c949cb187a9bad19a5
-size 13841
+oid sha256:4ab2c2551ce9cce0355c92f4f6499858e04e4bf764900b305273a7d57b887596
+size 13633
diff --git a/Development/Scripts/Linux/CallBuildTool.sh b/Development/Scripts/Linux/CallBuildTool.sh
index aa33bd65e..3a394c74b 100755
--- a/Development/Scripts/Linux/CallBuildTool.sh
+++ b/Development/Scripts/Linux/CallBuildTool.sh
@@ -10,8 +10,7 @@ if [ $testfilesize -le 1000 ]; then
fi
# Compile the build tool.
-xbuild /nologo /verbosity:quiet "Source/Tools/Flax.Build/Flax.Build.csproj" /property:Configuration=Release /property:Platform=AnyCPU /target:Build
+dotnet msbuild /nologo /verbosity:quiet "Source/Tools/Flax.Build/Flax.Build.csproj" /property:Configuration=Release /target:Restore,Build /property:RestorePackagesConfig=True /p:RuntimeIdentifiers=linux-x64
# Run the build tool using the provided arguments.
-#mono --debug --debugger-agent=transport=dt_socket,server=y,address=127.0.0.1:55555 Binaries/Tools/Flax.Build.exe "$@"
-mono Binaries/Tools/Flax.Build.exe "$@"
+Binaries/Tools/Flax.Build "$@"
diff --git a/Development/Scripts/Mac/CallBuildTool.sh b/Development/Scripts/Mac/CallBuildTool.sh
index 2096a5c57..27f78759a 100755
--- a/Development/Scripts/Mac/CallBuildTool.sh
+++ b/Development/Scripts/Mac/CallBuildTool.sh
@@ -10,8 +10,7 @@ if [ $testfilesize -le 1000 ]; then
fi
# Compile the build tool.
-xbuild /nologo /verbosity:quiet "Source/Tools/Flax.Build/Flax.Build.csproj" /property:Configuration=Release /property:Platform=AnyCPU /target:Build
+dotnet msbuild /nologo /verbosity:quiet "Source/Tools/Flax.Build/Flax.Build.csproj" /property:Configuration=Release /target:Restore,Build /property:RestorePackagesConfig=True /p:RuntimeIdentifiers=osx-x64
# Run the build tool using the provided arguments.
-#mono --debug --debugger-agent=transport=dt_socket,server=y,address=127.0.0.1:55555 Binaries/Tools/Flax.Build.exe "$@"
-mono Binaries/Tools/Flax.Build.exe "$@"
+Binaries/Tools/Flax.Build "$@"
diff --git a/Development/Scripts/Mac/XCodeBuild.sh b/Development/Scripts/Mac/XCodeBuild.sh
index 6b22e6e3f..3db9ff3ef 100755
--- a/Development/Scripts/Mac/XCodeBuild.sh
+++ b/Development/Scripts/Mac/XCodeBuild.sh
@@ -2,7 +2,7 @@
# Copyright (c) 2012-2023 Wojciech Figat. All rights reserved
# Fix mono bin to be in a path
-export PATH=/Library/Frameworks/Mono.framework/Versions/Current/Commands:$PATH
+#export PATH=/Library/Frameworks/Mono.framework/Versions/Current/Commands:$PATH
echo "Running Flax.Build $*"
-mono Binaries/Tools/Flax.Build.exe "$@"
+Binaries/Tools/Flax.Build "$@"
diff --git a/Development/Scripts/Windows/CallBuildTool.bat b/Development/Scripts/Windows/CallBuildTool.bat
index bfe0e01d0..9f60368c5 100644
--- a/Development/Scripts/Windows/CallBuildTool.bat
+++ b/Development/Scripts/Windows/CallBuildTool.bat
@@ -28,9 +28,9 @@ fc /b Cache\Intermediate\Build\Flax.Build.Files.txt Cache\Intermediate\Build\Fla
if not errorlevel 1 goto SkipClean
copy /y Cache\Intermediate\Build\Flax.Build.Files.txt Cache\Intermediate\Build\Flax.Build.PrevFiles.txt >nul
-%MSBUILD_PATH% /nologo /verbosity:quiet Source\Tools\Flax.Build\Flax.Build.csproj /property:Configuration=Release /property:Platform=AnyCPU /target:Clean
+%MSBUILD_PATH% /nologo /verbosity:quiet Source\Tools\Flax.Build\Flax.Build.csproj /property:Configuration=Release /target:Restore,Clean /property:RestorePackagesConfig=True /p:RuntimeIdentifiers=win-x64
:SkipClean
-%MSBUILD_PATH% /nologo /verbosity:quiet Source\Tools\Flax.Build\Flax.Build.csproj /property:Configuration=Release /property:Platform=AnyCPU /target:Build
+%MSBUILD_PATH% /nologo /verbosity:quiet Source\Tools\Flax.Build\Flax.Build.csproj /property:Configuration=Release /target:Build /property:SelfContained=False /property:RuntimeIdentifiers=win-x64
if errorlevel 1 goto Error_CompilationFailed
Binaries\Tools\Flax.Build.exe %*
diff --git a/Flax.flaxproj b/Flax.flaxproj
index 432045578..d8e86b1ed 100644
--- a/Flax.flaxproj
+++ b/Flax.flaxproj
@@ -2,8 +2,8 @@
"Name": "Flax",
"Version": {
"Major": 1,
- "Minor": 5,
- "Build": 6341
+ "Minor": 6,
+ "Build": 6342
},
"Company": "Flax",
"Copyright": "Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.",
@@ -11,6 +11,7 @@
"EditorTarget": "FlaxEditor",
"Configuration": {
"UseCSharp": true,
- "UseLargeWorlds": false
+ "UseLargeWorlds": false,
+ "UseDotNet": true
}
}
\ No newline at end of file
diff --git a/Flax.sln.DotSettings b/Flax.sln.DotSettings
index a270ffa47..e71c2da2d 100644
--- a/Flax.sln.DotSettings
+++ b/Flax.sln.DotSettings
@@ -321,6 +321,9 @@
True
True
True
+ True
+ True
+ True
True
True
True
diff --git a/README.md b/README.md
index 3cbcfb6fe..dc5abb84f 100644
--- a/README.md
+++ b/README.md
@@ -26,16 +26,12 @@ This repository contains full source code of the Flax Engine (excluding NDA-prot
Follow the instructions below to compile and run the engine from source.
-## Flax plugin for Visual Studio
-
-Flax Visual Studio extension provides better programming workflow, C# scripts debugging functionality and allows to attach to running engine instance to debug C# source. This extension is available to download [here](https://marketplace.visualstudio.com/items?itemName=Flax.FlaxVS).
-
## Windows
-* Install Visual Studio 2015 or newer
+* Install Visual Studio 2022 or newer
* Install Windows 8.1 SDK or newer (via Visual Studio Installer)
* Install Microsoft Visual C++ 2015 v140 toolset or newer (via Visual Studio Installer)
-* Install .Net Framework 4.5.2 SDK/Targeting Pack (via Visual Studio Installer)
+* Install .Net 7 SDK (via Visual Studio Installer or [from web](https://dotnet.microsoft.com/en-us/download/dotnet/7.0))
* Install Git with LFS
* Clone repo (with LFS)
* Run **GenerateProjectFiles.bat**
@@ -44,16 +40,13 @@ Flax Visual Studio extension provides better programming workflow, C# scripts de
* Compile Flax project (hit F7 or CTRL+Shift+B)
* Run Flax (hit F5 key)
-> When building on Windows to support Vulkan rendering, first install the Vulkan SDK then set an environment variable to provide the path to the SDK prior to running GenerateProjectFiles.bat: `set VULKAN_SDK=%sdk_path%`
-
## Linux
* Install Visual Studio Code
-* Install Mono
- * Ubuntu: see the instructions here: ([https://www.mono-project.com/download/stable](https://www.mono-project.com/download/stable))
- * Arch: `sudo pacman -S mono`
-* Install Vulkan SDK
- * Ubuntu: see the instructions here: ([https://vulkan.lunarg.com/](https://vulkan.lunarg.com/))
+* Install .Net 7 SDK ([https://dotnet.microsoft.com/en-us/download/dotnet/7.0](https://dotnet.microsoft.com/en-us/download/dotnet/7.0))
+ * Ubuntu: `sudo apt install dotnet-sdk-7.0`
+* Install Vulkan SDK ([https://vulkan.lunarg.com/](https://vulkan.lunarg.com/))
+ * Ubuntu: `sudo apt install vulkan-sdk`
* Arch: `sudo pacman -S spirv-tools vulkan-headers vulkan-tools vulkan-validation-layers`
* Install Git with LFS
* Ubuntu: `sudo apt-get install git git-lfs`
@@ -73,13 +66,31 @@ Flax Visual Studio extension provides better programming workflow, C# scripts de
## Mac
* Install XCode
-* Install Mono ([https://www.mono-project.com/download/stable](https://www.mono-project.com/download/stable))
+* Install .Net 7 SDK ([https://dotnet.microsoft.com/en-us/download/dotnet/7.0](https://dotnet.microsoft.com/en-us/download/dotnet/7.0))
* Install Vulkan SDK ([https://vulkan.lunarg.com/](https://vulkan.lunarg.com/))
* Clone repo (with LFS)
* Run `GenerateProjectFiles.command`
* Open workspace with XCode or Visual Studio Code
* Build and run (configuration `Editor.Mac.Development`)
+#### Troubleshooting
+
+* `Could not execute because the specified command or file was not found.`
+
+Restart PC - ensure DotNet is added to PATH for command line tools execution.
+
+* `Microsoft.NET.TargetFrameworkInference.targets(141,5): error NETSDK1045: The current .NET SDK does not support targeting .NET 7.0. Either target .NET 5.0 or lower, or use a version of the .NET SDK that supports .NET 7.0`
+
+Use Visual Studio 2022, older versions are not supported by .NET SDK 7.
+
+* `Building for Windows without Vulkan rendering backend (Vulkan SDK is missing)`
+
+Install the Vulkan SDK then set an environment variable to provide the path to the SDK prior to running GenerateProjectFiles.bat: `set VULKAN_SDK=%sdk_path%`.
+
+* `The NuGetSdkResolver did not resolve this SDK`
+
+Install `.NET SDK`, `NuGet package manager` and `NuGet targets and build tasks` in Visual Studio components.
+
## Workspace directory
- **Binaries/** - executable files
@@ -93,7 +104,6 @@ Flax Visual Studio extension provides better programming workflow, C# scripts de
- **Content/** - assets and binary files used by the engine and editor
- **Development/** - engine development files
- **Scripts/** - utility scripts
-- **packages/** - NuGet packages cache location
- **Source/** - source code location
- **Editor/** - Flax Editor source code
- **Engine/** - Flax Engine source code
@@ -103,7 +113,6 @@ Flax Visual Studio extension provides better programming workflow, C# scripts de
- ***PlatformName*/** - per-platform files
- **Binaries/** - per-platform binaries
- **Game/** - Flax Game binaries
- - **Mono/** - Mono runtime files and data
- **ThirdParty/** - prebuilt 3rd Party binaries
- **Shaders/** - shaders source code
- **ThirdParty/** - 3rd Party source code
diff --git a/Source/Editor/Analytics/EditorAnalytics.cpp b/Source/Editor/Analytics/EditorAnalytics.cpp
index 8e5ac4126..913efd91c 100644
--- a/Source/Editor/Analytics/EditorAnalytics.cpp
+++ b/Source/Editor/Analytics/EditorAnalytics.cpp
@@ -1,49 +1,103 @@
// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.
#include "EditorAnalytics.h"
-#include "EditorAnalyticsController.h"
+#include "Editor/Editor.h"
+#include "Editor/ProjectInfo.h"
+#include "Editor/Cooker/GameCooker.h"
+#include "Engine/Threading/Task.h"
#include "Engine/Threading/Threading.h"
#include "Engine/Platform/FileSystem.h"
+#include "Engine/Platform/MemoryStats.h"
#include "Engine/Core/Log.h"
#include "Engine/Core/Math/Vector2.h"
#include "Engine/Core/Types/DateTime.h"
#include "Engine/Core/Types/TimeSpan.h"
-#include "Editor/Editor.h"
-#include "Editor/ProjectInfo.h"
#include "Engine/Engine/EngineService.h"
#include "Engine/Engine/Globals.h"
#include "Engine/Graphics/GPUDevice.h"
#include "Engine/Utilities/StringConverter.h"
+#include "Engine/Utilities/TextWriter.h"
+#include "Engine/ShadowsOfMordor/Builder.h"
+#include "Engine/Profiler/ProfilerCPU.h"
#include "FlaxEngine.Gen.h"
-#include
+#include
-#define FLAX_EDITOR_GOOGLE_ID "UA-88357703-3"
+// Docs:
+// https://developers.google.com/analytics/devguides/collection/ga4
+// https://developers.google.com/analytics/devguides/collection/protocol/ga4
-// Helper doc: https://developers.google.com/analytics/devguides/collection/protocol/v1/parameters
+// [GA4] Flax Editor
+#define GA_MEASUREMENT_ID "G-2SNY6RW6VX"
+#define GA_API_SECRET "wFlau4khTPGFRnx-AIZ1zg"
+#define GA_DEBUG 0
+#if GA_DEBUG
+#define GA_URL "https://www.google-analytics.com/debug/mp/collect"
+#else
+#define GA_URL "https://www.google-analytics.com/mp/collect"
+#endif
-namespace EditorAnalyticsImpl
+namespace
{
- UATracker Tracker = nullptr;
-
+ StringAnsi Url;
StringAnsi ClientId;
- StringAnsi ProjectName;
- StringAnsi ScreenResolution;
- StringAnsi UserLanguage;
- StringAnsi GPU;
DateTime SessionStartTime;
-
CriticalSection Locker;
bool IsSessionActive = false;
- EditorAnalyticsController Controller;
- Array TmpBuffer;
+ TextWriterANSI JsonBuffer;
+ curl_slist* CurlHttpHeadersList = nullptr;
}
-using namespace EditorAnalyticsImpl;
+size_t curl_null_data_handler(char* ptr, size_t size, size_t nmemb, void* userdata)
+{
+ return nmemb * size;
+}
+
+void RegisterGameCookingStart(GameCooker::EventType type)
+{
+ if (type == GameCooker::EventType::BuildStarted)
+ {
+ auto& data = *GameCooker::GetCurrentData();
+ StringAnsi name = "Build " + StringAnsi(ToString(data.Platform));
+ const Pair params[1] = { { "GameCooker", name.Get() } };
+ EditorAnalytics::SendEvent("Actions", ToSpan(params, ARRAY_COUNT(params)));
+ }
+}
+
+void RegisterLightmapsBuildingStart()
+{
+ const Pair params[1] = { { "ShadowsOfMordor", "Build" }, };
+ EditorAnalytics::SendEvent("Actions", ToSpan(params, ARRAY_COUNT(params)));
+}
+
+void RegisterError(LogType type, const StringView& msg)
+{
+ if (type == LogType::Error && false)
+ {
+ StringAnsi value(msg);
+ const int32 MaxLength = 300;
+ if (msg.Length() > MaxLength)
+ value = value.Substring(0, MaxLength);
+ value.Replace('\n', ' ');
+ value.Replace('\r', ' ');
+ const Pair params[1] = { { "Error", value.Get() }, };
+ EditorAnalytics::SendEvent("Errors", ToSpan(params, ARRAY_COUNT(params)));
+ }
+ else if (type == LogType::Fatal)
+ {
+ StringAnsi value(msg);
+ const int32 MaxLength = 300;
+ if (msg.Length() > MaxLength)
+ value = value.Substring(0, MaxLength);
+ value.Replace('\n', ' ');
+ value.Replace('\r', ' ');
+ const Pair params[1] = { { "Fatal", value.Get() }, };
+ EditorAnalytics::SendEvent("Errors", ToSpan(params, ARRAY_COUNT(params)));
+ }
+}
class EditorAnalyticsService : public EngineService
{
public:
-
EditorAnalyticsService()
: EngineService(TEXT("Editor Analytics"))
{
@@ -57,192 +111,141 @@ EditorAnalyticsService EditorAnalyticsServiceInstance;
bool EditorAnalytics::IsSessionActive()
{
- return EditorAnalyticsImpl::IsSessionActive;
+ return ::IsSessionActive;
}
void EditorAnalytics::StartSession()
{
ScopeLock lock(Locker);
-
- if (EditorAnalyticsImpl::IsSessionActive)
+ if (::IsSessionActive)
return;
+ PROFILE_CPU();
// Prepare client metadata
- if (ClientId.IsEmpty())
- {
- ClientId = Platform::GetUniqueDeviceId().ToString(Guid::FormatType::N).ToStringAnsi();
- }
- if (ScreenResolution.IsEmpty())
- {
- const auto desktopSize = Platform::GetDesktopSize();
- ScreenResolution = StringAnsi(StringUtils::ToString((int32)desktopSize.X)) + "x" + StringAnsi(StringUtils::ToString((int32)desktopSize.Y));
- }
- if (UserLanguage.IsEmpty())
- {
- UserLanguage = Platform::GetUserLocaleName().ToStringAnsi();
- }
- if (GPU.IsEmpty())
- {
- const auto gpu = GPUDevice::Instance;
- if (gpu && gpu->GetState() == GPUDevice::DeviceState::Ready)
- GPU = StringAsANSI<>(gpu->GetAdapter()->GetDescription().GetText()).Get();
- }
- if (ProjectName.IsEmpty())
- {
- ProjectName = Editor::Project->Name.ToStringAnsi();
- }
+ ClientId = Platform::GetUniqueDeviceId().ToString(Guid::FormatType::N).ToStringAnsi();
+ StringAnsi ProjectName = Editor::Project->Name.ToStringAnsi();
+ const auto desktopSize = Platform::GetDesktopSize();
+ StringAnsi ScreenResolution = StringAnsi::Format("{0}x{1}", (int32)desktopSize.X, (int32)desktopSize.Y);
+ const auto memoryStats = Platform::GetMemoryStats();
+ StringAnsi Memory = StringAnsi::Format("{0} GB", (int32)(memoryStats.TotalPhysicalMemory / 1024 / 1024 / 1000));
+ StringAnsi UserLocale = Platform::GetUserLocaleName().ToStringAnsi();
+ StringAnsi GPU;
+ if (GPUDevice::Instance && GPUDevice::Instance->GetState() == GPUDevice::DeviceState::Ready)
+ GPU = StringAsANSI<>(GPUDevice::Instance->GetAdapter()->GetDescription().GetText()).Get();
SessionStartTime = DateTime::Now();
-
- // Initialize the analytics tracker
- Tracker = createTracker(FLAX_EDITOR_GOOGLE_ID, ClientId.Get(), nullptr);
- Tracker->user_agent = "Flax Editor";
-
- // Store these options permanently (for the lifetime of the tracker)
- setTrackerOption(Tracker, UA_OPTION_QUEUE, 1);
- UASettings GlobalSettings =
- {
- {
- { UA_DOCUMENT_PATH, 0, "Flax Editor" },
- { UA_DOCUMENT_TITLE, 0, "Flax Editor" },
+ StringAnsiView EngineVersion = FLAXENGINE_VERSION_TEXT;
#if PLATFORM_WINDOWS
- { UA_USER_AGENT, 0, "Windows " FLAXENGINE_VERSION_TEXT },
+ StringAnsiView PlatformName = "Windows";
#elif PLATFORM_LINUX
- { UA_USER_AGENT, 0, "Linux " FLAXENGINE_VERSION_TEXT },
+ StringAnsiView PlatformName = "Linux";
#elif PLATFORM_MAC
- { UA_USER_AGENT, 0, "Mac " FLAXENGINE_VERSION_TEXT },
+ StringAnsiView PlatformName = "Mac";
#else
#error "Unknown platform"
#endif
- { UA_ANONYMIZE_IP, 0, "0" },
- { UA_APP_ID, 0, "Flax Editor " FLAXENGINE_VERSION_TEXT },
- { UA_APP_INSTALLER_ID, 0, "Flax Editor" },
- { UA_APP_NAME, 0, "Flax Editor" },
- { UA_APP_VERSION, 0, FLAXENGINE_VERSION_TEXT },
- { UA_SCREEN_NAME, 0, "Flax Editor " FLAXENGINE_VERSION_TEXT },
- { UA_SCREEN_RESOLUTION, 0, ScreenResolution.Get() },
- { UA_USER_LANGUAGE, 0, UserLanguage.Get() },
- }
- };
- setParameters(Tracker, &GlobalSettings);
- // Send the initial session event
- UAOptions sessionViewOptions =
+ // Initialize HTTP
+ Url = StringAnsi::Format("{0}?measurement_id={1}&api_secret={2}", GA_URL, GA_MEASUREMENT_ID, GA_API_SECRET);
+ curl_global_init(CURL_GLOBAL_ALL);
+ CurlHttpHeadersList = curl_slist_append(nullptr, "Content-Type: application/json");
+ ::IsSessionActive = true;
+
+ // Start session
{
- {
- { UA_EVENT_CATEGORY, 0, "Session" },
- { UA_EVENT_ACTION, 0, "Start Editor" },
- { UA_EVENT_LABEL, 0, "Start Editor" },
- { UA_SESSION_CONTROL, 0, "start" },
- { UA_DOCUMENT_TITLE, 0, ProjectName.Get() },
- }
- };
- sendTracking(Tracker, UA_SCREENVIEW, &sessionViewOptions);
-
- EditorAnalyticsImpl::IsSessionActive = true;
-
- Controller.Init();
-
- // Report GPU model
- if (GPU.HasChars())
- {
- SendEvent("Telemetry", "GPU.Model", GPU.Get());
+ const Pair params[1] = { { "Project", ProjectName.Get() }, };
+ SendEvent("Session", ToSpan(params, ARRAY_COUNT(params)));
}
+
+ // Report telemetry stats
+#define SEND_TELEMETRY(name, value) \
+ if (value.HasChars()) \
+ { \
+ const Pair params[1] = { { name, value.Get() } }; \
+ SendEvent("Telemetry", ToSpan(params, ARRAY_COUNT(params))); \
+ }
+ SEND_TELEMETRY("Platform", PlatformName);
+ SEND_TELEMETRY("GPU", GPU);
+ SEND_TELEMETRY("Memory", Memory);
+ SEND_TELEMETRY("Locale", UserLocale);
+ SEND_TELEMETRY("Screen", ScreenResolution);
+ SEND_TELEMETRY("Version", EngineVersion);
+#undef SEND_TELEMETRY
+
+ // Bind events
+ GameCooker::OnEvent.Bind();
+ ShadowsOfMordor::Builder::Instance()->OnBuildStarted.Bind();
+ Log::Logger::OnError.Bind();
}
void EditorAnalytics::EndSession()
{
ScopeLock lock(Locker);
-
- if (!EditorAnalyticsImpl::IsSessionActive)
+ if (!::IsSessionActive)
return;
+ PROFILE_CPU();
- Controller.Cleanup();
+ // Unbind events
+ GameCooker::OnEvent.Unbind();
+ ShadowsOfMordor::Builder::Instance()->OnBuildStarted.Unbind();
+ Log::Logger::OnError.Unbind();
- StringAnsi sessionLength = StringAnsi::Format("{0}", (int32)(DateTime::Now() - SessionStartTime).GetTotalSeconds());
-
- // Send the end session event
- UAOptions sessionEventOptions =
+ // End session
{
+ StringAnsi sessionLength = StringAnsi::Format("{}", (int32)(DateTime::Now() - SessionStartTime).GetTotalSeconds());
+ const Pair params[1] =
{
- { UA_EVENT_CATEGORY, 0, "Session" },
- { UA_EVENT_ACTION, 0, "Session Length" },
- { UA_EVENT_LABEL, 0, "Session Length" },
- { UA_EVENT_VALUE, 0, sessionLength.Get() },
- { UA_CUSTOM_DIMENSION, 1, "Session Length" },
- { UA_CUSTOM_METRIC, 1, sessionLength.Get() },
- }
- };
- sendTracking(Tracker, UA_EVENT, &sessionEventOptions);
-
- // Send the end session event
- UAOptions sessionViewOptions =
- {
- {
- { UA_EVENT_CATEGORY, 0, "Session" },
- { UA_EVENT_ACTION, 0, "End Editor" },
- { UA_EVENT_LABEL, 0, "End Editor" },
- { UA_EVENT_VALUE, 0, sessionLength.Get() },
- { UA_SESSION_CONTROL, 0, "end" },
- }
- };
- sendTracking(Tracker, UA_SCREENVIEW, &sessionViewOptions);
+ { "Duration", sessionLength.Get() },
+ };
+ SendEvent("Session", ToSpan(params, ARRAY_COUNT(params)));
+ }
// Cleanup
- removeTracker(Tracker);
- Tracker = nullptr;
-
- EditorAnalyticsImpl::IsSessionActive = false;
+ curl_slist_free_all(CurlHttpHeadersList);
+ CurlHttpHeadersList = nullptr;
+ curl_global_cleanup();
+ ::IsSessionActive = false;
}
-void EditorAnalytics::SendEvent(const char* category, const char* name, const char* label)
+void EditorAnalytics::SendEvent(const char* name, Span> parameters)
{
ScopeLock lock(Locker);
-
- if (!EditorAnalyticsImpl::IsSessionActive)
+ if (!::IsSessionActive)
return;
+ PROFILE_CPU();
- UAOptions opts =
+ // Create Json request contents
+ JsonBuffer.Clear();
+ JsonBuffer.Write("{ \"client_id\": \"");
+ JsonBuffer.Write(ClientId);
+ JsonBuffer.Write("\", \"events\": [ { \"name\": \"");
+ JsonBuffer.Write(name);
+ JsonBuffer.Write("\", \"params\": {");
+ for (int32 i = 0; i < parameters.Length(); i++)
{
- {
- { UA_EVENT_CATEGORY, 0, (char*)category },
- { UA_EVENT_ACTION, 0, (char*)name },
- { UA_EVENT_LABEL, 0, (char*)label },
- }
- };
+ if (i != 0)
+ JsonBuffer.Write(",");
+ const auto& e = parameters[i];
+ JsonBuffer.Write("\"");
+ JsonBuffer.Write(e.First);
+ JsonBuffer.Write("\":\"");
+ JsonBuffer.Write(e.Second);
+ JsonBuffer.Write("\"");
+ }
+ JsonBuffer.Write("}}]}");
+ const StringAnsiView json((const char*)JsonBuffer.GetBuffer()->GetHandle(), (int32)JsonBuffer.GetBuffer()->GetPosition());
- sendTracking(Tracker, UA_EVENT, &opts);
-}
-
-void EditorAnalytics::SendEvent(const char* category, const char* name, const StringView& label)
-{
- SendEvent(category, name, label.Get());
-}
-
-void EditorAnalytics::SendEvent(const char* category, const char* name, const Char* label)
-{
- ScopeLock lock(Locker);
-
- if (!EditorAnalyticsImpl::IsSessionActive)
- return;
-
- ASSERT(category && name && label);
-
- const int32 labelLength = StringUtils::Length(label);
- TmpBuffer.Clear();
- TmpBuffer.Resize(labelLength + 1);
- StringUtils::ConvertUTF162ANSI(label, TmpBuffer.Get(), labelLength);
- TmpBuffer[labelLength] = 0;
-
- UAOptions opts =
- {
- {
- { UA_EVENT_CATEGORY, 0, (char*)category },
- { UA_EVENT_ACTION, 0, (char*)name },
- { UA_EVENT_LABEL, 0, (char*)TmpBuffer.Get() },
- }
- };
-
- sendTracking(Tracker, UA_EVENT, &opts);
+ // Send HTTP request
+ CURL* curl = curl_easy_init();
+ curl_easy_setopt(curl, CURLOPT_POST, 1);
+ curl_easy_setopt(curl, CURLOPT_URL, Url.Get());
+ curl_easy_setopt(curl, CURLOPT_HTTPHEADER, CurlHttpHeadersList);
+ curl_easy_setopt(curl, CURLOPT_POSTFIELDS, json.Get());
+ curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE, json.Length());
+ curl_easy_setopt(curl, CURLOPT_USERAGENT, "Flax Editor");
+ curl_easy_setopt(curl, CURLOPT_WRITEDATA, curl);
+ curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, curl_null_data_handler);
+ CURLcode res = curl_easy_perform(curl);
+ curl_easy_cleanup(curl);
}
bool EditorAnalyticsService::Init()
@@ -265,8 +268,7 @@ bool EditorAnalyticsService::Init()
}
LOG(Info, "Editor analytics service is enabled. Curl version: {0}", TEXT(LIBCURL_VERSION));
-
- EditorAnalytics::StartSession();
+ Task::StartNew(EditorAnalytics::StartSession);
return false;
}
diff --git a/Source/Editor/Analytics/EditorAnalytics.h b/Source/Editor/Analytics/EditorAnalytics.h
index 6a16020f2..2df4bbcc9 100644
--- a/Source/Editor/Analytics/EditorAnalytics.h
+++ b/Source/Editor/Analytics/EditorAnalytics.h
@@ -10,11 +10,9 @@
class EditorAnalytics
{
public:
-
///
/// Determines whether analytics session is active.
///
- /// true if there is active analytics session running; otherwise, false.
static bool IsSessionActive();
///
@@ -30,24 +28,7 @@ public:
///
/// Sends the custom event.
///
- /// The event category name.
/// The event name.
- /// The event label.
- static void SendEvent(const char* category, const char* name, const char* label = nullptr);
-
- ///
- /// Sends the custom event.
- ///
- /// The event category name.
- /// The event name.
- /// The event label.
- static void SendEvent(const char* category, const char* name, const StringView& label);
-
- ///
- /// Sends the custom event.
- ///
- /// The event category name.
- /// The event name.
- /// The event label.
- static void SendEvent(const char* category, const char* name, const Char* label);
+ /// The event parameters (key and value pairs).
+ static void SendEvent(const char* name, Span> parameters);
};
diff --git a/Source/Editor/Analytics/EditorAnalyticsController.cpp b/Source/Editor/Analytics/EditorAnalyticsController.cpp
deleted file mode 100644
index 0e1e461cf..000000000
--- a/Source/Editor/Analytics/EditorAnalyticsController.cpp
+++ /dev/null
@@ -1,69 +0,0 @@
-// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.
-
-#include "EditorAnalyticsController.h"
-#include "Editor/Cooker/GameCooker.h"
-#include "EditorAnalytics.h"
-#include "Engine/ShadowsOfMordor/Builder.h"
-
-void RegisterGameCookingStart(GameCooker::EventType type)
-{
- auto& data = *GameCooker::GetCurrentData();
- auto platform = ToString(data.Platform);
- if (type == GameCooker::EventType::BuildStarted)
- {
- EditorAnalytics::SendEvent("Actions", "GameCooker.Start", platform);
- }
- else if (type == GameCooker::EventType::BuildFailed)
- {
- EditorAnalytics::SendEvent("Actions", "GameCooker.Failed", platform);
- }
- else if (type == GameCooker::EventType::BuildDone)
- {
- EditorAnalytics::SendEvent("Actions", "GameCooker.End", platform);
- }
-}
-
-void RegisterLightmapsBuildingStart()
-{
- EditorAnalytics::SendEvent("Actions", "ShadowsOfMordor.Build", "ShadowsOfMordor.Build");
-}
-
-void RegisterError(LogType type, const StringView& msg)
-{
- if (type == LogType::Error && false)
- {
- String value = msg.ToString();
- const int32 MaxLength = 300;
- if (msg.Length() > MaxLength)
- value = value.Substring(0, MaxLength);
- value.Replace('\n', ' ');
- value.Replace('\r', ' ');
-
- EditorAnalytics::SendEvent("Errors", "Log.Error", value);
- }
- else if (type == LogType::Fatal)
- {
- String value = msg.ToString();
- const int32 MaxLength = 300;
- if (msg.Length() > MaxLength)
- value = value.Substring(0, MaxLength);
- value.Replace('\n', ' ');
- value.Replace('\r', ' ');
-
- EditorAnalytics::SendEvent("Errors", "Log.Fatal", value);
- }
-}
-
-void EditorAnalyticsController::Init()
-{
- GameCooker::OnEvent.Bind();
- ShadowsOfMordor::Builder::Instance()->OnBuildStarted.Bind();
- Log::Logger::OnError.Bind();
-}
-
-void EditorAnalyticsController::Cleanup()
-{
- GameCooker::OnEvent.Unbind();
- ShadowsOfMordor::Builder::Instance()->OnBuildStarted.Unbind();
- Log::Logger::OnError.Unbind();
-}
diff --git a/Source/Editor/Analytics/EditorAnalyticsController.h b/Source/Editor/Analytics/EditorAnalyticsController.h
deleted file mode 100644
index 71656caee..000000000
--- a/Source/Editor/Analytics/EditorAnalyticsController.h
+++ /dev/null
@@ -1,21 +0,0 @@
-// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.
-
-#pragma once
-
-///
-/// The controller object for the tracking events for the editor analytics.
-///
-class EditorAnalyticsController
-{
-public:
-
- ///
- /// Starts the service (registers to event handlers).
- ///
- void Init();
-
- ///
- /// Ends the service (unregisters to event handlers).
- ///
- void Cleanup();
-};
diff --git a/Source/Editor/Content/Import/AudioImportSettings.cs b/Source/Editor/Content/Import/AudioImportSettings.cs
index d71dbd114..2fc0a1795 100644
--- a/Source/Editor/Content/Import/AudioImportSettings.cs
+++ b/Source/Editor/Content/Import/AudioImportSettings.cs
@@ -2,9 +2,9 @@
using System.ComponentModel;
using System.Reflection;
-using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using FlaxEngine;
+using FlaxEngine.Interop;
namespace FlaxEditor.Content.Import
{
@@ -93,6 +93,7 @@ namespace FlaxEditor.Content.Import
[StructLayout(LayoutKind.Sequential)]
internal struct InternalOptions
{
+ [MarshalAs(UnmanagedType.I1)]
public AudioFormat Format;
public byte DisableStreaming;
public byte Is3D;
@@ -144,7 +145,7 @@ namespace FlaxEditor.Content.Import
/// Audio asset import entry.
///
///
- public class AudioImportEntry : AssetImportEntry
+ public partial class AudioImportEntry : AssetImportEntry
{
private AudioImportSettings _settings = new AudioImportSettings();
@@ -182,8 +183,9 @@ namespace FlaxEditor.Content.Import
#region Internal Calls
- [MethodImpl(MethodImplOptions.InternalCall)]
- internal static extern bool Internal_GetAudioImportOptions(string path, out AudioImportSettings.InternalOptions result);
+ [LibraryImport("FlaxEngine", EntryPoint = "AudioImportEntryInternal_GetAudioImportOptions", StringMarshalling = StringMarshalling.Custom, StringMarshallingCustomType = typeof(StringMarshaller))]
+ [return: MarshalAs(UnmanagedType.U1)]
+ internal static partial bool Internal_GetAudioImportOptions(string path, out AudioImportSettings.InternalOptions result);
#endregion
}
diff --git a/Source/Editor/Content/Import/ModelImportEntry.cs b/Source/Editor/Content/Import/ModelImportEntry.cs
index b17c8314d..bbb384562 100644
--- a/Source/Editor/Content/Import/ModelImportEntry.cs
+++ b/Source/Editor/Content/Import/ModelImportEntry.cs
@@ -1,88 +1,47 @@
// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.
-using System.ComponentModel;
-using System.Runtime.CompilerServices;
-using System.Runtime.InteropServices;
+using System.Collections.Generic;
+using FlaxEditor.CustomEditors.Editors;
+using FlaxEditor.Scripting;
using FlaxEngine;
+using FlaxEngine.Tools;
+
+namespace FlaxEngine.Tools
+{
+ partial class ModelTool
+ {
+ partial struct Options
+ {
+ private bool ShowGeometry => Type == ModelTool.ModelType.Model || Type == ModelTool.ModelType.SkinnedModel;
+ private bool ShowModel => Type == ModelTool.ModelType.Model;
+ private bool ShowSkinnedModel => Type == ModelTool.ModelType.SkinnedModel;
+ private bool ShowAnimation => Type == ModelTool.ModelType.Animation;
+ private bool ShowSmoothingNormalsAngle => ShowGeometry && CalculateNormals;
+ private bool ShowSmoothingTangentsAngle => ShowGeometry && CalculateTangents;
+ private bool ShowFramesRange => ShowAnimation && Duration == ModelTool.AnimationDuration.Custom;
+ }
+ }
+}
+
+namespace FlaxEditor.CustomEditors.Dedicated
+{
+ ///
+ /// Custom editor for .
+ ///
+ [CustomEditor(typeof(FlaxEngine.Tools.ModelTool.Options)), DefaultEditor]
+ public class ModelToolOptionsEditor : GenericEditor
+ {
+ ///
+ protected override List GetItemsForType(ScriptType type)
+ {
+ // Show both fields and properties
+ return GetItemsForType(type, true, true);
+ }
+ }
+}
namespace FlaxEditor.Content.Import
{
- ///
- /// Importing model lightmap UVs source
- ///
- [HideInEditor]
- public enum ModelLightmapUVsSource : int
- {
- ///
- /// No lightmap UVs.
- ///
- Disable = 0,
-
- ///
- /// Generate lightmap UVs from model geometry.
- ///
- Generate = 1,
-
- ///
- /// The texcoords channel 0.
- ///
- Channel0 = 2,
-
- ///
- /// The texcoords channel 1.
- ///
- Channel1 = 3,
-
- ///
- /// The texcoords channel 2.
- ///
- Channel2 = 4,
-
- ///
- /// The texcoords channel 3.
- ///
- Channel3 = 5
- }
-
- ///
- /// Declares the imported data type.
- ///
- [HideInEditor]
- public enum ModelType : int
- {
- ///
- /// The model asset.
- ///
- Model = 0,
-
- ///
- /// The skinned model asset.
- ///
- SkinnedModel = 1,
-
- ///
- /// The animation asset.
- ///
- Animation = 2,
- }
-
- ///
- /// Declares the imported animation clip duration.
- ///
- [HideInEditor]
- public enum AnimationDuration : int
- {
- ///
- /// The imported duration.
- ///
- Imported = 0,
-
- ///
- /// The custom duration specified via keyframes range.
- ///
- Custom = 1,
- }
-
///
/// Proxy object to present model import settings in .
///
@@ -90,431 +49,10 @@ namespace FlaxEditor.Content.Import
public class ModelImportSettings
{
///
- /// Type of the imported asset.
+ /// The settings data.
///
- [EditorOrder(0)]
- public ModelType Type { get; set; } = ModelType.Model;
-
- ///
- /// Enable model normal vectors recalculating.
- ///
- [EditorDisplay("Geometry"), VisibleIf(nameof(ShowGeometry))]
- [EditorOrder(20), DefaultValue(false)]
- public bool CalculateNormals { get; set; } = false;
-
- ///
- /// Specifies the maximum angle (in degrees) that may be between two face normals at the same vertex position that their are smoothed together. The default value is 175.
- ///
- [EditorDisplay("Geometry"), VisibleIf(nameof(ShowSmoothingNormalsAngle))]
- [EditorOrder(30), DefaultValue(175.0f), Limit(0, 175, 0.1f)]
- public float SmoothingNormalsAngle { get; set; } = 175.0f;
-
- private bool ShowSmoothingNormalsAngle => ShowGeometry && CalculateNormals;
-
- ///
- /// If checked, the imported normal vectors of the mesh will be flipped (scaled by -1).
- ///
- [EditorDisplay("Geometry"), VisibleIf(nameof(ShowGeometry))]
- [EditorOrder(35), DefaultValue(false)]
- public bool FlipNormals { get; set; } = false;
-
- ///
- /// Enable model tangent vectors recalculating.
- ///
- [EditorDisplay("Geometry"), VisibleIf(nameof(ShowGeometry))]
- [EditorOrder(40), DefaultValue(false)]
- public bool CalculateTangents { get; set; } = false;
-
- ///
- /// Specifies the maximum angle (in degrees) that may be between two vertex tangents that their tangents and bi-tangents are smoothed. The default value is 45.
- ///
- [EditorDisplay("Geometry"), VisibleIf(nameof(ShowSmoothingTangentsAngle))]
- [EditorOrder(45), DefaultValue(45.0f), Limit(0, 45, 0.1f)]
- public float SmoothingTangentsAngle { get; set; } = 45.0f;
-
- private bool ShowSmoothingTangentsAngle => ShowGeometry && CalculateTangents;
-
- ///
- /// Enable/disable meshes geometry optimization.
- ///
- [EditorDisplay("Geometry"), VisibleIf(nameof(ShowGeometry))]
- [EditorOrder(50), DefaultValue(true)]
- public bool OptimizeMeshes { get; set; } = true;
-
- ///
- /// Enable/disable geometry merge for meshes with the same materials.
- ///
- [EditorDisplay("Geometry"), VisibleIf(nameof(ShowGeometry))]
- [EditorOrder(60), DefaultValue(true)]
- public bool MergeMeshes { get; set; } = true;
-
- ///
- /// Enable/disable importing meshes Level of Details.
- ///
- [EditorDisplay("Geometry", "Import LODs"), VisibleIf(nameof(ShowGeometry))]
- [EditorOrder(70), DefaultValue(true)]
- public bool ImportLODs { get; set; } = true;
-
- ///
- /// Enable/disable importing vertex colors (channel 0 only).
- ///
- [EditorDisplay("Geometry"), VisibleIf(nameof(ShowModel))]
- [EditorOrder(80), DefaultValue(true)]
- public bool ImportVertexColors { get; set; } = true;
-
- ///
- /// Enable/disable importing blend shapes (morph targets).
- ///
- [EditorDisplay("Geometry"), VisibleIf(nameof(ShowSkinnedModel))]
- [EditorOrder(85), DefaultValue(false)]
- public bool ImportBlendShapes { get; set; } = false;
-
- ///
- /// The lightmap UVs source.
- ///
- [EditorDisplay("Geometry", "Lightmap UVs Source"), VisibleIf(nameof(ShowModel))]
- [EditorOrder(90), DefaultValue(ModelLightmapUVsSource.Disable)]
- public ModelLightmapUVsSource LightmapUVsSource { get; set; } = ModelLightmapUVsSource.Disable;
-
- ///
- /// If specified, all meshes which name starts with this prefix will be imported as a separate collision data (excluded used for rendering).
- ///
- [EditorDisplay("Geometry"), VisibleIf(nameof(ShowGeometry))]
- [EditorOrder(100), DefaultValue("")]
- public string CollisionMeshesPrefix { get; set; }
-
- ///
- /// Custom uniform import scale.
- ///
- [EditorOrder(500), DefaultValue(1.0f), EditorDisplay("Transform")]
- public float Scale { get; set; } = 1.0f;
-
- ///
- /// Custom import geometry rotation.
- ///
- [DefaultValue(typeof(Quaternion), "0,0,0,1")]
- [EditorOrder(510), EditorDisplay("Transform")]
- public Quaternion Rotation { get; set; } = Quaternion.Identity;
-
- ///
- /// Custom import geometry offset.
- ///
- [DefaultValue(typeof(Float3), "0,0,0")]
- [EditorOrder(520), EditorDisplay("Transform")]
- public Float3 Translation { get; set; } = Float3.Zero;
-
- ///
- /// If checked, the imported geometry will be shifted to the center of mass.
- ///
- [EditorOrder(530), DefaultValue(false), EditorDisplay("Transform")]
- public bool CenterGeometry { get; set; } = false;
-
- ///
- /// Imported animation duration mode. Can use the original value or overriden by settings.
- ///
- [EditorDisplay("Animation"), VisibleIf(nameof(ShowAnimation))]
- [EditorOrder(1000), DefaultValue(AnimationDuration.Imported)]
- public AnimationDuration Duration { get; set; } = AnimationDuration.Imported;
-
- ///
- /// Imported animation first frame index. Used only if Duration mode is set to Custom.
- ///
- [EditorDisplay("Animation"), VisibleIf(nameof(ShowFramesRange))]
- [EditorOrder(1010), DefaultValue(0.0f), Limit(0)]
- public float FramesRangeStart { get; set; } = 0;
-
- ///
- /// Imported animation last frame index. Used only if Duration mode is set to Custom.
- ///
- [EditorDisplay("Animation"), VisibleIf(nameof(ShowFramesRange))]
- [EditorOrder(1020), DefaultValue(0.0f), Limit(0)]
- public float FramesRangeEnd { get; set; } = 0;
-
- private bool ShowFramesRange => ShowAnimation && Duration == AnimationDuration.Custom;
-
- ///
- /// The imported animation default frame rate. Can specify the default frames per second amount for imported animation. If value is 0 then the original animation frame rate will be used.
- ///
- [EditorDisplay("Animation"), VisibleIf(nameof(ShowAnimation))]
- [EditorOrder(1025), DefaultValue(0.0f), Limit(0, 1000, 0.01f)]
- public float DefaultFrameRate { get; set; } = 0.0f;
-
- ///
- /// The imported animation sampling rate. If value is 0 then the original animation speed will be used.
- ///
- [EditorDisplay("Animation"), VisibleIf(nameof(ShowAnimation))]
- [EditorOrder(1030), DefaultValue(0.0f), Limit(0, 1000, 0.01f)]
- public float SamplingRate { get; set; } = 0.0f;
-
- ///
- /// The imported animation will have removed tracks with no keyframes or unspecified data.
- ///
- [EditorDisplay("Animation"), VisibleIf(nameof(ShowAnimation))]
- [EditorOrder(1040), DefaultValue(true)]
- public bool SkipEmptyCurves { get; set; } = true;
-
- ///
- /// The imported animation channels will be optimized to remove redundant keyframes.
- ///
- [EditorDisplay("Animation"), VisibleIf(nameof(ShowAnimation))]
- [EditorOrder(1050), DefaultValue(true)]
- public bool OptimizeKeyframes { get; set; } = true;
-
- ///
- /// If checked, the importer will import scale animation tracks (otherwise scale animation will be ignored).
- ///
- [EditorDisplay("Animation"), VisibleIf(nameof(ShowAnimation))]
- [EditorOrder(1055), DefaultValue(false)]
- public bool ImportScaleTracks { get; set; } = false;
-
- ///
- /// Enables root motion extraction support from this animation.
- ///
- [EditorDisplay("Animation"), VisibleIf(nameof(ShowAnimation))]
- [EditorOrder(1060), DefaultValue(false)]
- public bool EnableRootMotion { get; set; } = false;
-
- ///
- /// The custom node name to be used as a root motion source. If not specified the actual root node will be used.
- ///
- [EditorDisplay("Animation"), VisibleIf(nameof(ShowAnimation))]
- [EditorOrder(1070), DefaultValue(typeof(string), "")]
- public string RootNodeName { get; set; }
-
- ///
- /// If checked, the importer will generate a sequence of LODs based on the base LOD index.
- ///
- [EditorDisplay("Level Of Detail", "Generate LODs"), VisibleIf(nameof(ShowGeometry))]
- [EditorOrder(1100), DefaultValue(false)]
- public bool GenerateLODs { get; set; } = false;
-
- ///
- /// The index of the LOD from the source model data to use as a reference for following LODs generation.
- ///
- [EditorDisplay("Level Of Detail", "Base LOD"), VisibleIf(nameof(ShowGeometry))]
- [EditorOrder(1110), DefaultValue(0), Limit(0, Model.MaxLODs - 1)]
- public int BaseLOD { get; set; } = 0;
-
- ///
- /// The amount of LODs to include in the model (all remaining ones starting from Base LOD will be generated).
- ///
- [EditorDisplay("Level Of Detail", "LOD Count"), VisibleIf(nameof(ShowGeometry))]
- [EditorOrder(1120), DefaultValue(4), Limit(1, Model.MaxLODs)]
- public int LODCount { get; set; } = 4;
-
- ///
- /// The target amount of triangles for the generated LOD (based on the higher LOD). Normalized to range 0-1. For instance 0.4 cuts the triangle count to 40%.
- ///
- [EditorDisplay("Level Of Detail"), VisibleIf(nameof(ShowGeometry))]
- [EditorOrder(1130), DefaultValue(0.5f), Limit(0, 1, 0.001f)]
- public float TriangleReduction { get; set; } = 0.5f;
-
- ///
- /// If checked, the importer will create materials for model meshes as specified in the file.
- ///
- [EditorDisplay("Materials"), VisibleIf(nameof(ShowGeometry))]
- [EditorOrder(400), DefaultValue(true)]
- public bool ImportMaterials { get; set; } = true;
-
- ///
- /// If checked, the importer will import texture files used by the model and any embedded texture resources.
- ///
- [EditorDisplay("Materials"), VisibleIf(nameof(ShowGeometry))]
- [EditorOrder(410), DefaultValue(true)]
- public bool ImportTextures { get; set; } = true;
-
- ///
- /// If checked, the importer will try to restore the model material slots.
- ///
- [EditorDisplay("Materials", "Restore Materials On Reimport"), VisibleIf(nameof(ShowGeometry))]
- [EditorOrder(420), DefaultValue(true)]
- public bool RestoreMaterialsOnReimport { get; set; } = true;
-
- ///
- /// If checked, enables generation of Signed Distance Field (SDF).
- ///
- [EditorDisplay("SDF"), VisibleIf(nameof(ShowModel))]
- [EditorOrder(1500), DefaultValue(false)]
- public bool GenerateSDF { get; set; } = false;
-
- ///
- /// Resolution scale for generated Signed Distance Field (SDF) texture. Higher values improve accuracy but increase memory usage and reduce performance.
- ///
- [EditorDisplay("SDF"), VisibleIf(nameof(ShowModel))]
- [EditorOrder(1510), DefaultValue(1.0f), Limit(0.0001f, 100.0f)]
- public float SDFResolution { get; set; } = 1.0f;
-
- ///
- /// If checked, the imported mesh/animations are splitted into separate assets. Used if ObjectIndex is set to -1.
- ///
- [EditorOrder(2000), DefaultValue(false), EditorDisplay("Splitting")]
- public bool SplitObjects { get; set; } = false;
-
- ///
- /// The zero-based index for the mesh/animation clip to import. If the source file has more than one mesh/animation it can be used to pick a desire object. Default -1 imports all objects.
- ///
- [EditorOrder(2010), DefaultValue(-1), EditorDisplay("Splitting")]
- public int ObjectIndex { get; set; } = -1;
-
- private bool ShowGeometry => Type == ModelType.Model || Type == ModelType.SkinnedModel;
- private bool ShowModel => Type == ModelType.Model;
- private bool ShowSkinnedModel => Type == ModelType.SkinnedModel;
- private bool ShowAnimation => Type == ModelType.Animation;
-
- [StructLayout(LayoutKind.Sequential)]
- internal struct InternalOptions
- {
- public ModelType Type;
-
- // Geometry
- public byte CalculateNormals;
- public float SmoothingNormalsAngle;
- public byte FlipNormals;
- public float SmoothingTangentsAngle;
- public byte CalculateTangents;
- public byte OptimizeMeshes;
- public byte MergeMeshes;
- public byte ImportLODs;
- public byte ImportVertexColors;
- public byte ImportBlendShapes;
- public ModelLightmapUVsSource LightmapUVsSource;
- public string CollisionMeshesPrefix;
-
- // Transform
- public float Scale;
- public Quaternion Rotation;
- public Float3 Translation;
- public byte CenterGeometry;
-
- // Animation
- public AnimationDuration Duration;
- public float FramesRangeStart;
- public float FramesRangeEnd;
- public float DefaultFrameRate;
- public float SamplingRate;
- public byte SkipEmptyCurves;
- public byte OptimizeKeyframes;
- public byte ImportScaleTracks;
- public byte EnableRootMotion;
- public string RootNodeName;
-
- // Level Of Detail
- public byte GenerateLODs;
- public int BaseLOD;
- public int LODCount;
- public float TriangleReduction;
-
- // Misc
- public byte ImportMaterials;
- public byte ImportTextures;
- public byte RestoreMaterialsOnReimport;
-
- // SDF
- public byte GenerateSDF;
- public float SDFResolution;
-
- // Splitting
- public byte SplitObjects;
- public int ObjectIndex;
- }
-
- internal void ToInternal(out InternalOptions options)
- {
- options = new InternalOptions
- {
- Type = Type,
- CalculateNormals = (byte)(CalculateNormals ? 1 : 0),
- SmoothingNormalsAngle = SmoothingNormalsAngle,
- FlipNormals = (byte)(FlipNormals ? 1 : 0),
- SmoothingTangentsAngle = SmoothingTangentsAngle,
- CalculateTangents = (byte)(CalculateTangents ? 1 : 0),
- OptimizeMeshes = (byte)(OptimizeMeshes ? 1 : 0),
- MergeMeshes = (byte)(MergeMeshes ? 1 : 0),
- ImportLODs = (byte)(ImportLODs ? 1 : 0),
- ImportVertexColors = (byte)(ImportVertexColors ? 1 : 0),
- ImportBlendShapes = (byte)(ImportBlendShapes ? 1 : 0),
- LightmapUVsSource = LightmapUVsSource,
- CollisionMeshesPrefix = CollisionMeshesPrefix,
- Scale = Scale,
- Rotation = Rotation,
- Translation = Translation,
- CenterGeometry = (byte)(CenterGeometry ? 1 : 0),
- Duration = Duration,
- FramesRangeStart = FramesRangeStart,
- FramesRangeEnd = FramesRangeEnd,
- DefaultFrameRate = DefaultFrameRate,
- SamplingRate = SamplingRate,
- SkipEmptyCurves = (byte)(SkipEmptyCurves ? 1 : 0),
- OptimizeKeyframes = (byte)(OptimizeKeyframes ? 1 : 0),
- ImportScaleTracks = (byte)(ImportScaleTracks ? 1 : 0),
- EnableRootMotion = (byte)(EnableRootMotion ? 1 : 0),
- RootNodeName = RootNodeName,
- GenerateLODs = (byte)(GenerateLODs ? 1 : 0),
- BaseLOD = BaseLOD,
- LODCount = LODCount,
- TriangleReduction = TriangleReduction,
- ImportMaterials = (byte)(ImportMaterials ? 1 : 0),
- ImportTextures = (byte)(ImportTextures ? 1 : 0),
- RestoreMaterialsOnReimport = (byte)(RestoreMaterialsOnReimport ? 1 : 0),
- GenerateSDF = (byte)(GenerateSDF ? 1 : 0),
- SDFResolution = SDFResolution,
- SplitObjects = (byte)(SplitObjects ? 1 : 0),
- ObjectIndex = ObjectIndex,
- };
- }
-
- internal void FromInternal(ref InternalOptions options)
- {
- Type = options.Type;
- CalculateNormals = options.CalculateNormals != 0;
- SmoothingNormalsAngle = options.SmoothingNormalsAngle;
- FlipNormals = options.FlipNormals != 0;
- SmoothingTangentsAngle = options.SmoothingTangentsAngle;
- CalculateTangents = options.CalculateTangents != 0;
- OptimizeMeshes = options.OptimizeMeshes != 0;
- MergeMeshes = options.MergeMeshes != 0;
- ImportLODs = options.ImportLODs != 0;
- ImportVertexColors = options.ImportVertexColors != 0;
- ImportBlendShapes = options.ImportBlendShapes != 0;
- LightmapUVsSource = options.LightmapUVsSource;
- CollisionMeshesPrefix = options.CollisionMeshesPrefix;
- Scale = options.Scale;
- Rotation = options.Rotation;
- Translation = options.Translation;
- CenterGeometry = options.CenterGeometry != 0;
- FramesRangeStart = options.FramesRangeStart;
- FramesRangeEnd = options.FramesRangeEnd;
- DefaultFrameRate = options.DefaultFrameRate;
- SamplingRate = options.SamplingRate;
- SkipEmptyCurves = options.SkipEmptyCurves != 0;
- OptimizeKeyframes = options.OptimizeKeyframes != 0;
- ImportScaleTracks = options.ImportScaleTracks != 0;
- EnableRootMotion = options.EnableRootMotion != 0;
- RootNodeName = options.RootNodeName;
- GenerateLODs = options.GenerateLODs != 0;
- BaseLOD = options.BaseLOD;
- LODCount = options.LODCount;
- TriangleReduction = options.TriangleReduction;
- ImportMaterials = options.ImportMaterials != 0;
- ImportTextures = options.ImportTextures != 0;
- RestoreMaterialsOnReimport = options.RestoreMaterialsOnReimport != 0;
- GenerateSDF = options.GenerateSDF != 0;
- SDFResolution = options.SDFResolution;
- SplitObjects = options.SplitObjects != 0;
- ObjectIndex = options.ObjectIndex;
- }
-
- ///
- /// Tries the restore the asset import options from the target resource file. Applies the project default options too.
- ///
- /// The options.
- /// The asset path.
- /// True settings has been restored, otherwise false.
- public static void TryRestore(ref ModelImportSettings options, string assetPath)
- {
- ModelImportEntry.Internal_GetModelImportOptions(assetPath, out var internalOptions);
- options.FromInternal(ref internalOptions);
- }
+ [EditorDisplay(null, EditorDisplayAttribute.InlineStyle)]
+ public ModelTool.Options Settings = ModelTool.Options.Default;
}
///
@@ -523,7 +61,7 @@ namespace FlaxEditor.Content.Import
///
public class ModelImportEntry : AssetImportEntry
{
- private ModelImportSettings _settings = new ModelImportSettings();
+ private ModelImportSettings _settings = new();
///
/// Initializes a new instance of the class.
@@ -533,7 +71,7 @@ namespace FlaxEditor.Content.Import
: base(ref request)
{
// Try to restore target asset model import options (useful for fast reimport)
- ModelImportSettings.TryRestore(ref _settings, ResultUrl);
+ Editor.TryRestoreImportOptions(ref _settings.Settings, ResultUrl);
}
///
@@ -542,9 +80,14 @@ namespace FlaxEditor.Content.Import
///
public override bool TryOverrideSettings(object settings)
{
- if (settings is ModelImportSettings o)
+ if (settings is ModelImportSettings s)
{
- _settings = o;
+ _settings.Settings = s.Settings;
+ return true;
+ }
+ if (settings is ModelTool.Options o)
+ {
+ _settings.Settings = o;
return true;
}
return false;
@@ -553,14 +96,7 @@ namespace FlaxEditor.Content.Import
///
public override bool Import()
{
- return Editor.Import(SourceUrl, ResultUrl, _settings);
+ return Editor.Import(SourceUrl, ResultUrl, _settings.Settings);
}
-
- #region Internal Calls
-
- [MethodImpl(MethodImplOptions.InternalCall)]
- internal static extern void Internal_GetModelImportOptions(string path, out ModelImportSettings.InternalOptions result);
-
- #endregion
}
}
diff --git a/Source/Editor/Content/Import/TextureImportEntry.cs b/Source/Editor/Content/Import/TextureImportEntry.cs
index 33baf6c69..00cd56d24 100644
--- a/Source/Editor/Content/Import/TextureImportEntry.cs
+++ b/Source/Editor/Content/Import/TextureImportEntry.cs
@@ -3,56 +3,94 @@
using System.Collections.Generic;
using System.ComponentModel;
using System.Reflection;
-using System.Runtime.CompilerServices;
-using System.Runtime.InteropServices;
+using FlaxEditor.CustomEditors.Editors;
+using FlaxEditor.Scripting;
using FlaxEngine;
+using FlaxEngine.Tools;
-// ReSharper disable InconsistentNaming
+namespace FlaxEngine.Tools
+{
+ partial class TextureTool
+ {
+ partial struct Options
+ {
+#pragma warning disable CS1591
+ public enum CustomMaxSizes
+ {
+ _32 = 32,
+ _64 = 64,
+ _128 = 128,
+ _256 = 256,
+ _512 = 512,
+ _1024 = 1024,
+ _2048 = 2048,
+ _4096 = 4096,
+ _8192 = 8192,
+ _16384 = 16384,
+ }
+#pragma warning restore CS1591
+
+ ///
+ /// The size of the imported texture. If Resize property is set to true then texture will be resized during the import to this value. Otherwise it will be ignored.
+ ///
+ [EditorOrder(110), VisibleIf(nameof(Resize)), DefaultValue(typeof(Int2), "1024,1024")]
+ public Int2 Size
+ {
+ get => new Int2(SizeX, SizeY);
+ set
+ {
+ SizeX = value.X;
+ SizeY = value.Y;
+ }
+ }
+
+ ///
+ /// Maximum size of the texture (for both width and height). Higher resolution textures will be resized during importing process.
+ ///
+ [EditorOrder(90), DefaultValue(CustomMaxSizes._8192), EditorDisplay(null, "Max Size")]
+ public CustomMaxSizes CustomMaxSize
+ {
+ get
+ {
+ var value = MaxSize;
+ if (!Mathf.IsPowerOfTwo(value))
+ value = Mathf.NextPowerOfTwo(value);
+ FieldInfo[] fields = typeof(CustomMaxSizes).GetFields();
+ for (int i = 0; i < fields.Length; i++)
+ {
+ var field = fields[i];
+ if (field.Name.Equals("value__"))
+ continue;
+ if (value == (int)field.GetRawConstantValue())
+ return (CustomMaxSizes)value;
+ }
+ return CustomMaxSizes._8192;
+ }
+ set => MaxSize = (int)value;
+ }
+ }
+ }
+}
+
+namespace FlaxEditor.CustomEditors.Dedicated
+{
+ ///
+ /// Custom editor for .
+ ///
+ [CustomEditor(typeof(FlaxEngine.Tools.TextureTool.Options)), DefaultEditor]
+ public class TextureToolOptionsEditor : GenericEditor
+ {
+ ///
+ protected override List GetItemsForType(ScriptType type)
+ {
+ // Show both fields and properties
+ return GetItemsForType(type, true, true);
+ }
+ }
+}
namespace FlaxEditor.Content.Import
{
- ///
- /// Texture format types.
- ///
- [HideInEditor]
- public enum TextureFormatType : byte
- {
- ///
- /// The unknown.
- ///
- Unknown = 0,
-
- ///
- /// The color with RGB channels.
- ///
- ColorRGB = 1,
-
- ///
- /// The color with RGBA channels.
- ///
- ColorRGBA = 2,
-
- ///
- /// The normal map (packed and compressed).
- ///
- NormalMap = 3,
-
- ///
- /// The gray scale (R channel).
- ///
- GrayScale = 4,
-
- ///
- /// The HDR color (RGBA channels).
- ///
- HdrRGBA = 5,
-
- ///
- /// The HDR color (RGB channels).
- ///
- HdrRGB = 6
- }
-
///
/// Proxy object to present texture import settings in .
///
@@ -60,346 +98,10 @@ namespace FlaxEditor.Content.Import
public class TextureImportSettings
{
///
- /// A custom version of for GUI.
+ /// The settings data.
///
- public enum CustomTextureFormatType
- {
- ///
- /// The color with RGB channels.
- ///
- ColorRGB = 1,
-
- ///
- /// The color with RGBA channels.
- ///
- ColorRGBA = 2,
-
- ///
- /// The normal map (packed and compressed).
- ///
- NormalMap = 3,
-
- ///
- /// The gray scale (R channel).
- ///
- GrayScale = 4,
-
- ///
- /// The HDR color (RGBA channels).
- ///
- HdrRGBA = 5,
-
- ///
- /// The HDR color (RGB channels).
- ///
- HdrRGB = 6
- }
-
- ///
- /// A custom set of max texture import sizes.
- ///
- public enum CustomMaxSizeType
- {
- ///
- /// The 32.
- ///
- _32 = 32,
-
- ///
- /// The 64.
- ///
- _64 = 64,
-
- ///
- /// The 128.
- ///
- _128 = 128,
-
- ///
- /// The 256.
- ///
- _256 = 256,
-
- ///
- /// The 512.
- ///
- _512 = 512,
-
- ///
- /// The 1024.
- ///
- _1024 = 1024,
-
- ///
- /// The 2048.
- ///
- _2048 = 2048,
-
- ///
- /// The 4096.
- ///
- _4096 = 4096,
-
- ///
- /// The 8192.
- ///
- _8192 = 8192,
- }
-
- ///
- /// Converts the maximum size to enum.
- ///
- /// The max size.
- /// The converted enum.
- public static CustomMaxSizeType ConvertMaxSize(int f)
- {
- if (!Mathf.IsPowerOfTwo(f))
- f = Mathf.NextPowerOfTwo(f);
-
- FieldInfo[] fields = typeof(CustomMaxSizeType).GetFields();
- for (int i = 0; i < fields.Length; i++)
- {
- var field = fields[i];
- if (field.Name.Equals("value__"))
- continue;
-
- if (f == (int)field.GetRawConstantValue())
- return (CustomMaxSizeType)f;
- }
-
- return CustomMaxSizeType._8192;
- }
-
- ///
- /// The sprite info.
- ///
- [StructLayout(LayoutKind.Sequential)]
- public struct SpriteInfo
- {
- ///
- /// The sprite area.
- ///
- public Rectangle Area;
-
- ///
- /// The sprite name.
- ///
- public string Name;
-
- ///
- /// Initializes a new instance of the struct.
- ///
- /// The area.
- /// The name.
- public SpriteInfo(Rectangle area, string name)
- {
- Area = area;
- Name = name;
- }
- }
-
- ///
- /// Texture format type
- ///
- [EditorOrder(0), DefaultValue(CustomTextureFormatType.ColorRGB), Tooltip("Texture import format type")]
- public CustomTextureFormatType Type { get; set; } = CustomTextureFormatType.ColorRGB;
-
- ///
- /// True if texture should be imported as a texture atlas resource
- ///
- [EditorOrder(10), DefaultValue(false), Tooltip("True if texture should be imported as a texture atlas (with sprites)")]
- public bool IsAtlas { get; set; }
-
- ///
- /// True if disable dynamic texture streaming
- ///
- [EditorOrder(20), DefaultValue(false), Tooltip("True if disable dynamic texture streaming")]
- public bool NeverStream { get; set; }
-
- ///
- /// Enables/disables texture data compression.
- ///
- [EditorOrder(30), DefaultValue(true), Tooltip("True if compress texture data")]
- public bool Compress { get; set; } = true;
-
- ///
- /// True if texture channels have independent data
- ///
- [EditorOrder(40), DefaultValue(false), Tooltip("True if texture channels have independent data (for compression methods)")]
- public bool IndependentChannels { get; set; }
-
- ///
- /// True if use sRGB format for texture data. Recommended for color maps and diffuse color textures.
- ///
- [EditorOrder(50), DefaultValue(false), EditorDisplay(null, "sRGB"), Tooltip("True if use sRGB format for texture data. Recommended for color maps and diffuse color textures.")]
- public bool sRGB { get; set; }
-
- ///
- /// True if generate mip maps chain for the texture.
- ///
- [EditorOrder(60), DefaultValue(true), Tooltip("True if generate mip maps chain for the texture")]
- public bool GenerateMipMaps { get; set; } = true;
-
- ///
- /// True if flip Y coordinate of the texture.
- ///
- [EditorOrder(65), DefaultValue(false), EditorDisplay(null, "Flip Y"), Tooltip("True if flip Y coordinate of the texture.")]
- public bool FlipY { get; set; } = false;
-
- ///
- /// The import texture scale.
- ///
- [EditorOrder(70), DefaultValue(1.0f), Tooltip("Texture scale. Default is 1.")]
- public float Scale { get; set; } = 1.0f;
-
- ///
- /// Maximum size of the texture (for both width and height).
- /// Higher resolution textures will be resized during importing process.
- ///
- [EditorOrder(80), DefaultValue(CustomMaxSizeType._8192), Tooltip("Maximum texture size (will be resized if need to)")]
- public CustomMaxSizeType MaxSize { get; set; } = CustomMaxSizeType._8192;
-
- ///
- /// True if resize texture on import. Use Size property to define texture width and height. Texture scale property will be ignored.
- ///
- [EditorOrder(90), DefaultValue(false), Tooltip("True if resize texture on import. Use Size property to define texture width and height. Texture scale property will be ignored.")]
- public bool Resize { get; set; } = false;
-
- ///
- /// Gets or sets the size of the imported texture. If Resize property is set to true then texture will be resized during the import to this value. Otherwise it will be ignored.
- ///
- [EditorOrder(100), VisibleIf("Resize"), DefaultValue(typeof(Int2), "1024,1024"), Tooltip("The size of the imported texture. If Resize property is set to true then texture will be resized during the import to this value. Otherwise it will be ignored.")]
- public Int2 Size { get; set; } = new Int2(1024, 1024);
-
- ///
- /// True if preserve alpha coverage in generated mips for alpha test reference. Scales mipmap alpha values to preserve alpha coverage based on an alpha test reference value.
- ///
- [EditorOrder(240), DefaultValue(false), Tooltip("Check to preserve alpha coverage in generated mips for alpha test reference. Scales mipmap alpha values to preserve alpha coverage based on an alpha test reference value.")]
- public bool PreserveAlphaCoverage { get; set; } = false;
-
- ///
- /// The reference value for the alpha coverage preserving.
- ///
- [EditorOrder(250), VisibleIf("PreserveAlphaCoverage"), DefaultValue(0.5f), Tooltip("The reference value for the alpha coverage preserving.")]
- public float PreserveAlphaCoverageReference { get; set; } = 0.5f;
-
- ///
- /// Texture group for streaming (negative if unused). See Streaming Settings.
- ///
- [CustomEditor(typeof(CustomEditors.Dedicated.TextureGroupEditor))]
- [EditorOrder(300), Tooltip("Texture group for streaming (negative if unused). See Streaming Settings.")]
- public int TextureGroup = -1;
-
- ///
- /// The sprites. Used to keep created sprites on sprite atlas reimport.
- ///
- [HideInEditor]
- public List Sprites = new List();
-
- [StructLayout(LayoutKind.Sequential)]
- internal struct InternalOptions
- {
- public TextureFormatType Type;
- public byte IsAtlas;
- public byte NeverStream;
- public byte Compress;
- public byte IndependentChannels;
- public byte sRGB;
- public byte GenerateMipMaps;
- public byte FlipY;
- public byte Resize;
- public byte PreserveAlphaCoverage;
- public float PreserveAlphaCoverageReference;
- public float Scale;
- public int MaxSize;
- public int TextureGroup;
- public Int2 Size;
- public Rectangle[] SpriteAreas;
- public string[] SpriteNames;
- }
-
- internal void ToInternal(out InternalOptions options)
- {
- options = new InternalOptions
- {
- Type = (TextureFormatType)(int)Type,
- IsAtlas = (byte)(IsAtlas ? 1 : 0),
- NeverStream = (byte)(NeverStream ? 1 : 0),
- Compress = (byte)(Compress ? 1 : 0),
- IndependentChannels = (byte)(IndependentChannels ? 1 : 0),
- sRGB = (byte)(sRGB ? 1 : 0),
- GenerateMipMaps = (byte)(GenerateMipMaps ? 1 : 0),
- FlipY = (byte)(FlipY ? 1 : 0),
- Resize = (byte)(Resize ? 1 : 0),
- PreserveAlphaCoverage = (byte)(PreserveAlphaCoverage ? 1 : 0),
- PreserveAlphaCoverageReference = PreserveAlphaCoverageReference,
- Scale = Scale,
- Size = Size,
- MaxSize = (int)MaxSize,
- TextureGroup = TextureGroup,
- };
- if (Sprites != null && Sprites.Count > 0)
- {
- int count = Sprites.Count;
- options.SpriteAreas = new Rectangle[count];
- options.SpriteNames = new string[count];
- for (int i = 0; i < count; i++)
- {
- options.SpriteAreas[i] = Sprites[i].Area;
- options.SpriteNames[i] = Sprites[i].Name;
- }
- }
- else
- {
- options.SpriteAreas = null;
- options.SpriteNames = null;
- }
- }
-
- internal void FromInternal(ref InternalOptions options)
- {
- Type = (CustomTextureFormatType)(int)options.Type;
- IsAtlas = options.IsAtlas != 0;
- NeverStream = options.NeverStream != 0;
- Compress = options.Compress != 0;
- IndependentChannels = options.IndependentChannels != 0;
- sRGB = options.sRGB != 0;
- GenerateMipMaps = options.GenerateMipMaps != 0;
- FlipY = options.FlipY != 0;
- Resize = options.Resize != 0;
- PreserveAlphaCoverage = options.PreserveAlphaCoverage != 0;
- PreserveAlphaCoverageReference = options.PreserveAlphaCoverageReference;
- Scale = options.Scale;
- MaxSize = ConvertMaxSize(options.MaxSize);
- TextureGroup = options.TextureGroup;
- Size = options.Size;
- if (options.SpriteAreas != null)
- {
- int spritesCount = options.SpriteAreas.Length;
- Sprites.Capacity = spritesCount;
- for (int i = 0; i < spritesCount; i++)
- {
- Sprites.Add(new SpriteInfo(options.SpriteAreas[i], options.SpriteNames[i]));
- }
- }
- }
-
- ///
- /// Tries the restore the asset import options from the target resource file.
- ///
- /// The options.
- /// The asset path.
- /// True settings has been restored, otherwise false.
- public static bool TryRestore(ref TextureImportSettings options, string assetPath)
- {
- if (TextureImportEntry.Internal_GetTextureImportOptions(assetPath, out var internalOptions))
- {
- // Restore settings
- options.FromInternal(ref internalOptions);
- return true;
- }
- return false;
- }
+ [EditorDisplay(null, EditorDisplayAttribute.InlineStyle)]
+ public TextureTool.Options Settings = TextureTool.Options.Default;
}
///
@@ -408,7 +110,7 @@ namespace FlaxEditor.Content.Import
///
public class TextureImportEntry : AssetImportEntry
{
- private TextureImportSettings _settings = new TextureImportSettings();
+ private TextureImportSettings _settings = new();
///
/// Initializes a new instance of the class.
@@ -423,15 +125,15 @@ namespace FlaxEditor.Content.Import
if (extension == ".raw")
{
// Raw image data in 16bit gray-scale, preserve the quality
- _settings.Type = TextureImportSettings.CustomTextureFormatType.HdrRGBA;
- _settings.Compress = false;
+ _settings.Settings.Type = TextureFormatType.HdrRGBA;
+ _settings.Settings.Compress = false;
}
else if (extension == ".hdr")
{
// HDR sky texture
- _settings.Type = TextureImportSettings.CustomTextureFormatType.HdrRGB;
+ _settings.Settings.Type = TextureFormatType.HdrRGB;
}
- else if (_settings.Type != TextureImportSettings.CustomTextureFormatType.ColorRGB)
+ else if (_settings.Settings.Type != TextureFormatType.ColorRGB)
{
// Skip checking
}
@@ -443,7 +145,7 @@ namespace FlaxEditor.Content.Import
|| snl.EndsWith("normals"))
{
// Normal map
- _settings.Type = TextureImportSettings.CustomTextureFormatType.NormalMap;
+ _settings.Settings.Type = TextureFormatType.NormalMap;
}
else if (snl.EndsWith("_d")
|| snl.Contains("diffuse")
@@ -454,8 +156,8 @@ namespace FlaxEditor.Content.Import
|| snl.Contains("albedo"))
{
// Albedo or diffuse map
- _settings.Type = TextureImportSettings.CustomTextureFormatType.ColorRGB;
- _settings.sRGB = true;
+ _settings.Settings.Type = TextureFormatType.ColorRGB;
+ _settings.Settings.sRGB = true;
}
else if (snl.EndsWith("ao")
|| snl.EndsWith("ambientocclusion")
@@ -478,11 +180,11 @@ namespace FlaxEditor.Content.Import
|| snl.EndsWith("metallic"))
{
// Glossiness, metalness, ambient occlusion, displacement, height, cavity or specular
- _settings.Type = TextureImportSettings.CustomTextureFormatType.GrayScale;
+ _settings.Settings.Type = TextureFormatType.GrayScale;
}
// Try to restore target asset texture import options (useful for fast reimport)
- TextureImportSettings.TryRestore(ref _settings, ResultUrl);
+ Editor.TryRestoreImportOptions(ref _settings.Settings, ResultUrl);
}
///
@@ -491,11 +193,18 @@ namespace FlaxEditor.Content.Import
///
public override bool TryOverrideSettings(object settings)
{
- if (settings is TextureImportSettings o)
+ if (settings is TextureImportSettings s)
{
- var sprites = o.Sprites ?? _settings.Sprites; // Preserve sprites if not specified to override
- _settings = o;
- _settings.Sprites = sprites;
+ var sprites = s.Settings.Sprites ?? _settings.Settings.Sprites; // Preserve sprites if not specified to override
+ _settings.Settings = s.Settings;
+ _settings.Settings.Sprites = sprites;
+ return true;
+ }
+ if (settings is TextureTool.Options o)
+ {
+ var sprites = o.Sprites ?? _settings.Settings.Sprites; // Preserve sprites if not specified to override
+ _settings.Settings = o;
+ _settings.Settings.Sprites = sprites;
return true;
}
return false;
@@ -504,14 +213,7 @@ namespace FlaxEditor.Content.Import
///
public override bool Import()
{
- return Editor.Import(SourceUrl, ResultUrl, _settings);
+ return Editor.Import(SourceUrl, ResultUrl, _settings.Settings);
}
-
- #region Internal Calls
-
- [MethodImpl(MethodImplOptions.InternalCall)]
- internal static extern bool Internal_GetTextureImportOptions(string path, out TextureImportSettings.InternalOptions result);
-
- #endregion
}
}
diff --git a/Source/Editor/Content/Items/AssetItem.cs b/Source/Editor/Content/Items/AssetItem.cs
index c85a2c557..bbff4cb0c 100644
--- a/Source/Editor/Content/Items/AssetItem.cs
+++ b/Source/Editor/Content/Items/AssetItem.cs
@@ -88,6 +88,15 @@ namespace FlaxEditor.Content
}
}
+ ///
+ /// Loads the asset.
+ ///
+ /// The asset object.
+ public Asset LoadAsync()
+ {
+ return FlaxEngine.Content.LoadAsync(ID);
+ }
+
///
/// Reloads the asset (if it's loaded).
///
diff --git a/Source/Editor/Content/Items/VisualScriptItem.cs b/Source/Editor/Content/Items/VisualScriptItem.cs
index 6638c2c67..4ae4d06ba 100644
--- a/Source/Editor/Content/Items/VisualScriptItem.cs
+++ b/Source/Editor/Content/Items/VisualScriptItem.cs
@@ -8,6 +8,7 @@ using System.Reflection;
using FlaxEditor.Scripting;
using FlaxEngine;
using FlaxEngine.Json;
+using FlaxEngine.Utilities;
using Object = FlaxEngine.Object;
namespace FlaxEditor.Content
@@ -134,7 +135,7 @@ namespace FlaxEditor.Content
_index = index;
type.Asset.GetMethodSignature(index, out _name, out _flags, out var returnTypeName, out var paramNames, out var paramTypeNames, out var paramOuts);
_returnType = TypeUtils.GetType(returnTypeName);
- if (paramNames.Length != 0)
+ if (paramNames != null && paramNames.Length != 0)
{
_parameters = new ScriptMemberInfo.Parameter[paramNames.Length];
for (int i = 0; i < _parameters.Length; i++)
@@ -310,7 +311,10 @@ namespace FlaxEditor.Content
internal void Dispose()
{
- OnAssetReloading(_asset);
+ if (_parameters != null)
+ {
+ OnAssetReloading(_asset);
+ }
_asset = null;
}
diff --git a/Source/Editor/Content/Proxy/BinaryAssetProxy.cs b/Source/Editor/Content/Proxy/BinaryAssetProxy.cs
index dcd8fd68a..9924ff19f 100644
--- a/Source/Editor/Content/Proxy/BinaryAssetProxy.cs
+++ b/Source/Editor/Content/Proxy/BinaryAssetProxy.cs
@@ -2,6 +2,7 @@
using System;
using FlaxEngine;
+using FlaxEngine.Utilities;
namespace FlaxEditor.Content
{
@@ -42,7 +43,7 @@ namespace FlaxEditor.Content
///
public override AssetItem ConstructItem(string path, string typeName, ref Guid id)
{
- var type = Scripting.TypeUtils.GetType(typeName).Type;
+ var type = TypeUtils.GetType(typeName).Type;
if (typeof(TextureBase).IsAssignableFrom(type))
return new TextureAssetItem(path, ref id, typeName, type);
diff --git a/Source/Editor/Content/Proxy/SkinnedModelProxy.cs b/Source/Editor/Content/Proxy/SkinnedModelProxy.cs
index a500bc48e..f0193d0d0 100644
--- a/Source/Editor/Content/Proxy/SkinnedModelProxy.cs
+++ b/Source/Editor/Content/Proxy/SkinnedModelProxy.cs
@@ -59,7 +59,10 @@ namespace FlaxEditor.Content
// Check if asset is streamed enough
var asset = (SkinnedModel)request.Asset;
- return asset.LoadedLODs >= Mathf.Max(1, (int)(asset.LODs.Length * ThumbnailsModule.MinimumRequiredResourcesQuality));
+ var lods = asset.LODs.Length;
+ if (asset.IsLoaded && lods == 0)
+ return true; // Skeleton-only model
+ return asset.LoadedLODs >= Mathf.Max(1, (int)(lods * ThumbnailsModule.MinimumRequiredResourcesQuality));
}
///
diff --git a/Source/Editor/Content/Proxy/VisualScriptProxy.cs b/Source/Editor/Content/Proxy/VisualScriptProxy.cs
index ff2d8df65..c4fc804ae 100644
--- a/Source/Editor/Content/Proxy/VisualScriptProxy.cs
+++ b/Source/Editor/Content/Proxy/VisualScriptProxy.cs
@@ -8,6 +8,7 @@ using FlaxEditor.Windows;
using FlaxEditor.Windows.Assets;
using FlaxEngine;
using FlaxEngine.Json;
+using FlaxEngine.Utilities;
namespace FlaxEditor.Content
{
diff --git a/Source/Editor/Cooker/CookingData.h b/Source/Editor/Cooker/CookingData.h
index bf6b0123a..51a3fcd79 100644
--- a/Source/Editor/Cooker/CookingData.h
+++ b/Source/Editor/Cooker/CookingData.h
@@ -115,9 +115,19 @@ API_ENUM() enum class BuildPlatform
///
API_ENUM(Attributes="EditorDisplay(null, \"Mac x64\")")
MacOSx64 = 12,
-};
-extern FLAXENGINE_API const Char* ToString(const BuildPlatform platform);
+ ///
+ /// MacOS (ARM64 Apple Silicon)
+ ///
+ API_ENUM(Attributes="EditorDisplay(null, \"Mac ARM64\")")
+ MacOSARM64 = 13,
+
+ ///
+ /// iOS (ARM64)
+ ///
+ API_ENUM(Attributes="EditorDisplay(null, \"iOS ARM64\")")
+ iOSARM64 = 14,
+};
///
/// Game build configuration modes.
@@ -140,7 +150,35 @@ API_ENUM() enum class BuildConfiguration
Release = 2,
};
+///
+/// .NET Ahead of Time Compilation (AOT) modes.
+///
+enum class DotNetAOTModes
+{
+ ///
+ /// AOT is not used.
+ ///
+ None,
+
+ ///
+ /// Use .NET Native IL Compiler (shorten as ILC) to convert all C# assemblies in native platform executable binary.
+ ///
+ ILC,
+
+ ///
+ /// Use Mono AOT to cross-compile all used C# assemblies into native platform shared libraries.
+ ///
+ MonoAOTDynamic,
+
+ ///
+ /// Use Mono AOT to cross-compile all used C# assemblies into native platform static libraries which can be linked into a single shared library.
+ ///
+ MonoAOTStatic,
+};
+
+extern FLAXENGINE_API const Char* ToString(const BuildPlatform platform);
extern FLAXENGINE_API const Char* ToString(const BuildConfiguration configuration);
+extern FLAXENGINE_API const Char* ToString(const DotNetAOTModes mode);
#define BUILD_STEP_CANCEL_CHECK if (GameCooker::IsCancelRequested()) return true
@@ -188,7 +226,7 @@ public:
API_FIELD(ReadOnly) String OriginalOutputPath;
///
- /// The output path for data files (Content, Mono, etc.).
+ /// The output path for data files (Content, Dotnet, Mono, etc.).
///
API_FIELD(ReadOnly) String DataOutputPath;
@@ -300,15 +338,18 @@ public:
///
/// Gets the absolute path to the Platform Data folder that contains the binary files used by the current build configuration.
///
- /// The platform data folder path.
String GetGameBinariesPath() const;
///
/// Gets the absolute path to the platform folder that contains the dependency files used by the current build configuration.
///
- /// The platform deps folder path.
String GetPlatformBinariesRoot() const;
+ ///
+ /// Gets the name of the platform and architecture for the current BuildPlatform.
+ ///
+ void GetBuildPlatformName(const Char*& platform, const Char*& architecture) const;
+
public:
///
@@ -368,17 +409,15 @@ public:
public:
- void Error(const String& msg);
+ void Error(const StringView& msg);
+
+ void Error(const String& msg)
+ {
+ Error(StringView(msg));
+ }
void Error(const Char* msg)
{
- Error(String(msg));
- }
-
- template
- void Error(const Char* format, const Args& ... args)
- {
- const String msg = String::Format(format, args...);
- Error(msg);
+ Error(StringView(msg));
}
};
diff --git a/Source/Editor/Cooker/GameCooker.cpp b/Source/Editor/Cooker/GameCooker.cpp
index 15ea13153..52550128e 100644
--- a/Source/Editor/Cooker/GameCooker.cpp
+++ b/Source/Editor/Cooker/GameCooker.cpp
@@ -3,9 +3,10 @@
#include "GameCooker.h"
#include "PlatformTools.h"
#include "FlaxEngine.Gen.h"
-#include "Engine/Scripting/MainThreadManagedInvokeAction.h"
#include "Engine/Scripting/ManagedCLR/MTypes.h"
#include "Engine/Scripting/ManagedCLR/MClass.h"
+#include "Engine/Scripting/ManagedCLR/MException.h"
+#include "Engine/Scripting/Internal/MainThreadManagedInvokeAction.h"
#include "Engine/Scripting/Scripting.h"
#include "Engine/Scripting/ScriptingType.h"
#include "Engine/Scripting/BinaryModule.h"
@@ -23,12 +24,12 @@
#include "Steps/CookAssetsStep.h"
#include "Steps/PostProcessStep.h"
#include "Engine/Platform/ConditionVariable.h"
+#include "Engine/Platform/CreateProcessSettings.h"
#include "Engine/Scripting/ManagedCLR/MDomain.h"
#include "Engine/Scripting/ManagedCLR/MCore.h"
#include "Engine/Scripting/ManagedCLR/MAssembly.h"
#include "Engine/Content/JsonAsset.h"
#include "Engine/Content/AssetReference.h"
-#include "Engine/Scripting/MException.h"
#if PLATFORM_TOOLS_WINDOWS
#include "Platform/Windows/WindowsPlatformTools.h"
#include "Engine/Platform/Windows/WindowsPlatformSettings.h"
@@ -63,6 +64,10 @@
#include "Platform/Mac/MacPlatformTools.h"
#include "Engine/Platform/Mac/MacPlatformSettings.h"
#endif
+#if PLATFORM_TOOLS_IOS
+#include "Platform/iOS/iOSPlatformTools.h"
+#include "Engine/Platform/iOS/iOSPlatformSettings.h"
+#endif
namespace GameCookerImpl
{
@@ -139,8 +144,12 @@ const Char* ToString(const BuildPlatform platform)
return TEXT("PlayStation 5");
case BuildPlatform::MacOSx64:
return TEXT("Mac x64");
+ case BuildPlatform::MacOSARM64:
+ return TEXT("Mac ARM64");
+ case BuildPlatform::iOSARM64:
+ return TEXT("iOS ARM64");
default:
- return TEXT("?");
+ return TEXT("");
}
}
@@ -155,10 +164,37 @@ const Char* ToString(const BuildConfiguration configuration)
case BuildConfiguration::Release:
return TEXT("Release");
default:
- return TEXT("?");
+ return TEXT("");
}
}
+const Char* ToString(const DotNetAOTModes mode)
+{
+ switch (mode)
+ {
+ case DotNetAOTModes::None:
+ return TEXT("None");
+ case DotNetAOTModes::ILC:
+ return TEXT("ILC");
+ case DotNetAOTModes::MonoAOTDynamic:
+ return TEXT("MonoAOTDynamic");
+ case DotNetAOTModes::MonoAOTStatic:
+ return TEXT("MonoAOTStatic");
+ default:
+ return TEXT("");
+ }
+}
+
+bool PlatformTools::IsNativeCodeFile(CookingData& data, const String& file)
+{
+ const String filename = StringUtils::GetFileName(file);
+ if (filename.Contains(TEXT(".CSharp")) ||
+ filename.Contains(TEXT("Newtonsoft.Json")))
+ return false;
+ // TODO: maybe use Mono.Cecil via Flax.Build to read assembly image metadata and check if it contains C#?
+ return true;
+}
+
bool CookingData::AssetTypeStatistics::operator<(const AssetTypeStatistics& other) const
{
if (ContentSize != other.ContentSize)
@@ -211,6 +247,71 @@ String CookingData::GetPlatformBinariesRoot() const
return Globals::StartupFolder / TEXT("Source/Platforms") / Tools->GetName() / TEXT("Binaries");
}
+void CookingData::GetBuildPlatformName(const Char*& platform, const Char*& architecture) const
+{
+ switch (Platform)
+ {
+ case BuildPlatform::Windows32:
+ platform = TEXT("Windows");
+ architecture = TEXT("x86");
+ break;
+ case BuildPlatform::Windows64:
+ platform = TEXT("Windows");
+ architecture = TEXT("x64");
+ break;
+ case BuildPlatform::UWPx86:
+ platform = TEXT("UWP");
+ architecture = TEXT("x86");
+ break;
+ case BuildPlatform::UWPx64:
+ platform = TEXT("UWP");
+ architecture = TEXT("x64");
+ break;
+ case BuildPlatform::XboxOne:
+ platform = TEXT("XboxOne");
+ architecture = TEXT("x64");
+ break;
+ case BuildPlatform::LinuxX64:
+ platform = TEXT("Linux");
+ architecture = TEXT("x64");
+ break;
+ case BuildPlatform::PS4:
+ platform = TEXT("PS4");
+ architecture = TEXT("x64");
+ break;
+ case BuildPlatform::XboxScarlett:
+ platform = TEXT("XboxScarlett");
+ architecture = TEXT("x64");
+ break;
+ case BuildPlatform::AndroidARM64:
+ platform = TEXT("Android");
+ architecture = TEXT("ARM64");
+ break;
+ case BuildPlatform::Switch:
+ platform = TEXT("Switch");
+ architecture = TEXT("ARM64");
+ break;
+ case BuildPlatform::PS5:
+ platform = TEXT("PS5");
+ architecture = TEXT("x64");
+ break;
+ case BuildPlatform::MacOSx64:
+ platform = TEXT("Mac");
+ architecture = TEXT("x64");
+ break;
+ case BuildPlatform::MacOSARM64:
+ platform = TEXT("Mac");
+ architecture = TEXT("ARM64");
+ break;
+ case BuildPlatform::iOSARM64:
+ platform = TEXT("iOS");
+ architecture = TEXT("ARM64");
+ break;
+ default:
+ LOG(Fatal, "Unknown or unsupported build platform.");
+ }
+}
+
void CookingData::StepProgress(const String& info, const float stepProgress) const
{
const float singleStepProgress = 1.0f / (StepsCount + 1);
@@ -242,7 +343,7 @@ void CookingData::AddRootEngineAsset(const String& internalPath)
}
}
-void CookingData::Error(const String& msg)
+void CookingData::Error(const StringView& msg)
{
LOG_STR(Error, msg);
}
@@ -340,6 +441,14 @@ PlatformTools* GameCooker::GetTools(BuildPlatform platform)
case BuildPlatform::MacOSx64:
result = New(ArchitectureType::x64);
break;
+ case BuildPlatform::MacOSARM64:
+ result = New(ArchitectureType::ARM64);
+ break;
+#endif
+#if PLATFORM_TOOLS_IOS
+ case BuildPlatform::iOSARM64:
+ result = New();
+ break;
#endif
}
Tools.Add(platform, result);
@@ -470,7 +579,10 @@ void GameCooker::GetCurrentPlatform(PlatformType& platform, BuildPlatform& build
buildPlatform = BuildPlatform::PS5;
break;
case PlatformType::Mac:
- buildPlatform = BuildPlatform::MacOSx64;
+ buildPlatform = PLATFORM_ARCH_ARM || PLATFORM_ARCH_ARM64 ? BuildPlatform::MacOSARM64 : BuildPlatform::MacOSx64;
+ break;
+ case PlatformType::iOS:
+ buildPlatform = BuildPlatform::iOSARM64;
break;
default: ;
}
@@ -511,9 +623,9 @@ void GameCookerImpl::OnCollectAssets(HashSet& assets)
ASSERT(GameCookerImpl::Internal_OnCollectAssets);
}
- MCore::AttachThread();
+ MCore::Thread::Attach();
MObject* exception = nullptr;
- auto list = (MonoArray*)Internal_OnCollectAssets->Invoke(nullptr, nullptr, &exception);
+ auto list = (MArray*)Internal_OnCollectAssets->Invoke(nullptr, nullptr, &exception);
if (exception)
{
MException ex(exception);
@@ -547,13 +659,13 @@ bool GameCookerImpl::Build()
Steps.Add(New());
}
- MCore::AttachThread();
+ MCore::Thread::Attach();
// Build Started
CallEvent(GameCooker::EventType::BuildStarted);
+ data.Tools->OnBuildStarted(data);
for (int32 stepIndex = 0; stepIndex < Steps.Count(); stepIndex++)
Steps[stepIndex]->OnBuildStarted(data);
- data.Tools->OnBuildStarted(data);
data.InitProgress(Steps.Count());
// Execute all steps in a sequence
@@ -602,7 +714,15 @@ bool GameCookerImpl::Build()
const String commandLine = commandLineFormat.HasChars() ? String::Format(*commandLineFormat, gameArgs) : gameArgs;
if (workingDir.IsEmpty())
workingDir = data.NativeCodeOutputPath;
- Platform::StartProcess(executableFile, commandLine, workingDir);
+ CreateProcessSettings procSettings;
+ procSettings.FileName = executableFile;
+ procSettings.Arguments = commandLine;
+ procSettings.WorkingDirectory = workingDir;
+ procSettings.HiddenWindow = false;
+ procSettings.WaitForEnd = false;
+ procSettings.LogOutput = false;
+ procSettings.ShellExecute = true;
+ Platform::CreateProcess(procSettings);
}
else
{
@@ -612,9 +732,9 @@ bool GameCookerImpl::Build()
}
IsRunning = false;
CancelFlag = 0;
- data.Tools->OnBuildEnded(data, failed);
for (int32 stepIndex = 0; stepIndex < Steps.Count(); stepIndex++)
Steps[stepIndex]->OnBuildEnded(data, failed);
+ data.Tools->OnBuildEnded(data, failed);
CallEvent(failed ? GameCooker::EventType::BuildFailed : GameCooker::EventType::BuildDone);
Delete(Data);
Data = nullptr;
@@ -667,7 +787,7 @@ void GameCookerService::Update()
}
MainThreadManagedInvokeAction::ParamsBuilder params;
- params.AddParam(ProgressMsg, Scripting::GetScriptsDomain()->GetNative());
+ params.AddParam(ProgressMsg, Scripting::GetScriptsDomain());
params.AddParam(ProgressValue);
MainThreadManagedInvokeAction::Invoke(Internal_OnProgress, params);
GameCooker::OnProgress(ProgressMsg, ProgressValue);
diff --git a/Source/Editor/Cooker/GameCooker.cs b/Source/Editor/Cooker/GameCooker.cs
index 5ffa5fd25..9a7500d1d 100644
--- a/Source/Editor/Cooker/GameCooker.cs
+++ b/Source/Editor/Cooker/GameCooker.cs
@@ -103,7 +103,9 @@ namespace FlaxEditor
case BuildPlatform.AndroidARM64: return PlatformType.Android;
case BuildPlatform.XboxScarlett: return PlatformType.XboxScarlett;
case BuildPlatform.Switch: return PlatformType.Switch;
+ case BuildPlatform.MacOSARM64:
case BuildPlatform.MacOSx64: return PlatformType.Mac;
+ case BuildPlatform.iOSARM64: return PlatformType.iOS;
default: throw new ArgumentOutOfRangeException(nameof(buildPlatform), buildPlatform, null);
}
}
diff --git a/Source/Editor/Cooker/Platform/Android/AndroidPlatformTools.cpp b/Source/Editor/Cooker/Platform/Android/AndroidPlatformTools.cpp
index 8e3e91b51..7d05409e5 100644
--- a/Source/Editor/Cooker/Platform/Android/AndroidPlatformTools.cpp
+++ b/Source/Editor/Cooker/Platform/Android/AndroidPlatformTools.cpp
@@ -9,6 +9,7 @@
#include "Editor/Utilities/EditorUtilities.h"
#include "Engine/Platform/File.h"
#include "Engine/Platform/FileSystem.h"
+#include "Engine/Platform/CreateProcessSettings.h"
#include "Engine/Platform/Android/AndroidPlatformSettings.h"
#include "Engine/Graphics/PixelFormatExtensions.h"
#include "Engine/Graphics/Textures/TextureData.h"
@@ -115,8 +116,6 @@ void AndroidPlatformTools::OnBuildStarted(CookingData& data)
data.DataOutputPath /= TEXT("app/assets");
data.NativeCodeOutputPath /= TEXT("app/assets");
data.ManagedCodeOutputPath /= TEXT("app/assets");
-
- PlatformTools::OnBuildStarted(data);
}
bool AndroidPlatformTools::OnPostProcess(CookingData& data)
@@ -140,33 +139,8 @@ bool AndroidPlatformTools::OnPostProcess(CookingData& data)
// Setup package name (eg. com.company.project)
String packageName = platformSettings->PackageName;
- {
- String productName = gameSettings->ProductName;
- productName.Replace(TEXT(" "), TEXT(""));
- productName.Replace(TEXT("."), TEXT(""));
- productName.Replace(TEXT("-"), TEXT(""));
- String companyName = gameSettings->CompanyName;
- companyName.Replace(TEXT(" "), TEXT(""));
- companyName.Replace(TEXT("."), TEXT(""));
- companyName.Replace(TEXT("-"), TEXT(""));
- packageName.Replace(TEXT("${PROJECT_NAME}"), *productName, StringSearchCase::IgnoreCase);
- packageName.Replace(TEXT("${COMPANY_NAME}"), *companyName, StringSearchCase::IgnoreCase);
- packageName = packageName.ToLower();
- for (int32 i = 0; i < packageName.Length(); i++)
- {
- const auto c = packageName[i];
- if (c != '_' && c != '.' && !StringUtils::IsAlnum(c))
- {
- LOG(Error, "Android Package Name \'{0}\' contains invalid character. Only letters, numbers, dots and underscore characters are allowed.", packageName);
- return true;
- }
- }
- if (packageName.IsEmpty())
- {
- LOG(Error, "Android Package Name is empty.", packageName);
- return true;
- }
- }
+ if (EditorUtilities::FormatAppPackageName(packageName))
+ return true;
// Setup Android application permissions
HashSet permissionsList;
@@ -267,7 +241,7 @@ bool AndroidPlatformTools::OnPostProcess(CookingData& data)
}
}
- // Generate Mono files hash id used to skip deploying Mono files if already extracted on device (Mono cannot access files packed into .apk via unix file access)
+ // Generate Dotnet files hash id used to skip deploying Dotnet files if already extracted on device (Dotnet cannot access files packed into .apk via unix file access)
File::WriteAllText(assetsPath / TEXT("hash.txt"), Guid::New().ToString(), Encoding::ANSI);
// TODO: expose event to inject custom gradle and manifest options or custom binaries into app
@@ -309,11 +283,13 @@ bool AndroidPlatformTools::OnPostProcess(CookingData& data)
Platform::RunProcess(String::Format(TEXT("chmod +x \"{0}/gradlew\""), data.OriginalOutputPath), data.OriginalOutputPath, Dictionary(), true);
#endif
const bool distributionPackage = buildSettings->ForDistribution;
- const String gradleCommand = String::Format(TEXT("\"{0}\" {1}"), data.OriginalOutputPath / gradlew, distributionPackage ? TEXT("assemble") : TEXT("assembleDebug"));
- const int32 result = Platform::RunProcess(gradleCommand, data.OriginalOutputPath, Dictionary(), true);
+ CreateProcessSettings procSettings;
+ procSettings.FileName = String::Format(TEXT("\"{0}\" {1}"), data.OriginalOutputPath / gradlew, distributionPackage ? TEXT("assemble") : TEXT("assembleDebug"));
+ procSettings.WorkingDirectory = data.OriginalOutputPath;
+ const int32 result = Platform::CreateProcess(procSettings);
if (result != 0)
{
- data.Error(TEXT("Failed to build Gradle project into package (result code: {0}). See log for more info."), result);
+ data.Error(String::Format(TEXT("Failed to build Gradle project into package (result code: {0}). See log for more info."), result));
return true;
}
diff --git a/Source/Editor/Cooker/Platform/Android/AndroidPlatformTools.h b/Source/Editor/Cooker/Platform/Android/AndroidPlatformTools.h
index 527c95769..59697e758 100644
--- a/Source/Editor/Cooker/Platform/Android/AndroidPlatformTools.h
+++ b/Source/Editor/Cooker/Platform/Android/AndroidPlatformTools.h
@@ -1,4 +1,4 @@
-// Copyright (c) 2012-2019 Wojciech Figat. All rights reserved.
+// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.
#pragma once
diff --git a/Source/Editor/Cooker/Platform/GDK/GDKPlatformTools.cpp b/Source/Editor/Cooker/Platform/GDK/GDKPlatformTools.cpp
index e791b3264..d9a052419 100644
--- a/Source/Editor/Cooker/Platform/GDK/GDKPlatformTools.cpp
+++ b/Source/Editor/Cooker/Platform/GDK/GDKPlatformTools.cpp
@@ -5,6 +5,7 @@
#include "GDKPlatformTools.h"
#include "Engine/Platform/File.h"
#include "Engine/Platform/FileSystem.h"
+#include "Engine/Platform/CreateProcessSettings.h"
#include "Engine/Platform/GDK/GDKPlatformSettings.h"
#include "Engine/Core/Types/StringBuilder.h"
#include "Engine/Core/Collections/Sorting.h"
@@ -36,24 +37,9 @@ GDKPlatformTools::GDKPlatformTools()
}
}
-bool GDKPlatformTools::UseAOT() const
+DotNetAOTModes GDKPlatformTools::UseAOT() const
{
- return true;
-}
-
-bool GDKPlatformTools::OnScriptsStepDone(CookingData& data)
-{
- // Override Newtonsoft.Json.dll for some platforms (that don't support runtime code generation)
- const String customBinPath = data.GetPlatformBinariesRoot() / TEXT("Newtonsoft.Json.dll");
- const String assembliesPath = data.ManagedCodeOutputPath;
- if (FileSystem::CopyFile(assembliesPath / TEXT("Newtonsoft.Json.dll"), customBinPath))
- {
- data.Error(TEXT("Failed to copy deloy custom assembly."));
- return true;
- }
- FileSystem::DeleteFile(assembliesPath / TEXT("Newtonsoft.Json.pdb"));
-
- return false;
+ return DotNetAOTModes::MonoAOTDynamic;
}
bool GDKPlatformTools::OnDeployBinaries(CookingData& data)
@@ -66,7 +52,7 @@ bool GDKPlatformTools::OnDeployBinaries(CookingData& data)
files.Add(binPath / executableFilename);
if (!FileSystem::FileExists(files[0]))
{
- data.Error(TEXT("Missing executable file ({0})."), files[0]);
+ data.Error(String::Format(TEXT("Missing executable file ({0})."), files[0]));
return true;
}
FileSystem::DirectoryGetFiles(files, binPath, TEXT("*.dll"), DirectorySearchOption::TopDirectoryOnly);
@@ -74,7 +60,7 @@ bool GDKPlatformTools::OnDeployBinaries(CookingData& data)
{
if (FileSystem::CopyFile(data.NativeCodeOutputPath / StringUtils::GetFileName(files[i]), files[i]))
{
- data.Error(TEXT("Failed to setup output directory (file {0})."), files[i]);
+ data.Error(String::Format(TEXT("Failed to setup output directory (file {0})."), files[i]));
return true;
}
}
@@ -82,85 +68,6 @@ bool GDKPlatformTools::OnDeployBinaries(CookingData& data)
return false;
}
-void GDKPlatformTools::OnConfigureAOT(CookingData& data, AotConfig& config)
-{
- const auto platformDataPath = data.GetPlatformBinariesRoot();
- const bool useInterpreter = true; // TODO: use Full AOT on GDK
- const bool enableDebug = data.Configuration != BuildConfiguration::Release;
- const Char* aotMode = useInterpreter ? TEXT("full,interp") : TEXT("full");
- const Char* debugMode = enableDebug ? TEXT("soft-debug") : TEXT("nodebug");
- config.AotCompilerArgs = String::Format(TEXT("--aot={0},verbose,stats,print-skipped,{1} -O=all"), aotMode, debugMode);
- if (enableDebug)
- config.AotCompilerArgs = TEXT("--debug ") + config.AotCompilerArgs;
- config.AotCompilerPath = platformDataPath / TEXT("Tools/mono.exe");
-}
-
-bool GDKPlatformTools::OnPerformAOT(CookingData& data, AotConfig& config, const String& assemblyPath)
-{
- // Skip .dll.dll which could be a false result from the previous AOT which could fail
- if (assemblyPath.EndsWith(TEXT(".dll.dll")))
- {
- LOG(Warning, "Skip AOT for file '{0}' as it can be a result from the previous task", assemblyPath);
- return false;
- }
-
- // Check if skip this assembly (could be already processed)
- const String filename = StringUtils::GetFileName(assemblyPath);
- const String outputPath = config.AotCachePath / filename + TEXT(".dll");
- if (FileSystem::FileExists(outputPath) && FileSystem::GetFileLastEditTime(assemblyPath) < FileSystem::GetFileLastEditTime(outputPath))
- return false;
- LOG(Info, "Calling AOT tool for \"{0}\"", assemblyPath);
-
- // Cleanup temporary results (fromm the previous AT that fail or sth)
- const String resultPath = assemblyPath + TEXT(".dll");
- const String resultPathExp = resultPath + TEXT(".exp");
- const String resultPathLib = resultPath + TEXT(".lib");
- const String resultPathPdb = resultPath + TEXT(".pdb");
- if (FileSystem::FileExists(resultPath))
- FileSystem::DeleteFile(resultPath);
- if (FileSystem::FileExists(resultPathExp))
- FileSystem::DeleteFile(resultPathExp);
- if (FileSystem::FileExists(resultPathLib))
- FileSystem::DeleteFile(resultPathLib);
- if (FileSystem::FileExists(resultPathPdb))
- FileSystem::DeleteFile(resultPathPdb);
-
- // Call tool
- const String workingDir = StringUtils::GetDirectoryName(config.AotCompilerPath);
- const String command = String::Format(TEXT("\"{0}\" {1} \"{2}\""), config.AotCompilerPath, config.AotCompilerArgs, assemblyPath);
- const int32 result = Platform::RunProcess(command, workingDir, config.EnvVars);
- if (result != 0)
- {
- data.Error(TEXT("AOT tool execution failed with result code {1} for assembly \"{0}\". See log for more info."), assemblyPath, result);
- return true;
- }
-
- // Copy result
- if (FileSystem::CopyFile(outputPath, resultPath))
- {
- data.Error(TEXT("Failed to copy the AOT tool result file. It can be missing."));
- return true;
- }
-
- // Copy pdb file if exists
- if (data.Configuration != BuildConfiguration::Release && FileSystem::FileExists(resultPathPdb))
- {
- FileSystem::CopyFile(config.AotCachePath / StringUtils::GetFileName(resultPathPdb), resultPathPdb);
- }
-
- // Clean intermediate results
- if (FileSystem::DeleteFile(resultPath)
- || (FileSystem::FileExists(resultPathExp) && FileSystem::DeleteFile(resultPathExp))
- || (FileSystem::FileExists(resultPathLib) && FileSystem::DeleteFile(resultPathLib))
- || (FileSystem::FileExists(resultPathPdb) && FileSystem::DeleteFile(resultPathPdb))
- )
- {
- LOG(Warning, "Failed to remove the AOT tool result file(s).");
- }
-
- return false;
-}
-
bool GDKPlatformTools::OnPostProcess(CookingData& data, GDKPlatformSettings* platformSettings)
{
// Configuration
@@ -275,8 +182,10 @@ bool GDKPlatformTools::OnPostProcess(CookingData& data, GDKPlatformSettings* pla
data.StepProgress(TEXT("Generating package layout"), 0.3f);
const String gdkBinPath = _gdkPath / TEXT("../bin");
const String makePkgPath = gdkBinPath / TEXT("MakePkg.exe");
- const String command = String::Format(TEXT("\"{0}\" genmap /f layout.xml /d \"{1}\""), makePkgPath, data.DataOutputPath);
- const int32 result = Platform::RunProcess(command, data.DataOutputPath);
+ CreateProcessSettings procSettings;
+ procSettings.FileName = String::Format(TEXT("\"{0}\" genmap /f layout.xml /d \"{1}\""), makePkgPath, data.DataOutputPath);
+ procSettings.WorkingDirectory = data.DataOutputPath;
+ const int32 result = Platform::CreateProcess(procSettings);
if (result != 0)
{
data.Error(TEXT("Failed to generate package layout."));
diff --git a/Source/Editor/Cooker/Platform/GDK/GDKPlatformTools.h b/Source/Editor/Cooker/Platform/GDK/GDKPlatformTools.h
index 350d95257..284098ec9 100644
--- a/Source/Editor/Cooker/Platform/GDK/GDKPlatformTools.h
+++ b/Source/Editor/Cooker/Platform/GDK/GDKPlatformTools.h
@@ -26,11 +26,8 @@ public:
public:
// [PlatformTools]
- bool UseAOT() const override;
- bool OnScriptsStepDone(CookingData& data) override;
+ DotNetAOTModes UseAOT() const override;
bool OnDeployBinaries(CookingData& data) override;
- void OnConfigureAOT(CookingData& data, AotConfig& config) override;
- bool OnPerformAOT(CookingData& data, AotConfig& config, const String& assemblyPath) override;
};
#endif
diff --git a/Source/Editor/Cooker/Platform/Linux/LinuxPlatformTools.cpp b/Source/Editor/Cooker/Platform/Linux/LinuxPlatformTools.cpp
index f9c024071..ec7b8e7e1 100644
--- a/Source/Editor/Cooker/Platform/Linux/LinuxPlatformTools.cpp
+++ b/Source/Editor/Cooker/Platform/Linux/LinuxPlatformTools.cpp
@@ -1,4 +1,4 @@
-// Copyright (c) 2012-2019 Wojciech Figat. All rights reserved.
+// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.
#if PLATFORM_TOOLS_LINUX
@@ -34,6 +34,11 @@ ArchitectureType LinuxPlatformTools::GetArchitecture() const
return ArchitectureType::x64;
}
+bool LinuxPlatformTools::UseSystemDotnet() const
+{
+ return true;
+}
+
bool LinuxPlatformTools::OnDeployBinaries(CookingData& data)
{
const auto gameSettings = GameSettings::Get();
diff --git a/Source/Editor/Cooker/Platform/Linux/LinuxPlatformTools.h b/Source/Editor/Cooker/Platform/Linux/LinuxPlatformTools.h
index 9b6dcb9b8..562b38962 100644
--- a/Source/Editor/Cooker/Platform/Linux/LinuxPlatformTools.h
+++ b/Source/Editor/Cooker/Platform/Linux/LinuxPlatformTools.h
@@ -1,4 +1,4 @@
-// Copyright (c) 2012-2019 Wojciech Figat. All rights reserved.
+// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.
#pragma once
@@ -18,6 +18,7 @@ public:
const Char* GetName() const override;
PlatformType GetPlatform() const override;
ArchitectureType GetArchitecture() const override;
+ bool UseSystemDotnet() const override;
bool OnDeployBinaries(CookingData& data) override;
};
diff --git a/Source/Editor/Cooker/Platform/Mac/MacPlatformTools.cpp b/Source/Editor/Cooker/Platform/Mac/MacPlatformTools.cpp
index 56b1c8a4d..c059acba9 100644
--- a/Source/Editor/Cooker/Platform/Mac/MacPlatformTools.cpp
+++ b/Source/Editor/Cooker/Platform/Mac/MacPlatformTools.cpp
@@ -1,4 +1,4 @@
-// Copyright (c) 2012-2019 Wojciech Figat. All rights reserved.
+// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.
#if PLATFORM_TOOLS_MAC
@@ -59,6 +59,11 @@ ArchitectureType MacPlatformTools::GetArchitecture() const
return _arch;
}
+bool MacPlatformTools::UseSystemDotnet() const
+{
+ return true;
+}
+
bool MacPlatformTools::IsNativeCodeFile(CookingData& data, const String& file)
{
String extension = FileSystem::GetExtension(file);
@@ -87,33 +92,8 @@ bool MacPlatformTools::OnPostProcess(CookingData& data)
// Setup package name (eg. com.company.project)
String appIdentifier = platformSettings->AppIdentifier;
- {
- String productName = gameSettings->ProductName;
- productName.Replace(TEXT(" "), TEXT(""));
- productName.Replace(TEXT("."), TEXT(""));
- productName.Replace(TEXT("-"), TEXT(""));
- String companyName = gameSettings->CompanyName;
- companyName.Replace(TEXT(" "), TEXT(""));
- companyName.Replace(TEXT("."), TEXT(""));
- companyName.Replace(TEXT("-"), TEXT(""));
- appIdentifier.Replace(TEXT("${PROJECT_NAME}"), *productName, StringSearchCase::IgnoreCase);
- appIdentifier.Replace(TEXT("${COMPANY_NAME}"), *companyName, StringSearchCase::IgnoreCase);
- appIdentifier = appIdentifier.ToLower();
- for (int32 i = 0; i < appIdentifier.Length(); i++)
- {
- const auto c = appIdentifier[i];
- if (c != '_' && c != '.' && !StringUtils::IsAlnum(c))
- {
- LOG(Error, "Apple app identifier \'{0}\' contains invalid character. Only letters, numbers, dots and underscore characters are allowed.", appIdentifier);
- return true;
- }
- }
- if (appIdentifier.IsEmpty())
- {
- LOG(Error, "Apple app identifier is empty.", appIdentifier);
- return true;
- }
- }
+ if (EditorUtilities::FormatAppPackageName(appIdentifier))
+ return true;
// Find executable
String executableName;
@@ -187,7 +167,7 @@ bool MacPlatformTools::OnPostProcess(CookingData& data)
ADD_ENTRY("CFBundlePackageType", "APPL");
ADD_ENTRY("NSPrincipalClass", "NSApplication");
ADD_ENTRY("LSApplicationCategoryType", "public.app-category.games");
- ADD_ENTRY("LSMinimumSystemVersion", "10.14");
+ ADD_ENTRY("LSMinimumSystemVersion", "10.15");
ADD_ENTRY("CFBundleIconFile", "icon.icns");
ADD_ENTRY_STR("CFBundleExecutable", executableName);
ADD_ENTRY_STR("CFBundleIdentifier", appIdentifier);
@@ -201,10 +181,19 @@ bool MacPlatformTools::OnPostProcess(CookingData& data)
dict.append_child(PUGIXML_TEXT("key")).set_child_value(PUGIXML_TEXT("LSMinimumSystemVersionByArchitecture"));
xml_node LSMinimumSystemVersionByArchitecture = dict.append_child(PUGIXML_TEXT("dict"));
- LSMinimumSystemVersionByArchitecture.append_child(PUGIXML_TEXT("key")).set_child_value(PUGIXML_TEXT("x86_64"));
- LSMinimumSystemVersionByArchitecture.append_child(PUGIXML_TEXT("string")).set_child_value(PUGIXML_TEXT("10.14"));
+ switch (_arch)
+ {
+ case ArchitectureType::x64:
+ LSMinimumSystemVersionByArchitecture.append_child(PUGIXML_TEXT("key")).set_child_value(PUGIXML_TEXT("x86_64"));
+ break;
+ case ArchitectureType::ARM64:
+ LSMinimumSystemVersionByArchitecture.append_child(PUGIXML_TEXT("key")).set_child_value(PUGIXML_TEXT("arm64"));
+ break;
+ }
+ LSMinimumSystemVersionByArchitecture.append_child(PUGIXML_TEXT("string")).set_child_value(PUGIXML_TEXT("10.15"));
#undef ADD_ENTRY
+#undef ADD_ENTRY_STR
if (!doc.save_file(*StringAnsi(plistPath)))
{
@@ -215,8 +204,6 @@ bool MacPlatformTools::OnPostProcess(CookingData& data)
// TODO: sign binaries
- // TODO: expose event to inject custom post-processing before app packaging (eg. third-party plugins)
-
// Package application
const auto buildSettings = BuildSettings::Get();
if (buildSettings->SkipPackaging)
@@ -228,7 +215,7 @@ bool MacPlatformTools::OnPostProcess(CookingData& data)
const int32 result = Platform::RunProcess(dmgCommand, data.OriginalOutputPath);
if (result != 0)
{
- data.Error(TEXT("Failed to package app (result code: {0}). See log for more info."), result);
+ data.Error(String::Format(TEXT("Failed to package app (result code: {0}). See log for more info."), result));
return true;
}
// TODO: sign dmg
diff --git a/Source/Editor/Cooker/Platform/Mac/MacPlatformTools.h b/Source/Editor/Cooker/Platform/Mac/MacPlatformTools.h
index e50caae0f..21d9141e3 100644
--- a/Source/Editor/Cooker/Platform/Mac/MacPlatformTools.h
+++ b/Source/Editor/Cooker/Platform/Mac/MacPlatformTools.h
@@ -1,4 +1,4 @@
-// Copyright (c) 2012-2019 Wojciech Figat. All rights reserved.
+// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.
#pragma once
@@ -23,6 +23,7 @@ public:
const Char* GetName() const override;
PlatformType GetPlatform() const override;
ArchitectureType GetArchitecture() const override;
+ bool UseSystemDotnet() const override;
bool IsNativeCodeFile(CookingData& data, const String& file) override;
void OnBuildStarted(CookingData& data) override;
bool OnPostProcess(CookingData& data) override;
diff --git a/Source/Editor/Cooker/Platform/UWP/UWPPlatformTools.cpp b/Source/Editor/Cooker/Platform/UWP/UWPPlatformTools.cpp
index 01f8a1a83..f6af5bb5a 100644
--- a/Source/Editor/Cooker/Platform/UWP/UWPPlatformTools.cpp
+++ b/Source/Editor/Cooker/Platform/UWP/UWPPlatformTools.cpp
@@ -1,10 +1,11 @@
-// Copyright (c) 2012-2019 Wojciech Figat. All rights reserved.
+// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.
#if PLATFORM_TOOLS_UWP
#include "UWPPlatformTools.h"
#include "Engine/Platform/FileSystem.h"
#include "Engine/Platform/File.h"
+#include "Engine/Platform/CreateProcessSettings.h"
#include "Engine/Platform/UWP/UWPPlatformSettings.h"
#include "Engine/Core/Config/GameSettings.h"
#include "Engine/Core/Types/StringBuilder.h"
@@ -36,24 +37,9 @@ ArchitectureType UWPPlatformTools::GetArchitecture() const
return _arch;
}
-bool UWPPlatformTools::UseAOT() const
+DotNetAOTModes UWPPlatformTools::UseAOT() const
{
- return true;
-}
-
-bool UWPPlatformTools::OnScriptsStepDone(CookingData& data)
-{
- // Override Newtonsoft.Json.dll for some platforms (that don't support runtime code generation)
- const String customBinPath = data.GetPlatformBinariesRoot() / TEXT("Newtonsoft.Json.dll");
- const String assembliesPath = data.ManagedCodeOutputPath;
- if (FileSystem::CopyFile(assembliesPath / TEXT("Newtonsoft.Json.dll"), customBinPath))
- {
- data.Error(TEXT("Failed to copy deploy custom assembly."));
- return true;
- }
- FileSystem::DeleteFile(assembliesPath / TEXT("Newtonsoft.Json.pdb"));
-
- return false;
+ return DotNetAOTModes::MonoAOTDynamic;
}
bool UWPPlatformTools::OnDeployBinaries(CookingData& data)
@@ -79,7 +65,7 @@ bool UWPPlatformTools::OnDeployBinaries(CookingData& data)
{
if (!FileSystem::FileExists(files[i]))
{
- data.Error(TEXT("Missing source file {0}."), files[i]);
+ data.Error(String::Format(TEXT("Missing source file {0}."), files[i]));
return true;
}
@@ -414,90 +400,9 @@ bool UWPPlatformTools::OnDeployBinaries(CookingData& data)
return false;
}
-void UWPPlatformTools::OnConfigureAOT(CookingData& data, AotConfig& config)
-{
- const auto platformDataPath = data.GetPlatformBinariesRoot();
- const bool useInterpreter = true; // TODO: support using Full AOT instead of interpreter
- const bool enableDebug = data.Configuration != BuildConfiguration::Release;
- const Char* aotMode = useInterpreter ? TEXT("full,interp") : TEXT("full");
- const Char* debugMode = enableDebug ? TEXT("soft-debug") : TEXT("nodebug");
- config.AotCompilerArgs = String::Format(TEXT("--aot={0},verbose,stats,print-skipped,{1} -O=all"),
- aotMode,
- debugMode);
- if (enableDebug)
- config.AotCompilerArgs = TEXT("--debug ") + config.AotCompilerArgs;
- config.AotCompilerPath = platformDataPath / TEXT("Tools/mono.exe");
-}
-
-bool UWPPlatformTools::OnPerformAOT(CookingData& data, AotConfig& config, const String& assemblyPath)
-{
- // Skip .dll.dll which could be a false result from the previous AOT which could fail
- if (assemblyPath.EndsWith(TEXT(".dll.dll")))
- {
- LOG(Warning, "Skip AOT for file '{0}' as it can be a result from the previous task", assemblyPath);
- return false;
- }
-
- // Check if skip this assembly (could be already processed)
- const String filename = StringUtils::GetFileName(assemblyPath);
- const String outputPath = config.AotCachePath / filename + TEXT(".dll");
- if (FileSystem::FileExists(outputPath) && FileSystem::GetFileLastEditTime(assemblyPath) < FileSystem::GetFileLastEditTime(outputPath))
- return false;
- LOG(Info, "Calling AOT tool for \"{0}\"", assemblyPath);
-
- // Cleanup temporary results (fromm the previous AT that fail or sth)
- const String resultPath = assemblyPath + TEXT(".dll");
- const String resultPathExp = resultPath + TEXT(".exp");
- const String resultPathLib = resultPath + TEXT(".lib");
- const String resultPathPdb = resultPath + TEXT(".pdb");
- if (FileSystem::FileExists(resultPath))
- FileSystem::DeleteFile(resultPath);
- if (FileSystem::FileExists(resultPathExp))
- FileSystem::DeleteFile(resultPathExp);
- if (FileSystem::FileExists(resultPathLib))
- FileSystem::DeleteFile(resultPathLib);
- if (FileSystem::FileExists(resultPathPdb))
- FileSystem::DeleteFile(resultPathPdb);
-
- // Call tool
- String workingDir = StringUtils::GetDirectoryName(config.AotCompilerPath);
- String command = String::Format(TEXT("\"{0}\" {1} \"{2}\""), config.AotCompilerPath, config.AotCompilerArgs, assemblyPath);
- const int32 result = Platform::RunProcess(command, workingDir, config.EnvVars);
- if (result != 0)
- {
- data.Error(TEXT("AOT tool execution failed with result code {1} for assembly \"{0}\". See log for more info."), assemblyPath, result);
- return true;
- }
-
- // Copy result
- if (FileSystem::CopyFile(outputPath, resultPath))
- {
- data.Error(TEXT("Failed to copy the AOT tool result file. It can be missing."));
- return true;
- }
-
- // Copy pdb file if exists
- if (data.Configuration != BuildConfiguration::Release && FileSystem::FileExists(resultPathPdb))
- {
- FileSystem::CopyFile(config.AotCachePath / StringUtils::GetFileName(resultPathPdb), resultPathPdb);
- }
-
- // Clean intermediate results
- if (FileSystem::DeleteFile(resultPath)
- || (FileSystem::FileExists(resultPathExp) && FileSystem::DeleteFile(resultPathExp))
- || (FileSystem::FileExists(resultPathLib) && FileSystem::DeleteFile(resultPathLib))
- || (FileSystem::FileExists(resultPathPdb) && FileSystem::DeleteFile(resultPathPdb))
- )
- {
- LOG(Warning, "Failed to remove the AOT tool result file(s).");
- }
-
- return false;
-}
-
bool UWPPlatformTools::OnPostProcess(CookingData& data)
{
- LOG(Error, "UWP (Windows Store) platform has been deprecated and soon will be removed!");
+ LOG(Error, "UWP (Windows Store) platform has been deprecated and is no longer supported");
// Special case for UWP
// FlaxEngine.dll cannot be added to the solution as `Content` item (due to conflicts with C++ /CX FlaxEngine.dll)
diff --git a/Source/Editor/Cooker/Platform/UWP/UWPPlatformTools.h b/Source/Editor/Cooker/Platform/UWP/UWPPlatformTools.h
index 4e27f2238..d9b6673d6 100644
--- a/Source/Editor/Cooker/Platform/UWP/UWPPlatformTools.h
+++ b/Source/Editor/Cooker/Platform/UWP/UWPPlatformTools.h
@@ -1,4 +1,4 @@
-// Copyright (c) 2012-2019 Wojciech Figat. All rights reserved.
+// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.
#pragma once
@@ -29,11 +29,8 @@ public:
const Char* GetName() const override;
PlatformType GetPlatform() const override;
ArchitectureType GetArchitecture() const override;
- bool UseAOT() const override;
- bool OnScriptsStepDone(CookingData& data) override;
+ DotNetAOTModes UseAOT() const override;
bool OnDeployBinaries(CookingData& data) override;
- void OnConfigureAOT(CookingData& data, AotConfig& config) override;
- bool OnPerformAOT(CookingData& data, AotConfig& config, const String& assemblyPath) override;
bool OnPostProcess(CookingData& data) override;
};
diff --git a/Source/Editor/Cooker/Platform/Windows/WindowsPlatformTools.cpp b/Source/Editor/Cooker/Platform/Windows/WindowsPlatformTools.cpp
index 457c068c9..feca5c0a0 100644
--- a/Source/Editor/Cooker/Platform/Windows/WindowsPlatformTools.cpp
+++ b/Source/Editor/Cooker/Platform/Windows/WindowsPlatformTools.cpp
@@ -1,4 +1,4 @@
-// Copyright (c) 2012-2019 Wojciech Figat. All rights reserved.
+// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.
#if PLATFORM_TOOLS_WINDOWS
@@ -33,6 +33,11 @@ ArchitectureType WindowsPlatformTools::GetArchitecture() const
return _arch;
}
+bool WindowsPlatformTools::UseSystemDotnet() const
+{
+ return true;
+}
+
bool WindowsPlatformTools::OnDeployBinaries(CookingData& data)
{
const auto platformSettings = WindowsPlatformSettings::Get();
diff --git a/Source/Editor/Cooker/Platform/Windows/WindowsPlatformTools.h b/Source/Editor/Cooker/Platform/Windows/WindowsPlatformTools.h
index 404a9f9d6..5f03c9575 100644
--- a/Source/Editor/Cooker/Platform/Windows/WindowsPlatformTools.h
+++ b/Source/Editor/Cooker/Platform/Windows/WindowsPlatformTools.h
@@ -1,4 +1,4 @@
-// Copyright (c) 2012-2019 Wojciech Figat. All rights reserved.
+// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.
#pragma once
@@ -29,6 +29,7 @@ public:
const Char* GetName() const override;
PlatformType GetPlatform() const override;
ArchitectureType GetArchitecture() const override;
+ bool UseSystemDotnet() const override;
bool OnDeployBinaries(CookingData& data) override;
void OnRun(CookingData& data, String& executableFile, String& commandLineFormat, String& workingDir) override;
};
diff --git a/Source/Editor/Cooker/Platform/iOS/iOSPlatformTools.cpp b/Source/Editor/Cooker/Platform/iOS/iOSPlatformTools.cpp
new file mode 100644
index 000000000..8b0cf06a8
--- /dev/null
+++ b/Source/Editor/Cooker/Platform/iOS/iOSPlatformTools.cpp
@@ -0,0 +1,284 @@
+// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.
+
+#if PLATFORM_TOOLS_IOS
+
+#include "iOSPlatformTools.h"
+#include "Engine/Platform/File.h"
+#include "Engine/Platform/FileSystem.h"
+#include "Engine/Platform/CreateProcessSettings.h"
+#include "Engine/Platform/iOS/iOSPlatformSettings.h"
+#include "Engine/Core/Config/GameSettings.h"
+#include "Engine/Core/Config/BuildSettings.h"
+#include "Engine/Core/Collections/Dictionary.h"
+#include "Engine/Graphics/Textures/TextureData.h"
+#include "Engine/Graphics/PixelFormatExtensions.h"
+#include "Engine/Content/Content.h"
+#include "Engine/Content/JsonAsset.h"
+#include "Engine/Engine/Globals.h"
+#include "Editor/Editor.h"
+#include "Editor/ProjectInfo.h"
+#include "Editor/Cooker/GameCooker.h"
+#include "Editor/Utilities/EditorUtilities.h"
+
+IMPLEMENT_SETTINGS_GETTER(iOSPlatformSettings, iOSPlatform);
+
+namespace
+{
+ String GetAppName()
+ {
+ const auto gameSettings = GameSettings::Get();
+ String productName = gameSettings->ProductName;
+ productName.Replace(TEXT(" "), TEXT(""));
+ productName.Replace(TEXT("."), TEXT(""));
+ productName.Replace(TEXT("-"), TEXT(""));
+ return productName;
+ }
+
+ const Char* GetExportMethod(iOSPlatformSettings::ExportMethods method)
+ {
+ switch (method)
+ {
+ case iOSPlatformSettings::ExportMethods::AppStore: return TEXT("app-store");
+ case iOSPlatformSettings::ExportMethods::Development: return TEXT("development");
+ case iOSPlatformSettings::ExportMethods::AdHoc: return TEXT("ad-hoc");
+ case iOSPlatformSettings::ExportMethods::Enterprise: return TEXT("enterprise");
+ default: return TEXT("");
+ }
+ }
+
+ String GetUIInterfaceOrientation(iOSPlatformSettings::UIInterfaceOrientations orientations)
+ {
+ String result;
+ if (EnumHasAnyFlags(orientations, iOSPlatformSettings::UIInterfaceOrientations::Portrait))
+ result += TEXT("UIInterfaceOrientationPortrait ");
+ if (EnumHasAnyFlags(orientations, iOSPlatformSettings::UIInterfaceOrientations::PortraitUpsideDown))
+ result += TEXT("UIInterfaceOrientationPortraitUpsideDown ");
+ if (EnumHasAnyFlags(orientations, iOSPlatformSettings::UIInterfaceOrientations::LandscapeLeft))
+ result += TEXT("UIInterfaceOrientationLandscapeLeft ");
+ if (EnumHasAnyFlags(orientations, iOSPlatformSettings::UIInterfaceOrientations::LandscapeRight))
+ result += TEXT("UIInterfaceOrientationLandscapeRight ");
+ result = result.TrimTrailing();
+ return result;
+ }
+}
+
+const Char* iOSPlatformTools::GetDisplayName() const
+{
+ return TEXT("iOS");
+}
+
+const Char* iOSPlatformTools::GetName() const
+{
+ return TEXT("iOS");
+}
+
+PlatformType iOSPlatformTools::GetPlatform() const
+{
+ return PlatformType::iOS;
+}
+
+ArchitectureType iOSPlatformTools::GetArchitecture() const
+{
+ return ArchitectureType::ARM64;
+}
+
+DotNetAOTModes iOSPlatformTools::UseAOT() const
+{
+ return DotNetAOTModes::MonoAOTDynamic;
+}
+
+PixelFormat iOSPlatformTools::GetTextureFormat(CookingData& data, TextureBase* texture, PixelFormat format)
+{
+ // TODO: add ETC compression support for iOS
+ // TODO: add ASTC compression support for iOS
+
+ if (PixelFormatExtensions::IsCompressedBC(format))
+ {
+ switch (format)
+ {
+ case PixelFormat::BC1_Typeless:
+ case PixelFormat::BC2_Typeless:
+ case PixelFormat::BC3_Typeless:
+ return PixelFormat::R8G8B8A8_Typeless;
+ case PixelFormat::BC1_UNorm:
+ case PixelFormat::BC2_UNorm:
+ case PixelFormat::BC3_UNorm:
+ return PixelFormat::R8G8B8A8_UNorm;
+ case PixelFormat::BC1_UNorm_sRGB:
+ case PixelFormat::BC2_UNorm_sRGB:
+ case PixelFormat::BC3_UNorm_sRGB:
+ return PixelFormat::R8G8B8A8_UNorm_sRGB;
+ case PixelFormat::BC4_Typeless:
+ return PixelFormat::R8_Typeless;
+ case PixelFormat::BC4_UNorm:
+ return PixelFormat::R8_UNorm;
+ case PixelFormat::BC4_SNorm:
+ return PixelFormat::R8_SNorm;
+ case PixelFormat::BC5_Typeless:
+ return PixelFormat::R16G16_Typeless;
+ case PixelFormat::BC5_UNorm:
+ return PixelFormat::R16G16_UNorm;
+ case PixelFormat::BC5_SNorm:
+ return PixelFormat::R16G16_SNorm;
+ case PixelFormat::BC7_Typeless:
+ case PixelFormat::BC6H_Typeless:
+ return PixelFormat::R16G16B16A16_Typeless;
+ case PixelFormat::BC7_UNorm:
+ case PixelFormat::BC6H_Uf16:
+ case PixelFormat::BC6H_Sf16:
+ return PixelFormat::R16G16B16A16_Float;
+ case PixelFormat::BC7_UNorm_sRGB:
+ return PixelFormat::R16G16B16A16_UNorm;
+ default:
+ return format;
+ }
+ }
+
+ return format;
+}
+
+bool iOSPlatformTools::IsNativeCodeFile(CookingData& data, const String& file)
+{
+ String extension = FileSystem::GetExtension(file);
+ return extension.IsEmpty() || extension == TEXT("dylib");
+}
+
+void iOSPlatformTools::OnBuildStarted(CookingData& data)
+{
+ // Adjust the cooking output folders for packaging app
+ const Char* subDir = TEXT("FlaxGame/Data");
+ data.DataOutputPath /= subDir;
+ data.NativeCodeOutputPath /= subDir;
+ data.ManagedCodeOutputPath /= subDir;
+
+ PlatformTools::OnBuildStarted(data);
+}
+
+bool iOSPlatformTools::OnPostProcess(CookingData& data)
+{
+ const auto gameSettings = GameSettings::Get();
+ const auto platformSettings = iOSPlatformSettings::Get();
+ const auto platformDataPath = data.GetPlatformBinariesRoot();
+ const auto projectVersion = Editor::Project->Version.ToString();
+ const auto appName = GetAppName();
+
+ // Setup package name (eg. com.company.project)
+ String appIdentifier = platformSettings->AppIdentifier;
+ if (EditorUtilities::FormatAppPackageName(appIdentifier))
+ return true;
+
+ // Copy fresh Gradle project template
+ if (FileSystem::CopyDirectory(data.OriginalOutputPath, platformDataPath / TEXT("Project"), true))
+ {
+ LOG(Error, "Failed to deploy XCode project to {0} from {1}", data.OriginalOutputPath, platformDataPath);
+ return true;
+ }
+
+ // Format project template files
+ Dictionary configReplaceMap;
+ configReplaceMap[TEXT("${AppName}")] = appName;
+ configReplaceMap[TEXT("${AppIdentifier}")] = appIdentifier;
+ configReplaceMap[TEXT("${AppTeamId}")] = platformSettings->AppTeamId;
+ configReplaceMap[TEXT("${AppVersion}")] = platformSettings->AppVersion;
+ configReplaceMap[TEXT("${ProjectName}")] = gameSettings->ProductName;
+ configReplaceMap[TEXT("${ProjectVersion}")] = projectVersion;
+ configReplaceMap[TEXT("${HeaderSearchPaths}")] = Globals::StartupFolder;
+ configReplaceMap[TEXT("${ExportMethod}")] = GetExportMethod(platformSettings->ExportMethod);
+ configReplaceMap[TEXT("${UISupportedInterfaceOrientations_iPhone}")] = GetUIInterfaceOrientation(platformSettings->SupportedInterfaceOrientationsiPhone);
+ configReplaceMap[TEXT("${UISupportedInterfaceOrientations_iPad}")] = GetUIInterfaceOrientation(platformSettings->SupportedInterfaceOrientationsiPad);
+ {
+ // Initialize auto-generated areas as empty
+ configReplaceMap[TEXT("${PBXBuildFile}")] = String::Empty;
+ configReplaceMap[TEXT("${PBXCopyFilesBuildPhaseFiles}")] = String::Empty;
+ configReplaceMap[TEXT("${PBXFileReference}")] = String::Empty;
+ configReplaceMap[TEXT("${PBXFrameworksBuildPhase}")] = String::Empty;
+ configReplaceMap[TEXT("${PBXFrameworksGroup}")] = String::Empty;
+ configReplaceMap[TEXT("${PBXFilesGroup}")] = String::Empty;
+ configReplaceMap[TEXT("${PBXResourcesGroup}")] = String::Empty;
+ }
+ {
+ // Rename dotnet license files to not mislead the actual game licensing
+ FileSystem::MoveFile(data.DataOutputPath / TEXT("Dotnet/DOTNET-LICENSE.TXT"), data.DataOutputPath / TEXT("Dotnet/LICENSE.TXT"), true);
+ FileSystem::MoveFile(data.DataOutputPath / TEXT("Dotnet/DOTNET-THIRD-PARTY-NOTICES.TXT"), data.DataOutputPath / TEXT("Dotnet/THIRD-PARTY-NOTICES.TXT"), true);
+ }
+ Array files;
+ FileSystem::DirectoryGetFiles(files, data.DataOutputPath, TEXT("*"), DirectorySearchOption::AllDirectories);
+ for (const String& file : files)
+ {
+ String name = StringUtils::GetFileName(file);
+ if (name == TEXT(".DS_Store") || name == TEXT("FlaxGame"))
+ continue;
+ String fileId = Guid::New().ToString(Guid::FormatType::N).Left(24);
+ String projectPath = FileSystem::ConvertAbsolutePathToRelative(data.DataOutputPath, file);
+ if (name.EndsWith(TEXT(".dylib")))
+ {
+ String frameworkId = Guid::New().ToString(Guid::FormatType::N).Left(24);
+ String frameworkEmbedId = Guid::New().ToString(Guid::FormatType::N).Left(24);
+ configReplaceMap[TEXT("${PBXBuildFile}")] += String::Format(TEXT("\t\t{0} /* {1} in Frameworks */ = {{isa = PBXBuildFile; fileRef = {2} /* {1} */; }};\n"), frameworkId, name, fileId);
+ configReplaceMap[TEXT("${PBXBuildFile}")] += String::Format(TEXT("\t\t{0} /* {1} in Embed Frameworks */ = {{isa = PBXBuildFile; fileRef = {2} /* {1} */; settings = {{ATTRIBUTES = (CodeSignOnCopy, ); }}; }};\n"), frameworkEmbedId, name, fileId);
+ configReplaceMap[TEXT("${PBXCopyFilesBuildPhaseFiles}")] += String::Format(TEXT("\t\t\t\t{0} /* {1} in Embed Frameworks */,\n"), frameworkEmbedId, name);
+ configReplaceMap[TEXT("${PBXFileReference}")] += String::Format(TEXT("\t\t{0} /* {1} */ = {{isa = PBXFileReference; lastKnownFileType = \"compiled.mach-o.dylib\"; name = \"{1}\"; path = \"FlaxGame/Data/{2}\"; sourceTree = \"\"; }};\n"), fileId, name, projectPath);
+ configReplaceMap[TEXT("${PBXFrameworksBuildPhase}")] += String::Format(TEXT("\t\t\t\t{0} /* {1} in Frameworks */,\n"), frameworkId, name);
+ configReplaceMap[TEXT("${PBXFrameworksGroup}")] += String::Format(TEXT("\t\t\t\t{0} /* {1} */,\n"), fileId, name);
+ }
+ else
+ {
+ String fileRefId = Guid::New().ToString(Guid::FormatType::N).Left(24);
+ configReplaceMap[TEXT("${PBXBuildFile}")] += String::Format(TEXT("\t\t{0} /* {1} in Resources */ = {{isa = PBXBuildFile; fileRef = {2} /* {1} */; }};\n"), fileRefId, name, fileId);
+ configReplaceMap[TEXT("${PBXFileReference}")] += String::Format(TEXT("\t\t{0} /* {1} */ = {{isa = PBXFileReference; lastKnownFileType = file; name = \"{1}\"; path = \"Data/{2}\"; sourceTree = \"\"; }};\n"), fileId, name, projectPath);
+ configReplaceMap[TEXT("${PBXFilesGroup}")] += String::Format(TEXT("\t\t\t\t{0} /* {1} */,\n"), fileId, name);
+ configReplaceMap[TEXT("${PBXResourcesGroup}")] += String::Format(TEXT("\t\t\t\t{0} /* {1} in Resources */,\n"), fileRefId, name);
+ }
+ }
+ bool failed = false;
+ failed |= EditorUtilities::ReplaceInFile(data.OriginalOutputPath / TEXT("FlaxGame.xcodeproj/project.pbxproj"), configReplaceMap);
+ failed |= EditorUtilities::ReplaceInFile(data.OriginalOutputPath / TEXT("ExportOptions.plist"), configReplaceMap);
+ if (failed)
+ {
+ LOG(Error, "Failed to format XCode project");
+ return true;
+ }
+
+ // Export images
+ // TODO: provide per-device icons (eg. iPad 1x, iPad 2x) instead of a single icon size
+ TextureData iconData;
+ if (!EditorUtilities::GetApplicationImage(platformSettings->OverrideIcon, iconData))
+ {
+ String outputPath = data.OriginalOutputPath / TEXT("FlaxGame/Assets.xcassets/AppIcon.appiconset/ios_store_icon.png");
+ if (EditorUtilities::ExportApplicationImage(iconData, 1024, 1024, PixelFormat::R8G8B8A8_UNorm, outputPath))
+ {
+ LOG(Error, "Failed to export application icon.");
+ return true;
+ }
+ }
+
+ // Package application
+ const auto buildSettings = BuildSettings::Get();
+ if (buildSettings->SkipPackaging)
+ return false;
+ GameCooker::PackageFiles();
+ {
+ LOG(Info, "Building app package...");
+ const Char* configuration = data.Configuration == BuildConfiguration::Release ? TEXT("Release") : TEXT("Debug");
+ String command = String::Format(TEXT("xcodebuild -project FlaxGame.xcodeproj -configuration {} -scheme FlaxGame -archivePath FlaxGame.xcarchive archive"), configuration);
+ int32 result = Platform::RunProcess(command, data.OriginalOutputPath);
+ if (result != 0)
+ {
+ data.Error(String::Format(TEXT("Failed to package app (result code: {0}). See log for more info."), result));
+ return true;
+ }
+ command = TEXT("xcodebuild -exportArchive -archivePath FlaxGame.xcarchive -allowProvisioningUpdates -exportPath . -exportOptionsPlist ExportOptions.plist");
+ result = Platform::RunProcess(command, data.OriginalOutputPath);
+ if (result != 0)
+ {
+ data.Error(String::Format(TEXT("Failed to package app (result code: {0}). See log for more info."), result));
+ return true;
+ }
+ const String ipaPath = data.OriginalOutputPath / TEXT("FlaxGame.ipa");
+ LOG(Info, "Output application package: {0} (size: {1} MB)", ipaPath, FileSystem::GetFileSize(ipaPath) / 1024 / 1024);
+ }
+
+ return false;
+}
+
+#endif
diff --git a/Source/Editor/Cooker/Platform/iOS/iOSPlatformTools.h b/Source/Editor/Cooker/Platform/iOS/iOSPlatformTools.h
new file mode 100644
index 000000000..854961923
--- /dev/null
+++ b/Source/Editor/Cooker/Platform/iOS/iOSPlatformTools.h
@@ -0,0 +1,27 @@
+// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.
+
+#pragma once
+
+#if PLATFORM_TOOLS_IOS
+
+#include "../../PlatformTools.h"
+
+///
+/// The iOS platform support tools.
+///
+class iOSPlatformTools : public PlatformTools
+{
+public:
+ // [PlatformTools]
+ const Char* GetDisplayName() const override;
+ const Char* GetName() const override;
+ PlatformType GetPlatform() const override;
+ ArchitectureType GetArchitecture() const override;
+ DotNetAOTModes UseAOT() const override;
+ PixelFormat GetTextureFormat(CookingData& data, TextureBase* texture, PixelFormat format) override;
+ bool IsNativeCodeFile(CookingData& data, const String& file) override;
+ void OnBuildStarted(CookingData& data) override;
+ bool OnPostProcess(CookingData& data) override;
+};
+
+#endif
diff --git a/Source/Editor/Cooker/PlatformTools.h b/Source/Editor/Cooker/PlatformTools.h
index 58888964f..533dad805 100644
--- a/Source/Editor/Cooker/PlatformTools.h
+++ b/Source/Editor/Cooker/PlatformTools.h
@@ -1,9 +1,8 @@
-// Copyright (c) 2012-2019 Wojciech Figat. All rights reserved.
+// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.
#pragma once
#include "CookingData.h"
-#include "Engine/Core/Collections/Dictionary.h"
#include "Editor/Scripting/ScriptsBuilder.h"
#include "Engine/Graphics/PixelFormat.h"
@@ -15,7 +14,6 @@ class TextureBase;
class FLAXENGINE_API PlatformTools
{
public:
-
///
/// Finalizes an instance of the class.
///
@@ -24,32 +22,35 @@ public:
///
/// Gets the name of the platform for UI and logging.
///
- /// The name.
virtual const Char* GetDisplayName() const = 0;
///
/// Gets the name of the platform for filesystem cache directories, deps folder.
///
- /// The name.
virtual const Char* GetName() const = 0;
///
/// Gets the type of the platform.
///
- /// The platform type.
virtual PlatformType GetPlatform() const = 0;
///
/// Gets the architecture of the platform.
///
- /// The architecture type.
virtual ArchitectureType GetArchitecture() const = 0;
///
- /// Gets the value indicating whenever platform requires AOT.
+ /// Gets the value indicating whenever platform requires AOT (needs C# assemblies to be precompiled).
///
- /// True if platform uses AOT and needs C# assemblies to be precompiled, otherwise false.
- virtual bool UseAOT() const
+ virtual DotNetAOTModes UseAOT() const
+ {
+ return DotNetAOTModes::None;
+ }
+
+ ///
+ /// Gets the value indicating whenever platform supports using system-installed .Net Runtime.
+ ///
+ virtual bool UseSystemDotnet() const
{
return false;
}
@@ -72,13 +73,9 @@ public:
/// The cooking data.
/// The file path.
/// True if it's a native file, otherwise false.
- virtual bool IsNativeCodeFile(CookingData& data, const String& file)
- {
- return false;
- }
+ virtual bool IsNativeCodeFile(CookingData& data, const String& file);
public:
-
///
/// Called when game building starts.
///
@@ -136,64 +133,6 @@ public:
return false;
}
- ///
- /// The C# scripts AOT configuration options.
- ///
- struct AotConfig
- {
- String AotCompilerPath;
- String AotCompilerArgs;
- String AssemblerPath;
- String AssemblerArgs;
- String ArchiverPath;
- String ArchiverArgs;
- String AuxToolPath;
- String AuxToolArgs;
- String AotCachePath;
- Dictionary EnvVars;
- Array AssembliesSearchDirs;
- Array Assemblies;
-
- AotConfig(CookingData& data)
- {
- Platform::GetEnvironmentVariables(EnvVars);
- EnvVars[TEXT("MONO_PATH")] = data.DataOutputPath / TEXT("Mono/lib/mono/4.5");
- AssembliesSearchDirs.Add(data.DataOutputPath / TEXT("Mono/lib/mono/4.5"));
- }
- };
-
- ///
- /// Called to configure AOT options.
- ///
- /// The cooking data.
- /// The configuration.
- virtual void OnConfigureAOT(CookingData& data, AotConfig& config)
- {
- }
-
- ///
- /// Called to execute AOT for the given assembly.
- ///
- /// The cooking data.
- /// The configuration.
- /// The input managed library file path.
- /// True if failed, otherwise false.
- virtual bool OnPerformAOT(CookingData& data, AotConfig& config, const String& assemblyPath)
- {
- return false;
- }
-
- ///
- /// Called after AOT execution for the assemblies.
- ///
- /// The cooking data.
- /// The configuration.
- /// True if failed, otherwise false.
- virtual bool OnPostProcessAOT(CookingData& data, AotConfig& config)
- {
- return false;
- }
-
///
/// Called during staged build post-processing.
///
diff --git a/Source/Editor/Cooker/Steps/CompileScriptsStep.cpp b/Source/Editor/Cooker/Steps/CompileScriptsStep.cpp
index ae865e14a..361e5f937 100644
--- a/Source/Editor/Cooker/Steps/CompileScriptsStep.cpp
+++ b/Source/Editor/Cooker/Steps/CompileScriptsStep.cpp
@@ -95,8 +95,8 @@ bool CompileScriptsStep::DeployBinaries(CookingData& data, const String& path, c
Scripting::ProcessBuildInfoPath(e.NativePath, projectFolderPath);
Scripting::ProcessBuildInfoPath(e.ManagedPath, projectFolderPath);
- e.NativePath = StringUtils::GetFileName(e.NativePath);
- e.ManagedPath = StringUtils::GetFileName(e.ManagedPath);
+ e.NativePath = String(StringUtils::GetFileName(e.NativePath));
+ e.ManagedPath = String(StringUtils::GetFileName(e.ManagedPath));
LOG(Info, "Collecting binary module {0}", e.Name);
}
@@ -129,7 +129,7 @@ bool CompileScriptsStep::DeployBinaries(CookingData& data, const String& path, c
continue;
if (FileSystem::CopyFile(dst, file))
{
- data.Error(TEXT("Failed to copy file from {0} to {1}."), file, dst);
+ data.Error(String::Format(TEXT("Failed to copy file from {0} to {1}."), file, dst));
return true;
}
@@ -154,60 +154,7 @@ bool CompileScriptsStep::Perform(CookingData& data)
String target = project->GameTarget;
StringView workingDir;
const Char *platform, *architecture, *configuration = ::ToString(data.Configuration);
- switch (data.Platform)
- {
- case BuildPlatform::Windows32:
- platform = TEXT("Windows");
- architecture = TEXT("x86");
- break;
- case BuildPlatform::Windows64:
- platform = TEXT("Windows");
- architecture = TEXT("x64");
- break;
- case BuildPlatform::UWPx86:
- platform = TEXT("UWP");
- architecture = TEXT("x86");
- break;
- case BuildPlatform::UWPx64:
- platform = TEXT("UWP");
- architecture = TEXT("x64");
- break;
- case BuildPlatform::XboxOne:
- platform = TEXT("XboxOne");
- architecture = TEXT("x64");
- break;
- case BuildPlatform::LinuxX64:
- platform = TEXT("Linux");
- architecture = TEXT("x64");
- break;
- case BuildPlatform::PS4:
- platform = TEXT("PS4");
- architecture = TEXT("x64");
- break;
- case BuildPlatform::XboxScarlett:
- platform = TEXT("XboxScarlett");
- architecture = TEXT("x64");
- break;
- case BuildPlatform::AndroidARM64:
- platform = TEXT("Android");
- architecture = TEXT("ARM64");
- break;
- case BuildPlatform::Switch:
- platform = TEXT("Switch");
- architecture = TEXT("ARM64");
- break;
- case BuildPlatform::PS5:
- platform = TEXT("PS5");
- architecture = TEXT("x64");
- break;
- case BuildPlatform::MacOSx64:
- platform = TEXT("Mac");
- architecture = TEXT("x64");
- break;
- default:
- LOG(Error, "Unknown or unsupported build platform.");
- return true;
- }
+ data.GetBuildPlatformName(platform, architecture);
String targetBuildInfo = project->ProjectFolderPath / TEXT("Binaries") / target / platform / architecture / configuration / target + TEXT(".Build.json");
if (target.IsEmpty())
{
@@ -241,8 +188,8 @@ bool CompileScriptsStep::Perform(CookingData& data)
LOG(Info, "Starting scripts compilation for game...");
const String logFile = data.CacheDirectory / TEXT("CompileLog.txt");
auto args = String::Format(
- TEXT("-log -logfile=\"{4}\" -build -mutex -buildtargets={0} -platform={1} -arch={2} -configuration={3}"),
- target, platform, architecture, configuration, logFile);
+ TEXT("-log -logfile=\"{4}\" -build -mutex -buildtargets={0} -platform={1} -arch={2} -configuration={3} -aotMode={5}"),
+ target, platform, architecture, configuration, logFile, ToString(data.Tools->UseAOT()));
#if PLATFORM_WINDOWS
if (data.Platform == BuildPlatform::LinuxX64)
#elif PLATFORM_LINUX
@@ -257,7 +204,7 @@ bool CompileScriptsStep::Perform(CookingData& data)
// Assume FlaxGame was prebuilt for target platform
args += TEXT(" -SkipTargets=FlaxGame");
}
- for (auto& define : data.CustomDefines)
+ for (const String& define : data.CustomDefines)
{
args += TEXT(" -D");
args += define;
diff --git a/Source/Editor/Cooker/Steps/CookAssetsStep.cpp b/Source/Editor/Cooker/Steps/CookAssetsStep.cpp
index 16df276f7..4d4ea5e07 100644
--- a/Source/Editor/Cooker/Steps/CookAssetsStep.cpp
+++ b/Source/Editor/Cooker/Steps/CookAssetsStep.cpp
@@ -410,7 +410,7 @@ bool ProcessShaderBase(CookAssetsStep::AssetCookData& data, ShaderAssetBase* ass
assetBase->InitCompilationOptions(options); \
if (ShadersCompilation::Compile(options)) \
{ \
- data.Data.Error(TEXT("Failed to compile shader '{0}' (profile: {1})."), asset->ToString(), ::ToString(options.Profile)); \
+ data.Data.Error(String::Format(TEXT("Failed to compile shader '{0}' (profile: {1})."), asset->ToString(), ::ToString(options.Profile))); \
return true; \
} \
includes.Clear(); \
@@ -529,11 +529,20 @@ bool ProcessShaderBase(CookAssetsStep::AssetCookData& data, ShaderAssetBase* ass
#endif
#if PLATFORM_TOOLS_MAC
case BuildPlatform::MacOSx64:
+ case BuildPlatform::MacOSARM64:
{
const char* platformDefineName = "PLATFORM_MAC";
COMPILE_PROFILE(Vulkan_SM5, SHADER_FILE_CHUNK_INTERNAL_VULKAN_SM5_CACHE);
break;
}
+#endif
+#if PLATFORM_TOOLS_IOS
+ case BuildPlatform::iOSARM64:
+ {
+ const char* platformDefineName = "PLATFORM_IOS";
+ COMPILE_PROFILE(Vulkan_SM5, SHADER_FILE_CHUNK_INTERNAL_VULKAN_SM5_CACHE);
+ break;
+ }
#endif
default:
{
diff --git a/Source/Editor/Cooker/Steps/DeployDataStep.cpp b/Source/Editor/Cooker/Steps/DeployDataStep.cpp
index 49d71f09f..c9bbc016e 100644
--- a/Source/Editor/Cooker/Steps/DeployDataStep.cpp
+++ b/Source/Editor/Cooker/Steps/DeployDataStep.cpp
@@ -1,19 +1,23 @@
// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.
#include "DeployDataStep.h"
+#include "Engine/Platform/File.h"
#include "Engine/Platform/FileSystem.h"
-#include "Editor/Cooker/PlatformTools.h"
+#include "Engine/Core/Collections/Sorting.h"
#include "Engine/Core/Config/BuildSettings.h"
#include "Engine/Core/Config/GameSettings.h"
#include "Engine/Renderer/ReflectionsPass.h"
#include "Engine/Renderer/AntiAliasing/SMAA.h"
#include "Engine/Engine/Globals.h"
+#include "Editor/Cooker/PlatformTools.h"
+#include "Editor/Utilities/EditorUtilities.h"
bool DeployDataStep::Perform(CookingData& data)
{
data.StepProgress(TEXT("Deploying engine data"), 0);
const String depsRoot = data.GetPlatformBinariesRoot();
- const auto gameSettings = GameSettings::Get();
+ const auto& gameSettings = *GameSettings::Get();
+ const auto& buildSettings = *BuildSettings::Get();
// Setup output folders and copy required data
const auto contentDir = data.DataOutputPath / TEXT("Content");
@@ -26,22 +30,275 @@ bool DeployDataStep::Perform(CookingData& data)
Platform::Sleep(10);
}
FileSystem::CreateDirectory(contentDir);
- const auto srcMono = depsRoot / TEXT("Mono");
- const auto dstMono = data.DataOutputPath / TEXT("Mono");
+ const String dstMono = data.DataOutputPath / TEXT("Mono");
+#if USE_NETCORE
+ {
+ // Remove old Mono files
+ FileSystem::DeleteDirectory(dstMono);
+ FileSystem::DeleteFile(data.DataOutputPath / TEXT("MonoPosixHelper.dll"));
+ }
+ String dstDotnet = data.DataOutputPath / TEXT("Dotnet");
+ const DotNetAOTModes aotMode = data.Tools->UseAOT();
+ const bool usAOT = aotMode != DotNetAOTModes::None;
+ if (usAOT)
+ {
+ // Deploy Dotnet files into intermediate cooking directory for AOT
+ FileSystem::DeleteDirectory(dstDotnet);
+ dstDotnet = data.ManagedCodeOutputPath;
+ }
+ if (buildSettings.SkipDotnetPackaging && data.Tools->UseSystemDotnet())
+ {
+ // Use system-installed .Net Runtime
+ FileSystem::DeleteDirectory(dstDotnet);
+ }
+ else
+ {
+ // Deploy .Net Runtime files
+ FileSystem::CreateDirectory(dstDotnet);
+ String srcDotnet = depsRoot / TEXT("Dotnet");
+ if (FileSystem::DirectoryExists(srcDotnet))
+ {
+ // Use prebuilt .Net installation for that platform
+ LOG(Info, "Using .Net Runtime {} at {}", data.Tools->GetName(), srcDotnet);
+ if (EditorUtilities::CopyDirectoryIfNewer(dstDotnet, srcDotnet, true))
+ {
+ data.Error(TEXT("Failed to copy .Net runtime data files."));
+ return true;
+ }
+ }
+ else
+ {
+ bool canUseSystemDotnet = false;
+ switch (data.Platform)
+ {
+ case BuildPlatform::Windows32:
+ case BuildPlatform::Windows64:
+ canUseSystemDotnet = PLATFORM_TYPE == PlatformType::Windows;
+ break;
+ case BuildPlatform::LinuxX64:
+ canUseSystemDotnet = PLATFORM_TYPE == PlatformType::Linux;
+ break;
+ case BuildPlatform::MacOSx64:
+ case BuildPlatform::MacOSARM64:
+ canUseSystemDotnet = PLATFORM_TYPE == PlatformType::Mac;
+ break;
+ }
+ if (canUseSystemDotnet && (aotMode == DotNetAOTModes::None || aotMode == DotNetAOTModes::ILC))
+ {
+ // Ask Flax.Build to provide .Net SDK location for the current platform
+ String sdks;
+ bool failed = ScriptsBuilder::RunBuildTool(TEXT("-log -logMessagesOnly -logFileWithConsole -logfile=SDKs.txt -printSDKs"), data.CacheDirectory);
+ failed |= File::ReadAllText(data.CacheDirectory / TEXT("SDKs.txt"), sdks);
+ int32 idx = sdks.Find(TEXT("DotNetSdk, "), StringSearchCase::CaseSensitive);
+ if (idx != -1)
+ {
+ idx = sdks.Find(TEXT(", "), StringSearchCase::CaseSensitive, idx + 12);
+ idx += 2;
+ int32 end = sdks.Find(TEXT("\n"), StringSearchCase::CaseSensitive, idx);
+ if (sdks[end] == '\r')
+ end--;
+ srcDotnet = String(sdks.Get() + idx, end - idx).TrimTrailing();
+ }
+ if (failed || !FileSystem::DirectoryExists(srcDotnet))
+ {
+ data.Error(TEXT("Failed to get .Net SDK location for a current platform."));
+ return true;
+ }
+
+ // Select version to use
+ Array versions;
+ FileSystem::GetChildDirectories(versions, srcDotnet / TEXT("host/fxr"));
+ if (versions.Count() == 0)
+ {
+ data.Error(TEXT("Failed to get .Net SDK location for a current platform."));
+ return true;
+ }
+ for (String& version : versions)
+ {
+ version = String(StringUtils::GetFileName(version));
+ if (!version.StartsWith(TEXT("7.")))
+ version.Clear();
+ }
+ Sorting::QuickSort(versions.Get(), versions.Count());
+ const String version = versions.Last();
+ FileSystem::NormalizePath(srcDotnet);
+ LOG(Info, "Using .Net Runtime {} at {}", version, srcDotnet);
+
+ // Check if previously deployed files are valid (eg. system-installed .NET was updated from version 7.0.3 to 7.0.5)
+ {
+ const String dotnetCacheFilePath = data.CacheDirectory / TEXT("SystemDotnetInfo.txt");
+ String dotnetCachedValue = String::Format(TEXT("{};{}"), version, srcDotnet);
+ if (FileSystem::DirectoryExists(dstDotnet))
+ {
+ String cachedData;
+ File::ReadAllText(dotnetCacheFilePath, cachedData);
+ if (cachedData != dotnetCachedValue)
+ {
+ FileSystem::DeleteDirectory(dstDotnet);
+ FileSystem::CreateDirectory(dstDotnet);
+ }
+ }
+ File::WriteAllText(dotnetCacheFilePath, dotnetCachedValue, Encoding::ANSI);
+ }
+
+ // Deploy runtime files
+ FileSystem::CopyFile(dstDotnet / TEXT("LICENSE.TXT"), srcDotnet / TEXT("LICENSE.txt"));
+ FileSystem::CopyFile(dstDotnet / TEXT("LICENSE.TXT"), srcDotnet / TEXT("LICENSE.TXT"));
+ FileSystem::CopyFile(dstDotnet / TEXT("THIRD-PARTY-NOTICES.TXT"), srcDotnet / TEXT("ThirdPartyNotices.txt"));
+ FileSystem::CopyFile(dstDotnet / TEXT("THIRD-PARTY-NOTICES.TXT"), srcDotnet / TEXT("THIRD-PARTY-NOTICES.TXT"));
+ if (usAOT)
+ {
+ failed |= EditorUtilities::CopyDirectoryIfNewer(dstDotnet, srcDotnet / TEXT("shared/Microsoft.NETCore.App") / version, true);
+ }
+ else
+ {
+ failed |= EditorUtilities::CopyDirectoryIfNewer(dstDotnet / TEXT("host/fxr") / version, srcDotnet / TEXT("host/fxr") / version, true);
+ failed |= EditorUtilities::CopyDirectoryIfNewer(dstDotnet / TEXT("shared/Microsoft.NETCore.App") / version, srcDotnet / TEXT("shared/Microsoft.NETCore.App") / version, true);
+ }
+ if (failed)
+ {
+ data.Error(TEXT("Failed to copy .Net runtime data files."));
+ return true;
+ }
+ }
+ else
+ {
+ // Ask Flax.Build to provide .Net Host Runtime location for the target platform
+ String sdks;
+ const Char *platformName, *archName;
+ data.GetBuildPlatformName(platformName, archName);
+ String args = String::Format(TEXT("-log -logMessagesOnly -logFileWithConsole -logfile=SDKs.txt -printDotNetRuntime -platform={} -arch={}"), platformName, archName);
+ bool failed = ScriptsBuilder::RunBuildTool(args, data.CacheDirectory);
+ failed |= File::ReadAllText(data.CacheDirectory / TEXT("SDKs.txt"), sdks);
+ Array parts;
+ sdks.Split(',', parts);
+ failed |= parts.Count() != 3;
+ if (!failed)
+ {
+ srcDotnet = parts[2].TrimTrailing();
+ }
+ if (failed || !FileSystem::DirectoryExists(srcDotnet))
+ {
+ data.Error(TEXT("Failed to get .Net SDK location for a current platform."));
+ return true;
+ }
+ FileSystem::NormalizePath(srcDotnet);
+ LOG(Info, "Using .Net Runtime {} at {}", TEXT("Host"), srcDotnet);
+
+ // Deploy runtime files
+ const Char* corlibPrivateName = TEXT("System.Private.CoreLib.dll");
+ const bool srcDotnetFromEngine = srcDotnet.Contains(TEXT("Source/Platforms"));
+ String packFolder = srcDotnet / TEXT("../../../");
+ String dstDotnetLibs = dstDotnet, srcDotnetLibs = srcDotnet;
+ StringUtils::PathRemoveRelativeParts(packFolder);
+ if (usAOT)
+ {
+ if (srcDotnetFromEngine)
+ {
+ // AOT runtime files inside Engine Platform folder
+ packFolder /= TEXT("Dotnet");
+ dstDotnetLibs /= TEXT("lib/net7.0");
+ srcDotnetLibs = packFolder / TEXT("lib/net7.0");
+ }
+ else
+ {
+ // Runtime files inside Dotnet SDK folder but placed for AOT
+ dstDotnetLibs /= TEXT("lib/net7.0");
+ srcDotnetLibs /= TEXT("../lib/net7.0");
+ }
+ }
+ else
+ {
+ if (srcDotnetFromEngine)
+ {
+ // Runtime files inside Engine Platform folder
+ dstDotnetLibs /= TEXT("lib/net7.0");
+ srcDotnetLibs /= TEXT("lib/net7.0");
+ }
+ else
+ {
+ // Runtime files inside Dotnet SDK folder
+ dstDotnetLibs /= TEXT("shared/Microsoft.NETCore.App");
+ srcDotnetLibs /= TEXT("../lib/net7.0");
+ }
+ }
+ FileSystem::CopyFile(dstDotnet / TEXT("LICENSE.TXT"), packFolder / TEXT("LICENSE.txt"));
+ FileSystem::CopyFile(dstDotnet / TEXT("LICENSE.TXT"), packFolder / TEXT("LICENSE.TXT"));
+ FileSystem::CopyFile(dstDotnet / TEXT("THIRD-PARTY-NOTICES.TXT"), packFolder / TEXT("ThirdPartyNotices.txt"));
+ FileSystem::CopyFile(dstDotnet / TEXT("THIRD-PARTY-NOTICES.TXT"), packFolder / TEXT("THIRD-PARTY-NOTICES.TXT"));
+ failed |= EditorUtilities::CopyDirectoryIfNewer(dstDotnetLibs, srcDotnetLibs, true);
+ if (FileSystem::FileExists(srcDotnet / corlibPrivateName))
+ failed |= EditorUtilities::CopyFileIfNewer(dstDotnetLibs / corlibPrivateName, srcDotnet / corlibPrivateName);
+ switch (data.Platform)
+ {
+#define DEPLOY_NATIVE_FILE(filename) failed |= FileSystem::CopyFile(data.NativeCodeOutputPath / TEXT(filename), srcDotnet / TEXT(filename));
+ case BuildPlatform::AndroidARM64:
+ if (data.Configuration != BuildConfiguration::Release)
+ {
+ DEPLOY_NATIVE_FILE("libmono-component-debugger.so");
+ DEPLOY_NATIVE_FILE("libmono-component-diagnostics_tracing.so");
+ DEPLOY_NATIVE_FILE("libmono-component-hot_reload.so");
+ }
+ DEPLOY_NATIVE_FILE("libmonosgen-2.0.so");
+ DEPLOY_NATIVE_FILE("libSystem.IO.Compression.Native.so");
+ DEPLOY_NATIVE_FILE("libSystem.Native.so");
+ DEPLOY_NATIVE_FILE("libSystem.Security.Cryptography.Native.Android.so");
+ break;
+ case BuildPlatform::iOSARM64:
+ DEPLOY_NATIVE_FILE("libmonosgen-2.0.dylib");
+ DEPLOY_NATIVE_FILE("libSystem.IO.Compression.Native.dylib");
+ DEPLOY_NATIVE_FILE("libSystem.Native.dylib");
+ DEPLOY_NATIVE_FILE("libSystem.Net.Security.Native.dylib");
+ DEPLOY_NATIVE_FILE("libSystem.Security.Cryptography.Native.Apple.dylib");
+ break;
+#undef DEPLOY_NATIVE_FILE
+ default: ;
+ }
+ if (failed)
+ {
+ data.Error(TEXT("Failed to copy .Net runtime data files."));
+ return true;
+ }
+ }
+ }
+
+ // Optimize deployed C# class library (remove DLLs unused by scripts)
+ if (aotMode == DotNetAOTModes::None && buildSettings.SkipUnusedDotnetLibsPackaging)
+ {
+ LOG(Info, "Optimizing .NET class library size to include only used assemblies");
+ const String logFile = data.CacheDirectory / TEXT("StripDotnetLibs.txt");
+ String args = String::Format(
+ TEXT("-log -logfile=\"{}\" -runDotNetClassLibStripping -mutex -binaries=\"{}\""),
+ logFile, data.DataOutputPath);
+ for (const String& define : data.CustomDefines)
+ {
+ args += TEXT(" -D");
+ args += define;
+ }
+ if (ScriptsBuilder::RunBuildTool(args))
+ {
+ data.Error(TEXT("Failed to optimize .Net class library."));
+ return true;
+ }
+ }
+ }
+#else
if (!FileSystem::DirectoryExists(dstMono))
{
+ // Deploy Mono files (from platform data folder)
+ const String srcMono = depsRoot / TEXT("Mono");
if (!FileSystem::DirectoryExists(srcMono))
{
data.Error(TEXT("Missing Mono runtime data files."));
return true;
}
-
if (FileSystem::CopyDirectory(dstMono, srcMono, true))
{
data.Error(TEXT("Failed to copy Mono runtime data files."));
return true;
}
}
+#endif
// Deploy engine data for the target platform
if (data.Tools->OnDeployBinaries(data))
@@ -82,7 +339,7 @@ bool DeployDataStep::Perform(CookingData& data)
data.AddRootEngineAsset(TEXT("Engine/DefaultMaterial"));
data.AddRootEngineAsset(TEXT("Engine/DefaultDeformableMaterial"));
data.AddRootEngineAsset(TEXT("Engine/DefaultTerrainMaterial"));
- if (!gameSettings->NoSplashScreen && !gameSettings->SplashScreen.IsValid())
+ if (!gameSettings.NoSplashScreen && !gameSettings.SplashScreen.IsValid())
data.AddRootEngineAsset(TEXT("Engine/Textures/Logo"));
data.AddRootEngineAsset(TEXT("Engine/Textures/NormalTexture"));
data.AddRootEngineAsset(TEXT("Engine/Textures/BlackTexture"));
@@ -112,7 +369,6 @@ bool DeployDataStep::Perform(CookingData& data)
// Register game assets
data.StepProgress(TEXT("Deploying game data"), 50);
- auto& buildSettings = *BuildSettings::Get();
for (auto& e : buildSettings.AdditionalAssets)
data.AddRootAsset(e.GetID());
for (auto& e : buildSettings.AdditionalScenes)
diff --git a/Source/Editor/Cooker/Steps/PostProcessStep.cpp b/Source/Editor/Cooker/Steps/PostProcessStep.cpp
index b79eed03d..dce8c5d2f 100644
--- a/Source/Editor/Cooker/Steps/PostProcessStep.cpp
+++ b/Source/Editor/Cooker/Steps/PostProcessStep.cpp
@@ -2,9 +2,20 @@
#include "PostProcessStep.h"
#include "Editor/Cooker/PlatformTools.h"
+#include "Engine/Platform/FileSystem.h"
bool PostProcessStep::Perform(CookingData& data)
{
+ // Print .NET stats
+ const DotNetAOTModes aotMode = data.Tools->UseAOT();
+ uint64 outputSize = FileSystem::GetDirectorySize(data.DataOutputPath / TEXT("Dotnet"));
+ if (aotMode == DotNetAOTModes::None)
+ {
+ for (auto& binaryModule : data.BinaryModules)
+ outputSize += FileSystem::GetFileSize(data.DataOutputPath / binaryModule.ManagedPath);
+ }
+ LOG(Info, "Output .NET files size: {0} MB", (uint32)(outputSize / (1024ull * 1024)));
+
GameCooker::PostProcessFiles();
return data.Tools->OnPostProcess(data);
}
diff --git a/Source/Editor/Cooker/Steps/PrecompileAssembliesStep.cpp b/Source/Editor/Cooker/Steps/PrecompileAssembliesStep.cpp
index 54bd4111d..deb8f5022 100644
--- a/Source/Editor/Cooker/Steps/PrecompileAssembliesStep.cpp
+++ b/Source/Editor/Cooker/Steps/PrecompileAssembliesStep.cpp
@@ -1,78 +1,86 @@
// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.
#include "PrecompileAssembliesStep.h"
-#include "Editor/Scripting/ScriptsBuilder.h"
#include "Engine/Platform/FileSystem.h"
+#include "Engine/Platform/File.h"
+#include "Engine/Core/Config/BuildSettings.h"
+#include "Engine/Engine/Globals.h"
+#include "Editor/Scripting/ScriptsBuilder.h"
#include "Editor/Cooker/PlatformTools.h"
+#include "Editor/Utilities/EditorUtilities.h"
+
+void PrecompileAssembliesStep::OnBuildStarted(CookingData& data)
+{
+ const DotNetAOTModes aotMode = data.Tools->UseAOT();
+ if (aotMode == DotNetAOTModes::None)
+ return;
+ const auto& buildSettings = *BuildSettings::Get();
+
+ // Redirect C# assemblies to intermediate cooking directory (processed by ILC)
+ data.ManagedCodeOutputPath = data.CacheDirectory / TEXT("AOTAssemblies");
+
+ // Reset any AOT cache from previous run if the AOT mode has changed (eg. Mono AOT -> ILC on Desktop)
+ const String aotModeCacheFilePath = data.ManagedCodeOutputPath / TEXT("AOTMode.txt");
+ String aotModeCacheValue = String::Format(TEXT("{};{};{};{}"),
+ (int32)aotMode,
+ (int32)data.Configuration,
+ (int32)buildSettings.SkipUnusedDotnetLibsPackaging,
+ FileSystem::GetFileLastEditTime(ScriptsBuilder::GetBuildToolPath()).Ticks);
+ for (const String& define : data.CustomDefines)
+ aotModeCacheValue += define;
+ if (FileSystem::DirectoryExists(data.ManagedCodeOutputPath))
+ {
+ String cachedData;
+ File::ReadAllText(aotModeCacheFilePath, cachedData);
+ if (cachedData != aotModeCacheValue)
+ {
+ LOG(Info, "AOT cache invalidation");
+ FileSystem::DeleteDirectory(data.ManagedCodeOutputPath);
+ }
+ }
+ if (!FileSystem::DirectoryExists(data.ManagedCodeOutputPath))
+ {
+ FileSystem::CreateDirectory(data.ManagedCodeOutputPath);
+ File::WriteAllText(aotModeCacheFilePath, aotModeCacheValue, Encoding::ANSI);
+ }
+}
bool PrecompileAssembliesStep::Perform(CookingData& data)
{
- // Skip for some platforms
- if (!data.Tools->UseAOT())
+ const DotNetAOTModes aotMode = data.Tools->UseAOT();
+ if (aotMode == DotNetAOTModes::None)
+ return false;
+ const auto& buildSettings = *BuildSettings::Get();
+ if (buildSettings.SkipDotnetPackaging && data.Tools->UseSystemDotnet())
return false;
LOG(Info, "Using AOT...");
-
- // Useful references about AOT:
- // http://www.mono-project.com/docs/advanced/runtime/docs/aot/
- // http://www.mono-project.com/docs/advanced/aot/
-
const String infoMsg = TEXT("Running AOT");
data.StepProgress(infoMsg, 0);
- // Setup
- PlatformTools::AotConfig config(data);
- data.Tools->OnConfigureAOT(data, config);
+ // Override Newtonsoft.Json with AOT-version (one that doesn't use System.Reflection.Emit)
+ EditorUtilities::CopyFileIfNewer(data.ManagedCodeOutputPath / TEXT("Newtonsoft.Json.dll"), Globals::StartupFolder / TEXT("Source/Platforms/DotNet/AOT/Newtonsoft.Json.dll"));
+ FileSystem::DeleteFile(data.ManagedCodeOutputPath / TEXT("Newtonsoft.Json.xml"));
+ FileSystem::DeleteFile(data.ManagedCodeOutputPath / TEXT("Newtonsoft.Json.pdb"));
- // Prepare output directory
- config.AotCachePath = data.DataOutputPath / TEXT("Mono/lib/mono/aot-cache");
- switch (data.Tools->GetArchitecture())
+ // Run AOT by Flax.Build (see DotNetAOT)
+ const Char *platform, *architecture, *configuration = ::ToString(data.Configuration);
+ data.GetBuildPlatformName(platform, architecture);
+ const String logFile = data.CacheDirectory / TEXT("AOTLog.txt");
+ String args = String::Format(
+ TEXT("-log -logfile=\"{}\" -runDotNetAOT -mutex -platform={} -arch={} -configuration={} -aotMode={} -binaries=\"{}\" -intermediate=\"{}\""),
+ logFile, platform, architecture, configuration, ToString(aotMode), data.DataOutputPath, data.ManagedCodeOutputPath);
+ if (!buildSettings.SkipUnusedDotnetLibsPackaging)
+ args += TEXT(" -skipUnusedDotnetLibs=false"); // Run AOT on whole class library (not just used libs)
+ for (const String& define : data.CustomDefines)
{
- case ArchitectureType::x86:
- config.AotCachePath /= TEXT("x86");
- break;
- case ArchitectureType::x64:
- config.AotCachePath /= TEXT("amd64");
- break;
- default:
- data.Error(TEXT("Not supported AOT architecture"));
+ args += TEXT(" -D");
+ args += define;
+ }
+ if (ScriptsBuilder::RunBuildTool(args))
+ {
+ data.Error(TEXT("Failed to precompile game scripts."));
return true;
}
- if (!FileSystem::DirectoryExists(config.AotCachePath))
- {
- if (FileSystem::CreateDirectory(config.AotCachePath))
- {
- data.Error(TEXT("Failed to setup AOT output directory."));
- return true;
- }
- }
-
- // Collect assemblies for AOT
- // TODO: don't perform AOT on all assemblies but only ones used by the game and engine assemblies
- for (auto& dir : config.AssembliesSearchDirs)
- FileSystem::DirectoryGetFiles(config.Assemblies, dir, TEXT("*.dll"), DirectorySearchOption::TopDirectoryOnly);
- for (auto& binaryModule : data.BinaryModules)
- if (binaryModule.ManagedPath.HasChars())
- config.Assemblies.Add(data.ManagedCodeOutputPath / binaryModule.ManagedPath);
- // TODO: move AOT to Flax.Build and perform it on all C# assemblies used in target build
- config.Assemblies.Add(data.ManagedCodeOutputPath / TEXT("Newtonsoft.Json.dll"));
-
- // Perform AOT for the assemblies
- for (int32 i = 0; i < config.Assemblies.Count(); i++)
- {
- BUILD_STEP_CANCEL_CHECK;
-
- if (data.Tools->OnPerformAOT(data, config, config.Assemblies[i]))
- return true;
-
- data.StepProgress(infoMsg, static_cast(i) / config.Assemblies.Count());
- }
-
- BUILD_STEP_CANCEL_CHECK;
-
- if (data.Tools->OnPostProcessAOT(data, config))
- return true;
-
- // TODO: maybe remove GAC/assemblies? aot-cache could be only used in the build game
return false;
}
diff --git a/Source/Editor/Cooker/Steps/PrecompileAssembliesStep.h b/Source/Editor/Cooker/Steps/PrecompileAssembliesStep.h
index d7c89e7ed..dcfe2fc5d 100644
--- a/Source/Editor/Cooker/Steps/PrecompileAssembliesStep.h
+++ b/Source/Editor/Cooker/Steps/PrecompileAssembliesStep.h
@@ -5,8 +5,7 @@
#include "Editor/Cooker/GameCooker.h"
///
-/// Optional step used only on selected platform that precompiles C# script assemblies.
-/// Uses Mono Ahead of Time Compilation (AOT) feature.
+/// Optional step used only on selected platform that precompiles C# script assemblies. Uses Ahead of Time Compilation (AOT) feature.
///
///
class PrecompileAssembliesStep : public GameCooker::BuildStep
@@ -14,5 +13,6 @@ class PrecompileAssembliesStep : public GameCooker::BuildStep
public:
// [BuildStep]
+ void OnBuildStarted(CookingData& data) override;
bool Perform(CookingData& data) override;
};
diff --git a/Source/Editor/CustomEditors/CustomEditor.cs b/Source/Editor/CustomEditors/CustomEditor.cs
index 1330616d1..03b21e12b 100644
--- a/Source/Editor/CustomEditors/CustomEditor.cs
+++ b/Source/Editor/CustomEditors/CustomEditor.cs
@@ -7,6 +7,7 @@ using FlaxEditor.CustomEditors.GUI;
using FlaxEditor.Scripting;
using FlaxEngine;
using FlaxEngine.GUI;
+using FlaxEngine.Utilities;
using Newtonsoft.Json;
using JsonSerializer = FlaxEngine.Json.JsonSerializer;
@@ -625,9 +626,7 @@ namespace FlaxEditor.CustomEditors
JsonSerializer.Deserialize(obj, text);
}
}
-#pragma warning disable 618
- else if (Newtonsoft.Json.Schema.JsonSchema.Parse(text) == null)
-#pragma warning restore 618
+ else if (!text.StartsWith("{") || !text.EndsWith("}"))
{
return false;
}
@@ -650,7 +649,14 @@ namespace FlaxEditor.CustomEditors
else
{
// Default
- obj = JsonConvert.DeserializeObject(text, TypeUtils.GetType(Values.Type), JsonSerializer.Settings);
+ try
+ {
+ obj = JsonConvert.DeserializeObject(text, TypeUtils.GetType(Values.Type), JsonSerializer.Settings);
+ }
+ catch
+ {
+ obj = null;
+ }
}
if (obj == null || Values.Type.IsInstanceOfType(obj))
@@ -817,6 +823,8 @@ namespace FlaxEditor.CustomEditors
/// True if allow to handle this event, otherwise false.
protected virtual bool OnDirty(CustomEditor editor, object value, object token = null)
{
+ if (ParentEditor == null)
+ return false;
return ParentEditor.OnDirty(editor, value, token);
}
diff --git a/Source/Editor/CustomEditors/CustomEditorPresenter.cs b/Source/Editor/CustomEditors/CustomEditorPresenter.cs
index a04070a06..73e2ede0f 100644
--- a/Source/Editor/CustomEditors/CustomEditorPresenter.cs
+++ b/Source/Editor/CustomEditors/CustomEditorPresenter.cs
@@ -6,6 +6,7 @@ using System.Linq;
using FlaxEditor.Scripting;
using FlaxEngine;
using FlaxEngine.GUI;
+using FlaxEngine.Utilities;
namespace FlaxEditor.CustomEditors
{
diff --git a/Source/Editor/CustomEditors/CustomEditorsUtil.cpp b/Source/Editor/CustomEditors/CustomEditorsUtil.cpp
index bfb7a93a5..d0c10c13d 100644
--- a/Source/Editor/CustomEditors/CustomEditorsUtil.cpp
+++ b/Source/Editor/CustomEditors/CustomEditorsUtil.cpp
@@ -13,7 +13,6 @@
#include "Engine/Scripting/ManagedCLR/MClass.h"
#include "Engine/Scripting/ManagedCLR/MUtils.h"
#include "FlaxEngine.Gen.h"
-#include
#define TRACK_ASSEMBLY(assembly) \
if (assembly->IsLoaded()) \
@@ -49,24 +48,24 @@ struct Entry
}
};
-Dictionary Cache(512);
+Dictionary Cache(512);
void OnAssemblyLoaded(MAssembly* assembly);
void OnAssemblyUnloading(MAssembly* assembly);
void OnBinaryModuleLoaded(BinaryModule* module);
-MonoReflectionType* CustomEditorsUtil::GetCustomEditor(MonoReflectionType* refType)
+MTypeObject* CustomEditorsUtil::GetCustomEditor(MTypeObject* refType)
{
if (!refType)
return nullptr;
- MonoType* type = mono_reflection_type_get_type(refType);
+ MType* type = INTERNAL_TYPE_OBJECT_GET(refType);
Entry result;
if (Cache.TryGet(type, result))
{
- const auto editor = result.CustomEditor ? result.CustomEditor : result.DefaultEditor;
+ MClass* editor = result.CustomEditor ? result.CustomEditor : result.DefaultEditor;
if (editor)
{
- return MUtils::GetType(editor->GetNative());
+ return MUtils::GetType(editor);
}
}
return nullptr;
@@ -123,19 +122,19 @@ void OnAssemblyLoaded(MAssembly* assembly)
continue;
const auto attribute = mclass->GetAttribute(customEditorAttribute);
- if (attribute == nullptr || mono_object_get_class(attribute) != customEditorAttribute->GetNative())
+ if (attribute == nullptr || MCore::Object::GetClass(attribute) != customEditorAttribute)
continue;
// Check if attribute references a valid class
- MonoReflectionType* refType = nullptr;
+ MTypeObject* refType = nullptr;
customEditorTypeField->GetValue(attribute, &refType);
if (refType == nullptr)
continue;
- MonoType* type = mono_reflection_type_get_type(refType);
+ MType* type = INTERNAL_TYPE_OBJECT_GET(refType);
if (type == nullptr)
continue;
- MonoClass* typeClass = mono_type_get_class(type);
+ MClass* typeClass = MCore::Type::GetClass(type);
// Check if it's a custom editor class
if (mclass->IsSubClassOf(customEditor))
@@ -152,18 +151,14 @@ void OnAssemblyLoaded(MAssembly* assembly)
entry.CustomEditor = mclass;
}
- //LOG(Info, "Custom Editor {0} for type {1} (default: {2})", String(mclass->GetFullName()), String(mono_type_get_name(type)), isDefault);
+ //LOG(Info, "Custom Editor {0} for type {1} (default: {2})", String(mclass->GetFullName()), MCore::Type::ToString(type), isDefault);
}
else if (typeClass)
{
- MClass* referencedClass = Scripting::FindClass(typeClass);
- if (referencedClass)
- {
- auto& entry = Cache[mono_class_get_type(mclass->GetNative())];
- entry.CustomEditor = referencedClass;
+ auto& entry = Cache[mclass->GetType()];
+ entry.CustomEditor = typeClass;
- //LOG(Info, "Custom Editor {0} for type {1}", String(referencedClass->GetFullName()), String(mclass->GetFullName()));
- }
+ //LOG(Info, "Custom Editor {0} for type {1}", String(typeClass->GetFullName()), String(mclass->GetFullName()));
}
}
@@ -183,17 +178,16 @@ void OnAssemblyUnloading(MAssembly* assembly)
// Remove entries with user classes
for (auto i = Cache.Begin(); i.IsNotEnd(); ++i)
{
- MonoClass* monoClass = (MonoClass*)(void*)i->Key;
-
- if (assembly->GetClass(monoClass))
+ MClass* mClass = MCore::Type::GetClass(i->Key);
+ if (mClass && mClass->GetAssembly() == assembly)
{
Cache.Remove(i);
}
else
{
- if (i->Value.DefaultEditor && assembly->GetClass(i->Value.DefaultEditor->GetNative()))
+ if (i->Value.DefaultEditor && i->Value.DefaultEditor->GetAssembly() == assembly)
i->Value.DefaultEditor = nullptr;
- if (i->Value.CustomEditor && assembly->GetClass(i->Value.CustomEditor->GetNative()))
+ if (i->Value.CustomEditor && i->Value.CustomEditor->GetAssembly() == assembly)
i->Value.CustomEditor = nullptr;
}
}
diff --git a/Source/Editor/CustomEditors/CustomEditorsUtil.cs b/Source/Editor/CustomEditors/CustomEditorsUtil.cs
index 7f653376b..5f76b07e2 100644
--- a/Source/Editor/CustomEditors/CustomEditorsUtil.cs
+++ b/Source/Editor/CustomEditors/CustomEditorsUtil.cs
@@ -3,15 +3,18 @@
using System;
using System.Collections.Generic;
using System.Linq;
-using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+using System.Runtime.InteropServices.Marshalling;
using FlaxEditor.CustomEditors.Dedicated;
using FlaxEditor.CustomEditors.Editors;
using FlaxEditor.Scripting;
using FlaxEngine;
+using FlaxEngine.Interop;
+using FlaxEngine.Utilities;
namespace FlaxEditor.CustomEditors
{
- internal static class CustomEditorsUtil
+ internal static partial class CustomEditorsUtil
{
internal static readonly Dictionary InBuildTypeNames = new Dictionary()
{
@@ -126,7 +129,8 @@ namespace FlaxEditor.CustomEditors
return new GenericEditor();
}
- [MethodImpl(MethodImplOptions.InternalCall)]
- internal static extern Type Internal_GetCustomEditor(Type targetType);
+ [LibraryImport("FlaxEngine", EntryPoint = "CustomEditorsUtilInternal_GetCustomEditor", StringMarshalling = StringMarshalling.Custom, StringMarshallingCustomType = typeof(StringMarshaller))]
+ [return: MarshalUsing(typeof(SystemTypeMarshaller))]
+ internal static partial Type Internal_GetCustomEditor([MarshalUsing(typeof(SystemTypeMarshaller))] Type targetType);
}
}
diff --git a/Source/Editor/CustomEditors/CustomEditorsUtil.h b/Source/Editor/CustomEditors/CustomEditorsUtil.h
index 287602369..aea45729e 100644
--- a/Source/Editor/CustomEditors/CustomEditorsUtil.h
+++ b/Source/Editor/CustomEditors/CustomEditorsUtil.h
@@ -11,7 +11,7 @@ class CustomEditorsUtil
{
public:
-#if USE_MONO
- static MonoReflectionType* GetCustomEditor(MonoReflectionType* refType);
+#if USE_CSHARP
+ static MTypeObject* GetCustomEditor(MTypeObject* refType);
#endif
};
diff --git a/Source/Editor/CustomEditors/Dedicated/ActorEditor.cs b/Source/Editor/CustomEditors/Dedicated/ActorEditor.cs
index efa13d48a..db863b186 100644
--- a/Source/Editor/CustomEditors/Dedicated/ActorEditor.cs
+++ b/Source/Editor/CustomEditors/Dedicated/ActorEditor.cs
@@ -12,6 +12,7 @@ using FlaxEditor.Scripting;
using FlaxEngine;
using FlaxEngine.GUI;
using FlaxEngine.Json;
+using FlaxEngine.Utilities;
namespace FlaxEditor.CustomEditors.Dedicated
{
@@ -94,19 +95,7 @@ namespace FlaxEditor.CustomEditors.Dedicated
{
if (actor != null)
group.Panel.TooltipText = Surface.SurfaceUtils.GetVisualScriptTypeDescription(TypeUtils.GetObjectType(actor));
- float settingsButtonSize = group.Panel.HeaderHeight;
- var settingsButton = new Image
- {
- TooltipText = "Settings",
- AutoFocus = true,
- AnchorPreset = AnchorPresets.TopRight,
- Parent = group.Panel,
- Bounds = new Rectangle(group.Panel.Width - settingsButtonSize, 0, settingsButtonSize, settingsButtonSize),
- IsScrollable = false,
- Color = FlaxEngine.GUI.Style.Current.ForegroundGrey,
- Margin = new Margin(1),
- Brush = new SpriteBrush(FlaxEngine.GUI.Style.Current.Settings),
- };
+ var settingsButton = group.AddSettingsButton();
settingsButton.Clicked += OnSettingsButtonClicked;
break;
}
diff --git a/Source/Editor/CustomEditors/Dedicated/ScriptsEditor.cs b/Source/Editor/CustomEditors/Dedicated/ScriptsEditor.cs
index 613f5d3b4..bf82e19da 100644
--- a/Source/Editor/CustomEditors/Dedicated/ScriptsEditor.cs
+++ b/Source/Editor/CustomEditors/Dedicated/ScriptsEditor.cs
@@ -11,6 +11,7 @@ using FlaxEditor.GUI.Drag;
using FlaxEditor.Scripting;
using FlaxEngine;
using FlaxEngine.GUI;
+using FlaxEngine.Utilities;
using Object = FlaxEngine.Object;
namespace FlaxEditor.CustomEditors.Dedicated
@@ -461,20 +462,8 @@ namespace FlaxEditor.CustomEditors.Dedicated
var group = layout.Group("Missing script");
// Add settings button to the group
- const float settingsButtonSize = 14;
- var settingsButton = new Image
- {
- TooltipText = "Settings",
- AutoFocus = true,
- AnchorPreset = AnchorPresets.TopRight,
- Parent = group.Panel,
- Bounds = new Rectangle(group.Panel.Width - settingsButtonSize, 0, settingsButtonSize, settingsButtonSize),
- IsScrollable = false,
- Color = FlaxEngine.GUI.Style.Current.ForegroundGrey,
- Margin = new Margin(1),
- Brush = new SpriteBrush(FlaxEngine.GUI.Style.Current.Settings),
- Tag = index,
- };
+ var settingsButton = group.AddSettingsButton();
+ settingsButton.Tag = index;
settingsButton.Clicked += MissingSettingsButtonOnClicked;
}
@@ -664,19 +653,8 @@ namespace FlaxEditor.CustomEditors.Dedicated
};
// Add settings button to the group
- var settingsButton = new Image
- {
- TooltipText = "Settings",
- AutoFocus = true,
- AnchorPreset = AnchorPresets.TopRight,
- Parent = group.Panel,
- Bounds = new Rectangle(group.Panel.Width - headerHeight, 0, headerHeight, headerHeight),
- IsScrollable = false,
- Color = FlaxEngine.GUI.Style.Current.ForegroundGrey,
- Margin = new Margin(1),
- Brush = new SpriteBrush(FlaxEngine.GUI.Style.Current.Settings),
- Tag = script,
- };
+ var settingsButton = group.AddSettingsButton();
+ settingsButton.Tag = script;
settingsButton.Clicked += OnSettingsButtonClicked;
group.Panel.HeaderTextMargin = new Margin(scriptDrag.Right - 12, 15, 2, 2);
diff --git a/Source/Editor/CustomEditors/Dedicated/UIControlEditor.cs b/Source/Editor/CustomEditors/Dedicated/UIControlEditor.cs
index 6cbaa0ca0..fde4967e8 100644
--- a/Source/Editor/CustomEditors/Dedicated/UIControlEditor.cs
+++ b/Source/Editor/CustomEditors/Dedicated/UIControlEditor.cs
@@ -698,7 +698,7 @@ namespace FlaxEditor.CustomEditors.Dedicated
{
string newName = controlType.Name + uiControl.Name.Substring(previousName.Length);
if (uiControl.Parent != null)
- newName = StringUtils.IncrementNameNumber(newName, x => uiControl.Parent.GetChild(x) == null);
+ newName = Utilities.Utils.IncrementNameNumber(newName, x => uiControl.Parent.GetChild(x) == null);
uiControl.Name = newName;
}
}
diff --git a/Source/Editor/CustomEditors/Editors/ArrayEditor.cs b/Source/Editor/CustomEditors/Editors/ArrayEditor.cs
index de8491bcf..4d7beadda 100644
--- a/Source/Editor/CustomEditors/Editors/ArrayEditor.cs
+++ b/Source/Editor/CustomEditors/Editors/ArrayEditor.cs
@@ -4,6 +4,7 @@ using System;
using System.Collections;
using FlaxEditor.Scripting;
using FlaxEngine;
+using FlaxEngine.Utilities;
namespace FlaxEditor.CustomEditors.Editors
{
diff --git a/Source/Editor/CustomEditors/Editors/AssetRefEditor.cs b/Source/Editor/CustomEditors/Editors/AssetRefEditor.cs
index ddfe54897..17999d772 100644
--- a/Source/Editor/CustomEditors/Editors/AssetRefEditor.cs
+++ b/Source/Editor/CustomEditors/Editors/AssetRefEditor.cs
@@ -6,6 +6,7 @@ using FlaxEditor.Content;
using FlaxEditor.GUI;
using FlaxEditor.Scripting;
using FlaxEngine;
+using FlaxEngine.Utilities;
namespace FlaxEditor.CustomEditors.Editors
{
diff --git a/Source/Editor/CustomEditors/Editors/CollectionEditor.cs b/Source/Editor/CustomEditors/Editors/CollectionEditor.cs
index 8fe5fd5f2..8922e2d25 100644
--- a/Source/Editor/CustomEditors/Editors/CollectionEditor.cs
+++ b/Source/Editor/CustomEditors/Editors/CollectionEditor.cs
@@ -9,6 +9,7 @@ using FlaxEditor.GUI.ContextMenu;
using FlaxEditor.Scripting;
using FlaxEngine;
using FlaxEngine.GUI;
+using FlaxEngine.Utilities;
namespace FlaxEditor.CustomEditors.Editors
{
diff --git a/Source/Editor/CustomEditors/Editors/DictionaryEditor.cs b/Source/Editor/CustomEditors/Editors/DictionaryEditor.cs
index 18500fc08..73089ff04 100644
--- a/Source/Editor/CustomEditors/Editors/DictionaryEditor.cs
+++ b/Source/Editor/CustomEditors/Editors/DictionaryEditor.cs
@@ -11,6 +11,7 @@ using FlaxEditor.Scripting;
using FlaxEngine;
using FlaxEngine.GUI;
using FlaxEngine.Json;
+using FlaxEngine.Utilities;
namespace FlaxEditor.CustomEditors.Editors
{
diff --git a/Source/Editor/CustomEditors/Editors/EnumEditor.cs b/Source/Editor/CustomEditors/Editors/EnumEditor.cs
index 8f1da88db..3e305b09b 100644
--- a/Source/Editor/CustomEditors/Editors/EnumEditor.cs
+++ b/Source/Editor/CustomEditors/Editors/EnumEditor.cs
@@ -5,6 +5,7 @@ using System.Linq;
using FlaxEditor.CustomEditors.Elements;
using FlaxEditor.Scripting;
using FlaxEngine;
+using FlaxEngine.Utilities;
namespace FlaxEditor.CustomEditors.Editors
{
diff --git a/Source/Editor/CustomEditors/Editors/FlaxObjectRefEditor.cs b/Source/Editor/CustomEditors/Editors/FlaxObjectRefEditor.cs
index c9c49e7cb..731da3817 100644
--- a/Source/Editor/CustomEditors/Editors/FlaxObjectRefEditor.cs
+++ b/Source/Editor/CustomEditors/Editors/FlaxObjectRefEditor.cs
@@ -10,6 +10,7 @@ using FlaxEditor.SceneGraph;
using FlaxEditor.Scripting;
using FlaxEngine;
using FlaxEngine.GUI;
+using FlaxEngine.Utilities;
using Object = FlaxEngine.Object;
namespace FlaxEditor.CustomEditors.Editors
diff --git a/Source/Editor/CustomEditors/Editors/GenericEditor.cs b/Source/Editor/CustomEditors/Editors/GenericEditor.cs
index 7c021613e..cba2b5a5a 100644
--- a/Source/Editor/CustomEditors/Editors/GenericEditor.cs
+++ b/Source/Editor/CustomEditors/Editors/GenericEditor.cs
@@ -12,6 +12,7 @@ using FlaxEditor.Scripting;
using FlaxEngine;
using FlaxEngine.GUI;
using FlaxEngine.Json;
+using FlaxEngine.Utilities;
namespace FlaxEditor.CustomEditors.Editors
{
diff --git a/Source/Editor/CustomEditors/Editors/ListEditor.cs b/Source/Editor/CustomEditors/Editors/ListEditor.cs
index 866adf56d..12bacfcd5 100644
--- a/Source/Editor/CustomEditors/Editors/ListEditor.cs
+++ b/Source/Editor/CustomEditors/Editors/ListEditor.cs
@@ -3,6 +3,7 @@
using System.Collections;
using System.Collections.Generic;
using FlaxEngine;
+using FlaxEngine.Utilities;
namespace FlaxEditor.CustomEditors.Editors
{
@@ -20,7 +21,7 @@ namespace FlaxEditor.CustomEditors.Editors
{
var listType = Values.Type;
var list = (IList)listType.CreateInstance();
- var defaultValue = Scripting.TypeUtils.GetDefaultValue(ElementType);
+ var defaultValue = TypeUtils.GetDefaultValue(ElementType);
for (int i = 0; i < size; i++)
list.Add(defaultValue);
return list;
@@ -55,7 +56,7 @@ namespace FlaxEditor.CustomEditors.Editors
else
{
// Initialize new entries with default values
- var defaultValue = Scripting.TypeUtils.GetDefaultValue(elementType);
+ var defaultValue = TypeUtils.GetDefaultValue(elementType);
for (int i = oldSize; i < newSize; i++)
newValues.Add(defaultValue);
}
@@ -63,7 +64,7 @@ namespace FlaxEditor.CustomEditors.Editors
else if (newSize > 0)
{
// Fill new entries with default value
- var defaultValue = Scripting.TypeUtils.GetDefaultValue(elementType);
+ var defaultValue = TypeUtils.GetDefaultValue(elementType);
for (int i = oldSize; i < newSize; i++)
newValues.Add(defaultValue);
}
diff --git a/Source/Editor/CustomEditors/Editors/ObjectSwitcherEditor.cs b/Source/Editor/CustomEditors/Editors/ObjectSwitcherEditor.cs
index 7e86df06f..25a058d86 100644
--- a/Source/Editor/CustomEditors/Editors/ObjectSwitcherEditor.cs
+++ b/Source/Editor/CustomEditors/Editors/ObjectSwitcherEditor.cs
@@ -3,6 +3,7 @@
using System;
using FlaxEditor.GUI;
using FlaxEditor.Scripting;
+using FlaxEngine.Utilities;
namespace FlaxEditor.CustomEditors.Editors
{
diff --git a/Source/Editor/CustomEditors/Editors/TypeEditor.cs b/Source/Editor/CustomEditors/Editors/TypeEditor.cs
index 9ee2c37a4..ab17c4ae1 100644
--- a/Source/Editor/CustomEditors/Editors/TypeEditor.cs
+++ b/Source/Editor/CustomEditors/Editors/TypeEditor.cs
@@ -9,6 +9,7 @@ using FlaxEditor.GUI.Drag;
using FlaxEditor.Scripting;
using FlaxEngine;
using FlaxEngine.GUI;
+using FlaxEngine.Utilities;
namespace FlaxEditor.CustomEditors.Editors
{
diff --git a/Source/Editor/CustomEditors/Elements/Container/GroupElement.cs b/Source/Editor/CustomEditors/Elements/Container/GroupElement.cs
index e0ae259ec..2b9940cac 100644
--- a/Source/Editor/CustomEditors/Elements/Container/GroupElement.cs
+++ b/Source/Editor/CustomEditors/Elements/Container/GroupElement.cs
@@ -1,5 +1,6 @@
// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.
+using FlaxEngine;
using FlaxEngine.GUI;
namespace FlaxEditor.CustomEditors.Elements
@@ -25,5 +26,27 @@ namespace FlaxEditor.CustomEditors.Elements
///
public override ContainerControl ContainerControl => Panel;
+
+ ///
+ /// Adds utility settings button to the group header.
+ ///
+ /// The created control.
+ public Image AddSettingsButton()
+ {
+ var style = Style.Current;
+ var settingsButtonSize = Panel.HeaderHeight;
+ return new Image
+ {
+ TooltipText = "Settings",
+ AutoFocus = true,
+ AnchorPreset = AnchorPresets.TopRight,
+ Parent = Panel,
+ Bounds = new Rectangle(Panel.Width - settingsButtonSize, 0, settingsButtonSize, settingsButtonSize),
+ IsScrollable = false,
+ Color = style.ForegroundGrey,
+ Margin = new Margin(1),
+ Brush = new SpriteBrush(style.Settings),
+ };
+ }
}
}
diff --git a/Source/Editor/CustomEditors/Values/ValueContainer.cs b/Source/Editor/CustomEditors/Values/ValueContainer.cs
index 53af4387f..574e73564 100644
--- a/Source/Editor/CustomEditors/Values/ValueContainer.cs
+++ b/Source/Editor/CustomEditors/Values/ValueContainer.cs
@@ -6,6 +6,7 @@ using System.ComponentModel;
using System.Linq;
using FlaxEditor.Scripting;
using FlaxEngine;
+using FlaxEngine.Utilities;
namespace FlaxEditor.CustomEditors
{
@@ -242,7 +243,7 @@ namespace FlaxEditor.CustomEditors
if (objA == null && objB is string objBStr && objBStr.Length == 0)
return true;
- return Newtonsoft.Json.Utilities.MiscellaneousUtils.DefaultValueEquals(objA, objB);
+ return Newtonsoft.Json.Utilities.MiscellaneousUtils.ValueEquals(objA, objB);
}
///
diff --git a/Source/Editor/Editor.Build.cs b/Source/Editor/Editor.Build.cs
index 91785036e..64578570c 100644
--- a/Source/Editor/Editor.Build.cs
+++ b/Source/Editor/Editor.Build.cs
@@ -1,5 +1,6 @@
// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.
+using System;
using System.Collections.Generic;
using System.IO;
using Flax.Build;
@@ -36,9 +37,13 @@ public class Editor : EditorModule
{
base.Setup(options);
+ options.ScriptingAPI.SystemReferences.Add("System.Private.Xml");
+ options.ScriptingAPI.SystemReferences.Add("System.Text.RegularExpressions");
+ options.ScriptingAPI.SystemReferences.Add("System.ComponentModel.TypeConverter");
+
options.PublicDependencies.Add("Engine");
options.PrivateDependencies.Add("pugixml");
- options.PrivateDependencies.Add("UniversalAnalytics");
+ options.PrivateDependencies.Add("curl");
options.PrivateDependencies.Add("ContentImporters");
options.PrivateDependencies.Add("ContentExporters");
options.PrivateDependencies.Add("ShadowsOfMordor");
@@ -72,12 +77,15 @@ public class Editor : EditorModule
{
AddPlatformTools(options, platformToolsRoot, platformToolsRootExternal, "Mac", "PLATFORM_TOOLS_MAC");
AddPlatformTools(options, platformToolsRoot, platformToolsRootExternal, "Android", "PLATFORM_TOOLS_ANDROID");
+ AddPlatformTools(options, platformToolsRoot, platformToolsRootExternal, "iOS", "PLATFORM_TOOLS_IOS");
}
// Visual Studio integration
if (options.Platform.Target == TargetPlatform.Windows && Flax.Build.Platform.BuildTargetPlatform == TargetPlatform.Windows)
{
+#pragma warning disable CA1416
var path = Registry.GetValue("HKEY_CLASSES_ROOT\\TypeLib\\{80CC9F66-E7D8-4DDD-85B6-D9E6CD0E93E2}\\8.0\\0\\win32", null, null) as string;
+#pragma warning restore CA1416
if (path != null && File.Exists(path))
options.PrivateDefinitions.Add("USE_VISUAL_STUDIO_DTE");
}
diff --git a/Source/Editor/Editor.cpp b/Source/Editor/Editor.cpp
index 0456e28a4..8a92d11f4 100644
--- a/Source/Editor/Editor.cpp
+++ b/Source/Editor/Editor.cpp
@@ -259,13 +259,13 @@ bool Editor::CheckProjectUpgrade()
LOG(Warning, "Project layout upgraded!");
}
- // Check if last version was the same
+ // Check if last version was the same
else if (lastMajor == FLAXENGINE_VERSION_MAJOR && lastMinor == FLAXENGINE_VERSION_MINOR)
{
// Do nothing
IsOldProjectOpened = false;
}
- // Check if last version was older
+ // Check if last version was older
else if (lastMajor < FLAXENGINE_VERSION_MAJOR || (lastMajor == FLAXENGINE_VERSION_MAJOR && lastMinor < FLAXENGINE_VERSION_MINOR))
{
LOG(Warning, "The project was opened with the older editor version last time");
@@ -288,7 +288,7 @@ bool Editor::CheckProjectUpgrade()
return true;
}
}
- // Check if last version was newer
+ // Check if last version was newer
else if (lastMajor > FLAXENGINE_VERSION_MAJOR || (lastMajor == FLAXENGINE_VERSION_MAJOR && lastMinor > FLAXENGINE_VERSION_MINOR))
{
LOG(Warning, "The project was opened with the newer editor version last time");
@@ -312,6 +312,14 @@ bool Editor::CheckProjectUpgrade()
}
}
+ // When changing between major/minor version clear some caches to prevent possible issues
+ if (lastMajor != FLAXENGINE_VERSION_MAJOR || lastMinor != FLAXENGINE_VERSION_MINOR)
+ {
+ LOG(Info, "Cleaning cache files from different engine version");
+ FileSystem::DeleteDirectory(Globals::ProjectFolder / TEXT("Cache/Cooker"));
+ FileSystem::DeleteDirectory(Globals::ProjectFolder / TEXT("Cache/Intermediate"));
+ }
+
// Upgrade old 0.7 projects
// [Deprecated: 01.11.2020, expires 01.11.2021]
if (lastMajor == 0 && lastMinor == 7 && lastBuild <= 6197)
@@ -330,12 +338,11 @@ bool Editor::CheckProjectUpgrade()
file->WriteInt32(FLAXENGINE_VERSION_MAJOR);
file->WriteInt32(FLAXENGINE_VERSION_MINOR);
file->WriteInt32(FLAXENGINE_VERSION_BUILD);
-
Delete(file);
}
else
{
- LOG(Warning, "Failed to create version cache file");
+ LOG(Error, "Failed to create version cache file");
}
}
@@ -664,9 +671,7 @@ bool Editor::Init()
void Editor::BeforeRun()
{
- // If during last lightmaps baking engine crashed we could try to restore the progress
- if (ShadowsOfMordor::Builder::Instance()->RestoreState())
- Managed->GetClass()->GetMethod("Internal_StartLightingBake")->Invoke(Managed->GetOrCreateManagedInstance(), nullptr, nullptr);
+ Managed->BeforeRun();
}
void Editor::BeforeExit()
@@ -696,12 +701,6 @@ void EditorImpl::OnUpdate()
// Boost our priority back to normal
Platform::SetThreadPriority(ThreadPriority::Normal);
}
- if (!hasFocus)
- {
- // Sleep for a bit to not eat up all CPU time
- PROFILE_CPU_NAMED("Sleep");
- Platform::Sleep(5);
- }
HasFocus = hasFocus;
}
diff --git a/Source/Editor/Editor.cs b/Source/Editor/Editor.cs
index e4e945f31..1a1fd3b2d 100644
--- a/Source/Editor/Editor.cs
+++ b/Source/Editor/Editor.cs
@@ -5,6 +5,7 @@ using System.Collections.Generic;
using System.IO;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
+using System.Runtime.InteropServices.Marshalling;
using FlaxEditor.Content;
using FlaxEditor.Content.Import;
using FlaxEditor.Content.Settings;
@@ -18,8 +19,11 @@ using FlaxEditor.Windows.Assets;
using FlaxEngine;
using FlaxEngine.Assertions;
using FlaxEngine.GUI;
+using FlaxEngine.Interop;
using FlaxEngine.Json;
+#pragma warning disable CS1591
+
namespace FlaxEditor
{
///
@@ -62,17 +66,20 @@ namespace FlaxEditor
///
/// Gets a value indicating whether this Editor is running a dev instance of the engine.
///
- [MethodImpl(MethodImplOptions.InternalCall)]
- internal static extern bool IsDevInstance();
+ [LibraryImport("FlaxEngine", EntryPoint = "EditorInternal_IsDevInstance", StringMarshalling = StringMarshalling.Custom, StringMarshallingCustomType = typeof(StringMarshaller))]
+ [return: MarshalAs(UnmanagedType.U1)]
+ internal static partial bool IsDevInstance();
///
/// Gets a value indicating whether this Editor is running as official build (distributed via Flax services).
///
- [MethodImpl(MethodImplOptions.InternalCall)]
- internal static extern bool IsOfficialBuild();
+ [LibraryImport("FlaxEngine", EntryPoint = "EditorInternal_IsOfficialBuild", StringMarshalling = StringMarshalling.Custom, StringMarshallingCustomType = typeof(StringMarshaller))]
+ [return: MarshalAs(UnmanagedType.U1)]
+ internal static partial bool IsOfficialBuild();
- [MethodImpl(MethodImplOptions.InternalCall)]
- internal static extern bool Internal_IsPlayMode();
+ [LibraryImport("FlaxEngine", EntryPoint = "EditorInternal_IsPlayMode", StringMarshalling = StringMarshalling.Custom, StringMarshallingCustomType = typeof(StringMarshaller))]
+ [return: MarshalAs(UnmanagedType.U1)]
+ internal static partial bool Internal_IsPlayMode();
///
/// True if the editor is running now in a play mode. Assigned by the managed editor instance.
@@ -724,9 +731,16 @@ namespace FlaxEditor
// Invoke new instance if need to open a project
if (!string.IsNullOrEmpty(_projectToOpen))
{
- string args = string.Format("-project \"{0}\"", _projectToOpen);
+ var procSettings = new CreateProcessSettings
+ {
+ FileName = Platform.ExecutableFilePath,
+ Arguments = string.Format("-project \"{0}\"", _projectToOpen),
+ ShellExecute = true,
+ WaitForEnd = false,
+ HiddenWindow = false,
+ };
_projectToOpen = null;
- Platform.StartProcess(Platform.ExecutableFilePath, args, null);
+ Platform.CreateProcess(ref procSettings);
}
}
@@ -919,47 +933,6 @@ namespace FlaxEditor
Animation = 11,
}
- ///
- /// Imports the asset file to the target location.
- ///
- /// The source file path.
- /// The result asset file path.
- /// True if importing failed, otherwise false.
- public static bool Import(string inputPath, string outputPath)
- {
- return Internal_Import(inputPath, outputPath, IntPtr.Zero);
- }
-
- ///
- /// Imports the texture asset file to the target location.
- ///
- /// The source file path.
- /// The result asset file path.
- /// The settings.
- /// True if importing failed, otherwise false.
- public static bool Import(string inputPath, string outputPath, TextureImportSettings settings)
- {
- if (settings == null)
- throw new ArgumentNullException();
- settings.ToInternal(out var internalOptions);
- return Internal_ImportTexture(inputPath, outputPath, ref internalOptions);
- }
-
- ///
- /// Imports the model asset file to the target location.
- ///
- /// The source file path.
- /// The result asset file path.
- /// The settings.
- /// True if importing failed, otherwise false.
- public static bool Import(string inputPath, string outputPath, ModelImportSettings settings)
- {
- if (settings == null)
- throw new ArgumentNullException();
- settings.ToInternal(out var internalOptions);
- return Internal_ImportModel(inputPath, outputPath, ref internalOptions);
- }
-
///
/// Imports the audio asset file to the target location.
///
@@ -1318,6 +1291,7 @@ namespace FlaxEditor
}
[StructLayout(LayoutKind.Sequential)]
+ [NativeMarshalling(typeof(VisualScriptLocalMarshaller))]
internal struct VisualScriptLocal
{
public string Value;
@@ -1326,7 +1300,52 @@ namespace FlaxEditor
public int BoxId;
}
+ [CustomMarshaller(typeof(VisualScriptLocal), MarshalMode.Default, typeof(VisualScriptLocalMarshaller))]
+ internal static class VisualScriptLocalMarshaller
+ {
+ [StructLayout(LayoutKind.Sequential)]
+ internal struct VisualScriptLocalNative
+ {
+ public IntPtr Value;
+ public IntPtr ValueTypeName;
+ public uint NodeId;
+ public int BoxId;
+ }
+
+ internal static VisualScriptLocal ConvertToManaged(VisualScriptLocalNative unmanaged) => ToManaged(unmanaged);
+ internal static VisualScriptLocalNative ConvertToUnmanaged(VisualScriptLocal managed) => ToNative(managed);
+
+ internal static VisualScriptLocal ToManaged(VisualScriptLocalNative managed)
+ {
+ return new VisualScriptLocal()
+ {
+ Value = ManagedString.ToManaged(managed.Value),
+ ValueTypeName = ManagedString.ToManaged(managed.ValueTypeName),
+ NodeId = managed.NodeId,
+ BoxId = managed.BoxId,
+ };
+ }
+
+ internal static VisualScriptLocalNative ToNative(VisualScriptLocal managed)
+ {
+ return new VisualScriptLocalNative()
+ {
+ Value = ManagedString.ToNative(managed.Value),
+ ValueTypeName = ManagedString.ToNative(managed.ValueTypeName),
+ NodeId = managed.NodeId,
+ BoxId = managed.BoxId,
+ };
+ }
+
+ internal static void Free(VisualScriptLocalNative unmanaged)
+ {
+ ManagedString.Free(unmanaged.Value);
+ ManagedString.Free(unmanaged.ValueTypeName);
+ }
+ }
+
[StructLayout(LayoutKind.Sequential)]
+ [NativeMarshalling(typeof(VisualScriptStackFrameMarshaller))]
internal struct VisualScriptStackFrame
{
public VisualScript Script;
@@ -1334,6 +1353,45 @@ namespace FlaxEditor
public int BoxId;
}
+ [CustomMarshaller(typeof(VisualScriptStackFrame), MarshalMode.Default, typeof(VisualScriptStackFrameMarshaller))]
+ internal static class VisualScriptStackFrameMarshaller
+ {
+ [StructLayout(LayoutKind.Sequential)]
+ internal struct VisualScriptStackFrameNative
+ {
+ public IntPtr Script;
+ public uint NodeId;
+ public int BoxId;
+ }
+
+ internal static VisualScriptStackFrame ConvertToManaged(VisualScriptStackFrameNative unmanaged) => ToManaged(unmanaged);
+ internal static VisualScriptStackFrameNative ConvertToUnmanaged(VisualScriptStackFrame managed) => ToNative(managed);
+
+ internal static VisualScriptStackFrame ToManaged(VisualScriptStackFrameNative managed)
+ {
+ return new VisualScriptStackFrame()
+ {
+ Script = VisualScriptMarshaller.ConvertToManaged(managed.Script),
+ NodeId = managed.NodeId,
+ BoxId = managed.BoxId,
+ };
+ }
+
+ internal static VisualScriptStackFrameNative ToNative(VisualScriptStackFrame managed)
+ {
+ return new VisualScriptStackFrameNative()
+ {
+ Script = VisualScriptMarshaller.ConvertToUnmanaged(managed.Script),
+ NodeId = managed.NodeId,
+ BoxId = managed.BoxId,
+ };
+ }
+
+ internal static void Free(VisualScriptStackFrameNative unmanaged)
+ {
+ }
+ }
+
internal void BuildCommand(string arg)
{
if (TryBuildCommand(arg))
@@ -1531,116 +1589,121 @@ namespace FlaxEditor
Instance.StateMachine.StateChanged += RequestStartPlayOnEditMode;
}
- [MethodImpl(MethodImplOptions.InternalCall)]
- internal static extern int Internal_ReadOutputLogs(string[] outMessages, byte[] outLogTypes, long[] outLogTimes);
+ [LibraryImport("FlaxEngine", EntryPoint = "EditorInternal_ReadOutputLogs", StringMarshalling = StringMarshalling.Custom, StringMarshallingCustomType = typeof(FlaxEngine.Interop.StringMarshaller))]
+ internal static partial int Internal_ReadOutputLogs([MarshalUsing(typeof(FlaxEngine.Interop.ArrayMarshaller<,>), CountElementName = "outCapacity")] ref string[] outMessages, [MarshalUsing(typeof(FlaxEngine.Interop.ArrayMarshaller<,>), CountElementName = "outCapacity")] ref byte[] outLogTypes, [MarshalUsing(typeof(FlaxEngine.Interop.ArrayMarshaller<,>), CountElementName = "outCapacity")] ref long[] outLogTimes, int outCapacity);
- [MethodImpl(MethodImplOptions.InternalCall)]
- internal static extern void Internal_SetPlayMode(bool value);
+ [LibraryImport("FlaxEngine", EntryPoint = "EditorInternal_SetPlayMode", StringMarshalling = StringMarshalling.Custom, StringMarshallingCustomType = typeof(FlaxEngine.Interop.StringMarshaller))]
+ internal static partial void Internal_SetPlayMode([MarshalAs(UnmanagedType.U1)] bool value);
- [MethodImpl(MethodImplOptions.InternalCall)]
- internal static extern string Internal_GetProjectPath();
+ [LibraryImport("FlaxEngine", EntryPoint = "EditorInternal_GetProjectPath", StringMarshalling = StringMarshalling.Custom, StringMarshallingCustomType = typeof(StringMarshaller))]
+ internal static partial string Internal_GetProjectPath();
- [MethodImpl(MethodImplOptions.InternalCall)]
- internal static extern bool Internal_CloneAssetFile(string dstPath, string srcPath, ref Guid dstId);
+ [LibraryImport("FlaxEngine", EntryPoint = "EditorInternal_CloneAssetFile", StringMarshalling = StringMarshalling.Custom, StringMarshallingCustomType = typeof(StringMarshaller))]
+ [return: MarshalAs(UnmanagedType.U1)]
+ internal static partial bool Internal_CloneAssetFile(string dstPath, string srcPath, ref Guid dstId);
- [MethodImpl(MethodImplOptions.InternalCall)]
- internal static extern bool Internal_Import(string inputPath, string outputPath, IntPtr arg);
+ [LibraryImport("FlaxEngine", EntryPoint = "EditorInternal_ImportAudio", StringMarshalling = StringMarshalling.Custom, StringMarshallingCustomType = typeof(StringMarshaller))]
+ [return: MarshalAs(UnmanagedType.U1)]
+ internal static partial bool Internal_ImportAudio(string inputPath, string outputPath, ref AudioImportSettings.InternalOptions options);
- [MethodImpl(MethodImplOptions.InternalCall)]
- internal static extern bool Internal_ImportTexture(string inputPath, string outputPath, ref TextureImportSettings.InternalOptions options);
+ [LibraryImport("FlaxEngine", EntryPoint = "EditorInternal_GetAudioClipMetadata", StringMarshalling = StringMarshalling.Custom, StringMarshallingCustomType = typeof(StringMarshaller))]
+ internal static partial void Internal_GetAudioClipMetadata(IntPtr obj, out int originalSize, out int importedSize);
- [MethodImpl(MethodImplOptions.InternalCall)]
- internal static extern bool Internal_ImportModel(string inputPath, string outputPath, ref ModelImportSettings.InternalOptions options);
+ [LibraryImport("FlaxEngine", EntryPoint = "EditorInternal_SaveJsonAsset", StringMarshalling = StringMarshalling.Custom, StringMarshallingCustomType = typeof(StringMarshaller))]
+ [return: MarshalAs(UnmanagedType.U1)]
+ internal static partial bool Internal_SaveJsonAsset(string outputPath, string data, string typename);
- [MethodImpl(MethodImplOptions.InternalCall)]
- internal static extern bool Internal_ImportAudio(string inputPath, string outputPath, ref AudioImportSettings.InternalOptions options);
+ [LibraryImport("FlaxEngine", EntryPoint = "EditorInternal_CopyCache", StringMarshalling = StringMarshalling.Custom, StringMarshallingCustomType = typeof(StringMarshaller))]
+ internal static partial void Internal_CopyCache(ref Guid dstId, ref Guid srcId);
- [MethodImpl(MethodImplOptions.InternalCall)]
- internal static extern void Internal_GetAudioClipMetadata(IntPtr obj, out int originalSize, out int importedSize);
+ [LibraryImport("FlaxEngine", EntryPoint = "EditorInternal_BakeLightmaps", StringMarshalling = StringMarshalling.Custom, StringMarshallingCustomType = typeof(StringMarshaller))]
+ internal static partial void Internal_BakeLightmaps([MarshalAs(UnmanagedType.U1)] bool cancel);
- [MethodImpl(MethodImplOptions.InternalCall)]
- internal static extern bool Internal_SaveJsonAsset(string outputPath, string data, string typename);
+ [LibraryImport("FlaxEngine", EntryPoint = "EditorInternal_GetShaderAssetSourceCode", StringMarshalling = StringMarshalling.Custom, StringMarshallingCustomType = typeof(StringMarshaller))]
+ internal static partial string Internal_GetShaderAssetSourceCode(IntPtr obj);
- [MethodImpl(MethodImplOptions.InternalCall)]
- internal static extern void Internal_CopyCache(ref Guid dstId, ref Guid srcId);
+ [LibraryImport("FlaxEngine", EntryPoint = "EditorInternal_CookMeshCollision", StringMarshalling = StringMarshalling.Custom, StringMarshallingCustomType = typeof(StringMarshaller))]
+ [return: MarshalAs(UnmanagedType.U1)]
+ internal static partial bool Internal_CookMeshCollision(string path, CollisionDataType type, IntPtr model, int modelLodIndex, uint materialSlotsMask, ConvexMeshGenerationFlags convexFlags, int convexVertexLimit);
- [MethodImpl(MethodImplOptions.InternalCall)]
- internal static extern void Internal_BakeLightmaps(bool cancel);
+ [LibraryImport("FlaxEngine", EntryPoint = "EditorInternal_GetCollisionWires", StringMarshalling = StringMarshalling.Custom, StringMarshallingCustomType = typeof(StringMarshaller))]
+ internal static partial void Internal_GetCollisionWires(IntPtr collisionData, [MarshalUsing(typeof(FlaxEngine.Interop.ArrayMarshaller<,>), CountElementName = "trianglesCount")] out Float3[] triangles, [MarshalUsing(typeof(FlaxEngine.Interop.ArrayMarshaller<,>), CountElementName = "indicesCount")] out int[] indices, out int trianglesCount, out int indicesCount);
- [MethodImpl(MethodImplOptions.InternalCall)]
- internal static extern string Internal_GetShaderAssetSourceCode(IntPtr obj);
+ [LibraryImport("FlaxEngine", EntryPoint = "EditorInternal_GetEditorBoxWithChildren", StringMarshalling = StringMarshalling.Custom, StringMarshallingCustomType = typeof(StringMarshaller))]
+ internal static partial void Internal_GetEditorBoxWithChildren(IntPtr obj, out BoundingBox resultAsRef);
- [MethodImpl(MethodImplOptions.InternalCall)]
- internal static extern bool Internal_CookMeshCollision(string path, CollisionDataType type, IntPtr model, int modelLodIndex, uint materialSlotsMask, ConvexMeshGenerationFlags convexFlags, int convexVertexLimit);
+ [LibraryImport("FlaxEngine", EntryPoint = "EditorInternal_SetOptions", StringMarshalling = StringMarshalling.Custom, StringMarshallingCustomType = typeof(StringMarshaller))]
+ internal static partial void Internal_SetOptions(ref InternalOptions options);
- [MethodImpl(MethodImplOptions.InternalCall)]
- internal static extern void Internal_GetCollisionWires(IntPtr collisionData, out Float3[] triangles, out int[] indices);
+ [LibraryImport("FlaxEngine", EntryPoint = "EditorInternal_DrawNavMesh", StringMarshalling = StringMarshalling.Custom, StringMarshallingCustomType = typeof(StringMarshaller))]
+ internal static partial void Internal_DrawNavMesh();
- [MethodImpl(MethodImplOptions.InternalCall)]
- internal static extern void Internal_GetEditorBoxWithChildren(IntPtr obj, out BoundingBox resultAsRef);
+ [LibraryImport("FlaxEngine", EntryPoint = "EditorInternal_CloseSplashScreen", StringMarshalling = StringMarshalling.Custom, StringMarshallingCustomType = typeof(StringMarshaller))]
+ internal static partial void Internal_CloseSplashScreen();
- [MethodImpl(MethodImplOptions.InternalCall)]
- internal static extern void Internal_SetOptions(ref InternalOptions options);
+ [LibraryImport("FlaxEngine", EntryPoint = "EditorInternal_CreateAsset", StringMarshalling = StringMarshalling.Custom, StringMarshallingCustomType = typeof(StringMarshaller))]
+ [return: MarshalAs(UnmanagedType.U1)]
+ internal static partial bool Internal_CreateAsset(NewAssetType type, string outputPath);
- [MethodImpl(MethodImplOptions.InternalCall)]
- internal static extern void Internal_DrawNavMesh();
+ [LibraryImport("FlaxEngine", EntryPoint = "EditorInternal_CreateVisualScript", StringMarshalling = StringMarshalling.Custom, StringMarshallingCustomType = typeof(StringMarshaller))]
+ [return: MarshalAs(UnmanagedType.U1)]
+ internal static partial bool Internal_CreateVisualScript(string outputPath, string baseTypename);
- [MethodImpl(MethodImplOptions.InternalCall)]
- internal static extern void Internal_CloseSplashScreen();
+ [LibraryImport("FlaxEngine", EntryPoint = "EditorInternal_CanImport", StringMarshalling = StringMarshalling.Custom, StringMarshallingCustomType = typeof(StringMarshaller))]
+ internal static partial string Internal_CanImport(string extension);
- [MethodImpl(MethodImplOptions.InternalCall)]
- internal static extern bool Internal_CreateAsset(NewAssetType type, string outputPath);
+ [LibraryImport("FlaxEngine", EntryPoint = "EditorInternal_CanExport", StringMarshalling = StringMarshalling.Custom, StringMarshallingCustomType = typeof(StringMarshaller))]
+ [return: MarshalAs(UnmanagedType.U1)]
+ internal static partial bool Internal_CanExport(string path);
- [MethodImpl(MethodImplOptions.InternalCall)]
- internal static extern bool Internal_CreateVisualScript(string outputPath, string baseTypename);
+ [LibraryImport("FlaxEngine", EntryPoint = "EditorInternal_Export", StringMarshalling = StringMarshalling.Custom, StringMarshallingCustomType = typeof(StringMarshaller))]
+ [return: MarshalAs(UnmanagedType.U1)]
+ internal static partial bool Internal_Export(string inputPath, string outputFolder);
- [MethodImpl(MethodImplOptions.InternalCall)]
- internal static extern string Internal_CanImport(string extension);
+ [LibraryImport("FlaxEngine", EntryPoint = "EditorInternal_GetIsEveryAssemblyLoaded", StringMarshalling = StringMarshalling.Custom, StringMarshallingCustomType = typeof(StringMarshaller))]
+ [return: MarshalAs(UnmanagedType.U1)]
+ internal static partial bool Internal_GetIsEveryAssemblyLoaded();
- [MethodImpl(MethodImplOptions.InternalCall)]
- internal static extern bool Internal_CanExport(string path);
+ [LibraryImport("FlaxEngine", EntryPoint = "EditorInternal_GetLastProjectOpenedEngineBuild", StringMarshalling = StringMarshalling.Custom, StringMarshallingCustomType = typeof(StringMarshaller))]
+ internal static partial int Internal_GetLastProjectOpenedEngineBuild();
- [MethodImpl(MethodImplOptions.InternalCall)]
- internal static extern bool Internal_Export(string inputPath, string outputFolder);
+ [LibraryImport("FlaxEngine", EntryPoint = "EditorInternal_GetIsCSGActive", StringMarshalling = StringMarshalling.Custom, StringMarshallingCustomType = typeof(StringMarshaller))]
+ [return: MarshalAs(UnmanagedType.U1)]
+ internal static partial bool Internal_GetIsCSGActive();
- [MethodImpl(MethodImplOptions.InternalCall)]
- internal static extern bool Internal_GetIsEveryAssemblyLoaded();
+ [LibraryImport("FlaxEngine", EntryPoint = "EditorInternal_RunVisualScriptBreakpointLoopTick", StringMarshalling = StringMarshalling.Custom, StringMarshallingCustomType = typeof(StringMarshaller))]
+ internal static partial void Internal_RunVisualScriptBreakpointLoopTick(float deltaTime);
- [MethodImpl(MethodImplOptions.InternalCall)]
- internal static extern int Internal_GetLastProjectOpenedEngineBuild();
+ [LibraryImport("FlaxEngine", EntryPoint = "EditorInternal_GetVisualScriptLocals", StringMarshalling = StringMarshalling.Custom, StringMarshallingCustomType = typeof(StringMarshaller))]
+ [return: MarshalUsing(typeof(FlaxEngine.Interop.ArrayMarshaller<,>), CountElementName = "localsCount")]
+ internal static partial VisualScriptLocal[] Internal_GetVisualScriptLocals(out int localsCount);
- [MethodImpl(MethodImplOptions.InternalCall)]
- internal static extern bool Internal_GetIsCSGActive();
+ [LibraryImport("FlaxEngine", EntryPoint = "EditorInternal_GetVisualScriptStackFrames", StringMarshalling = StringMarshalling.Custom, StringMarshallingCustomType = typeof(StringMarshaller))]
+ [return: MarshalUsing(typeof(FlaxEngine.Interop.ArrayMarshaller<,>), CountElementName = "stackFrameCount")]
+ internal static partial VisualScriptStackFrame[] Internal_GetVisualScriptStackFrames(out int stackFrameCount);
- [MethodImpl(MethodImplOptions.InternalCall)]
- internal static extern void Internal_RunVisualScriptBreakpointLoopTick(float deltaTime);
+ [LibraryImport("FlaxEngine", EntryPoint = "EditorInternal_GetVisualScriptPreviousScopeFrame", StringMarshalling = StringMarshalling.Custom, StringMarshallingCustomType = typeof(StringMarshaller))]
+ internal static partial VisualScriptStackFrame Internal_GetVisualScriptPreviousScopeFrame();
- [MethodImpl(MethodImplOptions.InternalCall)]
- internal static extern VisualScriptLocal[] Internal_GetVisualScriptLocals();
+ [LibraryImport("FlaxEngine", EntryPoint = "EditorInternal_EvaluateVisualScriptLocal", StringMarshalling = StringMarshalling.Custom, StringMarshallingCustomType = typeof(StringMarshaller))]
+ [return: MarshalAs(UnmanagedType.U1)]
+ internal static partial bool Internal_EvaluateVisualScriptLocal(IntPtr script, ref VisualScriptLocal local);
- [MethodImpl(MethodImplOptions.InternalCall)]
- internal static extern VisualScriptStackFrame[] Internal_GetVisualScriptStackFrames();
+ [LibraryImport("FlaxEngine", EntryPoint = "EditorInternal_DeserializeSceneObject", StringMarshalling = StringMarshalling.Custom, StringMarshallingCustomType = typeof(StringMarshaller))]
+ internal static partial void Internal_DeserializeSceneObject(IntPtr sceneObject, string json);
- [MethodImpl(MethodImplOptions.InternalCall)]
- internal static extern VisualScriptStackFrame Internal_GetVisualScriptPreviousScopeFrame();
+ [LibraryImport("FlaxEngine", EntryPoint = "EditorInternal_LoadAsset", StringMarshalling = StringMarshalling.Custom, StringMarshallingCustomType = typeof(StringMarshaller))]
+ internal static partial void Internal_LoadAsset(ref Guid id);
- [MethodImpl(MethodImplOptions.InternalCall)]
- internal static extern bool Internal_EvaluateVisualScriptLocal(IntPtr script, ref VisualScriptLocal local);
+ [LibraryImport("FlaxEngine", EntryPoint = "EditorInternal_CanSetToRoot", StringMarshalling = StringMarshalling.Custom, StringMarshallingCustomType = typeof(StringMarshaller))]
+ [return: MarshalAs(UnmanagedType.U1)]
+ internal static partial bool Internal_CanSetToRoot(IntPtr prefab, IntPtr newRoot);
- [MethodImpl(MethodImplOptions.InternalCall)]
- internal static extern void Internal_DeserializeSceneObject(IntPtr sceneObject, string json);
+ [LibraryImport("FlaxEngine", EntryPoint = "EditorInternal_GetAnimationTime", StringMarshalling = StringMarshalling.Custom, StringMarshallingCustomType = typeof(StringMarshaller))]
+ internal static partial float Internal_GetAnimationTime(IntPtr animatedModel);
- [MethodImpl(MethodImplOptions.InternalCall)]
- internal static extern void Internal_LoadAsset(ref Guid id);
-
- [MethodImpl(MethodImplOptions.InternalCall)]
- internal static extern bool Internal_CanSetToRoot(IntPtr prefab, IntPtr newRoot);
-
- [MethodImpl(MethodImplOptions.InternalCall)]
- internal static extern float Internal_GetAnimationTime(IntPtr animatedModel);
-
- [MethodImpl(MethodImplOptions.InternalCall)]
- internal static extern void Internal_SetAnimationTime(IntPtr animatedModel, float time);
+ [LibraryImport("FlaxEngine", EntryPoint = "EditorInternal_SetAnimationTime", StringMarshalling = StringMarshalling.Custom, StringMarshallingCustomType = typeof(StringMarshaller))]
+ internal static partial void Internal_SetAnimationTime(IntPtr animatedModel, float time);
#endregion
}
diff --git a/Source/Editor/EditorIcons.cs b/Source/Editor/EditorIcons.cs
index 2de1ff763..127ba3f6f 100644
--- a/Source/Editor/EditorIcons.cs
+++ b/Source/Editor/EditorIcons.cs
@@ -137,11 +137,13 @@ namespace FlaxEditor
public SpriteHandle PS4Icon128;
public SpriteHandle PS5Icon128;
public SpriteHandle MacOSIcon128;
+ public SpriteHandle IOSIcon128;
public SpriteHandle FlaxLogo128;
public SpriteHandle SwitchIcon128;
public SpriteHandle SwitchSettings128;
public SpriteHandle LocalizationSettings128;
public SpriteHandle Json128;
+ public SpriteHandle AppleSettings128;
internal void LoadIcons()
{
diff --git a/Source/Editor/GUI/AssetPicker.cs b/Source/Editor/GUI/AssetPicker.cs
index a359c288c..bfded5380 100644
--- a/Source/Editor/GUI/AssetPicker.cs
+++ b/Source/Editor/GUI/AssetPicker.cs
@@ -7,6 +7,7 @@ using FlaxEditor.GUI.Drag;
using FlaxEditor.Scripting;
using FlaxEngine;
using FlaxEngine.GUI;
+using FlaxEngine.Utilities;
namespace FlaxEditor.GUI
{
@@ -205,6 +206,11 @@ namespace FlaxEditor.GUI
///
public event Action SelectedItemChanged;
+ ///
+ /// The custom callback for assets validation. Cane be used to implement a rule for assets to pick.
+ ///
+ public Func CheckValid;
+
///
/// False if changing selected item is disabled.
///
@@ -214,6 +220,8 @@ namespace FlaxEditor.GUI
{
if (_fileExtension != null && !item.Path.EndsWith(_fileExtension))
return false;
+ if (CheckValid != null && !CheckValid(item))
+ return false;
if (_type == ScriptType.Null)
return true;
diff --git a/Source/Editor/GUI/CurveEditor.Contents.cs b/Source/Editor/GUI/CurveEditor.Contents.cs
index dca6d0450..2d3c2d606 100644
--- a/Source/Editor/GUI/CurveEditor.Contents.cs
+++ b/Source/Editor/GUI/CurveEditor.Contents.cs
@@ -8,6 +8,7 @@ using FlaxEditor.Scripting;
using FlaxEngine;
using FlaxEngine.GUI;
using FlaxEngine.Json;
+using FlaxEngine.Utilities;
namespace FlaxEditor.GUI
{
diff --git a/Source/Editor/GUI/Docking/DockWindow.cs b/Source/Editor/GUI/Docking/DockWindow.cs
index 5eb650898..dd4e39ce2 100644
--- a/Source/Editor/GUI/Docking/DockWindow.cs
+++ b/Source/Editor/GUI/Docking/DockWindow.cs
@@ -1,6 +1,7 @@
// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.
using System.Xml;
+using System.Globalization;
using FlaxEngine;
using FlaxEngine.Assertions;
using FlaxEngine.GUI;
@@ -423,6 +424,29 @@ namespace FlaxEditor.GUI.Docking
{
}
+ ///
+ /// Serializes splitter panel value into the saved layout.
+ ///
+ /// The Xml writer.
+ /// The Xml attribute name to use for value.
+ /// The splitter panel.
+ protected void LayoutSerializeSplitter(XmlWriter writer, string name, SplitPanel splitter)
+ {
+ writer.WriteAttributeString(name, splitter.SplitterValue.ToString(CultureInfo.InvariantCulture));
+ }
+
+ ///
+ /// Deserializes splitter panel value from the saved layout.
+ ///
+ /// The Xml document node.
+ /// The Xml attribute name to use for value.
+ /// The splitter panel.
+ protected void LayoutDeserializeSplitter(XmlElement node, string name, SplitPanel splitter)
+ {
+ if (float.TryParse(node.GetAttribute(name), CultureInfo.InvariantCulture, out float value) && value > 0.01f && value < 0.99f)
+ splitter.SplitterValue = value;
+ }
+
///
public override void OnDestroy()
{
diff --git a/Source/Editor/GUI/Drag/DragActorType.cs b/Source/Editor/GUI/Drag/DragActorType.cs
index 40b66bbdf..ac62a045b 100644
--- a/Source/Editor/GUI/Drag/DragActorType.cs
+++ b/Source/Editor/GUI/Drag/DragActorType.cs
@@ -6,6 +6,7 @@ using FlaxEditor.SceneGraph;
using FlaxEditor.Scripting;
using FlaxEngine;
using FlaxEngine.GUI;
+using FlaxEngine.Utilities;
namespace FlaxEditor.GUI.Drag
{
diff --git a/Source/Editor/GUI/EnumComboBox.cs b/Source/Editor/GUI/EnumComboBox.cs
index fc0d45d2b..110693af8 100644
--- a/Source/Editor/GUI/EnumComboBox.cs
+++ b/Source/Editor/GUI/EnumComboBox.cs
@@ -4,6 +4,7 @@ using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
+using System.Runtime.InteropServices;
using FlaxEditor.CustomEditors;
using FlaxEditor.CustomEditors.Elements;
using FlaxEditor.Scripting;
@@ -170,7 +171,7 @@ namespace FlaxEditor.GUI
BuildEntriesDefault(type, _entries, formatMode);
var hasTooltips = false;
- var entries = Utils.ExtractArrayFromList(_entries);
+ var entries = CollectionsMarshal.AsSpan(_entries);
for (int i = 0; i < _entries.Count; i++)
{
ref var e = ref entries[i];
diff --git a/Source/Editor/GUI/PlatformSelector.cs b/Source/Editor/GUI/PlatformSelector.cs
index 0b57551e9..1e03a9677 100644
--- a/Source/Editor/GUI/PlatformSelector.cs
+++ b/Source/Editor/GUI/PlatformSelector.cs
@@ -92,6 +92,7 @@ namespace FlaxEditor.GUI
new PlatformData(PlatformType.Switch, icons.SwitchIcon128, "Switch"),
new PlatformData(PlatformType.PS5, icons.PS5Icon128, "PlayStation 5"),
new PlatformData(PlatformType.Mac, icons.MacOSIcon128, "macOS"),
+ new PlatformData(PlatformType.iOS, icons.IOSIcon128, "iOS"),
};
const float IconSize = 64.0f;
diff --git a/Source/Editor/GUI/Popups/ScriptSearchPopup.cs b/Source/Editor/GUI/Popups/ScriptSearchPopup.cs
index 839346b5a..b771adff6 100644
--- a/Source/Editor/GUI/Popups/ScriptSearchPopup.cs
+++ b/Source/Editor/GUI/Popups/ScriptSearchPopup.cs
@@ -4,6 +4,7 @@ using System;
using FlaxEditor.Scripting;
using FlaxEngine;
using FlaxEngine.GUI;
+using FlaxEngine.Utilities;
namespace FlaxEditor.GUI
{
diff --git a/Source/Editor/GUI/Timeline/GUI/KeyframesEditor.cs b/Source/Editor/GUI/Timeline/GUI/KeyframesEditor.cs
index afc0768d9..cb781229f 100644
--- a/Source/Editor/GUI/Timeline/GUI/KeyframesEditor.cs
+++ b/Source/Editor/GUI/Timeline/GUI/KeyframesEditor.cs
@@ -11,6 +11,7 @@ using FlaxEditor.Scripting;
using FlaxEngine;
using FlaxEngine.GUI;
using FlaxEngine.Json;
+using FlaxEngine.Utilities;
namespace FlaxEditor.GUI
{
diff --git a/Source/Editor/GUI/Timeline/ParticleSystemTimeline.cs b/Source/Editor/GUI/Timeline/ParticleSystemTimeline.cs
index fa9ecae70..5febb2eaa 100644
--- a/Source/Editor/GUI/Timeline/ParticleSystemTimeline.cs
+++ b/Source/Editor/GUI/Timeline/ParticleSystemTimeline.cs
@@ -10,6 +10,7 @@ using FlaxEditor.GUI.Timeline.Tracks;
using FlaxEditor.Utilities;
using FlaxEditor.Viewport.Previews;
using FlaxEngine;
+using FlaxEngine.Utilities;
namespace FlaxEditor.GUI.Timeline
{
diff --git a/Source/Editor/GUI/Timeline/Tracks/AnimationEventTrack.cs b/Source/Editor/GUI/Timeline/Tracks/AnimationEventTrack.cs
index 5b7ecddef..4e78ca8ae 100644
--- a/Source/Editor/GUI/Timeline/Tracks/AnimationEventTrack.cs
+++ b/Source/Editor/GUI/Timeline/Tracks/AnimationEventTrack.cs
@@ -10,6 +10,7 @@ using FlaxEditor.Scripting;
using FlaxEditor.Utilities;
using FlaxEngine;
using FlaxEngine.GUI;
+using FlaxEngine.Utilities;
using Object = FlaxEngine.Object;
namespace FlaxEditor.GUI.Timeline.Tracks
diff --git a/Source/Editor/GUI/Timeline/Tracks/CurvePropertyTrack.cs b/Source/Editor/GUI/Timeline/Tracks/CurvePropertyTrack.cs
index e74fd0273..65caf4f3d 100644
--- a/Source/Editor/GUI/Timeline/Tracks/CurvePropertyTrack.cs
+++ b/Source/Editor/GUI/Timeline/Tracks/CurvePropertyTrack.cs
@@ -9,6 +9,7 @@ using System.Text;
using FlaxEditor.GUI.Timeline.Undo;
using FlaxEngine;
using FlaxEngine.GUI;
+using FlaxEngine.Utilities;
namespace FlaxEditor.GUI.Timeline.Tracks
{
@@ -539,7 +540,7 @@ namespace FlaxEditor.GUI.Timeline.Tracks
throw new Exception("Invalid track data.");
var keyframes = new object[keyframesCount];
- var propertyType = Scripting.TypeUtils.GetType(e.MemberTypeName).Type;
+ var propertyType = TypeUtils.GetType(e.MemberTypeName).Type;
if (propertyType == null)
{
stream.ReadBytes(keyframesCount * (sizeof(float) + valueSize * 3));
diff --git a/Source/Editor/GUI/Timeline/Tracks/EventTrack.cs b/Source/Editor/GUI/Timeline/Tracks/EventTrack.cs
index e7e34fdda..f13bfe811 100644
--- a/Source/Editor/GUI/Timeline/Tracks/EventTrack.cs
+++ b/Source/Editor/GUI/Timeline/Tracks/EventTrack.cs
@@ -10,6 +10,7 @@ using System.Text;
using FlaxEditor.GUI.Timeline.Undo;
using FlaxEngine;
using FlaxEngine.GUI;
+using FlaxEngine.Utilities;
namespace FlaxEditor.GUI.Timeline.Tracks
{
@@ -51,7 +52,7 @@ namespace FlaxEditor.GUI.Timeline.Tracks
{
e.EventParamsSizes[i] = stream.ReadInt32();
var paramTypeName = LoadName(stream);
- e.EventParamsTypes[i] = Scripting.TypeUtils.GetManagedType(paramTypeName);
+ e.EventParamsTypes[i] = TypeUtils.GetManagedType(paramTypeName);
if (e.EventParamsTypes[i] == null)
isInvalid = true;
}
diff --git a/Source/Editor/GUI/Timeline/Tracks/KeyframesPropertyTrack.cs b/Source/Editor/GUI/Timeline/Tracks/KeyframesPropertyTrack.cs
index f2ac84a4d..fa80912f9 100644
--- a/Source/Editor/GUI/Timeline/Tracks/KeyframesPropertyTrack.cs
+++ b/Source/Editor/GUI/Timeline/Tracks/KeyframesPropertyTrack.cs
@@ -9,6 +9,7 @@ using System.Text;
using FlaxEditor.GUI.Timeline.Undo;
using FlaxEngine;
using FlaxEngine.GUI;
+using FlaxEngine.Utilities;
namespace FlaxEditor.GUI.Timeline.Tracks
{
@@ -56,7 +57,7 @@ namespace FlaxEditor.GUI.Timeline.Tracks
var keyframes = new KeyframesEditor.Keyframe[keyframesCount];
var dataBuffer = new byte[e.ValueSize];
- var propertyType = Scripting.TypeUtils.GetManagedType(e.MemberTypeName);
+ var propertyType = TypeUtils.GetManagedType(e.MemberTypeName);
if (propertyType == null)
{
e.Keyframes.ResetKeyframes();
diff --git a/Source/Editor/GUI/Timeline/Tracks/ObjectPropertyTrack.cs b/Source/Editor/GUI/Timeline/Tracks/ObjectPropertyTrack.cs
index c753c98ed..fde00ecaf 100644
--- a/Source/Editor/GUI/Timeline/Tracks/ObjectPropertyTrack.cs
+++ b/Source/Editor/GUI/Timeline/Tracks/ObjectPropertyTrack.cs
@@ -4,6 +4,7 @@ using System;
using System.IO;
using System.Text;
using FlaxEngine.GUI;
+using FlaxEngine.Utilities;
namespace FlaxEditor.GUI.Timeline.Tracks
{
@@ -48,7 +49,7 @@ namespace FlaxEditor.GUI.Timeline.Tracks
if (stream.ReadChar() != 0)
throw new Exception("Invalid track data.");
- var propertyType = Scripting.TypeUtils.GetType(e.MemberTypeName);
+ var propertyType = TypeUtils.GetType(e.MemberTypeName);
if (!propertyType)
{
if (!string.IsNullOrEmpty(e.MemberTypeName))
diff --git a/Source/Editor/GUI/Timeline/Tracks/ObjectReferencePropertyTrack.cs b/Source/Editor/GUI/Timeline/Tracks/ObjectReferencePropertyTrack.cs
index da1502b71..371d65082 100644
--- a/Source/Editor/GUI/Timeline/Tracks/ObjectReferencePropertyTrack.cs
+++ b/Source/Editor/GUI/Timeline/Tracks/ObjectReferencePropertyTrack.cs
@@ -4,6 +4,7 @@ using System;
using System.IO;
using System.Reflection;
using System.Text;
+using FlaxEngine.Utilities;
namespace FlaxEditor.GUI.Timeline.Tracks
{
@@ -51,7 +52,7 @@ namespace FlaxEditor.GUI.Timeline.Tracks
throw new Exception("Invalid track data.");
var keyframes = new KeyframesEditor.Keyframe[keyframesCount];
- var propertyType = Scripting.TypeUtils.GetType(e.MemberTypeName);
+ var propertyType = TypeUtils.GetType(e.MemberTypeName);
if (!propertyType)
{
e.Keyframes.ResetKeyframes();
diff --git a/Source/Editor/GUI/Timeline/Tracks/StringPropertyTrack.cs b/Source/Editor/GUI/Timeline/Tracks/StringPropertyTrack.cs
index ffbf64a99..6e733f47d 100644
--- a/Source/Editor/GUI/Timeline/Tracks/StringPropertyTrack.cs
+++ b/Source/Editor/GUI/Timeline/Tracks/StringPropertyTrack.cs
@@ -3,6 +3,7 @@
using System;
using System.IO;
using System.Text;
+using FlaxEngine.Utilities;
namespace FlaxEditor.GUI.Timeline.Tracks
{
@@ -50,7 +51,7 @@ namespace FlaxEditor.GUI.Timeline.Tracks
throw new Exception("Invalid track data.");
var keyframes = new KeyframesEditor.Keyframe[keyframesCount];
- var propertyType = Scripting.TypeUtils.GetType(e.MemberTypeName);
+ var propertyType = TypeUtils.GetType(e.MemberTypeName);
if (!propertyType)
{
e.Keyframes.ResetKeyframes();
diff --git a/Source/Editor/GUI/Timeline/Tracks/StructPropertyTrack.cs b/Source/Editor/GUI/Timeline/Tracks/StructPropertyTrack.cs
index 6cce0d738..26f9d2ad9 100644
--- a/Source/Editor/GUI/Timeline/Tracks/StructPropertyTrack.cs
+++ b/Source/Editor/GUI/Timeline/Tracks/StructPropertyTrack.cs
@@ -5,6 +5,7 @@ using System.IO;
using System.Reflection;
using System.Text;
using FlaxEngine.GUI;
+using FlaxEngine.Utilities;
namespace FlaxEditor.GUI.Timeline.Tracks
{
@@ -49,7 +50,7 @@ namespace FlaxEditor.GUI.Timeline.Tracks
if (stream.ReadChar() != 0)
throw new Exception("Invalid track data.");
- var propertyType = Scripting.TypeUtils.GetType(e.MemberTypeName);
+ var propertyType = TypeUtils.GetType(e.MemberTypeName);
if (!propertyType)
{
if (!string.IsNullOrEmpty(e.MemberTypeName))
diff --git a/Source/Editor/Managed/ManagedEditor.Internal.cpp b/Source/Editor/Managed/ManagedEditor.Internal.cpp
index dcaa25e6c..bed9cfb5c 100644
--- a/Source/Editor/Managed/ManagedEditor.Internal.cpp
+++ b/Source/Editor/Managed/ManagedEditor.Internal.cpp
@@ -9,11 +9,11 @@
#include "Engine/ContentExporters/AssetsExportingManager.h"
#include "Editor/CustomEditors/CustomEditorsUtil.h"
#include "Engine/Scripting/Scripting.h"
-#include "Engine/Scripting/InternalCalls.h"
#include "Engine/Scripting/ManagedCLR/MUtils.h"
#include "Engine/Scripting/ManagedCLR/MClass.h"
#include "Engine/Scripting/ManagedCLR/MAssembly.h"
-#include "Engine/Scripting/StdTypesContainer.h"
+#include "Engine/Scripting/Internal/InternalCalls.h"
+#include "Engine/Scripting/Internal/StdTypesContainer.h"
#include "Engine/Graphics/Shaders/Cache/ShaderCacheManager.h"
#include "Engine/ShadowsOfMordor/Builder.h"
#include "Engine/Physics/CollisionData.h"
@@ -47,7 +47,6 @@
#include "FlaxEngine.Gen.h"
#include "Engine/Level/Actors/AnimatedModel.h"
#include "Engine/Serialization/JsonTools.h"
-#include
Guid ManagedEditor::ObjectID(0x91970b4e, 0x99634f61, 0x84723632, 0x54c776af);
@@ -57,240 +56,6 @@ Guid ManagedEditor::ObjectID(0x91970b4e, 0x99634f61, 0x84723632, 0x54c776af);
#pragma warning( disable : 4800)
#endif
-struct InternalTextureOptions
-{
- TextureFormatType Type;
- byte IsAtlas;
- byte NeverStream;
- byte Compress;
- byte IndependentChannels;
- byte sRGB;
- byte GenerateMipMaps;
- byte FlipY;
- byte Resize;
- byte PreserveAlphaCoverage;
- float PreserveAlphaCoverageReference;
- float Scale;
- int32 MaxSize;
- int32 TextureGroup;
- int32 SizeX;
- int32 SizeY;
- MonoArray* SpriteAreas;
- MonoArray* SpriteNames;
-
- static void Convert(InternalTextureOptions* from, ImportTexture::Options* to)
- {
- to->Type = from->Type;
- to->IsAtlas = from->IsAtlas;
- to->NeverStream = from->NeverStream;
- to->Compress = from->Compress;
- to->IndependentChannels = from->IndependentChannels;
- to->sRGB = from->sRGB;
- to->GenerateMipMaps = from->GenerateMipMaps;
- to->FlipY = from->FlipY;
- to->Scale = from->Scale;
- to->Resize = from->Resize;
- to->PreserveAlphaCoverage = from->PreserveAlphaCoverage;
- to->PreserveAlphaCoverageReference = from->PreserveAlphaCoverageReference;
- to->MaxSize = from->MaxSize;
- to->TextureGroup = from->TextureGroup;
- to->SizeX = from->SizeX;
- to->SizeY = from->SizeY;
- to->Sprites.Clear();
- if (from->SpriteNames != nullptr)
- {
- int32 count = (int32)mono_array_length(from->SpriteNames);
- ASSERT(count == (int32)mono_array_length(from->SpriteAreas));
- to->Sprites.EnsureCapacity(count);
- for (int32 i = 0; i < count; i++)
- {
- Sprite& sprite = to->Sprites.AddOne();
- sprite.Area = mono_array_get(from->SpriteAreas, Rectangle, i);
- sprite.Name = MUtils::ToString(mono_array_get(from->SpriteNames, MonoString*, i));
- }
- }
- }
-
- static void Convert(ImportTexture::Options* from, InternalTextureOptions* to)
- {
- to->Type = from->Type;
- to->IsAtlas = from->IsAtlas;
- to->NeverStream = from->NeverStream;
- to->Compress = from->Compress;
- to->IndependentChannels = from->IndependentChannels;
- to->sRGB = from->sRGB;
- to->GenerateMipMaps = from->GenerateMipMaps;
- to->FlipY = from->FlipY;
- to->Resize = from->Resize;
- to->PreserveAlphaCoverage = from->PreserveAlphaCoverage;
- to->PreserveAlphaCoverageReference = from->PreserveAlphaCoverageReference;
- to->Scale = from->Scale;
- to->MaxSize = from->MaxSize;
- to->TextureGroup = from->TextureGroup;
- to->SizeX = from->SizeX;
- to->SizeY = from->SizeY;
- if (from->Sprites.HasItems())
- {
- const auto domain = mono_domain_get();
- int32 count = from->Sprites.Count();
- auto rectClass = Rectangle::TypeInitializer.GetType().ManagedClass;
- ASSERT(rectClass != nullptr);
- to->SpriteAreas = mono_array_new(domain, rectClass->GetNative(), count);
- to->SpriteNames = mono_array_new(domain, mono_get_string_class(), count);
- for (int32 i = 0; i < count; i++)
- {
- mono_array_set(to->SpriteAreas, Rectangle, i, from->Sprites[i].Area);
- mono_array_setref(to->SpriteNames, i, MUtils::ToString(from->Sprites[i].Name, domain));
- }
- }
- else
- {
- to->SpriteAreas = nullptr;
- to->SpriteNames = nullptr;
- }
- }
-};
-
-struct InternalModelOptions
-{
- ModelTool::ModelType Type;
-
- // Geometry
- byte CalculateNormals;
- float SmoothingNormalsAngle;
- byte FlipNormals;
- float SmoothingTangentsAngle;
- byte CalculateTangents;
- byte OptimizeMeshes;
- byte MergeMeshes;
- byte ImportLODs;
- byte ImportVertexColors;
- byte ImportBlendShapes;
- ModelLightmapUVsSource LightmapUVsSource;
- MonoString* CollisionMeshesPrefix;
-
- // Transform
- float Scale;
- Quaternion Rotation;
- Float3 Translation;
- byte CenterGeometry;
-
- // Animation
- ModelTool::AnimationDuration Duration;
- float FramesRangeStart;
- float FramesRangeEnd;
- float DefaultFrameRate;
- float SamplingRate;
- byte SkipEmptyCurves;
- byte OptimizeKeyframes;
- byte ImportScaleTracks;
- byte EnableRootMotion;
- MonoString* RootNodeName;
-
- // Level Of Detail
- byte GenerateLODs;
- int32 BaseLOD;
- int32 LODCount;
- float TriangleReduction;
-
- // Misc
- byte ImportMaterials;
- byte ImportTextures;
- byte RestoreMaterialsOnReimport;
-
- // SDF
- byte GenerateSDF;
- float SDFResolution;
-
- // Splitting
- byte SplitObjects;
- int32 ObjectIndex;
-
- static void Convert(InternalModelOptions* from, ImportModelFile::Options* to)
- {
- to->Type = from->Type;
- to->CalculateNormals = from->CalculateNormals;
- to->SmoothingNormalsAngle = from->SmoothingNormalsAngle;
- to->FlipNormals = from->FlipNormals;
- to->SmoothingTangentsAngle = from->SmoothingTangentsAngle;
- to->CalculateTangents = from->CalculateTangents;
- to->OptimizeMeshes = from->OptimizeMeshes;
- to->MergeMeshes = from->MergeMeshes;
- to->ImportLODs = from->ImportLODs;
- to->ImportVertexColors = from->ImportVertexColors;
- to->ImportBlendShapes = from->ImportBlendShapes;
- to->LightmapUVsSource = from->LightmapUVsSource;
- to->CollisionMeshesPrefix = MUtils::ToString(from->CollisionMeshesPrefix);
- to->Scale = from->Scale;
- to->Rotation = from->Rotation;
- to->Translation = from->Translation;
- to->CenterGeometry = from->CenterGeometry;
- to->Duration = from->Duration;
- to->FramesRange.X = from->FramesRangeStart;
- to->FramesRange.Y = from->FramesRangeEnd;
- to->DefaultFrameRate = from->DefaultFrameRate;
- to->SamplingRate = from->SamplingRate;
- to->SkipEmptyCurves = from->SkipEmptyCurves;
- to->OptimizeKeyframes = from->OptimizeKeyframes;
- to->ImportScaleTracks = from->ImportScaleTracks;
- to->EnableRootMotion = from->EnableRootMotion;
- to->RootNodeName = MUtils::ToString(from->RootNodeName);
- to->GenerateLODs = from->GenerateLODs;
- to->BaseLOD = from->BaseLOD;
- to->LODCount = from->LODCount;
- to->TriangleReduction = from->TriangleReduction;
- to->ImportMaterials = from->ImportMaterials;
- to->ImportTextures = from->ImportTextures;
- to->RestoreMaterialsOnReimport = from->RestoreMaterialsOnReimport;
- to->GenerateSDF = from->GenerateSDF;
- to->SDFResolution = from->SDFResolution;
- to->SplitObjects = from->SplitObjects;
- to->ObjectIndex = from->ObjectIndex;
- }
-
- static void Convert(ImportModelFile::Options* from, InternalModelOptions* to)
- {
- to->Type = from->Type;
- to->CalculateNormals = from->CalculateNormals;
- to->SmoothingNormalsAngle = from->SmoothingNormalsAngle;
- to->FlipNormals = from->FlipNormals;
- to->SmoothingTangentsAngle = from->SmoothingTangentsAngle;
- to->CalculateTangents = from->CalculateTangents;
- to->OptimizeMeshes = from->OptimizeMeshes;
- to->MergeMeshes = from->MergeMeshes;
- to->ImportLODs = from->ImportLODs;
- to->ImportVertexColors = from->ImportVertexColors;
- to->ImportBlendShapes = from->ImportBlendShapes;
- to->LightmapUVsSource = from->LightmapUVsSource;
- to->CollisionMeshesPrefix = MUtils::ToString(from->CollisionMeshesPrefix);
- to->Scale = from->Scale;
- to->Rotation = from->Rotation;
- to->Translation = from->Translation;
- to->CenterGeometry = from->CenterGeometry;
- to->Duration = from->Duration;
- to->FramesRangeStart = from->FramesRange.X;
- to->FramesRangeEnd = from->FramesRange.Y;
- to->DefaultFrameRate = from->DefaultFrameRate;
- to->SamplingRate = from->SamplingRate;
- to->SkipEmptyCurves = from->SkipEmptyCurves;
- to->OptimizeKeyframes = from->OptimizeKeyframes;
- to->ImportScaleTracks = from->ImportScaleTracks;
- to->EnableRootMotion = from->EnableRootMotion;
- to->RootNodeName = MUtils::ToString(from->RootNodeName);
- to->GenerateLODs = from->GenerateLODs;
- to->BaseLOD = from->BaseLOD;
- to->LODCount = from->LODCount;
- to->TriangleReduction = from->TriangleReduction;
- to->ImportMaterials = from->ImportMaterials;
- to->ImportTextures = from->ImportTextures;
- to->RestoreMaterialsOnReimport = from->RestoreMaterialsOnReimport;
- to->GenerateSDF = from->GenerateSDF;
- to->SDFResolution = from->SDFResolution;
- to->SplitObjects = from->SplitObjects;
- to->ObjectIndex = from->ObjectIndex;
- }
-};
-
struct InternalAudioOptions
{
AudioFormat Format;
@@ -322,31 +87,6 @@ struct InternalAudioOptions
#pragma warning( pop )
#endif
-namespace CustomEditorsUtilInternal
-{
- MonoReflectionType* GetCustomEditor(MonoReflectionType* targetType)
- {
- return CustomEditorsUtil::GetCustomEditor(targetType);
- }
-}
-
-namespace LayersAndTagsSettingsInternal
-{
- MonoArray* GetCurrentLayers()
- {
- return MUtils::ToArray(Span(Level::Layers, Math::Max(1, Level::GetNonEmptyLayerNamesCount())));
- }
-}
-
-namespace GameSettingsInternal1
-{
- void Apply()
- {
- LOG(Info, "Apply game settings");
- GameSettings::Load();
- }
-}
-
// Pack log messages into a single scratch buffer to reduce dynamic memory allocations
CriticalSection CachedLogDataLocker;
Array CachedLogData;
@@ -373,781 +113,720 @@ void OnLogMessage(LogType type, const StringView& msg)
CachedLogData.Add((byte*)msg.Get(), msg.Length() * 2);
}
-class ManagedEditorInternal
+DEFINE_INTERNAL_CALL(bool) EditorInternal_IsDevInstance()
{
-public:
- static bool IsDevInstance()
- {
#if COMPILE_WITH_DEV_ENV
- return true;
+ return true;
#else
- return false;
+ return false;
#endif
- }
+}
- static bool IsOfficialBuild()
- {
+DEFINE_INTERNAL_CALL(bool) EditorInternal_IsOfficialBuild()
+{
#if OFFICIAL_BUILD
+ return true;
+#else
+ return false;
+#endif
+}
+
+DEFINE_INTERNAL_CALL(bool) EditorInternal_IsPlayMode()
+{
+ return Editor::IsPlayMode;
+}
+
+DEFINE_INTERNAL_CALL(int32) EditorInternal_ReadOutputLogs(MArray** outMessages, MArray** outLogTypes, MArray** outLogTimes, int outArraySize)
+{
+ ScopeLock lock(CachedLogDataLocker);
+ if (CachedLogData.IsEmpty() || CachedLogData.Get() == nullptr)
+ return 0;
+
+ int32 count = 0;
+ const int32 maxCount = outArraySize;
+
+ byte* ptr = CachedLogData.Get();
+ byte* end = ptr + CachedLogData.Count();
+ byte* outLogTypesPtr = MCore::Array::GetAddress(*outLogTypes);
+ int64* outLogTimesPtr = MCore::Array::GetAddress(*outLogTimes);
+ while (count < maxCount && ptr != end)
+ {
+ auto type = (byte)*(int32*)ptr;
+ ptr += 4;
+
+ auto time = *(int64*)ptr;
+ ptr += 8;
+
+ auto length = *(int32*)ptr;
+ ptr += 4;
+
+ auto msg = (Char*)ptr;
+ ptr += length * 2;
+
+ auto msgObj = MUtils::ToString(StringView(msg, length));
+
+ MCore::GC::WriteArrayRef(*outMessages, (MObject*)msgObj, count);
+ outLogTypesPtr[count] = type;
+ outLogTimesPtr[count] = time;
+
+ count++;
+ }
+
+ const int32 dataLeft = (int32)(end - ptr);
+ Platform::MemoryCopy(CachedLogData.Get(), ptr, dataLeft);
+ CachedLogData.Resize(dataLeft);
+
+ return count;
+}
+
+DEFINE_INTERNAL_CALL(void) EditorInternal_SetPlayMode(bool value)
+{
+ Editor::IsPlayMode = value;
+}
+
+DEFINE_INTERNAL_CALL(MString*) EditorInternal_GetProjectPath()
+{
+ return MUtils::ToString(Editor::Project->ProjectPath);
+}
+
+DEFINE_INTERNAL_CALL(void) EditorInternal_CloseSplashScreen()
+{
+ Editor::CloseSplashScreen();
+}
+
+DEFINE_INTERNAL_CALL(bool) EditorInternal_CloneAssetFile(MString* dstPathObj, MString* srcPathObj, Guid* dstId)
+{
+ // Get normalized paths
+ String dstPath, srcPath;
+ MUtils::ToString(dstPathObj, dstPath);
+ MUtils::ToString(srcPathObj, srcPath);
+ FileSystem::NormalizePath(dstPath);
+ FileSystem::NormalizePath(srcPath);
+
+ // Call util function
+ return Content::CloneAssetFile(dstPath, srcPath, *dstId);
+}
+
+enum class NewAssetType
+{
+ Material = 0,
+ MaterialInstance = 1,
+ CollisionData = 2,
+ AnimationGraph = 3,
+ SkeletonMask = 4,
+ ParticleEmitter = 5,
+ ParticleSystem = 6,
+ SceneAnimation = 7,
+ MaterialFunction = 8,
+ ParticleEmitterFunction = 9,
+ AnimationGraphFunction = 10,
+ Animation = 11,
+};
+
+DEFINE_INTERNAL_CALL(bool) EditorInternal_CreateAsset(NewAssetType type, MString* outputPathObj)
+{
+ String tag;
+ switch (type)
+ {
+ case NewAssetType::Material:
+ tag = AssetsImportingManager::CreateMaterialTag;
+ break;
+ case NewAssetType::MaterialInstance:
+ tag = AssetsImportingManager::CreateMaterialInstanceTag;
+ break;
+ case NewAssetType::CollisionData:
+ tag = AssetsImportingManager::CreateCollisionDataTag;
+ break;
+ case NewAssetType::AnimationGraph:
+ tag = AssetsImportingManager::CreateAnimationGraphTag;
+ break;
+ case NewAssetType::SkeletonMask:
+ tag = AssetsImportingManager::CreateSkeletonMaskTag;
+ break;
+ case NewAssetType::ParticleEmitter:
+ tag = AssetsImportingManager::CreateParticleEmitterTag;
+ break;
+ case NewAssetType::ParticleSystem:
+ tag = AssetsImportingManager::CreateParticleSystemTag;
+ break;
+ case NewAssetType::SceneAnimation:
+ tag = AssetsImportingManager::CreateSceneAnimationTag;
+ break;
+ case NewAssetType::MaterialFunction:
+ tag = AssetsImportingManager::CreateMaterialFunctionTag;
+ break;
+ case NewAssetType::ParticleEmitterFunction:
+ tag = AssetsImportingManager::CreateParticleEmitterFunctionTag;
+ break;
+ case NewAssetType::AnimationGraphFunction:
+ tag = AssetsImportingManager::CreateAnimationGraphFunctionTag;
+ break;
+ case NewAssetType::Animation:
+ tag = AssetsImportingManager::CreateAnimationTag;
+ break;
+ default:
return true;
-#else
- return false;
-#endif
}
- static bool IsPlayMode()
- {
- return Editor::IsPlayMode;
- }
+ String outputPath;
+ MUtils::ToString(outputPathObj, outputPath);
+ FileSystem::NormalizePath(outputPath);
- static int32 ReadOutputLogs(MonoArray* outMessages, MonoArray* outLogTypes, MonoArray* outLogTimes)
- {
- ScopeLock lock(CachedLogDataLocker);
+ return AssetsImportingManager::Create(tag, outputPath);
+}
- if (CachedLogData.IsEmpty() || CachedLogData.Get() == nullptr)
- return 0;
+DEFINE_INTERNAL_CALL(bool) EditorInternal_CreateVisualScript(MString* outputPathObj, MString* baseTypenameObj)
+{
+ String outputPath;
+ MUtils::ToString(outputPathObj, outputPath);
+ FileSystem::NormalizePath(outputPath);
+ String baseTypename;
+ MUtils::ToString(baseTypenameObj, baseTypename);
+ return AssetsImportingManager::Create(AssetsImportingManager::CreateVisualScriptTag, outputPath, &baseTypename);
+}
- int32 count = 0;
- const int32 maxCount = (int32)mono_array_length(outMessages);
+DEFINE_INTERNAL_CALL(MString*) EditorInternal_CanImport(MString* extensionObj)
+{
+ String extension;
+ MUtils::ToString(extensionObj, extension);
+ if (extension.Length() > 0 && extension[0] == '.')
+ extension.Remove(0, 1);
+ const AssetImporter* importer = AssetsImportingManager::GetImporter(extension);
+ return importer ? MUtils::ToString(importer->ResultExtension) : nullptr;
+}
- byte* ptr = CachedLogData.Get();
- byte* end = ptr + CachedLogData.Count();
- while (count < maxCount && ptr != end)
- {
- auto type = (byte)*(int32*)ptr;
- ptr += 4;
+DEFINE_INTERNAL_CALL(bool) EditorInternal_ImportAudio(MString* inputPathObj, MString* outputPathObj, InternalAudioOptions* optionsObj)
+{
+ ImportAudio::Options options;
+ InternalAudioOptions::Convert(optionsObj, &options);
+ String inputPath, outputPath;
+ MUtils::ToString(inputPathObj, inputPath);
+ MUtils::ToString(outputPathObj, outputPath);
+ return ManagedEditor::Import(inputPath, outputPath, &options);
+}
- auto time = *(int64*)ptr;
- ptr += 8;
+DEFINE_INTERNAL_CALL(void) EditorInternal_GetAudioClipMetadata(AudioClip* clip, int32* originalSize, int32* importedSize)
+{
+ INTERNAL_CALL_CHECK(clip);
+ *originalSize = clip->AudioHeader.OriginalSize;
+ *importedSize = clip->AudioHeader.ImportedSize;
+}
- auto length = *(int32*)ptr;
- ptr += 4;
+DEFINE_INTERNAL_CALL(bool) EditorInternal_SaveJsonAsset(MString* outputPathObj, MString* dataObj, MString* dataTypeNameObj)
+{
+ String outputPath;
+ MUtils::ToString(outputPathObj, outputPath);
+ FileSystem::NormalizePath(outputPath);
- auto msg = (Char*)ptr;
- ptr += length * 2;
+ const StringView dataObjChars = MCore::String::GetChars(dataObj);
+ const StringAsANSI<> data(dataObjChars.Get(), dataObjChars.Length());
+ const StringAnsiView dataAnsi(data.Get(), data.Length());
+
+ const StringView dataTypeNameObjChars = MCore::String::GetChars(dataTypeNameObj);
+ const StringAsANSI<> dataTypeName(dataTypeNameObjChars.Get(), dataTypeNameObjChars.Length());
+ const StringAnsiView dataTypeNameAnsi(dataTypeName.Get(), dataTypeName.Length());
- auto msgObj = MUtils::ToString(StringView(msg, length));
+ return CreateJson::Create(outputPath, dataAnsi, dataTypeNameAnsi);
+}
- mono_array_setref(outMessages, count, msgObj);
- mono_array_set(outLogTypes, byte, count, type);
- mono_array_set(outLogTimes, int64, count, time);
-
- count++;
- }
-
- const int32 dataLeft = (int32)(end - ptr);
- Platform::MemoryCopy(CachedLogData.Get(), ptr, dataLeft);
- CachedLogData.Resize(dataLeft);
-
- return count;
- }
-
- static void SetPlayMode(bool value)
- {
- Editor::IsPlayMode = value;
- }
-
- static MonoString* GetProjectPath()
- {
- return MUtils::ToString(Editor::Project->ProjectPath);
- }
-
- static void CloseSplashScreen()
- {
- Editor::CloseSplashScreen();
- }
-
- static bool CloneAssetFile(MonoString* dstPathObj, MonoString* srcPathObj, Guid* dstId)
- {
- // Get normalized paths
- String dstPath, srcPath;
- MUtils::ToString(dstPathObj, dstPath);
- MUtils::ToString(srcPathObj, srcPath);
- FileSystem::NormalizePath(dstPath);
- FileSystem::NormalizePath(srcPath);
-
- // Call util function
- return Content::CloneAssetFile(dstPath, srcPath, *dstId);
- }
-
- enum class NewAssetType
- {
- Material = 0,
- MaterialInstance = 1,
- CollisionData = 2,
- AnimationGraph = 3,
- SkeletonMask = 4,
- ParticleEmitter = 5,
- ParticleSystem = 6,
- SceneAnimation = 7,
- MaterialFunction = 8,
- ParticleEmitterFunction = 9,
- AnimationGraphFunction = 10,
- Animation = 11,
- };
-
- static bool CreateAsset(NewAssetType type, MonoString* outputPathObj)
- {
- String tag;
- switch (type)
- {
- case NewAssetType::Material:
- tag = AssetsImportingManager::CreateMaterialTag;
- break;
- case NewAssetType::MaterialInstance:
- tag = AssetsImportingManager::CreateMaterialInstanceTag;
- break;
- case NewAssetType::CollisionData:
- tag = AssetsImportingManager::CreateCollisionDataTag;
- break;
- case NewAssetType::AnimationGraph:
- tag = AssetsImportingManager::CreateAnimationGraphTag;
- break;
- case NewAssetType::SkeletonMask:
- tag = AssetsImportingManager::CreateSkeletonMaskTag;
- break;
- case NewAssetType::ParticleEmitter:
- tag = AssetsImportingManager::CreateParticleEmitterTag;
- break;
- case NewAssetType::ParticleSystem:
- tag = AssetsImportingManager::CreateParticleSystemTag;
- break;
- case NewAssetType::SceneAnimation:
- tag = AssetsImportingManager::CreateSceneAnimationTag;
- break;
- case NewAssetType::MaterialFunction:
- tag = AssetsImportingManager::CreateMaterialFunctionTag;
- break;
- case NewAssetType::ParticleEmitterFunction:
- tag = AssetsImportingManager::CreateParticleEmitterFunctionTag;
- break;
- case NewAssetType::AnimationGraphFunction:
- tag = AssetsImportingManager::CreateAnimationGraphFunctionTag;
- break;
- case NewAssetType::Animation:
- tag = AssetsImportingManager::CreateAnimationTag;
- break;
- default:
- return true;
- }
-
- String outputPath;
- MUtils::ToString(outputPathObj, outputPath);
- FileSystem::NormalizePath(outputPath);
-
- return AssetsImportingManager::Create(tag, outputPath);
- }
-
- static bool CreateVisualScript(MonoString* outputPathObj, MonoString* baseTypenameObj)
- {
- String outputPath;
- MUtils::ToString(outputPathObj, outputPath);
- FileSystem::NormalizePath(outputPath);
- String baseTypename;
- MUtils::ToString(baseTypenameObj, baseTypename);
- return AssetsImportingManager::Create(AssetsImportingManager::CreateVisualScriptTag, outputPath, &baseTypename);
- }
-
- static MonoString* CanImport(MonoString* extensionObj)
- {
- String extension;
- MUtils::ToString(extensionObj, extension);
- if (extension.Length() > 0 && extension[0] == '.')
- extension.Remove(0, 1);
- const AssetImporter* importer = AssetsImportingManager::GetImporter(extension);
- return importer ? MUtils::ToString(importer->ResultExtension) : nullptr;
- }
-
- static bool Import(MonoString* inputPathObj, MonoString* outputPathObj, void* arg)
- {
- String inputPath, outputPath;
- MUtils::ToString(inputPathObj, inputPath);
- MUtils::ToString(outputPathObj, outputPath);
- FileSystem::NormalizePath(inputPath);
- FileSystem::NormalizePath(outputPath);
-
- return AssetsImportingManager::Import(inputPath, outputPath, arg);
- }
-
- static bool ImportTexture(MonoString* inputPathObj, MonoString* outputPathObj, InternalTextureOptions* optionsObj)
- {
- ImportTexture::Options options;
- InternalTextureOptions::Convert(optionsObj, &options);
-
- return Import(inputPathObj, outputPathObj, &options);
- }
-
- static bool ImportModel(MonoString* inputPathObj, MonoString* outputPathObj, InternalModelOptions* optionsObj)
- {
- ImportModelFile::Options options;
- InternalModelOptions::Convert(optionsObj, &options);
-
- return Import(inputPathObj, outputPathObj, &options);
- }
-
- static bool ImportAudio(MonoString* inputPathObj, MonoString* outputPathObj, InternalAudioOptions* optionsObj)
- {
- ImportAudio::Options options;
- InternalAudioOptions::Convert(optionsObj, &options);
-
- return Import(inputPathObj, outputPathObj, &options);
- }
-
- static void GetAudioClipMetadata(AudioClip* clip, int32* originalSize, int32* importedSize)
- {
- INTERNAL_CALL_CHECK(clip);
- *originalSize = clip->AudioHeader.OriginalSize;
- *importedSize = clip->AudioHeader.ImportedSize;
- }
-
- static bool SaveJsonAsset(MonoString* outputPathObj, MonoString* dataObj, MonoString* dataTypeNameObj)
- {
- String outputPath;
- MUtils::ToString(outputPathObj, outputPath);
- FileSystem::NormalizePath(outputPath);
-
- const auto dataObjPtr = mono_string_to_utf8(dataObj);
- StringAnsiView data(dataObjPtr);
-
- const auto dataTypeNameObjPtr = mono_string_to_utf8(dataTypeNameObj);
- StringAnsiView dataTypeName(dataTypeNameObjPtr);
-
- const bool result = CreateJson::Create(outputPath, data, dataTypeName);
-
- mono_free(dataObjPtr);
- mono_free(dataTypeNameObjPtr);
-
- return result;
- }
-
- static bool GetTextureImportOptions(MonoString* pathObj, InternalTextureOptions* result)
- {
- String path;
- MUtils::ToString(pathObj, path);
- FileSystem::NormalizePath(path);
-
- ImportTexture::Options options;
- if (ImportTexture::TryGetImportOptions(path, options))
- {
- // Convert into managed storage
- InternalTextureOptions::Convert(&options, result);
-
- return true;
- }
-
- return false;
- }
-
- static void GetModelImportOptions(MonoString* pathObj, InternalModelOptions* result)
- {
- // Initialize defaults
- ImportModelFile::Options options;
- if (const auto* graphicsSettings = GraphicsSettings::Get())
- {
- options.GenerateSDF = graphicsSettings->GenerateSDFOnModelImport;
- }
-
- // Get options from model
- String path;
- MUtils::ToString(pathObj, path);
- FileSystem::NormalizePath(path);
- ImportModelFile::TryGetImportOptions(path, options);
-
- // Convert into managed storage
- InternalModelOptions::Convert(&options, result);
- }
-
- static bool GetAudioImportOptions(MonoString* pathObj, InternalAudioOptions* result)
- {
- String path;
- MUtils::ToString(pathObj, path);
- FileSystem::NormalizePath(path);
-
- ImportAudio::Options options;
- if (ImportAudio::TryGetImportOptions(path, options))
- {
- // Convert into managed storage
- InternalAudioOptions::Convert(&options, result);
-
- return true;
- }
-
- return false;
- }
-
- static bool CanExport(MonoString* pathObj)
- {
+DEFINE_INTERNAL_CALL(bool) EditorInternal_CanExport(MString* pathObj)
+{
#if COMPILE_WITH_ASSETS_EXPORTER
- String path;
- MUtils::ToString(pathObj, path);
- FileSystem::NormalizePath(path);
+ String path;
+ MUtils::ToString(pathObj, path);
+ FileSystem::NormalizePath(path);
- return AssetsExportingManager::CanExport(path);
+ return AssetsExportingManager::CanExport(path);
#else
- return false;
+ return false;
#endif
- }
+}
- static bool Export(MonoString* inputPathObj, MonoString* outputFolderObj)
- {
+DEFINE_INTERNAL_CALL(bool) EditorInternal_Export(MString* inputPathObj, MString* outputFolderObj)
+{
#if COMPILE_WITH_ASSETS_EXPORTER
- String inputPath;
- MUtils::ToString(inputPathObj, inputPath);
- FileSystem::NormalizePath(inputPath);
+ String inputPath;
+ MUtils::ToString(inputPathObj, inputPath);
+ FileSystem::NormalizePath(inputPath);
- String outputFolder;
- MUtils::ToString(outputFolderObj, outputFolder);
- FileSystem::NormalizePath(outputFolder);
+ String outputFolder;
+ MUtils::ToString(outputFolderObj, outputFolder);
+ FileSystem::NormalizePath(outputFolder);
- return AssetsExportingManager::Export(inputPath, outputFolder);
+ return AssetsExportingManager::Export(inputPath, outputFolder);
#else
- return false;
+ return false;
#endif
- }
+}
- static void CopyCache(Guid* dstId, Guid* srcId)
- {
- ShaderCacheManager::CopyCache(*dstId, *srcId);
- }
+DEFINE_INTERNAL_CALL(void) EditorInternal_CopyCache(Guid* dstId, Guid* srcId)
+{
+ ShaderCacheManager::CopyCache(*dstId, *srcId);
+}
- static void BakeLightmaps(bool cancel)
- {
- auto builder = ShadowsOfMordor::Builder::Instance();
- if (cancel)
- builder->CancelBuild();
- else
- builder->Build();
- }
+DEFINE_INTERNAL_CALL(void) EditorInternal_BakeLightmaps(bool cancel)
+{
+ auto builder = ShadowsOfMordor::Builder::Instance();
+ if (cancel)
+ builder->CancelBuild();
+ else
+ builder->Build();
+}
- static MonoString* GetShaderAssetSourceCode(BinaryAsset* obj)
- {
- INTERNAL_CALL_CHECK_RETURN(obj, nullptr);
- if (obj->WaitForLoaded())
- DebugLog::ThrowNullReference();
+DEFINE_INTERNAL_CALL(MString*) EditorInternal_GetShaderAssetSourceCode(BinaryAsset* obj)
+{
+ INTERNAL_CALL_CHECK_RETURN(obj, nullptr);
+ if (obj->WaitForLoaded())
+ DebugLog::ThrowNullReference();
- auto lock = obj->Storage->Lock();
+ auto lock = obj->Storage->Lock();
- if (obj->LoadChunk(SHADER_FILE_CHUNK_SOURCE))
- return nullptr;
+ if (obj->LoadChunk(SHADER_FILE_CHUNK_SOURCE))
+ return nullptr;
- BytesContainer data;
- obj->GetChunkData(SHADER_FILE_CHUNK_SOURCE, data);
+ BytesContainer data;
+ obj->GetChunkData(SHADER_FILE_CHUNK_SOURCE, data);
- Encryption::DecryptBytes((byte*)data.Get(), data.Length());
+ Encryption::DecryptBytes((byte*)data.Get(), data.Length());
- const StringAnsiView srcData((const char*)data.Get(), data.Length());
- const String source(srcData);
- const auto str = MUtils::ToString(source);
+ const StringAnsiView srcData((const char*)data.Get(), data.Length());
+ const String source(srcData);
+ const auto str = MUtils::ToString(source);
- Encryption::EncryptBytes((byte*)data.Get(), data.Length());
+ Encryption::EncryptBytes((byte*)data.Get(), data.Length());
- return str;
- }
+ return str;
+}
- static bool CookMeshCollision(MonoString* pathObj, CollisionDataType type, ModelBase* modelObj, int32 modelLodIndex, uint32 materialSlotsMask, ConvexMeshGenerationFlags convexFlags, int32 convexVertexLimit)
- {
+DEFINE_INTERNAL_CALL(bool) EditorInternal_CookMeshCollision(MString* pathObj, CollisionDataType type, ModelBase* modelObj, int32 modelLodIndex, uint32 materialSlotsMask, ConvexMeshGenerationFlags convexFlags, int32 convexVertexLimit)
+{
#if COMPILE_WITH_PHYSICS_COOKING
- CollisionCooking::Argument arg;
- String path;
- MUtils::ToString(pathObj, path);
- FileSystem::NormalizePath(path);
- arg.Type = type;
- arg.Model = modelObj;
- arg.ModelLodIndex = modelLodIndex;
- arg.MaterialSlotsMask = materialSlotsMask;
- arg.ConvexFlags = convexFlags;
- arg.ConvexVertexLimit = convexVertexLimit;
- return CreateCollisionData::CookMeshCollision(path, arg);
+ CollisionCooking::Argument arg;
+ String path;
+ MUtils::ToString(pathObj, path);
+ FileSystem::NormalizePath(path);
+ arg.Type = type;
+ arg.Model = modelObj;
+ arg.ModelLodIndex = modelLodIndex;
+ arg.MaterialSlotsMask = materialSlotsMask;
+ arg.ConvexFlags = convexFlags;
+ arg.ConvexVertexLimit = convexVertexLimit;
+ return CreateCollisionData::CookMeshCollision(path, arg);
#else
- LOG(Warning, "Collision cooking is disabled.");
- return true;
+ LOG(Warning, "Collision cooking is disabled.");
+ return true;
#endif
- }
+}
- static void GetCollisionWires(CollisionData* collisionData, MonoArray** triangles, MonoArray** indices)
+DEFINE_INTERNAL_CALL(void) EditorInternal_GetCollisionWires(CollisionData* collisionData, MArray** triangles, MArray** indices, int* trianglesCount, int* indicesCount)
+{
+ if (!collisionData || collisionData->WaitForLoaded() || collisionData->GetOptions().Type == CollisionDataType::None)
+ return;
+
+ const auto& debugLines = collisionData->GetDebugLines();
+
+ const int32 linesCount = debugLines.Count() / 2;
+ MCore::GC::WriteRef(triangles, (MObject*)MCore::Array::New(Float3::TypeInitializer.GetClass(), debugLines.Count()));
+ MCore::GC::WriteRef(indices, (MObject*)MCore::Array::New( MCore::TypeCache::Int32, linesCount * 3));
+
+ // Use one triangle per debug line
+ Platform::MemoryCopy(MCore::Array::GetAddress(*triangles), debugLines.Get(), debugLines.Count() * sizeof(Float3));
+ int32 iI = 0;
+ int32* indicesPtr = MCore::Array::GetAddress(*indices);
+ for (int32 i = 0; i < debugLines.Count(); i += 2)
{
- if (!collisionData || collisionData->WaitForLoaded() || collisionData->GetOptions().Type == CollisionDataType::None)
- return;
-
- const auto& debugLines = collisionData->GetDebugLines();
-
- const int32 linesCount = debugLines.Count() / 2;
- mono_gc_wbarrier_generic_store(triangles, (MonoObject*)mono_array_new(mono_domain_get(), Float3::TypeInitializer.GetMonoClass(), debugLines.Count()));
- mono_gc_wbarrier_generic_store(indices, (MonoObject*)mono_array_new(mono_domain_get(), mono_get_int32_class(), linesCount * 3));
-
- // Use one triangle per debug line
- for (int32 i = 0; i < debugLines.Count(); i++)
- {
- mono_array_set(*triangles, Float3, i, debugLines[i]);
- }
- int32 iI = 0;
- for (int32 i = 0; i < debugLines.Count(); i += 2)
- {
- mono_array_set(*indices, int32, iI++, i);
- mono_array_set(*indices, int32, iI++, i + 1);
- mono_array_set(*indices, int32, iI++, i);
- }
+ indicesPtr[iI++] = i;
+ indicesPtr[iI++] = i + 1;
+ indicesPtr[iI++] = i;
}
+ *trianglesCount = debugLines.Count();
+ *indicesCount = linesCount * 3;
+}
- static void GetEditorBoxWithChildren(Actor* obj, BoundingBox* result)
- {
- INTERNAL_CALL_CHECK(obj);
- *result = obj->GetEditorBoxChildren();
- }
+DEFINE_INTERNAL_CALL(void) EditorInternal_GetEditorBoxWithChildren(Actor* obj, BoundingBox* result)
+{
+ INTERNAL_CALL_CHECK(obj);
+ *result = obj->GetEditorBoxChildren();
+}
- static void SetOptions(ManagedEditor::InternalOptions* options)
- {
- ManagedEditor::ManagedEditorOptions = *options;
+DEFINE_INTERNAL_CALL(void) EditorInternal_SetOptions(ManagedEditor::InternalOptions* options)
+{
+ ManagedEditor::ManagedEditorOptions = *options;
- // Apply options
- AssetsImportingManager::UseImportPathRelative = ManagedEditor::ManagedEditorOptions.UseAssetImportPathRelative != 0;
- }
+ // Apply options
+ AssetsImportingManager::UseImportPathRelative = ManagedEditor::ManagedEditorOptions.UseAssetImportPathRelative != 0;
+}
- static void DrawNavMesh()
- {
- Navigation::DrawNavMesh();
- }
+DEFINE_INTERNAL_CALL(void) EditorInternal_DrawNavMesh()
+{
+ Navigation::DrawNavMesh();
+}
- static bool GetIsEveryAssemblyLoaded()
- {
- return Scripting::IsEveryAssemblyLoaded();
- }
+DEFINE_INTERNAL_CALL(bool) EditorInternal_GetIsEveryAssemblyLoaded()
+{
+ return Scripting::IsEveryAssemblyLoaded();
+}
- static int32 GetLastProjectOpenedEngineBuild()
- {
- return Editor::LastProjectOpenedEngineBuild;
- }
+DEFINE_INTERNAL_CALL(int32) EditorInternal_GetLastProjectOpenedEngineBuild()
+{
+ return Editor::LastProjectOpenedEngineBuild;
+}
- static bool GetIsCSGActive()
- {
+DEFINE_INTERNAL_CALL(bool) EditorInternal_GetIsCSGActive()
+{
#if COMPILE_WITH_CSG_BUILDER
- return CSG::Builder::IsActive();
+ return CSG::Builder::IsActive();
#else
- return false;
+ return false;
#endif
- }
+}
- static void RunVisualScriptBreakpointLoopTick(float deltaTime)
+DEFINE_INTERNAL_CALL(void) EditorInternal_RunVisualScriptBreakpointLoopTick(float deltaTime)
+{
+ // Update
+ Platform::Tick();
+ Engine::HasFocus = (Engine::MainWindow && Engine::MainWindow->IsFocused()) || Platform::GetHasFocus();
+ if (Engine::HasFocus)
{
- // Update
- Platform::Tick();
- Engine::HasFocus = (Engine::MainWindow && Engine::MainWindow->IsFocused()) || Platform::GetHasFocus();
- if (Engine::HasFocus)
+ InputDevice::EventQueue inputEvents;
+ if (Input::Mouse)
{
- InputDevice::EventQueue inputEvents;
- if (Input::Mouse)
+ if (Input::Mouse->Update(inputEvents))
{
- if (Input::Mouse->Update(inputEvents))
- {
- Input::Mouse->DeleteObject();
- Input::Mouse = nullptr;
- }
+ Input::Mouse->DeleteObject();
+ Input::Mouse = nullptr;
}
- if (Input::Keyboard)
+ }
+ if (Input::Keyboard)
+ {
+ if (Input::Keyboard->Update(inputEvents))
{
- if (Input::Keyboard->Update(inputEvents))
- {
- Input::Keyboard->DeleteObject();
- Input::Keyboard = nullptr;
- }
+ Input::Keyboard->DeleteObject();
+ Input::Keyboard = nullptr;
}
- WindowsManager::WindowsLocker.Lock();
- Window* defaultWindow = nullptr;
- for (auto window : WindowsManager::Windows)
- {
- if (window->IsFocused() && window->GetSettings().AllowInput)
- {
- defaultWindow = window;
- break;
- }
- }
- for (const auto& e : inputEvents)
- {
- auto window = e.Target ? e.Target : defaultWindow;
- if (!window)
- continue;
- switch (e.Type)
- {
- // Keyboard events
- case InputDevice::EventType::Char:
- window->OnCharInput(e.CharData.Char);
- break;
- case InputDevice::EventType::KeyDown:
- window->OnKeyDown(e.KeyData.Key);
- break;
- case InputDevice::EventType::KeyUp:
- window->OnKeyUp(e.KeyData.Key);
- break;
- // Mouse events
- case InputDevice::EventType::MouseDown:
- window->OnMouseDown(window->ScreenToClient(e.MouseData.Position), e.MouseData.Button);
- break;
- case InputDevice::EventType::MouseUp:
- window->OnMouseUp(window->ScreenToClient(e.MouseData.Position), e.MouseData.Button);
- break;
- case InputDevice::EventType::MouseDoubleClick:
- window->OnMouseDoubleClick(window->ScreenToClient(e.MouseData.Position), e.MouseData.Button);
- break;
- case InputDevice::EventType::MouseWheel:
- window->OnMouseWheel(window->ScreenToClient(e.MouseWheelData.Position), e.MouseWheelData.WheelDelta);
- break;
- case InputDevice::EventType::MouseMove:
- window->OnMouseMove(window->ScreenToClient(e.MouseData.Position));
- break;
- case InputDevice::EventType::MouseLeave:
- window->OnMouseLeave();
- break;
- }
- }
- WindowsManager::WindowsLocker.Unlock();
}
WindowsManager::WindowsLocker.Lock();
- for (auto& win : WindowsManager::Windows)
+ Window* defaultWindow = nullptr;
+ for (auto window : WindowsManager::Windows)
{
- if (win->IsVisible())
- win->OnUpdate(deltaTime);
+ if (window->IsFocused() && window->GetSettings().AllowInput)
+ {
+ defaultWindow = window;
+ break;
+ }
+ }
+ for (const auto& e : inputEvents)
+ {
+ auto window = e.Target ? e.Target : defaultWindow;
+ if (!window)
+ continue;
+ switch (e.Type)
+ {
+ // Keyboard events
+ case InputDevice::EventType::Char:
+ window->OnCharInput(e.CharData.Char);
+ break;
+ case InputDevice::EventType::KeyDown:
+ window->OnKeyDown(e.KeyData.Key);
+ break;
+ case InputDevice::EventType::KeyUp:
+ window->OnKeyUp(e.KeyData.Key);
+ break;
+ // Mouse events
+ case InputDevice::EventType::MouseDown:
+ window->OnMouseDown(window->ScreenToClient(e.MouseData.Position), e.MouseData.Button);
+ break;
+ case InputDevice::EventType::MouseUp:
+ window->OnMouseUp(window->ScreenToClient(e.MouseData.Position), e.MouseData.Button);
+ break;
+ case InputDevice::EventType::MouseDoubleClick:
+ window->OnMouseDoubleClick(window->ScreenToClient(e.MouseData.Position), e.MouseData.Button);
+ break;
+ case InputDevice::EventType::MouseWheel:
+ window->OnMouseWheel(window->ScreenToClient(e.MouseWheelData.Position), e.MouseWheelData.WheelDelta);
+ break;
+ case InputDevice::EventType::MouseMove:
+ window->OnMouseMove(window->ScreenToClient(e.MouseData.Position));
+ break;
+ case InputDevice::EventType::MouseLeave:
+ window->OnMouseLeave();
+ break;
+ }
}
WindowsManager::WindowsLocker.Unlock();
-
- // Draw
- Engine::OnDraw();
}
-
- struct VisualScriptLocalManaged
+ WindowsManager::WindowsLocker.Lock();
+ for (auto& win : WindowsManager::Windows)
{
- MonoString* Value;
- MonoString* ValueTypeName;
- uint32 NodeId;
- int32 BoxId;
- };
-
- static MonoArray* GetVisualScriptLocals()
- {
- MonoArray* result = nullptr;
- const auto stack = VisualScripting::GetThreadStackTop();
- if (stack && stack->Scope)
- {
- const int32 count = stack->Scope->Parameters.Length() + stack->Scope->ReturnedValues.Count();
- const auto mclass = ((NativeBinaryModule*)GetBinaryModuleFlaxEngine())->Assembly->GetClass("FlaxEditor.Editor+VisualScriptLocal");
- ASSERT(mclass);
- result = mono_array_new(mono_domain_get(), mclass->GetNative(), count);
- VisualScriptLocalManaged local;
- local.NodeId = MAX_uint32;
- if (stack->Scope->Parameters.Length() != 0)
- {
- auto s = stack;
- while (s->PreviousFrame && s->PreviousFrame->Scope == stack->Scope)
- s = s->PreviousFrame;
- if (s)
- local.NodeId = s->Node->ID;
- }
- for (int32 i = 0; i < stack->Scope->Parameters.Length(); i++)
- {
- auto& v = stack->Scope->Parameters[i];
- local.BoxId = i + 1;
- local.Value = MUtils::ToString(v.ToString());
- local.ValueTypeName = MUtils::ToString(v.Type.GetTypeName());
- mono_array_set(result, VisualScriptLocalManaged, i, local);
- }
- for (int32 i = 0; i < stack->Scope->ReturnedValues.Count(); i++)
- {
- auto& v = stack->Scope->ReturnedValues[i];
- local.NodeId = v.NodeId;
- local.BoxId = v.BoxId;
- local.Value = MUtils::ToString(v.Value.ToString());
- local.ValueTypeName = MUtils::ToString(v.Value.Type.GetTypeName());
- mono_array_set(result, VisualScriptLocalManaged, stack->Scope->Parameters.Length() + i, local);
- }
- }
- return result;
+ if (win->IsVisible())
+ win->OnUpdate(deltaTime);
}
+ WindowsManager::WindowsLocker.Unlock();
- struct VisualScriptStackFrameManaged
- {
- MonoObject* Script;
- uint32 NodeId;
- int32 BoxId;
- };
+ // Draw
+ Engine::OnDraw();
+}
- static MonoArray* GetVisualScriptStackFrames()
- {
- MonoArray* result = nullptr;
- const auto stack = VisualScripting::GetThreadStackTop();
- if (stack)
- {
- int32 count = 0;
- auto s = stack;
- while (s)
- {
- s = s->PreviousFrame;
- count++;
- }
- const auto mclass = ((NativeBinaryModule*)GetBinaryModuleFlaxEngine())->Assembly->GetClass("FlaxEditor.Editor+VisualScriptStackFrame");
- ASSERT(mclass);
- result = mono_array_new(mono_domain_get(), mclass->GetNative(), count);
- s = stack;
- count = 0;
- while (s)
- {
- VisualScriptStackFrameManaged frame;
- frame.Script = s->Script->GetOrCreateManagedInstance();
- frame.NodeId = s->Node->ID;
- frame.BoxId = s->Box ? s->Box->ID : MAX_uint32;
- mono_array_set(result, VisualScriptStackFrameManaged, count, frame);
- s = s->PreviousFrame;
- count++;
- }
- }
- return result;
- }
+struct VisualScriptLocalManaged
+{
+ MString* Value;
+ MString* ValueTypeName;
+ uint32 NodeId;
+ int32 BoxId;
+};
- static VisualScriptStackFrameManaged GetVisualScriptPreviousScopeFrame()
+DEFINE_INTERNAL_CALL(MArray*) EditorInternal_GetVisualScriptLocals(int* localsCount)
+{
+ MArray* result = nullptr;
+ *localsCount = 0;
+ const auto stack = VisualScripting::GetThreadStackTop();
+ if (stack && stack->Scope)
{
- VisualScriptStackFrameManaged frame;
- Platform::MemoryClear(&frame, sizeof(frame));
- const auto stack = VisualScripting::GetThreadStackTop();
- if (stack)
+ const int32 count = stack->Scope->Parameters.Length() + stack->Scope->ReturnedValues.Count();
+ const MClass* mclass = ((NativeBinaryModule*)GetBinaryModuleFlaxEngine())->Assembly->GetClass("FlaxEditor.Editor+VisualScriptLocal");
+ ASSERT(mclass);
+ result = MCore::Array::New( mclass, count);
+ VisualScriptLocalManaged local;
+ local.NodeId = MAX_uint32;
+ if (stack->Scope->Parameters.Length() != 0)
{
auto s = stack;
while (s->PreviousFrame && s->PreviousFrame->Scope == stack->Scope)
s = s->PreviousFrame;
- if (s && s->PreviousFrame)
- {
- s = s->PreviousFrame;
- frame.Script = s->Script->GetOrCreateManagedInstance();
- frame.NodeId = s->Node->ID;
- frame.BoxId = s->Box ? s->Box->ID : MAX_uint32;
- }
+ if (s)
+ local.NodeId = s->Node->ID;
}
- return frame;
+ VisualScriptLocalManaged* resultPtr = MCore::Array::GetAddress(result);
+ for (int32 i = 0; i < stack->Scope->Parameters.Length(); i++)
+ {
+ auto& v = stack->Scope->Parameters[i];
+ local.BoxId = i + 1;
+ local.Value = MUtils::ToString(v.ToString());
+ local.ValueTypeName = MUtils::ToString(v.Type.GetTypeName());
+ resultPtr[i] = local;
+ }
+ for (int32 i = 0; i < stack->Scope->ReturnedValues.Count(); i++)
+ {
+ auto& v = stack->Scope->ReturnedValues[i];
+ local.NodeId = v.NodeId;
+ local.BoxId = v.BoxId;
+ local.Value = MUtils::ToString(v.Value.ToString());
+ local.ValueTypeName = MUtils::ToString(v.Value.Type.GetTypeName());
+ resultPtr[stack->Scope->Parameters.Length() + i] = local;
+ }
+ *localsCount = count;
+ }
+ return result;
+}
+
+struct VisualScriptStackFrameManaged
+{
+ MObject* Script;
+ uint32 NodeId;
+ int32 BoxId;
+};
+
+DEFINE_INTERNAL_CALL(MArray*) EditorInternal_GetVisualScriptStackFrames(int* stackFramesCount)
+{
+ MArray* result = nullptr;
+ *stackFramesCount = 0;
+ const auto stack = VisualScripting::GetThreadStackTop();
+ if (stack)
+ {
+ int32 count = 0;
+ auto s = stack;
+ while (s)
+ {
+ s = s->PreviousFrame;
+ count++;
+ }
+ const MClass* mclass = ((NativeBinaryModule*)GetBinaryModuleFlaxEngine())->Assembly->GetClass("FlaxEditor.Editor+VisualScriptStackFrame");
+ ASSERT(mclass);
+ result = MCore::Array::New( mclass, count);
+ VisualScriptStackFrameManaged* resultPtr = MCore::Array::GetAddress(result);
+ s = stack;
+ count = 0;
+ while (s)
+ {
+ VisualScriptStackFrameManaged frame;
+ frame.Script = s->Script->GetOrCreateManagedInstance();
+ frame.NodeId = s->Node->ID;
+ frame.BoxId = s->Box ? s->Box->ID : MAX_uint32;
+ resultPtr[count] = frame;
+ s = s->PreviousFrame;
+ count++;
+ }
+ *stackFramesCount = count;
+ }
+ return result;
+}
+
+DEFINE_INTERNAL_CALL(VisualScriptStackFrameManaged) EditorInternal_GetVisualScriptPreviousScopeFrame()
+{
+ VisualScriptStackFrameManaged frame;
+ Platform::MemoryClear(&frame, sizeof(frame));
+ const auto stack = VisualScripting::GetThreadStackTop();
+ if (stack)
+ {
+ auto s = stack;
+ while (s->PreviousFrame && s->PreviousFrame->Scope == stack->Scope)
+ s = s->PreviousFrame;
+ if (s && s->PreviousFrame)
+ {
+ s = s->PreviousFrame;
+ frame.Script = s->Script->GetOrCreateManagedInstance();
+ frame.NodeId = s->Node->ID;
+ frame.BoxId = s->Box ? s->Box->ID : MAX_uint32;
+ }
+ }
+ return frame;
+}
+
+DEFINE_INTERNAL_CALL(bool) EditorInternal_EvaluateVisualScriptLocal(VisualScript* script, VisualScriptLocalManaged* local)
+{
+ Variant v;
+ if (VisualScripting::Evaluate(script, VisualScripting::GetThreadStackTop()->Instance, local->NodeId, local->BoxId, v))
+ {
+ local->Value = MUtils::ToString(v.ToString());
+ local->ValueTypeName = MUtils::ToString(v.Type.GetTypeName());
+ return true;
+ }
+ return false;
+}
+
+DEFINE_INTERNAL_CALL(void) EditorInternal_DeserializeSceneObject(SceneObject* sceneObject, MString* jsonObj)
+{
+ PROFILE_CPU_NAMED("DeserializeSceneObject");
+
+ StringAnsi json;
+ MUtils::ToString(jsonObj, json);
+
+ rapidjson_flax::Document document;
+ {
+ PROFILE_CPU_NAMED("Json.Parse");
+ document.Parse(json.Get(), json.Length());
+ }
+ if (document.HasParseError())
+ {
+ Log::JsonParseException(document.GetParseError(), document.GetErrorOffset());
+ DebugLog::ThrowException("Failed to parse Json.");
}
- static bool EvaluateVisualScriptLocal(VisualScript* script, VisualScriptLocalManaged* local)
+ auto modifier = Cache::ISerializeModifier.Get();
+ modifier->EngineBuild = FLAXENGINE_VERSION_BUILD;
+ Scripting::ObjectsLookupIdMapping.Set(&modifier.Value->IdsMapping);
+
{
- Variant v;
- if (VisualScripting::Evaluate(script, VisualScripting::GetThreadStackTop()->Instance, local->NodeId, local->BoxId, v))
- {
- local->Value = MUtils::ToString(v.ToString());
- local->ValueTypeName = MUtils::ToString(v.Type.GetTypeName());
- return true;
- }
+ PROFILE_CPU_NAMED("Deserialize");
+ sceneObject->Deserialize(document, modifier.Value);
+ }
+}
+
+DEFINE_INTERNAL_CALL(void) EditorInternal_LoadAsset(Guid* id)
+{
+ Content::LoadAsync(*id);
+}
+
+DEFINE_INTERNAL_CALL(bool) EditorInternal_CanSetToRoot(Prefab* prefab, Actor* targetActor)
+{
+ // Reference: Prefab::ApplyAll(Actor* targetActor)
+ if (targetActor->GetPrefabID() != prefab->GetID())
return false;
- }
-
- static void DeserializeSceneObject(SceneObject* sceneObject, MonoString* jsonObj)
+ if (targetActor->GetPrefabObjectID() != prefab->GetRootObjectId())
{
- PROFILE_CPU_NAMED("DeserializeSceneObject");
-
- StringAnsi json;
- MUtils::ToString(jsonObj, json);
-
- rapidjson_flax::Document document;
- {
- PROFILE_CPU_NAMED("Json.Parse");
- document.Parse(json.Get(), json.Length());
- }
- if (document.HasParseError())
- {
- Log::JsonParseException(document.GetParseError(), document.GetErrorOffset());
- DebugLog::ThrowException("Failed to parse Json.");
- }
-
- auto modifier = Cache::ISerializeModifier.Get();
- modifier->EngineBuild = FLAXENGINE_VERSION_BUILD;
- Scripting::ObjectsLookupIdMapping.Set(&modifier.Value->IdsMapping);
-
- {
- PROFILE_CPU_NAMED("Deserialize");
- sceneObject->Deserialize(document, modifier.Value);
- }
- }
-
- static void LoadAsset(Guid* id)
- {
- Content::LoadAsync(*id);
- }
-
- static bool CanSetToRoot(Prefab* prefab, Actor* targetActor)
- {
- // Reference: Prefab::ApplyAll(Actor* targetActor)
- if (targetActor->GetPrefabID() != prefab->GetID())
+ const ISerializable::DeserializeStream** newRootDataPtr = prefab->ObjectsDataCache.TryGet(targetActor->GetPrefabObjectID());
+ if (!newRootDataPtr || !*newRootDataPtr)
return false;
- if (targetActor->GetPrefabObjectID() != prefab->GetRootObjectId())
+ const ISerializable::DeserializeStream& newRootData = **newRootDataPtr;
+ Guid prefabId, prefabObjectID;
+ if (JsonTools::GetGuidIfValid(prefabId, newRootData, "PrefabID") && JsonTools::GetGuidIfValid(prefabObjectID, newRootData, "PrefabObjectID"))
{
- const ISerializable::DeserializeStream** newRootDataPtr = prefab->ObjectsDataCache.TryGet(targetActor->GetPrefabObjectID());
- if (!newRootDataPtr || !*newRootDataPtr)
+ const auto nestedPrefab = Content::Load(prefabId);
+ if (nestedPrefab && nestedPrefab->GetRootObjectId() != prefabObjectID)
return false;
- const ISerializable::DeserializeStream& newRootData = **newRootDataPtr;
- Guid prefabId, prefabObjectID;
- if (JsonTools::GetGuidIfValid(prefabId, newRootData, "PrefabID") && JsonTools::GetGuidIfValid(prefabObjectID, newRootData, "PrefabObjectID"))
- {
- const auto nestedPrefab = Content::Load(prefabId);
- if (nestedPrefab && nestedPrefab->GetRootObjectId() != prefabObjectID)
- return false;
- }
}
+ }
+ return true;
+}
+
+DEFINE_INTERNAL_CALL(float) EditorInternal_GetAnimationTime(AnimatedModel* animatedModel)
+{
+ return animatedModel && animatedModel->GraphInstance.State.Count() == 1 ? animatedModel->GraphInstance.State[0].Animation.TimePosition : 0.0f;
+}
+
+DEFINE_INTERNAL_CALL(void) EditorInternal_SetAnimationTime(AnimatedModel* animatedModel, float time)
+{
+ if (animatedModel && animatedModel->GraphInstance.State.Count() == 1)
+ animatedModel->GraphInstance.State[0].Animation.TimePosition = time;
+}
+
+DEFINE_INTERNAL_CALL(MTypeObject*) CustomEditorsUtilInternal_GetCustomEditor(MTypeObject* targetType)
+{
+ return CustomEditorsUtil::GetCustomEditor(targetType);
+}
+
+DEFINE_INTERNAL_CALL(bool) AudioImportEntryInternal_GetAudioImportOptions(MString* pathObj, InternalAudioOptions* result)
+{
+ String path;
+ MUtils::ToString(pathObj, path);
+ FileSystem::NormalizePath(path);
+
+ ImportAudio::Options options;
+ if (ImportAudio::TryGetImportOptions(path, options))
+ {
+ // Convert into managed storage
+ InternalAudioOptions::Convert(&options, result);
+
return true;
}
- static float GetAnimationTime(AnimatedModel* animatedModel)
+ return false;
+}
+
+DEFINE_INTERNAL_CALL(MArray*) LayersAndTagsSettingsInternal_GetCurrentLayers(int* layersCount)
+{
+ *layersCount = Math::Max(1, Level::GetNonEmptyLayerNamesCount());
+ return MUtils::ToArray(Span(Level::Layers, *layersCount));
+}
+
+DEFINE_INTERNAL_CALL(void) GameSettingsInternal_Apply()
+{
+ LOG(Info, "Apply game settings");
+ GameSettings::Load();
+}
+
+bool ManagedEditor::Import(String inputPath, String outputPath, void* arg)
+{
+ FileSystem::NormalizePath(inputPath);
+ FileSystem::NormalizePath(outputPath);
+ return AssetsImportingManager::Import(inputPath, outputPath, arg);
+}
+
+bool ManagedEditor::Import(const String& inputPath, const String& outputPath, const TextureTool::Options& options)
+{
+ return Import(inputPath, outputPath, (void*)&options);
+}
+
+bool ManagedEditor::TryRestoreImportOptions(TextureTool::Options& options, String assetPath)
+{
+ FileSystem::NormalizePath(assetPath);
+ return ImportTexture::TryGetImportOptions(assetPath, options);
+}
+
+bool ManagedEditor::Import(const String& inputPath, const String& outputPath, const ModelTool::Options& options)
+{
+ return Import(inputPath, outputPath, (void*)&options);
+}
+
+bool ManagedEditor::TryRestoreImportOptions(ModelTool::Options& options, String assetPath)
+{
+ // Initialize defaults
+ if (const auto* graphicsSettings = GraphicsSettings::Get())
{
- return animatedModel && animatedModel->GraphInstance.State.Count() == 1 ? animatedModel->GraphInstance.State[0].Animation.TimePosition : 0.0f;
+ options.GenerateSDF = graphicsSettings->GenerateSDFOnModelImport;
}
- static void SetAnimationTime(AnimatedModel* animatedModel, float time)
- {
- if (animatedModel && animatedModel->GraphInstance.State.Count() == 1)
- animatedModel->GraphInstance.State[0].Animation.TimePosition = time;
- }
-
- static void InitRuntime()
- {
- ADD_INTERNAL_CALL("FlaxEditor.Editor::IsDevInstance", &IsDevInstance);
- ADD_INTERNAL_CALL("FlaxEditor.Editor::IsOfficialBuild", &IsOfficialBuild);
- ADD_INTERNAL_CALL("FlaxEditor.Editor::Internal_IsPlayMode", &IsPlayMode);
- ADD_INTERNAL_CALL("FlaxEditor.Editor::Internal_ReadOutputLogs", &ReadOutputLogs);
- ADD_INTERNAL_CALL("FlaxEditor.Editor::Internal_SetPlayMode", &SetPlayMode);
- ADD_INTERNAL_CALL("FlaxEditor.Editor::Internal_GetProjectPath", &GetProjectPath);
- ADD_INTERNAL_CALL("FlaxEditor.Editor::Internal_Import", &Import);
- ADD_INTERNAL_CALL("FlaxEditor.Editor::Internal_ImportTexture", &ImportTexture);
- ADD_INTERNAL_CALL("FlaxEditor.Editor::Internal_ImportModel", &ImportModel);
- ADD_INTERNAL_CALL("FlaxEditor.Editor::Internal_ImportAudio", &ImportAudio);
- ADD_INTERNAL_CALL("FlaxEditor.Editor::Internal_GetAudioClipMetadata", &GetAudioClipMetadata);
- ADD_INTERNAL_CALL("FlaxEditor.Editor::Internal_SaveJsonAsset", &SaveJsonAsset);
- ADD_INTERNAL_CALL("FlaxEditor.Editor::Internal_CopyCache", &CopyCache);
- ADD_INTERNAL_CALL("FlaxEditor.Editor::Internal_CloneAssetFile", &CloneAssetFile);
- ADD_INTERNAL_CALL("FlaxEditor.Editor::Internal_BakeLightmaps", &BakeLightmaps);
- ADD_INTERNAL_CALL("FlaxEditor.Editor::Internal_GetShaderAssetSourceCode", &GetShaderAssetSourceCode);
- ADD_INTERNAL_CALL("FlaxEditor.Editor::Internal_CookMeshCollision", &CookMeshCollision);
- ADD_INTERNAL_CALL("FlaxEditor.Editor::Internal_GetCollisionWires", &GetCollisionWires);
- ADD_INTERNAL_CALL("FlaxEditor.Editor::Internal_GetEditorBoxWithChildren", &GetEditorBoxWithChildren);
- ADD_INTERNAL_CALL("FlaxEditor.Editor::Internal_SetOptions", &SetOptions);
- ADD_INTERNAL_CALL("FlaxEditor.Editor::Internal_DrawNavMesh", &DrawNavMesh);
- ADD_INTERNAL_CALL("FlaxEditor.CustomEditors.CustomEditorsUtil::Internal_GetCustomEditor", &CustomEditorsUtilInternal::GetCustomEditor);
- ADD_INTERNAL_CALL("FlaxEditor.Content.Import.TextureImportEntry::Internal_GetTextureImportOptions", &GetTextureImportOptions);
- ADD_INTERNAL_CALL("FlaxEditor.Content.Import.ModelImportEntry::Internal_GetModelImportOptions", &GetModelImportOptions);
- ADD_INTERNAL_CALL("FlaxEditor.Content.Import.AudioImportEntry::Internal_GetAudioImportOptions", &GetAudioImportOptions);
- ADD_INTERNAL_CALL("FlaxEditor.Content.Settings.LayersAndTagsSettings::GetCurrentLayers", &LayersAndTagsSettingsInternal::GetCurrentLayers);
- ADD_INTERNAL_CALL("FlaxEditor.Content.Settings.GameSettings::Apply", &GameSettingsInternal1::Apply);
- ADD_INTERNAL_CALL("FlaxEditor.Editor::Internal_CloseSplashScreen", &CloseSplashScreen);
- ADD_INTERNAL_CALL("FlaxEditor.Editor::Internal_CreateAsset", &CreateAsset);
- ADD_INTERNAL_CALL("FlaxEditor.Editor::Internal_CreateVisualScript", &CreateVisualScript);
- ADD_INTERNAL_CALL("FlaxEditor.Editor::Internal_CanImport", &CanImport);
- ADD_INTERNAL_CALL("FlaxEditor.Editor::Internal_CanExport", &CanExport);
- ADD_INTERNAL_CALL("FlaxEditor.Editor::Internal_Export", &Export);
- ADD_INTERNAL_CALL("FlaxEditor.Editor::Internal_GetIsEveryAssemblyLoaded", &GetIsEveryAssemblyLoaded);
- ADD_INTERNAL_CALL("FlaxEditor.Editor::Internal_GetLastProjectOpenedEngineBuild", &GetLastProjectOpenedEngineBuild);
- ADD_INTERNAL_CALL("FlaxEditor.Editor::Internal_GetIsCSGActive", &GetIsCSGActive);
- ADD_INTERNAL_CALL("FlaxEditor.Editor::Internal_RunVisualScriptBreakpointLoopTick", &RunVisualScriptBreakpointLoopTick);
- ADD_INTERNAL_CALL("FlaxEditor.Editor::Internal_GetVisualScriptLocals", &GetVisualScriptLocals);
- ADD_INTERNAL_CALL("FlaxEditor.Editor::Internal_GetVisualScriptStackFrames", &GetVisualScriptStackFrames);
- ADD_INTERNAL_CALL("FlaxEditor.Editor::Internal_GetVisualScriptPreviousScopeFrame", &GetVisualScriptPreviousScopeFrame);
- ADD_INTERNAL_CALL("FlaxEditor.Editor::Internal_EvaluateVisualScriptLocal", &EvaluateVisualScriptLocal);
- ADD_INTERNAL_CALL("FlaxEditor.Editor::Internal_DeserializeSceneObject", &DeserializeSceneObject);
- ADD_INTERNAL_CALL("FlaxEditor.Editor::Internal_LoadAsset", &LoadAsset);
- ADD_INTERNAL_CALL("FlaxEditor.Editor::Internal_CanSetToRoot", &CanSetToRoot);
- ADD_INTERNAL_CALL("FlaxEditor.Editor::Internal_GetAnimationTime", &GetAnimationTime);
- ADD_INTERNAL_CALL("FlaxEditor.Editor::Internal_SetAnimationTime", &SetAnimationTime);
- }
-};
-
-IMPLEMENT_SCRIPTING_TYPE_NO_SPAWN_WITH_BASE(ManagedEditor, ScriptingObject, FlaxEngine, "FlaxEditor.Editor", nullptr, nullptr);
+ // Get options from model
+ FileSystem::NormalizePath(assetPath);
+ return ImportModelFile::TryGetImportOptions(assetPath, options);
+}
diff --git a/Source/Editor/Managed/ManagedEditor.cpp b/Source/Editor/Managed/ManagedEditor.cpp
index fdabf9056..fd9219d35 100644
--- a/Source/Editor/Managed/ManagedEditor.cpp
+++ b/Source/Editor/Managed/ManagedEditor.cpp
@@ -3,20 +3,19 @@
#include "ManagedEditor.h"
#include "Editor/Editor.h"
#include "FlaxEngine.Gen.h"
-#include "Engine/Scripting/MException.h"
-#include "Engine/Scripting/Scripting.h"
-#include "Engine/Scripting/MainThreadManagedInvokeAction.h"
#include "Engine/ShadowsOfMordor/Builder.h"
+#include "Engine/Scripting/Scripting.h"
#include "Engine/Scripting/ScriptingType.h"
#include "Engine/Scripting/BinaryModule.h"
#include "Engine/Scripting/ManagedCLR/MAssembly.h"
#include "Engine/Scripting/ManagedCLR/MClass.h"
+#include "Engine/Scripting/ManagedCLR/MException.h"
+#include "Engine/Scripting/Internal/MainThreadManagedInvokeAction.h"
#include "Engine/Content/Assets/VisualScript.h"
#include "Engine/CSG/CSGBuilder.h"
#include "Engine/Engine/CommandLine.h"
#include "Engine/Renderer/ProbesRenderer.h"
#include "Engine/Animations/Graph/AnimGraph.h"
-#include
ManagedEditor::InternalOptions ManagedEditor::ManagedEditorOptions;
@@ -79,7 +78,7 @@ void OnBakeEvent(bool started, const ProbesRenderer::Entry& e)
ASSERT(Internal_EnvProbeBake);
}
- MonoObject* probeObj = e.Actor ? e.Actor->GetManagedInstance() : nullptr;
+ MObject* probeObj = e.Actor ? e.Actor->GetManagedInstance() : nullptr;
MainThreadManagedInvokeAction::ParamsBuilder params;
params.AddParam(started);
@@ -107,8 +106,8 @@ void OnBrushModified(CSG::Brush* brush)
struct VisualScriptingDebugFlowInfo
{
- MonoObject* Script;
- MonoObject* ScriptInstance;
+ MObject* Script;
+ MObject* ScriptInstance;
uint32 NodeId;
int32 BoxId;
};
@@ -187,7 +186,7 @@ void ManagedEditor::Init()
{
LOG(Fatal, "Invalid Editor assembly! Missing initialization method.");
}
- MonoObject* instance = GetOrCreateManagedInstance();
+ MObject* instance = GetOrCreateManagedInstance();
if (instance == nullptr)
{
LOG(Fatal, "Failed to create editor instance.");
@@ -242,6 +241,13 @@ void ManagedEditor::Init()
}
}
+void ManagedEditor::BeforeRun()
+{
+ // If during last lightmaps baking engine crashed we could try to restore the progress
+ if (ShadowsOfMordor::Builder::Instance()->RestoreState())
+ GetClass()->GetMethod("Internal_StartLightingBake")->Invoke(GetOrCreateManagedInstance(), nullptr, nullptr);
+}
+
void ManagedEditor::Update()
{
// Skip if managed object is missing
@@ -325,7 +331,7 @@ bool ManagedEditor::CanReloadScripts()
bool ManagedEditor::CanAutoBuildCSG()
{
// Skip calls from non-managed thread (eg. physics worker)
- if (!mono_domain_get() || !mono_thread_current())
+ if (!MCore::Thread::IsAttached())
return false;
if (!HasManagedInstance())
@@ -343,7 +349,7 @@ bool ManagedEditor::CanAutoBuildCSG()
bool ManagedEditor::CanAutoBuildNavMesh()
{
// Skip calls from non-managed thread (eg. physics worker)
- if (!mono_domain_get() || !mono_thread_current())
+ if (!MCore::Thread::IsAttached())
return false;
if (!HasManagedInstance())
diff --git a/Source/Editor/Managed/ManagedEditor.h b/Source/Editor/Managed/ManagedEditor.h
index 36fc0c234..ddd94b47e 100644
--- a/Source/Editor/Managed/ManagedEditor.h
+++ b/Source/Editor/Managed/ManagedEditor.h
@@ -5,6 +5,8 @@
#include "Engine/Scripting/ScriptingObject.h"
#include "Engine/Platform/Window.h"
#include "Engine/ShadowsOfMordor/Types.h"
+#include "Engine/Tools/TextureTool/TextureTool.h"
+#include "Engine/Tools/ModelTool/ModelTool.h"
namespace CSG
{
@@ -12,14 +14,11 @@ namespace CSG
}
///
-/// Managed Editor root object
+/// The main managed editor class. Editor root object.
///
-class ManagedEditor : public ScriptingObject
+API_CLASS(Namespace="FlaxEditor", Name="Editor", NoSpawn, NoConstructor) class ManagedEditor : private ScriptingObject
{
-DECLARE_SCRIPTING_TYPE_NO_SPAWN(ManagedEditor);
-
-public:
-
+ DECLARE_SCRIPTING_TYPE_NO_SPAWN(ManagedEditor);
static Guid ObjectID;
struct InternalOptions
@@ -36,7 +35,6 @@ public:
static InternalOptions ManagedEditorOptions;
public:
-
///
/// Initializes a new instance of the class.
///
@@ -48,12 +46,13 @@ public:
~ManagedEditor();
public:
-
///
/// Initializes managed editor.
///
void Init();
+ void BeforeRun();
+
///
/// Updates managed editor.
///
@@ -94,7 +93,6 @@ public:
bool CanAutoBuildNavMesh();
public:
-
///
/// Checks whenever the game viewport is focused by the user (eg. can receive input).
///
@@ -144,12 +142,58 @@ public:
///
void RequestStartPlayOnEditMode();
-private:
+public:
+ ///
+ /// Imports the asset file to the target location.
+ ///
+ /// The source file path.
+ /// The result asset file path.
+ /// The custom argument 9native data).
+ /// True if importing failed, otherwise false.
+ API_FUNCTION() static bool Import(String inputPath, String outputPath, void* arg = nullptr);
+#if COMPILE_WITH_TEXTURE_TOOL
+ ///
+ /// Imports the texture asset file to the target location.
+ ///
+ /// The source file path.
+ /// The result asset file path.
+ /// The import settings.
+ /// True if importing failed, otherwise false.
+ API_FUNCTION() static bool Import(const String& inputPath, const String& outputPath, const TextureTool::Options& options);
+
+ ///
+ /// Tries the restore the asset import options from the target resource file.
+ ///
+ /// The options.
+ /// The asset path.
+ /// True settings has been restored, otherwise false.
+ API_FUNCTION() static bool TryRestoreImportOptions(API_PARAM(Ref) TextureTool::Options& options, String assetPath);
+#endif
+
+#if COMPILE_WITH_MODEL_TOOL
+ ///
+ /// Imports the model asset file to the target location.
+ ///
+ /// The source file path.
+ /// The result asset file path.
+ /// The import settings.
+ /// True if importing failed, otherwise false.
+ API_FUNCTION() static bool Import(const String& inputPath, const String& outputPath, const ModelTool::Options& options);
+
+ ///
+ /// Tries the restore the asset import options from the target resource file.
+ ///
+ /// The options.
+ /// The asset path.
+ /// True settings has been restored, otherwise false.
+ API_FUNCTION() static bool TryRestoreImportOptions(API_PARAM(Ref) ModelTool::Options& options, String assetPath);
+#endif
+
+private:
void OnEditorAssemblyLoaded(MAssembly* assembly);
public:
-
// [ScriptingObject]
void DestroyManaged() override;
};
diff --git a/Source/Editor/Modules/ContentDatabaseModule.cs b/Source/Editor/Modules/ContentDatabaseModule.cs
index 5e3b722b9..04e630381 100644
--- a/Source/Editor/Modules/ContentDatabaseModule.cs
+++ b/Source/Editor/Modules/ContentDatabaseModule.cs
@@ -8,6 +8,7 @@ using FlaxEditor.Content;
using FlaxEditor.Content.Settings;
using FlaxEditor.Scripting;
using FlaxEngine;
+using FlaxEngine.Utilities;
namespace FlaxEditor.Modules
{
@@ -19,6 +20,7 @@ namespace FlaxEditor.Modules
{
private bool _enableEvents;
private bool _isDuringFastSetup;
+ private bool _rebuildFlag;
private int _itemsCreated;
private int _itemsDeleted;
private readonly HashSet _dirtyNodes = new HashSet();
@@ -39,9 +41,9 @@ namespace FlaxEditor.Modules
public readonly List Projects = new List();
///
- /// The list with all content items proxy objects.
+ /// The list with all content items proxy objects. Use and to modify this or to refresh database when adding new item proxy types.
///
- public readonly List Proxy = new List(64);
+ public readonly List Proxy = new List(128);
///
/// Occurs when new items is added to the workspace content database.
@@ -58,6 +60,16 @@ namespace FlaxEditor.Modules
///
public event Action WorkspaceModified;
+ ///
+ /// Occurs when workspace has will be rebuilt.
+ ///
+ public event Action WorkspaceRebuilding;
+
+ ///
+ /// Occurs when workspace has been rebuilt.
+ ///
+ public event Action WorkspaceRebuilt;
+
///
/// Gets the amount of created items.
///
@@ -707,6 +719,100 @@ namespace FlaxEditor.Modules
WorkspaceModified?.Invoke();
}
+ ///
+ /// Adds the proxy.
+ ///
+ /// The proxy type.
+ public void AddProxy(ContentProxy proxy)
+ {
+ Proxy.Insert(0, proxy);
+ Rebuild();
+ }
+
+ ///
+ /// Removes the proxy.
+ ///
+ /// The proxy type.
+ public void RemoveProxy(ContentProxy proxy)
+ {
+ Proxy.Remove(proxy);
+ Rebuild();
+ }
+
+ ///
+ /// Rebuilds the whole database (eg. when adding custom item types from plugin).
+ ///
+ /// True if rebuild num, otherwise will be scheduled for the next editor update (eg. to batch multiple rebuilds within a frame).
+ public void Rebuild(bool immediate = false)
+ {
+ _rebuildFlag = true;
+ if (immediate)
+ RebuildInternal();
+ }
+
+ private void RebuildInternal()
+ {
+ var enableEvents = _enableEvents;
+ if (enableEvents)
+ {
+ WorkspaceRebuilding?.Invoke();
+ }
+
+ Profiler.BeginEvent("ContentDatabase.Rebuild");
+ var startTime = Platform.TimeSeconds;
+ _rebuildFlag = false;
+ _enableEvents = false;
+
+ // Load all folders
+ // TODO: we should create async task for gathering content and whole workspace contents if it takes too long
+ // TODO: create progress bar in content window and after end we should enable events and update it
+ _isDuringFastSetup = true;
+ var startItems = _itemsCreated;
+ foreach (var project in Projects)
+ {
+ if (project.Content != null)
+ LoadFolder(project.Content, true);
+ if (project.Source != null)
+ LoadFolder(project.Source, true);
+ }
+ _isDuringFastSetup = false;
+
+ _enableEvents = enableEvents;
+ var endTime = Platform.TimeSeconds;
+ Editor.Log(string.Format("Project database created in {0} ms. Items count: {1}", (int)((endTime - startTime) * 1000.0), _itemsCreated - startItems));
+ Profiler.EndEvent();
+
+ if (enableEvents)
+ {
+ WorkspaceModified?.Invoke();
+ WorkspaceRebuilt?.Invoke();
+ }
+ }
+
+ private void Dispose(ContentItem item)
+ {
+ if (_enableEvents)
+ ItemRemoved?.Invoke(item);
+
+ if (item is ContentFolder folder)
+ {
+ if (folder.Children.Count > 0)
+ {
+ var children = folder.Children.ToArray();
+ for (int i = 0; i < children.Length; i++)
+ Dispose(children[i]);
+ }
+
+ item.ParentFolder = null;
+ folder.Node.Dispose();
+ }
+ else
+ {
+ item.ParentFolder = null;
+ item.Dispose();
+ }
+ }
+
private void LoadFolder(ContentTreeNode node, bool checkSubDirs)
{
if (node == null)
@@ -716,9 +822,18 @@ namespace FlaxEditor.Modules
var folder = node.Folder;
var path = folder.Path;
- // Check for missing files/folders (skip it during fast tree setup)
- if (!_isDuringFastSetup)
+ if (_isDuringFastSetup)
{
+ // Remove any spawned children
+ for (int i = 0; i < folder.Children.Count; i++)
+ {
+ Dispose(folder.Children[i]);
+ i--;
+ }
+ }
+ else
+ {
+ // Check for missing files/folders (skip it during fast tree setup)
for (int i = 0; i < folder.Children.Count; i++)
{
var child = folder.Children[i];
@@ -946,7 +1061,8 @@ namespace FlaxEditor.Modules
Proxy.Add(new SettingsProxy(typeof(UWPPlatformSettings), Editor.Instance.Icons.UWPSettings128));
Proxy.Add(new SettingsProxy(typeof(LinuxPlatformSettings), Editor.Instance.Icons.LinuxSettings128));
Proxy.Add(new SettingsProxy(typeof(AndroidPlatformSettings), Editor.Instance.Icons.AndroidSettings128));
- Proxy.Add(new SettingsProxy(typeof(MacPlatformSettings), Editor.Instance.Icons.Document128));
+ Proxy.Add(new SettingsProxy(typeof(MacPlatformSettings), Editor.Instance.Icons.AppleSettings128));
+ Proxy.Add(new SettingsProxy(typeof(iOSPlatformSettings), Editor.Instance.Icons.AppleSettings128));
var typePS4PlatformSettings = TypeUtils.GetManagedType(GameSettings.PS4PlatformSettingsTypename);
if (typePS4PlatformSettings != null)
@@ -972,7 +1088,6 @@ namespace FlaxEditor.Modules
Proxy.Add(new GenericJsonAssetProxy());
// Create content folders nodes
- var startTime = Platform.TimeSeconds;
Engine = new ProjectTreeNode(Editor.EngineProject)
{
Content = new MainContentTreeNode(Engine, ContentFolderType.Content, Globals.EngineContentFolder),
@@ -996,25 +1111,10 @@ namespace FlaxEditor.Modules
LoadProjects(Game.Project);
}
- // Load all folders
- // TODO: we should create async task for gathering content and whole workspace contents if it takes too long
- // TODO: create progress bar in content window and after end we should enable events and update it
- _isDuringFastSetup = true;
- foreach (var project in Projects)
- {
- if (project.Content != null)
- LoadFolder(project.Content, true);
- if (project.Source != null)
- LoadFolder(project.Source, true);
- }
- _isDuringFastSetup = false;
+ RebuildInternal();
- // Enable events
- _enableEvents = true;
Editor.ContentImporting.ImportFileEnd += ContentImporting_ImportFileDone;
- var endTime = Platform.TimeSeconds;
-
- Editor.Log(string.Format("Project database created in {0} ms. Items count: {1}", (int)((endTime - startTime) * 1000.0), _itemsCreated));
+ _enableEvents = true;
}
private void ContentImporting_ImportFileDone(IFileEntryAction obj, bool failed)
@@ -1100,6 +1200,10 @@ namespace FlaxEditor.Modules
}
_dirtyNodes.Clear();
}
+
+ // Lazy-rebuilds
+ if (_rebuildFlag)
+ RebuildInternal();
}
///
diff --git a/Source/Editor/Modules/ContentFindingModule.cs b/Source/Editor/Modules/ContentFindingModule.cs
index e8fb40f73..ccf1cb3e4 100644
--- a/Source/Editor/Modules/ContentFindingModule.cs
+++ b/Source/Editor/Modules/ContentFindingModule.cs
@@ -2,7 +2,6 @@
using System;
using System.Collections.Generic;
-using System.Linq;
using System.Text.RegularExpressions;
using FlaxEditor.Content;
using FlaxEditor.GUI.Docking;
diff --git a/Source/Editor/Modules/ContentImportingModule.cs b/Source/Editor/Modules/ContentImportingModule.cs
index 9de3c10e0..c94ba650e 100644
--- a/Source/Editor/Modules/ContentImportingModule.cs
+++ b/Source/Editor/Modules/ContentImportingModule.cs
@@ -375,7 +375,9 @@ namespace FlaxEditor.Modules
Thread.Sleep(0);
_workerThread.Join(1000);
- _workerThread.Abort();
+#if !USE_NETCORE
+ _workerThread.Abort(); // Deprecated in .NET 7
+#endif
_workerThread = null;
}
diff --git a/Source/Editor/Modules/SourceCodeEditing/CachedCustomAnimGraphNodesCollection.cs b/Source/Editor/Modules/SourceCodeEditing/CachedCustomAnimGraphNodesCollection.cs
index 113a701c6..499a472d6 100644
--- a/Source/Editor/Modules/SourceCodeEditing/CachedCustomAnimGraphNodesCollection.cs
+++ b/Source/Editor/Modules/SourceCodeEditing/CachedCustomAnimGraphNodesCollection.cs
@@ -6,6 +6,7 @@ using System.Reflection;
using FlaxEditor.Scripting;
using FlaxEditor.Surface;
using FlaxEngine;
+using FlaxEngine.Utilities;
using MethodInfo = System.Reflection.MethodInfo;
namespace FlaxEditor.Modules.SourceCodeEditing
@@ -106,7 +107,7 @@ namespace FlaxEditor.Modules.SourceCodeEditing
// Validate node type
var typeName = Surface.Archetypes.Custom.GetNodeTypeName(arch);
- var type = Scripting.TypeUtils.GetType(typeName).Type;
+ var type = TypeUtils.GetType(typeName).Type;
if (type == null)
{
Debug.LogWarning(string.Format("Method {0} from {1} returned invalid node archetype. Failed to find node logic defined in type {2}.", method, method.DeclaringType, typeName));
diff --git a/Source/Editor/Modules/SourceCodeEditing/CachedTypesCollection.cs b/Source/Editor/Modules/SourceCodeEditing/CachedTypesCollection.cs
index 6e0517175..a73c3d2ee 100644
--- a/Source/Editor/Modules/SourceCodeEditing/CachedTypesCollection.cs
+++ b/Source/Editor/Modules/SourceCodeEditing/CachedTypesCollection.cs
@@ -5,6 +5,7 @@ using System.Collections.Generic;
using System.Reflection;
using FlaxEditor.Scripting;
using FlaxEngine;
+using FlaxEngine.Utilities;
namespace FlaxEditor.Modules.SourceCodeEditing
{
diff --git a/Source/Editor/Modules/SourceCodeEditing/CodeDocsModule.cs b/Source/Editor/Modules/SourceCodeEditing/CodeDocsModule.cs
index 8b93f8ea4..225bad082 100644
--- a/Source/Editor/Modules/SourceCodeEditing/CodeDocsModule.cs
+++ b/Source/Editor/Modules/SourceCodeEditing/CodeDocsModule.cs
@@ -257,13 +257,18 @@ namespace FlaxEditor.Modules.SourceCodeEditing
{
Profiler.BeginEvent("GetXmlDocs");
- var uri = new UriBuilder(assembly.CodeBase);
- var path = Uri.UnescapeDataString(uri.Path);
- var name = assembly.GetName().Name;
- var xmlFilePath = Path.Combine(Path.GetDirectoryName(path), name + ".xml");
+ var assemblyPath = Utils.GetAssemblyLocation(assembly);
+ var assemblyName = assembly.GetName().Name;
+ var xmlFilePath = Path.ChangeExtension(assemblyPath, ".xml");
+ if (!File.Exists(assemblyPath))
+ {
+ var uri = new UriBuilder(assemblyPath);
+ var path = Uri.UnescapeDataString(uri.Path);
+ xmlFilePath = Path.Combine(Path.GetDirectoryName(path), assemblyName + ".xml");
+ }
if (File.Exists(xmlFilePath))
{
- Profiler.BeginEvent(name);
+ Profiler.BeginEvent(assemblyName);
try
{
// Parse xml documentation
diff --git a/Source/Editor/Modules/SourceCodeEditing/CodeEditingModule.cs b/Source/Editor/Modules/SourceCodeEditing/CodeEditingModule.cs
index a06e0f658..c61fad713 100644
--- a/Source/Editor/Modules/SourceCodeEditing/CodeEditingModule.cs
+++ b/Source/Editor/Modules/SourceCodeEditing/CodeEditingModule.cs
@@ -8,6 +8,7 @@ using FlaxEditor.Options;
using FlaxEditor.Scripting;
using FlaxEngine;
using FlaxEngine.GUI;
+using FlaxEngine.Utilities;
namespace FlaxEditor.Modules.SourceCodeEditing
{
@@ -26,7 +27,11 @@ namespace FlaxEditor.Modules.SourceCodeEditing
private static bool CheckFunc(ScriptType scriptType)
{
- if (scriptType.IsStatic || scriptType.IsGenericType || !scriptType.IsPublic || scriptType.HasAttribute(typeof(HideInEditorAttribute), true))
+ if (scriptType.IsStatic ||
+ scriptType.IsGenericType ||
+ !scriptType.IsPublic ||
+ scriptType.HasAttribute(typeof(HideInEditorAttribute), true) ||
+ scriptType.HasAttribute(typeof(System.Runtime.CompilerServices.CompilerGeneratedAttribute), false))
return false;
var managedType = TypeUtils.GetType(scriptType);
return !TypeUtils.IsDelegate(managedType);
@@ -399,9 +404,27 @@ namespace FlaxEditor.Modules.SourceCodeEditing
private static bool HasAssemblyValidAnyTypes(Assembly assembly)
{
+ var codeBase = Utils.GetAssemblyLocation(assembly);
+#if USE_NETCORE
+ if (assembly.ManifestModule.FullyQualifiedName == "")
+ return false;
+
+ if (string.IsNullOrEmpty(codeBase))
+ return true;
+
+ // Skip runtime related assemblies
+ string repositoryUrl = assembly.GetCustomAttributes().FirstOrDefault(x => x.Key == "RepositoryUrl")?.Value ?? "";
+ if (repositoryUrl != "https://github.com/dotnet/runtime")
+ return true;
+#else
+ if (string.IsNullOrEmpty(codeBase))
+ return true;
+
// Skip assemblies from in-build Mono directory
- var codeBase = assembly.CodeBase;
- return string.IsNullOrEmpty(codeBase) || !codeBase.Contains("/Mono/lib/mono/");
+ if (!codeBase.Contains("/Mono/lib/mono/"))
+ return true;
+#endif
+ return false;
}
private static bool HasAssemblyValidScriptingTypes(Assembly a)
diff --git a/Source/Editor/Modules/SourceCodeEditing/InBuildSourceCodeEditor.cs b/Source/Editor/Modules/SourceCodeEditing/InBuildSourceCodeEditor.cs
index e00ea1b61..ff59cbe84 100644
--- a/Source/Editor/Modules/SourceCodeEditing/InBuildSourceCodeEditor.cs
+++ b/Source/Editor/Modules/SourceCodeEditing/InBuildSourceCodeEditor.cs
@@ -78,8 +78,8 @@ namespace FlaxEditor.Modules.SourceCodeEditing
switch (Type)
{
case CodeEditorTypes.VSCodeInsiders:
- case CodeEditorTypes.VSCode: return "-vscode -vs2019";
- case CodeEditorTypes.Rider: return "-vs2019";
+ case CodeEditorTypes.VSCode: return "-vscode -vs2022";
+ case CodeEditorTypes.Rider: return "-vs2022";
default: return null;
}
}
diff --git a/Source/Editor/Options/InputBinding.cs b/Source/Editor/Options/InputBinding.cs
index 83789338a..2e18dab9e 100644
--- a/Source/Editor/Options/InputBinding.cs
+++ b/Source/Editor/Options/InputBinding.cs
@@ -218,6 +218,16 @@ namespace FlaxEditor.Options
return base.CanConvertFrom(context, sourceType);
}
+ ///
+ public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
+ {
+ if (destinationType == typeof(string))
+ {
+ return false;
+ }
+ return base.CanConvertTo(context, destinationType);
+ }
+
///
public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
{
diff --git a/Source/Editor/Options/OptionsModule.cs b/Source/Editor/Options/OptionsModule.cs
index b82ad38cc..68aa11626 100644
--- a/Source/Editor/Options/OptionsModule.cs
+++ b/Source/Editor/Options/OptionsModule.cs
@@ -125,9 +125,12 @@ namespace FlaxEditor.Options
options.CustomSettings.Add(e.Key, JsonSerializer.Serialize(e.Value()));
}
+ float prevInterfaceScale = Options.Interface.InterfaceScale;
Options = options;
OnOptionsChanged();
- Platform.CustomDpiScale = Options.Interface.InterfaceScale;
+
+ // Scale interface relative to the current value (eg. when using system-provided Dpi Scale)
+ Platform.CustomDpiScale *= Options.Interface.InterfaceScale / prevInterfaceScale;
}
else
{
diff --git a/Source/Editor/Plugins/PluginUtils.cs b/Source/Editor/Plugins/PluginUtils.cs
index a3d535c87..755c57d7e 100644
--- a/Source/Editor/Plugins/PluginUtils.cs
+++ b/Source/Editor/Plugins/PluginUtils.cs
@@ -24,7 +24,7 @@ namespace FlaxEditor
var type = plugin.GetType();
var assembly = type.Assembly;
- var assemblyPath = assembly.Location;
+ var assemblyPath = Utils.GetAssemblyLocation(assembly);
var assemblyName = assembly.GetName().Name;
var dotEditorPos = assemblyName.LastIndexOf(".Editor", StringComparison.OrdinalIgnoreCase);
if (dotEditorPos != -1)
@@ -84,7 +84,7 @@ namespace FlaxEditor
editorPlugin = null;
// Cache data
- var allAssemblies = AppDomain.CurrentDomain.GetAssemblies();
+ var allAssemblies = Utils.GetAssemblies();
var gameAssembly = Utils.GetAssemblyByName("Game", allAssemblies);
var gameEditorAssembly = Utils.GetAssemblyByName("Game.Editor", allAssemblies);
var gameAssemblyTypes = gameAssembly.GetTypes();
diff --git a/Source/Editor/Progress/Handlers/CompileScriptsProgress.cs b/Source/Editor/Progress/Handlers/CompileScriptsProgress.cs
index 59d40463e..310612a73 100644
--- a/Source/Editor/Progress/Handlers/CompileScriptsProgress.cs
+++ b/Source/Editor/Progress/Handlers/CompileScriptsProgress.cs
@@ -40,8 +40,10 @@ namespace FlaxEditor.Progress.Handlers
private void OnScriptsReload()
{
+#if !USE_NETCORE
// Clear types cache
Newtonsoft.Json.JsonSerializer.ClearCache();
+#endif
}
private void OnCompilationFailed()
diff --git a/Source/Editor/ProjectInfo.cs b/Source/Editor/ProjectInfo.cs
index bc55a9c05..ed9470c35 100644
--- a/Source/Editor/ProjectInfo.cs
+++ b/Source/Editor/ProjectInfo.cs
@@ -8,6 +8,110 @@ using Newtonsoft.Json;
namespace FlaxEditor
{
+ ///
+ ///
+ ///
+ public class FlaxVersionConverter : JsonConverter
+ {
+ // Original implementation is based on Newtonsoft.Json VersionConverter
+ ///
+ /// Writes the JSON representation of the object.
+ ///
+ /// The to write to.
+ /// The value.
+ /// The calling serializer.
+ public override void WriteJson(JsonWriter writer, object? value, JsonSerializer serializer)
+ {
+ if (value == null)
+ {
+ writer.WriteNull();
+ }
+ else if (value is Version)
+ {
+ writer.WriteValue(value.ToString());
+ }
+ else
+ {
+ throw new JsonSerializationException("Expected Version object value");
+ }
+ }
+
+ ///
+ /// Reads the JSON representation of the object.
+ ///
+ /// The to read from.
+ /// Type of the object.
+ /// The existing property value of the JSON that is being converted.
+ /// The calling serializer.
+ /// The object value.
+ public override object? ReadJson(JsonReader reader, Type objectType, object? existingValue, JsonSerializer serializer)
+ {
+ if (reader.TokenType == JsonToken.Null)
+ {
+ return null;
+ }
+ else
+ {
+ if (reader.TokenType == JsonToken.StartObject)
+ {
+ try
+ {
+ reader.Read();
+ Dictionary values = new Dictionary();
+ while (reader.TokenType == JsonToken.PropertyName)
+ {
+ var key = reader.Value as string;
+ reader.Read();
+ var val = (long)reader.Value;
+ reader.Read();
+ values.Add(key, (int)val);
+ }
+
+ int major = 0, minor = 0, build = 0;
+ values.TryGetValue("Major", out major);
+ values.TryGetValue("Minor", out minor);
+ values.TryGetValue("Build", out build);
+
+ Version v = new Version(major, minor, build);
+ return v;
+ }
+ catch (Exception ex)
+ {
+ throw new Exception(String.Format("Error parsing version string: {0}", reader.Value), ex);
+ }
+ }
+ else if (reader.TokenType == JsonToken.String)
+ {
+ try
+ {
+ Version v = new Version((string)reader.Value!);
+ return v;
+ }
+ catch (Exception ex)
+ {
+ throw new Exception(String.Format("Error parsing version string: {0}", reader.Value), ex);
+ }
+ }
+ else
+ {
+ throw new Exception(String.Format("Unexpected token or value when parsing version. Token: {0}, Value: {1}", reader.TokenType, reader.Value));
+ }
+ }
+ }
+
+ ///
+ /// Determines whether this instance can convert the specified object type.
+ ///
+ /// Type of the object.
+ ///
+ /// true if this instance can convert the specified object type; otherwise, false.
+ ///
+ public override bool CanConvert(Type objectType)
+ {
+ return objectType == typeof(Version);
+ }
+ }
+
///
/// Contains information about Flax project.
///
@@ -154,7 +258,7 @@ namespace FlaxEditor
{
// Load
var contents = File.ReadAllText(path);
- var project = JsonConvert.DeserializeObject(contents);
+ var project = JsonConvert.DeserializeObject(contents, new JsonSerializerSettings() { Converters = new[] { new FlaxVersionConverter() } });
project.ProjectPath = path;
project.ProjectFolderPath = StringUtils.NormalizePath(Path.GetDirectoryName(path));
diff --git a/Source/Editor/SceneGraph/Actors/CameraNode.cs b/Source/Editor/SceneGraph/Actors/CameraNode.cs
index ac6edd149..400a2e06f 100644
--- a/Source/Editor/SceneGraph/Actors/CameraNode.cs
+++ b/Source/Editor/SceneGraph/Actors/CameraNode.cs
@@ -35,7 +35,7 @@ namespace FlaxEditor.SceneGraph.Actors
return false;
}
- return Camera.Internal_IntersectsItselfEditor(Object.GetUnmanagedPtr(_actor), ref ray.Ray, out distance);
+ return Camera.Internal_IntersectsItselfEditor(FlaxEngine.Object.GetUnmanagedPtr(_actor), ref ray.Ray, out distance);
}
}
}
diff --git a/Source/Editor/SceneGraph/Actors/SplineNode.cs b/Source/Editor/SceneGraph/Actors/SplineNode.cs
index ee03caa94..eebee0cd9 100644
--- a/Source/Editor/SceneGraph/Actors/SplineNode.cs
+++ b/Source/Editor/SceneGraph/Actors/SplineNode.cs
@@ -361,7 +361,7 @@ namespace FlaxEditor.SceneGraph.Actors
StaticFlags = Actor.StaticFlags,
Transform = Actor.Transform,
};
- Editor.Instance.SceneEditing.Spawn(actor, Actor);
+ Root.Spawn(actor, Actor);
}
private void OnAddSplineCollider()
@@ -372,7 +372,7 @@ namespace FlaxEditor.SceneGraph.Actors
Transform = Actor.Transform,
};
// TODO: auto pick the collision data if already using spline model
- Editor.Instance.SceneEditing.Spawn(actor, Actor);
+ Root.Spawn(actor, Actor);
}
private void OnAddSplineRopeBody()
@@ -382,7 +382,7 @@ namespace FlaxEditor.SceneGraph.Actors
StaticFlags = StaticFlags.None,
Transform = Actor.Transform,
};
- Editor.Instance.SceneEditing.Spawn(actor, Actor);
+ Root.Spawn(actor, Actor);
}
internal static void OnSplineEdited(Spline spline)
diff --git a/Source/Editor/SceneGraph/Actors/StaticModelNode.cs b/Source/Editor/SceneGraph/Actors/StaticModelNode.cs
index faded9025..52176ab8b 100644
--- a/Source/Editor/SceneGraph/Actors/StaticModelNode.cs
+++ b/Source/Editor/SceneGraph/Actors/StaticModelNode.cs
@@ -43,7 +43,7 @@ namespace FlaxEditor.SceneGraph.Actors
StaticFlags = Actor.StaticFlags,
Transform = Actor.Transform,
};
- Editor.Instance.SceneEditing.Spawn(actor, Actor);
+ Root.Spawn(actor, Actor);
return;
}
if (modelPath.EndsWith("/Primitives/Sphere.flax", StringComparison.Ordinal))
@@ -53,7 +53,7 @@ namespace FlaxEditor.SceneGraph.Actors
StaticFlags = Actor.StaticFlags,
Transform = Actor.Transform,
};
- Editor.Instance.SceneEditing.Spawn(actor, Actor);
+ Root.Spawn(actor, Actor);
return;
}
if (modelPath.EndsWith("/Primitives/Plane.flax", StringComparison.Ordinal))
@@ -64,7 +64,7 @@ namespace FlaxEditor.SceneGraph.Actors
Transform = Actor.Transform,
Size = new Float3(100.0f, 100.0f, 1.0f),
};
- Editor.Instance.SceneEditing.Spawn(actor, Actor);
+ Root.Spawn(actor, Actor);
return;
}
if (modelPath.EndsWith("/Primitives/Capsule.flax", StringComparison.Ordinal))
@@ -91,7 +91,7 @@ namespace FlaxEditor.SceneGraph.Actors
Transform = Actor.Transform,
CollisionData = collisionData,
};
- Editor.Instance.SceneEditing.Spawn(actor, Actor);
+ Root.Spawn(actor, Actor);
};
var collisionDataProxy = (CollisionDataProxy)Editor.Instance.ContentDatabase.GetProxy();
collisionDataProxy.CreateCollisionDataFromModel(model, created);
diff --git a/Source/Editor/SceneGraph/GUI/ActorTreeNode.cs b/Source/Editor/SceneGraph/GUI/ActorTreeNode.cs
index d00bfa10a..83ea666d9 100644
--- a/Source/Editor/SceneGraph/GUI/ActorTreeNode.cs
+++ b/Source/Editor/SceneGraph/GUI/ActorTreeNode.cs
@@ -13,6 +13,7 @@ using FlaxEditor.Windows;
using FlaxEditor.Windows.Assets;
using FlaxEngine;
using FlaxEngine.GUI;
+using FlaxEngine.Utilities;
using Object = FlaxEngine.Object;
namespace FlaxEditor.SceneGraph.GUI
@@ -637,7 +638,7 @@ namespace FlaxEditor.SceneGraph.GUI
}
actor.Name = item.ShortName;
actor.Transform = spawnParent.Transform;
- Editor.Instance.SceneEditing.Spawn(actor, spawnParent, false);
+ ActorNode.Root.Spawn(actor, spawnParent);
actor.OrderInParent = newOrder;
}
result = DragDropEffect.Move;
@@ -648,8 +649,6 @@ namespace FlaxEditor.SceneGraph.GUI
for (int i = 0; i < _dragActorType.Objects.Count; i++)
{
var item = _dragActorType.Objects[i];
-
- // Create actor
var actor = item.CreateInstance() as Actor;
if (actor == null)
{
@@ -659,8 +658,6 @@ namespace FlaxEditor.SceneGraph.GUI
actor.StaticFlags = Actor.StaticFlags;
actor.Name = item.Name;
actor.Transform = Actor.Transform;
-
- // Spawn
ActorNode.Root.Spawn(actor, Actor);
}
diff --git a/Source/Editor/Scripting/CodeEditors/RiderCodeEditor.cpp b/Source/Editor/Scripting/CodeEditors/RiderCodeEditor.cpp
index 438331e11..e28d1e381 100644
--- a/Source/Editor/Scripting/CodeEditors/RiderCodeEditor.cpp
+++ b/Source/Editor/Scripting/CodeEditors/RiderCodeEditor.cpp
@@ -9,6 +9,7 @@
#include "Engine/Engine/Globals.h"
#include "Engine/Core/Collections/Sorting.h"
#include "Engine/Platform/File.h"
+#include "Engine/Platform/CreateProcessSettings.h"
#include "Engine/Serialization/Json.h"
#if PLATFORM_WINDOWS
@@ -237,13 +238,19 @@ void RiderCodeEditor::OpenFile(const String& path, int32 line)
// Generate project files if solution is missing
if (!FileSystem::FileExists(_solutionPath))
{
- ScriptsBuilder::GenerateProject(TEXT("-vs2019"));
+ ScriptsBuilder::GenerateProject(TEXT("-vs2022"));
}
// Open file
line = line > 0 ? line : 1;
- const String args = String::Format(TEXT("\"{0}\" --line {2} \"{1}\""), _solutionPath, path, line);
- Platform::StartProcess(_execPath, args, StringView::Empty);
+ CreateProcessSettings procSettings;
+ procSettings.FileName = _execPath;
+ procSettings.Arguments = String::Format(TEXT("\"{0}\" --line {2} \"{1}\""), _solutionPath, path, line);
+ procSettings.HiddenWindow = false;
+ procSettings.WaitForEnd = false;
+ procSettings.LogOutput = false;
+ procSettings.ShellExecute = true;
+ Platform::CreateProcess(procSettings);
}
void RiderCodeEditor::OpenSolution()
@@ -251,12 +258,18 @@ void RiderCodeEditor::OpenSolution()
// Generate project files if solution is missing
if (!FileSystem::FileExists(_solutionPath))
{
- ScriptsBuilder::GenerateProject(TEXT("-vs2019"));
+ ScriptsBuilder::GenerateProject(TEXT("-vs2022"));
}
// Open solution
- const String args = String::Format(TEXT("\"{0}\""), _solutionPath);
- Platform::StartProcess(_execPath, args, StringView::Empty);
+ CreateProcessSettings procSettings;
+ procSettings.FileName = _execPath;
+ procSettings.Arguments = String::Format(TEXT("\"{0}\""), _solutionPath);
+ procSettings.HiddenWindow = false;
+ procSettings.WaitForEnd = false;
+ procSettings.LogOutput = false;
+ procSettings.ShellExecute = true;
+ Platform::CreateProcess(procSettings);
}
void RiderCodeEditor::OnFileAdded(const String& path)
diff --git a/Source/Editor/Scripting/CodeEditors/SystemDefaultCodeEditor.cpp b/Source/Editor/Scripting/CodeEditors/SystemDefaultCodeEditor.cpp
index b78a4310b..42386c731 100644
--- a/Source/Editor/Scripting/CodeEditors/SystemDefaultCodeEditor.cpp
+++ b/Source/Editor/Scripting/CodeEditors/SystemDefaultCodeEditor.cpp
@@ -1,7 +1,7 @@
// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.
#include "SystemDefaultCodeEditor.h"
-#include "Engine/Core/Types/StringView.h"
+#include "Engine/Platform/CreateProcessSettings.h"
CodeEditorTypes SystemDefaultCodeEditor::GetType() const
{
@@ -15,7 +15,13 @@ String SystemDefaultCodeEditor::GetName() const
void SystemDefaultCodeEditor::OpenFile(const String& path, int32 line)
{
- Platform::StartProcess(path, StringView::Empty, StringView::Empty);
+ CreateProcessSettings procSettings;
+ procSettings.FileName = path;
+ procSettings.HiddenWindow = false;
+ procSettings.WaitForEnd = false;
+ procSettings.LogOutput = false;
+ procSettings.ShellExecute = true;
+ Platform::CreateProcess(procSettings);
}
void SystemDefaultCodeEditor::OpenSolution()
diff --git a/Source/Editor/Scripting/CodeEditors/VisualStudioCodeEditor.cpp b/Source/Editor/Scripting/CodeEditors/VisualStudioCodeEditor.cpp
index 0ae9bd1c8..adccbba54 100644
--- a/Source/Editor/Scripting/CodeEditors/VisualStudioCodeEditor.cpp
+++ b/Source/Editor/Scripting/CodeEditors/VisualStudioCodeEditor.cpp
@@ -2,14 +2,19 @@
#include "VisualStudioCodeEditor.h"
#include "Engine/Platform/FileSystem.h"
+#include "Engine/Platform/CreateProcessSettings.h"
#include "Engine/Core/Log.h"
#include "Editor/Editor.h"
#include "Editor/ProjectInfo.h"
#include "Editor/Scripting/ScriptsBuilder.h"
#include "Engine/Engine/Globals.h"
-#include "Engine/Platform/Win32/IncludeWindowsHeaders.h"
#if PLATFORM_LINUX
#include
+#elif PLATFORM_WINDOWS
+#include "Engine/Platform/Win32/IncludeWindowsHeaders.h"
+#elif PLATFORM_MAC
+#include "Engine/Platform/Apple/AppleUtils.h"
+#include
#endif
VisualStudioCodeEditor::VisualStudioCodeEditor(const String& execPath, const bool isInsiders)
@@ -80,6 +85,33 @@ void VisualStudioCodeEditor::FindEditors(Array* output)
return;
}
}
+#elif PLATFORM_MAC
+ // System installed app
+ NSURL* AppURL = [[NSWorkspace sharedWorkspace] URLForApplicationWithBundleIdentifier:@"com.microsoft.VSCode"];
+ if (AppURL != nullptr)
+ {
+ const String path = AppleUtils::ToString((CFStringRef)[AppURL path]);
+ output->Add(New(path, false));
+ return;
+ }
+
+ // Predefined locations
+ String userFolder;
+ FileSystem::GetSpecialFolderPath(SpecialFolder::Documents, userFolder);
+ String paths[3] =
+ {
+ TEXT("/Applications/Visual Studio Code.app"),
+ userFolder + TEXT("/../Visual Studio Code.app"),
+ userFolder + TEXT("/../Downloads/Visual Studio Code.app"),
+ };
+ for (const String& path : paths)
+ {
+ if (FileSystem::DirectoryExists(path))
+ {
+ output->Add(New(path, false));
+ break;
+ }
+ }
#endif
}
@@ -98,7 +130,7 @@ void VisualStudioCodeEditor::OpenFile(const String& path, int32 line)
// Generate VS solution files for intellisense
if (!FileSystem::FileExists(Globals::ProjectFolder / Editor::Project->Name + TEXT(".sln")))
{
- ScriptsBuilder::GenerateProject(TEXT("-vs2019"));
+ ScriptsBuilder::GenerateProject(TEXT("-vs2022"));
}
// Generate project files if missing
@@ -110,8 +142,14 @@ void VisualStudioCodeEditor::OpenFile(const String& path, int32 line)
// Open file
line = line > 0 ? line : 1;
- const String args = String::Format(TEXT("\"{0}\" -g \"{1}:{2}\""), _workspacePath, path, line);
- Platform::StartProcess(_execPath, args, StringView::Empty);
+ CreateProcessSettings procSettings;
+ procSettings.FileName = _execPath;
+ procSettings.Arguments = String::Format(TEXT("\"{0}\" -g \"{1}:{2}\""), _workspacePath, path, line);
+ procSettings.HiddenWindow = false;
+ procSettings.WaitForEnd = false;
+ procSettings.LogOutput = false;
+ procSettings.ShellExecute = true;
+ Platform::CreateProcess(procSettings);
}
void VisualStudioCodeEditor::OpenSolution()
@@ -119,7 +157,7 @@ void VisualStudioCodeEditor::OpenSolution()
// Generate VS solution files for intellisense
if (!FileSystem::FileExists(Globals::ProjectFolder / Editor::Project->Name + TEXT(".sln")))
{
- ScriptsBuilder::GenerateProject(TEXT("-vs2019"));
+ ScriptsBuilder::GenerateProject(TEXT("-vs2022"));
}
// Generate project files if solution is missing
@@ -130,8 +168,14 @@ void VisualStudioCodeEditor::OpenSolution()
}
// Open solution
- const String args = String::Format(TEXT("\"{0}\""), _workspacePath);
- Platform::StartProcess(_execPath, args, StringView::Empty);
+ CreateProcessSettings procSettings;
+ procSettings.FileName = _execPath;
+ procSettings.Arguments = String::Format(TEXT("\"{0}\""), _workspacePath);
+ procSettings.HiddenWindow = false;
+ procSettings.WaitForEnd = false;
+ procSettings.LogOutput = false;
+ procSettings.ShellExecute = true;
+ Platform::CreateProcess(procSettings);
}
bool VisualStudioCodeEditor::UseAsyncForOpen() const
diff --git a/Source/Editor/Scripting/ScriptType.Interfaces.cs b/Source/Editor/Scripting/ScriptType.Interfaces.cs
index fb4230256..3b3b28ccb 100644
--- a/Source/Editor/Scripting/ScriptType.Interfaces.cs
+++ b/Source/Editor/Scripting/ScriptType.Interfaces.cs
@@ -4,6 +4,7 @@ using System;
using System.Collections.Generic;
using System.Reflection;
using FlaxEditor.Content;
+using FlaxEngine.Utilities;
namespace FlaxEditor.Scripting
{
diff --git a/Source/Editor/Scripting/ScriptType.cs b/Source/Editor/Scripting/ScriptType.cs
index d6b4fa271..d174b7dff 100644
--- a/Source/Editor/Scripting/ScriptType.cs
+++ b/Source/Editor/Scripting/ScriptType.cs
@@ -8,6 +8,7 @@ using System.Runtime.CompilerServices;
using System.Text;
using FlaxEditor.Content;
using FlaxEngine;
+using FlaxEngine.Utilities;
namespace FlaxEditor.Scripting
{
@@ -831,7 +832,7 @@ namespace FlaxEditor.Scripting
get
{
if (_managed != null)
- return _managed.GetConstructor(Type.EmptyTypes) != null;
+ return _managed.GetConstructor(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance, null, Type.EmptyTypes, null) != null;
return _custom?.CanCreateInstance ?? false;
}
}
@@ -891,7 +892,12 @@ namespace FlaxEditor.Scripting
public object CreateInstance()
{
if (_managed != null)
- return Activator.CreateInstance(_managed);
+ {
+ var ctor = _managed.GetConstructor(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance, null, Type.EmptyTypes, null);
+ object value = RuntimeHelpers.GetUninitializedObject(_managed);
+ ctor.Invoke(value, null);
+ return value;
+ }
return _custom.CreateInstance();
}
diff --git a/Source/Editor/Scripting/ScriptsBuilder.cpp b/Source/Editor/Scripting/ScriptsBuilder.cpp
index 1284d2df9..eaffe5691 100644
--- a/Source/Editor/Scripting/ScriptsBuilder.cpp
+++ b/Source/Editor/Scripting/ScriptsBuilder.cpp
@@ -10,18 +10,18 @@
#include "Engine/Debug/Exceptions/FileNotFoundException.h"
#include "Engine/Engine/Engine.h"
#include "Engine/Engine/Globals.h"
+#include "Engine/Engine/EngineService.h"
#include "Engine/Platform/FileSystem.h"
#include "Engine/Platform/FileSystemWatcher.h"
-#include "Engine/Threading/ThreadPool.h"
+#include "Engine/Platform/CreateProcessSettings.h"
#include "Engine/Threading/Threading.h"
-#include "Engine/Scripting/MainThreadManagedInvokeAction.h"
+#include "Engine/Scripting/Internal/MainThreadManagedInvokeAction.h"
#include "Engine/Scripting/ScriptingType.h"
#include "Engine/Scripting/BinaryModule.h"
#include "Engine/Scripting/ManagedCLR/MAssembly.h"
#include "Engine/Scripting/ManagedCLR/MClass.h"
#include "Engine/Scripting/Scripting.h"
#include "Engine/Scripting/Script.h"
-#include "Engine/Engine/EngineService.h"
#include "Engine/Profiler/ProfilerCPU.h"
#include "Engine/Level/Level.h"
#include "FlaxEngine.Gen.h"
@@ -77,7 +77,7 @@ namespace ScriptsBuilderImpl
void onScriptsReloadEnd();
void onScriptsLoaded();
- void GetClassName(const MString& fullname, MString& className);
+ void GetClassName(const StringAnsi& fullname, StringAnsi& className);
void onCodeEditorAsyncOpenBegin()
{
@@ -134,6 +134,15 @@ int32 ScriptsBuilder::GetCompilationsCount()
return _compilationsCount;
}
+String ScriptsBuilder::GetBuildToolPath()
+{
+#if USE_NETCORE && (PLATFORM_LINUX || PLATFORM_MAC)
+ return Globals::StartupFolder / TEXT("Binaries/Tools/Flax.Build");
+#else
+ return Globals::StartupFolder / TEXT("Binaries/Tools/Flax.Build.exe");
+#endif
+}
+
bool ScriptsBuilder::LastCompilationFailed()
{
return _lastCompilationFailed;
@@ -221,7 +230,7 @@ void ScriptsBuilder::Compile()
bool ScriptsBuilder::RunBuildTool(const StringView& args, const StringView& workingDir)
{
PROFILE_CPU();
- const String buildToolPath = Globals::StartupFolder / TEXT("Binaries/Tools/Flax.Build.exe");
+ const String buildToolPath = GetBuildToolPath();
if (!FileSystem::FileExists(buildToolPath))
{
Log::FileNotFoundException(buildToolPath).SetLevel(LogType::Fatal);
@@ -230,7 +239,7 @@ bool ScriptsBuilder::RunBuildTool(const StringView& args, const StringView& work
// Prepare build options
StringBuilder cmdLine(args.Length() + buildToolPath.Length() + 200);
-#if PLATFORM_LINUX || PLATFORM_MAC
+#if !USE_NETCORE && (PLATFORM_LINUX || PLATFORM_MAC)
const String monoPath = Globals::MonoPath / TEXT("bin/mono");
if (!FileSystem::FileExists(monoPath))
{
@@ -241,15 +250,20 @@ bool ScriptsBuilder::RunBuildTool(const StringView& args, const StringView& work
cmdLine.Append(TEXT("\""));
cmdLine.Append(monoPath);
cmdLine.Append(TEXT("\" "));
+ // TODO: Set env var for the mono MONO_GC_PARAMS=nursery-size64m to boost build performance -> profile it
#endif
cmdLine.Append(TEXT("\""));
cmdLine.Append(buildToolPath);
cmdLine.Append(TEXT("\" "));
cmdLine.Append(args.Get(), args.Length());
- // TODO: Set env var for the mono MONO_GC_PARAMS=nursery-size64m to boost build performance -> profile it
// Call build tool
- const int32 result = Platform::RunProcess(StringView(*cmdLine, cmdLine.Length()), workingDir);
+ CreateProcessSettings procSettings;
+ procSettings.FileName = StringView(*cmdLine, cmdLine.Length());
+ procSettings.WorkingDirectory = workingDir;
+ const int32 result = Platform::CreateProcess(procSettings);
+ if (result != 0)
+ LOG(Error, "Failed to run build tool, result: {0:x}", (uint32)result);
return result != 0;
}
@@ -261,7 +275,7 @@ bool ScriptsBuilder::GenerateProject(const StringView& customArgs)
return RunBuildTool(args);
}
-void ScriptsBuilderImpl::GetClassName(const MString& fullname, MString& className)
+void ScriptsBuilderImpl::GetClassName(const StringAnsi& fullname, StringAnsi& className)
{
const auto lastDotIndex = fullname.FindLast('.');
if (lastDotIndex != -1)
@@ -278,7 +292,7 @@ void ScriptsBuilderImpl::GetClassName(const MString& fullname, MString& classNam
MClass* ScriptsBuilder::FindScript(const StringView& scriptName)
{
PROFILE_CPU();
- const MString scriptNameStd = scriptName.ToStringAnsi();
+ const StringAnsi scriptNameStd = scriptName.ToStringAnsi();
const ScriptingTypeHandle scriptingType = Scripting::FindScriptingType(scriptNameStd);
if (scriptingType)
@@ -292,7 +306,7 @@ MClass* ScriptsBuilder::FindScript(const StringView& scriptName)
// Check all assemblies (ignoring the typename namespace)
auto& modules = BinaryModule::GetModules();
- MString className;
+ StringAnsi className;
GetClassName(scriptNameStd, className);
MClass* scriptClass = Script::GetStaticClass();
for (int32 j = 0; j < modules.Count(); j++)
@@ -309,7 +323,7 @@ MClass* ScriptsBuilder::FindScript(const StringView& scriptName)
// Managed scripts
if (mclass->IsSubClassOf(scriptClass) && !mclass->IsStatic() && !mclass->IsAbstract() && !mclass->IsInterface())
{
- MString mclassName;
+ StringAnsi mclassName;
GetClassName(mclass->GetFullName(), mclassName);
if (className == mclassName)
{
@@ -381,6 +395,10 @@ void ScriptsBuilder::GetBinariesConfiguration(const Char*& target, const Char*&
architecture = TEXT("x64");
#elif PLATFORM_ARCH_X86
architecture = TEXT("x86");
+#elif PLATFORM_ARCH_ARM
+ architecture = TEXT("arm");
+#elif PLATFORM_ARCH_ARM64
+ architecture = TEXT("arm64");
#else
#error "Unknown architecture"
#endif
diff --git a/Source/Editor/Scripting/ScriptsBuilder.h b/Source/Editor/Scripting/ScriptsBuilder.h
index cf0019df5..f954b0fd0 100644
--- a/Source/Editor/Scripting/ScriptsBuilder.h
+++ b/Source/Editor/Scripting/ScriptsBuilder.h
@@ -40,6 +40,11 @@ public:
///
API_PROPERTY() static int32 GetCompilationsCount();
+ ///
+ /// Gets the full path to the Flax.Build app.
+ ///
+ API_PROPERTY() static String GetBuildToolPath();
+
///
/// Checks if last scripting building failed due to errors.
///
diff --git a/Source/Editor/Scripting/TypeUtils.cs b/Source/Editor/Scripting/TypeUtils.cs
index ec8b2274e..4f66d6320 100644
--- a/Source/Editor/Scripting/TypeUtils.cs
+++ b/Source/Editor/Scripting/TypeUtils.cs
@@ -5,14 +5,12 @@ using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
-using FlaxEngine;
+using FlaxEditor;
+using FlaxEditor.Scripting;
-namespace FlaxEditor.Scripting
+namespace FlaxEngine.Utilities
{
- ///
- /// Editor scripting utilities and helper functions.
- ///
- public static class TypeUtils
+ partial class TypeUtils
{
///
/// Custom list of scripting types containers. Can be used by custom scripting languages to provide types info for the editor.
@@ -35,34 +33,6 @@ namespace FlaxEditor.Scripting
return o != null ? new ScriptType(o.GetType()) : ScriptType.Null;
}
- ///
- /// Gets the typename full name.
- ///
- /// The type.
- /// The full typename of the type.
- public static string GetTypeName(this Type type)
- {
- if (type.IsGenericType)
- {
- // For generic types (eg. Dictionary) FullName returns generic parameter types with fully qualified name so simplify it manually
- var sb = new StringBuilder();
- sb.Append(type.Namespace);
- sb.Append('.');
- sb.Append(type.Name);
- sb.Append('[');
- var genericArgs = type.GetGenericArguments();
- for (var i = 0; i < genericArgs.Length; i++)
- {
- if (i != 0)
- sb.Append(',');
- sb.Append(genericArgs[i].GetTypeName());
- }
- sb.Append(']');
- return sb.ToString();
- }
- return type.FullName;
- }
-
///
/// Gets the typename name for UI.
///
@@ -131,7 +101,7 @@ namespace FlaxEditor.Scripting
if (type.IsValueType)
{
var value = type.CreateInstance();
- Utilities.Utils.InitDefaultValues(value);
+ FlaxEditor.Utilities.Utils.InitDefaultValues(value);
return value;
}
if (ScriptType.Object.IsAssignableFrom(type))
@@ -139,7 +109,7 @@ namespace FlaxEditor.Scripting
if (type.CanCreateInstance)
{
var value = type.CreateInstance();
- Utilities.Utils.InitDefaultValues(value);
+ FlaxEditor.Utilities.Utils.InitDefaultValues(value);
return value;
}
return null;
@@ -192,7 +162,7 @@ namespace FlaxEditor.Scripting
/// Additional callback used to check if the given type is valid. Returns true if add type, otherwise false.
public static void GetDerivedTypes(ScriptType baseType, List result, Func checkFunc)
{
- var assemblies = AppDomain.CurrentDomain.GetAssemblies();
+ var assemblies = Utils.GetAssemblies();
for (int i = 0; i < assemblies.Length; i++)
{
GetDerivedTypes(assemblies[i], baseType, result, checkFunc);
@@ -209,7 +179,7 @@ namespace FlaxEditor.Scripting
public static void GetDerivedTypes(ScriptType baseType, List result, Func checkFunc, Func checkAssembly)
{
// C#/C++ types
- var assemblies = AppDomain.CurrentDomain.GetAssemblies();
+ var assemblies = Utils.GetAssemblies();
for (int i = 0; i < assemblies.Length; i++)
{
if (checkAssembly(assemblies[i]))
@@ -247,7 +217,7 @@ namespace FlaxEditor.Scripting
public static void GetTypes(List result, Func checkFunc, Func checkAssembly)
{
// C#/C++ types
- var assemblies = AppDomain.CurrentDomain.GetAssemblies();
+ var assemblies = Utils.GetAssemblies();
for (int i = 0; i < assemblies.Length; i++)
{
if (checkAssembly(assemblies[i]))
@@ -286,7 +256,7 @@ namespace FlaxEditor.Scripting
/// Additional callback used to check if the given assembly is valid. Returns true if search for types in the given assembly, otherwise false.
public static void GetTypesWithAttributeDefined(Type attributeType, List result, Func checkFunc, Func checkAssembly)
{
- var assemblies = AppDomain.CurrentDomain.GetAssemblies();
+ var assemblies = Utils.GetAssemblies();
for (int i = 0; i < assemblies.Length; i++)
{
if (checkAssembly(assemblies[i]))
@@ -303,7 +273,7 @@ namespace FlaxEditor.Scripting
{
if (string.IsNullOrEmpty(typeName))
return null;
- var assemblies = AppDomain.CurrentDomain.GetAssemblies();
+ var assemblies = Utils.GetAssemblies();
for (int i = 0; i < assemblies.Length; i++)
{
var assembly = assemblies[i];
@@ -333,7 +303,7 @@ namespace FlaxEditor.Scripting
if (type != null)
return new ScriptType(type);
}
- var assemblies = AppDomain.CurrentDomain.GetAssemblies();
+ var assemblies = Utils.GetAssemblies();
for (int i = 0; i < assemblies.Length; i++)
{
var assembly = assemblies[i];
@@ -366,7 +336,7 @@ namespace FlaxEditor.Scripting
}
}
- if (!Content.Settings.GameSettings.OptionalPlatformSettings.Contains(typeName))
+ if (!FlaxEditor.Content.Settings.GameSettings.OptionalPlatformSettings.Contains(typeName))
Editor.LogWarning($"Failed to find type '{typeName}'.");
return ScriptType.Null;
}
diff --git a/Source/Editor/States/BuildingScenesState.cs b/Source/Editor/States/BuildingScenesState.cs
index 28783e8b2..f889550c8 100644
--- a/Source/Editor/States/BuildingScenesState.cs
+++ b/Source/Editor/States/BuildingScenesState.cs
@@ -6,7 +6,9 @@ using System.Linq;
using FlaxEditor.Options;
using FlaxEditor.SceneGraph.Actors;
using FlaxEngine;
+using FlaxEditor.Utilities;
using FlaxEngine.Utilities;
+using Utils = FlaxEngine.Utils;
namespace FlaxEditor.States
{
diff --git a/Source/Editor/States/EditingSceneState.cs b/Source/Editor/States/EditingSceneState.cs
index 1e5cce41f..ded9bef30 100644
--- a/Source/Editor/States/EditingSceneState.cs
+++ b/Source/Editor/States/EditingSceneState.cs
@@ -1,6 +1,7 @@
// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.
using FlaxEngine;
+using FlaxEditor.Utilities;
using FlaxEngine.Utilities;
namespace FlaxEditor.States
diff --git a/Source/Editor/States/ReloadingScriptsState.cs b/Source/Editor/States/ReloadingScriptsState.cs
index d9024d5c8..4b8866202 100644
--- a/Source/Editor/States/ReloadingScriptsState.cs
+++ b/Source/Editor/States/ReloadingScriptsState.cs
@@ -1,6 +1,7 @@
// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.
using FlaxEngine;
+using FlaxEditor.Utilities;
using FlaxEngine.Utilities;
namespace FlaxEditor.States
diff --git a/Source/Editor/Surface/AnimGraphSurface.cs b/Source/Editor/Surface/AnimGraphSurface.cs
index 7d9c7b381..162b82270 100644
--- a/Source/Editor/Surface/AnimGraphSurface.cs
+++ b/Source/Editor/Surface/AnimGraphSurface.cs
@@ -46,6 +46,19 @@ namespace FlaxEditor.Surface
},
Size = new Float2(100, 0),
},
+ new NodeArchetype
+ {
+ TypeID = 34,
+ Create = (id, context, arch, groupArch) => new Animation.StateMachineAny(id, context, arch, groupArch),
+ Title = "Any",
+ Description = "The generic animation states machine state with source transitions from any other state",
+ Flags = NodeFlags.AnimGraph,
+ Size = new Float2(100, 0),
+ DefaultValues = new object[]
+ {
+ Utils.GetEmptyArray(),
+ },
+ },
}
}
});
diff --git a/Source/Editor/Surface/Archetypes/Animation.StateMachine.cs b/Source/Editor/Surface/Archetypes/Animation.StateMachine.cs
index 3859f842f..7e547c326 100644
--- a/Source/Editor/Surface/Archetypes/Animation.StateMachine.cs
+++ b/Source/Editor/Surface/Archetypes/Animation.StateMachine.cs
@@ -528,12 +528,12 @@ namespace FlaxEditor.Surface.Archetypes
}
///
- /// Customized for the state machine state node.
+ /// Base class for state machine state node.
///
///
///
///
- internal class StateMachineState : SurfaceNode, ISurfaceContext, IConnectionInstigator
+ internal abstract class StateMachineStateBase : SurfaceNode, IConnectionInstigator
{
internal class AddRemoveTransitionAction : IUndoAction
{
@@ -543,6 +543,7 @@ namespace FlaxEditor.Surface.Archetypes
private readonly uint _srcStateId;
private readonly uint _dstStateId;
private StateMachineTransition.Data _data;
+ private byte[] _ruleGraph;
public AddRemoveTransitionAction(StateMachineTransition transition)
{
@@ -552,6 +553,7 @@ namespace FlaxEditor.Surface.Archetypes
_dstStateId = transition.DestinationState.ID;
_isAdd = false;
transition.GetData(out _data);
+ _ruleGraph = (byte[])transition.RuleGraph.Clone();
}
public AddRemoveTransitionAction(SurfaceNode src, SurfaceNode dst)
@@ -595,32 +597,32 @@ namespace FlaxEditor.Surface.Archetypes
{
var context = _context.Get(_surface);
- var src = context.FindNode(_srcStateId) as StateMachineState;
+ var src = context.FindNode(_srcStateId) as StateMachineStateBase;
if (src == null)
throw new Exception("Missing source state.");
- var dst = context.FindNode(_dstStateId) as StateMachineState;
+ var dst = context.FindNode(_dstStateId) as StateMachineStateBase;
if (dst == null)
throw new Exception("Missing destination state.");
- var transition = new StateMachineTransition(src, dst, ref _data);
+ var transition = new StateMachineTransition(src, dst, ref _data, _ruleGraph);
src.Transitions.Add(transition);
src.UpdateTransitionsOrder();
src.UpdateTransitions();
src.UpdateTransitionsColors();
- src.SaveData();
+ src.SaveTransitions();
}
private void Remove()
{
var context = _context.Get(_surface);
- if (!(context.FindNode(_srcStateId) is StateMachineState src))
+ if (!(context.FindNode(_srcStateId) is StateMachineStateBase src))
throw new Exception("Missing source state.");
- if (!(context.FindNode(_dstStateId) is StateMachineState dst))
+ if (!(context.FindNode(_dstStateId) is StateMachineStateBase dst))
throw new Exception("Missing destination state.");
var transition = src.Transitions.Find(x => x.DestinationState == dst);
@@ -634,21 +636,23 @@ namespace FlaxEditor.Surface.Archetypes
src.UpdateTransitions();
src.UpdateTransitionsColors();
- src.SaveData();
+ src.SaveTransitions();
}
///
public void Dispose()
{
_surface = null;
+ _context = new ContextHandle();
+ _ruleGraph = null;
}
}
private bool _isSavingData;
private bool _isMouseDown;
- private Rectangle _textRect;
- private Rectangle _dragAreaRect;
- private Rectangle _renameButtonRect;
+ protected Rectangle _textRect;
+ protected Rectangle _dragAreaRect;
+ protected Rectangle _renameButtonRect;
private bool _cursorChanged = false;
private bool _textRectHovered = false;
@@ -657,40 +661,20 @@ namespace FlaxEditor.Surface.Archetypes
///
public readonly List Transitions = new List();
- ///
- /// Gets or sets the node title text.
- ///
- public string StateTitle
- {
- get => (string)Values[0];
- set
- {
- if (!string.Equals(value, (string)Values[0], StringComparison.Ordinal))
- {
- SetValue(0, value);
- }
- }
- }
-
- ///
- /// Gets or sets the state data (transitions list with rules graph and other options).
- ///
- public byte[] StateData
- {
- get => (byte[])Values[2];
- set => Values[2] = value;
- }
-
///
/// The transitions rectangle (in surface-space).
///
- public Rectangle TransitionsRectangle;
+ public Rectangle TransitionsRectangle = Rectangle.Empty;
+
+ ///
+ /// State transitions data value index (from node values).
+ ///
+ public abstract int TransitionsDataIndex { get; }
///
- public StateMachineState(uint id, VisjectSurfaceContext context, NodeArchetype nodeArch, GroupArchetype groupArch)
+ protected StateMachineStateBase(uint id, VisjectSurfaceContext context, NodeArchetype nodeArch, GroupArchetype groupArch)
: base(id, context, nodeArch, groupArch)
{
- TransitionsRectangle = Rectangle.Empty;
}
///
@@ -744,20 +728,7 @@ namespace FlaxEditor.Surface.Archetypes
base.OnValuesChanged();
if (!_isSavingData)
- LoadData();
- UpdateTitle();
- }
-
- private void UpdateTitle()
- {
- Title = StateTitle;
- var style = Style.Current;
- var titleSize = style.FontLarge.MeasureText(Title);
- var width = Mathf.Max(100, titleSize.X + 50);
- Resize(width, 0);
- titleSize.X += 8.0f;
- var padding = new Float2(8, 8);
- _dragAreaRect = new Rectangle(padding, Size - padding * 2);
+ LoadTransitions();
}
///
@@ -765,11 +736,9 @@ namespace FlaxEditor.Surface.Archetypes
{
base.UpdateRectangles();
- const float buttonMargin = FlaxEditor.Surface.Constants.NodeCloseButtonMargin;
- const float buttonSize = FlaxEditor.Surface.Constants.NodeCloseButtonSize;
- _renameButtonRect = new Rectangle(_closeButtonRect.Left - buttonSize - buttonMargin, buttonMargin, buttonSize, buttonSize);
_textRect = new Rectangle(Float2.Zero, Size);
- _dragAreaRect = _headerRect;
+ var padding = new Float2(8, 8);
+ _dragAreaRect = new Rectangle(padding, _textRect.Size - padding * 2);
}
///
@@ -777,8 +746,7 @@ namespace FlaxEditor.Surface.Archetypes
{
base.OnSurfaceLoaded();
- UpdateTitle();
- LoadData();
+ LoadTransitions();
// Register for surface mouse events to handle transition arrows interactions
Surface.CustomMouseUp += OnSurfaceMouseUp;
@@ -857,34 +825,14 @@ namespace FlaxEditor.Surface.Archetypes
}
}
- ///
- public override void OnSpawned()
- {
- base.OnSpawned();
-
- // Ensure to have unique name
- var title = StateTitle;
- var value = title;
- int count = 1;
- while (!OnRenameValidate(null, value))
- {
- value = title + " " + count++;
- }
- Values[0] = value;
- Title = value;
-
- // Let user pick a name
- StartRenaming();
- }
-
///
/// Loads the state data from the node value (reads transitions and related information).
///
- public void LoadData()
+ public void LoadTransitions()
{
- ClearData();
+ ClearTransitions();
- var bytes = StateData;
+ var bytes = (byte[])Values[TransitionsDataIndex];
if (bytes == null || bytes.Length == 0)
{
// Empty state
@@ -923,7 +871,7 @@ namespace FlaxEditor.Surface.Archetypes
if (ruleSize != 0)
rule = reader.ReadBytes(ruleSize);
- var destination = Context.FindNode(data.Destination) as StateMachineState;
+ var destination = Context.FindNode(data.Destination) as StateMachineStateBase;
if (destination == null)
{
Editor.LogWarning("Missing state machine state destination node.");
@@ -944,10 +892,10 @@ namespace FlaxEditor.Surface.Archetypes
}
///
- /// Saves the state data to the node value (writes transitions and related information).
+ /// Saves the state transitions data to the node value.
///
/// True if save data via node parameter editing via undo or without undo action.
- public void SaveData(bool withUndo = false)
+ public void SaveTransitions(bool withUndo = false)
{
try
{
@@ -998,9 +946,9 @@ namespace FlaxEditor.Surface.Archetypes
}
if (withUndo)
- SetValue(2, value);
+ SetValue(TransitionsDataIndex, value);
else
- Values[2] = value;
+ Values[TransitionsDataIndex] = value;
}
finally
{
@@ -1009,29 +957,21 @@ namespace FlaxEditor.Surface.Archetypes
}
///
- /// Clears the state data (removes transitions and related information).
+ /// Clears the state transitions.
///
- public void ClearData()
+ public void ClearTransitions()
{
Transitions.Clear();
TransitionsRectangle = Rectangle.Empty;
}
- ///
- /// Opens the state editing UI.
- ///
- public void Edit()
- {
- Surface.OpenContext(this);
- }
-
private bool IsSoloAndEnabled(StateMachineTransition t)
{
return t.Solo && t.Enabled;
}
///
- /// Updates the transitions order in the list vy using the property.
+ /// Updates the transitions order in the list by using the property.
///
public void UpdateTransitionsOrder()
{
@@ -1138,32 +1078,6 @@ namespace FlaxEditor.Surface.Archetypes
}
}
- ///
- /// Starts the state renaming by showing a rename popup to the user.
- ///
- public void StartRenaming()
- {
- Surface.Select(this);
- var dialog = RenamePopup.Show(this, _textRect, Title, false);
- dialog.Validate += OnRenameValidate;
- dialog.Renamed += OnRenamed;
- }
-
- private bool OnRenameValidate(RenamePopup popup, string value)
- {
- return Context.Nodes.All(node =>
- {
- if (node != this && node is StateMachineState state)
- return state.StateTitle != value;
- return true;
- });
- }
-
- private void OnRenamed(RenamePopup renamePopup)
- {
- StateTitle = renamePopup.Text;
- }
-
private void StartCreatingTransition()
{
Surface.ConnectingStart(this);
@@ -1216,9 +1130,6 @@ namespace FlaxEditor.Surface.Archetypes
// Close button
Render2D.DrawSprite(style.Cross, _closeButtonRect, _closeButtonRect.Contains(_mousePosition) ? style.Foreground : style.ForegroundGrey);
-
- // Rename button
- Render2D.DrawSprite(style.Settings, _renameButtonRect, _renameButtonRect.Contains(_mousePosition) ? style.Foreground : style.ForegroundGrey);
}
///
@@ -1236,8 +1147,7 @@ namespace FlaxEditor.Surface.Archetypes
if (_renameButtonRect.Contains(ref location) || _closeButtonRect.Contains(ref location))
return true;
- Edit();
- return true;
+ return false;
}
///
@@ -1269,17 +1179,7 @@ namespace FlaxEditor.Surface.Archetypes
Surface.ConnectingEnd(this);
}
- if (base.OnMouseUp(location, button))
- return true;
-
- // Rename
- if (_renameButtonRect.Contains(ref location))
- {
- StartRenaming();
- return true;
- }
-
- return false;
+ return base.OnMouseUp(location, button);
}
///
@@ -1305,7 +1205,7 @@ namespace FlaxEditor.Surface.Archetypes
{
_textRectHovered = false;
}
-
+
base.OnMouseMove(location);
}
@@ -1341,7 +1241,7 @@ namespace FlaxEditor.Surface.Archetypes
{
Transitions.Clear();
UpdateTransitions();
- SaveData(true);
+ SaveTransitions(true);
}
for (int i = 0; i < Surface.Nodes.Count; i++)
@@ -1351,7 +1251,7 @@ namespace FlaxEditor.Surface.Archetypes
// Break link
entry.FirstState = null;
}
- else if (Surface.Nodes[i] is StateMachineState state)
+ else if (Surface.Nodes[i] is StateMachineStateBase state)
{
bool modified = false;
for (int j = 0; j < state.Transitions.Count && state.Transitions.Count > 0; j++)
@@ -1366,7 +1266,7 @@ namespace FlaxEditor.Surface.Archetypes
if (modified)
{
state.UpdateTransitions();
- state.SaveData(true);
+ state.SaveTransitions(true);
}
}
}
@@ -1375,56 +1275,11 @@ namespace FlaxEditor.Surface.Archetypes
///
public override void OnDestroy()
{
- Surface.RemoveContext(this);
-
- ClearData();
+ ClearTransitions();
base.OnDestroy();
}
- ///
- public Asset SurfaceAsset => null;
-
- ///
- public string SurfaceName => StateTitle;
-
- ///
- public byte[] SurfaceData
- {
- get => (byte[])Values[1];
- set => Values[1] = value;
- }
-
- ///
- public VisjectSurfaceContext ParentContext => Context;
-
- ///
- public void OnContextCreated(VisjectSurfaceContext context)
- {
- context.Loaded += OnSurfaceLoaded;
- }
-
- private void OnSurfaceLoaded(VisjectSurfaceContext context)
- {
- // Ensure that loaded surface has output node for state
- if (context.FindNode(9, 21) == null)
- {
- var wasEnabled = true;
- if (Surface.Undo != null)
- {
- wasEnabled = Surface.Undo.Enabled;
- Surface.Undo.Enabled = false;
- }
-
- context.SpawnNode(9, 21, new Float2(100.0f));
-
- if (Surface.Undo != null)
- {
- Surface.Undo.Enabled = wasEnabled;
- }
- }
- }
-
///
public override void DrawConnections(ref Float2 mousePosition)
{
@@ -1448,7 +1303,7 @@ namespace FlaxEditor.Surface.Archetypes
///
public bool AreConnected(IConnectionInstigator other)
{
- if (other is StateMachineState otherState)
+ if (other is StateMachineStateBase otherState)
return Transitions.Any(x => x.DestinationState == otherState);
return false;
}
@@ -1473,8 +1328,7 @@ namespace FlaxEditor.Surface.Archetypes
///
public void Connect(IConnectionInstigator other)
{
- var state = (StateMachineState)other;
-
+ var state = (StateMachineStateBase)other;
var action = new AddRemoveTransitionAction(this, state);
Surface?.AddBatchedUndoAction(action);
action.Do();
@@ -1482,15 +1336,263 @@ namespace FlaxEditor.Surface.Archetypes
}
}
+ ///
+ /// Customized for the state machine state node.
+ ///
+ internal class StateMachineState : StateMachineStateBase, ISurfaceContext
+ {
+ ///
+ public StateMachineState(uint id, VisjectSurfaceContext context, NodeArchetype nodeArch, GroupArchetype groupArch)
+ : base(id, context, nodeArch, groupArch)
+ {
+ }
+
+ ///
+ /// Gets or sets the node title text.
+ ///
+ public string StateTitle
+ {
+ get => (string)Values[0];
+ set
+ {
+ if (!string.Equals(value, (string)Values[0], StringComparison.Ordinal))
+ {
+ SetValue(0, value);
+ }
+ }
+ }
+
+ ///
+ /// Opens the state editing UI.
+ ///
+ public void Edit()
+ {
+ Surface.OpenContext(this);
+ }
+
+ ///
+ /// Starts the state renaming by showing a rename popup to the user.
+ ///
+ public void StartRenaming()
+ {
+ Surface.Select(this);
+ var dialog = RenamePopup.Show(this, _textRect, Title, false);
+ dialog.Validate += OnRenameValidate;
+ dialog.Renamed += OnRenamed;
+ }
+
+ private bool OnRenameValidate(RenamePopup popup, string value)
+ {
+ return Context.Nodes.All(node =>
+ {
+ if (node != this && node is StateMachineState state)
+ return state.StateTitle != value;
+ return true;
+ });
+ }
+
+ private void OnRenamed(RenamePopup renamePopup)
+ {
+ StateTitle = renamePopup.Text;
+ }
+
+ private void UpdateTitle()
+ {
+ Title = StateTitle;
+ var style = Style.Current;
+ var titleSize = style.FontLarge.MeasureText(Title);
+ var width = Mathf.Max(100, titleSize.X + 50);
+ Resize(width, 0);
+ titleSize.X += 8.0f;
+ var padding = new Float2(8, 8);
+ _dragAreaRect = new Rectangle(padding, Size - padding * 2);
+ }
+
+ private void OnSurfaceLoaded(VisjectSurfaceContext context)
+ {
+ // Ensure that loaded surface has output node for state
+ if (context.FindNode(9, 21) == null)
+ {
+ var wasEnabled = true;
+ if (Surface.Undo != null)
+ {
+ wasEnabled = Surface.Undo.Enabled;
+ Surface.Undo.Enabled = false;
+ }
+
+ context.SpawnNode(9, 21, new Float2(100.0f));
+
+ if (Surface.Undo != null)
+ {
+ Surface.Undo.Enabled = wasEnabled;
+ }
+ }
+ }
+
+ ///
+ public override int TransitionsDataIndex => 2;
+
+ ///
+ public override void OnSpawned()
+ {
+ base.OnSpawned();
+
+ // Ensure to have unique name
+ var title = StateTitle;
+ var value = title;
+ int count = 1;
+ while (!OnRenameValidate(null, value))
+ {
+ value = title + " " + count++;
+ }
+ Values[0] = value;
+ Title = value;
+
+ // Let user pick a name
+ StartRenaming();
+ }
+
+ ///
+ public override void OnSurfaceLoaded()
+ {
+ base.OnSurfaceLoaded();
+
+ UpdateTitle();
+ }
+
+ ///
+ public override void OnValuesChanged()
+ {
+ base.OnValuesChanged();
+
+ UpdateTitle();
+ }
+
+ ///
+ public override void Draw()
+ {
+ base.Draw();
+
+ var style = Style.Current;
+
+ // Rename button
+ Render2D.DrawSprite(style.Settings, _renameButtonRect, _renameButtonRect.Contains(_mousePosition) ? style.Foreground : style.ForegroundGrey);
+ }
+
+ ///
+ public override bool OnMouseUp(Float2 location, MouseButton button)
+ {
+ if (base.OnMouseUp(location, button))
+ return true;
+
+ // Rename
+ if (_renameButtonRect.Contains(ref location))
+ {
+ StartRenaming();
+ return true;
+ }
+
+ return false;
+ }
+
+ ///
+ public override bool OnMouseDoubleClick(Float2 location, MouseButton button)
+ {
+ if (base.OnMouseDoubleClick(location, button))
+ return true;
+
+ Edit();
+ return true;
+ }
+
+ ///
+ public override void OnDestroy()
+ {
+ Surface.RemoveContext(this);
+
+ base.OnDestroy();
+ }
+
+ ///
+ protected override void UpdateRectangles()
+ {
+ base.UpdateRectangles();
+
+ const float buttonMargin = FlaxEditor.Surface.Constants.NodeCloseButtonMargin;
+ const float buttonSize = FlaxEditor.Surface.Constants.NodeCloseButtonSize;
+ _renameButtonRect = new Rectangle(_closeButtonRect.Left - buttonSize - buttonMargin, buttonMargin, buttonSize, buttonSize);
+ }
+
+ ///
+ public Asset SurfaceAsset => null;
+
+ ///
+ public string SurfaceName => StateTitle;
+
+ ///
+ public byte[] SurfaceData
+ {
+ get => (byte[])Values[1];
+ set => Values[1] = value;
+ }
+
+ ///
+ public VisjectSurfaceContext ParentContext => Context;
+
+ ///
+ public void OnContextCreated(VisjectSurfaceContext context)
+ {
+ context.Loaded += OnSurfaceLoaded;
+ }
+ }
+
+ ///
+ /// Customized for the state machine Any state node.
+ ///
+ internal class StateMachineAny : StateMachineStateBase
+ {
+ ///
+ public StateMachineAny(uint id, VisjectSurfaceContext context, NodeArchetype nodeArch, GroupArchetype groupArch)
+ : base(id, context, nodeArch, groupArch)
+ {
+ }
+
+ ///
+ public override int TransitionsDataIndex => 0;
+ }
+
///
/// State machine transition data container object.
///
- ///
+ ///
///
internal class StateMachineTransition : ISurfaceContext
{
+ ///
+ /// State transition interruption flags.
+ ///
+ [Flags]
+ public enum InterruptionFlags
+ {
+ ///
+ /// Nothing.
+ ///
+ None = 0,
+
+ ///
+ /// Transition rule will be rechecked during active transition with option to interrupt transition.
+ ///
+ RuleRechecking = 1,
+
+ ///
+ /// Interrupted transition is immediately stopped without blending out.
+ ///
+ Instant = 2,
+ }
+
///
/// The packed data container for the transition data storage. Helps with serialization and versioning the data.
+ /// Must match AnimGraphBase::LoadStateTransition in C++
///
///
/// It does not store GC objects references to make it more lightweight. Transition rule bytes data is stores in a separate way.
@@ -1498,31 +1600,16 @@ namespace FlaxEditor.Surface.Archetypes
[StructLayout(LayoutKind.Sequential, Pack = 1, Size = 32)]
internal struct Data
{
- ///
- /// The transition flag types.
- ///
+ // Must match AnimGraphStateTransition::FlagTypes
[Flags]
public enum FlagTypes
{
- ///
- /// The none.
- ///
None = 0,
-
- ///
- /// The enabled flag.
- ///
Enabled = 1,
-
- ///
- /// The solo flag.
- ///
Solo = 2,
-
- ///
- /// The use default rule flag.
- ///
UseDefaultRule = 4,
+ InterruptionRuleRechecking = 8,
+ InterruptionInstant = 16,
}
///
@@ -1565,21 +1652,11 @@ namespace FlaxEditor.Surface.Archetypes
///
public int Unused2;
- ///
- /// Determines whether the data has a given flag set.
- ///
- /// The flag.
- /// true if the specified flag is set; otherwise, false.
public bool HasFlag(FlagTypes flag)
{
return (Flags & flag) == flag;
}
- ///
- /// Sets the flag to the given value.
- ///
- /// The flag.
- /// If set to true the flag will be set, otherwise it will be cleared.
public void SetFlag(FlagTypes flag, bool value)
{
if (value)
@@ -1596,18 +1673,18 @@ namespace FlaxEditor.Surface.Archetypes
/// The transition start state.
///
[HideInEditor]
- public readonly StateMachineState SourceState;
+ public readonly StateMachineStateBase SourceState;
///
/// The transition end state.
///
[HideInEditor]
- public readonly StateMachineState DestinationState;
+ public readonly StateMachineStateBase DestinationState;
///
/// If checked, the transition can be triggered, otherwise it will be ignored.
///
- [EditorOrder(10), DefaultValue(true), Tooltip("If checked, the transition can be triggered, otherwise it will be ignored.")]
+ [EditorOrder(10), DefaultValue(true)]
public bool Enabled
{
get => _data.HasFlag(Data.FlagTypes.Enabled);
@@ -1615,14 +1692,14 @@ namespace FlaxEditor.Surface.Archetypes
{
_data.SetFlag(Data.FlagTypes.Enabled, value);
SourceState.UpdateTransitionsColors();
- SourceState.SaveData(true);
+ SourceState.SaveTransitions(true);
}
}
///
/// If checked, animation graph will ignore other transitions from the source state and use only this transition.
///
- [EditorOrder(20), DefaultValue(false), Tooltip("If checked, animation graph will ignore other transitions from the source state and use only this transition.")]
+ [EditorOrder(20), DefaultValue(false)]
public bool Solo
{
get => _data.HasFlag(Data.FlagTypes.Solo);
@@ -1630,28 +1707,28 @@ namespace FlaxEditor.Surface.Archetypes
{
_data.SetFlag(Data.FlagTypes.Solo, value);
SourceState.UpdateTransitionsColors();
- SourceState.SaveData(true);
+ SourceState.SaveTransitions(true);
}
}
///
/// If checked, animation graph will perform automatic transition based on the state animation pose (single shot animation play).
///
- [EditorOrder(30), DefaultValue(false), Tooltip("If checked, animation graph will perform automatic transition based on the state animation pose (single shot animation play).")]
+ [EditorOrder(30), DefaultValue(false)]
public bool UseDefaultRule
{
get => _data.HasFlag(Data.FlagTypes.UseDefaultRule);
set
{
_data.SetFlag(Data.FlagTypes.UseDefaultRule, value);
- SourceState.SaveData(true);
+ SourceState.SaveTransitions(true);
}
}
///
- /// The transition order (higher first).
+ /// The transition order. Transitions with the higher order are handled before the ones with the lower order.
///
- [EditorOrder(40), DefaultValue(0), Tooltip("The transition order. Transitions with the higher order are handled before the ones with the lower order.")]
+ [EditorOrder(40), DefaultValue(0)]
public int Order
{
get => _data.Order;
@@ -1660,35 +1737,58 @@ namespace FlaxEditor.Surface.Archetypes
_data.Order = value;
SourceState.UpdateTransitionsOrder();
SourceState.UpdateTransitionsColors();
- SourceState.SaveData(true);
+ SourceState.SaveTransitions(true);
}
}
///
/// The blend duration (in seconds).
///
- [EditorOrder(50), DefaultValue(0.1f), Limit(0, 20.0f, 0.1f), Tooltip("Transition blend duration (in seconds).")]
+ [EditorOrder(50), DefaultValue(0.1f), Limit(0, 20.0f, 0.1f)]
public float BlendDuration
{
get => _data.BlendDuration;
set
{
_data.BlendDuration = value;
- SourceState.SaveData(true);
+ SourceState.SaveTransitions(true);
}
}
///
- /// The blend mode.
+ /// Transition blending mode for blend alpha.
///
- [EditorOrder(60), DefaultValue(AlphaBlendMode.HermiteCubic), Tooltip("Transition blending mode for blend alpha.")]
+ [EditorOrder(60), DefaultValue(AlphaBlendMode.HermiteCubic)]
public AlphaBlendMode BlendMode
{
get => _data.BlendMode;
set
{
_data.BlendMode = value;
- SourceState.SaveData(true);
+ SourceState.SaveTransitions(true);
+ }
+ }
+
+ ///
+ /// Transition interruption options.
+ ///
+ [EditorOrder(70), DefaultValue(InterruptionFlags.None)]
+ public InterruptionFlags Interruption
+ {
+ get
+ {
+ var flags = InterruptionFlags.None;
+ if (_data.HasFlag(Data.FlagTypes.InterruptionRuleRechecking))
+ flags |= InterruptionFlags.RuleRechecking;
+ if (_data.HasFlag(Data.FlagTypes.InterruptionInstant))
+ flags |= InterruptionFlags.Instant;
+ return flags;
+ }
+ set
+ {
+ _data.SetFlag(Data.FlagTypes.InterruptionRuleRechecking, value.HasFlag(InterruptionFlags.RuleRechecking));
+ _data.SetFlag(Data.FlagTypes.InterruptionInstant, value.HasFlag(InterruptionFlags.Instant));
+ SourceState.SaveTransitions(true);
}
}
@@ -1702,7 +1802,7 @@ namespace FlaxEditor.Surface.Archetypes
set
{
_ruleGraph = value ?? Utils.GetEmptyArray();
- SourceState.SaveData();
+ SourceState.SaveTransitions();
}
}
@@ -1737,7 +1837,7 @@ namespace FlaxEditor.Surface.Archetypes
/// The destination.
/// The transition data container.
/// The transition rule graph. Can be null.
- public StateMachineTransition(StateMachineState source, StateMachineState destination, ref Data data, byte[] ruleGraph = null)
+ public StateMachineTransition(StateMachineStateBase source, StateMachineStateBase destination, ref Data data, byte[] ruleGraph = null)
{
SourceState = source;
DestinationState = destination;
@@ -1759,7 +1859,7 @@ namespace FlaxEditor.Surface.Archetypes
public Asset SurfaceAsset => null;
///
- public string SurfaceName => string.Format("{0} to {1}", SourceState.StateTitle, DestinationState.StateTitle);
+ public string SurfaceName => string.Format("{0} to {1}", SourceState.Title, DestinationState.Title);
///
[HideInEditor]
@@ -1807,7 +1907,7 @@ namespace FlaxEditor.Surface.Archetypes
///
public void Delete()
{
- var action = new StateMachineState.AddRemoveTransitionAction(this);
+ var action = new StateMachineStateBase.AddRemoveTransitionAction(this);
SourceState.Surface?.AddBatchedUndoAction(action);
action.Do();
}
diff --git a/Source/Editor/Surface/Archetypes/Animation.cs b/Source/Editor/Surface/Archetypes/Animation.cs
index a83e60deb..6364106e4 100644
--- a/Source/Editor/Surface/Archetypes/Animation.cs
+++ b/Source/Editor/Surface/Archetypes/Animation.cs
@@ -986,6 +986,19 @@ namespace FlaxEditor.Surface.Archetypes
NodeElementArchetype.Factory.Input(0, "Init", true, typeof(Float4), 1),
}
},
+ new NodeArchetype
+ {
+ TypeID = 34,
+ Create = (id, context, arch, groupArch) => new StateMachineAny(id, context, arch, groupArch),
+ Title = "Any",
+ Description = "The generic animation states machine state with source transitions from any other state",
+ Flags = NodeFlags.AnimGraph | NodeFlags.NoSpawnViaGUI | NodeFlags.NoSpawnViaPaste,
+ Size = new Float2(100, 0),
+ DefaultValues = new object[]
+ {
+ Utils.GetEmptyArray(),
+ },
+ },
};
}
}
diff --git a/Source/Editor/Surface/Archetypes/Constants.cs b/Source/Editor/Surface/Archetypes/Constants.cs
index 122093cbb..72a77ea4d 100644
--- a/Source/Editor/Surface/Archetypes/Constants.cs
+++ b/Source/Editor/Surface/Archetypes/Constants.cs
@@ -14,6 +14,7 @@ using FlaxEditor.Scripting;
using FlaxEditor.Surface.Elements;
using FlaxEngine;
using FlaxEngine.GUI;
+using FlaxEngine.Utilities;
namespace FlaxEditor.Surface.Archetypes
{
diff --git a/Source/Editor/Surface/Archetypes/Function.cs b/Source/Editor/Surface/Archetypes/Function.cs
index d68a1378f..cffb2ad6f 100644
--- a/Source/Editor/Surface/Archetypes/Function.cs
+++ b/Source/Editor/Surface/Archetypes/Function.cs
@@ -12,6 +12,7 @@ using FlaxEditor.GUI.ContextMenu;
using FlaxEditor.Scripting;
using FlaxEditor.Surface.Elements;
using FlaxEditor.Windows.Assets;
+using FlaxEngine.Utilities;
using FlaxEngine;
using FlaxEngine.GUI;
@@ -587,7 +588,7 @@ namespace FlaxEditor.Surface.Archetypes
for (int i = 0; i < _parameters.Length; i++)
{
writer.Write(_parameters[i].Name); // Parameter name
- Utilities.VariantUtils.WriteVariantType(writer, TypeUtils.GetType(_parameters[i].Type)); // Box type
+ VariantUtils.WriteVariantType(writer, TypeUtils.GetType(_parameters[i].Type)); // Box type
}
SetValue(2, stream.ToArray());
}
@@ -605,7 +606,7 @@ namespace FlaxEditor.Surface.Archetypes
for (int i = 0; i < parametersCount; i++)
{
var parameterName = reader.ReadString(); // Parameter name
- var boxType = Utilities.VariantUtils.ReadVariantType(reader); // Box type
+ var boxType = VariantUtils.ReadVariantType(reader); // Box type
MakeBox(i + 1, parameterName, boxType);
}
}
@@ -788,14 +789,14 @@ namespace FlaxEditor.Surface.Archetypes
{
reader.ReadByte(); // Version
signature.IsStatic = reader.ReadBoolean(); // Is Static
- signature.ReturnType = Utilities.VariantUtils.ReadVariantScriptType(reader); // Return type
+ signature.ReturnType = VariantUtils.ReadVariantScriptType(reader); // Return type
var parametersCount = reader.ReadInt32(); // Parameters count
signature.Params = parametersCount != 0 ? new SignatureParamInfo[parametersCount] : Utils.GetEmptyArray();
for (int i = 0; i < parametersCount; i++)
{
ref var param = ref signature.Params[i];
param.Name = Utilities.Utils.ReadStr(reader, 11); // Parameter name
- param.Type = Utilities.VariantUtils.ReadVariantScriptType(reader); // Parameter type
+ param.Type = VariantUtils.ReadVariantScriptType(reader); // Parameter type
param.IsOut = reader.ReadByte() != 0; // Is parameter out
}
}
@@ -809,14 +810,14 @@ namespace FlaxEditor.Surface.Archetypes
{
reader.ReadByte(); // Version
signature.IsStatic = reader.ReadBoolean(); // Is Static
- signature.ReturnType = Utilities.VariantUtils.ReadVariantScriptType(reader); // Return type
+ signature.ReturnType = VariantUtils.ReadVariantScriptType(reader); // Return type
var parametersCount = reader.ReadInt32(); // Parameters count
signature.Params = parametersCount != 0 ? new SignatureParamInfo[parametersCount] : Utils.GetEmptyArray();
for (int i = 0; i < parametersCount; i++)
{
ref var param = ref signature.Params[i];
param.Name = reader.ReadString(); // Parameter name
- param.Type = Utilities.VariantUtils.ReadVariantScriptType(reader); // Parameter type
+ param.Type = VariantUtils.ReadVariantScriptType(reader); // Parameter type
param.IsOut = reader.ReadByte() != 0; // Is parameter out
}
}
@@ -833,13 +834,13 @@ namespace FlaxEditor.Surface.Archetypes
{
writer.Write((byte)4); // Version
writer.Write(methodInfo.IsStatic); // Is Static
- Utilities.VariantUtils.WriteVariantType(writer, methodInfo.ValueType); // Return type
+ VariantUtils.WriteVariantType(writer, methodInfo.ValueType); // Return type
writer.Write(parameters.Length); // Parameters count
for (int i = 0; i < parameters.Length; i++)
{
ref var param = ref parameters[i];
Utilities.Utils.WriteStr(writer, param.Name, 11); // Parameter name
- Utilities.VariantUtils.WriteVariantType(writer, param.Type); // Parameter type
+ VariantUtils.WriteVariantType(writer, param.Type); // Parameter type
writer.Write((byte)(param.IsOut ? 1 : 0)); // Is parameter out
}
return stream.ToArray();
@@ -1461,14 +1462,14 @@ namespace FlaxEditor.Surface.Archetypes
if (_signature.IsVirtual)
flags |= Flags.Virtual;
writer.Write((byte)flags); // Flags
- Utilities.VariantUtils.WriteVariantType(writer, _signature.ReturnType); // Return Type
+ VariantUtils.WriteVariantType(writer, _signature.ReturnType); // Return Type
var parametersCount = _signature.Parameters?.Length ?? 0;
writer.Write(parametersCount); // Parameters count
for (int i = 0; i < parametersCount; i++)
{
ref var param = ref _signature.Parameters[i];
Utilities.Utils.WriteStrAnsi(writer, param.Name, 13); // Parameter name
- Utilities.VariantUtils.WriteVariantType(writer, param.Type); // Parameter type
+ VariantUtils.WriteVariantType(writer, param.Type); // Parameter type
writer.Write((byte)0); // Is parameter out
writer.Write((byte)0); // Has default value
}
@@ -1497,13 +1498,13 @@ namespace FlaxEditor.Surface.Archetypes
var flags = (Flags)reader.ReadByte(); // Flags
_signature.IsStatic = (flags & Flags.Static) == Flags.Static;
_signature.IsVirtual = (flags & Flags.Virtual) == Flags.Virtual;
- _signature.ReturnType = Utilities.VariantUtils.ReadVariantScriptType(reader); // Return Type
+ _signature.ReturnType = VariantUtils.ReadVariantScriptType(reader); // Return Type
var parametersCount = reader.ReadInt32(); // Parameters count
_signature.Parameters = new Parameter[parametersCount];
for (int i = 0; i < parametersCount; i++)
{
var paramName = Utilities.Utils.ReadStrAnsi(reader, 13); // Parameter name
- var paramType = Utilities.VariantUtils.ReadVariantScriptType(reader); // Parameter type
+ var paramType = VariantUtils.ReadVariantScriptType(reader); // Parameter type
var isOut = reader.ReadByte() != 0; // Is parameter out
var hasDefaultValue = reader.ReadByte() != 0; // Has default value
_signature.Parameters[i] = new Parameter
diff --git a/Source/Editor/Surface/Archetypes/Packing.cs b/Source/Editor/Surface/Archetypes/Packing.cs
index 54b578133..5a5e9cd5b 100644
--- a/Source/Editor/Surface/Archetypes/Packing.cs
+++ b/Source/Editor/Surface/Archetypes/Packing.cs
@@ -6,6 +6,7 @@ using System.Linq;
using System.Reflection;
using FlaxEditor.Scripting;
using FlaxEditor.Surface.Elements;
+using FlaxEngine.Utilities;
using FlaxEngine;
namespace FlaxEditor.Surface.Archetypes
@@ -167,7 +168,7 @@ namespace FlaxEditor.Surface.Archetypes
for (int i = 0; i < fieldsLength; i++)
{
Utilities.Utils.WriteStr(writer, fields[i].Name, 11); // Field type
- Utilities.VariantUtils.WriteVariantType(writer, fields[i].ValueType); // Field type
+ VariantUtils.WriteVariantType(writer, fields[i].ValueType); // Field type
}
Values[1] = stream.ToArray();
}
@@ -184,7 +185,7 @@ namespace FlaxEditor.Surface.Archetypes
for (int i = 0; i < fieldsLength; i++)
{
var fieldName = Utilities.Utils.ReadStr(reader, 11); // Field name
- var fieldType = Utilities.VariantUtils.ReadVariantType(reader); // Field type
+ var fieldType = VariantUtils.ReadVariantType(reader); // Field type
MakeBox(i + 1, fieldName, new ScriptType(fieldType));
}
}
diff --git a/Source/Editor/Surface/Archetypes/Parameters.cs b/Source/Editor/Surface/Archetypes/Parameters.cs
index 179ff8d84..c7f343cfb 100644
--- a/Source/Editor/Surface/Archetypes/Parameters.cs
+++ b/Source/Editor/Surface/Archetypes/Parameters.cs
@@ -12,6 +12,7 @@ using FlaxEditor.GUI;
using FlaxEditor.Scripting;
using FlaxEditor.Surface.Elements;
using FlaxEngine;
+using FlaxEngine.Utilities;
namespace FlaxEditor.Surface.Archetypes
{
diff --git a/Source/Editor/Surface/Archetypes/Tools.cs b/Source/Editor/Surface/Archetypes/Tools.cs
index 082cbc88f..9f6db7ea4 100644
--- a/Source/Editor/Surface/Archetypes/Tools.cs
+++ b/Source/Editor/Surface/Archetypes/Tools.cs
@@ -10,6 +10,7 @@ using FlaxEditor.Scripting;
using FlaxEditor.Surface.Elements;
using FlaxEngine;
using FlaxEngine.GUI;
+using FlaxEngine.Utilities;
namespace FlaxEditor.Surface.Archetypes
{
@@ -1523,9 +1524,9 @@ namespace FlaxEditor.Surface.Archetypes
Title = "Platform Switch",
Description = "Gets the input value based on the runtime-platform type",
Flags = NodeFlags.AllGraphs,
- Size = new Float2(220, 220),
+ Size = new Float2(220, 240),
ConnectionsHints = ConnectionsHint.Value,
- IndependentBoxes = new[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11 },
+ IndependentBoxes = new[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12 },
DependentBoxes = new[] { 0 },
Elements = new[]
{
@@ -1541,6 +1542,7 @@ namespace FlaxEditor.Surface.Archetypes
NodeElementArchetype.Factory.Input(8, "Switch", true, null, 9),
NodeElementArchetype.Factory.Input(9, "PlayStation 5", true, null, 10),
NodeElementArchetype.Factory.Input(10, "Mac", true, null, 11),
+ NodeElementArchetype.Factory.Input(11, "iOS", true, null, 12),
}
},
new NodeArchetype
diff --git a/Source/Editor/Surface/AttributesEditor.cs b/Source/Editor/Surface/AttributesEditor.cs
index 830c7b6be..81b11bb68 100644
--- a/Source/Editor/Surface/AttributesEditor.cs
+++ b/Source/Editor/Surface/AttributesEditor.cs
@@ -5,6 +5,7 @@ using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
+using System.Runtime.Loader;
using System.Runtime.Serialization.Formatters.Binary;
using FlaxEditor.CustomEditors;
using FlaxEditor.CustomEditors.Editors;
@@ -118,8 +119,13 @@ namespace FlaxEditor.Surface
using (var stream = new MemoryStream())
{
+ // Ensure we are in the correct load context (https://github.com/dotnet/runtime/issues/42041)
+ using var ctx = AssemblyLoadContext.EnterContextualReflection(typeof(Editor).Assembly);
+
var formatter = new BinaryFormatter();
+#pragma warning disable SYSLIB0011
formatter.Serialize(stream, attributes);
+#pragma warning restore SYSLIB0011
_oldData = stream.ToArray();
}
editor.Select(new Proxy
@@ -141,8 +147,13 @@ namespace FlaxEditor.Surface
}
using (var stream = new MemoryStream())
{
+ // Ensure we are in the correct load context (https://github.com/dotnet/runtime/issues/42041)
+ using var ctx = AssemblyLoadContext.EnterContextualReflection(typeof(Editor).Assembly);
+
var formatter = new BinaryFormatter();
+#pragma warning disable SYSLIB0011
formatter.Serialize(stream, newValue);
+#pragma warning restore SYSLIB0011
var newData = stream.ToArray();
if (!_oldData.SequenceEqual(newData))
{
diff --git a/Source/Editor/Surface/ContextMenu/VisjectCM.cs b/Source/Editor/Surface/ContextMenu/VisjectCM.cs
index 102fe88e2..3f74e4c07 100644
--- a/Source/Editor/Surface/ContextMenu/VisjectCM.cs
+++ b/Source/Editor/Surface/ContextMenu/VisjectCM.cs
@@ -8,6 +8,7 @@ using FlaxEditor.GUI.Input;
using FlaxEditor.Scripting;
using FlaxEngine;
using FlaxEngine.GUI;
+using FlaxEngine.Utilities;
namespace FlaxEditor.Surface.ContextMenu
{
@@ -135,7 +136,6 @@ namespace FlaxEditor.Surface.ContextMenu
Width = Width - 4,
X = 2,
Y = 1,
- BackgroundColor = Style.Current.BackgroundNormal,
};
// Title bar
diff --git a/Source/Editor/Surface/Elements/ActorSelect.cs b/Source/Editor/Surface/Elements/ActorSelect.cs
index 6da454ccf..3be493db7 100644
--- a/Source/Editor/Surface/Elements/ActorSelect.cs
+++ b/Source/Editor/Surface/Elements/ActorSelect.cs
@@ -3,6 +3,7 @@
using System;
using FlaxEditor.CustomEditors.Editors;
using FlaxEngine;
+using FlaxEngine.Utilities;
namespace FlaxEditor.Surface.Elements
{
@@ -30,7 +31,7 @@ namespace FlaxEditor.Surface.Elements
ParentNode = parentNode;
Archetype = archetype;
Bounds = new Rectangle(Archetype.ActualPosition, archetype.Size);
- Type = Scripting.TypeUtils.GetType(archetype.Text);
+ Type = TypeUtils.GetType(archetype.Text);
ParentNode.ValuesChanged += OnNodeValuesChanged;
OnNodeValuesChanged();
diff --git a/Source/Editor/Surface/Elements/AssetSelect.cs b/Source/Editor/Surface/Elements/AssetSelect.cs
index 933451259..e38989e08 100644
--- a/Source/Editor/Surface/Elements/AssetSelect.cs
+++ b/Source/Editor/Surface/Elements/AssetSelect.cs
@@ -3,6 +3,7 @@
using System;
using FlaxEditor.GUI;
using FlaxEngine;
+using FlaxEngine.Utilities;
namespace FlaxEditor.Surface.Elements
{
@@ -26,7 +27,7 @@ namespace FlaxEditor.Surface.Elements
/// The parent node.
/// The archetype.
public AssetSelect(SurfaceNode parentNode, NodeElementArchetype archetype)
- : base(Scripting.TypeUtils.GetType(archetype.Text), archetype.ActualPosition)
+ : base(TypeUtils.GetType(archetype.Text), archetype.ActualPosition)
{
ParentNode = parentNode;
Archetype = archetype;
diff --git a/Source/Editor/Surface/Elements/EnumValue.cs b/Source/Editor/Surface/Elements/EnumValue.cs
index 09591e55a..065646a53 100644
--- a/Source/Editor/Surface/Elements/EnumValue.cs
+++ b/Source/Editor/Surface/Elements/EnumValue.cs
@@ -2,6 +2,7 @@
using System;
using FlaxEditor.GUI;
+using FlaxEngine.Utilities;
namespace FlaxEditor.Surface.Elements
{
@@ -26,7 +27,7 @@ namespace FlaxEditor.Surface.Elements
///
public EnumValue(SurfaceNode parentNode, NodeElementArchetype archetype)
- : base(Scripting.TypeUtils.GetType(archetype.Text).Type)
+ : base(TypeUtils.GetType(archetype.Text).Type)
{
X = archetype.ActualPositionX;
Y = archetype.ActualPositionY;
diff --git a/Source/Editor/Surface/Elements/InputBox.cs b/Source/Editor/Surface/Elements/InputBox.cs
index a8954b797..4eac03105 100644
--- a/Source/Editor/Surface/Elements/InputBox.cs
+++ b/Source/Editor/Surface/Elements/InputBox.cs
@@ -17,6 +17,7 @@ using FlaxEditor.GUI.Input;
using FlaxEditor.Scripting;
using FlaxEngine;
using FlaxEngine.GUI;
+using FlaxEngine.Utilities;
using Newtonsoft.Json;
using JsonSerializer = FlaxEngine.Json.JsonSerializer;
diff --git a/Source/Editor/Surface/SurfaceMeta.cs b/Source/Editor/Surface/SurfaceMeta.cs
index 9cc7d51b4..460dcb302 100644
--- a/Source/Editor/Surface/SurfaceMeta.cs
+++ b/Source/Editor/Surface/SurfaceMeta.cs
@@ -4,6 +4,8 @@ using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
+using System.Reflection;
+using System.Runtime.Loader;
using System.Runtime.Serialization.Formatters.Binary;
using FlaxEngine;
@@ -54,8 +56,13 @@ namespace FlaxEditor.Surface
{
try
{
+ // Ensure we are in the correct load context (https://github.com/dotnet/runtime/issues/42041)
+ using var ctx = AssemblyLoadContext.EnterContextualReflection(typeof(Editor).Assembly);
+
var formatter = new BinaryFormatter();
+#pragma warning disable SYSLIB0011
return (Attribute[])formatter.Deserialize(stream);
+#pragma warning restore SYSLIB0011
}
catch (Exception ex)
{
@@ -122,8 +129,13 @@ namespace FlaxEditor.Surface
}
using (var stream = new MemoryStream())
{
+ // Ensure we are in the correct load context (https://github.com/dotnet/runtime/issues/42041)
+ using var ctx = AssemblyLoadContext.EnterContextualReflection(typeof(Editor).Assembly);
+
var formatter = new BinaryFormatter();
+#pragma warning disable SYSLIB0011
formatter.Serialize(stream, attributes);
+#pragma warning restore SYSLIB0011
AddEntry(AttributeMetaTypeID, stream.ToArray());
}
}
diff --git a/Source/Editor/Surface/SurfaceParameter.cs b/Source/Editor/Surface/SurfaceParameter.cs
index cfec4104a..6eafdc2aa 100644
--- a/Source/Editor/Surface/SurfaceParameter.cs
+++ b/Source/Editor/Surface/SurfaceParameter.cs
@@ -3,6 +3,7 @@
using System;
using FlaxEditor.Scripting;
using FlaxEngine;
+using FlaxEngine.Utilities;
namespace FlaxEditor.Surface
{
diff --git a/Source/Editor/Surface/SurfaceStyle.cs b/Source/Editor/Surface/SurfaceStyle.cs
index f3c2c6f18..18fa70e4e 100644
--- a/Source/Editor/Surface/SurfaceStyle.cs
+++ b/Source/Editor/Surface/SurfaceStyle.cs
@@ -3,6 +3,7 @@
using System;
using FlaxEditor.Scripting;
using FlaxEngine;
+using FlaxEngine.Utilities;
namespace FlaxEditor.Surface
{
diff --git a/Source/Editor/Surface/SurfaceUtils.cs b/Source/Editor/Surface/SurfaceUtils.cs
index be3066178..08b9c6d63 100644
--- a/Source/Editor/Surface/SurfaceUtils.cs
+++ b/Source/Editor/Surface/SurfaceUtils.cs
@@ -10,6 +10,7 @@ using FlaxEditor.CustomEditors;
using FlaxEditor.CustomEditors.Elements;
using FlaxEditor.Scripting;
using FlaxEditor.Utilities;
+using FlaxEngine.Utilities;
using FlaxEngine;
namespace FlaxEditor.Surface
@@ -405,7 +406,9 @@ namespace FlaxEditor.Surface
internal static bool IsValidVisualScriptType(ScriptType scriptType)
{
- if (!scriptType.IsPublic || scriptType.HasAttribute(typeof(HideInEditorAttribute), true))
+ if (!scriptType.IsPublic ||
+ scriptType.HasAttribute(typeof(HideInEditorAttribute), true) ||
+ scriptType.HasAttribute(typeof(System.Runtime.CompilerServices.CompilerGeneratedAttribute), false))
return false;
if (scriptType.IsGenericType)
{
diff --git a/Source/Editor/Surface/VisjectSurface.CopyPaste.cs b/Source/Editor/Surface/VisjectSurface.CopyPaste.cs
index ba7684127..3a5be4857 100644
--- a/Source/Editor/Surface/VisjectSurface.CopyPaste.cs
+++ b/Source/Editor/Surface/VisjectSurface.CopyPaste.cs
@@ -6,6 +6,7 @@ using FlaxEditor.Scripting;
using FlaxEditor.Surface.Elements;
using FlaxEditor.Surface.Undo;
using FlaxEngine;
+using FlaxEngine.Utilities;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
diff --git a/Source/Editor/Surface/VisjectSurfaceContext.Serialization.cs b/Source/Editor/Surface/VisjectSurfaceContext.Serialization.cs
index b20d849a2..f2059c581 100644
--- a/Source/Editor/Surface/VisjectSurfaceContext.Serialization.cs
+++ b/Source/Editor/Surface/VisjectSurfaceContext.Serialization.cs
@@ -8,6 +8,7 @@ using FlaxEditor.Scripting;
using FlaxEditor.Surface.Elements;
using FlaxEditor.Utilities;
using FlaxEngine;
+using FlaxEngine.Utilities;
using Utils = FlaxEditor.Utilities.Utils;
namespace FlaxEditor.Surface
diff --git a/Source/Editor/Surface/VisjectSurfaceWindow.cs b/Source/Editor/Surface/VisjectSurfaceWindow.cs
index 0b3bc8130..305121e11 100644
--- a/Source/Editor/Surface/VisjectSurfaceWindow.cs
+++ b/Source/Editor/Surface/VisjectSurfaceWindow.cs
@@ -368,7 +368,7 @@ namespace FlaxEditor.Surface
}
if (!asset.IsLoaded)
{
- layout.Label("Loading...");
+ layout.Label("Loading...", TextAlignment.Center);
return;
}
var parameters = window.VisjectSurface.Parameters;
@@ -472,7 +472,7 @@ namespace FlaxEditor.Surface
{
Window = window,
IsAdd = true,
- Name = StringUtils.IncrementNameNumber("New parameter", x => OnParameterRenameValidate(null, x)),
+ Name = Utilities.Utils.IncrementNameNumber("New parameter", x => OnParameterRenameValidate(null, x)),
Type = type,
Index = window.VisjectSurface.Parameters.Count,
};
@@ -1003,18 +1003,15 @@ namespace FlaxEditor.Surface
///
public override void OnLayoutSerialize(XmlWriter writer)
{
- writer.WriteAttributeString("Split1", _split1.SplitterValue.ToString());
- writer.WriteAttributeString("Split2", _split2.SplitterValue.ToString());
+ LayoutSerializeSplitter(writer, "Split1", _split1);
+ LayoutSerializeSplitter(writer, "Split2", _split2);
}
///
public override void OnLayoutDeserialize(XmlElement node)
{
- if (float.TryParse(node.GetAttribute("Split1"), out float value1))
- _split1.SplitterValue = value1;
-
- if (float.TryParse(node.GetAttribute("Split2"), out value1))
- _split2.SplitterValue = value1;
+ LayoutDeserializeSplitter(node, "Split1", _split1);
+ LayoutDeserializeSplitter(node, "Split2", _split2);
}
///
diff --git a/Source/Editor/Surface/VisualScriptSurface.cs b/Source/Editor/Surface/VisualScriptSurface.cs
index b40fb1603..e961f686d 100644
--- a/Source/Editor/Surface/VisualScriptSurface.cs
+++ b/Source/Editor/Surface/VisualScriptSurface.cs
@@ -21,6 +21,7 @@ using FlaxEditor.Surface.ContextMenu;
using FlaxEditor.Surface.Elements;
using FlaxEngine;
using FlaxEngine.GUI;
+using FlaxEngine.Utilities;
using Object = FlaxEngine.Object;
namespace FlaxEditor.Surface
diff --git a/Source/Editor/Tools/Terrain/Sculpt/NoiseMode.cs b/Source/Editor/Tools/Terrain/Sculpt/NoiseMode.cs
index fb52adb0e..32d02bb3f 100644
--- a/Source/Editor/Tools/Terrain/Sculpt/NoiseMode.cs
+++ b/Source/Editor/Tools/Terrain/Sculpt/NoiseMode.cs
@@ -1,6 +1,7 @@
// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.
using FlaxEngine;
+using FlaxEditor.Utilities;
using FlaxEngine.Utilities;
namespace FlaxEditor.Tools.Terrain.Sculpt
diff --git a/Source/Editor/Undo/Actions/AddRemoveScriptAction.cs b/Source/Editor/Undo/Actions/AddRemoveScriptAction.cs
index 567bd39e9..69a790b39 100644
--- a/Source/Editor/Undo/Actions/AddRemoveScriptAction.cs
+++ b/Source/Editor/Undo/Actions/AddRemoveScriptAction.cs
@@ -3,6 +3,7 @@
using System;
using FlaxEditor.Scripting;
using FlaxEngine;
+using FlaxEngine.Utilities;
using Object = FlaxEngine.Object;
namespace FlaxEditor.Actions
diff --git a/Source/Editor/Undo/Actions/DeleteActorsAction.cs b/Source/Editor/Undo/Actions/DeleteActorsAction.cs
index 740f41306..292497a4c 100644
--- a/Source/Editor/Undo/Actions/DeleteActorsAction.cs
+++ b/Source/Editor/Undo/Actions/DeleteActorsAction.cs
@@ -5,6 +5,7 @@ using System.Collections.Generic;
using FlaxEditor.SceneGraph;
using FlaxEditor.Scripting;
using FlaxEngine;
+using FlaxEngine.Utilities;
namespace FlaxEditor.Actions
{
diff --git a/Source/Editor/Undo/Actions/PasteActorsAction.cs b/Source/Editor/Undo/Actions/PasteActorsAction.cs
index 2a70c06cf..c90e94189 100644
--- a/Source/Editor/Undo/Actions/PasteActorsAction.cs
+++ b/Source/Editor/Undo/Actions/PasteActorsAction.cs
@@ -153,7 +153,7 @@ namespace FlaxEditor.Actions
if (child != actor && child.Name == actor.Name)
{
var children = parent.Children;
- actor.Name = StringUtils.IncrementNameNumber(name, x => children.All(y => y.Name != x));
+ actor.Name = Utilities.Utils.IncrementNameNumber(name, x => children.All(y => y.Name != x));
}
}
}
diff --git a/Source/Editor/Utilities/EditorScene.cpp b/Source/Editor/Utilities/EditorScene.cpp
index 764b79184..7060baac5 100644
--- a/Source/Editor/Utilities/EditorScene.cpp
+++ b/Source/Editor/Utilities/EditorScene.cpp
@@ -20,4 +20,6 @@ void EditorScene::Update()
e.Call();
for (auto& e : Ticking.FixedUpdate.Ticks)
e.Call();
+ for (auto& e : Ticking.LateFixedUpdate.Ticks)
+ e.Call();
}
diff --git a/Source/Editor/Utilities/EditorUtilities.cpp b/Source/Editor/Utilities/EditorUtilities.cpp
index 3fba14859..8e3fc0e4a 100644
--- a/Source/Editor/Utilities/EditorUtilities.cpp
+++ b/Source/Editor/Utilities/EditorUtilities.cpp
@@ -4,6 +4,7 @@
#include "Engine/Engine/Globals.h"
#include "Engine/Platform/File.h"
#include "Engine/Platform/FileSystem.h"
+#include "Engine/Platform/CreateProcessSettings.h"
#include "Engine/Core/Log.h"
#include "Engine/Graphics/Textures/TextureData.h"
#include "Engine/Graphics/PixelFormatExtensions.h"
@@ -14,6 +15,9 @@
#include "Engine/Content/AssetReference.h"
#include "Engine/Content/Assets/Texture.h"
#include "Engine/Utilities/StringConverter.h"
+#if PLATFORM_MAC
+#include "Engine/Platform/Apple/ApplePlatformSettings.h"
+#endif
#include
#define MSDOS_SIGNATURE 0x5A4D
@@ -503,6 +507,37 @@ bool EditorUtilities::UpdateExeIcon(const String& path, const TextureData& icon)
return false;
}
+bool EditorUtilities::FormatAppPackageName(String& packageName)
+{
+ const auto gameSettings = GameSettings::Get();
+ String productName = gameSettings->ProductName;
+ productName.Replace(TEXT(" "), TEXT(""));
+ productName.Replace(TEXT("."), TEXT(""));
+ productName.Replace(TEXT("-"), TEXT(""));
+ String companyName = gameSettings->CompanyName;
+ companyName.Replace(TEXT(" "), TEXT(""));
+ companyName.Replace(TEXT("."), TEXT(""));
+ companyName.Replace(TEXT("-"), TEXT(""));
+ packageName.Replace(TEXT("${PROJECT_NAME}"), *productName, StringSearchCase::IgnoreCase);
+ packageName.Replace(TEXT("${COMPANY_NAME}"), *companyName, StringSearchCase::IgnoreCase);
+ packageName = packageName.ToLower();
+ for (int32 i = 0; i < packageName.Length(); i++)
+ {
+ const auto c = packageName[i];
+ if (c != '_' && c != '.' && !StringUtils::IsAlnum(c))
+ {
+ LOG(Error, "App identifier \'{0}\' contains invalid character. Only letters, numbers, dots and underscore characters are allowed.", packageName);
+ return true;
+ }
+ }
+ if (packageName.IsEmpty())
+ {
+ LOG(Error, "App identifier is empty.", packageName);
+ return true;
+ }
+ return false;
+}
+
bool EditorUtilities::GetApplicationImage(const Guid& imageId, TextureData& imageData, ApplicationImageType type)
{
AssetReference icon = Content::LoadAsync(imageId);
@@ -726,8 +761,9 @@ bool EditorUtilities::GenerateCertificate(const String& name, const String& outp
// MakeCert
auto path = wdkPath / TEXT("makecert.exe");
- auto args = String::Format(TEXT("\"{0}\" /r /h 0 /eku \"1.3.6.1.5.5.7.3.3,1.3.6.1.4.1.311.10.3.13\" /m 12 /len 2048 /n \"CN={1}\" -sv \"{2}\" \"{3}\""), path, name, outputPvkFilePath, outputCerFilePath);
- int32 result = Platform::RunProcess(args, String::Empty);
+ CreateProcessSettings procSettings;
+ procSettings.FileName = String::Format(TEXT("\"{0}\" /r /h 0 /eku \"1.3.6.1.5.5.7.3.3,1.3.6.1.4.1.311.10.3.13\" /m 12 /len 2048 /n \"CN={1}\" -sv \"{2}\" \"{3}\""), path, name, outputPvkFilePath, outputCerFilePath);
+ int32 result = Platform::CreateProcess(procSettings);
if (result != 0)
{
LOG(Warning, "MakeCert failed with result {0}.", result);
@@ -736,8 +772,8 @@ bool EditorUtilities::GenerateCertificate(const String& name, const String& outp
// Pvk2Pfx
path = wdkPath / TEXT("pvk2pfx.exe");
- args = String::Format(TEXT("\"{0}\" -pvk \"{1}\" -spc \"{2}\" -pfx \"{3}\""), path, outputPvkFilePath, outputCerFilePath, outputPfxFilePath);
- result = Platform::RunProcess(args, String::Empty);
+ procSettings.FileName = String::Format(TEXT("\"{0}\" -pvk \"{1}\" -spc \"{2}\" -pfx \"{3}\""), path, outputPvkFilePath, outputCerFilePath, outputPfxFilePath);
+ result = Platform::CreateProcess(procSettings);
if (result != 0)
{
LOG(Warning, "MakeCert failed with result {0}.", result);
@@ -818,3 +854,59 @@ bool EditorUtilities::ReplaceInFile(const StringView& file, const StringView& fi
text.Replace(findWhat.Get(), findWhat.Length(), replaceWith.Get(), replaceWith.Length());
return File::WriteAllText(file, text, Encoding::ANSI);
}
+
+bool EditorUtilities::ReplaceInFile(const StringView& file, const Dictionary& replaceMap)
+{
+ String text;
+ if (File::ReadAllText(file, text))
+ return true;
+ for (const auto& e : replaceMap)
+ text.Replace(e.Key.Get(), e.Key.Length(), e.Value.Get(), e.Value.Length());
+ return File::WriteAllText(file, text, Encoding::ANSI);
+}
+
+bool EditorUtilities::CopyFileIfNewer(const StringView& dst, const StringView& src)
+{
+ if (FileSystem::FileExists(dst) &&
+ FileSystem::GetFileLastEditTime(src) <= FileSystem::GetFileLastEditTime(dst) &&
+ FileSystem::GetFileSize(dst) == FileSystem::GetFileSize(src))
+ return false;
+ return FileSystem::CopyFile(dst, src);
+}
+
+bool EditorUtilities::CopyDirectoryIfNewer(const StringView& dst, const StringView& src, bool withSubDirectories)
+{
+ if (FileSystem::DirectoryExists(dst))
+ {
+ // Copy all files
+ Array cache(32);
+ if (FileSystem::DirectoryGetFiles(cache, *src, TEXT("*"), DirectorySearchOption::TopDirectoryOnly))
+ return true;
+ for (int32 i = 0; i < cache.Count(); i++)
+ {
+ String dstFile = String(dst) / StringUtils::GetFileName(cache[i]);
+ if (CopyFileIfNewer(*dstFile, *cache[i]))
+ return true;
+ }
+
+ // Copy all subdirectories (if need to)
+ if (withSubDirectories)
+ {
+ cache.Clear();
+ if (FileSystem::GetChildDirectories(cache, src))
+ return true;
+ for (int32 i = 0; i < cache.Count(); i++)
+ {
+ String dstDir = String(dst) / StringUtils::GetFileName(cache[i]);
+ if (CopyDirectoryIfNewer(dstDir, cache[i], true))
+ return true;
+ }
+ }
+
+ return false;
+ }
+ else
+ {
+ return FileSystem::CopyDirectory(dst, src, withSubDirectories);
+ }
+}
diff --git a/Source/Editor/Utilities/EditorUtilities.h b/Source/Editor/Utilities/EditorUtilities.h
index 37fb28a2a..881bfe3b8 100644
--- a/Source/Editor/Utilities/EditorUtilities.h
+++ b/Source/Editor/Utilities/EditorUtilities.h
@@ -30,6 +30,7 @@ public:
/// True if fails, otherwise false.
static bool UpdateExeIcon(const String& path, const TextureData& icon);
+ static bool FormatAppPackageName(String& packageName);
static bool GetApplicationImage(const Guid& imageId, TextureData& imageData, ApplicationImageType type = ApplicationImageType::Icon);
static bool GetTexture(const Guid& textureId, TextureData& textureData);
static bool ExportApplicationImage(const Guid& iconId, int32 width, int32 height, PixelFormat format, const String& path, ApplicationImageType type = ApplicationImageType::Icon);
@@ -82,4 +83,8 @@ public:
/// The value to replace to.
/// True if failed, otherwise false.
static bool ReplaceInFile(const StringView& file, const StringView& findWhat, const StringView& replaceWith);
+ static bool ReplaceInFile(const StringView& file, const Dictionary& replaceMap);
+
+ static bool CopyFileIfNewer(const StringView& dst, const StringView& src);
+ static bool CopyDirectoryIfNewer(const StringView& dst, const StringView& src, bool withSubDirectories);
};
diff --git a/Source/Editor/Utilities/ObjectSnapshot.cs b/Source/Editor/Utilities/ObjectSnapshot.cs
index 2a1c320f3..a0e5e2c97 100644
--- a/Source/Editor/Utilities/ObjectSnapshot.cs
+++ b/Source/Editor/Utilities/ObjectSnapshot.cs
@@ -10,6 +10,7 @@ using System.Reflection;
using FlaxEditor.Scripting;
using FlaxEngine;
using FlaxEngine.Json;
+using FlaxEngine.Utilities;
namespace FlaxEditor.Utilities
{
diff --git a/Source/Editor/Utilities/Utils.cs b/Source/Editor/Utilities/Utils.cs
index 7397b743e..d980b0c4a 100644
--- a/Source/Editor/Utilities/Utils.cs
+++ b/Source/Editor/Utilities/Utils.cs
@@ -13,6 +13,7 @@ using System.IO;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Text;
+using System.Text.RegularExpressions;
using FlaxEditor.GUI.ContextMenu;
using FlaxEditor.GUI.Input;
using FlaxEditor.GUI.Tree;
@@ -20,6 +21,7 @@ using FlaxEditor.SceneGraph;
using FlaxEditor.Scripting;
using FlaxEngine;
using FlaxEngine.GUI;
+using FlaxEngine.Utilities;
namespace FlaxEngine
{
@@ -37,6 +39,8 @@ namespace FlaxEditor.Utilities
public static class Utils
{
private static readonly StringBuilder CachedSb = new StringBuilder(256);
+ private static readonly Regex IncNameRegex1 = new Regex("(\\d+)$");
+ private static readonly Regex IncNameRegex2 = new Regex("\\((\\d+)\\)$");
private static readonly string[] MemorySizePostfixes =
{
@@ -53,6 +57,92 @@ namespace FlaxEditor.Utilities
///
public static readonly string FlaxEngineAssemblyName = "FlaxEngine.CSharp";
+ ///
+ /// Tries to parse number in the name brackets at the end of the value and then increment it to create a new name.
+ /// Supports numbers at the end without brackets.
+ ///
+ /// The input name.
+ /// Custom function to validate the created name.
+ /// The new name.
+ public static string IncrementNameNumber(string name, Func isValid)
+ {
+ // Validate input name
+ if (isValid == null || isValid(name))
+ return name;
+
+ // Temporary data
+ int index;
+ int MaxChecks = 10000;
+ string result;
+
+ // Find '' case
+ var match = IncNameRegex1.Match(name);
+ if (match.Success && match.Groups.Count == 2)
+ {
+ // Get result
+ string num = match.Groups[0].Value;
+
+ // Parse value
+ if (int.TryParse(num, out index))
+ {
+ // Get prefix
+ string prefix = name.Substring(0, name.Length - num.Length);
+
+ // Generate name
+ do
+ {
+ result = string.Format("{0}{1}", prefix, ++index);
+
+ if (MaxChecks-- < 0)
+ return name + Guid.NewGuid();
+ } while (!isValid(result));
+
+ if (result.Length > 0)
+ return result;
+ }
+ }
+
+ // Find ' ()' case
+ match = IncNameRegex2.Match(name);
+ if (match.Success && match.Groups.Count == 2)
+ {
+ // Get result
+ string num = match.Groups[0].Value;
+ num = num.Substring(1, num.Length - 2);
+
+ // Parse value
+ if (int.TryParse(num, out index))
+ {
+ // Get prefix
+ string prefix = name.Substring(0, name.Length - num.Length - 2);
+
+ // Generate name
+ do
+ {
+ result = string.Format("{0}({1})", prefix, ++index);
+
+ if (MaxChecks-- < 0)
+ return name + Guid.NewGuid();
+ } while (!isValid(result));
+
+ if (result.Length > 0)
+ return result;
+ }
+ }
+
+ // Generate name
+ index = 0;
+ do
+ {
+ result = string.Format("{0} {1}", name, index++);
+
+ if (MaxChecks-- < 0)
+ return name + Guid.NewGuid();
+ } while (!isValid(result));
+
+ return result;
+ }
+
///
/// Formats the amount of bytes to get a human-readable data size in bytes with abbreviation. Eg. 32 kB
///
@@ -1084,7 +1174,7 @@ namespace FlaxEditor.Utilities
internal static string FormatFloat(string str, bool isNegative)
{
// Reference: https://stackoverflow.com/questions/1546113/double-to-string-conversion-without-scientific-notation
- int x = str.IndexOf('E');
+ int x = str.IndexOf('E', StringComparison.OrdinalIgnoreCase);
if (x < 0)
return str;
int x1 = x + 1;
diff --git a/Source/Editor/Viewport/MainEditorGizmoViewport.cs b/Source/Editor/Viewport/MainEditorGizmoViewport.cs
index 43f5cb63c..48cd4fd98 100644
--- a/Source/Editor/Viewport/MainEditorGizmoViewport.cs
+++ b/Source/Editor/Viewport/MainEditorGizmoViewport.cs
@@ -422,7 +422,7 @@ namespace FlaxEditor.Viewport
var actor = new Camera
{
StaticFlags = StaticFlags.None,
- Name = StringUtils.IncrementNameNumber("Camera", x => parent.GetChild(x) == null),
+ Name = Utilities.Utils.IncrementNameNumber("Camera", x => parent.GetChild(x) == null),
Transform = ViewTransform,
NearPlane = NearPlane,
FarPlane = FarPlane,
@@ -998,7 +998,7 @@ namespace FlaxEditor.Viewport
{
actor.Position = PostProcessSpawnedActorLocation(actor, ref hitLocation);
var parent = actor.Parent ?? Level.GetScene(0);
- actor.Name = StringUtils.IncrementNameNumber(actor.Name, x => parent.GetChild(x) == null);
+ actor.Name = Utilities.Utils.IncrementNameNumber(actor.Name, x => parent.GetChild(x) == null);
Editor.Instance.SceneEditing.Spawn(actor);
Focus();
}
diff --git a/Source/Editor/Viewport/Previews/AnimatedModelPreview.cs b/Source/Editor/Viewport/Previews/AnimatedModelPreview.cs
index 268295405..2310668b6 100644
--- a/Source/Editor/Viewport/Previews/AnimatedModelPreview.cs
+++ b/Source/Editor/Viewport/Previews/AnimatedModelPreview.cs
@@ -15,8 +15,8 @@ namespace FlaxEditor.Viewport.Previews
///
public class AnimatedModelPreview : AssetPreview
{
- private ContextMenuButton _showNodesButton, _showBoundsButton, _showFloorButton;
- private bool _showNodes, _showBounds, _showFloor, _showCurrentLOD;
+ private ContextMenuButton _showNodesButton, _showBoundsButton, _showFloorButton, _showNodesNamesButton;
+ private bool _showNodes, _showBounds, _showFloor, _showCurrentLOD, _showNodesNames;
private AnimatedModel _previewModel;
private StaticModel _floorModel;
private ContextMenuButton _showCurrentLODButton;
@@ -98,6 +98,24 @@ namespace FlaxEditor.Viewport.Previews
}
}
+ ///
+ /// Gets or sets a value indicating whether show animated model skeleton nodes names debug view.
+ ///
+ public bool ShowNodesNames
+ {
+ get => _showNodesNames;
+ set
+ {
+ if (_showNodesNames == value)
+ return;
+ _showNodesNames = value;
+ if (value)
+ ShowDebugDraw = true;
+ if (_showNodesNamesButton != null)
+ _showNodesNamesButton.Checked = value;
+ }
+ }
+
///
/// Gets or sets a value indicating whether show animated model bounding box debug view.
///
@@ -183,6 +201,9 @@ namespace FlaxEditor.Viewport.Previews
// Show Skeleton
_showNodesButton = ViewWidgetShowMenu.AddButton("Skeleton", () => ShowNodes = !ShowNodes);
+ // Show Skeleton Names
+ _showNodesNamesButton = ViewWidgetShowMenu.AddButton("Skeleton Names", () => ShowNodesNames = !ShowNodesNames);
+
// Show Floor
_showFloorButton = ViewWidgetShowMenu.AddButton("Floor", button => ShowFloor = !ShowFloor);
_showFloorButton.IndexInParent = 1;
@@ -252,6 +273,41 @@ namespace FlaxEditor.Viewport.Previews
_playAnimationOnce = true;
}
+ ///
+ /// Gets the skinned model bounds. Handles skeleton-only assets.
+ ///
+ /// The local bounds.
+ public BoundingBox GetBounds()
+ {
+ var box = BoundingBox.Zero;
+ var skinnedModel = SkinnedModel;
+ if (skinnedModel && skinnedModel.IsLoaded)
+ {
+ if (skinnedModel.LODs.Length != 0)
+ {
+ // Use model geometry bounds
+ box = skinnedModel.GetBox();
+ }
+ else
+ {
+ // Use skeleton bounds
+ _previewModel.GetCurrentPose(out var pose);
+ if (pose != null && pose.Length != 0)
+ {
+ var point = pose[0].TranslationVector;
+ box = new BoundingBox(point, point);
+ for (int i = 1; i < pose.Length; i++)
+ {
+ point = pose[i].TranslationVector;
+ box.Minimum = Vector3.Min(box.Minimum, point);
+ box.Maximum = Vector3.Max(box.Maximum, point);
+ }
+ }
+ }
+ }
+ return box;
+ }
+
private void OnBegin(RenderTask task, GPUContext context)
{
if (!ScaleToFit)
@@ -269,7 +325,7 @@ namespace FlaxEditor.Viewport.Previews
if (skinnedModel && skinnedModel.IsLoaded)
{
float targetSize = 50.0f;
- BoundingBox box = skinnedModel.GetBox();
+ BoundingBox box = GetBounds();
float maxSize = Mathf.Max(0.001f, (float)box.Size.MaxValue);
float scale = targetSize / maxSize;
_previewModel.Scale = new Vector3(scale);
@@ -327,37 +383,51 @@ namespace FlaxEditor.Viewport.Previews
base.OnDebugDraw(context, ref renderContext);
// Draw skeleton nodes
- if (_showNodes)
+ if (_showNodes || _showNodesNames)
{
_previewModel.GetCurrentPose(out var pose, true);
var nodes = _previewModel.SkinnedModel?.Nodes;
if (pose != null && pose.Length != 0 && nodes != null)
{
- // Draw bounding box at the node locations
var nodesMask = NodesMask != null && NodesMask.Length == nodes.Length ? NodesMask : null;
- var localBox = new OrientedBoundingBox(new Vector3(-1.0f), new Vector3(1.0f));
- for (int nodeIndex = 0; nodeIndex < pose.Length; nodeIndex++)
+ if (_showNodes)
{
- if (nodesMask != null && !nodesMask[nodeIndex])
- continue;
- var transform = pose[nodeIndex];
- transform.Decompose(out var scale, out Matrix _, out _);
- transform = Matrix.Invert(Matrix.Scaling(scale)) * transform;
- var box = localBox * transform;
- DebugDraw.DrawWireBox(box, Color.Green, 0, false);
- }
-
- // Nodes connections
- for (int nodeIndex = 0; nodeIndex < nodes.Length; nodeIndex++)
- {
- int parentIndex = nodes[nodeIndex].ParentIndex;
- if (parentIndex != -1)
+ // Draw bounding box at the node locations
+ var localBox = new OrientedBoundingBox(new Vector3(-1.0f), new Vector3(1.0f));
+ for (int nodeIndex = 0; nodeIndex < pose.Length; nodeIndex++)
{
- if (nodesMask != null && (!nodesMask[nodeIndex] || !nodesMask[parentIndex]))
+ if (nodesMask != null && !nodesMask[nodeIndex])
continue;
- var parentPos = pose[parentIndex].TranslationVector;
- var bonePos = pose[nodeIndex].TranslationVector;
- DebugDraw.DrawLine(parentPos, bonePos, Color.Green, 0, false);
+ var transform = pose[nodeIndex];
+ transform.Decompose(out var scale, out Matrix _, out _);
+ transform = Matrix.Invert(Matrix.Scaling(scale)) * transform;
+ var box = localBox * transform;
+ DebugDraw.DrawWireBox(box, Color.Green, 0, false);
+ }
+
+ // Nodes connections
+ for (int nodeIndex = 0; nodeIndex < nodes.Length; nodeIndex++)
+ {
+ int parentIndex = nodes[nodeIndex].ParentIndex;
+ if (parentIndex != -1)
+ {
+ if (nodesMask != null && (!nodesMask[nodeIndex] || !nodesMask[parentIndex]))
+ continue;
+ var parentPos = pose[parentIndex].TranslationVector;
+ var bonePos = pose[nodeIndex].TranslationVector;
+ DebugDraw.DrawLine(parentPos, bonePos, Color.Green, 0, false);
+ }
+ }
+ }
+ if (_showNodesNames)
+ {
+ // Nodes names
+ for (int nodeIndex = 0; nodeIndex < nodes.Length; nodeIndex++)
+ {
+ if (nodesMask != null && !nodesMask[nodeIndex])
+ continue;
+ //var t = new Transform(pose[nodeIndex].TranslationVector, Quaternion.Identity, new Float3(0.1f));
+ DebugDraw.DrawText(nodes[nodeIndex].Name, pose[nodeIndex].TranslationVector, Color.White, 20, 0.0f, 0.1f);
}
}
}
@@ -376,13 +446,21 @@ namespace FlaxEditor.Viewport.Previews
base.Draw();
var skinnedModel = _previewModel.SkinnedModel;
- if (_showCurrentLOD && skinnedModel)
+ if (skinnedModel == null || !skinnedModel.IsLoaded)
+ return;
+ var lods = skinnedModel.LODs;
+ if (lods.Length == 0)
+ {
+ // Force show skeleton for models without geometry
+ ShowNodes = true;
+ return;
+ }
+ if (_showCurrentLOD)
{
var lodIndex = ComputeLODIndex(skinnedModel);
string text = string.Format("Current LOD: {0}", lodIndex);
if (lodIndex != -1)
{
- var lods = skinnedModel.LODs;
lodIndex = Mathf.Clamp(lodIndex + PreviewActor.LODBias, 0, lods.Length - 1);
var lod = lods[lodIndex];
int triangleCount = 0, vertexCount = 0;
@@ -448,6 +526,7 @@ namespace FlaxEditor.Viewport.Previews
_showBoundsButton = null;
_showFloorButton = null;
_showCurrentLODButton = null;
+ _showNodesNamesButton = null;
base.OnDestroy();
}
diff --git a/Source/Editor/Viewport/Previews/AssetPreview.cs b/Source/Editor/Viewport/Previews/AssetPreview.cs
index 1267fded6..0bcc4e115 100644
--- a/Source/Editor/Viewport/Previews/AssetPreview.cs
+++ b/Source/Editor/Viewport/Previews/AssetPreview.cs
@@ -128,6 +128,18 @@ namespace FlaxEditor.Viewport.Previews
///
public EditorPrimitives EditorPrimitives => _editorPrimitives;
+ ///
+ /// Custom debug drawing event (via ).
+ ///
+ public event CustomDebugDrawDelegate CustomDebugDraw;
+
+ ///
+ /// Debug shapes drawing delegate.
+ ///
+ /// The GPU context.
+ /// The render context.
+ public delegate void CustomDebugDrawDelegate(GPUContext context, ref RenderContext renderContext);
+
///
/// Initializes a new instance of the class.
///
@@ -249,6 +261,7 @@ namespace FlaxEditor.Viewport.Previews
{
DebugDraw.SetContext(_debugDrawContext);
DebugDraw.UpdateContext(_debugDrawContext, 1.0f / Mathf.Max(Engine.FramesPerSecond, 1));
+ CustomDebugDraw?.Invoke(context, ref renderContext);
OnDebugDraw(context, ref renderContext);
DebugDraw.Draw(ref renderContext, target.View(), targetDepth.View(), true);
DebugDraw.SetContext(IntPtr.Zero);
diff --git a/Source/Editor/Viewport/Previews/CubeTexturePreview.cs b/Source/Editor/Viewport/Previews/CubeTexturePreview.cs
index 08a402eb4..e65349cb8 100644
--- a/Source/Editor/Viewport/Previews/CubeTexturePreview.cs
+++ b/Source/Editor/Viewport/Previews/CubeTexturePreview.cs
@@ -243,7 +243,7 @@ namespace FlaxEditor.Viewport.Previews
public override void OnDestroy()
{
Material = null;
- Object.Destroy(ref _previewMaterial);
+ FlaxEngine.Object.Destroy(ref _previewMaterial);
base.OnDestroy();
}
diff --git a/Source/Editor/ViewportDebugDrawData.cs b/Source/Editor/ViewportDebugDrawData.cs
index 10bc19c45..48b551675 100644
--- a/Source/Editor/ViewportDebugDrawData.cs
+++ b/Source/Editor/ViewportDebugDrawData.cs
@@ -2,6 +2,7 @@
using System;
using System.Collections.Generic;
+using System.Runtime.InteropServices;
using FlaxEngine;
namespace FlaxEditor
@@ -20,7 +21,7 @@ namespace FlaxEditor
private int[] _highlightIndicesSet;
private Model _highlightTrianglesModel;
- internal IntPtr[] ActorsPtrs => Utils.ExtractArrayFromList(_actors);
+ internal Span ActorsPtrs => CollectionsMarshal.AsSpan(_actors);
internal int ActorsCount => _actors.Count;
diff --git a/Source/Editor/Windows/AboutDialog.cs b/Source/Editor/Windows/AboutDialog.cs
index 6ef152e28..4b9595445 100644
--- a/Source/Editor/Windows/AboutDialog.cs
+++ b/Source/Editor/Windows/AboutDialog.cs
@@ -125,6 +125,9 @@ namespace FlaxEditor.Windows
"Used third party software:",
"",
"Mono Project - www.mono-project.com",
+#if USE_NETCORE
+ ".NET - www.dotnet.microsoft.com",
+#endif
"FreeType Project - www.freetype.org",
"Assimp - www.assimp.sourceforge.net",
"DirectXMesh - Copyright (c) Microsoft Corporation. All rights reserved.",
diff --git a/Source/Editor/Windows/Assets/AnimationWindow.cs b/Source/Editor/Windows/Assets/AnimationWindow.cs
index db812d442..a765c2faa 100644
--- a/Source/Editor/Windows/Assets/AnimationWindow.cs
+++ b/Source/Editor/Windows/Assets/AnimationWindow.cs
@@ -1,6 +1,7 @@
// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.
using System;
+using System.Globalization;
using System.Reflection;
using System.Xml;
using FlaxEditor.Content;
@@ -143,7 +144,7 @@ namespace FlaxEditor.Windows.Assets
Asset = window.Asset;
// Try to restore target asset import options (useful for fast reimport)
- ModelImportSettings.TryRestore(ref ImportSettings, window.Item.Path);
+ Editor.TryRestoreImportOptions(ref ImportSettings.Settings, window.Item.Path);
}
public void OnClean()
@@ -165,10 +166,9 @@ namespace FlaxEditor.Windows.Assets
public override void Initialize(LayoutElementsContainer layout)
{
var proxy = (PropertiesProxy)Values[0];
-
if (proxy.Asset == null || !proxy.Asset.IsLoaded)
{
- layout.Label("Loading...");
+ layout.Label("Loading...", TextAlignment.Center);
return;
}
@@ -374,12 +374,12 @@ namespace FlaxEditor.Windows.Assets
///
public override void OnLayoutSerialize(XmlWriter writer)
{
- writer.WriteAttributeString("TimelineSplitter", _timeline.Splitter.SplitterValue.ToString());
+ LayoutSerializeSplitter(writer, "TimelineSplitter", _timeline.Splitter);
+ LayoutSerializeSplitter(writer, "Panel1Splitter", _panel1);
+ if (_panel2 != null)
+ LayoutSerializeSplitter(writer, "Panel2Splitter", _panel2);
writer.WriteAttributeString("TimeShowMode", _timeline.TimeShowMode.ToString());
writer.WriteAttributeString("ShowPreviewValues", _timeline.ShowPreviewValues.ToString());
- writer.WriteAttributeString("Panel1Splitter", _panel1.SplitterValue.ToString());
- if (_panel2 != null)
- writer.WriteAttributeString("Panel2Splitter", _panel2.SplitterValue.ToString());
if (_properties.PreviewModel)
writer.WriteAttributeString("PreviewModel", _properties.PreviewModel.ID.ToString());
}
@@ -387,18 +387,16 @@ namespace FlaxEditor.Windows.Assets
///
public override void OnLayoutDeserialize(XmlElement node)
{
- if (Guid.TryParse(node.GetAttribute("PreviewModel"), out Guid value4))
- _initialPreviewModel = FlaxEngine.Content.LoadAsync(value4);
- if (float.TryParse(node.GetAttribute("TimelineSplitter"), out float value1))
- _timeline.Splitter.SplitterValue = value1;
- if (float.TryParse(node.GetAttribute("Panel1Splitter"), out value1))
- _panel1.SplitterValue = value1;
- if (float.TryParse(node.GetAttribute("Panel2Splitter"), out value1))
+ LayoutDeserializeSplitter(node, "TimelineSplitter", _timeline.Splitter);
+ LayoutDeserializeSplitter(node, "Panel1Splitter", _panel1);
+ if (float.TryParse(node.GetAttribute("Panel2Splitter"), CultureInfo.InvariantCulture, out float value1) && value1 > 0.01f && value1 < 0.99f)
_initialPanel2Splitter = value1;
if (Enum.TryParse(node.GetAttribute("TimeShowMode"), out Timeline.TimeShowModes value2))
_timeline.TimeShowMode = value2;
if (bool.TryParse(node.GetAttribute("ShowPreviewValues"), out bool value3))
_timeline.ShowPreviewValues = value3;
+ if (Guid.TryParse(node.GetAttribute("PreviewModel"), out Guid value4))
+ _initialPreviewModel = FlaxEngine.Content.LoadAsync(value4);
}
///
diff --git a/Source/Editor/Windows/Assets/AudioClipWindow.cs b/Source/Editor/Windows/Assets/AudioClipWindow.cs
index dddfaa296..f5f244c58 100644
--- a/Source/Editor/Windows/Assets/AudioClipWindow.cs
+++ b/Source/Editor/Windows/Assets/AudioClipWindow.cs
@@ -284,10 +284,10 @@ namespace FlaxEditor.Windows.Assets
{
_preview.Source = null;
_previewSource.Stop();
- Object.Destroy(_previewSource);
+ FlaxEngine.Object.Destroy(_previewSource);
_previewSource = null;
}
- Object.Destroy(ref _previewScene);
+ FlaxEngine.Object.Destroy(ref _previewScene);
base.OnDestroy();
}
@@ -336,14 +336,13 @@ namespace FlaxEditor.Windows.Assets
///
public override void OnLayoutSerialize(XmlWriter writer)
{
- writer.WriteAttributeString("Split", _split.SplitterValue.ToString());
+ LayoutSerializeSplitter(writer, "Split", _split);
}
///
public override void OnLayoutDeserialize(XmlElement node)
{
- if (float.TryParse(node.GetAttribute("Split"), out float value1))
- _split.SplitterValue = value1;
+ LayoutDeserializeSplitter(node, "Split", _split);
}
///
diff --git a/Source/Editor/Windows/Assets/CollisionDataWindow.cs b/Source/Editor/Windows/Assets/CollisionDataWindow.cs
index fd044a22c..9ce4c8765 100644
--- a/Source/Editor/Windows/Assets/CollisionDataWindow.cs
+++ b/Source/Editor/Windows/Assets/CollisionDataWindow.cs
@@ -236,7 +236,7 @@ namespace FlaxEditor.Windows.Assets
_collisionWiresModel = FlaxEngine.Content.CreateVirtualAsset();
_collisionWiresModel.SetupLODs(new[] { 1 });
}
- Editor.Internal_GetCollisionWires(FlaxEngine.Object.GetUnmanagedPtr(Asset), out var triangles, out var indices);
+ Editor.Internal_GetCollisionWires(FlaxEngine.Object.GetUnmanagedPtr(Asset), out var triangles, out var indices, out var _, out var _);
if (triangles != null && indices != null)
_collisionWiresModel.LODs[0].Meshes[0].UpdateMesh(triangles, indices);
else
@@ -317,14 +317,13 @@ namespace FlaxEditor.Windows.Assets
///
public override void OnLayoutSerialize(XmlWriter writer)
{
- writer.WriteAttributeString("Split", _split.SplitterValue.ToString());
+ LayoutSerializeSplitter(writer, "Split", _split);
}
///
public override void OnLayoutDeserialize(XmlElement node)
{
- if (float.TryParse(node.GetAttribute("Split"), out float value1))
- _split.SplitterValue = value1;
+ LayoutDeserializeSplitter(node, "Split", _split);
}
///
diff --git a/Source/Editor/Windows/Assets/CubeTextureWindow.cs b/Source/Editor/Windows/Assets/CubeTextureWindow.cs
index 2292ba556..272cf351b 100644
--- a/Source/Editor/Windows/Assets/CubeTextureWindow.cs
+++ b/Source/Editor/Windows/Assets/CubeTextureWindow.cs
@@ -27,7 +27,7 @@ namespace FlaxEditor.Windows.Assets
private CubeTextureWindow _window;
[EditorOrder(1000), EditorDisplay("Import Settings", EditorDisplayAttribute.InlineStyle)]
- public TextureImportSettings ImportSettings = new TextureImportSettings();
+ public FlaxEngine.Tools.TextureTool.Options ImportSettings = new();
public sealed class ProxyEditor : GenericEditor
{
@@ -69,7 +69,7 @@ namespace FlaxEditor.Windows.Assets
_window = window;
// Try to restore target asset texture import options (useful for fast reimport)
- TextureImportSettings.TryRestore(ref ImportSettings, window.Item.Path);
+ Editor.TryRestoreImportOptions(ref ImportSettings, window.Item.Path);
// Prepare restore data
PeekState();
@@ -206,14 +206,13 @@ namespace FlaxEditor.Windows.Assets
///
public override void OnLayoutSerialize(XmlWriter writer)
{
- writer.WriteAttributeString("Split", _split.SplitterValue.ToString());
+ LayoutSerializeSplitter(writer, "Split", _split);
}
///
public override void OnLayoutDeserialize(XmlElement node)
{
- if (float.TryParse(node.GetAttribute("Split"), out float value1))
- _split.SplitterValue = value1;
+ LayoutDeserializeSplitter(node, "Split", _split);
}
///
diff --git a/Source/Editor/Windows/Assets/GameplayGlobalsWindow.cs b/Source/Editor/Windows/Assets/GameplayGlobalsWindow.cs
index bfd93d5ae..623c4ef5b 100644
--- a/Source/Editor/Windows/Assets/GameplayGlobalsWindow.cs
+++ b/Source/Editor/Windows/Assets/GameplayGlobalsWindow.cs
@@ -11,6 +11,7 @@ using FlaxEditor.GUI.ContextMenu;
using FlaxEditor.Scripting;
using FlaxEngine;
using FlaxEngine.GUI;
+using FlaxEngine.Utilities;
namespace FlaxEditor.Windows.Assets
{
@@ -331,7 +332,7 @@ namespace FlaxEditor.Windows.Assets
{
Proxy = _proxy,
IsAdd = true,
- Name = StringUtils.IncrementNameNumber("New parameter", x => OnParameterRenameValidate(null, x)),
+ Name = Utilities.Utils.IncrementNameNumber("New parameter", x => OnParameterRenameValidate(null, x)),
DefaultValue = TypeUtils.GetDefaultValue(new ScriptType(type)),
};
_proxy.Window.Undo.AddAction(action);
diff --git a/Source/Editor/Windows/Assets/MaterialInstanceWindow.cs b/Source/Editor/Windows/Assets/MaterialInstanceWindow.cs
index 9401908d0..d5ab2ae8a 100644
--- a/Source/Editor/Windows/Assets/MaterialInstanceWindow.cs
+++ b/Source/Editor/Windows/Assets/MaterialInstanceWindow.cs
@@ -233,7 +233,7 @@ namespace FlaxEditor.Windows.Assets
}
if (!materialInstance.IsLoaded || (materialInstance.BaseMaterial && !materialInstance.BaseMaterial.IsLoaded))
{
- layout.Label("Loading...");
+ layout.Label("Loading...", TextAlignment.Center);
return;
}
var parameters = materialInstance.Parameters;
@@ -548,14 +548,13 @@ namespace FlaxEditor.Windows.Assets
///
public override void OnLayoutSerialize(XmlWriter writer)
{
- writer.WriteAttributeString("Split", _split.SplitterValue.ToString());
+ LayoutSerializeSplitter(writer, "Split", _split);
}
///
public override void OnLayoutDeserialize(XmlElement node)
{
- if (float.TryParse(node.GetAttribute("Split"), out float value1))
- _split.SplitterValue = value1;
+ LayoutDeserializeSplitter(node, "Split", _split);
}
///
diff --git a/Source/Editor/Windows/Assets/ModelBaseWindow.cs b/Source/Editor/Windows/Assets/ModelBaseWindow.cs
index 977b15839..5fd90bd23 100644
--- a/Source/Editor/Windows/Assets/ModelBaseWindow.cs
+++ b/Source/Editor/Windows/Assets/ModelBaseWindow.cs
@@ -36,6 +36,10 @@ namespace FlaxEditor.Windows.Assets
Asset = window.Asset;
}
+ public virtual void OnSave()
+ {
+ }
+
public virtual void OnClean()
{
Window = null;
@@ -109,7 +113,7 @@ namespace FlaxEditor.Windows.Assets
{
AnchorPreset = AnchorPresets.StretchAll,
Offsets = new Margin(0, 0, _toolstrip.Bottom, 0),
- SplitterValue = 0.65f,
+ SplitterValue = 0.59f,
Parent = this
};
@@ -139,9 +143,7 @@ namespace FlaxEditor.Windows.Assets
foreach (var child in _tabs.Children)
{
if (child is Tab tab && tab.Proxy.Window != null)
- {
tab.Proxy.OnClean();
- }
}
base.UnlinkItem();
@@ -187,20 +189,19 @@ namespace FlaxEditor.Windows.Assets
///
public override void OnLayoutSerialize(XmlWriter writer)
{
- writer.WriteAttributeString("Split", _split.SplitterValue.ToString());
+ LayoutSerializeSplitter(writer, "Split", _split);
}
///
public override void OnLayoutDeserialize(XmlElement node)
{
- if (float.TryParse(node.GetAttribute("Split"), out float value1))
- _split.SplitterValue = value1;
+ LayoutDeserializeSplitter(node, "Split", _split);
}
///
public override void OnLayoutDeserialize()
{
- _split.SplitterValue = 0.65f;
+ _split.SplitterValue = 0.59f;
}
}
}
diff --git a/Source/Editor/Windows/Assets/ModelWindow.cs b/Source/Editor/Windows/Assets/ModelWindow.cs
index af3bfdf22..e44882b73 100644
--- a/Source/Editor/Windows/Assets/ModelWindow.cs
+++ b/Source/Editor/Windows/Assets/ModelWindow.cs
@@ -11,6 +11,7 @@ using FlaxEditor.Viewport.Cameras;
using FlaxEditor.Viewport.Previews;
using FlaxEngine;
using FlaxEngine.GUI;
+using FlaxEngine.Tools;
using FlaxEngine.Utilities;
using Object = FlaxEngine.Object;
@@ -157,7 +158,7 @@ namespace FlaxEditor.Windows.Assets
var proxy = (MeshesPropertiesProxy)Values[0];
if (proxy.Asset == null || !proxy.Asset.IsLoaded)
{
- layout.Label("Loading...");
+ layout.Label("Loading...", TextAlignment.Center);
return;
}
proxy._materialSlotComboBoxes.Clear();
@@ -196,12 +197,12 @@ namespace FlaxEditor.Windows.Assets
group.Label("No SDF");
}
- var resolution = group.FloatValue("Resolution Scale", proxy.Window.Editor.CodeDocs.GetTooltip(typeof(ModelImportSettings), nameof(ModelImportSettings.SDFResolution)));
+ var resolution = group.FloatValue("Resolution Scale", proxy.Window.Editor.CodeDocs.GetTooltip(typeof(ModelTool.Options), nameof(ModelImportSettings.Settings.SDFResolution)));
resolution.ValueBox.MinValue = 0.0001f;
resolution.ValueBox.MaxValue = 100.0f;
resolution.ValueBox.Value = sdf.Texture != null ? sdf.ResolutionScale : 1.0f;
- resolution.ValueBox.BoxValueChanged += b => { proxy.Window._importSettings.SDFResolution = b.Value; };
- proxy.Window._importSettings.SDFResolution = sdf.ResolutionScale;
+ resolution.ValueBox.BoxValueChanged += b => { proxy.Window._importSettings.Settings.SDFResolution = b.Value; };
+ proxy.Window._importSettings.Settings.SDFResolution = sdf.ResolutionScale;
var backfacesThreshold = group.FloatValue("Backfaces Threshold", "Custom threshold (in range 0-1) for adjusting mesh internals detection based on the percentage of test rays hit triangle backfaces. Use lower value for more dense mesh.");
backfacesThreshold.ValueBox.MinValue = 0.001f;
@@ -293,7 +294,7 @@ namespace FlaxEditor.Windows.Assets
private void OnRebuildSDF()
{
var proxy = (MeshesPropertiesProxy)Values[0];
- proxy.Asset.GenerateSDF(proxy.Window._importSettings.SDFResolution, _sdfModelLodIndex.Value, true, proxy.Window._backfacesThreshold);
+ proxy.Asset.GenerateSDF(proxy.Window._importSettings.Settings.SDFResolution, _sdfModelLodIndex.Value, true, proxy.Window._backfacesThreshold);
proxy.Window.MarkAsEdited();
Presenter.BuildLayoutOnUpdate();
}
@@ -402,7 +403,7 @@ namespace FlaxEditor.Windows.Assets
var proxy = (MaterialsPropertiesProxy)Values[0];
if (proxy.Asset == null || !proxy.Asset.IsLoaded)
{
- layout.Label("Loading...");
+ layout.Label("Loading...", TextAlignment.Center);
return;
}
@@ -464,7 +465,7 @@ namespace FlaxEditor.Windows.Assets
var proxy = (UVsPropertiesProxy)Values[0];
if (proxy.Asset == null || !proxy.Asset.IsLoaded)
{
- layout.Label("Loading...");
+ layout.Label("Loading...", TextAlignment.Center);
return;
}
@@ -713,7 +714,7 @@ namespace FlaxEditor.Windows.Assets
var proxy = (ImportPropertiesProxy)Values[0];
if (proxy.Asset == null || !proxy.Asset.IsLoaded)
{
- layout.Label("Loading...");
+ layout.Label("Loading...", TextAlignment.Center);
return;
}
@@ -876,10 +877,13 @@ namespace FlaxEditor.Windows.Assets
{
if (!IsEdited)
return;
-
if (_asset.WaitForLoaded())
- {
return;
+
+ foreach (var child in _tabs.Children)
+ {
+ if (child is Tab tab && tab.Proxy.Window != null)
+ tab.Proxy.OnSave();
}
if (_asset.Save())
@@ -916,7 +920,7 @@ namespace FlaxEditor.Windows.Assets
{
_refreshOnLODsLoaded = true;
_preview.ViewportCamera.SetArcBallView(Asset.GetBox());
- ModelImportSettings.TryRestore(ref _importSettings, Item.Path);
+ Editor.TryRestoreImportOptions(ref _importSettings.Settings, Item.Path);
UpdateEffectsOnAsset();
// TODO: disable streaming for this model
diff --git a/Source/Editor/Windows/Assets/ParticleSystemWindow.cs b/Source/Editor/Windows/Assets/ParticleSystemWindow.cs
index 0b59dc86c..328369680 100644
--- a/Source/Editor/Windows/Assets/ParticleSystemWindow.cs
+++ b/Source/Editor/Windows/Assets/ParticleSystemWindow.cs
@@ -543,22 +543,17 @@ namespace FlaxEditor.Windows.Assets
///
public override void OnLayoutSerialize(XmlWriter writer)
{
- writer.WriteAttributeString("Split1", _split1.SplitterValue.ToString());
- writer.WriteAttributeString("Split2", _split2.SplitterValue.ToString());
- writer.WriteAttributeString("Split3", _timeline.Splitter.SplitterValue.ToString());
+ LayoutSerializeSplitter(writer, "Split1", _split1);
+ LayoutSerializeSplitter(writer, "Split2", _split2);
+ LayoutSerializeSplitter(writer, "Split3", _timeline.Splitter);
}
///
public override void OnLayoutDeserialize(XmlElement node)
{
- if (float.TryParse(node.GetAttribute("Split1"), out float value1))
- _split1.SplitterValue = value1;
-
- if (float.TryParse(node.GetAttribute("Split2"), out value1))
- _split2.SplitterValue = value1;
-
- if (float.TryParse(node.GetAttribute("Split3"), out value1))
- _timeline.Splitter.SplitterValue = value1;
+ LayoutDeserializeSplitter(node, "Split1", _split1);
+ LayoutDeserializeSplitter(node, "Split2", _split2);
+ LayoutDeserializeSplitter(node, "Split3", _timeline.Splitter);
}
///
diff --git a/Source/Editor/Windows/Assets/PrefabWindow.Hierarchy.cs b/Source/Editor/Windows/Assets/PrefabWindow.Hierarchy.cs
index 625fd92b4..1b1cfb4ff 100644
--- a/Source/Editor/Windows/Assets/PrefabWindow.Hierarchy.cs
+++ b/Source/Editor/Windows/Assets/PrefabWindow.Hierarchy.cs
@@ -298,13 +298,13 @@ namespace FlaxEditor.Windows.Assets
}
// Custom options
- bool showCustomNodeOptions = Editor.SceneEditing.Selection.Count == 1;
- if (!showCustomNodeOptions && Editor.SceneEditing.Selection.Count != 0)
+ bool showCustomNodeOptions = Selection.Count == 1;
+ if (!showCustomNodeOptions && Selection.Count != 0)
{
showCustomNodeOptions = true;
- for (int i = 1; i < Editor.SceneEditing.Selection.Count; i++)
+ for (int i = 1; i < Selection.Count; i++)
{
- if (Editor.SceneEditing.Selection[0].GetType() != Editor.SceneEditing.Selection[i].GetType())
+ if (Selection[0].GetType() != Selection[i].GetType())
{
showCustomNodeOptions = false;
break;
@@ -313,7 +313,7 @@ namespace FlaxEditor.Windows.Assets
}
if (showCustomNodeOptions)
{
- Editor.SceneEditing.Selection[0].OnContextMenu(contextMenu);
+ Selection[0].OnContextMenu(contextMenu);
}
ContextMenuShow?.Invoke(contextMenu);
@@ -368,7 +368,7 @@ namespace FlaxEditor.Windows.Assets
actor.Layer = parentActor.Layer;
// Rename actor to identify it easily
- actor.Name = StringUtils.IncrementNameNumber(actor.GetType().Name, x => parentActor.GetChild(x) == null);
+ actor.Name = Utilities.Utils.IncrementNameNumber(actor.GetType().Name, x => parentActor.GetChild(x) == null);
}
// Spawn it
diff --git a/Source/Editor/Windows/Assets/PrefabWindow.cs b/Source/Editor/Windows/Assets/PrefabWindow.cs
index 7a283e52d..659bab249 100644
--- a/Source/Editor/Windows/Assets/PrefabWindow.cs
+++ b/Source/Editor/Windows/Assets/PrefabWindow.cs
@@ -483,8 +483,8 @@ namespace FlaxEditor.Windows.Assets
///
public override void OnLayoutSerialize(XmlWriter writer)
{
- writer.WriteAttributeString("Split1", _split1.SplitterValue.ToString());
- writer.WriteAttributeString("Split2", _split2.SplitterValue.ToString());
+ LayoutSerializeSplitter(writer, "Split1", _split1);
+ LayoutSerializeSplitter(writer, "Split2", _split2);
writer.WriteAttributeString("LiveReload", LiveReload.ToString());
writer.WriteAttributeString("GizmoMode", Viewport.TransformGizmo.ActiveMode.ToString());
}
@@ -492,15 +492,10 @@ namespace FlaxEditor.Windows.Assets
///
public override void OnLayoutDeserialize(XmlElement node)
{
- if (float.TryParse(node.GetAttribute("Split1"), out float value1))
- _split1.SplitterValue = value1;
-
- if (float.TryParse(node.GetAttribute("Split2"), out value1))
- _split2.SplitterValue = value1;
-
+ LayoutDeserializeSplitter(node, "Split1", _split1);
+ LayoutDeserializeSplitter(node, "Split2", _split2);
if (bool.TryParse(node.GetAttribute("LiveReload"), out bool value2))
LiveReload = value2;
-
if (Enum.TryParse(node.GetAttribute("GizmoMode"), out TransformGizmoBase.Mode value3))
Viewport.TransformGizmo.ActiveMode = value3;
}
diff --git a/Source/Editor/Windows/Assets/SceneAnimationWindow.cs b/Source/Editor/Windows/Assets/SceneAnimationWindow.cs
index c85343158..824928743 100644
--- a/Source/Editor/Windows/Assets/SceneAnimationWindow.cs
+++ b/Source/Editor/Windows/Assets/SceneAnimationWindow.cs
@@ -989,7 +989,7 @@ namespace FlaxEditor.Windows.Assets
///
public override void OnLayoutSerialize(XmlWriter writer)
{
- writer.WriteAttributeString("TimelineSplitter", _timeline.Splitter.SplitterValue.ToString());
+ LayoutSerializeSplitter(writer, "TimelineSplitter", _timeline.Splitter);
writer.WriteAttributeString("TimeShowMode", _timeline.TimeShowMode.ToString());
var id = _previewButton.Checked ? Guid.Empty : (_timeline.Player?.ID ?? _cachedPlayerId);
writer.WriteAttributeString("SelectedPlayer", id.ToString());
@@ -1000,18 +1000,13 @@ namespace FlaxEditor.Windows.Assets
///
public override void OnLayoutDeserialize(XmlElement node)
{
- if (float.TryParse(node.GetAttribute("TimelineSplitter"), out float value1))
- _timeline.Splitter.SplitterValue = value1;
-
+ LayoutDeserializeSplitter(node, "TimelineSplitter", _timeline.Splitter);
if (Guid.TryParse(node.GetAttribute("SelectedPlayer"), out Guid value2))
_cachedPlayerId = value2;
-
if (Enum.TryParse(node.GetAttribute("TimeShowMode"), out Timeline.TimeShowModes value3))
_timeline.TimeShowMode = value3;
-
if (bool.TryParse(node.GetAttribute("ShowPreviewValues"), out bool value4))
_timeline.ShowPreviewValues = value4;
-
if (bool.TryParse(node.GetAttribute("ShowSelected3dTrack"), out value4))
_timeline.ShowSelected3dTrack = value4;
}
diff --git a/Source/Editor/Windows/Assets/SkeletonMaskWindow.cs b/Source/Editor/Windows/Assets/SkeletonMaskWindow.cs
index 507e9032d..23f229f65 100644
--- a/Source/Editor/Windows/Assets/SkeletonMaskWindow.cs
+++ b/Source/Editor/Windows/Assets/SkeletonMaskWindow.cs
@@ -82,7 +82,7 @@ namespace FlaxEditor.Windows.Assets
var proxy = (PropertiesProxy)Values[0];
if (proxy.Asset == null || !proxy.Asset.IsLoaded)
{
- layout.Label("Loading...");
+ layout.Label("Loading...", TextAlignment.Center);
return;
}
@@ -304,14 +304,13 @@ namespace FlaxEditor.Windows.Assets
///
public override void OnLayoutSerialize(XmlWriter writer)
{
- writer.WriteAttributeString("Split", _split.SplitterValue.ToString());
+ LayoutSerializeSplitter(writer, "Split", _split);
}
///
public override void OnLayoutDeserialize(XmlElement node)
{
- if (float.TryParse(node.GetAttribute("Split"), out float value1))
- _split.SplitterValue = value1;
+ LayoutDeserializeSplitter(node, "Split", _split);
}
///
diff --git a/Source/Editor/Windows/Assets/SkinnedModelWindow.cs b/Source/Editor/Windows/Assets/SkinnedModelWindow.cs
index a3439cbe9..d18593549 100644
--- a/Source/Editor/Windows/Assets/SkinnedModelWindow.cs
+++ b/Source/Editor/Windows/Assets/SkinnedModelWindow.cs
@@ -2,6 +2,8 @@
using System;
using System.Collections.Generic;
+using System.IO;
+using System.Linq;
using System.Reflection;
using System.Threading;
using System.Threading.Tasks;
@@ -11,6 +13,8 @@ using FlaxEditor.CustomEditors;
using FlaxEditor.CustomEditors.Elements;
using FlaxEditor.CustomEditors.GUI;
using FlaxEditor.GUI;
+using FlaxEditor.GUI.ContextMenu;
+using FlaxEditor.GUI.Tree;
using FlaxEditor.Scripting;
using FlaxEditor.Viewport.Cameras;
using FlaxEditor.Viewport.Previews;
@@ -173,7 +177,7 @@ namespace FlaxEditor.Windows.Assets
var proxy = (MeshesPropertiesProxy)Values[0];
if (proxy.Asset == null || !proxy.Asset.IsLoaded)
{
- layout.Label("Loading...");
+ layout.Label("Loading...", TextAlignment.Center);
return;
}
proxy._materialSlotComboBoxes.Clear();
@@ -276,6 +280,8 @@ namespace FlaxEditor.Windows.Assets
[CustomEditor(typeof(ProxyEditor))]
private sealed class SkeletonPropertiesProxy : PropertiesProxyBase
{
+ internal Tree NodesTree;
+
private class ProxyEditor : ProxyEditorBase
{
public override void Initialize(LayoutElementsContainer layout)
@@ -283,7 +289,7 @@ namespace FlaxEditor.Windows.Assets
var proxy = (SkeletonPropertiesProxy)Values[0];
if (proxy.Asset == null || !proxy.Asset.IsLoaded)
{
- layout.Label("Loading...");
+ layout.Label("Loading...", TextAlignment.Center);
return;
}
var lods = proxy.Asset.LODs;
@@ -294,8 +300,10 @@ namespace FlaxEditor.Windows.Assets
// Skeleton Bones
{
var group = layout.Group("Skeleton Bones");
+ group.Panel.Close();
var tree = group.Tree();
+ tree.TreeControl.RightClick += OnTreeNodeRightClick;
for (int i = 0; i < bones.Length; i++)
{
if (bones[i].ParentIndex == -1)
@@ -312,6 +320,7 @@ namespace FlaxEditor.Windows.Assets
var group = layout.Group("Skeleton Nodes");
var tree = group.Tree();
+ tree.TreeControl.RightClick += OnTreeNodeRightClick;
for (int i = 0; i < nodes.Length; i++)
{
if (nodes[i].ParentIndex == -1)
@@ -321,6 +330,7 @@ namespace FlaxEditor.Windows.Assets
node.TreeNode.ExpandAll(true);
}
}
+ proxy.NodesTree = tree.TreeControl;
}
// Blend Shapes
@@ -346,6 +356,22 @@ namespace FlaxEditor.Windows.Assets
}
}
+ private void OnTreeNodeRightClick(TreeNode node, Float2 location)
+ {
+ var menu = new ContextMenu();
+
+ var b = menu.AddButton("Copy name");
+ b.Tag = node.Text;
+ b.ButtonClicked += OnTreeNodeCopyName;
+
+ menu.Show(node, location);
+ }
+
+ private void OnTreeNodeCopyName(ContextMenuButton b)
+ {
+ Clipboard.Text = (string)b.Tag;
+ }
+
private void BuildSkeletonBonesTree(SkeletonNode[] nodes, SkeletonBone[] bones, TreeNodeElement layout, int boneIndex)
{
for (int i = 0; i < bones.Length; i++)
@@ -466,7 +492,7 @@ namespace FlaxEditor.Windows.Assets
var proxy = (MaterialsPropertiesProxy)Values[0];
if (proxy.Asset == null || !proxy.Asset.IsLoaded)
{
- layout.Label("Loading...");
+ layout.Label("Loading...", TextAlignment.Center);
return;
}
@@ -527,7 +553,7 @@ namespace FlaxEditor.Windows.Assets
var proxy = (UVsPropertiesProxy)Values[0];
if (proxy.Asset == null || !proxy.Asset.IsLoaded)
{
- layout.Label("Loading...");
+ layout.Label("Loading...", TextAlignment.Center);
return;
}
@@ -686,21 +712,24 @@ namespace FlaxEditor.Windows.Assets
Render2D.PushClip(new Rectangle(Float2.Zero, size));
var meshDatas = Proxy.Window._meshDatas;
- var lodIndex = Mathf.Clamp(_lod, 0, meshDatas.Length - 1);
- var lod = meshDatas[lodIndex];
- var mesh = Mathf.Clamp(_mesh, -1, lod.Length - 1);
- if (mesh == -1)
+ if (meshDatas.Length != 0)
{
- for (int meshIndex = 0; meshIndex < lod.Length; meshIndex++)
+ var lodIndex = Mathf.Clamp(_lod, 0, meshDatas.Length - 1);
+ var lod = meshDatas[lodIndex];
+ var mesh = Mathf.Clamp(_mesh, -1, lod.Length - 1);
+ if (mesh == -1)
{
- if (_isolateIndex != -1 && _isolateIndex != meshIndex)
- continue;
- DrawMeshUVs(meshIndex, lod[meshIndex]);
+ for (int meshIndex = 0; meshIndex < lod.Length; meshIndex++)
+ {
+ if (_isolateIndex != -1 && _isolateIndex != meshIndex)
+ continue;
+ DrawMeshUVs(meshIndex, lod[meshIndex]);
+ }
+ }
+ else
+ {
+ DrawMeshUVs(mesh, lod[mesh]);
}
- }
- else
- {
- DrawMeshUVs(mesh, lod[mesh]);
}
Render2D.PopClip();
@@ -723,6 +752,230 @@ namespace FlaxEditor.Windows.Assets
}
}
+ [CustomEditor(typeof(ProxyEditor))]
+ private sealed class RetargetPropertiesProxy : PropertiesProxyBase
+ {
+ internal class SetupProxy
+ {
+ public SkinnedModel Skeleton;
+ public Dictionary NodesMapping;
+ }
+
+ internal Dictionary Setups;
+
+ public override void OnSave()
+ {
+ base.OnSave();
+
+ if (Setups != null)
+ {
+ var retargetSetups = new SkinnedModel.SkeletonRetarget[Setups.Count];
+ int i = 0;
+ foreach (var setup in Setups)
+ {
+ retargetSetups[i++] = new SkinnedModel.SkeletonRetarget
+ {
+ SourceAsset = setup.Key?.ID ?? Guid.Empty,
+ SkeletonAsset = setup.Value.Skeleton?.ID ?? Guid.Empty,
+ NodesMapping = setup.Value.NodesMapping,
+ };
+ }
+ Window.Asset.SkeletonRetargets = retargetSetups;
+ }
+ }
+
+ private class ProxyEditor : ProxyEditorBase
+ {
+ public override void Initialize(LayoutElementsContainer layout)
+ {
+ var proxy = (RetargetPropertiesProxy)Values[0];
+ if (proxy.Asset == null || !proxy.Asset.IsLoaded)
+ {
+ layout.Label("Loading...", TextAlignment.Center);
+ return;
+ }
+ if (proxy.Setups == null)
+ {
+ proxy.Setups = new Dictionary();
+ var retargetSetups = proxy.Asset.SkeletonRetargets;
+ foreach (var retargetSetup in retargetSetups)
+ {
+ var sourceAsset = FlaxEngine.Content.LoadAsync(retargetSetup.SourceAsset);
+ if (sourceAsset)
+ {
+ proxy.Setups.Add(sourceAsset, new SetupProxy
+ {
+ Skeleton = FlaxEngine.Content.LoadAsync(retargetSetup.SkeletonAsset),
+ NodesMapping = retargetSetup.NodesMapping,
+ });
+ }
+ }
+ }
+ var targetNodes = proxy.Asset.Nodes;
+
+ layout.Space(10.0f);
+ var infoLabel = layout.Label("Each retarget setup defines how to convert animated skeleton pose from a source asset to this skinned model skeleton. It allows to play animation from different skeleton on this skeleton. See documentation to learn more.").Label;
+ infoLabel.Wrapping = TextWrapping.WrapWords;
+ infoLabel.AutoHeight = true;
+ layout.Space(10.0f);
+
+ // New setup
+ {
+ var setupGroup = layout.Group("New setup");
+ infoLabel = setupGroup.Label("Select model or animation asset to add new retarget source", TextAlignment.Center).Label;
+ infoLabel.Wrapping = TextWrapping.WrapWords;
+ infoLabel.AutoHeight = true;
+ var sourceAssetPicker = setupGroup.AddPropertyItem("Source Asset").Custom().CustomControl;
+ sourceAssetPicker.Height = 48;
+ sourceAssetPicker.CheckValid = CheckSourceAssetValid;
+ sourceAssetPicker.SelectedItemChanged += () =>
+ {
+ proxy.Setups.Add(sourceAssetPicker.SelectedAsset, new SetupProxy());
+ proxy.Window.MarkAsEdited();
+ RebuildLayout();
+ };
+ }
+
+ // Setups
+ foreach (var setup in proxy.Setups)
+ {
+ var sourceAsset = setup.Key;
+ if (sourceAsset == null)
+ continue;
+ var setupGroup = layout.Group(Path.GetFileNameWithoutExtension(sourceAsset.Path));
+ var settingsButton = setupGroup.AddSettingsButton();
+ settingsButton.Tag = sourceAsset;
+ settingsButton.Clicked += OnShowSetupSettings;
+
+ // Source asset picker
+ var sourceAssetPicker = setupGroup.AddPropertyItem("Source Asset").Custom().CustomControl;
+ sourceAssetPicker.SelectedAsset = sourceAsset;
+ sourceAssetPicker.CanEdit = false;
+ sourceAssetPicker.Height = 48;
+
+ if (sourceAsset is SkinnedModel sourceModel)
+ {
+ // Initialize nodes mapping structure
+ if (sourceModel.WaitForLoaded())
+ continue;
+ var sourceNodes = sourceModel.Nodes;
+ if (setup.Value.NodesMapping == null)
+ setup.Value.NodesMapping = new Dictionary();
+ var nodesMapping = setup.Value.NodesMapping;
+ foreach (var targetNode in targetNodes)
+ {
+ if (!nodesMapping.ContainsKey(targetNode.Name))
+ {
+ var node = string.Empty;
+ foreach (var sourceNode in sourceNodes)
+ {
+ if (string.Equals(targetNode.Name, sourceNode.Name, StringComparison.OrdinalIgnoreCase))
+ {
+ node = sourceNode.Name;
+ break;
+ }
+ }
+ nodesMapping.Add(targetNode.Name, node);
+ }
+ }
+
+ // Build source skeleton nodes list (with hierarchy indentation)
+ var items = new string[sourceNodes.Length + 1];
+ items[0] = string.Empty;
+ for (int i = 0; i < sourceNodes.Length; i++)
+ items[i + 1] = sourceNodes[i].Name;
+
+ // Show combo boxes with this skeleton nodes to retarget from
+ foreach (var targetNode in targetNodes)
+ {
+ var nodeName = targetNode.Name;
+ var propertyName = nodeName;
+ var tmp = targetNode.ParentIndex;
+ while (tmp != -1)
+ {
+ tmp = targetNodes[tmp].ParentIndex;
+ propertyName = " " + propertyName;
+ }
+ var comboBox = setupGroup.AddPropertyItem(propertyName).Custom().CustomControl;
+ comboBox.AddItems(items);
+ comboBox.Tag = new KeyValuePair(nodeName, sourceAsset);
+ comboBox.SelectedItem = nodesMapping[nodeName];
+ if (comboBox.SelectedIndex == -1)
+ comboBox.SelectedIndex = 0; // Auto-select empty node
+ comboBox.SelectedIndexChanged += OnSelectedNodeChanged;
+ }
+ }
+ else if (sourceAsset is Animation sourceAnimation)
+ {
+ // Show skeleton asset picker
+ var sourceSkeletonPicker = setupGroup.AddPropertyItem("Skeleton", "Skinned model that contains a skeleton for this animation retargeting.").Custom().CustomControl;
+ sourceSkeletonPicker.AssetType = new ScriptType(typeof(SkinnedModel));
+ sourceSkeletonPicker.SelectedAsset = setup.Value.Skeleton;
+ sourceSkeletonPicker.Height = 48;
+ sourceSkeletonPicker.SelectedItemChanged += () =>
+ {
+ setup.Value.Skeleton = (SkinnedModel)sourceSkeletonPicker.SelectedAsset;
+ proxy.Window.MarkAsEdited();
+ };
+ }
+ }
+ }
+
+ private void OnSelectedNodeChanged(ComboBox comboBox)
+ {
+ var proxy = (RetargetPropertiesProxy)Values[0];
+ var sourceAsset = ((KeyValuePair)comboBox.Tag).Value;
+ var nodeMappingKey = ((KeyValuePair)comboBox.Tag).Key;
+ var nodeMappingValue = comboBox.SelectedItem;
+ // TODO: check for recursion in setup
+ proxy.Setups[sourceAsset].NodesMapping[nodeMappingKey] = nodeMappingValue;
+ proxy.Window.MarkAsEdited();
+ }
+
+ private void OnShowSetupSettings(Image settingsButton, MouseButton button)
+ {
+ if (button == MouseButton.Left)
+ {
+ var sourceAsset = (Asset)settingsButton.Tag;
+ var menu = new ContextMenu { Tag = sourceAsset };
+ menu.AddButton("Clear", OnClearSetup);
+ menu.AddButton("Remove", OnRemoveSetup).Icon = Editor.Instance.Icons.Cross12;
+ menu.Show(settingsButton, new Float2(0, settingsButton.Height));
+ }
+ }
+
+ private void OnClearSetup(ContextMenuButton button)
+ {
+ var proxy = (RetargetPropertiesProxy)Values[0];
+ var sourceAsset = (Asset)button.ParentContextMenu.Tag;
+ var setup = proxy.Setups[sourceAsset];
+ setup.Skeleton = null;
+ foreach (var e in setup.NodesMapping.Keys.ToArray())
+ setup.NodesMapping[e] = string.Empty;
+ proxy.Window.MarkAsEdited();
+ RebuildLayout();
+ }
+
+ private void OnRemoveSetup(ContextMenuButton button)
+ {
+ var proxy = (RetargetPropertiesProxy)Values[0];
+ var sourceAsset = (Asset)button.ParentContextMenu.Tag;
+ proxy.Setups.Remove(sourceAsset);
+ proxy.Window.MarkAsEdited();
+ RebuildLayout();
+ }
+
+ private bool CheckSourceAssetValid(ContentItem item)
+ {
+ var proxy = (RetargetPropertiesProxy)Values[0];
+ return item is BinaryAssetItem binaryItem &&
+ (binaryItem.Type == typeof(SkinnedModel) || binaryItem.Type == typeof(Animation)) &&
+ item != proxy.Window.Item &&
+ !proxy.Setups.ContainsKey(binaryItem.LoadAsync());
+ }
+ }
+ }
+
[CustomEditor(typeof(ProxyEditor))]
private sealed class ImportPropertiesProxy : PropertiesProxyBase
{
@@ -733,7 +986,7 @@ namespace FlaxEditor.Windows.Assets
{
base.OnLoad(window);
- ModelImportSettings.TryRestore(ref ImportSettings, window.Item.Path);
+ Editor.TryRestoreImportOptions(ref ImportSettings.Settings, window.Item.Path);
}
public void Reimport()
@@ -748,7 +1001,7 @@ namespace FlaxEditor.Windows.Assets
var proxy = (ImportPropertiesProxy)Values[0];
if (proxy.Asset == null || !proxy.Asset.IsLoaded)
{
- layout.Label("Loading...");
+ layout.Label("Loading...", TextAlignment.Center);
return;
}
@@ -785,6 +1038,21 @@ namespace FlaxEditor.Windows.Assets
{
Proxy = new SkeletonPropertiesProxy();
Presenter.Select(Proxy);
+ window._preview.CustomDebugDraw += OnDebugDraw;
+ }
+
+ private void OnDebugDraw(GPUContext context, ref RenderContext renderContext)
+ {
+ var proxy = (SkeletonPropertiesProxy)Proxy;
+ if (proxy.NodesTree != null)
+ {
+ // Draw selected skeleton nodes
+ foreach (var node in proxy.NodesTree.Selection)
+ {
+ proxy.Window._preview.PreviewActor.GetNodeTransformation(node.Text, out var nodeTransformation, true);
+ DebugDraw.DrawWireSphere(new BoundingSphere(nodeTransformation.TranslationVector, 4.0f), Color.Red, 0.0f, false);
+ }
+ }
}
}
@@ -808,6 +1076,16 @@ namespace FlaxEditor.Windows.Assets
}
}
+ private class RetargetTab : Tab
+ {
+ public RetargetTab(SkinnedModelWindow window)
+ : base("Retarget", window)
+ {
+ Proxy = new RetargetPropertiesProxy();
+ Presenter.Select(Proxy);
+ }
+ }
+
private class ImportTab : Tab
{
public ImportTab(SkinnedModelWindow window)
@@ -855,6 +1133,7 @@ namespace FlaxEditor.Windows.Assets
_tabs.AddTab(new SkeletonTab(this));
_tabs.AddTab(new MaterialsTab(this));
_tabs.AddTab(new UVsTab(this));
+ _tabs.AddTab(new RetargetTab(this));
_tabs.AddTab(new ImportTab(this));
// Highlight actor (used to highlight selected material slot, see UpdateEffectsOnAsset)
@@ -996,6 +1275,14 @@ namespace FlaxEditor.Windows.Assets
{
if (!IsEdited)
return;
+ if (_asset.WaitForLoaded())
+ return;
+
+ foreach (var child in _tabs.Children)
+ {
+ if (child is Tab tab && tab.Proxy.Window != null)
+ tab.Proxy.OnSave();
+ }
if (_asset.Save())
{
@@ -1030,7 +1317,7 @@ namespace FlaxEditor.Windows.Assets
protected override void OnAssetLoaded()
{
_refreshOnLODsLoaded = true;
- _preview.ViewportCamera.SetArcBallView(Asset.GetBox());
+ _preview.ViewportCamera.SetArcBallView(_preview.GetBounds());
UpdateEffectsOnAsset();
// TODO: disable streaming for this model
diff --git a/Source/Editor/Windows/Assets/SpriteAtlasWindow.cs b/Source/Editor/Windows/Assets/SpriteAtlasWindow.cs
index ecd031d48..7d09620d5 100644
--- a/Source/Editor/Windows/Assets/SpriteAtlasWindow.cs
+++ b/Source/Editor/Windows/Assets/SpriteAtlasWindow.cs
@@ -103,7 +103,7 @@ namespace FlaxEditor.Windows.Assets
public SpriteEntry[] Sprites;
[EditorOrder(1000), EditorDisplay("Import Settings", EditorDisplayAttribute.InlineStyle)]
- public TextureImportSettings ImportSettings = new TextureImportSettings();
+ public FlaxEngine.Tools.TextureTool.Options ImportSettings = new();
public sealed class ProxyEditor : GenericEditor
{
@@ -183,11 +183,7 @@ namespace FlaxEditor.Windows.Assets
UpdateSprites();
// Try to restore target asset texture import options (useful for fast reimport)
- if (TextureImportEntry.Internal_GetTextureImportOptions(win.Item.Path, out TextureImportSettings.InternalOptions options))
- {
- // Restore settings
- ImportSettings.FromInternal(ref options);
- }
+ Editor.TryRestoreImportOptions(ref ImportSettings, win.Item.Path);
// Prepare restore data
PeekState();
@@ -269,7 +265,7 @@ namespace FlaxEditor.Windows.Assets
{
var sprite = new Sprite
{
- Name = StringUtils.IncrementNameNumber("New Sprite", name => Asset.Sprites.All(s => s.Name != name)),
+ Name = Utilities.Utils.IncrementNameNumber("New Sprite", name => Asset.Sprites.All(s => s.Name != name)),
Area = new Rectangle(Float2.Zero, Float2.One),
};
Asset.AddSprite(sprite);
@@ -366,14 +362,13 @@ namespace FlaxEditor.Windows.Assets
///
public override void OnLayoutSerialize(XmlWriter writer)
{
- writer.WriteAttributeString("Split", _split.SplitterValue.ToString());
+ LayoutSerializeSplitter(writer, "Split", _split);
}
///
public override void OnLayoutDeserialize(XmlElement node)
{
- if (float.TryParse(node.GetAttribute("Split"), out float value1))
- _split.SplitterValue = value1;
+ LayoutDeserializeSplitter(node, "Split", _split);
}
///
diff --git a/Source/Editor/Windows/Assets/TextureWindow.cs b/Source/Editor/Windows/Assets/TextureWindow.cs
index 3b1f1911c..80786f1ed 100644
--- a/Source/Editor/Windows/Assets/TextureWindow.cs
+++ b/Source/Editor/Windows/Assets/TextureWindow.cs
@@ -70,7 +70,7 @@ namespace FlaxEditor.Windows.Assets
internal TextureWindow _window;
[EditorOrder(1000), EditorDisplay("Import Settings", EditorDisplayAttribute.InlineStyle)]
- public TextureImportSettings ImportSettings = new TextureImportSettings();
+ public FlaxEngine.Tools.TextureTool.Options ImportSettings = new();
///
/// Gathers parameters from the specified texture.
@@ -82,7 +82,7 @@ namespace FlaxEditor.Windows.Assets
_window = window;
// Try to restore target asset texture import options (useful for fast reimport)
- TextureImportSettings.TryRestore(ref ImportSettings, window.Item.Path);
+ Editor.TryRestoreImportOptions(ref ImportSettings, window.Item.Path);
// Prepare restore data
PeekState();
@@ -245,14 +245,13 @@ namespace FlaxEditor.Windows.Assets
///
public override void OnLayoutSerialize(XmlWriter writer)
{
- writer.WriteAttributeString("Split", _split.SplitterValue.ToString());
+ LayoutSerializeSplitter(writer, "Split", _split);
}
///
public override void OnLayoutDeserialize(XmlElement node)
{
- if (float.TryParse(node.GetAttribute("Split"), out float value1))
- _split.SplitterValue = value1;
+ LayoutDeserializeSplitter(node, "Split", _split);
}
///
diff --git a/Source/Editor/Windows/Assets/VisualScriptWindow.cs b/Source/Editor/Windows/Assets/VisualScriptWindow.cs
index 6c2ec72b0..ed20b591e 100644
--- a/Source/Editor/Windows/Assets/VisualScriptWindow.cs
+++ b/Source/Editor/Windows/Assets/VisualScriptWindow.cs
@@ -16,6 +16,7 @@ using FlaxEditor.Surface;
using FlaxEngine;
using FlaxEngine.GUI;
using FlaxEngine.Json;
+using FlaxEngine.Utilities;
using FlaxEngine.Windows.Search;
#pragma warning disable 649
@@ -818,7 +819,7 @@ namespace FlaxEditor.Windows.Assets
var state = (BreakpointHangState)Editor.Instance.Simulation.BreakpointHangTag;
if (state.Locals == null)
{
- state.Locals = Editor.Internal_GetVisualScriptLocals();
+ state.Locals = Editor.Internal_GetVisualScriptLocals(out var _);
Editor.Instance.Simulation.BreakpointHangTag = state;
}
return state;
@@ -829,7 +830,7 @@ namespace FlaxEditor.Windows.Assets
var state = (BreakpointHangState)Editor.Instance.Simulation.BreakpointHangTag;
if (state.StackFrames == null)
{
- state.StackFrames = Editor.Internal_GetVisualScriptStackFrames();
+ state.StackFrames = Editor.Internal_GetVisualScriptStackFrames(out var _);
Editor.Instance.Simulation.BreakpointHangTag = state;
}
return state;
@@ -1370,14 +1371,13 @@ namespace FlaxEditor.Windows.Assets
///
public override void OnLayoutSerialize(XmlWriter writer)
{
- writer.WriteAttributeString("Split", _split.SplitterValue.ToString());
+ LayoutSerializeSplitter(writer, "Split", _split);
}
///
public override void OnLayoutDeserialize(XmlElement node)
{
- if (float.TryParse(node.GetAttribute("Split"), out float value1))
- _split.SplitterValue = value1;
+ LayoutDeserializeSplitter(node, "Split", _split);
}
///
diff --git a/Source/Editor/Windows/ContentWindow.cs b/Source/Editor/Windows/ContentWindow.cs
index 207abb84f..f993b79f9 100644
--- a/Source/Editor/Windows/ContentWindow.cs
+++ b/Source/Editor/Windows/ContentWindow.cs
@@ -2,6 +2,7 @@
using System;
using System.Collections.Generic;
+using System.Globalization;
using System.IO;
using System.Xml;
using FlaxEditor.Content;
@@ -26,6 +27,7 @@ namespace FlaxEditor.Windows
{
private const string ProjectDataLastViewedFolder = "LastViewedFolder";
private bool _isWorkspaceDirty;
+ private string _workspaceRebuildLocation;
private SplitPanel _split;
private Panel _contentViewPanel;
private Panel _contentTreePanel;
@@ -74,7 +76,23 @@ namespace FlaxEditor.Windows
// Content database events
editor.ContentDatabase.WorkspaceModified += () => _isWorkspaceDirty = true;
- editor.ContentDatabase.ItemRemoved += ContentDatabaseOnItemRemoved;
+ editor.ContentDatabase.ItemRemoved += OnContentDatabaseItemRemoved;
+ editor.ContentDatabase.WorkspaceRebuilding += () => { _workspaceRebuildLocation = SelectedNode?.Path; };
+ editor.ContentDatabase.WorkspaceRebuilt += () =>
+ {
+ var selected = Editor.ContentDatabase.Find(_workspaceRebuildLocation);
+ if (selected is ContentFolder selectedFolder)
+ {
+ _navigationUnlocked = false;
+ RefreshView(selectedFolder.Node);
+ _tree.Select(selectedFolder.Node);
+ UpdateItemsSearch();
+ _navigationUnlocked = true;
+ UpdateUI();
+ }
+ else
+ ShowRoot();
+ };
var options = Editor.Options;
options.OptionsChanged += OnOptionsChanged;
@@ -534,12 +552,12 @@ namespace FlaxEditor.Windows
string destinationName;
if (item.IsFolder)
{
- destinationName = StringUtils.IncrementNameNumber(item.ShortName, x => !Directory.Exists(StringUtils.CombinePaths(sourceFolder, x)));
+ destinationName = Utilities.Utils.IncrementNameNumber(item.ShortName, x => !Directory.Exists(StringUtils.CombinePaths(sourceFolder, x)));
}
else
{
string extension = Path.GetExtension(sourcePath);
- destinationName = StringUtils.IncrementNameNumber(item.ShortName, x => !File.Exists(StringUtils.CombinePaths(sourceFolder, x + extension))) + extension;
+ destinationName = Utilities.Utils.IncrementNameNumber(item.ShortName, x => !File.Exists(StringUtils.CombinePaths(sourceFolder, x + extension))) + extension;
}
return StringUtils.NormalizePath(StringUtils.CombinePaths(sourceFolder, destinationName));
@@ -736,7 +754,7 @@ namespace FlaxEditor.Windows
}
}
- private void ContentDatabaseOnItemRemoved(ContentItem contentItem)
+ private void OnContentDatabaseItemRemoved(ContentItem contentItem)
{
if (contentItem is ContentFolder folder)
{
@@ -1032,8 +1050,8 @@ namespace FlaxEditor.Windows
///
public override void OnLayoutSerialize(XmlWriter writer)
{
- writer.WriteAttributeString("Split", _split.SplitterValue.ToString());
- writer.WriteAttributeString("Scale", _view.ViewScale.ToString());
+ LayoutSerializeSplitter(writer, "Split", _split);
+ writer.WriteAttributeString("Scale", _view.ViewScale.ToString(CultureInfo.InvariantCulture));
writer.WriteAttributeString("ShowFileExtensions", _view.ShowFileExtensions.ToString());
writer.WriteAttributeString("ViewType", _view.ViewType.ToString());
}
@@ -1041,15 +1059,11 @@ namespace FlaxEditor.Windows
///
public override void OnLayoutDeserialize(XmlElement node)
{
- if (float.TryParse(node.GetAttribute("Split"), out float value1))
- _split.SplitterValue = value1;
-
- if (float.TryParse(node.GetAttribute("Scale"), out value1))
+ LayoutDeserializeSplitter(node, "Split", _split);
+ if (float.TryParse(node.GetAttribute("Scale"), CultureInfo.InvariantCulture, out var value1))
_view.ViewScale = value1;
-
if (bool.TryParse(node.GetAttribute("ShowFileExtensions"), out bool value2))
_view.ShowFileExtensions = value2;
-
if (Enum.TryParse(node.GetAttribute("ViewType"), out ContentViewType viewType))
_view.ViewType = viewType;
}
diff --git a/Source/Editor/Windows/DebugLogWindow.cs b/Source/Editor/Windows/DebugLogWindow.cs
index b1136523a..b02f63f87 100644
--- a/Source/Editor/Windows/DebugLogWindow.cs
+++ b/Source/Editor/Windows/DebugLogWindow.cs
@@ -673,14 +673,13 @@ namespace FlaxEditor.Windows
///
public override void OnLayoutSerialize(XmlWriter writer)
{
- writer.WriteAttributeString("Split", _split.SplitterValue.ToString());
+ LayoutSerializeSplitter(writer, "Split", _split);
}
///
public override void OnLayoutDeserialize(XmlElement node)
{
- if (float.TryParse(node.GetAttribute("Split"), out float value1))
- _split.SplitterValue = value1;
+ LayoutDeserializeSplitter(node, "Split", _split);
}
///
diff --git a/Source/Editor/Windows/GameCookerWindow.cs b/Source/Editor/Windows/GameCookerWindow.cs
index 0c602422b..f3fd753d1 100644
--- a/Source/Editor/Windows/GameCookerWindow.cs
+++ b/Source/Editor/Windows/GameCookerWindow.cs
@@ -47,6 +47,7 @@ namespace FlaxEditor.Windows
{ PlatformType.Switch, new Switch() },
{ PlatformType.PS5, new PS5() },
{ PlatformType.Mac, new Mac() },
+ { PlatformType.iOS, new iOS() },
};
public BuildTabProxy(GameCookerWindow win, PlatformSelector platformSelector)
@@ -64,6 +65,7 @@ namespace FlaxEditor.Windows
PerPlatformOptions[PlatformType.Switch].Init("Output/Switch", "Switch");
PerPlatformOptions[PlatformType.PS5].Init("Output/PS5", "PS5");
PerPlatformOptions[PlatformType.Mac].Init("Output/Mac", "Mac");
+ PerPlatformOptions[PlatformType.iOS].Init("Output/iOS", "iOS");
}
[HideInEditor]
@@ -109,6 +111,8 @@ namespace FlaxEditor.Windows
switch (BuildPlatform)
{
case BuildPlatform.MacOSx64:
+ case BuildPlatform.MacOSARM64:
+ case BuildPlatform.iOSARM64:
IsSupported = false;
break;
default:
@@ -130,6 +134,8 @@ namespace FlaxEditor.Windows
switch (BuildPlatform)
{
case BuildPlatform.MacOSx64:
+ case BuildPlatform.MacOSARM64:
+ case BuildPlatform.iOSARM64:
case BuildPlatform.AndroidARM64:
IsSupported = true;
break;
@@ -228,7 +234,22 @@ namespace FlaxEditor.Windows
class Mac : Platform
{
- protected override BuildPlatform BuildPlatform => BuildPlatform.MacOSx64;
+ public enum Archs
+ {
+ [EditorDisplay(null, "arm64")]
+ ARM64,
+ [EditorDisplay(null, "x64")]
+ x64,
+ }
+
+ public Archs CPU = Archs.ARM64;
+
+ protected override BuildPlatform BuildPlatform => CPU == Archs.ARM64 ? BuildPlatform.MacOSARM64 : BuildPlatform.MacOSx64;
+ }
+
+ class iOS : Platform
+ {
+ protected override BuildPlatform BuildPlatform => BuildPlatform.iOSARM64;
}
class Editor : CustomEditor
@@ -259,7 +280,7 @@ namespace FlaxEditor.Windows
break;
case PlatformType.UWP:
name = "Windows Store";
- layout.Label("UWP (Windows Store) platform has been deprecated and soon will be removed!", TextAlignment.Center).Label.TextColor = Color.Red;
+ layout.Label("UWP (Windows Store) platform has been deprecated and is no longer supported", TextAlignment.Center).Label.TextColor = Color.Red;
break;
case PlatformType.Linux:
name = "Linux";
@@ -282,6 +303,9 @@ namespace FlaxEditor.Windows
case PlatformType.Mac:
name = "Mac";
break;
+ case PlatformType.iOS:
+ name = "iOS";
+ break;
default:
name = Utilities.Utils.GetPropertyNameUI(_platform.ToString());
break;
@@ -569,7 +593,13 @@ namespace FlaxEditor.Windows
{
var tmpBat = StringUtils.CombinePaths(Globals.TemporaryFolder, Guid.NewGuid().ToString("N") + ".bat");
File.WriteAllText(tmpBat, command);
- Platform.StartProcess(tmpBat, null, null, true, true);
+ var procSettings = new CreateProcessSettings
+ {
+ FileName = tmpBat,
+ HiddenWindow = true,
+ WaitForEnd = true,
+ };
+ Platform.CreateProcess(ref procSettings);
File.Delete(tmpBat);
}
catch (Exception ex)
diff --git a/Source/Editor/Windows/OutputLogWindow.cs b/Source/Editor/Windows/OutputLogWindow.cs
index f2f3b4aff..0b2249a33 100644
--- a/Source/Editor/Windows/OutputLogWindow.cs
+++ b/Source/Editor/Windows/OutputLogWindow.cs
@@ -3,6 +3,7 @@
using System;
using System.Collections.Generic;
using System.IO;
+using System.Runtime.InteropServices;
using System.Text;
using System.Text.RegularExpressions;
using System.Xml;
@@ -366,7 +367,7 @@ namespace FlaxEditor.Windows
// Try to add the line for multi-line logs
if (_entries.Count != 0 && !line.StartsWith("======"))
{
- ref var last = ref Utils.ExtractArrayFromList(_entries)[_entries.Count - 1];
+ ref var last = ref CollectionsMarshal.AsSpan(_entries)[_entries.Count - 1];
last.Message += '\n';
last.Message += line;
}
@@ -445,7 +446,7 @@ namespace FlaxEditor.Windows
int logCount;
do
{
- logCount = Editor.Internal_ReadOutputLogs(_outMessages, _outLogTypes, _outLogTimes);
+ logCount = Editor.Internal_ReadOutputLogs(ref _outMessages, ref _outLogTypes, ref _outLogTimes, OutCapacity);
for (int i = 0; i < logCount; i++)
{
@@ -471,7 +472,7 @@ namespace FlaxEditor.Windows
_output.ErrorStyle.Font.GetFont();
// Generate the output log
- var entries = Utils.ExtractArrayFromList(_entries);
+ Span entries = CollectionsMarshal.AsSpan(_entries);
var searchQuery = _searchBox.Text;
for (int i = _textBufferCount; i < _entries.Count; i++)
{
diff --git a/Source/Editor/Windows/Profiler/CPU.cs b/Source/Editor/Windows/Profiler/CPU.cs
index 3324fddac..36fbf21f4 100644
--- a/Source/Editor/Windows/Profiler/CPU.cs
+++ b/Source/Editor/Windows/Profiler/CPU.cs
@@ -19,16 +19,16 @@ namespace FlaxEngine
{
get
{
- fixed (char* name = &Name0)
+ fixed (short* name = Name0)
{
- return new string(name);
+ return new string((char*)name);
}
}
}
internal unsafe bool NameStartsWith(string prefix)
{
- fixed (char* name = &Name0)
+ fixed (short* name = Name0)
{
fixed (char* p = prefix)
{
diff --git a/Source/Editor/Windows/Profiler/Memory.cs b/Source/Editor/Windows/Profiler/Memory.cs
index 6541fd723..f087da88e 100644
--- a/Source/Editor/Windows/Profiler/Memory.cs
+++ b/Source/Editor/Windows/Profiler/Memory.cs
@@ -61,7 +61,7 @@ namespace FlaxEditor.Windows.Profiler
{
// Count memory allocated during last frame
int nativeMemoryAllocation = 0;
- int managedMemoryAllocation = 0;
+ int managedMemoryAllocation = sharedData.ManagedMemoryAllocation;
var events = sharedData.GetEventsCPU();
var length = events?.Length ?? 0;
for (int i = 0; i < length; i++)
diff --git a/Source/Editor/Windows/Profiler/Physics.cs b/Source/Editor/Windows/Profiler/Physics.cs
new file mode 100644
index 000000000..b2f52462d
--- /dev/null
+++ b/Source/Editor/Windows/Profiler/Physics.cs
@@ -0,0 +1,113 @@
+// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.
+
+using FlaxEngine;
+using FlaxEngine.GUI;
+
+namespace FlaxEditor.Windows.Profiler
+{
+ ///
+ /// The physics simulation profiling mode.
+ ///
+ ///
+ internal sealed class Physics : ProfilerMode
+ {
+ private readonly SingleChart _activeBodiesChart;
+ private readonly SingleChart _activeJointsChart;
+ private readonly SingleChart _dynamicBodiesChart;
+ private readonly SingleChart _staticBodiesChart;
+ private readonly SingleChart _newPairsChart;
+ private readonly SingleChart _newTouchesChart;
+
+ public Physics()
+ : base("Physics")
+ {
+ // Layout
+ var panel = new Panel(ScrollBars.Vertical)
+ {
+ AnchorPreset = AnchorPresets.StretchAll,
+ Offsets = Margin.Zero,
+ Parent = this,
+ };
+ var layout = new VerticalPanel
+ {
+ AnchorPreset = AnchorPresets.HorizontalStretchTop,
+ Offsets = Margin.Zero,
+ IsScrollable = true,
+ Parent = panel,
+ };
+
+ // Charts
+ _activeBodiesChart = new SingleChart
+ {
+ Title = "Active Bodies",
+ Parent = layout,
+ };
+ _activeBodiesChart.SelectedSampleChanged += OnSelectedSampleChanged;
+ _activeJointsChart = new SingleChart
+ {
+ Title = "Active Joints",
+ Parent = layout,
+ };
+ _activeJointsChart.SelectedSampleChanged += OnSelectedSampleChanged;
+ _dynamicBodiesChart = new SingleChart
+ {
+ Title = "Dynamic Bodies",
+ Parent = layout,
+ };
+ _dynamicBodiesChart.SelectedSampleChanged += OnSelectedSampleChanged;
+ _staticBodiesChart = new SingleChart
+ {
+ Title = "Static Bodies",
+ Parent = layout,
+ };
+ _staticBodiesChart.SelectedSampleChanged += OnSelectedSampleChanged;
+ _newPairsChart = new SingleChart
+ {
+ Title = "New Pairs",
+ Parent = layout,
+ };
+ _newPairsChart.SelectedSampleChanged += OnSelectedSampleChanged;
+ _newTouchesChart = new SingleChart
+ {
+ Title = "New Touches",
+ Parent = layout,
+ };
+ _newTouchesChart.SelectedSampleChanged += OnSelectedSampleChanged;
+ }
+
+ ///
+ public override void Clear()
+ {
+ _activeBodiesChart.Clear();
+ _activeJointsChart.Clear();
+ _dynamicBodiesChart.Clear();
+ _staticBodiesChart.Clear();
+ _newPairsChart.Clear();
+ _newTouchesChart.Clear();
+ }
+
+ ///
+ public override void Update(ref SharedUpdateData sharedData)
+ {
+ PhysicsStatistics statistics = FlaxEngine.Physics.DefaultScene.Statistics;
+
+ _activeBodiesChart.AddSample(statistics.ActiveDynamicBodies + statistics.ActiveKinematicBodies);
+ _activeJointsChart.AddSample(statistics.ActiveJoints);
+ _dynamicBodiesChart.AddSample(statistics.DynamicBodies + statistics.KinematicBodies);
+ _staticBodiesChart.AddSample(statistics.StaticBodies);
+ _newPairsChart.AddSample(statistics.NewPairs);
+ _newTouchesChart.AddSample(statistics.NewTouches);
+ }
+
+ ///
+ public override void UpdateView(int selectedFrame, bool showOnlyLastUpdateEvents)
+ {
+ _activeBodiesChart.SelectedSampleIndex = selectedFrame;
+ _activeJointsChart.SelectedSampleIndex = selectedFrame;
+ _dynamicBodiesChart.SelectedSampleIndex = selectedFrame;
+ _staticBodiesChart.SelectedSampleIndex = selectedFrame;
+ _newPairsChart.SelectedSampleIndex = selectedFrame;
+ _newTouchesChart.SelectedSampleIndex = selectedFrame;
+ }
+ }
+}
diff --git a/Source/Editor/Windows/Profiler/ProfilerMode.cs b/Source/Editor/Windows/Profiler/ProfilerMode.cs
index 45a5723a2..0cc1a39ee 100644
--- a/Source/Editor/Windows/Profiler/ProfilerMode.cs
+++ b/Source/Editor/Windows/Profiler/ProfilerMode.cs
@@ -24,6 +24,11 @@ namespace FlaxEditor.Windows.Profiler
///
public ProfilingTools.MainStats Stats;
+ ///
+ /// The additional managed memory allocation size during this update. Given value is in bytes.
+ ///
+ public int ManagedMemoryAllocation;
+
///
/// Gets the collected CPU events by the profiler from local or remote session.
///
diff --git a/Source/Editor/Windows/Profiler/ProfilerWindow.cs b/Source/Editor/Windows/Profiler/ProfilerWindow.cs
index deffd00bb..f5a5c6f86 100644
--- a/Source/Editor/Windows/Profiler/ProfilerWindow.cs
+++ b/Source/Editor/Windows/Profiler/ProfilerWindow.cs
@@ -24,6 +24,8 @@ namespace FlaxEditor.Windows.Profiler
private int _frameIndex = -1;
private int _framesCount;
private bool _showOnlyLastUpdateEvents = true;
+ private long _lastManagedMemory = 0;
+ private long _lastManagedMemoryProfiler = 0;
///
/// Gets or sets a value indicating whether live events recording is enabled.
@@ -144,6 +146,7 @@ namespace FlaxEditor.Windows.Profiler
{
_frameIndex = -1;
_framesCount = 0;
+ _lastManagedMemory = 0;
for (int i = 0; i < _tabs.ChildrenCount; i++)
{
if (_tabs.Children[i] is ProfilerMode mode)
@@ -198,6 +201,7 @@ namespace FlaxEditor.Windows.Profiler
AddMode(new Memory());
AddMode(new Assets());
AddMode(new Network());
+ AddMode(new Physics());
// Init view
_frameIndex = -1;
@@ -219,8 +223,16 @@ namespace FlaxEditor.Windows.Profiler
{
FlaxEngine.Profiler.BeginEvent("ProfilerWindow.OnUpdate");
+ // Get memory allocations during last frame
+ long managedMemory = GC.GetAllocatedBytesForCurrentThread();
+ if (_lastManagedMemory == 0)
+ _lastManagedMemory = managedMemory;
+ var managedAllocs = managedMemory - _lastManagedMemory - _lastManagedMemoryProfiler;
+ _lastManagedMemory = managedMemory;
+
ProfilerMode.SharedUpdateData sharedData = new ProfilerMode.SharedUpdateData();
sharedData.Begin();
+ sharedData.ManagedMemoryAllocation = (int)managedAllocs;
for (int i = 0; i < _tabs.ChildrenCount; i++)
{
if (_tabs.Children[i] is ProfilerMode mode)
@@ -243,6 +255,10 @@ namespace FlaxEditor.Windows.Profiler
_framesCount = Mathf.Min(_framesCount + 1, ProfilerMode.MaxSamples);
UpdateButtons();
+ // Get memory allocations within profiler window update to exclude from stats
+ managedMemory = GC.GetAllocatedBytesForCurrentThread();
+ _lastManagedMemoryProfiler = managedMemory - _lastManagedMemory;
+
FlaxEngine.Profiler.EndEvent();
}
}
diff --git a/Source/Editor/Windows/SceneTreeWindow.cs b/Source/Editor/Windows/SceneTreeWindow.cs
index 532f400a3..609f98f83 100644
--- a/Source/Editor/Windows/SceneTreeWindow.cs
+++ b/Source/Editor/Windows/SceneTreeWindow.cs
@@ -168,7 +168,7 @@ namespace FlaxEditor.Windows
actor.Transform = parentActor.Transform;
// Rename actor to identify it easily
- actor.Name = StringUtils.IncrementNameNumber(type.Name, x => parentActor.GetChild(x) == null);
+ actor.Name = Utilities.Utils.IncrementNameNumber(type.Name, x => parentActor.GetChild(x) == null);
}
// Spawn it
diff --git a/Source/Editor/Windows/Search/ContentSearchWindow.cs b/Source/Editor/Windows/Search/ContentSearchWindow.cs
index 5ee3ff11c..e6e407247 100644
--- a/Source/Editor/Windows/Search/ContentSearchWindow.cs
+++ b/Source/Editor/Windows/Search/ContentSearchWindow.cs
@@ -17,6 +17,7 @@ using FlaxEditor.Surface;
using FlaxEditor.Windows;
using FlaxEditor.Windows.Assets;
using FlaxEngine.GUI;
+using FlaxEngine.Utilities;
namespace FlaxEngine.Windows.Search
{
diff --git a/Source/Editor/Windows/SplashScreen.cpp b/Source/Editor/Windows/SplashScreen.cpp
index 649f0bcff..3e36d3618 100644
--- a/Source/Editor/Windows/SplashScreen.cpp
+++ b/Source/Editor/Windows/SplashScreen.cpp
@@ -123,7 +123,6 @@ const Char* SplashScreenQuotes[] =
TEXT("Hello There"),
TEXT("BAGUETTE"),
TEXT("All we had to do was follow the damn train, CJ"),
- TEXT("28 stab wounds"),
TEXT("Here we go again"),
TEXT("@everyone"),
TEXT("Potato"),
@@ -135,7 +134,8 @@ const Char* SplashScreenQuotes[] =
TEXT("Scooby dooby doo"),
TEXT("You shall not load!"),
TEXT("The roof, the roof, the roof is on fire!"),
- TEXT("I've seen better documentation ...\nFrom ransomware gangs !")
+ TEXT("I've seen better documentation...\nFrom ransomware gangs!"),
+ TEXT("Slava Ukraini!"),
};
SplashScreen::~SplashScreen()
@@ -153,7 +153,7 @@ void SplashScreen::Show()
LOG(Info, "Showing splash screen");
// Create window
- const float dpiScale = (float)Platform::GetDpi() / (float)DefaultDPI;
+ const float dpiScale = Platform::GetDpiScale();
CreateWindowSettings settings;
settings.Title = TEXT("Flax Editor");
settings.Size.X = 500 * dpiScale;
diff --git a/Source/Editor/Windows/VisualScriptDebuggerWindow.cs b/Source/Editor/Windows/VisualScriptDebuggerWindow.cs
index c85507a1c..330a3963b 100644
--- a/Source/Editor/Windows/VisualScriptDebuggerWindow.cs
+++ b/Source/Editor/Windows/VisualScriptDebuggerWindow.cs
@@ -12,6 +12,7 @@ using FlaxEditor.Surface;
using FlaxEditor.Windows.Assets;
using FlaxEngine;
using FlaxEngine.GUI;
+using FlaxEngine.Utilities;
namespace FlaxEditor.Windows
{
diff --git a/Source/Engine/Animations/AnimEvent.cpp b/Source/Engine/Animations/AnimEvent.cpp
index a3ec94861..e46a99c62 100644
--- a/Source/Engine/Animations/AnimEvent.cpp
+++ b/Source/Engine/Animations/AnimEvent.cpp
@@ -2,7 +2,7 @@
#include "AnimEvent.h"
#include "Engine/Scripting/BinaryModule.h"
-#include "Engine/Scripting/ManagedSerialization.h"
+#include "Engine/Scripting/Internal/ManagedSerialization.h"
#include "Engine/Serialization/SerializationFwd.h"
#include "Engine/Serialization/Serialization.h"
diff --git a/Source/Engine/Animations/AnimationGraph.cs b/Source/Engine/Animations/AnimationGraph.cs
index 86c623337..46b3acfc1 100644
--- a/Source/Engine/Animations/AnimationGraph.cs
+++ b/Source/Engine/Animations/AnimationGraph.cs
@@ -3,6 +3,7 @@
using System;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
+using System.Runtime.InteropServices.Marshalling;
namespace FlaxEngine
{
@@ -44,6 +45,7 @@ namespace FlaxEngine
/// The node evaluation context structure.
///
[StructLayout(LayoutKind.Sequential)]
+ [NativeMarshalling(typeof(ContextMarshaller))]
public struct Context
{
///
@@ -92,6 +94,61 @@ namespace FlaxEngine
public AnimatedModel Instance;
}
+ [CustomMarshaller(typeof(Context), MarshalMode.Default, typeof(ContextMarshaller))]
+ internal static class ContextMarshaller
+ {
+ [StructLayout(LayoutKind.Sequential)]
+ public struct ContextNative
+ {
+ public IntPtr Graph;
+ public IntPtr GraphExecutor;
+ public IntPtr Node;
+ public uint NodeId;
+ public int BoxId;
+ public float DeltaTime;
+ public ulong CurrentFrameIndex;
+ public IntPtr BaseModel;
+ public IntPtr Instance;
+ }
+
+ internal static Context ConvertToManaged(ContextNative unmanaged) => ToManaged(unmanaged);
+ internal static ContextNative ConvertToUnmanaged(Context managed) => ToNative(managed);
+
+ internal static Context ToManaged(ContextNative managed)
+ {
+ return new Context()
+ {
+ Graph = managed.Graph,
+ GraphExecutor = managed.GraphExecutor,
+ Node = managed.Node,
+ NodeId = managed.NodeId,
+ BoxId = managed.BoxId,
+ DeltaTime = managed.DeltaTime,
+ CurrentFrameIndex = managed.CurrentFrameIndex,
+ BaseModel = SkinnedModelMarshaller.ConvertToManaged(managed.BaseModel),
+ Instance = AnimatedModelMarshaller.ConvertToManaged(managed.Instance),
+ };
+ }
+ internal static ContextNative ToNative(Context managed)
+ {
+ return new ContextNative()
+ {
+ Graph = managed.Graph,
+ GraphExecutor = managed.GraphExecutor,
+ Node = managed.Node,
+ NodeId = managed.NodeId,
+ BoxId = managed.BoxId,
+ DeltaTime = managed.DeltaTime,
+ CurrentFrameIndex = managed.CurrentFrameIndex,
+ BaseModel = SkinnedModelMarshaller.ConvertToUnmanaged(managed.BaseModel),
+ Instance = AnimatedModelMarshaller.ConvertToUnmanaged(managed.Instance),
+ };
+ }
+ internal static void Free(ContextNative unmanaged)
+ {
+ }
+ }
+
///
/// The animation graph 'impulse' connections data container (the actual transfer is done via pointer as it gives better performance).
/// Container for skeleton nodes transformation hierarchy and any other required data.
@@ -118,12 +175,7 @@ namespace FlaxEngine
///
/// The root motion data.
///
- public Vector3 RootMotionTranslation;
-
- ///
- /// The root motion data.
- ///
- public Quaternion RootMotionRotation;
+ public Transform RootMotion;
///
/// The animation time position (in seconds).
@@ -194,8 +246,7 @@ namespace FlaxEngine
destination->NodesCount = source->NodesCount;
destination->Unused = source->Unused;
Utils.MemoryCopy(new IntPtr(destination->Nodes), new IntPtr(source->Nodes), (ulong)(source->NodesCount * sizeof(Transform)));
- destination->RootMotionTranslation = source->RootMotionTranslation;
- destination->RootMotionRotation = source->RootMotionRotation;
+ destination->RootMotion = source->RootMotion;
destination->Position = source->Position;
destination->Length = source->Length;
}
@@ -203,14 +254,16 @@ namespace FlaxEngine
#region Internal Calls
- [MethodImpl(MethodImplOptions.InternalCall)]
- internal static extern bool Internal_HasConnection(ref CustomNode.Context context, int boxId);
+ [LibraryImport("FlaxEngine", EntryPoint = "AnimGraphInternal_HasConnection")]
+ [return: MarshalAs(UnmanagedType.U1)]
+ internal static partial bool Internal_HasConnection(ref AnimationGraph.CustomNode.Context context, int boxId);
- [MethodImpl(MethodImplOptions.InternalCall)]
- internal static extern object Internal_GetInputValue(ref CustomNode.Context context, int boxId);
+ [LibraryImport("FlaxEngine", EntryPoint = "AnimGraphInternal_GetInputValue")]
+ [return: MarshalUsing(typeof(FlaxEngine.Interop.ManagedHandleMarshaller))]
+ internal static partial object Internal_GetInputValue(ref AnimationGraph.CustomNode.Context context, int boxId);
- [MethodImpl(MethodImplOptions.InternalCall)]
- internal static extern IntPtr Internal_GetOutputImpulseData(ref CustomNode.Context context);
+ [LibraryImport("FlaxEngine", EntryPoint = "AnimGraphInternal_GetOutputImpulseData")]
+ internal static partial IntPtr Internal_GetOutputImpulseData(ref AnimationGraph.CustomNode.Context context);
#endregion
}
diff --git a/Source/Engine/Animations/Animations.cpp b/Source/Engine/Animations/Animations.cpp
index 6d6787d91..3c80cadbe 100644
--- a/Source/Engine/Animations/Animations.cpp
+++ b/Source/Engine/Animations/Animations.cpp
@@ -29,6 +29,22 @@ public:
void PostExecute(TaskGraph* graph) override;
};
+namespace
+{
+ FORCE_INLINE bool CanUpdateModel(AnimatedModel* animatedModel)
+ {
+ auto skinnedModel = animatedModel->SkinnedModel.Get();
+ auto animGraph = animatedModel->AnimationGraph.Get();
+ return animGraph && animGraph->IsLoaded()
+ && skinnedModel && skinnedModel->IsLoaded()
+#if USE_EDITOR
+ // It may happen in editor so just add safe check to prevent any crashes
+ && animGraph->Graph.Parameters.Count() == animatedModel->GraphInstance.Parameters.Count()
+#endif
+ && animGraph->Graph.IsReady();
+ }
+}
+
AnimationsService AnimationManagerInstance;
Array UpdateList;
TaskGraphSystem* Animations::System = nullptr;
@@ -53,14 +69,9 @@ void AnimationsSystem::Job(int32 index)
{
PROFILE_CPU_NAMED("Animations.Job");
auto animatedModel = UpdateList[index];
- auto skinnedModel = animatedModel->SkinnedModel.Get();
- auto graph = animatedModel->AnimationGraph.Get();
- if (graph && graph->IsLoaded() && graph->Graph.CanUseWithSkeleton(skinnedModel)
-#if USE_EDITOR
- && graph->Graph.Parameters.Count() == animatedModel->GraphInstance.Parameters.Count() // It may happen in editor so just add safe check to prevent any crashes
-#endif
- )
+ if (CanUpdateModel(animatedModel))
{
+ auto graph = animatedModel->AnimationGraph.Get();
#if COMPILE_WITH_PROFILER && TRACY_ENABLE
const StringView graphName(graph->GetPath());
ZoneName(*graphName, graphName.Length());
@@ -120,13 +131,7 @@ void AnimationsSystem::PostExecute(TaskGraph* graph)
for (int32 index = 0; index < UpdateList.Count(); index++)
{
auto animatedModel = UpdateList[index];
- auto skinnedModel = animatedModel->SkinnedModel.Get();
- auto animGraph = animatedModel->AnimationGraph.Get();
- if (animGraph && animGraph->IsLoaded() && animGraph->Graph.CanUseWithSkeleton(skinnedModel)
-#if USE_EDITOR
- && animGraph->Graph.Parameters.Count() == animatedModel->GraphInstance.Parameters.Count() // It may happen in editor so just add safe check to prevent any crashes
-#endif
- )
+ if (CanUpdateModel(animatedModel))
{
animatedModel->OnAnimationUpdated_Sync();
}
diff --git a/Source/Engine/Animations/Graph/AnimGraph.Base.cpp b/Source/Engine/Animations/Graph/AnimGraph.Base.cpp
index d7cdbeecd..48fa7fed1 100644
--- a/Source/Engine/Animations/Graph/AnimGraph.Base.cpp
+++ b/Source/Engine/Animations/Graph/AnimGraph.Base.cpp
@@ -23,7 +23,7 @@ AnimSubGraph* AnimGraphBase::LoadSubGraph(const void* data, int32 dataLength, co
auto subGraph = New(_graph);
// Load graph
- MemoryReadStream stream((byte*)data, dataLength);
+ MemoryReadStream stream((const byte*)data, dataLength);
if (subGraph->Load(&stream, false))
{
// Load failed
@@ -180,8 +180,6 @@ bool AnimGraphBase::onNodeLoaded(Node* n)
const Float4 range = n->Values[0].AsFloat4();
for (int32 i = 0; i < ANIM_GRAPH_MULTI_BLEND_MAX_ANIMS; i++)
{
- auto data0 = n->Values[i * 2 + 4].AsFloat4();
- data0.X = Math::Clamp(data0.X, range.X, range.Y);
n->Assets[i] = Content::LoadAsync((Guid)n->Values[i * 2 + 5]);
n->Data.MultiBlend1D.IndicesSorted[i] = n->Assets[i] ? i : ANIM_GRAPH_MULTI_BLEND_MAX_ANIMS;
}
@@ -200,9 +198,6 @@ bool AnimGraphBase::onNodeLoaded(Node* n)
const Float4 range = n->Values[0].AsFloat4();
for (int32 i = 0; i < ANIM_GRAPH_MULTI_BLEND_MAX_ANIMS; i++)
{
- auto data0 = n->Values[i * 2 + 4].AsFloat4();
- data0.X = Math::Clamp(data0.X, range.X, range.Y);
- data0.Y = Math::Clamp(data0.Y, range.Z, range.W);
n->Assets[i] = (Asset*)Content::LoadAsync((Guid)n->Values[i * 2 + 5]);
if (n->Assets[i])
{
@@ -283,91 +278,11 @@ bool AnimGraphBase::onNodeLoaded(Node* n)
Value& surfaceData = n->Values[1];
data.Graph = LoadSubGraph(surfaceData.AsBlob.Data, surfaceData.AsBlob.Length, (const Char*)name.AsBlob.Data);
- // Initialize transitions
- Value& transitionsData = n->Values[2];
- int32 validTransitions = 0;
- if (transitionsData.Type == VariantType::Blob && transitionsData.AsBlob.Length)
- {
- MemoryReadStream stream((byte*)transitionsData.AsBlob.Data, transitionsData.AsBlob.Length);
-
- int32 version;
- stream.ReadInt32(&version);
- if (version != 1)
- {
- LOG(Warning, "Invalid version of the Anim Graph state transitions data.");
- return true;
- }
-
- int32 transitionsCount;
- stream.ReadInt32(&transitionsCount);
-
- StateTransitions.EnsureCapacity(StateTransitions.Count() + transitionsCount);
-
- AnimGraphStateTransition transition;
- for (int32 i = 0; i < transitionsCount; i++)
- {
- struct Data
- {
- int32 Destination;
- int32 Flags;
- int32 Order;
- float BlendDuration;
- int32 BlendMode;
- int32 Unused0;
- int32 Unused1;
- int32 Unused2;
- };
- Data transitionData;
- stream.ReadBytes(&transitionData, sizeof(transitionData));
-
- transition.Flags = (AnimGraphStateTransition::FlagTypes)transitionData.Flags;
- transition.BlendDuration = transitionData.BlendDuration;
- transition.BlendMode = (AlphaBlendMode)transitionData.BlendMode;
- transition.Destination = GetNode(transitionData.Destination);
- transition.RuleGraph = nullptr;
-
- int32 ruleSize;
- stream.ReadInt32(&ruleSize);
- const auto ruleBytes = (byte*)stream.Move(ruleSize);
-
- if (static_cast(transition.Flags & AnimGraphStateTransition::FlagTypes::Enabled) == 0)
- {
- // Skip disabled transitions
- continue;
- }
-
- if (ruleSize != 0)
- {
- transition.RuleGraph = LoadSubGraph(ruleBytes, ruleSize, TEXT("Rule"));
- if (transition.RuleGraph && transition.RuleGraph->GetRootNode() == nullptr)
- {
- LOG(Warning, "Missing root node for the state machine transition rule graph.");
- continue;
- }
- }
-
- if (transition.Destination == nullptr)
- {
- LOG(Warning, "Missing target node for the state machine transition.");
- continue;
- }
-
- if (validTransitions == ANIM_GRAPH_MAX_STATE_TRANSITIONS)
- {
- LOG(Warning, "State uses too many transitions.");
- continue;
- }
-
- data.Transitions[validTransitions++] = (uint16)StateTransitions.Count();
- StateTransitions.Add(transition);
- }
- }
- if (validTransitions != ANIM_GRAPH_MAX_STATE_TRANSITIONS)
- data.Transitions[validTransitions] = AnimGraphNode::StateData::InvalidTransitionIndex;
-
// Release data to don't use that memory
surfaceData = Value::Null;
- transitionsData = Value::Null;
+
+ // Initialize transitions
+ LoadStateTransitions(data, n->Values[2]);
break;
}
@@ -443,6 +358,10 @@ bool AnimGraphBase::onNodeLoaded(Node* n)
case 33:
ADD_BUCKET(InstanceDataBucketInit);
break;
+ // Any State
+ case 34:
+ LoadStateTransitions(n->Data.AnyState, n->Values[0]);
+ break;
}
break;
// Custom
@@ -465,4 +384,91 @@ bool AnimGraphBase::onNodeLoaded(Node* n)
return VisjectGraph::onNodeLoaded(n);
}
+void AnimGraphBase::LoadStateTransitions(AnimGraphNode::StateBaseData& data, Value& transitionsData)
+{
+ int32 validTransitions = 0;
+ if (transitionsData.Type == VariantType::Blob && transitionsData.AsBlob.Length)
+ {
+ MemoryReadStream stream((byte*)transitionsData.AsBlob.Data, transitionsData.AsBlob.Length);
+
+ int32 version;
+ stream.ReadInt32(&version);
+ if (version != 1)
+ {
+ LOG(Warning, "Invalid version of the Anim Graph state transitions data.");
+ return;
+ }
+
+ int32 transitionsCount;
+ stream.ReadInt32(&transitionsCount);
+
+ StateTransitions.EnsureCapacity(StateTransitions.Count() + transitionsCount);
+
+ AnimGraphStateTransition transition;
+ for (int32 i = 0; i < transitionsCount; i++)
+ {
+ // Must match StateMachineTransition.Data in C#
+ struct Data
+ {
+ int32 Destination;
+ int32 Flags;
+ int32 Order;
+ float BlendDuration;
+ int32 BlendMode;
+ int32 Unused0;
+ int32 Unused1;
+ int32 Unused2;
+ };
+ Data transitionData;
+ stream.ReadBytes(&transitionData, sizeof(transitionData));
+
+ transition.Flags = (AnimGraphStateTransition::FlagTypes)transitionData.Flags;
+ transition.BlendDuration = transitionData.BlendDuration;
+ transition.BlendMode = (AlphaBlendMode)transitionData.BlendMode;
+ transition.Destination = GetNode(transitionData.Destination);
+ transition.RuleGraph = nullptr;
+
+ int32 ruleSize;
+ stream.ReadInt32(&ruleSize);
+ const auto ruleBytes = (byte*)stream.Move(ruleSize);
+
+ if (static_cast(transition.Flags & AnimGraphStateTransition::FlagTypes::Enabled) == 0)
+ {
+ // Skip disabled transitions
+ continue;
+ }
+
+ if (ruleSize != 0)
+ {
+ transition.RuleGraph = LoadSubGraph(ruleBytes, ruleSize, TEXT("Rule"));
+ if (transition.RuleGraph && transition.RuleGraph->GetRootNode() == nullptr)
+ {
+ LOG(Warning, "Missing root node for the state machine transition rule graph.");
+ continue;
+ }
+ }
+
+ if (transition.Destination == nullptr)
+ {
+ LOG(Warning, "Missing target node for the state machine transition.");
+ continue;
+ }
+
+ if (validTransitions == ANIM_GRAPH_MAX_STATE_TRANSITIONS)
+ {
+ LOG(Warning, "State uses too many transitions.");
+ continue;
+ }
+
+ data.Transitions[validTransitions++] = (uint16)StateTransitions.Count();
+ StateTransitions.Add(transition);
+ }
+ }
+ if (validTransitions != ANIM_GRAPH_MAX_STATE_TRANSITIONS)
+ data.Transitions[validTransitions] = AnimGraphNode::StateData::InvalidTransitionIndex;
+
+ // Release data to don't use that memory
+ transitionsData = AnimGraphExecutor::Value::Null;
+}
+
#undef ADD_BUCKET
diff --git a/Source/Engine/Animations/Graph/AnimGraph.Custom.cpp b/Source/Engine/Animations/Graph/AnimGraph.Custom.cpp
index d2068ae02..d74c523f1 100644
--- a/Source/Engine/Animations/Graph/AnimGraph.Custom.cpp
+++ b/Source/Engine/Animations/Graph/AnimGraph.Custom.cpp
@@ -2,24 +2,23 @@
#include "AnimGraph.h"
#include "Engine/Debug/DebugLog.h"
-#include "Engine/Scripting/InternalCalls.h"
+#include "Engine/Scripting/Scripting.h"
#include "Engine/Scripting/BinaryModule.h"
+#include "Engine/Scripting/ManagedCLR/MCore.h"
#include "Engine/Scripting/ManagedCLR/MDomain.h"
#include "Engine/Scripting/ManagedCLR/MMethod.h"
#include "Engine/Scripting/ManagedCLR/MClass.h"
#include "Engine/Scripting/ManagedCLR/MUtils.h"
-#include "Engine/Scripting/Scripting.h"
-#include "Engine/Scripting/MException.h"
+#include "Engine/Scripting/ManagedCLR/MException.h"
+#include "Engine/Scripting/Internal/InternalCalls.h"
#include "Engine/Content/Assets/SkinnedModel.h"
-#if USE_MONO
-
-#include
+#if USE_CSHARP
struct InternalInitData
{
- MonoArray* Values;
- MonoObject* BaseModel;
+ MArray* Values;
+ MObject* BaseModel;
};
struct InternalContext
@@ -31,8 +30,8 @@ struct InternalContext
int32 BoxId;
float DeltaTime;
uint64 CurrentFrameIndex;
- MonoObject* BaseModel;
- MonoObject* Instance;
+ MObject* BaseModel;
+ MObject* Instance;
};
struct InternalImpulse
@@ -40,63 +39,57 @@ struct InternalImpulse
int32 NodesCount;
int32 Unused;
Transform* Nodes;
- Vector3 RootMotionTranslation;
- Quaternion RootMotionRotation;
+ Transform RootMotion;
float Position;
float Length;
};
static_assert(sizeof(InternalImpulse) == sizeof(AnimGraphImpulse), "Please update managed impulse type for Anim Graph to match the C++ backend data layout.");
-namespace AnimGraphInternal
+DEFINE_INTERNAL_CALL(bool) AnimGraphInternal_HasConnection(InternalContext* context, int32 boxId)
{
- bool HasConnection(InternalContext* context, int32 boxId)
- {
- const auto box = context->Node->TryGetBox(boxId);
- if (box == nullptr)
- DebugLog::ThrowArgumentOutOfRange("boxId");
- return box->HasConnection();
- }
+ const auto box = context->Node->TryGetBox(boxId);
+ if (box == nullptr)
+ DebugLog::ThrowArgumentOutOfRange("boxId");
+ return box->HasConnection();
+}
- MonoObject* GetInputValue(InternalContext* context, int32 boxId)
- {
- const auto box = context->Node->TryGetBox(boxId);
- if (box == nullptr)
- DebugLog::ThrowArgumentOutOfRange("boxId");
- if (!box->HasConnection())
- DebugLog::ThrowArgument("boxId", "This box has no connection. Use HasConnection to check if can get input value.");
+DEFINE_INTERNAL_CALL(MObject*) AnimGraphInternal_GetInputValue(InternalContext* context, int32 boxId)
+{
+ const auto box = context->Node->TryGetBox(boxId);
+ if (box == nullptr)
+ DebugLog::ThrowArgumentOutOfRange("boxId");
+ if (!box->HasConnection())
+ DebugLog::ThrowArgument("boxId", "This box has no connection. Use HasConnection to check if can get input value.");
- Variant value = Variant::Null;
- context->GraphExecutor->GetInputValue(box, value);
+ Variant value = Variant::Null;
+ context->GraphExecutor->GetInputValue(box, value);
- // Cast value to prevent implicit value conversion issues and handling this on C# side
- if (!(box->Type.Type == VariantType::Void && value.Type.Type == VariantType::Pointer))
- value = Variant::Cast(value, box->Type);
- return MUtils::BoxVariant(value);
- }
+ // Cast value to prevent implicit value conversion issues and handling this on C# side
+ if (!(box->Type.Type == VariantType::Void && value.Type.Type == VariantType::Pointer))
+ value = Variant::Cast(value, box->Type);
+ return MUtils::BoxVariant(value);
+}
- AnimGraphImpulse* GetOutputImpulseData(InternalContext* context)
- {
- const auto nodes = context->Node->GetNodes(context->GraphExecutor);
- context->GraphExecutor->InitNodes(nodes);
- return nodes;
- }
+DEFINE_INTERNAL_CALL(AnimGraphImpulse*) AnimGraphInternal_GetOutputImpulseData(InternalContext* context)
+{
+ const auto nodes = context->Node->GetNodes(context->GraphExecutor);
+ context->GraphExecutor->InitNodes(nodes);
+ return nodes;
}
#endif
void AnimGraphExecutor::initRuntime()
{
-#if USE_MONO
- ADD_INTERNAL_CALL("FlaxEngine.AnimationGraph::Internal_HasConnection", &AnimGraphInternal::HasConnection);
- ADD_INTERNAL_CALL("FlaxEngine.AnimationGraph::Internal_GetInputValue", &AnimGraphInternal::GetInputValue);
- ADD_INTERNAL_CALL("FlaxEngine.AnimationGraph::Internal_GetOutputImpulseData", &AnimGraphInternal::GetOutputImpulseData);
-#endif
+ ADD_INTERNAL_CALL("FlaxEngine.AnimationGraph::Internal_HasConnection", &AnimGraphInternal_HasConnection);
+ ADD_INTERNAL_CALL("FlaxEngine.AnimationGraph::Internal_GetInputValue", &AnimGraphInternal_GetInputValue);
+ ADD_INTERNAL_CALL("FlaxEngine.AnimationGraph::Internal_GetOutputImpulseData", &AnimGraphInternal_GetOutputImpulseData);
}
void AnimGraphExecutor::ProcessGroupCustom(Box* boxBase, Node* nodeBase, Value& value)
{
-#if USE_MONO
+#if USE_CSHARP
auto& context = Context.Get();
if (context.ValueCache.TryGet(boxBase, value))
return;
@@ -122,7 +115,7 @@ void AnimGraphExecutor::ProcessGroupCustom(Box* boxBase, Node* nodeBase, Value&
internalContext.Instance = context.Data->Object ? context.Data->Object->GetOrCreateManagedInstance() : nullptr;
// Peek managed object
- const auto obj = mono_gchandle_get_target(data.Handle);
+ const auto obj = MCore::GCHandle::GetTarget(data.Handle);
if (obj == nullptr)
{
LOG(Warning, "Custom node instance is null.");
@@ -133,7 +126,7 @@ void AnimGraphExecutor::ProcessGroupCustom(Box* boxBase, Node* nodeBase, Value&
void* params[1];
params[0] = &internalContext;
MObject* exception = nullptr;
- MonoObject* result = data.Evaluate->Invoke(obj, params, &exception);
+ MObject* result = data.Evaluate->Invoke(obj, params, &exception);
if (exception)
{
MException ex(exception);
@@ -152,12 +145,6 @@ bool AnimGraph::IsReady() const
return BaseModel && BaseModel->IsLoaded();
}
-bool AnimGraph::CanUseWithSkeleton(SkinnedModel* other) const
-{
- // All data loaded and nodes count the same
- return IsReady() && other && other->IsLoaded() && other->Skeleton.Nodes.Count() == BaseModel->Skeleton.Nodes.Count();
-}
-
void AnimGraph::ClearCustomNode(Node* node)
{
// Clear data
@@ -165,16 +152,14 @@ void AnimGraph::ClearCustomNode(Node* node)
data.Evaluate = nullptr;
if (data.Handle)
{
-#if USE_MONO
- mono_gchandle_free(data.Handle);
-#endif
+ MCore::GCHandle::Free(data.Handle);
data.Handle = 0;
}
}
bool AnimGraph::InitCustomNode(Node* node)
{
-#if USE_MONO
+#if USE_CSHARP
// Fetch the node logic controller type
if (node->Values.Count() < 2 || node->Values[0].Type.Type != ValueType::String)
{
@@ -182,7 +167,7 @@ bool AnimGraph::InitCustomNode(Node* node)
return false;
}
const StringView typeName(node->Values[0]);
- const MString typeNameStd = typeName.ToStringAnsi();
+ const StringAnsi typeNameStd = typeName.ToStringAnsi();
MClass* type = Scripting::FindClass(typeNameStd);
if (type == nullptr)
{
@@ -205,18 +190,15 @@ bool AnimGraph::InitCustomNode(Node* node)
}
// Create node values managed array
- if (mono_domain_get() == nullptr)
- Scripting::GetScriptsDomain()->Dispatch();
- const auto values = mono_array_new(mono_domain_get(), mono_get_object_class(), node->Values.Count());
+ MCore::Thread::Attach();
+ MArray* values = MCore::Array::New( MCore::TypeCache::Object, node->Values.Count());
+ MObject** valuesPtr = MCore::Array::GetAddress(values);
for (int32 i = 0; i < node->Values.Count(); i++)
- {
- const auto v = MUtils::BoxVariant(node->Values[i]);
- mono_array_set(values, MonoObject*, i, v);
- }
+ valuesPtr[i] = MUtils::BoxVariant(node->Values[i]);
// Allocate managed node object (create GC handle to prevent destruction)
- const auto obj = type->CreateInstance();
- const auto handleGC = mono_gchandle_new(obj, false);
+ MObject* obj = type->CreateInstance();
+ const MGCHandle handleGC = MCore::GCHandle::New(obj);
// Initialize node
InternalInitData initData;
@@ -228,7 +210,7 @@ bool AnimGraph::InitCustomNode(Node* node)
load->Invoke(obj, params, &exception);
if (exception)
{
- mono_gchandle_free(handleGC);
+ MCore::GCHandle::Free(handleGC);
MException ex(exception);
ex.Log(LogType::Warning, TEXT("AnimGraph"));
diff --git a/Source/Engine/Animations/Graph/AnimGraph.cpp b/Source/Engine/Animations/Graph/AnimGraph.cpp
index fa770f31b..71c9534e0 100644
--- a/Source/Engine/Animations/Graph/AnimGraph.cpp
+++ b/Source/Engine/Animations/Graph/AnimGraph.cpp
@@ -7,55 +7,10 @@
#include "Engine/Graphics/Models/SkeletonData.h"
#include "Engine/Scripting/Scripting.h"
+extern void RetargetSkeletonNode(const SkeletonData& sourceSkeleton, const SkeletonData& targetSkeleton, const SkinnedModel::SkeletonMapping& sourceMapping, Transform& node, int32 i);
+
ThreadLocal AnimGraphExecutor::Context;
-RootMotionData RootMotionData::Identity = { Vector3(0.0f), Quaternion(0.0f, 0.0f, 0.0f, 1.0f) };
-
-RootMotionData& RootMotionData::operator+=(const RootMotionData& b)
-{
- Translation += b.Translation;
- Rotation *= b.Rotation;
- return *this;
-}
-
-RootMotionData& RootMotionData::operator+=(const Transform& b)
-{
- Translation += b.Translation;
- Rotation *= b.Orientation;
- return *this;
-}
-
-RootMotionData& RootMotionData::operator-=(const Transform& b)
-{
- Translation -= b.Translation;
- Quaternion invRotation = Rotation;
- invRotation.Invert();
- Quaternion::Multiply(invRotation, b.Orientation, Rotation);
- return *this;
-}
-
-RootMotionData RootMotionData::operator+(const RootMotionData& b) const
-{
- RootMotionData result;
-
- result.Translation = Translation + b.Translation;
- result.Rotation = Rotation * b.Rotation;
-
- return result;
-}
-
-RootMotionData RootMotionData::operator-(const RootMotionData& b) const
-{
- RootMotionData result;
-
- result.Rotation = Rotation;
- result.Rotation.Invert();
- Vector3::Transform(b.Translation - Translation, result.Rotation, result.Translation);
- Quaternion::Multiply(result.Rotation, b.Rotation, result.Rotation);
-
- return result;
-}
-
Transform AnimGraphImpulse::GetNodeModelTransformation(SkeletonData& skeleton, int32 nodeIndex) const
{
const int32 parentIndex = skeleton.Nodes[nodeIndex].ParentIndex;
@@ -87,7 +42,7 @@ void AnimGraphInstanceData::Clear()
LastUpdateTime = -1;
CurrentFrame = 0;
RootTransform = Transform::Identity;
- RootMotion = RootMotionData::Identity;
+ RootMotion = Transform::Identity;
Parameters.Resize(0);
State.Resize(0);
NodesPose.Resize(0);
@@ -103,7 +58,7 @@ void AnimGraphInstanceData::ClearState()
LastUpdateTime = -1;
CurrentFrame = 0;
RootTransform = Transform::Identity;
- RootMotion = RootMotionData::Identity;
+ RootMotion = Transform::Identity;
State.Resize(0);
NodesPose.Resize(0);
Slots.Clear();
@@ -257,15 +212,12 @@ void AnimGraphExecutor::Update(AnimGraphInstanceData& data, float dt)
e.Hit = false;
// Init empty nodes data
- context.EmptyNodes.RootMotion = RootMotionData::Identity;
+ context.EmptyNodes.RootMotion = Transform::Identity;
context.EmptyNodes.Position = 0.0f;
context.EmptyNodes.Length = 0.0f;
context.EmptyNodes.Nodes.Resize(_skeletonNodesCount, false);
for (int32 i = 0; i < _skeletonNodesCount; i++)
- {
- auto& node = skeleton.Nodes[i];
- context.EmptyNodes.Nodes[i] = node.LocalTransform;
- }
+ context.EmptyNodes.Nodes[i] = skeleton.Nodes[i].LocalTransform;
}
// Update the animation graph and gather skeleton nodes transformations in nodes local space
@@ -301,6 +253,44 @@ void AnimGraphExecutor::Update(AnimGraphInstanceData& data, float dt)
}
}
}
+ SkeletonData* animResultSkeleton = &skeleton;
+
+ // Retarget animation when using output pose from other skeleton
+ AnimGraphImpulse retargetNodes;
+ if (_graph.BaseModel != data.NodesSkeleton)
+ {
+ ANIM_GRAPH_PROFILE_EVENT("Retarget");
+
+ // Init nodes for the target skeleton
+ auto& targetSkeleton = data.NodesSkeleton->Skeleton;
+ retargetNodes = *animResult;
+ retargetNodes.Nodes.Resize(targetSkeleton.Nodes.Count());
+ Transform* targetNodes = retargetNodes.Nodes.Get();
+ for (int32 i = 0; i < retargetNodes.Nodes.Count(); i++)
+ targetNodes[i] = targetSkeleton.Nodes[i].LocalTransform;
+
+ // Use skeleton mapping
+ const SkinnedModel::SkeletonMapping mapping = data.NodesSkeleton->GetSkeletonMapping(_graph.BaseModel);
+ if (mapping.NodesMapping.IsValid())
+ {
+ const auto& sourceSkeleton = _graph.BaseModel->Skeleton;
+ Transform* sourceNodes = animResult->Nodes.Get();
+ for (int32 i = 0; i < retargetNodes.Nodes.Count(); i++)
+ {
+ const int32 nodeToNode = mapping.NodesMapping[i];
+ if (nodeToNode != -1)
+ {
+ Transform node = sourceNodes[nodeToNode];
+ RetargetSkeletonNode(sourceSkeleton, targetSkeleton, mapping, node, i);
+ targetNodes[i] = node;
+ }
+
+ }
+ }
+
+ animResult = &retargetNodes;
+ animResultSkeleton = &targetSkeleton;
+ }
// Allow for external override of the local pose (eg. by the ragdoll)
data.LocalPoseOverride(animResult);
@@ -309,13 +299,14 @@ void AnimGraphExecutor::Update(AnimGraphInstanceData& data, float dt)
{
ANIM_GRAPH_PROFILE_EVENT("Global Pose");
- data.NodesPose.Resize(_skeletonNodesCount, false);
+ ASSERT(animResultSkeleton->Nodes.Count() == animResult->Nodes.Count());
+ data.NodesPose.Resize(animResultSkeleton->Nodes.Count(), false);
Transform* nodesTransformations = animResult->Nodes.Get();
// Note: this assumes that nodes are sorted (parents first)
- for (int32 nodeIndex = 0; nodeIndex < _skeletonNodesCount; nodeIndex++)
+ for (int32 nodeIndex = 0; nodeIndex < animResultSkeleton->Nodes.Count(); nodeIndex++)
{
- const int32 parentIndex = skeleton.Nodes[nodeIndex].ParentIndex;
+ const int32 parentIndex = animResultSkeleton->Nodes[nodeIndex].ParentIndex;
if (parentIndex != -1)
{
nodesTransformations[parentIndex].LocalToWorld(nodesTransformations[nodeIndex], nodesTransformations[nodeIndex]);
diff --git a/Source/Engine/Animations/Graph/AnimGraph.h b/Source/Engine/Animations/Graph/AnimGraph.h
index aeb91fbd0..41edb72f3 100644
--- a/Source/Engine/Animations/Graph/AnimGraph.h
+++ b/Source/Engine/Animations/Graph/AnimGraph.h
@@ -26,58 +26,6 @@ class AnimGraphExecutor;
class SkinnedModel;
class SkeletonData;
-///
-/// The root motion data container. Supports displacement and rotation (no scale component).
-///
-struct RootMotionData
-{
- static RootMotionData Identity;
-
- Vector3 Translation;
- Quaternion Rotation;
-
- RootMotionData()
- {
- }
-
- RootMotionData(const Vector3& translation, const Quaternion& rotation)
- {
- Translation = translation;
- Rotation = rotation;
- }
-
- RootMotionData(const RootMotionData& other)
- {
- Translation = other.Translation;
- Rotation = other.Rotation;
- }
-
- explicit RootMotionData(const Transform& other)
- {
- Translation = other.Translation;
- Rotation = other.Orientation;
- }
-
- RootMotionData& operator=(const Transform& other)
- {
- Translation = other.Translation;
- Rotation = other.Orientation;
- return *this;
- }
-
- RootMotionData& operator+=(const RootMotionData& b);
- RootMotionData& operator+=(const Transform& b);
- RootMotionData& operator-=(const Transform& b);
- RootMotionData operator+(const RootMotionData& b) const;
- RootMotionData operator-(const RootMotionData& b) const;
-
- static void Lerp(const RootMotionData& t1, const RootMotionData& t2, float amount, RootMotionData& result)
- {
- Vector3::Lerp(t1.Translation, t2.Translation, amount, result.Translation);
- Quaternion::Slerp(t1.Rotation, t2.Rotation, amount, result.Rotation);
- }
-};
-
///
/// The animation graph 'impulse' connections data container (the actual transfer is done via pointer as it gives better performance).
/// Container for skeleton nodes transformation hierarchy and any other required data.
@@ -93,7 +41,7 @@ struct FLAXENGINE_API AnimGraphImpulse
///
/// The root motion extracted from the animation to apply on animated object.
///
- RootMotionData RootMotion = RootMotionData::Identity;
+ Transform RootMotion = Transform::Identity;
///
/// The animation time position (in seconds).
@@ -172,25 +120,12 @@ public:
///
enum class FlagTypes
{
- ///
- /// The none.
- ///
None = 0,
-
- ///
- /// The enabled flag.
- ///
Enabled = 1,
-
- ///
- /// The solo flag.
- ///
Solo = 2,
-
- ///
- /// The use default rule flag.
- ///
UseDefaultRule = 4,
+ InterruptionRuleRechecking = 8,
+ InterruptionInstant = 16,
};
public:
@@ -361,7 +296,7 @@ public:
///
/// The current root motion delta to apply on a target object.
///
- RootMotionData RootMotion = RootMotionData::Identity;
+ Transform RootMotion = Transform::Identity;
///
/// The animation graph parameters collection (instanced, override the default values).
@@ -384,7 +319,12 @@ public:
ScriptingObject* Object;
///
- /// The custom event called after local pose evaluation.
+ /// The output nodes pose skeleton asset to use. Allows to remap evaluated animation pose for Base Model of the Anim Graph to the target Animated Model that plays it. Nodes Pose will match its skeleton. Use null if disable retargetting.
+ ///
+ SkinnedModel* NodesSkeleton = nullptr;
+
+ ///
+ /// The custom event called after local pose evaluation and retargetting.
///
Delegate LocalPoseOverride;
@@ -495,24 +435,31 @@ public:
AnimSubGraph* Graph;
};
- struct StateData
+ struct StateBaseData
{
///
/// The invalid transition valid used in Transitions to indicate invalid transition linkage.
///
const static uint16 InvalidTransitionIndex = MAX_uint16;
-
- ///
- /// The graph of the state. Contains the state animation evaluation graph. Its root node is the state output node with an input box for the state blend pose sampling.
- ///
- AnimSubGraph* Graph;
-
+
///
/// The outgoing transitions from this state to the other states. Each array item contains index of the transition data from the state node graph transitions cache. Value InvalidTransitionIndex is used for last transition to indicate the transitions amount.
///
uint16 Transitions[ANIM_GRAPH_MAX_STATE_TRANSITIONS];
};
+ struct StateData : StateBaseData
+ {
+ ///
+ /// The graph of the state. Contains the state animation evaluation graph. Its root node is the state output node with an input box for the state blend pose sampling.
+ ///
+ AnimSubGraph* Graph;
+ };
+
+ struct AnyStateData : StateBaseData
+ {
+ };
+
struct CustomData
{
///
@@ -523,7 +470,7 @@ public:
///
/// The GC handle to the managed instance of the node object.
///
- uint32 Handle;
+ MGCHandle Handle;
};
struct CurveData
@@ -564,6 +511,7 @@ public:
MultiBlend2DData MultiBlend2D;
StateMachineData StateMachine;
StateData State;
+ AnyStateData AnyState;
CustomData Custom;
CurveData Curve;
AnimationGraphFunctionData AnimationGraphFunction;
@@ -681,6 +629,9 @@ public:
protected:
// [Graph]
bool onNodeLoaded(Node* n) override;
+
+private:
+ void LoadStateTransitions(AnimGraphNode::StateBaseData& data, Value& transitionsData);
};
///
@@ -763,13 +714,6 @@ public:
///
bool IsReady() const;
- ///
- /// Determines whether this graph can be used with the specified skeleton.
- ///
- /// The other skinned model to check.
- /// True if can perform the update, otherwise false.
- bool CanUseWithSkeleton(SkinnedModel* other) const;
-
private:
void ClearCustomNode(Node* node);
bool InitCustomNode(Node* node);
@@ -887,7 +831,6 @@ private:
};
int32 GetRootNodeIndex(Animation* anim);
- void ExtractRootMotion(const Animation::NodeToChannel* mapping, int32 rootNodeIndex, Animation* anim, float pos, float prevPos, Transform& rootNode, RootMotionData& rootMotion);
void ProcessAnimEvents(AnimGraphNode* node, bool loop, float length, float animPos, float animPrevPos, Animation* anim, float speed);
void ProcessAnimation(AnimGraphImpulse* nodes, AnimGraphNode* node, bool loop, float length, float pos, float prevPos, Animation* anim, float speed, float weight = 1.0f, ProcessAnimationMode mode = ProcessAnimationMode::Override);
Variant SampleAnimation(AnimGraphNode* node, bool loop, float length, float startTimePos, float prevTimePos, float& newTimePos, Animation* anim, float speed);
@@ -895,4 +838,5 @@ private:
Variant SampleAnimationsWithBlend(AnimGraphNode* node, bool loop, float length, float startTimePos, float prevTimePos, float& newTimePos, Animation* animA, Animation* animB, Animation* animC, float speedA, float speedB, float speedC, float alphaA, float alphaB, float alphaC);
Variant Blend(AnimGraphNode* node, const Value& poseA, const Value& poseB, float alpha, AlphaBlendMode alphaMode);
Variant SampleState(AnimGraphNode* state);
+ void UpdateStateTransitions(AnimGraphContext& context, const AnimGraphNode::StateMachineData& stateMachineData, AnimGraphInstanceData::StateMachineBucket& stateMachineBucket, const AnimGraphNode::StateBaseData& stateData);
};
diff --git a/Source/Engine/Animations/Graph/AnimGroup.Animation.cpp b/Source/Engine/Animations/Graph/AnimGroup.Animation.cpp
index 86aa42ff6..c508ab3c8 100644
--- a/Source/Engine/Animations/Graph/AnimGroup.Animation.cpp
+++ b/Source/Engine/Animations/Graph/AnimGroup.Animation.cpp
@@ -11,7 +11,7 @@
namespace
{
- void BlendAdditiveWeightedRotation(Quaternion& base, Quaternion& additive, float weight)
+ FORCE_INLINE void BlendAdditiveWeightedRotation(Quaternion& base, Quaternion& additive, float weight)
{
// Pick a shortest path between rotation to fix blending artifacts
additive *= weight;
@@ -19,6 +19,36 @@ namespace
additive *= -1;
base += additive;
}
+
+ FORCE_INLINE void NormalizeRotations(AnimGraphImpulse* nodes, RootMotionMode rootMotionMode)
+ {
+ for (int32 i = 0; i < nodes->Nodes.Count(); i++)
+ {
+ nodes->Nodes[i].Orientation.Normalize();
+ }
+ if (rootMotionMode != RootMotionMode::NoExtraction)
+ {
+ nodes->RootMotion.Orientation.Normalize();
+ }
+ }
+}
+
+void RetargetSkeletonNode(const SkeletonData& sourceSkeleton, const SkeletonData& targetSkeleton, const SkinnedModel::SkeletonMapping& mapping, Transform& node, int32 i)
+{
+ const int32 nodeToNode = mapping.NodesMapping[i];
+ if (nodeToNode == -1)
+ return;
+
+ // Map source skeleton node to the target skeleton (use ref pose difference)
+ const auto& sourceNode = sourceSkeleton.Nodes[nodeToNode];
+ const auto& targetNode = targetSkeleton.Nodes[i];
+ Transform value = node;
+ const Transform sourceToTarget = targetNode.LocalTransform - sourceNode.LocalTransform;
+ value.Translation += sourceToTarget.Translation;
+ value.Scale *= sourceToTarget.Scale;
+ value.Orientation = sourceToTarget.Orientation * value.Orientation; // TODO: find out why this doesn't match referenced animation when played on that skeleton originally
+ value.Orientation.Normalize();
+ node = value;
}
int32 AnimGraphExecutor::GetRootNodeIndex(Animation* anim)
@@ -40,52 +70,6 @@ int32 AnimGraphExecutor::GetRootNodeIndex(Animation* anim)
return rootNodeIndex;
}
-void AnimGraphExecutor::ExtractRootMotion(const Animation::NodeToChannel* mapping, int32 rootNodeIndex, Animation* anim, float pos, float prevPos, Transform& rootNode, RootMotionData& rootMotion)
-{
- const Transform& refPose = GetEmptyNodes()->Nodes[rootNodeIndex];
- const int32 nodeToChannel = mapping->At(rootNodeIndex);
- if (_rootMotionMode == RootMotionMode::Enable && nodeToChannel != -1)
- {
- // Get the root bone transformation
- Transform rootBefore = refPose;
- const NodeAnimationData& rootChannel = anim->Data.Channels[nodeToChannel];
- rootChannel.Evaluate(prevPos, &rootBefore, false);
-
- // Check if animation looped
- if (pos < prevPos)
- {
- const float length = anim->GetLength();
- const float endPos = length * static_cast(anim->Data.FramesPerSecond);
- const float timeToEnd = endPos - prevPos;
-
- Transform rootBegin = refPose;
- rootChannel.Evaluate(0, &rootBegin, false);
-
- Transform rootEnd = refPose;
- rootChannel.Evaluate(endPos, &rootEnd, false);
-
- //rootChannel.Evaluate(pos - timeToEnd, &rootNow, true);
-
- // Complex motion calculation to preserve the looped movement
- // (end - before + now - begin)
- // It sums the motion since the last update to anim end and since the start to now
- rootMotion.Translation = rootEnd.Translation - rootBefore.Translation + rootNode.Translation - rootBegin.Translation;
- rootMotion.Rotation = rootEnd.Orientation * rootBefore.Orientation.Conjugated() * (rootNode.Orientation * rootBegin.Orientation.Conjugated());
- //rootMotion.Rotation = Quaternion::Identity;
- }
- else
- {
- // Simple motion delta
- // (now - before)
- rootMotion.Translation = rootNode.Translation - rootBefore.Translation;
- rootMotion.Rotation = rootBefore.Orientation.Conjugated() * rootNode.Orientation;
- }
- }
-
- // Remove root node motion after extraction
- rootNode = refPose;
-}
-
void AnimGraphExecutor::ProcessAnimEvents(AnimGraphNode* node, bool loop, float length, float animPos, float animPrevPos, Animation* anim, float speed)
{
if (anim->Events.Count() == 0)
@@ -185,13 +169,15 @@ float GetAnimPos(float& timePos, float startTimePos, bool loop, float length)
{
// Animation looped
result = Math::Mod(result, length);
+
+ // Remove start time offset to properly loop from animation start during the next frame
+ timePos = result - startTimePos;
}
else
{
// Animation ended
- result = length;
+ timePos = result = length;
}
- timePos = result;
}
return result;
}
@@ -257,19 +243,33 @@ void AnimGraphExecutor::ProcessAnimation(AnimGraphImpulse* nodes, AnimGraphNode*
}
}
+ // Get skeleton nodes mapping descriptor
+ const SkinnedModel::SkeletonMapping mapping = _graph.BaseModel->GetSkeletonMapping(anim);
+ if (mapping.NodesMapping.IsInvalid())
+ return;
+
// Evaluate nodes animations
- const auto mapping = anim->GetMapping(_graph.BaseModel);
const bool weighted = weight < 1.0f;
+ const bool retarget = mapping.SourceSkeleton && mapping.SourceSkeleton != mapping.TargetSkeleton;
const auto emptyNodes = GetEmptyNodes();
+ SkinnedModel::SkeletonMapping sourceMapping;
+ if (retarget)
+ sourceMapping = _graph.BaseModel->GetSkeletonMapping(mapping.SourceSkeleton);
for (int32 i = 0; i < nodes->Nodes.Count(); i++)
{
- const int32 nodeToChannel = mapping->At(i);
+ const int32 nodeToChannel = mapping.NodesMapping[i];
Transform& dstNode = nodes->Nodes[i];
Transform srcNode = emptyNodes->Nodes[i];
if (nodeToChannel != -1)
{
// Calculate the animated node transformation
anim->Data.Channels[nodeToChannel].Evaluate(animPos, &srcNode, false);
+
+ // Optionally retarget animation into the skeleton used by the Anim Graph
+ if (retarget)
+ {
+ RetargetSkeletonNode(mapping.SourceSkeleton->Skeleton, mapping.TargetSkeleton->Skeleton, sourceMapping, srcNode, i);
+ }
}
// Blend node
@@ -302,26 +302,76 @@ void AnimGraphExecutor::ProcessAnimation(AnimGraphImpulse* nodes, AnimGraphNode*
{
// Calculate the root motion node transformation
const int32 rootNodeIndex = GetRootNodeIndex(anim);
- Transform rootNode = emptyNodes->Nodes[rootNodeIndex];
- RootMotionData& dstNode = nodes->RootMotion;
- RootMotionData srcNode(rootNode);
- ExtractRootMotion(mapping, rootNodeIndex, anim, animPos, animPrevPos, rootNode, srcNode);
+ const Transform& refPose = emptyNodes->Nodes[rootNodeIndex];
+ Transform& rootNode = nodes->Nodes[rootNodeIndex];
+ Transform& dstNode = nodes->RootMotion;
+ Transform srcNode = Transform::Identity;
+ const int32 nodeToChannel = mapping.NodesMapping[rootNodeIndex];
+ if (_rootMotionMode == RootMotionMode::Enable && nodeToChannel != -1)
+ {
+ // Get the root bone transformation
+ Transform rootBefore = refPose;
+ const NodeAnimationData& rootChannel = anim->Data.Channels[nodeToChannel];
+ rootChannel.Evaluate(animPrevPos, &rootBefore, false);
+
+ // Check if animation looped
+ if (animPos < animPrevPos)
+ {
+ const float endPos = anim->GetLength() * static_cast(anim->Data.FramesPerSecond);
+ const float timeToEnd = endPos - animPrevPos;
+
+ Transform rootBegin = refPose;
+ rootChannel.Evaluate(0, &rootBegin, false);
+
+ Transform rootEnd = refPose;
+ rootChannel.Evaluate(endPos, &rootEnd, false);
+
+ //rootChannel.Evaluate(animPos - timeToEnd, &rootNow, true);
+
+ // Complex motion calculation to preserve the looped movement
+ // (end - before + now - begin)
+ // It sums the motion since the last update to anim end and since the start to now
+ srcNode.Translation = rootEnd.Translation - rootBefore.Translation + rootNode.Translation - rootBegin.Translation;
+ srcNode.Orientation = rootEnd.Orientation * rootBefore.Orientation.Conjugated() * (rootNode.Orientation * rootBegin.Orientation.Conjugated());
+ //srcNode.Orientation = Quaternion::Identity;
+ }
+ else
+ {
+ // Simple motion delta
+ // (now - before)
+ srcNode.Translation = rootNode.Translation - rootBefore.Translation;
+ srcNode.Orientation = rootBefore.Orientation.Conjugated() * rootNode.Orientation;
+ }
+
+ // Convert root motion from local-space to the actor-space (eg. if root node is not actually a root and its parents have rotation/scale)
+ auto& skeleton = _graph.BaseModel->Skeleton;
+ int32 parentIndex = skeleton.Nodes[rootNodeIndex].ParentIndex;
+ while (parentIndex != -1)
+ {
+ const Transform& parentNode = nodes->Nodes[parentIndex];
+ srcNode.Translation = parentNode.LocalToWorld(srcNode.Translation);
+ parentIndex = skeleton.Nodes[parentIndex].ParentIndex;
+ }
+ }
+
+ // Remove root node motion after extraction
+ rootNode = refPose;
// Blend root motion
if (mode == ProcessAnimationMode::BlendAdditive)
{
dstNode.Translation += srcNode.Translation * weight;
- BlendAdditiveWeightedRotation(dstNode.Rotation, srcNode.Rotation, weight);
+ BlendAdditiveWeightedRotation(dstNode.Orientation, srcNode.Orientation, weight);
}
else if (mode == ProcessAnimationMode::Add)
{
dstNode.Translation += srcNode.Translation * weight;
- dstNode.Rotation += srcNode.Rotation * weight;
+ dstNode.Orientation += srcNode.Orientation * weight;
}
else if (weighted)
{
dstNode.Translation = srcNode.Translation * weight;
- dstNode.Rotation = srcNode.Rotation * weight;
+ dstNode.Orientation = srcNode.Orientation * weight;
}
else
{
@@ -349,6 +399,7 @@ Variant AnimGraphExecutor::SampleAnimation(AnimGraphNode* node, bool loop, float
nodes->Position = pos;
nodes->Length = length;
ProcessAnimation(nodes, node, loop, length, pos, prevPos, anim, speed);
+ NormalizeRotations(nodes, _rootMotionMode);
return nodes;
}
@@ -370,16 +421,7 @@ Variant AnimGraphExecutor::SampleAnimationsWithBlend(AnimGraphNode* node, bool l
nodes->Length = length;
ProcessAnimation(nodes, node, loop, length, pos, prevPos, animA, speedA, 1.0f - alpha, ProcessAnimationMode::Override);
ProcessAnimation(nodes, node, loop, length, pos, prevPos, animB, speedB, alpha, ProcessAnimationMode::BlendAdditive);
-
- // Normalize rotations
- for (int32 i = 0; i < nodes->Nodes.Count(); i++)
- {
- nodes->Nodes[i].Orientation.Normalize();
- }
- if (_rootMotionMode != RootMotionMode::NoExtraction)
- {
- nodes->RootMotion.Rotation.Normalize();
- }
+ NormalizeRotations(nodes, _rootMotionMode);
return nodes;
}
@@ -404,16 +446,7 @@ Variant AnimGraphExecutor::SampleAnimationsWithBlend(AnimGraphNode* node, bool l
ProcessAnimation(nodes, node, loop, length, pos, prevPos, animA, speedA, alphaA, ProcessAnimationMode::Override);
ProcessAnimation(nodes, node, loop, length, pos, prevPos, animB, speedB, alphaB, ProcessAnimationMode::BlendAdditive);
ProcessAnimation(nodes, node, loop, length, pos, prevPos, animC, speedC, alphaC, ProcessAnimationMode::BlendAdditive);
-
- // Normalize rotations
- for (int32 i = 0; i < nodes->Nodes.Count(); i++)
- {
- nodes->Nodes[i].Orientation.Normalize();
- }
- if (_rootMotionMode != RootMotionMode::NoExtraction)
- {
- nodes->RootMotion.Rotation.Normalize();
- }
+ NormalizeRotations(nodes, _rootMotionMode);
return nodes;
}
@@ -437,7 +470,7 @@ Variant AnimGraphExecutor::Blend(AnimGraphNode* node, const Value& poseA, const
{
Transform::Lerp(nodesA->Nodes[i], nodesB->Nodes[i], alpha, nodes->Nodes[i]);
}
- RootMotionData::Lerp(nodesA->RootMotion, nodesB->RootMotion, alpha, nodes->RootMotion);
+ Transform::Lerp(nodesA->RootMotion, nodesB->RootMotion, alpha, nodes->RootMotion);
nodes->Position = Math::Lerp(nodesA->Position, nodesB->Position, alpha);
nodes->Length = Math::Lerp(nodesA->Length, nodesB->Length, alpha);
@@ -463,6 +496,79 @@ Variant AnimGraphExecutor::SampleState(AnimGraphNode* state)
return result;
}
+void AnimGraphExecutor::UpdateStateTransitions(AnimGraphContext& context, const AnimGraphNode::StateMachineData& stateMachineData, AnimGraphInstanceData::StateMachineBucket& stateMachineBucket, const AnimGraphNode::StateBaseData& stateData)
+{
+ int32 transitionIndex = 0;
+ while (transitionIndex < ANIM_GRAPH_MAX_STATE_TRANSITIONS && stateData.Transitions[transitionIndex] != AnimGraphNode::StateData::InvalidTransitionIndex)
+ {
+ const uint16 idx = stateData.Transitions[transitionIndex];
+ ASSERT(idx < stateMachineData.Graph->StateTransitions.Count());
+ auto& transition = stateMachineData.Graph->StateTransitions[idx];
+ if (transition.Destination == stateMachineBucket.CurrentState)
+ {
+ // Ignore transition to the current state
+ transitionIndex++;
+ continue;
+ }
+ const bool useDefaultRule = EnumHasAnyFlags(transition.Flags, AnimGraphStateTransition::FlagTypes::UseDefaultRule);
+ if (transition.RuleGraph && !useDefaultRule)
+ {
+ // Execute transition rule
+ auto rootNode = transition.RuleGraph->GetRootNode();
+ ASSERT(rootNode);
+ if (!(bool)eatBox((Node*)rootNode, &rootNode->Boxes[0]))
+ {
+ transitionIndex++;
+ continue;
+ }
+ }
+
+ // Evaluate source state transition data (position, length, etc.)
+ const Value sourceStatePtr = SampleState(stateMachineBucket.CurrentState);
+ auto& transitionData = context.TransitionData; // Note: this could support nested transitions but who uses state machine inside transition rule?
+ if (ANIM_GRAPH_IS_VALID_PTR(sourceStatePtr))
+ {
+ // Use source state as data provider
+ const auto sourceState = (AnimGraphImpulse*)sourceStatePtr.AsPointer;
+ auto sourceLength = Math::Max(sourceState->Length, 0.0f);
+ transitionData.Position = Math::Clamp(sourceState->Position, 0.0f, sourceLength);
+ transitionData.Length = sourceLength;
+ }
+ else
+ {
+ // Reset
+ transitionData.Position = 0;
+ transitionData.Length = ZeroTolerance;
+ }
+
+ // Check if can trigger the transition
+ bool canEnter = false;
+ if (useDefaultRule)
+ {
+ // Start transition when the current state animation is about to end (split blend duration evenly into two states)
+ const auto transitionDurationHalf = transition.BlendDuration * 0.5f + ZeroTolerance;
+ const auto endPos = transitionData.Length - transitionDurationHalf;
+ canEnter = transitionData.Position >= endPos;
+ }
+ else if (transition.RuleGraph)
+ canEnter = true;
+ if (canEnter)
+ {
+ // Start transition
+ stateMachineBucket.ActiveTransition = &transition;
+ stateMachineBucket.TransitionPosition = 0.0f;
+ break;
+ }
+
+ // Skip after Solo transition
+ // TODO: don't load transitions after first enabled Solo transition and remove this check here
+ if (EnumHasAnyFlags(transition.Flags, AnimGraphStateTransition::FlagTypes::Solo))
+ break;
+
+ transitionIndex++;
+ }
+}
+
void ComputeMultiBlendLength(float& length, AnimGraphNode* node)
{
ANIM_GRAPH_PROFILE_EVENT("Setup Mutli Blend Length");
@@ -643,9 +749,6 @@ void AnimGraphExecutor::ProcessGroupAnimation(Box* boxBase, Node* nodeBase, Valu
{
const auto anim = node->Assets[0].As();
auto& bucket = context.Data->State[node->BucketIndex].Animation;
- const float speed = (float)tryGetValue(node->GetBox(5), node->Values[1]);
- const bool loop = (bool)tryGetValue(node->GetBox(6), node->Values[2]);
- const float startTimePos = (float)tryGetValue(node->GetBox(7), node->Values[3]);
switch (box->ID)
{
@@ -653,6 +756,9 @@ void AnimGraphExecutor::ProcessGroupAnimation(Box* boxBase, Node* nodeBase, Valu
case 0:
{
ANIM_GRAPH_PROFILE_EVENT("Animation");
+ const float speed = (float)tryGetValue(node->GetBox(5), node->Values[1]);
+ const bool loop = (bool)tryGetValue(node->GetBox(6), node->Values[2]);
+ const float startTimePos = (float)tryGetValue(node->GetBox(7), node->Values[3]);
const float length = anim ? anim->GetLength() : 0.0f;
// Calculate new time position
@@ -672,14 +778,20 @@ void AnimGraphExecutor::ProcessGroupAnimation(Box* boxBase, Node* nodeBase, Valu
}
// Normalized Time
case 1:
+ {
+ const float startTimePos = (float)tryGetValue(node->GetBox(7), node->Values[3]);
value = startTimePos + bucket.TimePosition;
if (anim && anim->IsLoaded())
value.AsFloat /= anim->GetLength();
break;
+ }
// Time
case 2:
+ {
+ const float startTimePos = (float)tryGetValue(node->GetBox(7), node->Values[3]);
value = startTimePos + bucket.TimePosition;
break;
+ }
// Length
case 3:
value = anim ? anim->GetLength() : 0.0f;
@@ -912,7 +1024,7 @@ void AnimGraphExecutor::ProcessGroupAnimation(Box* boxBase, Node* nodeBase, Valu
{
Transform::Lerp(nodesA->Nodes[i], nodesB->Nodes[i], alpha, nodes->Nodes[i]);
}
- RootMotionData::Lerp(nodesA->RootMotion, nodesB->RootMotion, alpha, nodes->RootMotion);
+ Transform::Lerp(nodesA->RootMotion, nodesB->RootMotion, alpha, nodes->RootMotion);
value = nodes;
}
@@ -958,7 +1070,7 @@ void AnimGraphExecutor::ProcessGroupAnimation(Box* boxBase, Node* nodeBase, Valu
t.Orientation.Normalize();
Transform::Lerp(tA, t, alpha, nodes->Nodes[i]);
}
- RootMotionData::Lerp(nodesA->RootMotion, nodesA->RootMotion + nodesB->RootMotion, alpha, nodes->RootMotion);
+ Transform::Lerp(nodesA->RootMotion, nodesA->RootMotion + nodesB->RootMotion, alpha, nodes->RootMotion);
value = nodes;
}
}
@@ -1006,7 +1118,7 @@ void AnimGraphExecutor::ProcessGroupAnimation(Box* boxBase, Node* nodeBase, Valu
nodes->Nodes[nodeIndex] = tA;
}
}
- RootMotionData::Lerp(nodesA->RootMotion, nodesB->RootMotion, alpha, nodes->RootMotion);
+ Transform::Lerp(nodesA->RootMotion, nodesB->RootMotion, alpha, nodes->RootMotion);
value = nodes;
}
@@ -1391,7 +1503,7 @@ void AnimGraphExecutor::ProcessGroupAnimation(Box* boxBase, Node* nodeBase, Valu
value = poseData->RootMotion.Translation;
break;
case 1:
- value = poseData->RootMotion.Rotation;
+ value = poseData->RootMotion.Orientation;
break;
}
}
@@ -1423,7 +1535,7 @@ void AnimGraphExecutor::ProcessGroupAnimation(Box* boxBase, Node* nodeBase, Valu
auto nodes = node->GetNodes(this);
nodes->Nodes = poseData->Nodes;
nodes->RootMotion.Translation = (Vector3)tryGetValue(node->GetBox(2), Value::Zero);
- nodes->RootMotion.Rotation = (Quaternion)tryGetValue(node->GetBox(3), Value::Zero);
+ nodes->RootMotion.Orientation = (Quaternion)tryGetValue(node->GetBox(3), Value::Zero);
value = nodes;
break;
}
@@ -1441,19 +1553,18 @@ void AnimGraphExecutor::ProcessGroupAnimation(Box* boxBase, Node* nodeBase, Valu
auto nodes = node->GetNodes(this);
nodes->Nodes = poseData->Nodes;
nodes->RootMotion.Translation = poseData->RootMotion.Translation + (Vector3)tryGetValue(node->GetBox(2), Value::Zero);
- nodes->RootMotion.Rotation = poseData->RootMotion.Rotation * (Quaternion)tryGetValue(node->GetBox(3), Value::Zero);
+ nodes->RootMotion.Orientation = poseData->RootMotion.Orientation * (Quaternion)tryGetValue(node->GetBox(3), Value::Zero);
value = nodes;
break;
}
// State Machine
case 18:
{
+ ANIM_GRAPH_PROFILE_EVENT("State Machine");
const int32 maxTransitionsPerUpdate = node->Values[2].AsInt;
const bool reinitializeOnBecomingRelevant = node->Values[3].AsBool;
const bool skipFirstUpdateTransition = node->Values[4].AsBool;
- ANIM_GRAPH_PROFILE_EVENT("State Machine");
-
// Prepare
auto& bucket = context.Data->State[node->BucketIndex].StateMachine;
auto& data = node->Data.StateMachine;
@@ -1485,6 +1596,11 @@ void AnimGraphExecutor::ProcessGroupAnimation(Box* boxBase, Node* nodeBase, Valu
// Reset all state buckets pof the graphs and nodes included inside the state machine
ResetBuckets(context, data.Graph);
}
+#define END_TRANSITION() \
+ ResetBuckets(context, bucket.CurrentState->Data.State.Graph); \
+ bucket.CurrentState = bucket.ActiveTransition->Destination; \
+ bucket.ActiveTransition = nullptr; \
+ bucket.TransitionPosition = 0.0f
// Update the active transition
if (bucket.ActiveTransition)
@@ -1494,95 +1610,66 @@ void AnimGraphExecutor::ProcessGroupAnimation(Box* boxBase, Node* nodeBase, Valu
// Check for transition end
if (bucket.TransitionPosition >= bucket.ActiveTransition->BlendDuration)
{
- // End transition
- ResetBuckets(context, bucket.CurrentState->Data.State.Graph);
- bucket.CurrentState = bucket.ActiveTransition->Destination;
- bucket.ActiveTransition = nullptr;
- bucket.TransitionPosition = 0.0f;
+ END_TRANSITION();
+ }
+ // Check for transition interruption
+ else if (EnumHasAnyFlags(bucket.ActiveTransition->Flags, AnimGraphStateTransition::FlagTypes::InterruptionRuleRechecking))
+ {
+ const bool useDefaultRule = EnumHasAnyFlags(bucket.ActiveTransition->Flags, AnimGraphStateTransition::FlagTypes::UseDefaultRule);
+ if (bucket.ActiveTransition->RuleGraph && !useDefaultRule)
+ {
+ // Execute transition rule
+ auto rootNode = bucket.ActiveTransition->RuleGraph->GetRootNode();
+ if (!(bool)eatBox((Node*)rootNode, &rootNode->Boxes[0]))
+ {
+ bool cancelTransition = false;
+ if (EnumHasAnyFlags(bucket.ActiveTransition->Flags, AnimGraphStateTransition::FlagTypes::InterruptionInstant))
+ {
+ cancelTransition = true;
+ }
+ else
+ {
+ // Blend back to the source state (remove currently applied delta and rewind transition)
+ bucket.TransitionPosition -= context.DeltaTime;
+ bucket.TransitionPosition -= context.DeltaTime;
+ if (bucket.TransitionPosition <= ZeroTolerance)
+ {
+ cancelTransition = true;
+ }
+ }
+ if (cancelTransition)
+ {
+ // Go back to the source state
+ ResetBuckets(context, bucket.CurrentState->Data.State.Graph);
+ bucket.ActiveTransition = nullptr;
+ bucket.TransitionPosition = 0.0f;
+ }
+ }
+ }
}
}
- ASSERT(bucket.CurrentState && bucket.CurrentState->GroupID == 9 && bucket.CurrentState->TypeID == 20);
+ ASSERT(bucket.CurrentState && bucket.CurrentState->Type == GRAPH_NODE_MAKE_TYPE(9, 20));
// Update transitions
- // Note: this logic assumes that all transitions are sorted by Order property and Enabled
+ // Note: this logic assumes that all transitions are sorted by Order property and Enabled (by Editor when saving Anim Graph asset)
while (!bucket.ActiveTransition && transitionsLeft-- > 0)
{
- // Check if can change the current state
- const auto& stateData = bucket.CurrentState->Data.State;
- int32 transitionIndex = 0;
- while (stateData.Transitions[transitionIndex] != AnimGraphNode::StateData::InvalidTransitionIndex
- && transitionIndex < ANIM_GRAPH_MAX_STATE_TRANSITIONS)
+ // State transitions
+ UpdateStateTransitions(context, data, bucket, bucket.CurrentState->Data.State);
+
+ // Any state transitions
+ // TODO: cache Any state nodes inside State Machine to optimize the loop below
+ for (const AnimGraphNode& anyStateNode : data.Graph->Nodes)
{
- const uint16 idx = stateData.Transitions[transitionIndex];
- ASSERT(idx < data.Graph->StateTransitions.Count());
- auto& transition = data.Graph->StateTransitions[idx];
- const bool useDefaultRule = EnumHasAnyFlags(transition.Flags, AnimGraphStateTransition::FlagTypes::UseDefaultRule);
- if (transition.RuleGraph && !useDefaultRule)
- {
- // Execute transition rule
- auto rootNode = transition.RuleGraph->GetRootNode();
- ASSERT(rootNode);
- if (!(bool)eatBox((Node*)rootNode, &rootNode->Boxes[0]))
- {
- transitionIndex++;
- continue;
- }
- }
-
- // Evaluate source state transition data (position, length, etc.)
- const Value sourceStatePtr = SampleState(bucket.CurrentState);
- auto& transitionData = context.TransitionData; // Note: this could support nested transitions but who uses state machine inside transition rule?
- if (ANIM_GRAPH_IS_VALID_PTR(sourceStatePtr))
- {
- // Use source state as data provider
- const auto sourceState = (AnimGraphImpulse*)sourceStatePtr.AsPointer;
- auto sourceLength = Math::Max(sourceState->Length, 0.0f);
- transitionData.Position = Math::Clamp(sourceState->Position, 0.0f, sourceLength);
- transitionData.Length = sourceLength;
- }
- else
- {
- // Reset
- transitionData.Position = 0;
- transitionData.Length = ZeroTolerance;
- }
-
- // Check if can trigger the transition
- bool canEnter = false;
- if (useDefaultRule)
- {
- // Start transition when the current state animation is about to end (split blend duration evenly into two states)
- const auto transitionDurationHalf = transition.BlendDuration * 0.5f + ZeroTolerance;
- const auto endPos = transitionData.Length - transitionDurationHalf;
- canEnter = transitionData.Position >= endPos;
- }
- else if (transition.RuleGraph)
- canEnter = true;
- if (canEnter)
- {
- // Start transition
- bucket.ActiveTransition = &transition;
- bucket.TransitionPosition = 0.0f;
- break;
- }
-
- // Skip after Solo transition
- // TODO: don't load transitions after first enabled Solo transition and remove this check here
- if (EnumHasAnyFlags(transition.Flags, AnimGraphStateTransition::FlagTypes::Solo))
- break;
-
- transitionIndex++;
+ if (anyStateNode.Type == GRAPH_NODE_MAKE_TYPE(9, 34))
+ UpdateStateTransitions(context, data, bucket, anyStateNode.Data.AnyState);
}
// Check for instant transitions
if (bucket.ActiveTransition && bucket.ActiveTransition->BlendDuration <= ZeroTolerance)
{
- // End transition
- ResetBuckets(context, bucket.CurrentState->Data.State.Graph);
- bucket.CurrentState = bucket.ActiveTransition->Destination;
- bucket.ActiveTransition = nullptr;
- bucket.TransitionPosition = 0.0f;
+ END_TRANSITION();
}
}
@@ -1603,18 +1690,16 @@ void AnimGraphExecutor::ProcessGroupAnimation(Box* boxBase, Node* nodeBase, Valu
// Update bucket
bucket.LastUpdateFrame = context.CurrentFrameIndex;
+#undef END_TRANSITION
break;
}
// Entry
case 19:
- {
- // Not used
- CRASH;
- break;
- }
// State
case 20:
+ // Any State
+ case 34:
{
// Not used
CRASH;
@@ -1622,8 +1707,6 @@ void AnimGraphExecutor::ProcessGroupAnimation(Box* boxBase, Node* nodeBase, Valu
}
// State Output
case 21:
- value = box->HasConnection() ? eatBox(nodeBase, box->FirstConnection()) : Value::Null;
- break;
// Rule Output
case 22:
value = box->HasConnection() ? eatBox(nodeBase, box->FirstConnection()) : Value::Null;
diff --git a/Source/Engine/Animations/SceneAnimations/SceneAnimationPlayer.cpp b/Source/Engine/Animations/SceneAnimations/SceneAnimationPlayer.cpp
index dda6e6fcb..fa7999ce0 100644
--- a/Source/Engine/Animations/SceneAnimations/SceneAnimationPlayer.cpp
+++ b/Source/Engine/Animations/SceneAnimations/SceneAnimationPlayer.cpp
@@ -13,12 +13,12 @@
#include "Engine/Renderer/RenderList.h"
#include "Engine/Scripting/Scripting.h"
#include "Engine/Scripting/Script.h"
-#include "Engine/Scripting/MException.h"
+#include "Engine/Scripting/ManagedCLR/MException.h"
#include "Engine/Scripting/ManagedCLR/MProperty.h"
#include "Engine/Scripting/ManagedCLR/MUtils.h"
-#include "Engine/Scripting/ManagedCLR/MType.h"
#include "Engine/Scripting/ManagedCLR/MField.h"
#include "Engine/Scripting/ManagedCLR/MClass.h"
+#include "Engine/Scripting/ManagedCLR/MMethod.h"
// This could be Update, LateUpdate or FixedUpdate
#define UPDATE_POINT Update
@@ -260,7 +260,7 @@ void SceneAnimationPlayer::MapTrack(const StringView& from, const Guid& to)
void SceneAnimationPlayer::Restore(SceneAnimation* anim, int32 stateIndexOffset)
{
-#if USE_MONO
+#if USE_CSHARP
// Restore all tracks
for (int32 j = 0; j < anim->Tracks.Count(); j++)
{
@@ -364,7 +364,7 @@ void SceneAnimationPlayer::Restore(SceneAnimation* anim, int32 stateIndexOffset)
bool SceneAnimationPlayer::TickPropertyTrack(int32 trackIndex, int32 stateIndexOffset, SceneAnimation* anim, float time, const SceneAnimation::Track& track, TrackInstance& state, void* target)
{
-#if USE_MONO
+#if USE_CSHARP
switch (track.Type)
{
case SceneAnimation::Track::Types::KeyframesProperty:
@@ -511,7 +511,7 @@ bool SceneAnimationPlayer::TickPropertyTrack(int32 trackIndex, int32 stateIndexO
// Return the value
StringView str(keyframesValues[leftKey], keyframesLengths[leftKey]);
- *(MonoString**)target = MUtils::ToString(str);
+ *(MString**)target = MUtils::ToString(str);
break;
}
case SceneAnimation::Track::Types::StructProperty:
@@ -528,10 +528,10 @@ bool SceneAnimationPlayer::TickPropertyTrack(int32 trackIndex, int32 stateIndexO
// Cache field
if (!childTrackState.Field)
{
- MType type = state.Property ? state.Property->GetType() : (state.Field ? state.Field->GetType() : MType());
+ MType* type = state.Property ? state.Property->GetType() : (state.Field ? state.Field->GetType() : nullptr);
if (!type)
continue;
- MClass* mclass = Scripting::FindClass(mono_type_get_class(type.GetNative()));
+ MClass* mclass = MCore::Type::GetClass(type);
childTrackState.Field = mclass->GetField(childTrackRuntimeData->PropertyName);
if (!childTrackState.Field)
continue;
@@ -556,7 +556,7 @@ bool SceneAnimationPlayer::TickPropertyTrack(int32 trackIndex, int32 stateIndexO
void SceneAnimationPlayer::Tick(SceneAnimation* anim, float time, float dt, int32 stateIndexOffset, CallStack& callStack)
{
-#if USE_MONO
+#if USE_CSHARP
const float fps = anim->FramesPerSecond;
#if !BUILD_RELEASE || USE_EDITOR
callStack.Add(anim);
@@ -873,7 +873,7 @@ void SceneAnimationPlayer::Tick(SceneAnimation* anim, float time, float dt, int3
// Cache property or field
if (!state.Property && !state.Field)
{
- MClass* mclass = Scripting::FindClass(mono_object_get_class(instance));
+ MClass* mclass = MCore::Object::GetClass(instance);
state.Property = mclass->GetProperty(runtimeData->PropertyName);
if (!state.Property)
{
@@ -886,9 +886,8 @@ void SceneAnimationPlayer::Tick(SceneAnimation* anim, float time, float dt, int3
}
// Get stack memory for data value
- MType valueType = state.Property ? state.Property->GetType() : state.Field->GetType();
- int32 valueAlignment;
- int32 valueSize = mono_type_stack_size(valueType.GetNative(), &valueAlignment);
+ MType* valueType = state.Property ? state.Property->GetType() : state.Field->GetType();
+ int32 valueSize = MCore::Type::GetSize(valueType);
_tracksDataStack.AddDefault(valueSize);
void* value = &_tracksDataStack[_tracksDataStack.Count() - valueSize];
@@ -906,10 +905,10 @@ void SceneAnimationPlayer::Tick(SceneAnimation* anim, float time, float dt, int3
MException ex(exception);
ex.Log(LogType::Error, TEXT("Property"));
}
- else if (!valueType.IsPointer())
+ else if (!MCore::Type::IsPointer(valueType))
{
if (boxed)
- Platform::MemoryCopy(value, mono_object_unbox(boxed), valueSize);
+ Platform::MemoryCopy(value, MCore::Object::Unbox(boxed), valueSize);
else
Platform::MemoryClear(value, valueSize);
}
@@ -932,7 +931,7 @@ void SceneAnimationPlayer::Tick(SceneAnimation* anim, float time, float dt, int3
case SceneAnimation::Track::Types::StringProperty:
{
StringView str;
- MUtils::ToString(*(MonoString**)value, str);
+ MUtils::ToString(*(MString**)value, str);
_restoreData.Add((byte*)str.Get(), str.Length());
_restoreData.Add('\0');
break;
@@ -957,7 +956,7 @@ void SceneAnimationPlayer::Tick(SceneAnimation* anim, float time, float dt, int3
if (TickPropertyTrack(j, stateIndexOffset, anim, time, track, state, value))
{
// Set the value
- if (valueType.IsPointer())
+ if (MCore::Type::IsPointer(valueType))
value = (void*)*(intptr*)value;
if (state.Property)
{
@@ -1027,7 +1026,7 @@ void SceneAnimationPlayer::Tick(SceneAnimation* anim, float time, float dt, int3
// Cache method
if (!state.Method)
{
- state.Method = mono_class_get_method_from_name(mono_object_get_class(instance), runtimeData->EventName, runtimeData->EventParamsCount);
+ state.Method = MCore::Object::GetClass(instance)->FindMethod(runtimeData->EventName, runtimeData->EventParamsCount);
if (!state.Method)
break;
}
@@ -1035,7 +1034,7 @@ void SceneAnimationPlayer::Tick(SceneAnimation* anim, float time, float dt, int3
// Invoke the method
Variant result;
MObject* exception = nullptr;
- mono_runtime_invoke((MonoMethod*)state.Method, instance, paramsData, &exception);
+ state.Method->Invoke(instance, paramsData, &exception);
if (exception)
{
MException ex(exception);
diff --git a/Source/Engine/Animations/SceneAnimations/SceneAnimationPlayer.h b/Source/Engine/Animations/SceneAnimations/SceneAnimationPlayer.h
index 46ebc918d..55cb35bb2 100644
--- a/Source/Engine/Animations/SceneAnimations/SceneAnimationPlayer.h
+++ b/Source/Engine/Animations/SceneAnimations/SceneAnimationPlayer.h
@@ -46,7 +46,7 @@ private:
MObject* ManagedObject = nullptr;
MProperty* Property = nullptr;
MField* Field = nullptr;
- void* Method = nullptr;
+ MMethod* Method = nullptr;
int32 RestoreStateIndex = -1;
bool Warn = true;
diff --git a/Source/Engine/Audio/Audio.Build.cs b/Source/Engine/Audio/Audio.Build.cs
index 03e7ba2aa..c3296c6f9 100644
--- a/Source/Engine/Audio/Audio.Build.cs
+++ b/Source/Engine/Audio/Audio.Build.cs
@@ -57,6 +57,9 @@ public class Audio : EngineModule
case TargetPlatform.Mac:
useOpenAL = true;
break;
+ case TargetPlatform.iOS:
+ useOpenAL = true;
+ break;
default: throw new InvalidPlatformException(options.Platform.Target);
}
@@ -91,6 +94,11 @@ public class Audio : EngineModule
options.Libraries.Add("AudioUnit.framework");
options.Libraries.Add("AudioToolbox.framework");
break;
+ case TargetPlatform.iOS:
+ options.OutputFiles.Add(Path.Combine(depsRoot, "libopenal.a"));
+ options.Libraries.Add("CoreAudio.framework");
+ options.Libraries.Add("AudioToolbox.framework");
+ break;
default: throw new InvalidPlatformException(options.Platform.Target);
}
}
diff --git a/Source/Engine/Content/Asset.cpp b/Source/Engine/Content/Asset.cpp
index 7eec9ac16..309a82e04 100644
--- a/Source/Engine/Content/Asset.cpp
+++ b/Source/Engine/Content/Asset.cpp
@@ -12,9 +12,7 @@
#include "Engine/Profiler/ProfilerCPU.h"
#include "Engine/Threading/MainThreadTask.h"
#include "Engine/Threading/ConcurrentTaskQueue.h"
-#if USE_MONO
-#include
-#endif
+#include "Engine/Scripting/ManagedCLR/MCore.h"
AssetReferenceBase::~AssetReferenceBase()
{
@@ -269,9 +267,7 @@ void Asset::OnManagedInstanceDeleted()
// Cleanup
if (_gcHandle)
{
-#if USE_MONO
- mono_gchandle_free(_gcHandle);
-#endif
+ MCore::GCHandle::Free(_gcHandle);
_gcHandle = 0;
}
@@ -399,6 +395,11 @@ bool Asset::WaitForLoaded(double timeoutInMilliseconds) const
return false;
}
+ // Check if loading failed
+ Platform::MemoryBarrier();
+ if (LastLoadFailed())
+ return true;
+
// Check if has missing loading task
Platform::MemoryBarrier();
const auto loadingTask = _loadingTask;
diff --git a/Source/Engine/Content/Assets/Animation.cpp b/Source/Engine/Content/Assets/Animation.cpp
index e9ce2a1cb..8aa635244 100644
--- a/Source/Engine/Content/Assets/Animation.cpp
+++ b/Source/Engine/Content/Assets/Animation.cpp
@@ -3,7 +3,6 @@
#include "Animation.h"
#include "SkinnedModel.h"
#include "Engine/Core/Log.h"
-#include "Engine/Profiler/ProfilerCPU.h"
#include "Engine/Content/Factories/BinaryAssetFactory.h"
#include "Engine/Animations/CurveSerialization.h"
#include "Engine/Animations/AnimEvent.h"
@@ -30,9 +29,7 @@ void Animation::OnScriptsReloadStart()
for (auto& e : Events)
{
for (auto& k : e.Second.GetKeyframes())
- {
Level::ScriptsReloadRegisterObject((ScriptingObject*&)k.Value.Instance);
- }
}
}
@@ -67,72 +64,11 @@ Animation::InfoData Animation::GetInfo() const
}
info.MemoryUsage += Events.Capacity() * sizeof(Pair>);
info.MemoryUsage += NestedAnims.Capacity() * sizeof(Pair);
- info.MemoryUsage += MappingCache.Capacity() * (sizeof(void*) + sizeof(NodeToChannel) + 1);
for (auto& e : Events)
info.MemoryUsage += e.Second.GetKeyframes().Capacity() * sizeof(StepCurve);
- for (auto& e : MappingCache)
- info.MemoryUsage += e.Value.Capacity() * sizeof(int32);
return info;
}
-void Animation::ClearCache()
-{
- ScopeLock lock(Locker);
-
- // Unlink events
- for (auto i = MappingCache.Begin(); i.IsNotEnd(); ++i)
- {
- i->Key->OnUnloaded.Unbind(this);
- i->Key->OnReloading.Unbind(this);
- }
-
- // Free memory
- MappingCache.Clear();
- MappingCache.SetCapacity(0);
-}
-
-const Animation::NodeToChannel* Animation::GetMapping(SkinnedModel* obj)
-{
- ASSERT(obj && obj->IsLoaded() && IsLoaded());
-
- ScopeLock lock(Locker);
-
- // Try quick lookup
- NodeToChannel* result = MappingCache.TryGet(obj);
- if (result == nullptr)
- {
- PROFILE_CPU();
-
- // Add to cache
- NodeToChannel tmp;
- auto bucket = MappingCache.Add(obj, tmp);
- result = &bucket->Value;
- obj->OnUnloaded.Bind(this);
- obj->OnReloading.Bind(this);
-
- // Initialize the mapping
- const auto& skeleton = obj->Skeleton;
- const int32 nodesCount = skeleton.Nodes.Count();
- result->Resize(nodesCount, false);
- result->SetAll(-1);
- for (int32 i = 0; i < Data.Channels.Count(); i++)
- {
- auto& nodeAnim = Data.Channels[i];
-
- for (int32 j = 0; j < nodesCount; j++)
- {
- if (skeleton.Nodes[j].Name == nodeAnim.NodeName)
- {
- result->At(j) = i;
- break;
- }
- }
- }
- }
-
- return result;
-}
-
#if USE_EDITOR
void Animation::LoadTimeline(BytesContainer& result) const
@@ -552,23 +488,6 @@ bool Animation::Save(const StringView& path)
#endif
-void Animation::OnSkinnedModelUnloaded(Asset* obj)
-{
- ScopeLock lock(Locker);
-
- const auto key = static_cast(obj);
- auto i = MappingCache.Find(key);
- ASSERT(i != MappingCache.End());
-
- // Unlink event
- key->OnUnloaded.Unbind(this);
- key->OnReloading.Unbind(this);
-
- // Clear cache
- i->Value.Resize(0, false);
- MappingCache.Remove(i);
-}
-
uint64 Animation::GetMemoryUsage() const
{
Locker.Lock();
@@ -579,9 +498,6 @@ uint64 Animation::GetMemoryUsage() const
for (const auto& e : Events)
result += e.First.Length() * sizeof(Char) + e.Second.GetMemoryUsage();
result += NestedAnims.Capacity() * sizeof(Pair);
- result += MappingCache.Capacity() * sizeof(Pair>);
- for (const auto& e : MappingCache)
- result += e.Value.Capacity() * sizeof(int32);
Locker.Unlock();
return result;
}
@@ -738,7 +654,6 @@ void Animation::unload(bool isReloading)
Level::ScriptsReloadStart.Unbind(this);
}
#endif
- ClearCache();
Data.Dispose();
for (const auto& e : Events)
{
diff --git a/Source/Engine/Content/Assets/Animation.h b/Source/Engine/Content/Assets/Animation.h
index 6d89039fe..5a96e5bc0 100644
--- a/Source/Engine/Content/Assets/Animation.h
+++ b/Source/Engine/Content/Assets/Animation.h
@@ -98,17 +98,6 @@ public:
///
Array> NestedAnims;
- ///
- /// Contains the mapping for every skeleton node to the animation data channels.
- /// Can be used for a simple lookup or to check if a given node is animated (unused nodes are using -1 index).
- ///
- typedef Array NodeToChannel;
-
- ///
- /// The skeleton nodes to animation channel indices mapping cache. Use it as read-only. It's being maintained internally by the asset.
- ///
- Dictionary MappingCache;
-
public:
///
/// Gets the length of the animation (in seconds).
@@ -139,18 +128,6 @@ public:
///
API_PROPERTY() InfoData GetInfo() const;
- ///
- /// Clears the skeleton mapping cache.
- ///
- void ClearCache();
-
- ///
- /// Clears the skeleton mapping cache.
- ///
- /// The target skinned model to get mapping to its skeleton.
- /// The cached node-to-channel mapping for the fast animation sampling for the skinned model skeleton nodes.
- const NodeToChannel* GetMapping(SkinnedModel* obj);
-
#if USE_EDITOR
///
/// Gets the animation as serialized timeline data. Used to show it in Editor.
@@ -174,9 +151,6 @@ public:
bool Save(const StringView& path = StringView::Empty);
#endif
-private:
- void OnSkinnedModelUnloaded(Asset* obj);
-
public:
// [BinaryAsset]
uint64 GetMemoryUsage() const override;
diff --git a/Source/Engine/Content/Assets/SkinnedModel.cpp b/Source/Engine/Content/Assets/SkinnedModel.cpp
index 603f685d8..a2e687131 100644
--- a/Source/Engine/Content/Assets/SkinnedModel.cpp
+++ b/Source/Engine/Content/Assets/SkinnedModel.cpp
@@ -1,6 +1,7 @@
// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.
#include "SkinnedModel.h"
+#include "Animation.h"
#include "Engine/Core/Log.h"
#include "Engine/Engine/Engine.h"
#include "Engine/Serialization/MemoryReadStream.h"
@@ -11,10 +12,12 @@
#include "Engine/Graphics/RenderTask.h"
#include "Engine/Graphics/Models/ModelInstanceEntry.h"
#include "Engine/Graphics/Models/Config.h"
+#include "Engine/Content/Content.h"
#include "Engine/Content/WeakAssetReference.h"
#include "Engine/Content/Factories/BinaryAssetFactory.h"
#include "Engine/Content/Upgraders/SkinnedModelAssetUpgrader.h"
#include "Engine/Debug/Exceptions/ArgumentOutOfRangeException.h"
+#include "Engine/Profiler/ProfilerCPU.h"
#include "Engine/Renderer/DrawCall.h"
#define CHECK_INVALID_BUFFER(model, buffer) \
@@ -116,6 +119,7 @@ SkinnedModel::SkinnedModel(const SpawnParams& params, const AssetInfo* info)
SkinnedModel::~SkinnedModel()
{
ASSERT(_streamingTask == nullptr);
+ ASSERT(_skeletonMappingCache.Count() == 0);
}
bool SkinnedModel::HasAnyLODInitialized() const
@@ -152,23 +156,165 @@ void SkinnedModel::GetLODData(int32 lodIndex, BytesContainer& data) const
GetChunkData(chunkIndex, data);
}
+SkinnedModel::SkeletonMapping SkinnedModel::GetSkeletonMapping(Asset* source)
+{
+ SkeletonMapping mapping;
+ mapping.TargetSkeleton = this;
+ if (WaitForLoaded() || !source || source->WaitForLoaded())
+ return mapping;
+ ScopeLock lock(Locker);
+ SkeletonMappingData mappingData;
+ if (!_skeletonMappingCache.TryGet(source, mappingData))
+ {
+ PROFILE_CPU();
+
+ // Initialize the mapping
+ const int32 nodesCount = Skeleton.Nodes.Count();
+ mappingData.NodesMapping = Span((int32*)Allocator::Allocate(nodesCount * sizeof(int32)), nodesCount);
+ for (int32 i = 0; i < nodesCount; i++)
+ mappingData.NodesMapping[i] = -1;
+ SkeletonRetarget* retarget = nullptr;
+ const Guid sourceId = source->GetID();
+ for (auto& e : _skeletonRetargets)
+ {
+ if (e.SourceAsset == sourceId)
+ {
+ retarget = &e;
+ break;
+ }
+ }
+ if (const auto* sourceAnim = Cast(source))
+ {
+ const auto& channels = sourceAnim->Data.Channels;
+ if (retarget && retarget->SkeletonAsset)
+ {
+ // Map retarget skeleton nodes from animation channels
+ if (auto* skeleton = Content::Load(retarget->SkeletonAsset))
+ {
+ const SkeletonMapping skeletonMapping = GetSkeletonMapping(skeleton);
+ mappingData.SourceSkeleton = skeleton;
+ if (skeletonMapping.NodesMapping.Length() == nodesCount)
+ {
+ const auto& nodes = skeleton->Skeleton.Nodes;
+ for (int32 j = 0; j < nodesCount; j++)
+ {
+ if (skeletonMapping.NodesMapping[j] != -1)
+ {
+ const Char* nodeName = nodes[skeletonMapping.NodesMapping[j]].Name.GetText();
+ for (int32 i = 0; i < channels.Count(); i++)
+ {
+ if (StringUtils::CompareIgnoreCase(nodeName, channels[i].NodeName.GetText()) == 0)
+ {
+ mappingData.NodesMapping[j] = i;
+ break;
+ }
+ }
+ }
+ }
+ }
+ }
+ else
+ {
+ #if !BUILD_RELEASE
+ LOG(Error, "Missing asset {0} to use for skeleton mapping of {1}", retarget->SkeletonAsset, ToString());
+ #endif
+ return mapping;
+ }
+ }
+ else
+ {
+ // Map animation channels to the skeleton nodes (by name)
+ for (int32 i = 0; i < channels.Count(); i++)
+ {
+ const NodeAnimationData& nodeAnim = channels[i];
+ for (int32 j = 0; j < nodesCount; j++)
+ {
+ if (StringUtils::CompareIgnoreCase(Skeleton.Nodes[j].Name.GetText(), nodeAnim.NodeName.GetText()) == 0)
+ {
+ mappingData.NodesMapping[j] = i;
+ break;
+ }
+ }
+ }
+ }
+ }
+ else if (const auto* sourceModel = Cast(source))
+ {
+ if (retarget)
+ {
+ // Use nodes retargeting
+ for (const auto& e : retarget->NodesMapping)
+ {
+ const int32 dstIndex = Skeleton.FindNode(e.Key);
+ const int32 srcIndex = sourceModel->Skeleton.FindNode(e.Value);
+ if (dstIndex != -1 && srcIndex != -1)
+ {
+ mappingData.NodesMapping[dstIndex] = srcIndex;
+ }
+ }
+ }
+ else
+ {
+ // Map source skeleton nodes to the target skeleton nodes (by name)
+ const auto& nodes = sourceModel->Skeleton.Nodes;
+ for (int32 i = 0; i < nodes.Count(); i++)
+ {
+ const SkeletonNode& node = nodes[i];
+ for (int32 j = 0; j < nodesCount; j++)
+ {
+ if (StringUtils::CompareIgnoreCase(Skeleton.Nodes[j].Name.GetText(), node.Name.GetText()) == 0)
+ {
+ mappingData.NodesMapping[j] = i;
+ break;
+ }
+ }
+ }
+ }
+ }
+ else
+ {
+#if !BUILD_RELEASE
+ LOG(Error, "Invalid asset type {0} to use for skeleton mapping of {1}", source->GetTypeName(), ToString());
+#endif
+ }
+
+ // Add to cache
+ _skeletonMappingCache.Add(source, mappingData);
+ source->OnUnloaded.Bind(this);
+#if USE_EDITOR
+ source->OnReloading.Bind(this);
+#endif
+ }
+ mapping.SourceSkeleton = mappingData.SourceSkeleton;
+ mapping.NodesMapping = mappingData.NodesMapping;
+ return mapping;
+}
+
bool SkinnedModel::Intersects(const Ray& ray, const Matrix& world, Real& distance, Vector3& normal, SkinnedMesh** mesh, int32 lodIndex)
{
+ if (LODs.Count() == 0)
+ return false;
return LODs[lodIndex].Intersects(ray, world, distance, normal, mesh);
}
bool SkinnedModel::Intersects(const Ray& ray, const Transform& transform, Real& distance, Vector3& normal, SkinnedMesh** mesh, int32 lodIndex)
{
+ if (LODs.Count() == 0)
+ return false;
return LODs[lodIndex].Intersects(ray, transform, distance, normal, mesh);
}
BoundingBox SkinnedModel::GetBox(const Matrix& world, int32 lodIndex) const
{
+ if (LODs.Count() == 0)
+ return BoundingBox::Zero;
return LODs[lodIndex].GetBox(world);
}
BoundingBox SkinnedModel::GetBox(int32 lodIndex) const
{
+ if (LODs.Count() == 0)
+ return BoundingBox::Zero;
return LODs[lodIndex].GetBox();
}
@@ -305,10 +451,8 @@ bool SkinnedModel::SetupSkeleton(const Array& nodes)
ScopeLock lock(model->Locker);
- // Setup nodes
+ // Setup
model->Skeleton.Nodes = nodes;
-
- // Setup bones
model->Skeleton.Bones.Resize(nodes.Count());
for (int32 i = 0; i < nodes.Count(); i++)
{
@@ -317,6 +461,7 @@ bool SkinnedModel::SetupSkeleton(const Array& nodes)
model->Skeleton.Bones[i].LocalTransform = node.LocalTransform;
model->Skeleton.Bones[i].NodeIndex = i;
}
+ ClearSkeletonMapping();
// Calculate offset matrix (inverse bind pose transform) for every bone manually
for (int32 i = 0; i < model->Skeleton.Bones.Count(); i++)
@@ -351,11 +496,10 @@ bool SkinnedModel::SetupSkeleton(const Array& nodes, const ArrayLocker);
- // Setup nodes
+ // Setup
model->Skeleton.Nodes = nodes;
-
- // Setup bones
model->Skeleton.Bones = bones;
+ ClearSkeletonMapping();
// Calculate offset matrix (inverse bind pose transform) for every bone manually
if (autoCalculateOffsetMatrix)
@@ -412,6 +556,9 @@ bool SkinnedModel::Save(bool withMeshDataFromGpu, const StringView& path)
MemoryWriteStream headerStream(1024);
MemoryWriteStream* stream = &headerStream;
{
+ // Header Version
+ stream->WriteByte(1);
+
// Min Screen Size
stream->WriteFloat(MinScreenSize);
@@ -499,6 +646,17 @@ bool SkinnedModel::Save(bool withMeshDataFromGpu, const StringView& path)
stream->Write(bone.OffsetMatrix);
}
}
+
+ // Retargeting
+ {
+ stream->WriteInt32(_skeletonRetargets.Count());
+ for (const auto& retarget : _skeletonRetargets)
+ {
+ stream->Write(retarget.SourceAsset);
+ stream->Write(retarget.SkeletonAsset);
+ stream->Write(retarget.NodesMapping);
+ }
+ }
}
// Use a temporary chunks for data storage for virtual assets
@@ -552,8 +710,6 @@ bool SkinnedModel::Save(bool withMeshDataFromGpu, const StringView& path)
task->Start();
tasks.Add(task);
}
-
- // Wait for all
if (Task::WaitAll(tasks))
return true;
tasks.Clear();
@@ -562,12 +718,10 @@ bool SkinnedModel::Save(bool withMeshDataFromGpu, const StringView& path)
{
int32 dataSize = meshesCount * (2 * sizeof(uint32) + sizeof(bool));
for (int32 meshIndex = 0; meshIndex < meshesCount; meshIndex++)
- {
dataSize += meshesData[meshIndex].DataSize();
- }
-
- MemoryWriteStream meshesStream(dataSize);
+ MemoryWriteStream meshesStream(Math::RoundUpToPowerOf2(dataSize));
+ meshesStream.WriteByte(1);
for (int32 meshIndex = 0; meshIndex < meshesCount; meshIndex++)
{
const auto& mesh = lod.Meshes[meshIndex];
@@ -597,10 +751,10 @@ bool SkinnedModel::Save(bool withMeshDataFromGpu, const StringView& path)
return true;
}
+ // #MODEL_DATA_FORMAT_USAGE
meshesStream.WriteUint32(vertices);
meshesStream.WriteUint32(triangles);
meshesStream.WriteUint16(mesh.BlendShapes.Count());
-
for (const auto& blendShape : mesh.BlendShapes)
{
meshesStream.WriteBool(blendShape.UseNormals);
@@ -609,9 +763,7 @@ bool SkinnedModel::Save(bool withMeshDataFromGpu, const StringView& path)
meshesStream.WriteUint32(blendShape.Vertices.Count());
meshesStream.WriteBytes(blendShape.Vertices.Get(), blendShape.Vertices.Count() * sizeof(BlendShapeVertex));
}
-
meshesStream.WriteBytes(meshData.VB0.Get(), vb0Size);
-
if (shouldUse16BitIndexBuffer == use16BitIndexBuffer)
{
meshesStream.WriteBytes(meshData.IB.Get(), ibSize);
@@ -690,11 +842,10 @@ bool SkinnedModel::Init(const Span& meshesCountPerLod)
// Setup
MaterialSlots.Resize(1);
MinScreenSize = 0.0f;
-
- // Setup LODs
for (int32 lodIndex = 0; lodIndex < LODs.Count(); lodIndex++)
LODs[lodIndex].Dispose();
LODs.Resize(meshesCountPerLod.Length());
+ _initialized = true;
// Setup meshes
for (int32 lodIndex = 0; lodIndex < meshesCountPerLod.Length(); lodIndex++)
@@ -720,6 +871,49 @@ bool SkinnedModel::Init(const Span& meshesCountPerLod)
return false;
}
+void SkinnedModel::ClearSkeletonMapping()
+{
+ for (auto& e : _skeletonMappingCache)
+ {
+ e.Key->OnUnloaded.Unbind(this);
+#if USE_EDITOR
+ e.Key->OnReloading.Unbind(this);
+#endif
+ Allocator::Free(e.Value.NodesMapping.Get());
+ }
+ _skeletonMappingCache.Clear();
+}
+
+void SkinnedModel::OnSkeletonMappingSourceAssetUnloaded(Asset* obj)
+{
+ ScopeLock lock(Locker);
+ auto i = _skeletonMappingCache.Find(obj);
+ ASSERT(i != _skeletonMappingCache.End());
+
+ // Unlink event
+ obj->OnUnloaded.Unbind(this);
+#if USE_EDITOR
+ obj->OnReloading.Unbind(this);
+#endif
+
+ // Clear cache
+ Allocator::Free(i->Value.NodesMapping.Get());
+ _skeletonMappingCache.Remove(i);
+}
+
+uint64 SkinnedModel::GetMemoryUsage() const
+{
+ Locker.Lock();
+ uint64 result = BinaryAsset::GetMemoryUsage();
+ result += sizeof(SkinnedModel) - sizeof(BinaryAsset);
+ result += Skeleton.GetMemoryUsage();
+ result += _skeletonMappingCache.Capacity() * sizeof(Dictionary>::Bucket);
+ for (const auto& e : _skeletonMappingCache)
+ result += e.Value.NodesMapping.Length();
+ Locker.Unlock();
+ return result;
+}
+
void SkinnedModel::SetupMaterialSlots(int32 slotsCount)
{
ModelBase::SetupMaterialSlots(slotsCount);
@@ -754,6 +948,7 @@ void SkinnedModel::InitAsVirtual()
// Init with one mesh and single bone
int32 meshesCount = 1;
Init(ToSpan(&meshesCount, 1));
+ ClearSkeletonMapping();
Skeleton.Dispose();
//
Skeleton.Nodes.Resize(1);
@@ -874,13 +1069,16 @@ Asset::LoadResult SkinnedModel::load()
MemoryReadStream headerStream(chunk0->Get(), chunk0->Size());
ReadStream* stream = &headerStream;
+ // Header Version
+ byte version = stream->ReadByte();
+
// Min Screen Size
stream->ReadFloat(&MinScreenSize);
// Amount of material slots
int32 materialSlotsCount;
stream->ReadInt32(&materialSlotsCount);
- if (materialSlotsCount <= 0 || materialSlotsCount > 4096)
+ if (materialSlotsCount < 0 || materialSlotsCount > 4096)
return LoadResult::InvalidData;
MaterialSlots.Resize(materialSlotsCount, false);
@@ -904,9 +1102,10 @@ Asset::LoadResult SkinnedModel::load()
// Amount of LODs
byte lods;
stream->ReadByte(&lods);
- if (lods == 0 || lods > MODEL_MAX_LODS)
+ if (lods > MODEL_MAX_LODS)
return LoadResult::InvalidData;
LODs.Resize(lods);
+ _initialized = true;
// For each LOD
for (int32 lodIndex = 0; lodIndex < lods; lodIndex++)
@@ -972,12 +1171,9 @@ Asset::LoadResult SkinnedModel::load()
if (nodesCount <= 0)
return LoadResult::InvalidData;
Skeleton.Nodes.Resize(nodesCount, false);
-
- // For each node
for (int32 nodeIndex = 0; nodeIndex < nodesCount; nodeIndex++)
{
- auto& node = Skeleton.Nodes[nodeIndex];
-
+ auto& node = Skeleton.Nodes.Get()[nodeIndex];
stream->Read(node.ParentIndex);
stream->ReadTransform(&node.LocalTransform);
stream->ReadString(&node.Name, 71);
@@ -988,12 +1184,9 @@ Asset::LoadResult SkinnedModel::load()
if (bonesCount <= 0)
return LoadResult::InvalidData;
Skeleton.Bones.Resize(bonesCount, false);
-
- // For each bone
for (int32 boneIndex = 0; boneIndex < bonesCount; boneIndex++)
{
- auto& bone = Skeleton.Bones[boneIndex];
-
+ auto& bone = Skeleton.Bones.Get()[boneIndex];
stream->Read(bone.ParentIndex);
stream->Read(bone.NodeIndex);
stream->ReadTransform(&bone.LocalTransform);
@@ -1001,6 +1194,20 @@ Asset::LoadResult SkinnedModel::load()
}
}
+ // Retargeting
+ {
+ int32 entriesCount;
+ stream->ReadInt32(&entriesCount);
+ _skeletonRetargets.Resize(entriesCount);
+ for (int32 entryIndex = 0; entryIndex < entriesCount; entryIndex++)
+ {
+ auto& retarget = _skeletonRetargets[entryIndex];
+ stream->Read(retarget.SourceAsset);
+ stream->Read(retarget.SkeletonAsset);
+ stream->Read(retarget.NodesMapping);
+ }
+ }
+
// Request resource streaming
StartStreaming(true);
@@ -1023,7 +1230,10 @@ void SkinnedModel::unload(bool isReloading)
LODs[i].Dispose();
LODs.Clear();
Skeleton.Dispose();
+ _initialized = false;
_loadedLODs = 0;
+ _skeletonRetargets.Clear();
+ ClearSkeletonMapping();
}
bool SkinnedModel::init(AssetInitData& initData)
diff --git a/Source/Engine/Content/Assets/SkinnedModel.h b/Source/Engine/Content/Assets/SkinnedModel.h
index 02afcacba..11fc0c0aa 100644
--- a/Source/Engine/Content/Assets/SkinnedModel.h
+++ b/Source/Engine/Content/Assets/SkinnedModel.h
@@ -14,12 +14,32 @@ class StreamSkinnedModelLODTask;
///
API_CLASS(NoSpawn) class FLAXENGINE_API SkinnedModel : public ModelBase
{
- DECLARE_BINARY_ASSET_HEADER(SkinnedModel, 4);
+ DECLARE_BINARY_ASSET_HEADER(SkinnedModel, 5);
friend SkinnedMesh;
friend StreamSkinnedModelLODTask;
+public:
+ // Skeleton mapping descriptor.
+ struct FLAXENGINE_API SkeletonMapping
+ {
+ // Target skeleton.
+ AssetReference TargetSkeleton;
+ // Source skeleton.
+ AssetReference SourceSkeleton;
+ // The node-to-node mapping for the fast animation sampling for the skinned model skeleton nodes. Each item is index of the source skeleton node into target skeleton node.
+ Span NodesMapping;
+ };
+
private:
+ struct SkeletonMappingData
+ {
+ AssetReference SourceSkeleton;
+ Span NodesMapping;
+ };
+
+ bool _initialized = false;
int32 _loadedLODs = 0;
StreamSkinnedModelLODTask* _streamingTask = nullptr;
+ Dictionary _skeletonMappingCache;
public:
///
@@ -44,7 +64,7 @@ public:
///
FORCE_INLINE bool IsInitialized() const
{
- return LODs.HasItems();
+ return _initialized;
}
///
@@ -76,13 +96,11 @@ public:
///
/// Determines whether any LOD has been initialized.
///
- /// True if any LOD has been initialized, otherwise false.
bool HasAnyLODInitialized() const;
///
/// Determines whether this model can be rendered.
///
- /// True if can render that model, otherwise false.
FORCE_INLINE bool CanBeRendered() const
{
return _loadedLODs > 0;
@@ -109,7 +127,7 @@ public:
///
/// The name of the node.
/// The index of the node or -1 if not found.
- API_FUNCTION() FORCE_INLINE int32 FindNode(const StringView& name)
+ API_FUNCTION() FORCE_INLINE int32 FindNode(const StringView& name) const
{
return Skeleton.FindNode(name);
}
@@ -119,7 +137,7 @@ public:
///
/// The name of the node used by the bone.
/// The index of the bone or -1 if not found.
- API_FUNCTION() FORCE_INLINE int32 FindBone(const StringView& name)
+ API_FUNCTION() FORCE_INLINE int32 FindBone(const StringView& name) const
{
return FindBone(FindNode(name));
}
@@ -129,7 +147,7 @@ public:
///
/// The index of the node.
/// The index of the bone or -1 if not found.
- API_FUNCTION() FORCE_INLINE int32 FindBone(int32 nodeIndex)
+ API_FUNCTION() FORCE_INLINE int32 FindBone(int32 nodeIndex) const
{
return Skeleton.FindBone(nodeIndex);
}
@@ -154,7 +172,13 @@ public:
/// The data (may be missing if failed to get it).
void GetLODData(int32 lodIndex, BytesContainer& data) const;
-public:
+ ///
+ /// Gets the skeleton mapping for a given asset (animation or other skinned model). Uses identity mapping or manually created retargeting setup.
+ ///
+ /// The source asset (animation or other skinned model) to get mapping to its skeleton.
+ /// The skeleton mapping for the source asset into this skeleton.
+ SkeletonMapping GetSkeletonMapping(Asset* source);
+
///
/// Determines if there is an intersection between the SkinnedModel and a Ray in given world using given instance.
///
@@ -194,7 +218,6 @@ public:
/// The bounding box.
API_FUNCTION() BoundingBox GetBox(int32 lodIndex = 0) const;
-public:
///
/// Draws the meshes. Binds vertex and index buffers and invokes the draw calls.
///
@@ -219,7 +242,6 @@ public:
/// The packed drawing info data.
void Draw(const RenderContextBatch& renderContextBatch, const SkinnedMesh::DrawInfo& info);
-public:
///
/// Setups the model LODs collection including meshes creation.
///
@@ -244,7 +266,6 @@ public:
API_FUNCTION() bool SetupSkeleton(const Array& nodes, const Array& bones, bool autoCalculateOffsetMatrix);
#if USE_EDITOR
-
///
/// Saves this asset to the file. Supported only in Editor.
///
@@ -253,7 +274,6 @@ public:
/// The custom asset path to use for the saving. Use empty value to save this asset to its own storage location. Can be used to duplicate asset. Must be specified when saving virtual asset.
/// True if cannot save data, otherwise false.
API_FUNCTION() bool Save(bool withMeshDataFromGpu = false, const StringView& path = StringView::Empty);
-
#endif
private:
@@ -264,8 +284,38 @@ private:
/// True if failed, otherwise false.
bool Init(const Span& meshesCountPerLod);
+ void ClearSkeletonMapping();
+ void OnSkeletonMappingSourceAssetUnloaded(Asset* obj);
+
+#if USE_EDITOR
+public:
+ // Skeleton retargeting setup (internal use only - accessed by Editor)
+ API_STRUCT(NoDefault) struct SkeletonRetarget
+ {
+ DECLARE_SCRIPTING_TYPE_MINIMAL(SkeletonRetarget);
+ // Source asset id.
+ API_FIELD() Guid SourceAsset;
+ // Skeleton asset id to use for remapping.
+ API_FIELD() Guid SkeletonAsset;
+ // Skeleton nodes remapping table (maps this skeleton node name to other skeleton node).
+ API_FIELD() Dictionary NodesMapping;
+ };
+ // Gets or sets the skeleton retarget entries (accessed in Editor only).
+ API_PROPERTY() const Array& GetSkeletonRetargets() const { return _skeletonRetargets; }
+ API_PROPERTY() void SetSkeletonRetargets(const Array& value) { Locker.Lock(); _skeletonRetargets = value; ClearSkeletonMapping(); Locker.Unlock(); }
+private:
+#else
+ struct SkeletonRetarget
+ {
+ Guid SourceAsset, SkeletonAsset;
+ Dictionary NodesMapping;
+ };
+#endif
+ Array _skeletonRetargets;
+
public:
// [ModelBase]
+ uint64 GetMemoryUsage() const override;
void SetupMaterialSlots(int32 slotsCount) override;
int32 GetLODsCount() const override;
void GetMeshes(Array& meshes, int32 lodIndex = 0) override;
diff --git a/Source/Engine/Content/Assets/Texture.cpp b/Source/Engine/Content/Assets/Texture.cpp
index 33154fae8..d796db914 100644
--- a/Source/Engine/Content/Assets/Texture.cpp
+++ b/Source/Engine/Content/Assets/Texture.cpp
@@ -7,7 +7,7 @@
#include "Engine/Platform/FileSystem.h"
#include "Engine/Graphics/RenderTools.h"
#include "Engine/Graphics/Textures/TextureData.h"
-#include "Engine/Scripting/MainThreadManagedInvokeAction.h"
+#include "Engine/Scripting/Internal/MainThreadManagedInvokeAction.h"
#include "Engine/Tools/TextureTool/TextureTool.h"
REGISTER_BINARY_ASSET_WITH_UPGRADER(Texture, "FlaxEngine.Texture", TextureAssetUpgrader, true);
diff --git a/Source/Engine/Content/Assets/VisualScript.cpp b/Source/Engine/Content/Assets/VisualScript.cpp
index 932a611da..b11a2af96 100644
--- a/Source/Engine/Content/Assets/VisualScript.cpp
+++ b/Source/Engine/Content/Assets/VisualScript.cpp
@@ -5,14 +5,13 @@
#include "Engine/Core/Types/DataContainer.h"
#include "Engine/Content/Content.h"
#include "Engine/Content/Factories/BinaryAssetFactory.h"
-#include "Engine/Scripting/MException.h"
#include "Engine/Scripting/Scripting.h"
#include "Engine/Scripting/Events.h"
#include "Engine/Scripting/ManagedCLR/MClass.h"
#include "Engine/Scripting/ManagedCLR/MMethod.h"
#include "Engine/Scripting/ManagedCLR/MField.h"
#include "Engine/Scripting/ManagedCLR/MUtils.h"
-#include "Engine/Scripting/ManagedCLR/MType.h"
+#include "Engine/Scripting/ManagedCLR/MException.h"
#include "Engine/Serialization/MemoryReadStream.h"
#include "Engine/Serialization/MemoryWriteStream.h"
#include "Engine/Serialization/Serialization.h"
@@ -310,15 +309,15 @@ void VisualScriptExecutor::ProcessGroupTools(Box* box, Node* node, Value& value)
const StringAsANSI<100> typeNameAnsi(typeName.Get(), typeName.Length());
if (StringUtils::Compare(typeNameAnsi.Get(), obj.Type.GetTypeName()) != 0)
{
-#if USE_MONO
- MonoClass* klass = Scripting::FindClassNative(StringAnsiView(typeNameAnsi.Get(), typeName.Length()));
- MonoClass* objKlass = MUtils::GetClass(obj);
- if (!klass || !objKlass || mono_class_is_subclass_of(objKlass, klass, false) == 0)
+#if USE_CSHARP
+ MClass* klass = Scripting::FindClass(StringAnsiView(typeNameAnsi.Get(), typeName.Length()));
+ MClass* objKlass = MUtils::GetClass(obj);
+ if (!klass || !objKlass || !objKlass->IsSubClassOf(klass))
obj = Value::Null;
#else
const ScriptingTypeHandle type = Scripting::FindScriptingType(StringAnsiView(typeNameAnsi.Get(), typeName.Length()));
const ScriptingTypeHandle objType = Scripting::FindScriptingType(obj.Type.GetTypeName());
- if (!type || !objType || objType.IsSubclassOf(type))
+ if (!type || !objType || !objType.IsSubclassOf(type))
obj = Value::Null;
#endif
}
diff --git a/Source/Engine/Content/Content.cpp b/Source/Engine/Content/Content.cpp
index c28ed61ef..0d06dbba8 100644
--- a/Source/Engine/Content/Content.cpp
+++ b/Source/Engine/Content/Content.cpp
@@ -760,9 +760,14 @@ bool Content::CloneAssetFile(const StringView& dstPath, const StringView& srcPat
// Change asset ID
{
auto storage = ContentStorageManager::GetStorage(tmpPath);
+ if (!storage)
+ {
+ LOG(Warning, "Cannot change asset ID.");
+ return true;
+ }
FlaxStorage::Entry e;
storage->GetEntry(0, e);
- if (!storage || storage->ChangeAssetID(e, dstId))
+ if (storage->ChangeAssetID(e, dstId))
{
LOG(Warning, "Cannot change asset ID.");
return true;
diff --git a/Source/Engine/Content/JsonAsset.cpp b/Source/Engine/Content/JsonAsset.cpp
index 86880e56b..1fbc2abed 100644
--- a/Source/Engine/Content/JsonAsset.cpp
+++ b/Source/Engine/Content/JsonAsset.cpp
@@ -416,9 +416,13 @@ void JsonAsset::DeleteInstance()
ScopeLock lock(Locker);
// C# instance
- if (MObject* object = GetManagedInstance())
+ MObject* object = GetManagedInstance();
+ MClass* klass = GetClass();
+ if (object && klass)
{
- GetClass()->GetField("_instance")->SetValue(object, nullptr);
+ const MField* field = klass->GetField("_instance");
+ if (field)
+ field->SetValue(object, nullptr);
}
// C++ instance
diff --git a/Source/Engine/Content/JsonAsset.cs b/Source/Engine/Content/JsonAsset.cs
index afd83bafd..6ba13a2d5 100644
--- a/Source/Engine/Content/JsonAsset.cs
+++ b/Source/Engine/Content/JsonAsset.cs
@@ -40,7 +40,7 @@ namespace FlaxEngine
Debug.LogError(string.Format("Missing typename of data in Json asset '{0}'.", Path), this);
return null;
}
- var assemblies = AppDomain.CurrentDomain.GetAssemblies();
+ var assemblies = Utils.GetAssemblies();
for (int i = 0; i < assemblies.Length; i++)
{
var assembly = assemblies[i];
diff --git a/Source/Engine/Content/Storage/FlaxStorage.cpp b/Source/Engine/Content/Storage/FlaxStorage.cpp
index 8608a34f6..52d7c34b5 100644
--- a/Source/Engine/Content/Storage/FlaxStorage.cpp
+++ b/Source/Engine/Content/Storage/FlaxStorage.cpp
@@ -211,6 +211,12 @@ FlaxStorage::~FlaxStorage()
CHECK(_chunksLock == 0);
CHECK(_refCount == 0);
ASSERT(_chunks.IsEmpty());
+
+#if USE_EDITOR
+ // Ensure to close any outstanding file handles to prevent file locking in case it failed to load
+ _file.DeleteAll();
+#endif
+
}
FlaxStorage::LockData FlaxStorage::LockSafe()
diff --git a/Source/Engine/Content/Upgraders/ModelAssetUpgrader.h b/Source/Engine/Content/Upgraders/ModelAssetUpgrader.h
index eb8b2ca2b..977f27349 100644
--- a/Source/Engine/Content/Upgraders/ModelAssetUpgrader.h
+++ b/Source/Engine/Content/Upgraders/ModelAssetUpgrader.h
@@ -26,10 +26,10 @@ public:
{
static const Upgrader upgraders[] =
{
- { 24, 25, &Upgrade_With_Repack },
- { 23, 24, &Upgrade_22OrNewer_To_Newest },
- { 22, 24, &Upgrade_22OrNewer_To_Newest },
- { 1, 24, &Upgrade_Old_To_Newest },
+ { 24, 25, &Upgrade_With_Repack }, // [Deprecated on 28.04.2023, expires on 01.01.2024]
+ { 23, 24, &Upgrade_22OrNewer_To_Newest }, // [Deprecated on 28.04.2023, expires on 01.01.2024]
+ { 22, 24, &Upgrade_22OrNewer_To_Newest }, // [Deprecated on 28.04.2023, expires on 01.01.2024]
+ { 1, 24, &Upgrade_Old_To_Newest }, // [Deprecated on 28.04.2023, expires on 01.01.2024]
};
setup(upgraders, ARRAY_COUNT(upgraders));
}
diff --git a/Source/Engine/Content/Upgraders/SkinnedModelAssetUpgrader.h b/Source/Engine/Content/Upgraders/SkinnedModelAssetUpgrader.h
index cd3eb75d1..49be3b347 100644
--- a/Source/Engine/Content/Upgraders/SkinnedModelAssetUpgrader.h
+++ b/Source/Engine/Content/Upgraders/SkinnedModelAssetUpgrader.h
@@ -28,9 +28,10 @@ public:
{
static const Upgrader upgraders[] =
{
- { 1, 2, &Upgrade_1_To_2 },
- { 2, 3, &Upgrade_2_To_3 },
- { 3, 4, &Upgrade_3_To_4 },
+ { 1, 2, &Upgrade_1_To_2 }, // [Deprecated on 28.04.2023, expires on 01.01.2024]
+ { 2, 3, &Upgrade_2_To_3 }, // [Deprecated on 28.04.2023, expires on 01.01.2024]
+ { 3, 4, &Upgrade_3_To_4 }, // [Deprecated on 28.04.2023, expires on 01.01.2024]
+ { 4, 5, &Upgrade_4_To_5 }, // [Deprecated on 28.04.2023, expires on 28.04.2026]
};
setup(upgraders, ARRAY_COUNT(upgraders));
}
@@ -57,7 +58,6 @@ private:
MemoryWriteStream output(srcData->Size());
do
{
- // #MODEL_DATA_FORMAT_USAGE
uint32 vertices;
stream.ReadUint32(&vertices);
uint32 triangles;
@@ -422,6 +422,241 @@ private:
return false;
}
+
+ static bool Upgrade_4_To_5(AssetMigrationContext& context)
+ {
+ ASSERT(context.Input.SerializedVersion == 4 && context.Output.SerializedVersion == 5);
+
+ // Changes:
+ // - added version number to header (for easier changes in future)
+ // - added version number to mesh data (for easier changes in future)
+ // - added skeleton retarget setups to header
+
+ // Rewrite header chunk (added header version and retarget entries)
+ byte lodCount;
+ Array meshesCounts;
+ {
+ const auto srcData = context.Input.Header.Chunks[0];
+ if (srcData == nullptr || srcData->IsMissing())
+ {
+ LOG(Warning, "Missing model header chunk");
+ return true;
+ }
+ MemoryReadStream stream(srcData->Get(), srcData->Size());
+ MemoryWriteStream output(srcData->Size());
+
+ // Header Version
+ output.WriteByte(1);
+
+ // Min Screen Size
+ float minScreenSize;
+ stream.ReadFloat(&minScreenSize);
+ output.WriteFloat(minScreenSize);
+
+ // Amount of material slots
+ int32 materialSlotsCount;
+ stream.ReadInt32(&materialSlotsCount);
+ output.WriteInt32(materialSlotsCount);
+
+ // For each material slot
+ for (int32 materialSlotIndex = 0; materialSlotIndex < materialSlotsCount; materialSlotIndex++)
+ {
+ // Material
+ Guid materialId;
+ stream.Read(materialId);
+ output.Write(materialId);
+
+ // Shadows Mode
+ output.WriteByte(stream.ReadByte());
+
+ // Name
+ String name;
+ stream.ReadString(&name, 11);
+ output.WriteString(name, 11);
+ }
+
+ // Amount of LODs
+ stream.ReadByte(&lodCount);
+ output.WriteByte(lodCount);
+ meshesCounts.Resize(lodCount);
+
+ // For each LOD
+ for (int32 lodIndex = 0; lodIndex < lodCount; lodIndex++)
+ {
+ // Screen Size
+ float screenSize;
+ stream.ReadFloat(&screenSize);
+ output.WriteFloat(screenSize);
+
+ // Amount of meshes
+ uint16 meshesCount;
+ stream.ReadUint16(&meshesCount);
+ output.WriteUint16(meshesCount);
+ meshesCounts[lodIndex] = meshesCount;
+
+ // For each mesh
+ for (uint16 meshIndex = 0; meshIndex < meshesCount; meshIndex++)
+ {
+ // Material Slot index
+ int32 materialSlotIndex;
+ stream.ReadInt32(&materialSlotIndex);
+ output.WriteInt32(materialSlotIndex);
+
+ // Box
+ BoundingBox box;
+ stream.Read(box);
+ output.Write(box);
+
+ // Sphere
+ BoundingSphere sphere;
+ stream.Read(sphere);
+ output.Write(sphere);
+
+ // Blend Shapes
+ uint16 blendShapes;
+ stream.ReadUint16(&blendShapes);
+ output.WriteUint16(blendShapes);
+ for (int32 blendShapeIndex = 0; blendShapeIndex < blendShapes; blendShapeIndex++)
+ {
+ String blendShapeName;
+ stream.ReadString(&blendShapeName, 13);
+ output.WriteString(blendShapeName, 13);
+ float blendShapeWeight;
+ stream.ReadFloat(&blendShapeWeight);
+ output.WriteFloat(blendShapeWeight);
+ }
+ }
+ }
+
+ // Skeleton
+ {
+ int32 nodesCount;
+ stream.ReadInt32(&nodesCount);
+ output.WriteInt32(nodesCount);
+
+ // For each node
+ for (int32 nodeIndex = 0; nodeIndex < nodesCount; nodeIndex++)
+ {
+ int32 parentIndex;
+ stream.ReadInt32(&parentIndex);
+ output.WriteInt32(parentIndex);
+
+ Transform localTransform;
+ stream.Read(localTransform);
+ output.Write(localTransform);
+
+ String name;
+ stream.ReadString(&name, 71);
+ output.WriteString(name, 71);
+ }
+
+ int32 bonesCount;
+ stream.ReadInt32(&bonesCount);
+ output.WriteInt32(bonesCount);
+
+ // For each bone
+ for (int32 boneIndex = 0; boneIndex < bonesCount; boneIndex++)
+ {
+ int32 parentIndex;
+ stream.ReadInt32(&parentIndex);
+ output.WriteInt32(parentIndex);
+
+ int32 nodeIndex;
+ stream.ReadInt32(&nodeIndex);
+ output.WriteInt32(nodeIndex);
+
+ Transform localTransform;
+ stream.Read(localTransform);
+ output.Write(localTransform);
+
+ Matrix offsetMatrix;
+ stream.ReadBytes(&offsetMatrix, sizeof(Matrix));
+ output.WriteBytes(&offsetMatrix, sizeof(Matrix));
+ }
+ }
+
+ // Retargeting
+ {
+ output.WriteInt32(0);
+ }
+
+ // Save new data
+ if (stream.GetPosition() != stream.GetLength())
+ {
+ LOG(Error, "Invalid position after upgrading skinned model header data.");
+ return true;
+ }
+ if (context.AllocateChunk(0))
+ return true;
+ context.Output.Header.Chunks[0]->Data.Copy(output.GetHandle(), output.GetPosition());
+ }
+
+ // Rewrite meshes data chunks
+ for (int32 lodIndex = 0; lodIndex < lodCount; lodIndex++)
+ {
+ const int32 chunkIndex = lodIndex + 1;
+ const auto srcData = context.Input.Header.Chunks[chunkIndex];
+ if (srcData == nullptr || srcData->IsMissing())
+ {
+ LOG(Warning, "Missing skinned model LOD meshes data chunk");
+ return true;
+ }
+ MemoryReadStream stream(srcData->Get(), srcData->Size());
+ MemoryWriteStream output(srcData->Size());
+
+ // Mesh Data Version
+ output.WriteByte(1);
+
+ for (int32 meshIndex = 0; meshIndex < meshesCounts[lodIndex]; meshIndex++)
+ {
+ uint32 vertices;
+ stream.ReadUint32(&vertices);
+ output.WriteUint32(vertices);
+ uint32 triangles;
+ stream.ReadUint32(&triangles);
+ output.WriteUint32(triangles);
+ uint16 blendShapesCount;
+ stream.ReadUint16(&blendShapesCount);
+ output.WriteUint16(blendShapesCount);
+ for (int32 blendShapeIndex = 0; blendShapeIndex < blendShapesCount; blendShapeIndex++)
+ {
+ output.WriteBool(stream.ReadBool());
+ uint32 minVertexIndex, maxVertexIndex;
+ stream.ReadUint32(&minVertexIndex);
+ output.WriteUint32(minVertexIndex);
+ stream.ReadUint32(&maxVertexIndex);
+ output.WriteUint32(maxVertexIndex);
+ uint32 blendShapeVertices;
+ stream.ReadUint32(&blendShapeVertices);
+ output.WriteUint32(blendShapeVertices);
+ const uint32 blendShapeDataSize = blendShapeVertices * sizeof(BlendShapeVertex);
+ const auto blendShapeData = stream.Move(blendShapeDataSize);
+ output.WriteBytes(blendShapeData, blendShapeDataSize);
+ }
+ const uint32 indicesCount = triangles * 3;
+ const bool use16BitIndexBuffer = indicesCount <= MAX_uint16;
+ const uint32 ibStride = use16BitIndexBuffer ? sizeof(uint16) : sizeof(uint32);
+ if (vertices == 0 || triangles == 0)
+ return true;
+ const auto vb0 = stream.Move(vertices);
+ output.WriteBytes(vb0, vertices * sizeof(VB0SkinnedElementType));
+ const auto ib = stream.Move(indicesCount * ibStride);
+ output.WriteBytes(ib, indicesCount * ibStride);
+ }
+
+ // Save new data
+ if (stream.GetPosition() != stream.GetLength())
+ {
+ LOG(Error, "Invalid position after upgrading skinned model LOD meshes data.");
+ return true;
+ }
+ if (context.AllocateChunk(chunkIndex))
+ return true;
+ context.Output.Header.Chunks[chunkIndex]->Data.Copy(output.GetHandle(), output.GetPosition());
+ }
+
+ return false;
+ }
};
#endif
diff --git a/Source/Engine/ContentExporters/ExportModel.cpp b/Source/Engine/ContentExporters/ExportModel.cpp
index 1134108e6..f68e6162b 100644
--- a/Source/Engine/ContentExporters/ExportModel.cpp
+++ b/Source/Engine/ContentExporters/ExportModel.cpp
@@ -151,6 +151,7 @@ ExportAssetResult AssetExporters::ExportSkinnedModel(ExportAssetContext& context
// Extract all meshes
const auto& lod = asset->LODs[lodIndex];
int32 vertexStart = 1; // OBJ counts vertices from 1 not from 0
+ byte version = stream.ReadByte();
for (int32 meshIndex = 0; meshIndex < lod.Meshes.Count(); meshIndex++)
{
auto& mesh = lod.Meshes[meshIndex];
@@ -160,6 +161,18 @@ ExportAssetResult AssetExporters::ExportSkinnedModel(ExportAssetContext& context
stream.ReadUint32(&vertices);
uint32 triangles;
stream.ReadUint32(&triangles);
+ uint16 blendShapesCount;
+ stream.ReadUint16(&blendShapesCount);
+ for (int32 blendShapeIndex = 0; blendShapeIndex < blendShapesCount; blendShapeIndex++)
+ {
+ stream.ReadBool();
+ uint32 tmp;
+ stream.ReadUint32(&tmp);
+ stream.ReadUint32(&tmp);
+ uint32 blendShapeVertices;
+ stream.ReadUint32(&blendShapeVertices);
+ stream.Move(blendShapeVertices * sizeof(BlendShapeVertex));
+ }
uint32 indicesCount = triangles * 3;
bool use16BitIndexBuffer = indicesCount <= MAX_uint16;
uint32 ibStride = use16BitIndexBuffer ? sizeof(uint16) : sizeof(uint32);
diff --git a/Source/Engine/ContentImporters/CreateJson.cpp b/Source/Engine/ContentImporters/CreateJson.cpp
index ed4926505..3ca21601c 100644
--- a/Source/Engine/ContentImporters/CreateJson.cpp
+++ b/Source/Engine/ContentImporters/CreateJson.cpp
@@ -30,7 +30,7 @@ bool CreateJson::Create(const StringView& path, rapidjson_flax::StringBuffer& da
return Create(path, data1, data2);
}
-bool CreateJson::Create(const StringView& path, StringAnsiView& data, StringAnsiView& dataTypename)
+bool CreateJson::Create(const StringView& path, const StringAnsiView& data, const StringAnsiView& dataTypename)
{
Guid id = Guid::New();
diff --git a/Source/Engine/ContentImporters/CreateJson.h b/Source/Engine/ContentImporters/CreateJson.h
index c3a3da58d..abe119dd7 100644
--- a/Source/Engine/ContentImporters/CreateJson.h
+++ b/Source/Engine/ContentImporters/CreateJson.h
@@ -16,7 +16,7 @@ class CreateJson
public:
static bool Create(const StringView& path, rapidjson_flax::StringBuffer& data, const String& dataTypename);
static bool Create(const StringView& path, rapidjson_flax::StringBuffer& data, const char* dataTypename);
- static bool Create(const StringView& path, StringAnsiView& data, StringAnsiView& dataTypename);
+ static bool Create(const StringView& path, const StringAnsiView& data, const StringAnsiView& dataTypename);
static CreateAssetResult ImportPo(CreateAssetContext& context);
};
diff --git a/Source/Engine/ContentImporters/ImportAudio.cpp b/Source/Engine/ContentImporters/ImportAudio.cpp
index a9955a4d4..10915b2d1 100644
--- a/Source/Engine/ContentImporters/ImportAudio.cpp
+++ b/Source/Engine/ContentImporters/ImportAudio.cpp
@@ -45,12 +45,11 @@ void ImportAudio::Options::Deserialize(DeserializeStream& stream, ISerializeModi
DESERIALIZE(BitDepth);
}
-bool ImportAudio::TryGetImportOptions(const String& path, Options& options)
+bool ImportAudio::TryGetImportOptions(const StringView& path, Options& options)
{
#if IMPORT_AUDIO_CACHE_OPTIONS
if (FileSystem::FileExists(path))
{
- // Try to load asset file and asset info
auto tmpFile = ContentStorageManager::GetStorage(path);
AssetInitData data;
if (tmpFile
@@ -64,13 +63,11 @@ bool ImportAudio::TryGetImportOptions(const String& path, Options& options)
metadata.Parse(data.Metadata.Get(), data.Metadata.Length());
if (!metadata.HasParseError())
{
- // Success
options.Deserialize(metadata, nullptr);
return true;
}
}
}
-
#endif
return false;
}
diff --git a/Source/Engine/ContentImporters/ImportAudio.h b/Source/Engine/ContentImporters/ImportAudio.h
index 28c8a2e67..fb4ee64e3 100644
--- a/Source/Engine/ContentImporters/ImportAudio.h
+++ b/Source/Engine/ContentImporters/ImportAudio.h
@@ -45,7 +45,7 @@ public:
/// The asset path.
/// The options.
/// True if success, otherwise false.
- static bool TryGetImportOptions(const String& path, Options& options);
+ static bool TryGetImportOptions(const StringView& path, Options& options);
///
/// Imports the audio data (with given audio decoder).
diff --git a/Source/Engine/ContentImporters/ImportModel.h b/Source/Engine/ContentImporters/ImportModel.h
index 6a39d0bb3..02e6bfc8d 100644
--- a/Source/Engine/ContentImporters/ImportModel.h
+++ b/Source/Engine/ContentImporters/ImportModel.h
@@ -28,7 +28,7 @@ public:
/// The asset path.
/// The options.
/// True if success, otherwise false.
- static bool TryGetImportOptions(String path, Options& options);
+ static bool TryGetImportOptions(const StringView& path, Options& options);
///
/// Imports the model file.
diff --git a/Source/Engine/ContentImporters/ImportModelFile.cpp b/Source/Engine/ContentImporters/ImportModelFile.cpp
index 8851ee4ce..b9a4f5675 100644
--- a/Source/Engine/ContentImporters/ImportModelFile.cpp
+++ b/Source/Engine/ContentImporters/ImportModelFile.cpp
@@ -16,11 +16,9 @@
#include "Engine/Platform/FileSystem.h"
#include "AssetsImportingManager.h"
-bool ImportModelFile::TryGetImportOptions(String path, Options& options)
+bool ImportModelFile::TryGetImportOptions(const StringView& path, Options& options)
{
#if IMPORT_MODEL_CACHE_OPTIONS
-
- // Check if target asset file exists
if (FileSystem::FileExists(path))
{
// Try to load asset file and asset info
@@ -41,15 +39,12 @@ bool ImportModelFile::TryGetImportOptions(String path, Options& options)
metadata.Parse((const char*)data.Metadata.Get(), data.Metadata.Length());
if (metadata.HasParseError() == false)
{
- // Success
options.Deserialize(metadata, nullptr);
return true;
}
}
}
-
#endif
-
return false;
}
@@ -269,6 +264,9 @@ CreateAssetResult ImportModelFile::ImportSkinnedModel(CreateAssetContext& contex
{
stream.SetPosition(0);
+ // Mesh Data Version
+ stream.WriteByte(1);
+
// Pack meshes
auto& meshes = modelData.LODs[lodIndex].Meshes;
for (int32 meshIndex = 0; meshIndex < meshes.Count(); meshIndex++)
diff --git a/Source/Engine/ContentImporters/ImportTexture.cpp b/Source/Engine/ContentImporters/ImportTexture.cpp
index 278837729..51b93420c 100644
--- a/Source/Engine/ContentImporters/ImportTexture.cpp
+++ b/Source/Engine/ContentImporters/ImportTexture.cpp
@@ -1,9 +1,7 @@
// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.
#include "ImportTexture.h"
-
#if COMPILE_WITH_ASSETS_IMPORTER
-
#include "Engine/Core/Log.h"
#include "Engine/Serialization/Serialization.h"
#include "Engine/Serialization/JsonWriters.h"
@@ -28,11 +26,8 @@ bool IsSpriteAtlasOrTexture(const String& typeName)
bool ImportTexture::TryGetImportOptions(const StringView& path, Options& options)
{
#if IMPORT_TEXTURE_CACHE_OPTIONS
-
- // Check if target asset texture exists
if (FileSystem::FileExists(path))
{
- // Try to load asset file and asset info (also check for Sprite Atlas or Texture assets)
auto tmpFile = ContentStorageManager::GetStorage(path);
AssetInitData data;
if (tmpFile
@@ -48,12 +43,11 @@ bool ImportTexture::TryGetImportOptions(const StringView& path, Options& options
if (chunk15 != nullptr && !tmpFile->LoadAssetChunk(chunk15) && chunk15->Data.IsValid())
{
MemoryReadStream stream(chunk15->Data.Get(), chunk15->Data.Length());
-
- // Load tiles data
int32 tilesVersion, tilesCount;
stream.ReadInt32(&tilesVersion);
if (tilesVersion == 1)
{
+ options.Sprites.Clear();
stream.ReadInt32(&tilesCount);
for (int32 i = 0; i < tilesCount; i++)
{
@@ -72,15 +66,12 @@ bool ImportTexture::TryGetImportOptions(const StringView& path, Options& options
metadata.Parse((const char*)data.Metadata.Get(), data.Metadata.Length());
if (metadata.HasParseError() == false)
{
- // Success
options.Deserialize(metadata, nullptr);
return true;
}
}
}
-
#endif
-
return false;
}
diff --git a/Source/Engine/Core/Config/BuildSettings.h b/Source/Engine/Core/Config/BuildSettings.h
index c2bbfa843..318c304e0 100644
--- a/Source/Engine/Core/Config/BuildSettings.h
+++ b/Source/Engine/Core/Config/BuildSettings.h
@@ -13,37 +13,37 @@
///
API_CLASS(sealed, Namespace="FlaxEditor.Content.Settings") class FLAXENGINE_API BuildSettings : public SettingsBase
{
-DECLARE_SCRIPTING_TYPE_MINIMAL(BuildSettings);
-public:
+ DECLARE_SCRIPTING_TYPE_MINIMAL(BuildSettings);
+public:
///
/// The maximum amount of assets to include into a single assets package. Asset packages will split into several packages if need to.
///
- API_FIELD(Attributes="EditorOrder(10), DefaultValue(4096), Limit(1, ushort.MaxValue), EditorDisplay(\"General\", \"Max assets per package\")")
+ API_FIELD(Attributes="EditorOrder(10), Limit(1, ushort.MaxValue), EditorDisplay(\"General\", \"Max assets per package\")")
int32 MaxAssetsPerPackage = 4096;
///
/// The maximum size of the single assets package (in megabytes). Asset packages will split into several packages if need to.
///
- API_FIELD(Attributes="EditorOrder(20), DefaultValue(1024), Limit(1, ushort.MaxValue), EditorDisplay(\"General\", \"Max package size (in MB)\")")
+ API_FIELD(Attributes="EditorOrder(20), Limit(1, ushort.MaxValue), EditorDisplay(\"General\", \"Max package size (in MB)\")")
int32 MaxPackageSizeMB = 1024;
///
/// The game content cooking keycode. Use the same value for a game and DLC packages to support loading them by the build game. Use 0 to randomize it during building.
///
- API_FIELD(Attributes="EditorOrder(30), DefaultValue(0), EditorDisplay(\"General\")")
+ API_FIELD(Attributes="EditorOrder(30), EditorDisplay(\"General\")")
int32 ContentKey = 0;
///
/// If checked, the builds produced by the Game Cooker will be treated as for final game distribution (eg. for game store upload). Builds done this way cannot be tested on console devkits (eg. Xbox One, Xbox Scarlett).
///
- API_FIELD(Attributes="EditorOrder(40), DefaultValue(false), EditorDisplay(\"General\")")
+ API_FIELD(Attributes="EditorOrder(40), EditorDisplay(\"General\")")
bool ForDistribution = false;
///
/// If checked, the output build files won't be packaged for the destination platform. Useful when debugging build from local PC.
///
- API_FIELD(Attributes="EditorOrder(50), DefaultValue(false), EditorDisplay(\"General\")")
+ API_FIELD(Attributes="EditorOrder(50), EditorDisplay(\"General\")")
bool SkipPackaging = false;
///
@@ -51,7 +51,7 @@ public:
///
API_FIELD(Attributes="EditorOrder(1000), EditorDisplay(\"Additional Data\")")
Array> AdditionalAssets;
-
+
///
/// The list of additional scenes to include into build (into root assets set).
///
@@ -67,17 +67,28 @@ public:
///
/// Disables shaders compiler optimizations in cooked game. Can be used to debug shaders on a target platform or to speed up the shaders compilation time.
///
- API_FIELD(Attributes="EditorOrder(2000), DefaultValue(false), EditorDisplay(\"Content\", \"Shaders No Optimize\")")
+ API_FIELD(Attributes="EditorOrder(2000), EditorDisplay(\"Content\", \"Shaders No Optimize\")")
bool ShadersNoOptimize = false;
///
/// Enables shader debug data generation for shaders in cooked game (depends on the target platform rendering backend).
///
- API_FIELD(Attributes="EditorOrder(2010), DefaultValue(false), EditorDisplay(\"Content\")")
+ API_FIELD(Attributes="EditorOrder(2010), EditorDisplay(\"Content\")")
bool ShadersGenerateDebugData = false;
-public:
+ ///
+ /// If checked, .NET Runtime won't be packaged with a game and will be required by user to be installed on system upon running game build. Available only on supported platforms such as Windows, Linux and macOS.
+ ///
+ API_FIELD(Attributes="EditorOrder(3000), EditorDisplay(\"Scripting\", \"Skip .NET Runtime Packaging\")")
+ bool SkipDotnetPackaging = false;
+ ///
+ /// If checked, .NET Runtime packaging will skip unused libraries from packaging resulting in smaller game builds.
+ ///
+ API_FIELD(Attributes="EditorOrder(3010), EditorDisplay(\"Scripting\", \"Skip Unused .NET Runtime Libs Packaging\")")
+ bool SkipUnusedDotnetLibsPackaging = true;
+
+public:
///
/// Gets the instance of the settings asset (default value if missing). Object returned by this method is always loaded with valid data to use.
///
@@ -95,5 +106,7 @@ public:
DESERIALIZE(AdditionalAssetFolders);
DESERIALIZE(ShadersNoOptimize);
DESERIALIZE(ShadersGenerateDebugData);
+ DESERIALIZE(SkipDotnetPackaging);
+ DESERIALIZE(SkipUnusedDotnetLibsPackaging);
}
};
diff --git a/Source/Engine/Core/Config/GameSettings.cpp b/Source/Engine/Core/Config/GameSettings.cpp
index c64fc0d4e..3784c46ff 100644
--- a/Source/Engine/Core/Config/GameSettings.cpp
+++ b/Source/Engine/Core/Config/GameSettings.cpp
@@ -71,6 +71,8 @@ IMPLEMENT_ENGINE_SETTINGS_GETTER(AndroidPlatformSettings, AndroidPlatform);
IMPLEMENT_ENGINE_SETTINGS_GETTER(SwitchPlatformSettings, SwitchPlatform);
#elif PLATFORM_MAC
IMPLEMENT_ENGINE_SETTINGS_GETTER(MacPlatformSettings, MacPlatform);
+#elif PLATFORM_IOS
+IMPLEMENT_ENGINE_SETTINGS_GETTER(iOSPlatformSettings, iOSPlatform);
#else
#error Unknown platform
#endif
@@ -254,6 +256,7 @@ void GameSettings::Deserialize(DeserializeStream& stream, ISerializeModifier* mo
DESERIALIZE(SwitchPlatform);
DESERIALIZE(PS5Platform);
DESERIALIZE(MacPlatform);
+ DESERIALIZE(iOSPlatform);
}
void LayersAndTagsSettings::Deserialize(DeserializeStream& stream, ISerializeModifier* modifier)
diff --git a/Source/Engine/Core/Config/GameSettings.cs b/Source/Engine/Core/Config/GameSettings.cs
index fbd27e3ca..40d9f5dbc 100644
--- a/Source/Engine/Core/Config/GameSettings.cs
+++ b/Source/Engine/Core/Config/GameSettings.cs
@@ -3,6 +3,7 @@
using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
using FlaxEngine;
namespace FlaxEditor.Content.Settings
@@ -201,6 +202,14 @@ namespace FlaxEditor.Content.Settings
public JsonAsset MacPlatform;
#endif
+#if FLAX_EDITOR || PLATFORM_IOS
+ ///
+ /// Reference to asset. Used to apply configuration on iOS platform.
+ ///
+ [EditorOrder(2100), EditorDisplay("Platform Settings", "iOS"), AssetReference(typeof(iOSPlatformSettings), true), Tooltip("Reference to iOS Platform Settings asset")]
+ public JsonAsset iOSPlatform;
+#endif
+
///
/// Gets the absolute path to the game settings asset file.
///
@@ -332,6 +341,10 @@ namespace FlaxEditor.Content.Settings
if (type == typeof(MacPlatformSettings))
return Load(gameSettings.MacPlatform) as T;
#endif
+#if FLAX_EDITOR || PLATFORM_IOS
+ if (type == typeof(iOSPlatformSettings))
+ return Load(gameSettings.iOSPlatform) as T;
+#endif
if (gameSettings.CustomSettings != null)
{
@@ -426,6 +439,10 @@ namespace FlaxEditor.Content.Settings
if (type == typeof(MacPlatformSettings))
return gameSettings.MacPlatform;
#endif
+#if FLAX_EDITOR || PLATFORM_IOS
+ if (type == typeof(iOSPlatformSettings))
+ return gameSettings.iOSPlatform;
+#endif
if (gameSettings.CustomSettings != null)
{
@@ -538,6 +555,8 @@ namespace FlaxEditor.Content.Settings
return SaveAsset(gameSettings, ref gameSettings.Audio, obj);
if (type == typeof(MacPlatformSettings))
return SaveAsset(gameSettings, ref gameSettings.MacPlatform, obj);
+ if (type == typeof(iOSPlatformSettings))
+ return SaveAsset(gameSettings, ref gameSettings.iOSPlatform, obj);
return true;
}
@@ -572,8 +591,8 @@ namespace FlaxEditor.Content.Settings
///
/// Loads the current game settings asset and applies it to the engine runtime configuration.
///
- [MethodImpl(MethodImplOptions.InternalCall)]
- public static extern void Apply();
+ [LibraryImport("FlaxEngine", EntryPoint = "GameSettingsInternal_Apply")]
+ public static partial void Apply();
#endif
}
}
diff --git a/Source/Engine/Core/Config/GameSettings.h b/Source/Engine/Core/Config/GameSettings.h
index d34dca4de..54ad29a7b 100644
--- a/Source/Engine/Core/Config/GameSettings.h
+++ b/Source/Engine/Core/Config/GameSettings.h
@@ -84,6 +84,7 @@ public:
Guid SwitchPlatform;
Guid PS5Platform;
Guid MacPlatform;
+ Guid iOSPlatform;
public:
diff --git a/Source/Engine/Core/Config/LayersAndTagsSettings.cs b/Source/Engine/Core/Config/LayersAndTagsSettings.cs
index 0dd6da445..008f70504 100644
--- a/Source/Engine/Core/Config/LayersAndTagsSettings.cs
+++ b/Source/Engine/Core/Config/LayersAndTagsSettings.cs
@@ -1,7 +1,8 @@
// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.
using System.Collections.Generic;
-using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+using System.Runtime.InteropServices.Marshalling;
using FlaxEngine;
namespace FlaxEditor.Content.Settings
@@ -24,7 +25,13 @@ namespace FlaxEditor.Content.Settings
/// Gets the current layer names (max 32 items but trims last empty items).
///
/// The layers.
- [MethodImpl(MethodImplOptions.InternalCall)]
- public static extern string[] GetCurrentLayers();
+ public static string[] GetCurrentLayers()
+ {
+ return GetCurrentLayers(out int _);
+ }
+
+ [LibraryImport("FlaxEngine", EntryPoint = "LayersAndTagsSettingsInternal_GetCurrentLayers", StringMarshalling = StringMarshalling.Custom, StringMarshallingCustomType = typeof(FlaxEngine.Interop.StringMarshaller))]
+ [return: MarshalUsing(typeof(FlaxEngine.Interop.ArrayMarshaller<,>), CountElementName = "layerCount")]
+ internal static partial string[] GetCurrentLayers(out int layerCount);
}
}
diff --git a/Source/Engine/Core/Config/PlatformSettings.h b/Source/Engine/Core/Config/PlatformSettings.h
index ed26af514..5353ac7fb 100644
--- a/Source/Engine/Core/Config/PlatformSettings.h
+++ b/Source/Engine/Core/Config/PlatformSettings.h
@@ -35,3 +35,6 @@
#if PLATFORM_MAC
#include "Engine/Platform/Mac/MacPlatformSettings.h"
#endif
+#if PLATFORM_IOS
+#include "Engine/Platform/iOS/iOSPlatformSettings.h"
+#endif
diff --git a/Source/Engine/Core/Formatting.h b/Source/Engine/Core/Formatting.h
index bc2dc150b..069b17e5d 100644
--- a/Source/Engine/Core/Formatting.h
+++ b/Source/Engine/Core/Formatting.h
@@ -18,8 +18,7 @@ namespace fmt_flax
FORCE_INLINE static void format(fmt::basic_memory_buffer>& buffer, const T* format, const Args& ... args)
{
typedef fmt::buffer_context context;
- fmt::format_arg_store as{ args... };
- fmt::internal::vformat_to(buffer, fmt::to_string_view(format), fmt::basic_format_args(as));
+ fmt::detail::vformat_to(buffer, fmt::basic_string_view(format), fmt::make_format_args(args...), {});
}
}
@@ -37,7 +36,7 @@ namespace fmt_flax
template \
auto format(const type& v, FormatContext& ctx) -> decltype(ctx.out()) \
{ \
- return format_to(ctx.out(), TEXT(formatText), ##__VA_ARGS__); \
+ return fmt::format_to(ctx.out(), basic_string_view(TEXT(formatText)), ##__VA_ARGS__); \
} \
}; \
}
@@ -57,7 +56,7 @@ namespace fmt_flax
auto format(const type& v, FormatContext& ctx) -> decltype(ctx.out()) \
{ \
const String str = v.ToString(); \
- return fmt::internal::copy(str.Get(), str.Get() + str.Length(), ctx.out()); \
+ return fmt::detail::copy_str(str.Get(), str.Get() + str.Length(), ctx.out()); \
} \
}; \
}
diff --git a/Source/Engine/Core/Log.h b/Source/Engine/Core/Log.h
index de4dc108d..a7e927612 100644
--- a/Source/Engine/Core/Log.h
+++ b/Source/Engine/Core/Log.h
@@ -13,7 +13,7 @@
///
/// Sends a formatted message to the log file (message type - describes level of the log (see LogType enum))
///
-#define LOG(messageType, format, ...) Log::Logger::Write(LogType::messageType, TEXT(format), ##__VA_ARGS__)
+#define LOG(messageType, format, ...) Log::Logger::Write(LogType::messageType, ::String::Format(TEXT(format), ##__VA_ARGS__))
///
/// Sends a string message to the log file (message type - describes level of the log (see LogType enum))
diff --git a/Source/Engine/Core/Math/Color.cs b/Source/Engine/Core/Math/Color.cs
index 7bf60b42a..d585177a0 100644
--- a/Source/Engine/Core/Math/Color.cs
+++ b/Source/Engine/Core/Math/Color.cs
@@ -1,7 +1,6 @@
// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.
using System;
-using System.ComponentModel;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
@@ -9,7 +8,9 @@ using System.Runtime.InteropServices;
namespace FlaxEngine
{
[Serializable]
- [TypeConverter(typeof(TypeConverters.ColorConverter))]
+#if FLAX_EDITOR
+ [System.ComponentModel.TypeConverter(typeof(TypeConverters.ColorConverter))]
+#endif
partial struct Color
{
///
diff --git a/Source/Engine/Core/Math/Double2.cs b/Source/Engine/Core/Math/Double2.cs
index 20a8094f6..02669a869 100644
--- a/Source/Engine/Core/Math/Double2.cs
+++ b/Source/Engine/Core/Math/Double2.cs
@@ -55,7 +55,6 @@ using Real = System.Single;
* THE SOFTWARE.
*/
using System;
-using System.ComponentModel;
using System.Globalization;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
@@ -63,7 +62,9 @@ using System.Runtime.InteropServices;
namespace FlaxEngine
{
[Serializable]
- [TypeConverter(typeof(TypeConverters.Double2Converter))]
+#if FLAX_EDITOR
+ [System.ComponentModel.TypeConverter(typeof(TypeConverters.Double2Converter))]
+#endif
partial struct Double2 : IEquatable, IFormattable
{
private static readonly string _formatString = "X:{0:F2} Y:{1:F2}";
diff --git a/Source/Engine/Core/Math/Double3.cs b/Source/Engine/Core/Math/Double3.cs
index 85360f0ff..eb41ad400 100644
--- a/Source/Engine/Core/Math/Double3.cs
+++ b/Source/Engine/Core/Math/Double3.cs
@@ -56,7 +56,6 @@ using Real = System.Single;
*/
using System;
-using System.ComponentModel;
using System.Globalization;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
@@ -64,7 +63,9 @@ using System.Runtime.InteropServices;
namespace FlaxEngine
{
[Serializable]
- [TypeConverter(typeof(TypeConverters.Double3Converter))]
+#if FLAX_EDITOR
+ [System.ComponentModel.TypeConverter(typeof(TypeConverters.Double3Converter))]
+#endif
partial struct Double3 : IEquatable, IFormattable
{
private static readonly string _formatString = "X:{0:F2} Y:{1:F2} Z:{2:F2}";
diff --git a/Source/Engine/Core/Math/Double4.cs b/Source/Engine/Core/Math/Double4.cs
index bf69f2c72..68c9a005c 100644
--- a/Source/Engine/Core/Math/Double4.cs
+++ b/Source/Engine/Core/Math/Double4.cs
@@ -56,7 +56,6 @@ using Real = System.Single;
*/
using System;
-using System.ComponentModel;
using System.Globalization;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
@@ -64,7 +63,9 @@ using System.Runtime.InteropServices;
namespace FlaxEngine
{
[Serializable]
- [TypeConverter(typeof(TypeConverters.Double4Converter))]
+#if FLAX_EDITOR
+ [System.ComponentModel.TypeConverter(typeof(TypeConverters.Double4Converter))]
+#endif
partial struct Double4 : IEquatable, IFormattable
{
private static readonly string _formatString = "X:{0:F2} Y:{1:F2} Z:{2:F2} W:{3:F2}";
diff --git a/Source/Engine/Core/Math/Float2.cs b/Source/Engine/Core/Math/Float2.cs
index a53fddd34..3ca6f51d7 100644
--- a/Source/Engine/Core/Math/Float2.cs
+++ b/Source/Engine/Core/Math/Float2.cs
@@ -50,7 +50,6 @@
*/
using System;
-using System.ComponentModel;
using System.Globalization;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
@@ -58,7 +57,9 @@ using System.Runtime.InteropServices;
namespace FlaxEngine
{
[Serializable]
- [TypeConverter(typeof(TypeConverters.Float2Converter))]
+#if FLAX_EDITOR
+ [System.ComponentModel.TypeConverter(typeof(TypeConverters.Float2Converter))]
+#endif
partial struct Float2 : IEquatable, IFormattable
{
private static readonly string _formatString = "X:{0:F2} Y:{1:F2}";
diff --git a/Source/Engine/Core/Math/Float3.cs b/Source/Engine/Core/Math/Float3.cs
index 9ea922c2b..1831a3b99 100644
--- a/Source/Engine/Core/Math/Float3.cs
+++ b/Source/Engine/Core/Math/Float3.cs
@@ -50,7 +50,6 @@
*/
using System;
-using System.ComponentModel;
using System.Globalization;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
@@ -58,7 +57,9 @@ using System.Runtime.InteropServices;
namespace FlaxEngine
{
[Serializable]
- [TypeConverter(typeof(TypeConverters.Float3Converter))]
+#if FLAX_EDITOR
+ [System.ComponentModel.TypeConverter(typeof(TypeConverters.Float3Converter))]
+#endif
partial struct Float3 : IEquatable, IFormattable
{
private static readonly string _formatString = "X:{0:F2} Y:{1:F2} Z:{2:F2}";
diff --git a/Source/Engine/Core/Math/Float4.cs b/Source/Engine/Core/Math/Float4.cs
index eb10e5db4..ff84a07b2 100644
--- a/Source/Engine/Core/Math/Float4.cs
+++ b/Source/Engine/Core/Math/Float4.cs
@@ -50,7 +50,6 @@
*/
using System;
-using System.ComponentModel;
using System.Globalization;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
@@ -58,7 +57,9 @@ using System.Runtime.InteropServices;
namespace FlaxEngine
{
[Serializable]
- [TypeConverter(typeof(TypeConverters.Float4Converter))]
+#if FLAX_EDITOR
+ [System.ComponentModel.TypeConverter(typeof(TypeConverters.Float4Converter))]
+#endif
partial struct Float4 : IEquatable, IFormattable
{
private static readonly string _formatString = "X:{0:F2} Y:{1:F2} Z:{2:F2} W:{3:F2}";
diff --git a/Source/Engine/Core/Math/Int2.cs b/Source/Engine/Core/Math/Int2.cs
index 62c8bf7ee..47b225ff6 100644
--- a/Source/Engine/Core/Math/Int2.cs
+++ b/Source/Engine/Core/Math/Int2.cs
@@ -1,7 +1,6 @@
// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.
using System;
-using System.ComponentModel;
using System.Globalization;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
@@ -12,7 +11,9 @@ namespace FlaxEngine
/// Represents a two dimensional mathematical vector (signed integers).
///
[Serializable]
- [TypeConverter(typeof(TypeConverters.Int2Converter))]
+#if FLAX_EDITOR
+ [System.ComponentModel.TypeConverter(typeof(TypeConverters.Int2Converter))]
+#endif
partial struct Int2 : IEquatable, IFormattable
{
private static readonly string _formatString = "X:{0} Y:{1}";
diff --git a/Source/Engine/Core/Math/Int3.cs b/Source/Engine/Core/Math/Int3.cs
index d8fe4bda7..c382a2518 100644
--- a/Source/Engine/Core/Math/Int3.cs
+++ b/Source/Engine/Core/Math/Int3.cs
@@ -1,7 +1,6 @@
// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.
using System;
-using System.ComponentModel;
using System.Globalization;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
@@ -12,7 +11,9 @@ namespace FlaxEngine
/// Represents a three dimensional mathematical vector (signed integers).
///
[Serializable]
- [TypeConverter(typeof(TypeConverters.Int3Converter))]
+#if FLAX_EDITOR
+ [System.ComponentModel.TypeConverter(typeof(TypeConverters.Int3Converter))]
+#endif
partial struct Int3 : IEquatable, IFormattable
{
private static readonly string _formatString = "X:{0} Y:{1} Z:{2}";
diff --git a/Source/Engine/Core/Math/Int4.cs b/Source/Engine/Core/Math/Int4.cs
index f3ff7d05a..451197cfe 100644
--- a/Source/Engine/Core/Math/Int4.cs
+++ b/Source/Engine/Core/Math/Int4.cs
@@ -1,7 +1,6 @@
// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.
using System;
-using System.ComponentModel;
using System.Globalization;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
@@ -12,7 +11,9 @@ namespace FlaxEngine
/// Represents a four dimensional mathematical vector (signed integers).
///
[Serializable]
- [TypeConverter(typeof(TypeConverters.Int4Converter))]
+#if FLAX_EDITOR
+ [System.ComponentModel.TypeConverter(typeof(TypeConverters.Int4Converter))]
+#endif
partial struct Int4 : IEquatable, IFormattable
{
private static readonly string _formatString = "X:{0} Y:{1} Z:{2} W:{3}";
diff --git a/Source/Engine/Core/Math/Quaternion.cs b/Source/Engine/Core/Math/Quaternion.cs
index 5fccd5941..ff50a98bf 100644
--- a/Source/Engine/Core/Math/Quaternion.cs
+++ b/Source/Engine/Core/Math/Quaternion.cs
@@ -50,7 +50,6 @@
*/
using System;
-using System.ComponentModel;
using System.Globalization;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
@@ -58,7 +57,9 @@ using System.Runtime.InteropServices;
namespace FlaxEngine
{
[Serializable]
- [TypeConverter(typeof(TypeConverters.QuaternionConverter))]
+#if FLAX_EDITOR
+ [System.ComponentModel.TypeConverter(typeof(TypeConverters.QuaternionConverter))]
+#endif
partial struct Quaternion : IEquatable, IFormattable
{
private static readonly string _formatString = "X:{0:F2} Y:{1:F2} Z:{2:F2} W:{3:F2}";
diff --git a/Source/Engine/Core/Math/TypeConverters/ColorConverter.cs b/Source/Engine/Core/Math/TypeConverters/ColorConverter.cs
index a8c0cb766..afd34bbfd 100644
--- a/Source/Engine/Core/Math/TypeConverters/ColorConverter.cs
+++ b/Source/Engine/Core/Math/TypeConverters/ColorConverter.cs
@@ -1,5 +1,6 @@
// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.
+#if FLAX_EDITOR
using System;
using System.ComponentModel;
using System.Globalization;
@@ -18,6 +19,16 @@ namespace FlaxEngine.TypeConverters
return base.CanConvertFrom(context, sourceType);
}
+ ///
+ public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
+ {
+ if (destinationType == typeof(string))
+ {
+ return false;
+ }
+ return base.CanConvertTo(context, destinationType);
+ }
+
///
public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
{
@@ -25,9 +36,9 @@ namespace FlaxEngine.TypeConverters
{
string[] v = str.Split(',');
if (v.Length == 4)
- return new Color(float.Parse(v[0]), float.Parse(v[1]), float.Parse(v[2]), float.Parse(v[3]));
+ return new Color(float.Parse(v[0], culture), float.Parse(v[1], culture), float.Parse(v[2], culture), float.Parse(v[3], culture));
if (v.Length == 3)
- return new Color(float.Parse(v[0]), float.Parse(v[1]), float.Parse(v[2]), 1.0f);
+ return new Color(float.Parse(v[0], culture), float.Parse(v[1], culture), float.Parse(v[2], culture), 1.0f);
throw new FormatException("Invalid Color value format.");
}
return base.ConvertFrom(context, culture, value);
@@ -39,9 +50,10 @@ namespace FlaxEngine.TypeConverters
if (destinationType == typeof(string))
{
var v = (Color)value;
- return v.R + "," + v.G + "," + v.B + "," + v.A;
+ return v.R.ToString(culture) + "," + v.G.ToString(culture) + "," + v.B.ToString(culture) + "," + v.A.ToString(culture);
}
return base.ConvertTo(context, culture, value, destinationType);
}
}
}
+#endif
diff --git a/Source/Engine/Core/Math/TypeConverters/Double2Converter.cs b/Source/Engine/Core/Math/TypeConverters/Double2Converter.cs
index 7afca8e6d..4eebbfce4 100644
--- a/Source/Engine/Core/Math/TypeConverters/Double2Converter.cs
+++ b/Source/Engine/Core/Math/TypeConverters/Double2Converter.cs
@@ -1,5 +1,6 @@
// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.
+#if FLAX_EDITOR
using System;
using System.ComponentModel;
using System.Globalization;
@@ -18,13 +19,23 @@ namespace FlaxEngine.TypeConverters
return base.CanConvertFrom(context, sourceType);
}
+ ///
+ public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
+ {
+ if (destinationType == typeof(string))
+ {
+ return false;
+ }
+ return base.CanConvertTo(context, destinationType);
+ }
+
///
public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
{
if (value is string str)
{
string[] v = str.Split(',');
- return new Double2(double.Parse(v[0]), double.Parse(v[1]));
+ return new Double2(double.Parse(v[0], culture), double.Parse(v[1], culture));
}
return base.ConvertFrom(context, culture, value);
}
@@ -35,9 +46,10 @@ namespace FlaxEngine.TypeConverters
if (destinationType == typeof(string))
{
var v = (Double2)value;
- return v.X + "," + v.Y;
+ return v.X.ToString(culture) + "," + v.Y.ToString(culture);
}
return base.ConvertTo(context, culture, value, destinationType);
}
}
}
+#endif
diff --git a/Source/Engine/Core/Math/TypeConverters/Double3Converter.cs b/Source/Engine/Core/Math/TypeConverters/Double3Converter.cs
index 898f74e8a..420e0016c 100644
--- a/Source/Engine/Core/Math/TypeConverters/Double3Converter.cs
+++ b/Source/Engine/Core/Math/TypeConverters/Double3Converter.cs
@@ -1,5 +1,6 @@
// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.
+#if FLAX_EDITOR
using System;
using System.ComponentModel;
using System.Globalization;
@@ -18,13 +19,23 @@ namespace FlaxEngine.TypeConverters
return base.CanConvertFrom(context, sourceType);
}
+ ///
+ public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
+ {
+ if (destinationType == typeof(string))
+ {
+ return false;
+ }
+ return base.CanConvertTo(context, destinationType);
+ }
+
///
public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
{
if (value is string str)
{
string[] v = str.Split(',');
- return new Double3(double.Parse(v[0]), double.Parse(v[1]), double.Parse(v[2]));
+ return new Double3(double.Parse(v[0], culture), double.Parse(v[1], culture), double.Parse(v[2], culture));
}
return base.ConvertFrom(context, culture, value);
}
@@ -35,9 +46,10 @@ namespace FlaxEngine.TypeConverters
if (destinationType == typeof(string))
{
var v = (Double3)value;
- return v.X + "," + v.Y + "," + v.Z;
+ return v.X.ToString(culture) + "," + v.Y.ToString(culture) + "," + v.Z.ToString(culture);
}
return base.ConvertTo(context, culture, value, destinationType);
}
}
}
+#endif
diff --git a/Source/Engine/Core/Math/TypeConverters/Double4Converter.cs b/Source/Engine/Core/Math/TypeConverters/Double4Converter.cs
index ad3593178..fc1d9a7fe 100644
--- a/Source/Engine/Core/Math/TypeConverters/Double4Converter.cs
+++ b/Source/Engine/Core/Math/TypeConverters/Double4Converter.cs
@@ -1,5 +1,6 @@
// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.
+#if FLAX_EDITOR
using System;
using System.ComponentModel;
using System.Globalization;
@@ -18,13 +19,23 @@ namespace FlaxEngine.TypeConverters
return base.CanConvertFrom(context, sourceType);
}
+ ///
+ public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
+ {
+ if (destinationType == typeof(string))
+ {
+ return false;
+ }
+ return base.CanConvertTo(context, destinationType);
+ }
+
///
public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
{
if (value is string str)
{
string[] v = str.Split(',');
- return new Double4(double.Parse(v[0]), double.Parse(v[1]), double.Parse(v[2]), double.Parse(v[3]));
+ return new Double4(double.Parse(v[0], culture), double.Parse(v[1], culture), double.Parse(v[2], culture), double.Parse(v[3], culture));
}
return base.ConvertFrom(context, culture, value);
}
@@ -35,9 +46,10 @@ namespace FlaxEngine.TypeConverters
if (destinationType == typeof(string))
{
var v = (Double4)value;
- return v.X + "," + v.Y + "," + v.Z + "," + v.W;
+ return v.X.ToString(culture) + "," + v.Y.ToString(culture) + "," + v.Z.ToString(culture) + "," + v.W.ToString(culture);
}
return base.ConvertTo(context, culture, value, destinationType);
}
}
}
+#endif
diff --git a/Source/Engine/Core/Math/TypeConverters/Float2Converter.cs b/Source/Engine/Core/Math/TypeConverters/Float2Converter.cs
index 415b63cbd..a41a0f4d5 100644
--- a/Source/Engine/Core/Math/TypeConverters/Float2Converter.cs
+++ b/Source/Engine/Core/Math/TypeConverters/Float2Converter.cs
@@ -1,5 +1,6 @@
// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.
+#if FLAX_EDITOR
using System;
using System.ComponentModel;
using System.Globalization;
@@ -18,13 +19,23 @@ namespace FlaxEngine.TypeConverters
return base.CanConvertFrom(context, sourceType);
}
+ ///
+ public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
+ {
+ if (destinationType == typeof(string))
+ {
+ return false;
+ }
+ return base.CanConvertTo(context, destinationType);
+ }
+
///
public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
{
if (value is string str)
{
string[] v = str.Split(',');
- return new Float2(float.Parse(v[0]), float.Parse(v[1]));
+ return new Float2(float.Parse(v[0], culture), float.Parse(v[1], culture));
}
return base.ConvertFrom(context, culture, value);
}
@@ -35,9 +46,10 @@ namespace FlaxEngine.TypeConverters
if (destinationType == typeof(string))
{
var v = (Float2)value;
- return v.X + "," + v.Y;
+ return v.X.ToString(culture) + "," + v.Y.ToString(culture);
}
return base.ConvertTo(context, culture, value, destinationType);
}
}
}
+#endif
diff --git a/Source/Engine/Core/Math/TypeConverters/Float3Converter.cs b/Source/Engine/Core/Math/TypeConverters/Float3Converter.cs
index ef64662fb..aded4117e 100644
--- a/Source/Engine/Core/Math/TypeConverters/Float3Converter.cs
+++ b/Source/Engine/Core/Math/TypeConverters/Float3Converter.cs
@@ -1,5 +1,6 @@
// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.
+#if FLAX_EDITOR
using System;
using System.ComponentModel;
using System.Globalization;
@@ -18,13 +19,23 @@ namespace FlaxEngine.TypeConverters
return base.CanConvertFrom(context, sourceType);
}
+ ///
+ public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
+ {
+ if (destinationType == typeof(string))
+ {
+ return false;
+ }
+ return base.CanConvertTo(context, destinationType);
+ }
+
///
public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
{
if (value is string str)
{
string[] v = str.Split(',');
- return new Float3(float.Parse(v[0]), float.Parse(v[1]), float.Parse(v[2]));
+ return new Float3(float.Parse(v[0], culture), float.Parse(v[1], culture), float.Parse(v[2], culture));
}
return base.ConvertFrom(context, culture, value);
}
@@ -35,9 +46,10 @@ namespace FlaxEngine.TypeConverters
if (destinationType == typeof(string))
{
var v = (Float3)value;
- return v.X + "," + v.Y + "," + v.Z;
+ return v.X.ToString(culture) + "," + v.Y.ToString(culture) + "," + v.Z.ToString(culture);
}
return base.ConvertTo(context, culture, value, destinationType);
}
}
}
+#endif
diff --git a/Source/Engine/Core/Math/TypeConverters/Float4Converter.cs b/Source/Engine/Core/Math/TypeConverters/Float4Converter.cs
index 79da8d765..58c76ac65 100644
--- a/Source/Engine/Core/Math/TypeConverters/Float4Converter.cs
+++ b/Source/Engine/Core/Math/TypeConverters/Float4Converter.cs
@@ -1,5 +1,6 @@
// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.
+#if FLAX_EDITOR
using System;
using System.ComponentModel;
using System.Globalization;
@@ -18,13 +19,23 @@ namespace FlaxEngine.TypeConverters
return base.CanConvertFrom(context, sourceType);
}
+ ///
+ public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
+ {
+ if (destinationType == typeof(string))
+ {
+ return false;
+ }
+ return base.CanConvertTo(context, destinationType);
+ }
+
///
public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
{
if (value is string str)
{
string[] v = str.Split(',');
- return new Float4(float.Parse(v[0]), float.Parse(v[1]), float.Parse(v[2]), float.Parse(v[3]));
+ return new Float4(float.Parse(v[0], culture), float.Parse(v[1], culture), float.Parse(v[2], culture), float.Parse(v[3], culture));
}
return base.ConvertFrom(context, culture, value);
}
@@ -35,9 +46,10 @@ namespace FlaxEngine.TypeConverters
if (destinationType == typeof(string))
{
var v = (Float4)value;
- return v.X + "," + v.Y + "," + v.Z + "," + v.W;
+ return v.X.ToString(culture) + "," + v.Y.ToString(culture) + "," + v.Z.ToString(culture) + "," + v.W.ToString(culture);
}
return base.ConvertTo(context, culture, value, destinationType);
}
}
}
+#endif
diff --git a/Source/Engine/Core/Math/TypeConverters/Int2Converter.cs b/Source/Engine/Core/Math/TypeConverters/Int2Converter.cs
index ee4e6bf38..c4989c085 100644
--- a/Source/Engine/Core/Math/TypeConverters/Int2Converter.cs
+++ b/Source/Engine/Core/Math/TypeConverters/Int2Converter.cs
@@ -1,5 +1,6 @@
// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.
+#if FLAX_EDITOR
using System;
using System.ComponentModel;
using System.Globalization;
@@ -18,13 +19,23 @@ namespace FlaxEngine.TypeConverters
return base.CanConvertFrom(context, sourceType);
}
+ ///
+ public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
+ {
+ if (destinationType == typeof(string))
+ {
+ return false;
+ }
+ return base.CanConvertTo(context, destinationType);
+ }
+
///
public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
{
if (value is string str)
{
string[] v = str.Split(',');
- return new Int2(int.Parse(v[0]), int.Parse(v[1]));
+ return new Int2(int.Parse(v[0], culture), int.Parse(v[1], culture));
}
return base.ConvertFrom(context, culture, value);
}
@@ -35,9 +46,10 @@ namespace FlaxEngine.TypeConverters
if (destinationType == typeof(string))
{
var v = (Int2)value;
- return v.X + "," + v.Y;
+ return v.X.ToString(culture) + "," + v.Y.ToString(culture);
}
return base.ConvertTo(context, culture, value, destinationType);
}
}
}
+#endif
diff --git a/Source/Engine/Core/Math/TypeConverters/Int3Converter.cs b/Source/Engine/Core/Math/TypeConverters/Int3Converter.cs
index 0b6f74a5b..fe01f91fd 100644
--- a/Source/Engine/Core/Math/TypeConverters/Int3Converter.cs
+++ b/Source/Engine/Core/Math/TypeConverters/Int3Converter.cs
@@ -1,5 +1,6 @@
// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.
+#if FLAX_EDITOR
using System;
using System.ComponentModel;
using System.Globalization;
@@ -18,13 +19,23 @@ namespace FlaxEngine.TypeConverters
return base.CanConvertFrom(context, sourceType);
}
+ ///
+ public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
+ {
+ if (destinationType == typeof(string))
+ {
+ return false;
+ }
+ return base.CanConvertTo(context, destinationType);
+ }
+
///
public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
{
if (value is string str)
{
string[] v = str.Split(',');
- return new Int3(int.Parse(v[0]), int.Parse(v[1]), int.Parse(v[2]));
+ return new Int3(int.Parse(v[0], culture), int.Parse(v[1], culture), int.Parse(v[2], culture));
}
return base.ConvertFrom(context, culture, value);
}
@@ -35,9 +46,10 @@ namespace FlaxEngine.TypeConverters
if (destinationType == typeof(string))
{
var v = (Int3)value;
- return v.X + "," + v.Y + "," + v.Z;
+ return v.X.ToString(culture) + "," + v.Y.ToString(culture) + "," + v.Z.ToString(culture);
}
return base.ConvertTo(context, culture, value, destinationType);
}
}
}
+#endif
diff --git a/Source/Engine/Core/Math/TypeConverters/Int4Converter.cs b/Source/Engine/Core/Math/TypeConverters/Int4Converter.cs
index 71d19dc4b..2ce0fc202 100644
--- a/Source/Engine/Core/Math/TypeConverters/Int4Converter.cs
+++ b/Source/Engine/Core/Math/TypeConverters/Int4Converter.cs
@@ -1,5 +1,6 @@
// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.
+#if FLAX_EDITOR
using System;
using System.ComponentModel;
using System.Globalization;
@@ -18,13 +19,23 @@ namespace FlaxEngine.TypeConverters
return base.CanConvertFrom(context, sourceType);
}
+ ///
+ public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
+ {
+ if (destinationType == typeof(string))
+ {
+ return false;
+ }
+ return base.CanConvertTo(context, destinationType);
+ }
+
///
public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
{
if (value is string str)
{
string[] v = str.Split(',');
- return new Int4(int.Parse(v[0]), int.Parse(v[1]), int.Parse(v[2]), int.Parse(v[3]));
+ return new Int4(int.Parse(v[0], culture), int.Parse(v[1], culture), int.Parse(v[2], culture), int.Parse(v[3], culture));
}
return base.ConvertFrom(context, culture, value);
}
@@ -35,9 +46,10 @@ namespace FlaxEngine.TypeConverters
if (destinationType == typeof(string))
{
var v = (Int4)value;
- return v.X + "," + v.Y + "," + v.Z + "," + v.W;
+ return v.X.ToString(culture) + "," + v.Y.ToString(culture) + "," + v.Z.ToString(culture) + "," + v.W.ToString(culture);
}
return base.ConvertTo(context, culture, value, destinationType);
}
}
}
+#endif
diff --git a/Source/Engine/Core/Math/TypeConverters/QuaternionConverter.cs b/Source/Engine/Core/Math/TypeConverters/QuaternionConverter.cs
index e8b1ff6a3..23bb901be 100644
--- a/Source/Engine/Core/Math/TypeConverters/QuaternionConverter.cs
+++ b/Source/Engine/Core/Math/TypeConverters/QuaternionConverter.cs
@@ -1,5 +1,6 @@
// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.
+#if FLAX_EDITOR
using System;
using System.ComponentModel;
using System.Globalization;
@@ -18,13 +19,23 @@ namespace FlaxEngine.TypeConverters
return base.CanConvertFrom(context, sourceType);
}
+ ///
+ public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
+ {
+ if (destinationType == typeof(string))
+ {
+ return false;
+ }
+ return base.CanConvertTo(context, destinationType);
+ }
+
///
public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
{
if (value is string str)
{
string[] v = str.Split(',');
- return new Quaternion(float.Parse(v[0]), float.Parse(v[1]), float.Parse(v[2]), float.Parse(v[3]));
+ return new Quaternion(float.Parse(v[0], culture), float.Parse(v[1], culture), float.Parse(v[2], culture), float.Parse(v[3], culture));
}
return base.ConvertFrom(context, culture, value);
}
@@ -35,9 +46,10 @@ namespace FlaxEngine.TypeConverters
if (destinationType == typeof(string))
{
var v = (Quaternion)value;
- return v.X + "," + v.Y + "," + v.Z + "," + v.W;
+ return v.X.ToString(culture) + "," + v.Y.ToString(culture) + "," + v.Z.ToString(culture) + "," + v.W.ToString(culture);
}
return base.ConvertTo(context, culture, value, destinationType);
}
}
}
+#endif
diff --git a/Source/Engine/Core/Math/TypeConverters/Vector2Converter.cs b/Source/Engine/Core/Math/TypeConverters/Vector2Converter.cs
index 1eef270ee..96d6beadc 100644
--- a/Source/Engine/Core/Math/TypeConverters/Vector2Converter.cs
+++ b/Source/Engine/Core/Math/TypeConverters/Vector2Converter.cs
@@ -1,5 +1,6 @@
// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.
+#if FLAX_EDITOR
using System;
using System.ComponentModel;
using System.Globalization;
@@ -18,13 +19,23 @@ namespace FlaxEngine.TypeConverters
return base.CanConvertFrom(context, sourceType);
}
+ ///
+ public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
+ {
+ if (destinationType == typeof(string))
+ {
+ return false;
+ }
+ return base.CanConvertTo(context, destinationType);
+ }
+
///
public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
{
if (value is string str)
{
string[] v = str.Split(',');
- return new Vector2(float.Parse(v[0]), float.Parse(v[1]));
+ return new Vector2(float.Parse(v[0], culture), float.Parse(v[1], culture));
}
return base.ConvertFrom(context, culture, value);
}
@@ -35,9 +46,10 @@ namespace FlaxEngine.TypeConverters
if (destinationType == typeof(string))
{
var v = (Vector2)value;
- return v.X + "," + v.Y;
+ return v.X.ToString(culture) + "," + v.Y.ToString(culture);
}
return base.ConvertTo(context, culture, value, destinationType);
}
}
}
+#endif
diff --git a/Source/Engine/Core/Math/TypeConverters/Vector3Converter.cs b/Source/Engine/Core/Math/TypeConverters/Vector3Converter.cs
index 398a85811..23ee4df11 100644
--- a/Source/Engine/Core/Math/TypeConverters/Vector3Converter.cs
+++ b/Source/Engine/Core/Math/TypeConverters/Vector3Converter.cs
@@ -1,5 +1,6 @@
// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.
+#if FLAX_EDITOR
using System;
using System.ComponentModel;
using System.Globalization;
@@ -18,13 +19,23 @@ namespace FlaxEngine.TypeConverters
return base.CanConvertFrom(context, sourceType);
}
+ ///
+ public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
+ {
+ if (destinationType == typeof(string))
+ {
+ return false;
+ }
+ return base.CanConvertTo(context, destinationType);
+ }
+
///
public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
{
if (value is string str)
{
string[] v = str.Split(',');
- return new Vector3(float.Parse(v[0]), float.Parse(v[1]), float.Parse(v[2]));
+ return new Vector3(float.Parse(v[0], culture), float.Parse(v[1], culture), float.Parse(v[2], culture));
}
return base.ConvertFrom(context, culture, value);
}
@@ -35,9 +46,10 @@ namespace FlaxEngine.TypeConverters
if (destinationType == typeof(string))
{
var v = (Vector3)value;
- return v.X + "," + v.Y + "," + v.Z;
+ return v.X.ToString(culture) + "," + v.Y.ToString(culture) + "," + v.Z.ToString(culture);
}
return base.ConvertTo(context, culture, value, destinationType);
}
}
}
+#endif
diff --git a/Source/Engine/Core/Math/TypeConverters/Vector4Converter.cs b/Source/Engine/Core/Math/TypeConverters/Vector4Converter.cs
index 75cc264af..c3b4d074b 100644
--- a/Source/Engine/Core/Math/TypeConverters/Vector4Converter.cs
+++ b/Source/Engine/Core/Math/TypeConverters/Vector4Converter.cs
@@ -1,5 +1,6 @@
// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.
+#if FLAX_EDITOR
using System;
using System.ComponentModel;
using System.Globalization;
@@ -18,13 +19,23 @@ namespace FlaxEngine.TypeConverters
return base.CanConvertFrom(context, sourceType);
}
+ ///
+ public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
+ {
+ if (destinationType == typeof(string))
+ {
+ return false;
+ }
+ return base.CanConvertTo(context, destinationType);
+ }
+
///
public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
{
if (value is string str)
{
string[] v = str.Split(',');
- return new Vector4(float.Parse(v[0]), float.Parse(v[1]), float.Parse(v[2]), float.Parse(v[3]));
+ return new Vector4(float.Parse(v[0], culture), float.Parse(v[1], culture), float.Parse(v[2], culture), float.Parse(v[3], culture));
}
return base.ConvertFrom(context, culture, value);
}
@@ -35,9 +46,10 @@ namespace FlaxEngine.TypeConverters
if (destinationType == typeof(string))
{
var v = (Vector4)value;
- return v.X + "," + v.Y + "," + v.Z + "," + v.W;
+ return v.X.ToString(culture) + "," + v.Y.ToString(culture) + "," + v.Z.ToString(culture) + "," + v.W.ToString(culture);
}
return base.ConvertTo(context, culture, value, destinationType);
}
}
}
+#endif
diff --git a/Source/Engine/Core/Math/Vector2.cs b/Source/Engine/Core/Math/Vector2.cs
index 730988556..2af365638 100644
--- a/Source/Engine/Core/Math/Vector2.cs
+++ b/Source/Engine/Core/Math/Vector2.cs
@@ -57,7 +57,6 @@ using Mathr = FlaxEngine.Mathf;
* THE SOFTWARE.
*/
using System;
-using System.ComponentModel;
using System.Globalization;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
@@ -70,7 +69,9 @@ namespace FlaxEngine
[Unmanaged]
[Serializable]
[StructLayout(LayoutKind.Sequential)]
- [TypeConverter(typeof(TypeConverters.Vector2Converter))]
+#if FLAX_EDITOR
+ [System.ComponentModel.TypeConverter(typeof(TypeConverters.Vector2Converter))]
+#endif
public unsafe partial struct Vector2 : IEquatable, IFormattable
{
private static readonly string _formatString = "X:{0:F2} Y:{1:F2}";
diff --git a/Source/Engine/Core/Math/Vector3.cs b/Source/Engine/Core/Math/Vector3.cs
index d80a6a87f..4442d3334 100644
--- a/Source/Engine/Core/Math/Vector3.cs
+++ b/Source/Engine/Core/Math/Vector3.cs
@@ -57,7 +57,6 @@ using Mathr = FlaxEngine.Mathf;
* THE SOFTWARE.
*/
using System;
-using System.ComponentModel;
using System.Globalization;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
@@ -70,7 +69,9 @@ namespace FlaxEngine
[Unmanaged]
[Serializable]
[StructLayout(LayoutKind.Sequential)]
- [TypeConverter(typeof(TypeConverters.Vector3Converter))]
+#if FLAX_EDITOR
+ [System.ComponentModel.TypeConverter(typeof(TypeConverters.Vector3Converter))]
+#endif
public unsafe partial struct Vector3 : IEquatable, IFormattable
{
private static readonly string _formatString = "X:{0:F2} Y:{1:F2} Z:{2:F2}";
diff --git a/Source/Engine/Core/Math/Vector4.cs b/Source/Engine/Core/Math/Vector4.cs
index 8c83e5d3e..ceeafe88f 100644
--- a/Source/Engine/Core/Math/Vector4.cs
+++ b/Source/Engine/Core/Math/Vector4.cs
@@ -57,7 +57,6 @@ using Mathr = FlaxEngine.Mathf;
* THE SOFTWARE.
*/
using System;
-using System.ComponentModel;
using System.Globalization;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
@@ -70,7 +69,9 @@ namespace FlaxEngine
[Unmanaged]
[Serializable]
[StructLayout(LayoutKind.Sequential)]
- [TypeConverter(typeof(TypeConverters.Vector4Converter))]
+#if FLAX_EDITOR
+ [System.ComponentModel.TypeConverter(typeof(TypeConverters.Vector4Converter))]
+#endif
public partial struct Vector4 : IEquatable, IFormattable
{
private static readonly string _formatString = "X:{0:F2} Y:{1:F2} Z:{2:F2} W:{3:F2}";
diff --git a/Source/Engine/Core/Types/DateTime.h b/Source/Engine/Core/Types/DateTime.h
index 13a2261c8..67b2d70ff 100644
--- a/Source/Engine/Core/Types/DateTime.h
+++ b/Source/Engine/Core/Types/DateTime.h
@@ -309,7 +309,7 @@ namespace fmt
{
int32 year, month, day;
v.GetDate(year, month, day);
- return format_to(ctx.out(), TEXT("{0}-{1:0>2}-{2:0>2} {3:0>2}:{4:0>2}:{5:0>2}"), year, month, day, v.GetHour(), v.GetMinute(), v.GetSecond());
+ return fmt::format_to(ctx.out(), basic_string_view(TEXT("{0}-{1:0>2}-{2:0>2} {3:0>2}:{4:0>2}:{5:0>2}")), year, month, day, v.GetHour(), v.GetMinute(), v.GetSecond());
}
};
}
diff --git a/Source/Engine/Core/Types/String.h b/Source/Engine/Core/Types/String.h
index a58dbf454..8728876a1 100644
--- a/Source/Engine/Core/Types/String.h
+++ b/Source/Engine/Core/Types/String.h
@@ -17,6 +17,8 @@ protected:
int32 _length = 0;
public:
+ typedef T CharType;
+
///
/// Finalizes an instance of the class.
///
@@ -1237,7 +1239,7 @@ namespace fmt
template
auto format(const String& v, FormatContext& ctx) -> decltype(ctx.out())
{
- return fmt::internal::copy(v.Get(), v.Get() + v.Length(), ctx.out());
+ return fmt::detail::copy_str(v.Get(), v.Get() + v.Length(), ctx.out());
}
};
}
@@ -1808,7 +1810,7 @@ namespace fmt
template
auto format(const StringAnsi& v, FormatContext& ctx) -> decltype(ctx.out())
{
- return fmt::internal::copy(v.Get(), v.Get() + v.Length(), ctx.out());
+ return fmt::detail::copy_str(v.Get(), v.Get() + v.Length(), ctx.out());
}
};
}
diff --git a/Source/Engine/Core/Types/StringBuilder.h b/Source/Engine/Core/Types/StringBuilder.h
index 1df7fc4a2..39c1b3a84 100644
--- a/Source/Engine/Core/Types/StringBuilder.h
+++ b/Source/Engine/Core/Types/StringBuilder.h
@@ -284,7 +284,7 @@ namespace fmt
template
auto format(const String& v, FormatContext& ctx) -> decltype(ctx.out())
{
- return fmt::internal::copy(v.Get(), v.Get() + v.Length(), ctx.out());
+ return fmt::detail::copy_str(v.Get(), v.Get() + v.Length(), ctx.out());
}
};
}
diff --git a/Source/Engine/Core/Types/StringView.h b/Source/Engine/Core/Types/StringView.h
index 1c841070e..27c63c999 100644
--- a/Source/Engine/Core/Types/StringView.h
+++ b/Source/Engine/Core/Types/StringView.h
@@ -29,6 +29,8 @@ protected:
}
public:
+ typedef T CharType;
+
///
/// Gets the specific const character from this string.
///
@@ -381,7 +383,7 @@ namespace fmt
template
auto format(const StringView& v, FormatContext& ctx) -> decltype(ctx.out())
{
- return fmt::internal::copy(v.Get(), v.Get() + v.Length(), ctx.out());
+ return fmt::detail::copy_str(v.Get(), v.Get() + v.Length(), ctx.out());
}
};
}
@@ -551,7 +553,7 @@ namespace fmt
template
auto format(const StringAnsiView& v, FormatContext& ctx) -> decltype(ctx.out())
{
- return fmt::internal::copy(v.Get(), v.Get() + v.Length(), ctx.out());
+ return fmt::detail::copy_str(v.Get(), v.Get() + v.Length(), ctx.out());
}
};
}
diff --git a/Source/Engine/Core/Types/Variant.cpp b/Source/Engine/Core/Types/Variant.cpp
index d46b4f8a0..33ffde349 100644
--- a/Source/Engine/Core/Types/Variant.cpp
+++ b/Source/Engine/Core/Types/Variant.cpp
@@ -23,12 +23,10 @@
#include "Engine/Scripting/ScriptingObject.h"
#include "Engine/Scripting/ManagedCLR/MClass.h"
#include "Engine/Scripting/ManagedCLR/MCore.h"
+#include "Engine/Scripting/ManagedCLR/MCore.h"
#include "Engine/Scripting/ManagedCLR/MUtils.h"
#include "Engine/Utilities/Crc.h"
#include "Engine/Utilities/StringConverter.h"
-#if USE_MONO
-#include
-#endif
namespace
{
@@ -108,15 +106,14 @@ VariantType::VariantType(Types type, const StringAnsiView& typeName)
}
}
-VariantType::VariantType(Types type, _MonoClass* klass)
+VariantType::VariantType(Types type, MClass* klass)
{
Type = type;
TypeName = nullptr;
-#if USE_MONO
+#if USE_CSHARP
if (klass)
{
- MString typeName;
- MUtils::GetClassFullname(klass, typeName);
+ const StringAnsi& typeName = klass->GetFullName();
const int32 length = typeName.Length();
TypeName = static_cast(Allocator::Allocate(length + 1));
Platform::MemoryCopy(TypeName, typeName.Get(), length);
@@ -166,7 +163,7 @@ VariantType::VariantType(const StringAnsiView& typeName)
}
// Try using managed class
-#if USE_MONO
+#if USE_CSHARP
if (const auto mclass = Scripting::FindClass(typeName))
{
new(this) VariantType(ManagedObject, typeName);
@@ -615,17 +612,21 @@ Variant::Variant(Asset* v)
}
}
-#if USE_MONO
+#if USE_CSHARP
-Variant::Variant(_MonoObject* v)
- : Type(VariantType::ManagedObject, v ? mono_object_get_class(v) : nullptr)
+Variant::Variant(MObject* v)
+ : Type(VariantType::ManagedObject, v ? MCore::Object::GetClass(v) : nullptr)
{
- AsUint = v ? mono_gchandle_new(v, true) : 0;
+#if USE_NETCORE
+ AsUint64 = v ? MCore::GCHandle::New(v) : 0;
+#else
+ AsUint = v ? MCore::GCHandle::New(v) : 0;
+#endif
}
#else
-Variant::Variant(_MonoObject* v)
+Variant::Variant(MObject* v)
: Type(VariantType::ManagedObject, nullptr)
{
AsUint = 0;
@@ -958,11 +959,14 @@ Variant::~Variant()
Delete(AsDictionary);
break;
case VariantType::ManagedObject:
-#if USE_MONO
+#if USE_NETCORE
+ if (AsUint64)
+ MCore::GCHandle::Free(AsUint64);
+#elif USE_MONO
if (AsUint)
- mono_gchandle_free(AsUint);
- break;
+ MCore::GCHandle::Free(AsUint);
#endif
+ break;
default: ;
}
}
@@ -1089,8 +1093,10 @@ Variant& Variant::operator=(const Variant& other)
AsDictionary = New>(*other.AsDictionary);
break;
case VariantType::ManagedObject:
-#if USE_MONO
- AsUint = other.AsUint ? mono_gchandle_new(mono_gchandle_get_target(other.AsUint), true) : 0;
+#if USE_NETCORE
+ AsUint64 = other.AsUint64 ? MCore::GCHandle::New(MCore::GCHandle::GetTarget(other.AsUint64)) : 0;
+#elif USE_MONO
+ AsUint = other.AsUint ? MCore::GCHandle::New(MCore::GCHandle::GetTarget(other.AsUint)) : 0;
#endif
break;
case VariantType::Null:
@@ -1216,9 +1222,13 @@ bool Variant::operator==(const Variant& other) const
return false;
return AsBlob.Length == other.AsBlob.Length && StringUtils::Compare(static_cast(AsBlob.Data), static_cast(other.AsBlob.Data), AsBlob.Length - 1) == 0;
case VariantType::ManagedObject:
-#if USE_MONO
+#if USE_NETCORE
// TODO: invoke C# Equality logic?
- return AsUint == other.AsUint || mono_gchandle_get_target(AsUint) == mono_gchandle_get_target(other.AsUint);
+ return AsUint64 == other.AsUint64 || MCore::GCHandle::GetTarget(AsUint64) == MCore::GCHandle::GetTarget(other.AsUint64);
+#elif USE_MONO
+ return AsUint == other.AsUint || MCore::GCHandle::GetTarget(AsUint) == MCore::GCHandle::GetTarget(other.AsUint);
+#else
+ return false;
#endif
default:
return false;
@@ -1309,8 +1319,12 @@ Variant::operator bool() const
case VariantType::Asset:
return AsAsset != nullptr;
case VariantType::ManagedObject:
-#if USE_MONO
- return AsUint != 0 && mono_gchandle_get_target(AsUint) != nullptr;
+#if USE_NETCORE
+ return AsUint64 != 0 && MCore::GCHandle::GetTarget(AsUint64) != nullptr;
+#elif USE_MONO
+ return AsUint != 0 && MCore::GCHandle::GetTarget(AsUint) != nullptr;
+#else
+ return false;
#endif
default:
return false;
@@ -1579,8 +1593,12 @@ Variant::operator void*() const
case VariantType::Blob:
return AsBlob.Data;
case VariantType::ManagedObject:
-#if USE_MONO
- return AsUint ? mono_gchandle_get_target(AsUint) : nullptr;
+#if USE_NETCORE
+ return AsUint64 ? MCore::GCHandle::GetTarget(AsUint64) : nullptr;
+#elif USE_MONO
+ return AsUint ? MCore::GCHandle::GetTarget(AsUint) : nullptr;
+#else
+ return nullptr;
#endif
default:
return nullptr;
@@ -1622,10 +1640,12 @@ Variant::operator ScriptingObject*() const
}
}
-Variant::operator _MonoObject*() const
+Variant::operator MObject*() const
{
-#if USE_MONO
- return Type.Type == VariantType::ManagedObject && AsUint ? mono_gchandle_get_target(AsUint) : nullptr;
+#if USE_NETCORE
+ return Type.Type == VariantType::ManagedObject && AsUint64 ? MCore::GCHandle::GetTarget(AsUint64) : nullptr;
+#elif USE_MONO
+ return Type.Type == VariantType::ManagedObject && AsUint ? MCore::GCHandle::GetTarget(AsUint) : nullptr;
#else
return nullptr;
#endif
@@ -2338,9 +2358,12 @@ void Variant::SetType(const VariantType& type)
Delete(AsDictionary);
break;
case VariantType::ManagedObject:
-#if USE_MONO
+#if USE_NETCORE
+ if (AsUint64)
+ MCore::GCHandle::Free(AsUint64);
+#elif USE_MONO
if (AsUint)
- mono_gchandle_free(AsUint);
+ MCore::GCHandle::Free(AsUint);
#endif
break;
default: ;
@@ -2448,9 +2471,12 @@ void Variant::SetType(VariantType&& type)
Delete(AsDictionary);
break;
case VariantType::ManagedObject:
-#if USE_MONO
+#if USE_NETCORE
+ if (AsUint64)
+ MCore::GCHandle::Free(AsUint64);
+#elif USE_MONO
if (AsUint)
- mono_gchandle_free(AsUint);
+ MCore::GCHandle::Free(AsUint);
#endif
break;
default: ;
@@ -2626,14 +2652,18 @@ void Variant::SetObject(ScriptingObject* object)
object->Deleted.Bind(this);
}
-void Variant::SetManagedObject(_MonoObject* object)
+void Variant::SetManagedObject(MObject* object)
{
-#if USE_MONO
+#if USE_CSHARP
if (object)
{
if (Type.Type != VariantType::ManagedObject)
- SetType(VariantType(VariantType::ManagedObject, mono_object_get_class(object)));
- AsUint = mono_gchandle_new(object, true);
+ SetType(VariantType(VariantType::ManagedObject, MCore::Object::GetClass(object)));
+#if USE_NETCORE
+ AsUint64 = MCore::GCHandle::New(object);
+#else
+ AsUint = MCore::GCHandle::New(object);
+#endif
}
else
{
@@ -2752,8 +2782,12 @@ String Variant::ToString() const
case VariantType::Typename:
return String((const char*)AsBlob.Data, AsBlob.Length ? AsBlob.Length - 1 : 0);
case VariantType::ManagedObject:
-#if USE_MONO
- return AsUint ? String(MUtils::ToString(mono_object_to_string(mono_gchandle_get_target(AsUint), nullptr))) : TEXT("null");
+#if USE_NETCORE
+ return AsUint64 ? String(MUtils::ToString(MCore::Object::ToString(MCore::GCHandle::GetTarget(AsUint64)))) : TEXT("null");
+#elif USE_MONO
+ return AsUint ? String(MUtils::ToString(MCore::Object::ToString(MCore::GCHandle::GetTarget(AsUint)))) : TEXT("null");
+#else
+ return String::Empty;
#endif
default:
return String::Empty;
@@ -3656,23 +3690,28 @@ void Variant::AllocStructure()
AsBlob.Data = Allocator::Allocate(AsBlob.Length);
*((int16*)AsBlob.Data) = 0;
}
-#if USE_MONO
+#if USE_CSHARP
else if (const auto mclass = Scripting::FindClass(typeName))
{
// Fallback to C#-only types
- MCore::AttachThread();
- auto instance = mclass->CreateInstance();
+ MCore::Thread::Attach();
+ MObject* instance = mclass->CreateInstance();
if (instance)
{
#if 0
- void* data = mono_object_unbox(instance);
- int32 instanceSize = mono_class_instance_size(mclass->GetNative());
+ void* data = MCore::Object::Unbox(instance);
+ int32 instanceSize = mclass->GetInstanceSize();
AsBlob.Length = instanceSize - (int32)((uintptr)data - (uintptr)instance);
AsBlob.Data = Allocator::Allocate(AsBlob.Length);
Platform::MemoryCopy(AsBlob.Data, data, AsBlob.Length);
#else
Type.Type = VariantType::ManagedObject;
- AsUint = mono_gchandle_new(instance, true);
+
+#if USE_NETCORE
+ AsUint64 = MCore::GCHandle::New(instance);
+#else
+ AsUint = MCore::GCHandle::New(instance);
+#endif
#endif
}
else
@@ -3764,8 +3803,12 @@ uint32 GetHash(const Variant& key)
case VariantType::Typename:
return GetHash((const char*)key.AsBlob.Data);
case VariantType::ManagedObject:
-#if USE_MONO
- return key.AsUint ? (uint32)mono_object_hash(mono_gchandle_get_target(key.AsUint)) : 0;
+#if USE_NETCORE
+ return key.AsUint64 ? (uint32)MCore::Object::GetHashCode(MCore::GCHandle::GetTarget(key.AsUint64)) : 0;
+#elif USE_MONO
+ return key.AsUint ? (uint32)MCore::Object::GetHashCode(MCore::GCHandle::GetTarget(key.AsUint)) : 0;
+#else
+ return 0;
#endif
default:
return 0;
diff --git a/Source/Engine/Core/Types/Variant.h b/Source/Engine/Core/Types/Variant.h
index c50854707..7745307c3 100644
--- a/Source/Engine/Core/Types/Variant.h
+++ b/Source/Engine/Core/Types/Variant.h
@@ -3,10 +3,9 @@
#pragma once
#include "Engine/Core/Types/String.h"
+#include "Engine/Scripting/Types.h"
class Asset;
-class ScriptingObject;
-struct ScriptingType;
struct Transform;
struct CommonValue;
template
@@ -105,7 +104,7 @@ public:
explicit VariantType(Types type, const StringView& typeName);
explicit VariantType(Types type, const StringAnsiView& typeName);
- explicit VariantType(Types type, struct _MonoClass* klass);
+ explicit VariantType(Types type, MClass* klass);
explicit VariantType(const StringAnsiView& typeName);
VariantType(const VariantType& other);
VariantType(VariantType&& other) noexcept;
@@ -215,7 +214,7 @@ public:
Variant(void* v);
Variant(ScriptingObject* v);
Variant(Asset* v);
- Variant(struct _MonoObject* v);
+ Variant(MObject* v);
Variant(const StringView& v);
Variant(const StringAnsiView& v);
Variant(const Char* v);
@@ -296,7 +295,7 @@ public:
explicit operator StringView() const; // Returned StringView, if not empty, is guaranteed to point to a null terminated buffer.
explicit operator StringAnsiView() const; // Returned StringView, if not empty, is guaranteed to point to a null terminated buffer.
explicit operator ScriptingObject*() const;
- explicit operator struct _MonoObject*() const;
+ explicit operator MObject*() const;
explicit operator Asset*() const;
explicit operator Float2() const;
explicit operator Float3() const;
@@ -356,7 +355,7 @@ public:
void SetBlob(int32 length);
void SetBlob(const void* data, int32 length);
void SetObject(ScriptingObject* object);
- void SetManagedObject(struct _MonoObject* object);
+ void SetManagedObject(MObject* object);
void SetAsset(Asset* asset);
String ToString() const;
diff --git a/Source/Engine/Debug/DebugDraw.cpp b/Source/Engine/Debug/DebugDraw.cpp
index 125ffd520..891792d79 100644
--- a/Source/Engine/Debug/DebugDraw.cpp
+++ b/Source/Engine/Debug/DebugDraw.cpp
@@ -454,7 +454,12 @@ inline void DrawText3D(const DebugText3D& t, const RenderContext& renderContext,
{
Matrix w, fw, m;
if (t.FaceCamera)
- Matrix::CreateWorld(t.Transform.Translation, renderContext.View.Direction, viewUp, w);
+ {
+ Matrix s, ss;
+ Matrix::Scaling(t.Transform.Scale.X, s);
+ Matrix::CreateWorld(t.Transform.Translation, renderContext.View.Direction, viewUp, ss);
+ Matrix::Multiply(s, ss, w);
+ }
else
t.Transform.GetWorld(w);
Matrix::Multiply(f, w, fw);
@@ -1995,7 +2000,7 @@ void DebugDraw::DrawText(const StringView& text, const Float2& position, const C
t.TimeLeft = duration;
}
-void DebugDraw::DrawText(const StringView& text, const Vector3& position, const Color& color, int32 size, float duration)
+void DebugDraw::DrawText(const StringView& text, const Vector3& position, const Color& color, int32 size, float duration, float scale)
{
if (text.Length() == 0 || size < 4)
return;
@@ -2005,6 +2010,7 @@ void DebugDraw::DrawText(const StringView& text, const Vector3& position, const
Platform::MemoryCopy(t.Text.Get(), text.Get(), text.Length() * sizeof(Char));
t.Text[text.Length()] = 0;
t.Transform = position - Context->Origin;
+ t.Transform.Scale.X = scale;
t.FaceCamera = true;
t.Size = size;
t.Color = color;
diff --git a/Source/Engine/Debug/DebugDraw.h b/Source/Engine/Debug/DebugDraw.h
index b37080d0f..8cc5452ed 100644
--- a/Source/Engine/Debug/DebugDraw.h
+++ b/Source/Engine/Debug/DebugDraw.h
@@ -590,7 +590,8 @@ API_CLASS(Static) class FLAXENGINE_API DebugDraw
/// The color.
/// The font size.
/// The duration (in seconds). Use 0 to draw it only once.
- API_FUNCTION() static void DrawText(const StringView& text, const Vector3& position, const Color& color, int32 size = 32, float duration = 0.0f);
+ /// The text scale.
+ API_FUNCTION() static void DrawText(const StringView& text, const Vector3& position, const Color& color, int32 size = 32, float duration = 0.0f, float scale = 1.0f);
///
/// Draws the text (3D).
diff --git a/Source/Engine/Debug/DebugLog.cpp b/Source/Engine/Debug/DebugLog.cpp
index d873f4886..da867f0a1 100644
--- a/Source/Engine/Debug/DebugLog.cpp
+++ b/Source/Engine/Debug/DebugLog.cpp
@@ -3,17 +3,15 @@
#include "DebugLog.h"
#include "Engine/Scripting/Scripting.h"
#include "Engine/Scripting/BinaryModule.h"
-#include "Engine/Scripting/MainThreadManagedInvokeAction.h"
+#include "Engine/Scripting/ManagedCLR/MCore.h"
#include "Engine/Scripting/ManagedCLR/MDomain.h"
#include "Engine/Scripting/ManagedCLR/MAssembly.h"
#include "Engine/Scripting/ManagedCLR/MClass.h"
+#include "Engine/Scripting/Internal/MainThreadManagedInvokeAction.h"
#include "Engine/Threading/Threading.h"
#include "FlaxEngine.Gen.h"
-#if USE_MONO
-
-#include
-#include
+#if USE_CSHARP
namespace Impl
{
@@ -68,19 +66,19 @@ bool CacheMethods()
void DebugLog::Log(LogType type, const StringView& message)
{
-#if USE_MONO
+#if USE_CSHARP
if (CacheMethods())
return;
auto scriptsDomain = Scripting::GetScriptsDomain();
MainThreadManagedInvokeAction::ParamsBuilder params;
params.AddParam(type);
- params.AddParam(message, scriptsDomain->GetNative());
+ params.AddParam(message, scriptsDomain);
#if BUILD_RELEASE
- params.AddParam(StringView::Empty, scriptsDomain->GetNative());
+ params.AddParam(StringView::Empty, scriptsDomain);
#else
const String stackTrace = Platform::GetStackTrace(1);
- params.AddParam(stackTrace, scriptsDomain->GetNative());
+ params.AddParam(stackTrace, scriptsDomain);
#endif
MainThreadManagedInvokeAction::Invoke(Internal_SendLog, params);
#endif
@@ -88,7 +86,7 @@ void DebugLog::Log(LogType type, const StringView& message)
void DebugLog::LogException(MObject* exceptionObject)
{
-#if USE_MONO
+#if USE_CSHARP
if (exceptionObject == nullptr || CacheMethods())
return;
@@ -101,11 +99,11 @@ void DebugLog::LogException(MObject* exceptionObject)
String DebugLog::GetStackTrace()
{
String result;
-#if USE_MONO
+#if USE_CSHARP
if (!CacheMethods())
{
auto stackTraceObj = Internal_GetStackTrace->Invoke(nullptr, nullptr, nullptr);
- MUtils::ToString((MonoString*)stackTraceObj, result);
+ MUtils::ToString((MString*)stackTraceObj, result);
}
#endif
return result;
@@ -113,10 +111,9 @@ String DebugLog::GetStackTrace()
void DebugLog::ThrowException(const char* msg)
{
-#if USE_MONO
- // Throw exception to the C# world
- auto ex = mono_exception_from_name_msg(mono_get_corlib(), "System", "Exception", msg);
- mono_raise_exception(ex);
+#if USE_CSHARP
+ auto ex = MCore::Exception::Get(msg);
+ MCore::Exception::Throw(ex);
#endif
}
@@ -125,45 +122,40 @@ void DebugLog::ThrowNullReference()
//LOG(Warning, "Invalid null reference.");
//LOG_STR(Warning, DebugLog::GetStackTrace());
-#if USE_MONO
- // Throw exception to the C# world
- auto ex = mono_get_exception_null_reference();
- mono_raise_exception(ex);
+#if USE_CSHARP
+ auto ex = MCore::Exception::GetNullReference();
+ MCore::Exception::Throw(ex);
#endif
}
void DebugLog::ThrowArgument(const char* arg, const char* msg)
{
-#if USE_MONO
- // Throw exception to the C# world
- auto ex = mono_get_exception_argument(arg, msg);
- mono_raise_exception(ex);
+#if USE_CSHARP
+ auto ex = MCore::Exception::GetArgument(arg, msg);
+ MCore::Exception::Throw(ex);
#endif
}
void DebugLog::ThrowArgumentNull(const char* arg)
{
-#if USE_MONO
- // Throw exception to the C# world
- auto ex = mono_get_exception_argument_null(arg);
- mono_raise_exception(ex);
+#if USE_CSHARP
+ auto ex = MCore::Exception::GetArgumentNull(arg);
+ MCore::Exception::Throw(ex);
#endif
}
void DebugLog::ThrowArgumentOutOfRange(const char* arg)
{
-#if USE_MONO
- // Throw exception to the C# world
- auto ex = mono_get_exception_argument_out_of_range(arg);
- mono_raise_exception(ex);
+#if USE_CSHARP
+ auto ex = MCore::Exception::GetArgumentOutOfRange(arg);
+ MCore::Exception::Throw(ex);
#endif
}
void DebugLog::ThrowNotSupported(const char* msg)
{
-#if USE_MONO
- // Throw exception to the C# world
- auto ex = mono_get_exception_not_supported(msg);
- mono_raise_exception(ex);
+#if USE_CSHARP
+ auto ex = MCore::Exception::GetNotSupported(msg);
+ MCore::Exception::Throw(ex);
#endif
}
diff --git a/Source/Engine/Debug/Exceptions/ArgumentException.h b/Source/Engine/Debug/Exceptions/ArgumentException.h
index 32142f60e..e3f4a1df4 100644
--- a/Source/Engine/Debug/Exceptions/ArgumentException.h
+++ b/Source/Engine/Debug/Exceptions/ArgumentException.h
@@ -24,7 +24,7 @@ namespace Log
///
/// Creates default exception with additional data
///
- /// Additional information that help describe error
+ /// Additional information that help describe error
ArgumentException(const String& additionalInfo)
: Exception(TEXT("One or more of provided arguments are not valid."), additionalInfo)
{
@@ -33,7 +33,8 @@ namespace Log
///
/// Creates default exception with additional data
///
- /// Additional information that help describe error
+ /// Argument name
+ /// Additional information that help describe error
ArgumentException(const String& argumentName, const String& additionalInfo)
: Exception(String::Format(TEXT("Provided argument {0} is not valid."), argumentName), additionalInfo)
{
diff --git a/Source/Engine/Debug/Exceptions/ArgumentNullException.h b/Source/Engine/Debug/Exceptions/ArgumentNullException.h
index ef0ea95d5..dc2d9ea0f 100644
--- a/Source/Engine/Debug/Exceptions/ArgumentNullException.h
+++ b/Source/Engine/Debug/Exceptions/ArgumentNullException.h
@@ -24,7 +24,7 @@ namespace Log
///
/// Creates default exception with additional data
///
- /// Additional information that help describe error
+ /// Additional information that help describe error
ArgumentNullException(const String& additionalInfo)
: Exception(TEXT("One or more of provided arguments is null"), additionalInfo)
{
@@ -33,7 +33,8 @@ namespace Log
///
/// Creates default exception with additional data
///
- /// Additional information that help describe error
+ /// Argument name
+ /// Additional information that help describe error
ArgumentNullException(const String& argumentName, const String& additionalInfo)
: Exception(String::Format(TEXT("Provided argument {0} is null."), argumentName), additionalInfo)
{
diff --git a/Source/Engine/Debug/Exceptions/ArgumentOutOfRangeException.h b/Source/Engine/Debug/Exceptions/ArgumentOutOfRangeException.h
index 011b64bbb..f75576062 100644
--- a/Source/Engine/Debug/Exceptions/ArgumentOutOfRangeException.h
+++ b/Source/Engine/Debug/Exceptions/ArgumentOutOfRangeException.h
@@ -24,7 +24,7 @@ namespace Log
///
/// Creates default exception with additional data
///
- /// Additional information that help describe error
+ /// Additional information that help describe error
ArgumentOutOfRangeException(const String& additionalInfo)
: Exception(TEXT("One or more of provided arguments has index out of range"), additionalInfo)
{
@@ -33,7 +33,8 @@ namespace Log
///
/// Creates default exception with additional data
///
- /// Additional information that help describe error
+ /// Argument name
+ /// Additional information that help describe error
ArgumentOutOfRangeException(const String& argumentName, const String& additionalInfo)
: Exception(String::Format(TEXT("Provided argument {0} is out of range."), argumentName), additionalInfo)
{
diff --git a/Source/Engine/Debug/Exceptions/CLRInnerException.h b/Source/Engine/Debug/Exceptions/CLRInnerException.h
index 919739d57..8a7c5e09e 100644
--- a/Source/Engine/Debug/Exceptions/CLRInnerException.h
+++ b/Source/Engine/Debug/Exceptions/CLRInnerException.h
@@ -3,6 +3,7 @@
#pragma once
#include "Engine/Debug/Exception.h"
+#include "Engine/Scripting/Types.h"
namespace Log
{
@@ -24,15 +25,15 @@ namespace Log
///
/// Creates default exception with additional data
///
- /// Additional information that help describe error
+ /// Additional information that help describe error
CLRInnerException(const String& additionalInfo)
: Exception(String::Format(TEXT("Current {0} CLR method has thrown an inner exception"),
#if USE_MONO
- TEXT("Mono")
-#elif USE_CORECLR
- TEXT(".NET Core")
+ TEXT("Mono")
+#elif USE_NETCORE
+ TEXT(".NET Core")
#else
- TEXT("Unknown engine")
+ TEXT("Unknown engine")
#endif
), additionalInfo)
{
diff --git a/Source/Engine/Debug/Exceptions/DivideByZeroException.h b/Source/Engine/Debug/Exceptions/DivideByZeroException.h
index 3d319df19..cda5a637f 100644
--- a/Source/Engine/Debug/Exceptions/DivideByZeroException.h
+++ b/Source/Engine/Debug/Exceptions/DivideByZeroException.h
@@ -24,7 +24,7 @@ namespace Log
///
/// Creates default exception with additional data
///
- /// Additional information that help describe error
+ /// Additional information that help describe error
DivideByZeroException(const String& additionalInfo)
: Exception(TEXT("Tried to divide value by zero"), additionalInfo)
{
diff --git a/Source/Engine/Debug/Exceptions/FileNotFoundException.h b/Source/Engine/Debug/Exceptions/FileNotFoundException.h
index bcb9463d5..09e0cc3fb 100644
--- a/Source/Engine/Debug/Exceptions/FileNotFoundException.h
+++ b/Source/Engine/Debug/Exceptions/FileNotFoundException.h
@@ -24,7 +24,7 @@ namespace Log
///
/// Creates default exception with additional data
///
- /// Additional information that help describe error
+ /// Additional information that help describe error
FileNotFoundException(const String& additionalInfo)
: Exception(TEXT("File not found"), additionalInfo)
{
diff --git a/Source/Engine/Debug/Exceptions/IOException.h b/Source/Engine/Debug/Exceptions/IOException.h
index f130c7e18..b0fadec6f 100644
--- a/Source/Engine/Debug/Exceptions/IOException.h
+++ b/Source/Engine/Debug/Exceptions/IOException.h
@@ -24,7 +24,7 @@ namespace Log
///
/// Creates default exception with additional data
///
- /// Additional information that help describe error
+ /// Additional information that help describe error
IOException(const String& additionalInfo)
: Exception(TEXT("I/O error occurred."), additionalInfo)
{
diff --git a/Source/Engine/Debug/Exceptions/IndexOutOfRangeException.h b/Source/Engine/Debug/Exceptions/IndexOutOfRangeException.h
index 23511d5ce..445b8df59 100644
--- a/Source/Engine/Debug/Exceptions/IndexOutOfRangeException.h
+++ b/Source/Engine/Debug/Exceptions/IndexOutOfRangeException.h
@@ -24,7 +24,7 @@ namespace Log
///
/// Creates default exception with additional data
///
- /// Additional information that help describe error
+ /// Additional information that help describe error
IndexOutOfRangeException(const String& additionalInfo)
: Exception(TEXT("Index is out of range for items in array"), additionalInfo)
{
diff --git a/Source/Engine/Debug/Exceptions/InvalidOperationException.h b/Source/Engine/Debug/Exceptions/InvalidOperationException.h
index 3194741b1..63299ff82 100644
--- a/Source/Engine/Debug/Exceptions/InvalidOperationException.h
+++ b/Source/Engine/Debug/Exceptions/InvalidOperationException.h
@@ -24,7 +24,7 @@ namespace Log
///
/// Creates default exception with additional data
///
- /// Additional information that help describe error
+ /// Additional information that help describe error
InvalidOperationException(const String& additionalInfo)
: Exception(TEXT("Current object didn't exists or its state was invalid."), additionalInfo)
{
diff --git a/Source/Engine/Debug/Exceptions/NotImplementedException.h b/Source/Engine/Debug/Exceptions/NotImplementedException.h
index 49e330a73..118b57187 100644
--- a/Source/Engine/Debug/Exceptions/NotImplementedException.h
+++ b/Source/Engine/Debug/Exceptions/NotImplementedException.h
@@ -24,7 +24,7 @@ namespace Log
///
/// Creates default exception with additional data
///
- /// Additional information that help describe error
+ /// Additional information that help describe error
NotImplementedException(const String& additionalInfo)
: Exception(TEXT("Current method or operation is not implemented."), additionalInfo)
{
diff --git a/Source/Engine/Debug/Exceptions/NotSupportedException.h b/Source/Engine/Debug/Exceptions/NotSupportedException.h
index 44f692480..c4ecbaa40 100644
--- a/Source/Engine/Debug/Exceptions/NotSupportedException.h
+++ b/Source/Engine/Debug/Exceptions/NotSupportedException.h
@@ -24,7 +24,7 @@ namespace Log
///
/// Creates default exception with additional data
///
- /// Additional information that help describe error
+ /// Additional information that help describe error
NotSupportedException(const String& additionalInfo)
: Exception(TEXT("Current method or operation is not supported in current context"), additionalInfo)
{
diff --git a/Source/Engine/Debug/Exceptions/OverflowException.h b/Source/Engine/Debug/Exceptions/OverflowException.h
index d484d109e..3b158cae3 100644
--- a/Source/Engine/Debug/Exceptions/OverflowException.h
+++ b/Source/Engine/Debug/Exceptions/OverflowException.h
@@ -24,7 +24,7 @@ namespace Log
///
/// Creates default exception with additional data
///
- /// Additional information that help describe error
+ /// Additional information that help describe error
OverflowException(const String& additionalInfo)
: Exception(TEXT("Arithmetic, casting, or conversion operation results in an overflow."), additionalInfo)
{
diff --git a/Source/Engine/Debug/Exceptions/PathTooLongException.h b/Source/Engine/Debug/Exceptions/PathTooLongException.h
index cc350986c..76d270a62 100644
--- a/Source/Engine/Debug/Exceptions/PathTooLongException.h
+++ b/Source/Engine/Debug/Exceptions/PathTooLongException.h
@@ -24,7 +24,7 @@ namespace Log
///
/// Creates default exception with additional data
///
- /// Additional information that help describe error
+ /// Additional information that help describe error
PathTooLongException(const String& additionalInfo)
: Exception(TEXT("Path too long."), additionalInfo)
{
diff --git a/Source/Engine/Debug/Exceptions/PlatformNotSupportedException.h b/Source/Engine/Debug/Exceptions/PlatformNotSupportedException.h
index 46559080b..ad84233e1 100644
--- a/Source/Engine/Debug/Exceptions/PlatformNotSupportedException.h
+++ b/Source/Engine/Debug/Exceptions/PlatformNotSupportedException.h
@@ -24,7 +24,7 @@ namespace Log
///
/// Creates default exception with additional data
///
- /// Additional information that help describe error
+ /// Additional information that help describe error
PlatformNotSupportedException(const String& additionalInfo)
: Exception(TEXT("Method or operation in not supported on current platform."), additionalInfo)
{
diff --git a/Source/Engine/Debug/Exceptions/TimeoutException.h b/Source/Engine/Debug/Exceptions/TimeoutException.h
index 9220e50cf..67c1c64a3 100644
--- a/Source/Engine/Debug/Exceptions/TimeoutException.h
+++ b/Source/Engine/Debug/Exceptions/TimeoutException.h
@@ -24,7 +24,7 @@ namespace Log
///
/// Creates default exception with additional data
///
- /// Additional information that help describe error
+ /// Additional information that help describe error
TimeoutException(const String& additionalInfo)
: Exception(TEXT("Current operation has timed out."), additionalInfo)
{
diff --git a/Source/Engine/Engine/DebugLogHandler.cs b/Source/Engine/Engine/DebugLogHandler.cs
index c29da21cc..d123cbe07 100644
--- a/Source/Engine/Engine/DebugLogHandler.cs
+++ b/Source/Engine/Engine/DebugLogHandler.cs
@@ -4,10 +4,12 @@ using System;
using System.Diagnostics;
using System.Runtime.CompilerServices;
using System.Security;
+using System.Runtime.InteropServices;
+using System.Runtime.InteropServices.Marshalling;
namespace FlaxEngine
{
- internal sealed class DebugLogHandler : ILogHandler
+ internal partial class DebugLogHandler : ILogHandler
{
///
/// Occurs on sending a log message.
@@ -64,14 +66,14 @@ namespace FlaxEngine
Debug.Logger.LogException(exception);
}
- [MethodImpl(MethodImplOptions.InternalCall)]
- internal static extern void Internal_LogWrite(LogType level, string msg);
+ [LibraryImport("FlaxEngine", EntryPoint = "DebugLogHandlerInternal_LogWrite", StringMarshalling = StringMarshalling.Custom, StringMarshallingCustomType = typeof(Interop.StringMarshaller))]
+ internal static partial void Internal_LogWrite(LogType level, string msg);
- [MethodImpl(MethodImplOptions.InternalCall)]
- internal static extern void Internal_Log(LogType level, string msg, IntPtr obj, string stackTrace);
+ [LibraryImport("FlaxEngine", EntryPoint = "DebugLogHandlerInternal_Log", StringMarshalling = StringMarshalling.Custom, StringMarshallingCustomType = typeof(Interop.StringMarshaller))]
+ internal static partial void Internal_Log(LogType level, string msg, IntPtr obj, string stackTrace);
- [MethodImpl(MethodImplOptions.InternalCall)]
- internal static extern void Internal_LogException(Exception exception, IntPtr obj);
+ [LibraryImport("FlaxEngine", EntryPoint = "DebugLogHandlerInternal_LogException", StringMarshalling = StringMarshalling.Custom, StringMarshallingCustomType = typeof(Interop.StringMarshaller))]
+ internal static partial void Internal_LogException([MarshalUsing(typeof(Interop.ExceptionMarshaller))] Exception exception, IntPtr obj);
[SecuritySafeCritical]
public static string Internal_GetStackTrace()
diff --git a/Source/Engine/Engine/Engine.cpp b/Source/Engine/Engine/Engine.cpp
index 9c34dafda..bafcd30b3 100644
--- a/Source/Engine/Engine/Engine.cpp
+++ b/Source/Engine/Engine/Engine.cpp
@@ -20,6 +20,7 @@
#include "Engine/Threading/MainThreadTask.h"
#include "Engine/Threading/ThreadRegistry.h"
#include "Engine/Graphics/GPUDevice.h"
+#include "Engine/Scripting/ManagedCLR/MCore.h"
#include "Engine/Scripting/ScriptingType.h"
#include "Engine/Content/Content.h"
#include "Engine/Content/JsonAsset.h"
@@ -39,7 +40,7 @@
#include "Engine/Scripting/ManagedCLR/MAssembly.h"
#include "Engine/Scripting/ManagedCLR/MClass.h"
#include "Engine/Scripting/ManagedCLR/MMethod.h"
-#include "Engine/Scripting/MException.h"
+#include "Engine/Scripting/ManagedCLR/MException.h"
#include "Engine/Core/Config/PlatformSettings.h"
#endif
@@ -65,6 +66,7 @@ Action Engine::FixedUpdate;
Action Engine::Update;
TaskGraph* Engine::UpdateGraph = nullptr;
Action Engine::LateUpdate;
+Action Engine::LateFixedUpdate;
Action Engine::Draw;
Action Engine::Pause;
Action Engine::Unpause;
@@ -198,6 +200,7 @@ int32 Engine::Main(const Char* cmdLine)
if (Time::OnBeginPhysics())
{
OnFixedUpdate();
+ OnLateFixedUpdate();
Time::OnEndPhysics();
}
@@ -273,6 +276,17 @@ void Engine::OnFixedUpdate()
}
}
+void Engine::OnLateFixedUpdate()
+{
+ PROFILE_CPU_NAMED("Late Fixed Update");
+
+ // Call event
+ LateFixedUpdate();
+
+ // Update services
+ EngineService::OnLateFixedUpdate();
+}
+
void Engine::OnUpdate()
{
PROFILE_CPU_NAMED("Update");
@@ -309,6 +323,14 @@ void Engine::OnUpdate()
// Update services
EngineService::OnUpdate();
+
+#ifdef USE_NETCORE
+ // Force GC to run in background periodically to avoid large blocking collections causing hitches
+ if (Time::Update.TicksCount % 60 == 0)
+ {
+ MCore::GC::Collect(MCore::GC::MaxGeneration(), MGCCollectionMode::Forced, false, false);
+ }
+#endif
}
void Engine::OnLateUpdate()
@@ -547,6 +569,7 @@ void EngineImpl::InitPaths()
#endif
#if USE_EDITOR
Globals::EngineContentFolder = Globals::StartupFolder / TEXT("Content");
+#if USE_MONO
#if PLATFORM_WINDOWS
Globals::MonoPath = Globals::StartupFolder / TEXT("Source/Platforms/Editor/Windows/Mono");
#elif PLATFORM_LINUX
@@ -556,8 +579,11 @@ void EngineImpl::InitPaths()
#else
#error "Please specify the Mono data location for Editor on this platform."
#endif
+#endif
#else
+#if USE_MONO
Globals::MonoPath = Globals::StartupFolder / TEXT("Mono");
+#endif
#endif
Globals::ProjectContentFolder = Globals::ProjectFolder / TEXT("Content");
#if USE_EDITOR
diff --git a/Source/Engine/Engine/Engine.h b/Source/Engine/Engine/Engine.h
index 6f8c5fe6e..31d1f6ea6 100644
--- a/Source/Engine/Engine/Engine.h
+++ b/Source/Engine/Engine/Engine.h
@@ -54,6 +54,11 @@ public:
///
static Action LateUpdate;
+ ///
+ /// Event called after engine update.
+ ///
+ static Action LateFixedUpdate;
+
///
/// Event called during frame rendering and can be used to invoke custom rendering with GPUDevice.
///
@@ -107,6 +112,11 @@ public:
///
static void OnLateUpdate();
+ ///
+ /// Late fixed update callback.
+ ///
+ static void OnLateFixedUpdate();
+
///
/// Draw callback.
///
diff --git a/Source/Engine/Engine/EngineService.cpp b/Source/Engine/Engine/EngineService.cpp
index 9a01b08b5..7eea66853 100644
--- a/Source/Engine/Engine/EngineService.cpp
+++ b/Source/Engine/Engine/EngineService.cpp
@@ -33,6 +33,7 @@ static bool CompareEngineServices(EngineService* const& a, EngineService* const&
DEFINE_ENGINE_SERVICE_EVENT(FixedUpdate);
DEFINE_ENGINE_SERVICE_EVENT(Update);
DEFINE_ENGINE_SERVICE_EVENT(LateUpdate);
+DEFINE_ENGINE_SERVICE_EVENT(LateFixedUpdate);
DEFINE_ENGINE_SERVICE_EVENT(Draw);
DEFINE_ENGINE_SERVICE_EVENT_INVERTED(BeforeExit);
diff --git a/Source/Engine/Engine/EngineService.h b/Source/Engine/Engine/EngineService.h
index f4ae1b271..a142455d0 100644
--- a/Source/Engine/Engine/EngineService.h
+++ b/Source/Engine/Engine/EngineService.h
@@ -44,6 +44,7 @@ public:
DECLARE_ENGINE_SERVICE_EVENT(void, FixedUpdate);
DECLARE_ENGINE_SERVICE_EVENT(void, Update);
DECLARE_ENGINE_SERVICE_EVENT(void, LateUpdate);
+ DECLARE_ENGINE_SERVICE_EVENT(void, LateFixedUpdate);
DECLARE_ENGINE_SERVICE_EVENT(void, Draw);
DECLARE_ENGINE_SERVICE_EVENT(void, BeforeExit);
DECLARE_ENGINE_SERVICE_EVENT(void, Dispose);
diff --git a/Source/Engine/Engine/Game.h b/Source/Engine/Engine/Game.h
index 16a992c82..6b71b4e1c 100644
--- a/Source/Engine/Engine/Game.h
+++ b/Source/Engine/Engine/Game.h
@@ -24,6 +24,8 @@
#include "Platforms/Switch/Engine/Engine/SwitchGame.h"
#elif PLATFORM_MAC
#include "Mac/MacGame.h"
+#elif PLATFORM_IOS
+#include "iOS/iOSGame.h"
#else
#error Missing Game implementation!
#endif
diff --git a/Source/Engine/Engine/Globals.cpp b/Source/Engine/Engine/Globals.cpp
index 88d1ced23..d08aa5b5b 100644
--- a/Source/Engine/Engine/Globals.cpp
+++ b/Source/Engine/Engine/Globals.cpp
@@ -15,7 +15,9 @@ String Globals::EngineContentFolder;
String Globals::ProjectSourceFolder;
#endif
String Globals::ProjectContentFolder;
+#if USE_MONO
String Globals::MonoPath;
+#endif
bool Globals::FatalErrorOccurred;
bool Globals::IsRequestingExit;
int32 Globals::ExitCode;
diff --git a/Source/Engine/Engine/Globals.h b/Source/Engine/Engine/Globals.h
index c033e9ef5..a303590de 100644
--- a/Source/Engine/Engine/Globals.h
+++ b/Source/Engine/Engine/Globals.h
@@ -49,8 +49,10 @@ DECLARE_SCRIPTING_TYPE_NO_SPAWN(Globals);
// Project content directory path
API_FIELD(ReadOnly) static String ProjectContentFolder;
+#if USE_MONO
// Mono library folder path
API_FIELD(ReadOnly) static String MonoPath;
+#endif
// State
diff --git a/Source/Engine/Engine/NativeInterop.Invoker.cs b/Source/Engine/Engine/NativeInterop.Invoker.cs
new file mode 100644
index 000000000..2fc54597c
--- /dev/null
+++ b/Source/Engine/Engine/NativeInterop.Invoker.cs
@@ -0,0 +1,1296 @@
+// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.
+
+#if USE_NETCORE
+using System;
+using System.Linq;
+using System.Linq.Expressions;
+using System.Reflection;
+using System.Runtime.InteropServices;
+using System.Runtime.CompilerServices;
+using System.Diagnostics;
+
+namespace FlaxEngine.Interop
+{
+ unsafe partial class NativeInterop
+ {
+ ///
+ /// Helper class for invoking managed methods from delegates.
+ ///
+ internal static class Invoker
+ {
+ // TODO: Use .NET8 Unsafe.BitCast(returnValue) for more efficient casting of value types over boxing cast
+
+ internal static IntPtr MarshalReturnValue(ref TRet returnValue)
+ {
+ if (returnValue == null)
+ return IntPtr.Zero;
+ if (typeof(TRet) == typeof(string))
+ return ManagedString.ToNativeWeak(Unsafe.As(returnValue));
+ if (typeof(TRet) == typeof(ManagedHandle))
+ return ManagedHandle.ToIntPtr((ManagedHandle)(object)returnValue);
+ if (typeof(TRet) == typeof(bool))
+ return (bool)(object)returnValue ? boolTruePtr : boolFalsePtr;
+ if (typeof(TRet) == typeof(Type))
+ return ManagedHandle.ToIntPtr(GetTypeGCHandle(Unsafe.As(returnValue)));
+ if (typeof(TRet).IsArray)
+ {
+ var elementType = typeof(TRet).GetElementType();
+ if (ArrayFactory.GetMarshalledType(elementType) == elementType)
+ return ManagedHandle.ToIntPtr(ManagedArray.WrapNewArray(Unsafe.As(returnValue)), GCHandleType.Weak);
+ return ManagedHandle.ToIntPtr(ManagedArrayToGCHandleWrappedArray(Unsafe.As(returnValue)), GCHandleType.Weak);
+ }
+ return ManagedHandle.ToIntPtr(returnValue, GCHandleType.Weak);
+ }
+
+ internal static IntPtr MarshalReturnValueGeneric(Type returnType, object returnObject)
+ {
+ if (returnObject == null)
+ return IntPtr.Zero;
+ if (returnType == typeof(string))
+ return ManagedString.ToNativeWeak(Unsafe.As(returnObject));
+ if (returnType == typeof(ManagedHandle))
+ return ManagedHandle.ToIntPtr((ManagedHandle)(object)returnObject);
+ if (returnType == typeof(bool))
+ return (bool)returnObject ? boolTruePtr : boolFalsePtr;
+ if (returnType == typeof(Type))
+ return ManagedHandle.ToIntPtr(GetTypeGCHandle(Unsafe.As(returnObject)));
+ if (returnType.IsArray && ArrayFactory.GetMarshalledType(returnType.GetElementType()) == returnType.GetElementType())
+ return ManagedHandle.ToIntPtr(ManagedArray.WrapNewArray(Unsafe.As(returnObject)), GCHandleType.Weak);
+ if (returnType.IsArray)
+ return ManagedHandle.ToIntPtr(ManagedArrayToGCHandleWrappedArray(Unsafe.As(returnObject)), GCHandleType.Weak);
+ return ManagedHandle.ToIntPtr(returnObject, GCHandleType.Weak);
+ }
+
+ internal static IntPtr MarshalReturnValueThunk(ref TRet returnValue)
+ {
+ if (returnValue == null)
+ return IntPtr.Zero;
+ if (typeof(TRet) == typeof(string))
+ return ManagedString.ToNativeWeak(Unsafe.As(returnValue));
+ if (typeof(TRet) == typeof(IntPtr))
+ return (IntPtr)(object)returnValue;
+ if (typeof(TRet) == typeof(ManagedHandle))
+ return ManagedHandle.ToIntPtr((ManagedHandle)(object)returnValue);
+ if (typeof(TRet) == typeof(Type))
+ return ManagedHandle.ToIntPtr(GetTypeGCHandle(Unsafe.As(returnValue)));
+ if (typeof(TRet).IsArray)
+ {
+ var elementType = typeof(TRet).GetElementType();
+ if (ArrayFactory.GetMarshalledType(elementType) == elementType)
+ return ManagedHandle.ToIntPtr(ManagedArray.WrapNewArray(Unsafe.As(returnValue)), GCHandleType.Weak);
+ return ManagedHandle.ToIntPtr(ManagedArrayToGCHandleWrappedArray(Unsafe.As(returnValue)), GCHandleType.Weak);
+ }
+ // Match Mono bindings and pass value as pointer to prevent boxing it
+ if (typeof(TRet) == typeof(System.Boolean))
+ return new IntPtr(((System.Boolean)(object)returnValue) ? 1 : 0);
+ if (typeof(TRet) == typeof(System.Int16))
+ return new IntPtr((int)(System.Int16)(object)returnValue);
+ if (typeof(TRet) == typeof(System.Int32))
+ return new IntPtr((int)(System.Int32)(object)returnValue);
+ if (typeof(TRet) == typeof(System.Int64))
+ return new IntPtr((long)(System.Int64)(object)returnValue);
+ if (typeof(TRet) == typeof(System.UInt16))
+ return (IntPtr)new UIntPtr((ulong)(System.UInt16)(object)returnValue);
+ if (typeof(TRet) == typeof(System.UInt32))
+ return (IntPtr)new UIntPtr((ulong)(System.UInt32)(object)returnValue);
+ if (typeof(TRet) == typeof(System.UInt64))
+ return (IntPtr)new UIntPtr((ulong)(System.UInt64)(object)returnValue);
+ return ManagedHandle.ToIntPtr(returnValue, GCHandleType.Weak);
+ }
+
+ internal static IntPtr MarshalReturnValueThunkGeneric(Type returnType, object returnObject)
+ {
+ if (returnObject == null)
+ return IntPtr.Zero;
+ if (returnType == typeof(string))
+ return ManagedString.ToNativeWeak(Unsafe.As(returnObject));
+ if (returnType == typeof(IntPtr))
+ return (IntPtr)(object)returnObject;
+ if (returnType == typeof(ManagedHandle))
+ return ManagedHandle.ToIntPtr((ManagedHandle)(object)returnObject);
+ if (returnType == typeof(Type))
+ return returnObject != null ? ManagedHandle.ToIntPtr(GetTypeGCHandle(Unsafe.As(returnObject))) : IntPtr.Zero;
+ if (returnType.IsArray)
+ {
+ var elementType = returnType.GetElementType();
+ if (ArrayFactory.GetMarshalledType(elementType) == elementType)
+ return ManagedHandle.ToIntPtr(ManagedArray.WrapNewArray(Unsafe.As(returnObject)), GCHandleType.Weak);
+ return ManagedHandle.ToIntPtr(ManagedArrayToGCHandleWrappedArray(Unsafe.As(returnObject)), GCHandleType.Weak);
+ }
+ // Match Mono bindings and pass value as pointer to prevent boxing it
+ if (returnType == typeof(System.Boolean))
+ return new IntPtr(((System.Boolean)(object)returnObject) ? 1 : 0);
+ if (returnType == typeof(System.Int16))
+ return new IntPtr((int)(System.Int16)(object)returnObject);
+ if (returnType == typeof(System.Int32))
+ return new IntPtr((int)(System.Int32)(object)returnObject);
+ if (returnType == typeof(System.Int64))
+ return new IntPtr((long)(System.Int64)(object)returnObject);
+ if (returnType == typeof(System.UInt16))
+ return (IntPtr)new UIntPtr((ulong)(System.UInt16)(object)returnObject);
+ if (returnType == typeof(System.UInt32))
+ return (IntPtr)new UIntPtr((ulong)(System.UInt32)(object)returnObject);
+ if (returnType == typeof(System.UInt64))
+ return (IntPtr)new UIntPtr((ulong)(System.UInt64)(object)returnObject);
+ return ManagedHandle.ToIntPtr(returnObject, GCHandleType.Weak);
+ }
+
+#if !USE_AOT
+ internal delegate IntPtr MarshalAndInvokeDelegate(object delegateContext, ManagedHandle instancePtr, IntPtr paramPtr);
+
+ internal delegate IntPtr InvokeThunkDelegate(object delegateContext, ManagedHandle instancePtr, IntPtr* paramPtrs);
+
+ ///
+ /// Casts managed pointer to unmanaged pointer.
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ internal static T* ToPointer(IntPtr ptr) where T : unmanaged
+ {
+ return (T*)ptr.ToPointer();
+ }
+
+ internal static MethodInfo ToPointerMethod = typeof(Invoker).GetMethod(nameof(Invoker.ToPointer), BindingFlags.Static | BindingFlags.NonPublic);
+
+ ///
+ /// Creates a delegate for invoker to pass parameters as references.
+ ///
+ internal static Delegate CreateDelegateFromMethod(MethodInfo method, bool passParametersByRef = true)
+ {
+ Type[] methodParameters;
+ if (method.IsStatic)
+ methodParameters = method.GetParameterTypes();
+ else
+ methodParameters = method.GetParameters().Select(x => x.ParameterType).Prepend(method.DeclaringType).ToArray();
+
+ // Pass delegate parameters by reference
+ Type[] delegateParameters = methodParameters.Select(x => x.IsPointer ? typeof(IntPtr) : x).Select(x => passParametersByRef && !x.IsByRef ? x.MakeByRefType() : x).ToArray();
+ if (!method.IsStatic && passParametersByRef)
+ delegateParameters[0] = method.DeclaringType;
+
+ // Convert unmanaged pointer parameters to IntPtr
+ ParameterExpression[] parameterExpressions = delegateParameters.Select(Expression.Parameter).ToArray();
+ Expression[] callExpressions = new Expression[methodParameters.Length];
+ for (int i = 0; i < methodParameters.Length; i++)
+ {
+ Type parameterType = methodParameters[i];
+ if (parameterType.IsPointer)
+ callExpressions[i] = Expression.Call(null, ToPointerMethod.MakeGenericMethod(parameterType.GetElementType()), parameterExpressions[i]);
+ else
+ callExpressions[i] = parameterExpressions[i];
+ }
+
+ // Create and compile the delegate
+ MethodCallExpression callDelegExp;
+ if (method.IsStatic)
+ callDelegExp = Expression.Call(null, method, callExpressions.ToArray());
+ else
+ callDelegExp = Expression.Call(parameterExpressions[0], method, callExpressions.Skip(1).ToArray());
+ Type delegateType = DelegateHelpers.MakeNewCustomDelegate(delegateParameters.Append(method.ReturnType).ToArray());
+ return Expression.Lambda(delegateType, callDelegExp, parameterExpressions).Compile();
+ }
+
+ internal static class InvokerNoRet0
+ {
+ internal delegate void InvokerDelegate(object instance);
+
+ internal delegate void ThunkInvokerDelegate(object instance);
+
+ internal static object CreateDelegate(MethodInfo method)
+ {
+ return new Tuple(method.GetParameterTypes(), Unsafe.As(CreateDelegateFromMethod(method)));
+ }
+
+ internal static object CreateInvokerDelegate(MethodInfo method)
+ {
+ return Unsafe.As(CreateDelegateFromMethod(method, false));
+ }
+
+ [DebuggerStepThrough]
+ internal static IntPtr MarshalAndInvoke(object delegateContext, ManagedHandle instancePtr, IntPtr paramPtr)
+ {
+ (Type[] types, InvokerDelegate deleg) = (Tuple)(delegateContext);
+
+ deleg(instancePtr.Target);
+
+ return IntPtr.Zero;
+ }
+
+ [DebuggerStepThrough]
+ internal static unsafe IntPtr InvokeThunk(object delegateContext, ManagedHandle instancePtr, IntPtr* paramPtrs)
+ {
+ ThunkInvokerDelegate deleg = Unsafe.As(delegateContext);
+
+ deleg(instancePtr.Target);
+
+ return IntPtr.Zero;
+ }
+ }
+
+ internal static class InvokerNoRet1
+ {
+ internal delegate void InvokerDelegate(object instance, ref T1 param1);
+
+ internal delegate void ThunkInvokerDelegate(object instance, T1 param1);
+
+ internal static object CreateDelegate(MethodInfo method)
+ {
+ return new Tuple(method.GetParameterTypes(), Unsafe.As(CreateDelegateFromMethod(method)));
+ }
+
+ internal static object CreateInvokerDelegate(MethodInfo method)
+ {
+ return Unsafe.As(CreateDelegateFromMethod(method, false));
+ }
+
+ [DebuggerStepThrough]
+ internal static IntPtr MarshalAndInvoke(object delegateContext, ManagedHandle instancePtr, IntPtr paramPtr)
+ {
+ (Type[] types, InvokerDelegate deleg) = (Tuple)(delegateContext);
+
+ IntPtr param1Ptr = Marshal.ReadIntPtr(paramPtr);
+
+ T1 param1 = default;
+ if (param1Ptr != IntPtr.Zero)
+ MarshalHelper.ToManaged(ref param1, param1Ptr, types[0].IsByRef);
+
+ deleg(instancePtr.Target, ref param1);
+
+ // Marshal reference parameters back to original unmanaged references
+ if (param1Ptr != IntPtr.Zero && types[0].IsByRef)
+ MarshalHelper.ToNative(ref param1, param1Ptr);
+
+ return IntPtr.Zero;
+ }
+
+ [DebuggerStepThrough]
+ internal static unsafe IntPtr InvokeThunk(object delegateContext, ManagedHandle instancePtr, IntPtr* paramPtrs)
+ {
+ ThunkInvokerDelegate deleg = Unsafe.As(delegateContext);
+
+ T1 param1 = MarshalHelper.ToManagedUnbox(paramPtrs[0]);
+
+ deleg(instancePtr.Target, param1);
+
+ return IntPtr.Zero;
+ }
+ }
+
+ internal static class InvokerNoRet2
+ {
+ internal delegate void InvokerDelegate(object instance, ref T1 param1, ref T2 param2);
+
+ internal delegate void ThunkInvokerDelegate(object instance, T1 param1, T2 param2);
+
+ internal static object CreateDelegate(MethodInfo method)
+ {
+ return new Tuple(method.GetParameterTypes(), Unsafe.As(CreateDelegateFromMethod(method)));
+ }
+
+ internal static object CreateInvokerDelegate(MethodInfo method)
+ {
+ return Unsafe.As(CreateDelegateFromMethod(method, false));
+ }
+
+ [DebuggerStepThrough]
+ internal static IntPtr MarshalAndInvoke(object delegateContext, ManagedHandle instancePtr, IntPtr paramPtr)
+ {
+ (Type[] types, InvokerDelegate deleg) = (Tuple)(delegateContext);
+
+ IntPtr param1Ptr = Marshal.ReadIntPtr(paramPtr);
+ IntPtr param2Ptr = Marshal.ReadIntPtr(paramPtr + IntPtr.Size);
+
+ T1 param1 = default;
+ T2 param2 = default;
+ if (param1Ptr != IntPtr.Zero)
+ MarshalHelper.ToManaged(ref param1, param1Ptr, types[0].IsByRef);
+ if (param2Ptr != IntPtr.Zero)
+ MarshalHelper.ToManaged(ref param2, param2Ptr, types[1].IsByRef);
+
+ deleg(instancePtr.Target, ref param1, ref param2);
+
+ // Marshal reference parameters back to original unmanaged references
+ if (param1Ptr != IntPtr.Zero && types[0].IsByRef)
+ MarshalHelper.ToNative(ref param1, param1Ptr);
+ if (param2Ptr != IntPtr.Zero && types[1].IsByRef)
+ MarshalHelper.ToNative(ref param2, param2Ptr);
+
+ return IntPtr.Zero;
+ }
+
+ [DebuggerStepThrough]
+ internal static unsafe IntPtr InvokeThunk(object delegateContext, ManagedHandle instancePtr, IntPtr* paramPtrs)
+ {
+ ThunkInvokerDelegate deleg = Unsafe.As(delegateContext);
+
+ T1 param1 = MarshalHelper.ToManagedUnbox(paramPtrs[0]);
+ T2 param2 = MarshalHelper.ToManagedUnbox(paramPtrs[1]);
+
+ deleg(instancePtr.Target, param1, param2);
+
+ return IntPtr.Zero;
+ }
+ }
+
+ internal static class InvokerNoRet3
+ {
+ internal delegate void InvokerDelegate(object instance, ref T1 param1, ref T2 param2, ref T3 param3);
+
+ internal delegate void ThunkInvokerDelegate(object instance, T1 param1, T2 param2, T3 param3);
+
+ internal static object CreateDelegate(MethodInfo method)
+ {
+ return new Tuple(method.GetParameterTypes(), Unsafe.As(CreateDelegateFromMethod(method)));
+ }
+
+ internal static object CreateInvokerDelegate(MethodInfo method)
+ {
+ return Unsafe.As(CreateDelegateFromMethod(method, false));
+ }
+
+ [DebuggerStepThrough]
+ internal static IntPtr MarshalAndInvoke(object delegateContext, ManagedHandle instancePtr, IntPtr paramPtr)
+ {
+ (Type[] types, InvokerDelegate deleg) = (Tuple)(delegateContext);
+
+ IntPtr param1Ptr = Marshal.ReadIntPtr(paramPtr);
+ IntPtr param2Ptr = Marshal.ReadIntPtr(paramPtr + IntPtr.Size);
+ IntPtr param3Ptr = Marshal.ReadIntPtr(paramPtr + IntPtr.Size + IntPtr.Size);
+
+ T1 param1 = default;
+ T2 param2 = default;
+ T3 param3 = default;
+ if (param1Ptr != IntPtr.Zero)
+ MarshalHelper.ToManaged(ref param1, param1Ptr, types[0].IsByRef);
+ if (param2Ptr != IntPtr.Zero)
+ MarshalHelper.ToManaged(ref param2, param2Ptr, types[1].IsByRef);
+ if (param3Ptr != IntPtr.Zero)
+ MarshalHelper.ToManaged(ref param3, param3Ptr, types[2].IsByRef);
+
+ deleg(instancePtr.Target, ref param1, ref param2, ref param3);
+
+ // Marshal reference parameters back to original unmanaged references
+ if (param1Ptr != IntPtr.Zero && types[0].IsByRef)
+ MarshalHelper.ToNative(ref param1, param1Ptr);
+ if (param2Ptr != IntPtr.Zero && types[1].IsByRef)
+ MarshalHelper.ToNative(ref param2, param2Ptr);
+ if (param3Ptr != IntPtr.Zero && types[2].IsByRef)
+ MarshalHelper.ToNative(ref param3, param3Ptr);
+
+ return IntPtr.Zero;
+ }
+
+ [DebuggerStepThrough]
+ internal static unsafe IntPtr InvokeThunk(object delegateContext, ManagedHandle instancePtr, IntPtr* paramPtrs)
+ {
+ ThunkInvokerDelegate deleg = Unsafe.As(delegateContext);
+
+ T1 param1 = MarshalHelper.ToManagedUnbox(paramPtrs[0]);
+ T2 param2 = MarshalHelper.ToManagedUnbox(paramPtrs[1]);
+ T3 param3 = MarshalHelper.ToManagedUnbox(paramPtrs[2]);
+
+ deleg(instancePtr.Target, param1, param2, param3);
+
+ return IntPtr.Zero;
+ }
+ }
+
+ internal static class InvokerNoRet4
+ {
+ internal delegate void InvokerDelegate(object instance, ref T1 param1, ref T2 param2, ref T3 param3, ref T4 param4);
+
+ internal delegate void ThunkInvokerDelegate(object instance, T1 param1, T2 param2, T3 param3, T4 param4);
+
+ internal static object CreateDelegate(MethodInfo method)
+ {
+ return new Tuple(method.GetParameterTypes(), Unsafe.As(CreateDelegateFromMethod(method)));
+ }
+
+ internal static object CreateInvokerDelegate(MethodInfo method)
+ {
+ return Unsafe.As(CreateDelegateFromMethod(method, false));
+ }
+
+ [DebuggerStepThrough]
+ internal static IntPtr MarshalAndInvoke(object delegateContext, ManagedHandle instancePtr, IntPtr paramPtr)
+ {
+ (Type[] types, InvokerDelegate deleg) = (Tuple)(delegateContext);
+
+ IntPtr param1Ptr = Marshal.ReadIntPtr(paramPtr);
+ IntPtr param2Ptr = Marshal.ReadIntPtr(paramPtr + IntPtr.Size);
+ IntPtr param3Ptr = Marshal.ReadIntPtr(paramPtr + IntPtr.Size + IntPtr.Size);
+ IntPtr param4Ptr = Marshal.ReadIntPtr(paramPtr + IntPtr.Size + IntPtr.Size + IntPtr.Size);
+
+ T1 param1 = default;
+ T2 param2 = default;
+ T3 param3 = default;
+ T4 param4 = default;
+ if (param1Ptr != IntPtr.Zero)
+ MarshalHelper.ToManaged(ref param1, param1Ptr, types[0].IsByRef);
+ if (param2Ptr != IntPtr.Zero)
+ MarshalHelper.ToManaged(ref param2, param2Ptr, types[1].IsByRef);
+ if (param3Ptr != IntPtr.Zero)
+ MarshalHelper.ToManaged(ref param3, param3Ptr, types[2].IsByRef);
+ if (param4Ptr != IntPtr.Zero)
+ MarshalHelper.ToManaged(ref param4, param4Ptr, types[3].IsByRef);
+
+ deleg(instancePtr.Target, ref param1, ref param2, ref param3, ref param4);
+
+ // Marshal reference parameters back to original unmanaged references
+ if (param1Ptr != IntPtr.Zero && types[0].IsByRef)
+ MarshalHelper.ToNative(ref param1, param1Ptr);
+ if (param2Ptr != IntPtr.Zero && types[1].IsByRef)
+ MarshalHelper.ToNative(ref param2, param2Ptr);
+ if (param3Ptr != IntPtr.Zero && types[2].IsByRef)
+ MarshalHelper.ToNative(ref param3, param3Ptr);
+ if (param4Ptr != IntPtr.Zero && types[3].IsByRef)
+ MarshalHelper.ToNative(ref param4, param4Ptr);
+
+ return IntPtr.Zero;
+ }
+
+ [DebuggerStepThrough]
+ internal static unsafe IntPtr InvokeThunk(object delegateContext, ManagedHandle instancePtr, IntPtr* paramPtrs)
+ {
+ ThunkInvokerDelegate deleg = Unsafe.As(delegateContext);
+
+ T1 param1 = MarshalHelper.ToManagedUnbox(paramPtrs[0]);
+ T2 param2 = MarshalHelper.ToManagedUnbox(paramPtrs[1]);
+ T3 param3 = MarshalHelper.ToManagedUnbox(paramPtrs[2]);
+ T4 param4 = MarshalHelper.ToManagedUnbox(paramPtrs[3]);
+
+ deleg(instancePtr.Target, param1, param2, param3, param4);
+
+ return IntPtr.Zero;
+ }
+ }
+
+ internal static class InvokerStaticNoRet0
+ {
+ internal delegate void InvokerDelegate();
+
+ internal delegate void ThunkInvokerDelegate();
+
+ internal static object CreateDelegate(MethodInfo method)
+ {
+ return new Tuple(method.GetParameterTypes(), Unsafe.As(CreateDelegateFromMethod(method)));
+ }
+
+ internal static object CreateInvokerDelegate(MethodInfo method)
+ {
+ return Unsafe.As(CreateDelegateFromMethod(method, false));
+ }
+
+ [DebuggerStepThrough]
+ internal static IntPtr MarshalAndInvoke(object delegateContext, ManagedHandle instancePtr, IntPtr paramPtr)
+ {
+ (Type[] types, InvokerDelegate deleg) = (Tuple)(delegateContext);
+
+ deleg();
+
+ return IntPtr.Zero;
+ }
+
+ [DebuggerStepThrough]
+ internal static unsafe IntPtr InvokeThunk(object delegateContext, ManagedHandle instancePtr, IntPtr* paramPtrs)
+ {
+ ThunkInvokerDelegate deleg = Unsafe.As(delegateContext);
+
+ deleg();
+
+ return IntPtr.Zero;
+ }
+ }
+
+ internal static class InvokerStaticNoRet1
+ {
+ internal delegate void InvokerDelegate(ref T1 param1);
+
+ internal delegate void ThunkInvokerDelegate(T1 param1);
+
+ internal static object CreateDelegate(MethodInfo method)
+ {
+ return new Tuple(method.GetParameterTypes(), Unsafe.As(CreateDelegateFromMethod(method)));
+ }
+
+ internal static object CreateInvokerDelegate(MethodInfo method)
+ {
+ return Unsafe.As(CreateDelegateFromMethod(method, false));
+ }
+
+ [DebuggerStepThrough]
+ internal static IntPtr MarshalAndInvoke(object delegateContext, ManagedHandle instancePtr, IntPtr paramPtr)
+ {
+ (Type[] types, InvokerDelegate deleg) = (Tuple)(delegateContext);
+
+ IntPtr param1Ptr = Marshal.ReadIntPtr(paramPtr);
+
+ T1 param1 = default;
+ if (param1Ptr != IntPtr.Zero)
+ MarshalHelper.ToManaged(ref param1, param1Ptr, types[0].IsByRef);
+
+ deleg(ref param1);
+
+ // Marshal reference parameters back to original unmanaged references
+ if (param1Ptr != IntPtr.Zero && types[0].IsByRef)
+ MarshalHelper.ToNative(ref param1, param1Ptr);
+
+ return IntPtr.Zero;
+ }
+
+ [DebuggerStepThrough]
+ internal static unsafe IntPtr InvokeThunk(object delegateContext, ManagedHandle instancePtr, IntPtr* paramPtrs)
+ {
+ ThunkInvokerDelegate deleg = Unsafe.As(delegateContext);
+
+ T1 param1 = MarshalHelper.ToManagedUnbox(paramPtrs[0]);
+
+ deleg(param1);
+
+ return IntPtr.Zero;
+ }
+ }
+
+ internal static class InvokerStaticNoRet2
+ {
+ internal delegate void InvokerDelegate(ref T1 param1, ref T2 param2);
+
+ internal delegate void ThunkInvokerDelegate(T1 param1, T2 param2);
+
+ internal static object CreateDelegate(MethodInfo method)
+ {
+ return new Tuple(method.GetParameterTypes(), Unsafe.As(CreateDelegateFromMethod(method)));
+ }
+
+ internal static object CreateInvokerDelegate(MethodInfo method)
+ {
+ return Unsafe.As(CreateDelegateFromMethod(method, false));
+ }
+
+ [DebuggerStepThrough]
+ internal static IntPtr MarshalAndInvoke(object delegateContext, ManagedHandle instancePtr, IntPtr paramPtr)
+ {
+ (Type[] types, InvokerDelegate deleg) = (Tuple)(delegateContext);
+
+ IntPtr param1Ptr = Marshal.ReadIntPtr(paramPtr);
+ IntPtr param2Ptr = Marshal.ReadIntPtr(paramPtr + IntPtr.Size);
+
+ T1 param1 = default;
+ T2 param2 = default;
+ if (param1Ptr != IntPtr.Zero)
+ MarshalHelper.ToManaged(ref param1, param1Ptr, types[0].IsByRef);
+ if (param2Ptr != IntPtr.Zero)
+ MarshalHelper.ToManaged(ref param2, param2Ptr, types[1].IsByRef);
+
+ deleg(ref param1, ref param2);
+
+ // Marshal reference parameters back to original unmanaged references
+ if (param1Ptr != IntPtr.Zero && types[0].IsByRef)
+ MarshalHelper.ToNative(ref param1, param1Ptr);
+ if (param2Ptr != IntPtr.Zero && types[1].IsByRef)
+ MarshalHelper.ToNative(ref param2, param2Ptr);
+
+ return IntPtr.Zero;
+ }
+
+ [DebuggerStepThrough]
+ internal static unsafe IntPtr InvokeThunk(object delegateContext, ManagedHandle instancePtr, IntPtr* paramPtrs)
+ {
+ ThunkInvokerDelegate deleg = Unsafe.As(delegateContext);
+
+ T1 param1 = MarshalHelper.ToManagedUnbox(paramPtrs[0]);
+ T2 param2 = MarshalHelper.ToManagedUnbox(paramPtrs[1]);
+
+ deleg(param1, param2);
+
+ return IntPtr.Zero;
+ }
+ }
+
+ internal static class InvokerStaticNoRet3
+ {
+ internal delegate void InvokerDelegate(ref T1 param1, ref T2 param2, ref T3 param3);
+
+ internal delegate void ThunkInvokerDelegate(T1 param1, T2 param2, T3 param3);
+
+ internal static object CreateDelegate(MethodInfo method)
+ {
+ return new Tuple(method.GetParameterTypes(), Unsafe.As(CreateDelegateFromMethod(method)));
+ }
+
+ internal static object CreateInvokerDelegate(MethodInfo method)
+ {
+ return Unsafe.As(CreateDelegateFromMethod(method, false));
+ }
+
+ [DebuggerStepThrough]
+ internal static IntPtr MarshalAndInvoke(object delegateContext, ManagedHandle instancePtr, IntPtr paramPtr)
+ {
+ (Type[] types, InvokerDelegate deleg) = (Tuple)(delegateContext);
+
+ IntPtr param1Ptr = Marshal.ReadIntPtr(paramPtr);
+ IntPtr param2Ptr = Marshal.ReadIntPtr(paramPtr + IntPtr.Size);
+ IntPtr param3Ptr = Marshal.ReadIntPtr(paramPtr + IntPtr.Size + IntPtr.Size);
+
+ T1 param1 = default;
+ T2 param2 = default;
+ T3 param3 = default;
+ if (param1Ptr != IntPtr.Zero)
+ MarshalHelper.ToManaged(ref param1, param1Ptr, types[0].IsByRef);
+ if (param2Ptr != IntPtr.Zero)
+ MarshalHelper.ToManaged(ref param2, param2Ptr, types[1].IsByRef);
+ if (param3Ptr != IntPtr.Zero)
+ MarshalHelper.ToManaged(ref param3, param3Ptr, types[2].IsByRef);
+
+ deleg(ref param1, ref param2, ref param3);
+
+ // Marshal reference parameters back to original unmanaged references
+ if (param1Ptr != IntPtr.Zero && types[0].IsByRef)
+ MarshalHelper.ToNative(ref param1, param1Ptr);
+ if (param2Ptr != IntPtr.Zero && types[1].IsByRef)
+ MarshalHelper.ToNative(ref param2, param2Ptr);
+ if (param3Ptr != IntPtr.Zero && types[2].IsByRef)
+ MarshalHelper.ToNative(ref param3, param3Ptr);
+
+ return IntPtr.Zero;
+ }
+
+ [DebuggerStepThrough]
+ internal static unsafe IntPtr InvokeThunk(object delegateContext, ManagedHandle instancePtr, IntPtr* paramPtrs)
+ {
+ ThunkInvokerDelegate deleg = Unsafe.As(delegateContext);
+
+ T1 param1 = MarshalHelper.ToManagedUnbox(paramPtrs[0]);
+ T2 param2 = MarshalHelper.ToManagedUnbox(paramPtrs[1]);
+ T3 param3 = MarshalHelper.ToManagedUnbox(paramPtrs[2]);
+
+ deleg(param1, param2, param3);
+
+ return IntPtr.Zero;
+ }
+ }
+
+ internal static class InvokerStaticNoRet4
+ {
+ internal delegate void InvokerDelegate(ref T1 param1, ref T2 param2, ref T3 param3, ref T4 param4);
+
+ internal delegate void ThunkInvokerDelegate(T1 param1, T2 param2, T3 param3, T4 param4);
+
+ internal static object CreateDelegate(MethodInfo method)
+ {
+ return new Tuple(method.GetParameterTypes(), Unsafe.As(CreateDelegateFromMethod(method)));
+ }
+
+ internal static object CreateInvokerDelegate(MethodInfo method)
+ {
+ return Unsafe.As(CreateDelegateFromMethod(method, false));
+ }
+
+ [DebuggerStepThrough]
+ internal static IntPtr MarshalAndInvoke(object delegateContext, ManagedHandle instancePtr, IntPtr paramPtr)
+ {
+ (Type[] types, InvokerDelegate deleg) = (Tuple)(delegateContext);
+
+ IntPtr param1Ptr = Marshal.ReadIntPtr(paramPtr);
+ IntPtr param2Ptr = Marshal.ReadIntPtr(paramPtr + IntPtr.Size);
+ IntPtr param3Ptr = Marshal.ReadIntPtr(paramPtr + IntPtr.Size + IntPtr.Size);
+ IntPtr param4Ptr = Marshal.ReadIntPtr(paramPtr + IntPtr.Size + IntPtr.Size + IntPtr.Size);
+
+ T1 param1 = default;
+ T2 param2 = default;
+ T3 param3 = default;
+ T4 param4 = default;
+ if (param1Ptr != IntPtr.Zero)
+ MarshalHelper.ToManaged(ref param1, param1Ptr, types[0].IsByRef);
+ if (param2Ptr != IntPtr.Zero)
+ MarshalHelper.ToManaged(ref param2, param2Ptr, types[1].IsByRef);
+ if (param3Ptr != IntPtr.Zero)
+ MarshalHelper.ToManaged(ref param3, param3Ptr, types[2].IsByRef);
+ if (param4Ptr != IntPtr.Zero)
+ MarshalHelper.ToManaged(ref param4, param4Ptr, types[3].IsByRef);
+
+ deleg(ref param1, ref param2, ref param3, ref param4);
+
+ // Marshal reference parameters back to original unmanaged references
+ if (param1Ptr != IntPtr.Zero && types[0].IsByRef)
+ MarshalHelper.ToNative(ref param1, param1Ptr);
+ if (param2Ptr != IntPtr.Zero && types[1].IsByRef)
+ MarshalHelper.ToNative(ref param2, param2Ptr);
+ if (param3Ptr != IntPtr.Zero && types[2].IsByRef)
+ MarshalHelper.ToNative(ref param3, param3Ptr);
+ if (param4Ptr != IntPtr.Zero && types[3].IsByRef)
+ MarshalHelper.ToNative(ref param4, param4Ptr);
+
+ return IntPtr.Zero;
+ }
+
+ [DebuggerStepThrough]
+ internal static unsafe IntPtr InvokeThunk(object delegateContext, ManagedHandle instancePtr, IntPtr* paramPtrs)
+ {
+ ThunkInvokerDelegate deleg = Unsafe.As(delegateContext);
+
+ T1 param1 = MarshalHelper.ToManagedUnbox(paramPtrs[0]);
+ T2 param2 = MarshalHelper.ToManagedUnbox(paramPtrs[1]);
+ T3 param3 = MarshalHelper.ToManagedUnbox(paramPtrs[2]);
+ T4 param4 = MarshalHelper.ToManagedUnbox(paramPtrs[3]);
+
+ deleg(param1, param2, param3, param4);
+
+ return IntPtr.Zero;
+ }
+ }
+
+ internal static class InvokerRet0
+ {
+ internal delegate TRet InvokerDelegate(object instance);
+
+ internal delegate TRet ThunkInvokerDelegate(object instance);
+
+ internal static object CreateDelegate(MethodInfo method)
+ {
+ return new Tuple(method.GetParameterTypes(), Unsafe.As(CreateDelegateFromMethod(method)));
+ }
+
+ internal static object CreateInvokerDelegate(MethodInfo method)
+ {
+ return Unsafe.As(CreateDelegateFromMethod(method, false));
+ }
+
+ [DebuggerStepThrough]
+ internal static IntPtr MarshalAndInvoke(object delegateContext, ManagedHandle instancePtr, IntPtr paramPtr)
+ {
+ (Type[] types, InvokerDelegate deleg) = (Tuple)(delegateContext);
+
+ TRet ret = deleg(instancePtr.Target);
+
+ return MarshalReturnValue(ref ret);
+ }
+
+ [DebuggerStepThrough]
+ internal static unsafe IntPtr InvokeThunk(object delegateContext, ManagedHandle instancePtr, IntPtr* paramPtrs)
+ {
+ ThunkInvokerDelegate deleg = Unsafe.As(delegateContext);
+
+ TRet ret = deleg(instancePtr.Target);
+
+ return MarshalReturnValueThunk(ref ret);
+ }
+ }
+
+ internal static class InvokerRet1
+ {
+ internal delegate TRet InvokerDelegate(object instance, ref T1 param1);
+
+ internal delegate TRet ThunkInvokerDelegate(object instance, T1 param1);
+
+ internal static object CreateDelegate(MethodInfo method)
+ {
+ return new Tuple(method.GetParameterTypes(), Unsafe.As(CreateDelegateFromMethod(method)));
+ }
+
+ internal static object CreateInvokerDelegate(MethodInfo method)
+ {
+ return Unsafe.As(CreateDelegateFromMethod(method, false));
+ }
+
+ [DebuggerStepThrough]
+ internal static IntPtr MarshalAndInvoke(object delegateContext, ManagedHandle instancePtr, IntPtr paramPtr)
+ {
+ (Type[] types, InvokerDelegate deleg) = (Tuple)(delegateContext);
+
+ IntPtr param1Ptr = Marshal.ReadIntPtr(paramPtr);
+
+ T1 param1 = default;
+ if (param1Ptr != IntPtr.Zero)
+ MarshalHelper.ToManaged(ref param1, param1Ptr, types[0].IsByRef);
+
+ TRet ret = deleg(instancePtr.Target, ref param1);
+
+ // Marshal reference parameters back to original unmanaged references
+ if (param1Ptr != IntPtr.Zero && types[0].IsByRef)
+ MarshalHelper.ToNative(ref param1, param1Ptr);
+
+ return MarshalReturnValue(ref ret);
+ }
+
+ [DebuggerStepThrough]
+ internal static unsafe IntPtr InvokeThunk(object delegateContext, ManagedHandle instancePtr, IntPtr* paramPtrs)
+ {
+ ThunkInvokerDelegate deleg = Unsafe.As(delegateContext);
+
+ T1 param1 = MarshalHelper.ToManagedUnbox(paramPtrs[0]);
+
+ TRet ret = deleg(instancePtr.Target, param1);
+
+ return MarshalReturnValueThunk(ref ret);
+ }
+ }
+
+ internal static class InvokerRet2
+ {
+ internal delegate TRet InvokerDelegate(object instance, ref T1 param1, ref T2 param2);
+
+ internal delegate TRet ThunkInvokerDelegate(object instance, T1 param1, T2 param2);
+
+ internal static object CreateDelegate(MethodInfo method)
+ {
+ return new Tuple(method.GetParameterTypes(), Unsafe.As(CreateDelegateFromMethod(method)));
+ }
+
+ internal static object CreateInvokerDelegate(MethodInfo method)
+ {
+ return Unsafe.As(CreateDelegateFromMethod(method, false));
+ }
+
+ [DebuggerStepThrough]
+ internal static IntPtr MarshalAndInvoke(object delegateContext, ManagedHandle instancePtr, IntPtr paramPtr)
+ {
+ (Type[] types, InvokerDelegate deleg) = (Tuple)(delegateContext);
+
+ IntPtr param1Ptr = Marshal.ReadIntPtr(paramPtr);
+ IntPtr param2Ptr = Marshal.ReadIntPtr(paramPtr + IntPtr.Size);
+
+ T1 param1 = default;
+ T2 param2 = default;
+ if (param1Ptr != IntPtr.Zero)
+ MarshalHelper.ToManaged(ref param1, param1Ptr, types[0].IsByRef);
+ if (param2Ptr != IntPtr.Zero)
+ MarshalHelper.ToManaged(ref param2, param2Ptr, types[1].IsByRef);
+
+ TRet ret = deleg(instancePtr.Target, ref param1, ref param2);
+
+ // Marshal reference parameters back to original unmanaged references
+ if (param1Ptr != IntPtr.Zero && types[0].IsByRef)
+ MarshalHelper.ToNative(ref param1, param1Ptr);
+ if (param2Ptr != IntPtr.Zero && types[1].IsByRef)
+ MarshalHelper.ToNative(ref param2, param2Ptr);
+
+ return MarshalReturnValue(ref ret);
+ }
+
+ [DebuggerStepThrough]
+ internal static unsafe IntPtr InvokeThunk(object delegateContext, ManagedHandle instancePtr, IntPtr* paramPtrs)
+ {
+ ThunkInvokerDelegate deleg = Unsafe.As(delegateContext);
+
+ T1 param1 = MarshalHelper.ToManagedUnbox(paramPtrs[0]);
+ T2 param2 = MarshalHelper.ToManagedUnbox(paramPtrs[1]);
+
+ TRet ret = deleg(instancePtr.Target, param1, param2);
+
+ return MarshalReturnValueThunk(ref ret);
+ }
+ }
+
+ internal static class InvokerRet3
+ {
+ internal delegate TRet InvokerDelegate(object instance, ref T1 param1, ref T2 param2, ref T3 param3);
+
+ internal delegate TRet ThunkInvokerDelegate(object instance, T1 param1, T2 param2, T3 param3);
+
+ internal static object CreateDelegate(MethodInfo method)
+ {
+ return new Tuple(method.GetParameterTypes(), Unsafe.As(CreateDelegateFromMethod(method)));
+ }
+
+ internal static object CreateInvokerDelegate(MethodInfo method)
+ {
+ return Unsafe.As(CreateDelegateFromMethod(method, false));
+ }
+
+ [DebuggerStepThrough]
+ internal static IntPtr MarshalAndInvoke(object delegateContext, ManagedHandle instancePtr, IntPtr paramPtr)
+ {
+ (Type[] types, InvokerDelegate deleg) = (Tuple)(delegateContext);
+
+ IntPtr param1Ptr = Marshal.ReadIntPtr(paramPtr);
+ IntPtr param2Ptr = Marshal.ReadIntPtr(paramPtr + IntPtr.Size);
+ IntPtr param3Ptr = Marshal.ReadIntPtr(paramPtr + IntPtr.Size + IntPtr.Size);
+
+ T1 param1 = default;
+ T2 param2 = default;
+ T3 param3 = default;
+ if (param1Ptr != IntPtr.Zero)
+ MarshalHelper.ToManaged(ref param1, param1Ptr, types[0].IsByRef);
+ if (param2Ptr != IntPtr.Zero)
+ MarshalHelper.ToManaged(ref param2, param2Ptr, types[1].IsByRef);
+ if (param3Ptr != IntPtr.Zero)
+ MarshalHelper.ToManaged(ref param3, param3Ptr, types[2].IsByRef);
+
+ TRet ret = deleg(instancePtr.Target, ref param1, ref param2, ref param3);
+
+ // Marshal reference parameters back to original unmanaged references
+ if (param1Ptr != IntPtr.Zero && types[0].IsByRef)
+ MarshalHelper.ToNative(ref param1, param1Ptr);
+ if (param2Ptr != IntPtr.Zero && types[1].IsByRef)
+ MarshalHelper.ToNative(ref param2, param2Ptr);
+ if (param3Ptr != IntPtr.Zero && types[2].IsByRef)
+ MarshalHelper.ToNative(ref param3, param3Ptr);
+
+ return MarshalReturnValue(ref ret);
+ }
+
+ [DebuggerStepThrough]
+ internal static unsafe IntPtr InvokeThunk(object delegateContext, ManagedHandle instancePtr, IntPtr* paramPtrs)
+ {
+ ThunkInvokerDelegate deleg = Unsafe.As(delegateContext);
+
+ T1 param1 = MarshalHelper.ToManagedUnbox(paramPtrs[0]);
+ T2 param2 = MarshalHelper.ToManagedUnbox(paramPtrs[1]);
+ T3 param3 = MarshalHelper.ToManagedUnbox(paramPtrs[2]);
+
+ TRet ret = deleg(instancePtr.Target, param1, param2, param3);
+
+ return MarshalReturnValueThunk(ref ret);
+ }
+ }
+
+ internal static class InvokerRet4
+ {
+ internal delegate TRet InvokerDelegate(object instance, ref T1 param1, ref T2 param2, ref T3 param3, ref T4 param4);
+
+ internal delegate TRet ThunkInvokerDelegate(object instance, T1 param1, T2 param2, T3 param3, T4 param4);
+
+ internal static object CreateDelegate(MethodInfo method)
+ {
+ return new Tuple(method.GetParameterTypes(), Unsafe.As(CreateDelegateFromMethod(method)));
+ }
+
+ internal static object CreateInvokerDelegate(MethodInfo method)
+ {
+ return Unsafe.As(CreateDelegateFromMethod(method, false));
+ }
+
+ [DebuggerStepThrough]
+ internal static IntPtr MarshalAndInvoke(object delegateContext, ManagedHandle instancePtr, IntPtr paramPtr)
+ {
+ (Type[] types, InvokerDelegate deleg) = (Tuple)(delegateContext);
+
+ IntPtr param1Ptr = Marshal.ReadIntPtr(paramPtr);
+ IntPtr param2Ptr = Marshal.ReadIntPtr(paramPtr + IntPtr.Size);
+ IntPtr param3Ptr = Marshal.ReadIntPtr(paramPtr + IntPtr.Size + IntPtr.Size);
+ IntPtr param4Ptr = Marshal.ReadIntPtr(paramPtr + IntPtr.Size + IntPtr.Size + IntPtr.Size);
+
+ T1 param1 = default;
+ T2 param2 = default;
+ T3 param3 = default;
+ T4 param4 = default;
+ if (param1Ptr != IntPtr.Zero)
+ MarshalHelper.ToManaged(ref param1, param1Ptr, types[0].IsByRef);
+ if (param2Ptr != IntPtr.Zero)
+ MarshalHelper.ToManaged(ref param2, param2Ptr, types[1].IsByRef);
+ if (param3Ptr != IntPtr.Zero)
+ MarshalHelper.ToManaged(ref param3, param3Ptr, types[2].IsByRef);
+ if (param4Ptr != IntPtr.Zero)
+ MarshalHelper.ToManaged(ref param4, param4Ptr, types[3].IsByRef);
+
+ TRet ret = deleg(instancePtr.Target, ref param1, ref param2, ref param3, ref param4);
+
+ // Marshal reference parameters back to original unmanaged references
+ if (param1Ptr != IntPtr.Zero && types[0].IsByRef)
+ MarshalHelper