diff --git a/.github/workflows/build_android.yml b/.github/workflows/build_android.yml index 2c99ad118..f25e6836b 100644 --- a/.github/workflows/build_android.yml +++ b/.github/workflows/build_android.yml @@ -5,6 +5,7 @@ jobs: # Game game-windows: + if: ${{ false }} name: Game (Android, Release ARM64) runs-on: "windows-2019" steps: diff --git a/.github/workflows/build_linux.yml b/.github/workflows/build_linux.yml index a860010fd..e04e10a5c 100644 --- a/.github/workflows/build_linux.yml +++ b/.github/workflows/build_linux.yml @@ -5,6 +5,7 @@ jobs: # Editor editor-linux: + if: ${{ false }} name: Editor (Linux, Development x64) runs-on: "ubuntu-20.04" steps: @@ -28,6 +29,7 @@ jobs: # Game game-linux: + if: ${{ false }} name: Game (Linux, Release x64) runs-on: "ubuntu-20.04" steps: diff --git a/.github/workflows/build_mac.yml b/.github/workflows/build_mac.yml index 33e2668ae..ea658efc3 100644 --- a/.github/workflows/build_mac.yml +++ b/.github/workflows/build_mac.yml @@ -5,6 +5,7 @@ jobs: # Editor editor-mac: + if: ${{ false }} name: Editor (Mac, Development x64) runs-on: "macos-latest" steps: @@ -22,6 +23,7 @@ jobs: # Game game-mac: + if: ${{ false }} name: Game (Mac, Release x64) runs-on: "macos-latest" steps: diff --git a/.github/workflows/build_windows.yml b/.github/workflows/build_windows.yml index ac88b30f4..d107f074a 100644 --- a/.github/workflows/build_windows.yml +++ b/.github/workflows/build_windows.yml @@ -6,12 +6,19 @@ 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: Check .NET versions + run: | + dotnet --list-sdks - name: Checkout LFS run: | git lfs version @@ -23,12 +30,16 @@ jobs: # 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: Checkout LFS run: | git lfs version diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index e326fa28d..2a90f9b27 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -5,6 +5,7 @@ jobs: # Tests tests-linux: + if: ${{ false }} name: Tests (Linux) runs-on: "ubuntu-20.04" steps: diff --git a/Development/Scripts/Linux/CallBuildTool.sh b/Development/Scripts/Linux/CallBuildTool.sh index 3052ee241..9aaeca75a 100755 --- a/Development/Scripts/Linux/CallBuildTool.sh +++ b/Development/Scripts/Linux/CallBuildTool.sh @@ -10,8 +10,9 @@ 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,Clean /property:RestorePackagesConfig=True /p:RuntimeIdentifiers=linux-x64 +dotnet msbuild /nologo /verbosity:quiet "Source/Tools/Flax.Build/Flax.Build.csproj" /property:Configuration=Release /target:Build /property:SelfContained=False /property: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/Windows/CallBuildTool.bat b/Development/Scripts/Windows/CallBuildTool.bat index 396d59da4..a01a7dbcc 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 bd64ab7e8..52c466fd7 100644 --- a/Flax.flaxproj +++ b/Flax.flaxproj @@ -11,6 +11,7 @@ "EditorTarget": "FlaxEditor", "Configuration": { "UseCSharp": true, - "UseLargeWorlds": false + "UseLargeWorlds": false, + "UseDotNet": true } } \ No newline at end of file diff --git a/Source/Editor/Content/Import/AudioImportSettings.cs b/Source/Editor/Content/Import/AudioImportSettings.cs index 19ad1065d..40e715c06 100644 --- a/Source/Editor/Content/Import/AudioImportSettings.cs +++ b/Source/Editor/Content/Import/AudioImportSettings.cs @@ -4,6 +4,7 @@ using System.ComponentModel; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; +using System.Runtime.InteropServices.Marshalling; using FlaxEngine; namespace FlaxEditor.Content.Import @@ -93,6 +94,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 +146,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 +184,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 = "FlaxEditor.Content.Import.AudioImportEntry::Internal_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 bded33956..2b959ddb1 100644 --- a/Source/Editor/Content/Import/ModelImportEntry.cs +++ b/Source/Editor/Content/Import/ModelImportEntry.cs @@ -1,8 +1,10 @@ // Copyright (c) 2012-2022 Wojciech Figat. All rights reserved. +using System; using System.ComponentModel; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; +using System.Runtime.InteropServices.Marshalling; using FlaxEngine; namespace FlaxEditor.Content.Import @@ -355,6 +357,7 @@ namespace FlaxEditor.Content.Import private bool ShowAnimation => Type == ModelType.Animation; [StructLayout(LayoutKind.Sequential)] + [NativeMarshalling(typeof(InternalOptionsMarshaler))] internal struct InternalOptions { public ModelType Type; @@ -410,6 +413,162 @@ namespace FlaxEditor.Content.Import public int ObjectIndex; } + [CustomMarshaller(typeof(InternalOptions), MarshalMode.Default, typeof(InternalOptionsMarshaler))] + internal static class InternalOptionsMarshaler + { + [Unmanaged] + [StructLayout(LayoutKind.Sequential)] + internal struct InternalOptionsNative + { + public int 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 int LightmapUVsSource; + //[MarshalAs(UnmanagedType.LPWStr)] + public IntPtr CollisionMeshesPrefix; + + // Transform + public float Scale; + public Quaternion Rotation; + public Float3 Translation; + public byte CenterGeometry; + + // Animation + public int Duration; + public float FramesRangeStart; + public float FramesRangeEnd; + public float DefaultFrameRate; + public float SamplingRate; + public byte SkipEmptyCurves; + public byte OptimizeKeyframes; + public byte EnableRootMotion; + //[MarshalAs(UnmanagedType.LPWStr)] + public IntPtr 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 static InternalOptions ConvertToManaged(InternalOptionsNative unmanaged) => ToManaged(unmanaged); + internal static InternalOptionsNative ConvertToUnmanaged(InternalOptions managed) => ToNative(managed); + + internal static InternalOptions ToManaged(InternalOptionsNative managed) + { + return new InternalOptions() + { + Type = (ModelType)managed.Type, + CalculateNormals = managed.CalculateNormals, + SmoothingNormalsAngle = managed.SmoothingNormalsAngle, + FlipNormals = managed.FlipNormals, + SmoothingTangentsAngle = managed.SmoothingTangentsAngle, + CalculateTangents = managed.CalculateTangents, + OptimizeMeshes = managed.OptimizeMeshes, + MergeMeshes = managed.MergeMeshes, + ImportLODs = managed.ImportLODs, + ImportVertexColors = managed.ImportVertexColors, + ImportBlendShapes = managed.ImportBlendShapes, + LightmapUVsSource = (ModelLightmapUVsSource)managed.LightmapUVsSource, + CollisionMeshesPrefix = ManagedString.ToManaged(managed.CollisionMeshesPrefix), + Scale = managed.Scale, + Rotation = managed.Rotation, + Translation = managed.Translation, + CenterGeometry = managed.CenterGeometry, + Duration = (AnimationDuration)managed.Duration, + FramesRangeStart = managed.FramesRangeStart, + FramesRangeEnd = managed.FramesRangeEnd, + DefaultFrameRate = managed.DefaultFrameRate, + SamplingRate = managed.SamplingRate, + SkipEmptyCurves = managed.SkipEmptyCurves, + OptimizeKeyframes = managed.OptimizeKeyframes, + EnableRootMotion = managed.EnableRootMotion, + RootNodeName = ManagedString.ToManaged(managed.RootNodeName), + GenerateLODs = managed.GenerateLODs, + BaseLOD = managed.BaseLOD, + LODCount = managed.LODCount, + TriangleReduction = managed.TriangleReduction, + ImportMaterials = managed.ImportMaterials, + ImportTextures = managed.ImportTextures, + RestoreMaterialsOnReimport = managed.RestoreMaterialsOnReimport, + GenerateSDF = managed.GenerateSDF, + SDFResolution = managed.SDFResolution, + SplitObjects = managed.SplitObjects, + ObjectIndex = managed.ObjectIndex, + }; + } + internal static InternalOptionsNative ToNative(InternalOptions managed) + { + return new InternalOptionsNative() + { + Type = (int)managed.Type, + CalculateNormals = managed.CalculateNormals, + SmoothingNormalsAngle = managed.SmoothingNormalsAngle, + FlipNormals = managed.FlipNormals, + SmoothingTangentsAngle = managed.SmoothingTangentsAngle, + CalculateTangents = managed.CalculateTangents, + OptimizeMeshes = managed.OptimizeMeshes, + MergeMeshes = managed.MergeMeshes, + ImportLODs = managed.ImportLODs, + ImportVertexColors = managed.ImportVertexColors, + ImportBlendShapes = managed.ImportBlendShapes, + LightmapUVsSource = (int)managed.LightmapUVsSource, + CollisionMeshesPrefix = ManagedString.ToNative(managed.CollisionMeshesPrefix), + Scale = managed.Scale, + Rotation = managed.Rotation, + Translation = managed.Translation, + CenterGeometry = managed.CenterGeometry, + Duration = (int)managed.Duration, + FramesRangeStart = managed.FramesRangeStart, + FramesRangeEnd = managed.FramesRangeEnd, + DefaultFrameRate = managed.DefaultFrameRate, + SamplingRate = managed.SamplingRate, + SkipEmptyCurves = managed.SkipEmptyCurves, + OptimizeKeyframes = managed.OptimizeKeyframes, + EnableRootMotion = managed.EnableRootMotion, + RootNodeName = ManagedString.ToNative(managed.RootNodeName), + GenerateLODs = managed.GenerateLODs, + BaseLOD = managed.BaseLOD, + LODCount = managed.LODCount, + TriangleReduction = managed.TriangleReduction, + ImportMaterials = managed.ImportMaterials, + ImportTextures = managed.ImportTextures, + RestoreMaterialsOnReimport = managed.RestoreMaterialsOnReimport, + GenerateSDF = managed.GenerateSDF, + SDFResolution = managed.SDFResolution, + SplitObjects = managed.SplitObjects, + ObjectIndex = managed.ObjectIndex, + }; + } + internal static void Free(InternalOptionsNative unmanaged) + { + } + } + internal void ToInternal(out InternalOptions options) { options = new InternalOptions @@ -511,7 +670,7 @@ namespace FlaxEditor.Content.Import /// Model asset import entry. /// /// - public class ModelImportEntry : AssetImportEntry + public partial class ModelImportEntry : AssetImportEntry { private ModelImportSettings _settings = new ModelImportSettings(); @@ -548,8 +707,8 @@ namespace FlaxEditor.Content.Import #region Internal Calls - [MethodImpl(MethodImplOptions.InternalCall)] - internal static extern void Internal_GetModelImportOptions(string path, out ModelImportSettings.InternalOptions result); + [LibraryImport("FlaxEngine", EntryPoint = "FlaxEditor.Content.Import.ModelImportEntry::Internal_GetModelImportOptions", StringMarshalling = StringMarshalling.Custom, StringMarshallingCustomType = typeof(StringMarshaller))] + internal static partial 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 0ac44ec0b..abffe0ce7 100644 --- a/Source/Editor/Content/Import/TextureImportEntry.cs +++ b/Source/Editor/Content/Import/TextureImportEntry.cs @@ -1,10 +1,12 @@ // Copyright (c) 2012-2022 Wojciech Figat. All rights reserved. +using System; using System.Collections.Generic; using System.ComponentModel; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; +using System.Runtime.InteropServices.Marshalling; using FlaxEngine; // ReSharper disable InconsistentNaming @@ -297,6 +299,7 @@ namespace FlaxEditor.Content.Import public List Sprites = new List(); [StructLayout(LayoutKind.Sequential)] + [NativeMarshalling(typeof(InternalOptionsMarshaler))] internal struct InternalOptions { public TextureFormatType Type; @@ -318,6 +321,85 @@ namespace FlaxEditor.Content.Import public string[] SpriteNames; } + [CustomMarshaller(typeof(InternalOptions), MarshalMode.Default, typeof(InternalOptionsMarshaler))] + internal static class InternalOptionsMarshaler + { + [StructLayout(LayoutKind.Sequential)] + internal struct InternalOptionsNative + { + public byte 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 IntPtr SpriteAreas; + public IntPtr SpriteNames; + } + + internal static InternalOptions ConvertToManaged(InternalOptionsNative unmanaged) => ToManaged(unmanaged); + internal static InternalOptionsNative ConvertToUnmanaged(InternalOptions managed) => ToNative(managed); + + internal static InternalOptions ToManaged(InternalOptionsNative managed) + { + return new InternalOptions() + { + Type = (TextureFormatType)managed.Type, + IsAtlas = managed.IsAtlas, + NeverStream = managed.NeverStream, + Compress = managed.Compress, + IndependentChannels = managed.IndependentChannels, + sRGB = managed.sRGB, + GenerateMipMaps = managed.GenerateMipMaps, + FlipY = managed.FlipY, + Resize = managed.Resize, + PreserveAlphaCoverage = managed.PreserveAlphaCoverage, + PreserveAlphaCoverageReference = managed.PreserveAlphaCoverageReference, + Scale = managed.Scale, + MaxSize = managed.MaxSize, + TextureGroup = managed.TextureGroup, + Size = managed.Size, + SpriteAreas = managed.SpriteAreas != IntPtr.Zero ? NativeInterop.GCHandleArrayToManagedArray((ManagedArray)GCHandle.FromIntPtr(managed.SpriteAreas).Target) : null, + SpriteNames = managed.SpriteNames != IntPtr.Zero ? NativeInterop.GCHandleArrayToManagedArray((ManagedArray)GCHandle.FromIntPtr(managed.SpriteNames).Target) : null, + }; + } + internal static InternalOptionsNative ToNative(InternalOptions managed) + { + return new InternalOptionsNative() + { + Type = (byte)managed.Type, + IsAtlas = managed.IsAtlas, + NeverStream = managed.NeverStream, + Compress = managed.Compress, + IndependentChannels = managed.IndependentChannels, + sRGB = managed.sRGB, + GenerateMipMaps = managed.GenerateMipMaps, + FlipY = managed.FlipY, + Resize = managed.Resize, + PreserveAlphaCoverage = managed.PreserveAlphaCoverage, + PreserveAlphaCoverageReference = managed.PreserveAlphaCoverageReference, + Scale = managed.Scale, + MaxSize = managed.MaxSize, + TextureGroup = managed.TextureGroup, + Size = managed.Size, + SpriteAreas = managed.SpriteAreas?.Length > 0 ? GCHandle.ToIntPtr(GCHandle.Alloc(ManagedArray.Get(NativeInterop.ManagedArrayToGCHandleArray(managed.SpriteAreas)))) : IntPtr.Zero, + SpriteNames = managed.SpriteNames?.Length > 0 ? GCHandle.ToIntPtr(GCHandle.Alloc(ManagedArray.Get(NativeInterop.ManagedArrayToGCHandleArray(managed.SpriteNames)))) : IntPtr.Zero, + }; + } + internal static void Free(InternalOptionsNative unmanaged) + { + } + } + internal void ToInternal(out InternalOptions options) { options = new InternalOptions @@ -406,7 +488,7 @@ namespace FlaxEditor.Content.Import /// Texture asset import entry. /// /// - public class TextureImportEntry : AssetImportEntry + public partial class TextureImportEntry : AssetImportEntry { private TextureImportSettings _settings = new TextureImportSettings(); @@ -509,8 +591,9 @@ namespace FlaxEditor.Content.Import #region Internal Calls - [MethodImpl(MethodImplOptions.InternalCall)] - internal static extern bool Internal_GetTextureImportOptions(string path, out TextureImportSettings.InternalOptions result); + [LibraryImport("FlaxEngine", EntryPoint = "FlaxEditor.Content.Import.TextureImportEntry::Internal_GetTextureImportOptions", StringMarshalling = StringMarshalling.Custom, StringMarshallingCustomType = typeof(FlaxEngine.StringMarshaller))] + [return: MarshalAs(UnmanagedType.U1)] + internal static partial bool Internal_GetTextureImportOptions(string path, out TextureImportSettings.InternalOptions result); #endregion } diff --git a/Source/Editor/CustomEditors/CustomEditorsUtil.cs b/Source/Editor/CustomEditors/CustomEditorsUtil.cs index 4b934cbd6..4df247786 100644 --- a/Source/Editor/CustomEditors/CustomEditorsUtil.cs +++ b/Source/Editor/CustomEditors/CustomEditorsUtil.cs @@ -4,13 +4,15 @@ 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.Editors; using FlaxEditor.Scripting; using FlaxEngine; namespace FlaxEditor.CustomEditors { - internal static class CustomEditorsUtil + internal static partial class CustomEditorsUtil { internal static readonly Dictionary InBuildTypeNames = new Dictionary() { @@ -129,7 +131,8 @@ namespace FlaxEditor.CustomEditors return new GenericEditor(); } - [MethodImpl(MethodImplOptions.InternalCall)] - internal static extern Type Internal_GetCustomEditor(Type targetType); + [LibraryImport("FlaxEngine", EntryPoint = "FlaxEditor.CustomEditors.CustomEditorsUtil::Internal_GetCustomEditor", StringMarshalling = StringMarshalling.Custom, StringMarshallingCustomType = typeof(FlaxEngine.StringMarshaller))] + [return: MarshalUsing(typeof(SystemTypeMarshaller))] + internal static partial Type Internal_GetCustomEditor([MarshalUsing(typeof(SystemTypeMarshaller))] Type targetType); } } diff --git a/Source/Editor/Editor.cs b/Source/Editor/Editor.cs index 421512ce0..9eeb4bb35 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; @@ -20,6 +21,8 @@ using FlaxEngine.Assertions; using FlaxEngine.GUI; using FlaxEngine.Json; +#pragma warning disable CS1591 + namespace FlaxEditor { /// @@ -59,17 +62,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 = "FlaxEditor.Editor::IsDevInstance", StringMarshalling = StringMarshalling.Custom, StringMarshallingCustomType = typeof(FlaxEngine.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 = "FlaxEditor.Editor::IsOfficialBuild", StringMarshalling = StringMarshalling.Custom, StringMarshallingCustomType = typeof(FlaxEngine.StringMarshaller))] + [return: MarshalAs(UnmanagedType.U1)] + internal static partial bool IsOfficialBuild(); - [MethodImpl(MethodImplOptions.InternalCall)] - internal static extern bool Internal_IsPlayMode(); + [LibraryImport("FlaxEngine", EntryPoint = "FlaxEditor.Editor::Internal_IsPlayMode", StringMarshalling = StringMarshalling.Custom, StringMarshallingCustomType = typeof(FlaxEngine.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. @@ -1206,6 +1212,7 @@ namespace FlaxEditor } [StructLayout(LayoutKind.Sequential)] + [NativeMarshalling(typeof(VisualScriptLocalMarshaller))] internal struct VisualScriptLocal { public string Value; @@ -1214,7 +1221,48 @@ 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) + { + } + } + [StructLayout(LayoutKind.Sequential)] + [NativeMarshalling(typeof(VisualScriptStackFrameMarshaller))] internal struct VisualScriptStackFrame { public VisualScript Script; @@ -1222,6 +1270,43 @@ 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)) @@ -1419,116 +1504,133 @@ namespace FlaxEditor Instance.StateMachine.StateChanged += RequestStartPlayOnEditMode; } - [MethodImpl(MethodImplOptions.InternalCall)] - internal static extern int Internal_ReadOutputLogs(string[] outMessages, byte[] outLogTypes, long[] outLogTimes); + [LibraryImport("FlaxEngine", EntryPoint = "FlaxEditor.Editor::Internal_ReadOutputLogs", StringMarshalling = StringMarshalling.Custom, StringMarshallingCustomType = typeof(FlaxEngine.StringMarshaller))] + internal static partial int Internal_ReadOutputLogs([MarshalUsing(typeof(FlaxEngine.ArrayMarshaller<,>), CountElementName = "outCapacity")] ref string[] outMessages, [MarshalUsing(typeof(FlaxEngine.ArrayMarshaller<,>), CountElementName = "outCapacity")] ref byte[] outLogTypes, [MarshalUsing(typeof(FlaxEngine.ArrayMarshaller<,>), CountElementName = "outCapacity")] ref long[] outLogTimes, int outCapacity); - [MethodImpl(MethodImplOptions.InternalCall)] - internal static extern void Internal_SetPlayMode(bool value); + [LibraryImport("FlaxEngine", EntryPoint = "FlaxEditor.Editor::Internal_SetPlayMode", StringMarshalling = StringMarshalling.Custom, StringMarshallingCustomType = typeof(FlaxEngine.StringMarshaller))] + internal static partial void Internal_SetPlayMode([MarshalAs(UnmanagedType.U1)] bool value); - [MethodImpl(MethodImplOptions.InternalCall)] - internal static extern string Internal_GetProjectPath(); + [LibraryImport("FlaxEngine", EntryPoint = "FlaxEditor.Editor::Internal_GetProjectPath", StringMarshalling = StringMarshalling.Custom, StringMarshallingCustomType = typeof(FlaxEngine.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 = "FlaxEditor.Editor::Internal_CloneAssetFile", StringMarshalling = StringMarshalling.Custom, StringMarshallingCustomType = typeof(FlaxEngine.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 = "FlaxEditor.Editor::Internal_Import", StringMarshalling = StringMarshalling.Custom, StringMarshallingCustomType = typeof(FlaxEngine.StringMarshaller))] + [return: MarshalAs(UnmanagedType.U1)] + internal static partial bool Internal_Import(string inputPath, string outputPath, IntPtr arg); - [MethodImpl(MethodImplOptions.InternalCall)] - internal static extern bool Internal_ImportTexture(string inputPath, string outputPath, ref TextureImportSettings.InternalOptions options); + [LibraryImport("FlaxEngine", EntryPoint = "FlaxEditor.Editor::Internal_ImportTexture", StringMarshalling = StringMarshalling.Custom, StringMarshallingCustomType = typeof(FlaxEngine.StringMarshaller))] + [return: MarshalAs(UnmanagedType.U1)] + internal static partial bool Internal_ImportTexture(string inputPath, string outputPath, ref TextureImportSettings.InternalOptions options); - [MethodImpl(MethodImplOptions.InternalCall)] - internal static extern bool Internal_ImportModel(string inputPath, string outputPath, ref ModelImportSettings.InternalOptions options); + [LibraryImport("FlaxEngine", EntryPoint = "FlaxEditor.Editor::Internal_ImportModel", StringMarshalling = StringMarshalling.Custom, StringMarshallingCustomType = typeof(FlaxEngine.StringMarshaller))] + [return: MarshalAs(UnmanagedType.U1)] + internal static partial bool Internal_ImportModel(string inputPath, string outputPath, ref ModelImportSettings.InternalOptions options); - [MethodImpl(MethodImplOptions.InternalCall)] - internal static extern bool Internal_ImportAudio(string inputPath, string outputPath, ref AudioImportSettings.InternalOptions options); + [LibraryImport("FlaxEngine", EntryPoint = "FlaxEditor.Editor::Internal_ImportAudio", StringMarshalling = StringMarshalling.Custom, StringMarshallingCustomType = typeof(FlaxEngine.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 void Internal_GetAudioClipMetadata(IntPtr obj, out int originalSize, out int importedSize); + [LibraryImport("FlaxEngine", EntryPoint = "FlaxEditor.Editor::Internal_GetAudioClipMetadata", StringMarshalling = StringMarshalling.Custom, StringMarshallingCustomType = typeof(FlaxEngine.StringMarshaller))] + internal static partial void Internal_GetAudioClipMetadata(IntPtr obj, out int originalSize, out int importedSize); - [MethodImpl(MethodImplOptions.InternalCall)] - internal static extern bool Internal_SaveJsonAsset(string outputPath, string data, string typename); + [LibraryImport("FlaxEngine", EntryPoint = "FlaxEditor.Editor::Internal_SaveJsonAsset", StringMarshalling = StringMarshalling.Custom, StringMarshallingCustomType = typeof(FlaxEngine.StringMarshaller))] + [return: MarshalAs(UnmanagedType.U1)] + internal static partial bool Internal_SaveJsonAsset(string outputPath, string data, string typename); - [MethodImpl(MethodImplOptions.InternalCall)] - internal static extern void Internal_CopyCache(ref Guid dstId, ref Guid srcId); + [LibraryImport("FlaxEngine", EntryPoint = "FlaxEditor.Editor::Internal_CopyCache", StringMarshalling = StringMarshalling.Custom, StringMarshallingCustomType = typeof(FlaxEngine.StringMarshaller))] + internal static partial void Internal_CopyCache(ref Guid dstId, ref Guid srcId); - [MethodImpl(MethodImplOptions.InternalCall)] - internal static extern void Internal_BakeLightmaps(bool cancel); + [LibraryImport("FlaxEngine", EntryPoint = "FlaxEditor.Editor::Internal_BakeLightmaps", StringMarshalling = StringMarshalling.Custom, StringMarshallingCustomType = typeof(FlaxEngine.StringMarshaller))] + internal static partial void Internal_BakeLightmaps([MarshalAs(UnmanagedType.U1)] bool cancel); - [MethodImpl(MethodImplOptions.InternalCall)] - internal static extern string Internal_GetShaderAssetSourceCode(IntPtr obj); + [LibraryImport("FlaxEngine", EntryPoint = "FlaxEditor.Editor::Internal_GetShaderAssetSourceCode", StringMarshalling = StringMarshalling.Custom, StringMarshallingCustomType = typeof(FlaxEngine.StringMarshaller))] + internal static partial string Internal_GetShaderAssetSourceCode(IntPtr obj); - [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 = "FlaxEditor.Editor::Internal_CookMeshCollision", StringMarshalling = StringMarshalling.Custom, StringMarshallingCustomType = typeof(FlaxEngine.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_GetCollisionWires(IntPtr collisionData, out Float3[] triangles, out int[] indices); + [LibraryImport("FlaxEngine", EntryPoint = "FlaxEditor.Editor::Internal_GetCollisionWires", StringMarshalling = StringMarshalling.Custom, StringMarshallingCustomType = typeof(FlaxEngine.StringMarshaller))] + internal static partial void Internal_GetCollisionWires(IntPtr collisionData, [MarshalUsing(typeof(FlaxEngine.ArrayMarshaller<,>), ConstantElementCount = 1)] out Float3[] triangles, [MarshalUsing(typeof(FlaxEngine.ArrayMarshaller<,>), ConstantElementCount = 1)] out int[] indices); - [MethodImpl(MethodImplOptions.InternalCall)] - internal static extern void Internal_GetEditorBoxWithChildren(IntPtr obj, out BoundingBox resultAsRef); + [LibraryImport("FlaxEngine", EntryPoint = "FlaxEditor.Editor::Internal_GetEditorBoxWithChildren", StringMarshalling = StringMarshalling.Custom, StringMarshallingCustomType = typeof(FlaxEngine.StringMarshaller))] + internal static partial void Internal_GetEditorBoxWithChildren(IntPtr obj, out BoundingBox resultAsRef); - [MethodImpl(MethodImplOptions.InternalCall)] - internal static extern void Internal_SetOptions(ref InternalOptions options); + [LibraryImport("FlaxEngine", EntryPoint = "FlaxEditor.Editor::Internal_SetOptions", StringMarshalling = StringMarshalling.Custom, StringMarshallingCustomType = typeof(FlaxEngine.StringMarshaller))] + internal static partial void Internal_SetOptions(ref InternalOptions options); - [MethodImpl(MethodImplOptions.InternalCall)] - internal static extern void Internal_DrawNavMesh(); + [LibraryImport("FlaxEngine", EntryPoint = "FlaxEditor.Editor::Internal_DrawNavMesh", StringMarshalling = StringMarshalling.Custom, StringMarshallingCustomType = typeof(FlaxEngine.StringMarshaller))] + internal static partial void Internal_DrawNavMesh(); - [MethodImpl(MethodImplOptions.InternalCall)] - internal static extern void Internal_CloseSplashScreen(); + [LibraryImport("FlaxEngine", EntryPoint = "FlaxEditor.Editor::Internal_CloseSplashScreen", StringMarshalling = StringMarshalling.Custom, StringMarshallingCustomType = typeof(FlaxEngine.StringMarshaller))] + internal static partial void Internal_CloseSplashScreen(); - [MethodImpl(MethodImplOptions.InternalCall)] - internal static extern bool Internal_CreateAsset(NewAssetType type, string outputPath); + [LibraryImport("FlaxEngine", EntryPoint = "FlaxEditor.Editor::Internal_CreateAsset", StringMarshalling = StringMarshalling.Custom, StringMarshallingCustomType = typeof(FlaxEngine.StringMarshaller))] + [return: MarshalAs(UnmanagedType.U1)] + internal static partial bool Internal_CreateAsset(NewAssetType type, string outputPath); - [MethodImpl(MethodImplOptions.InternalCall)] - internal static extern bool Internal_CreateVisualScript(string outputPath, string baseTypename); + [LibraryImport("FlaxEngine", EntryPoint = "FlaxEditor.Editor::Internal_CreateVisualScript", StringMarshalling = StringMarshalling.Custom, StringMarshallingCustomType = typeof(FlaxEngine.StringMarshaller))] + [return: MarshalAs(UnmanagedType.U1)] + internal static partial bool Internal_CreateVisualScript(string outputPath, string baseTypename); - [MethodImpl(MethodImplOptions.InternalCall)] - internal static extern string Internal_CanImport(string extension); + [LibraryImport("FlaxEngine", EntryPoint = "FlaxEditor.Editor::Internal_CanImport", StringMarshalling = StringMarshalling.Custom, StringMarshallingCustomType = typeof(FlaxEngine.StringMarshaller))] + internal static partial string Internal_CanImport(string extension); - [MethodImpl(MethodImplOptions.InternalCall)] - internal static extern bool Internal_CanExport(string path); + [LibraryImport("FlaxEngine", EntryPoint = "FlaxEditor.Editor::Internal_CanExport", StringMarshalling = StringMarshalling.Custom, StringMarshallingCustomType = typeof(FlaxEngine.StringMarshaller))] + [return: MarshalAs(UnmanagedType.U1)] + internal static partial bool Internal_CanExport(string path); - [MethodImpl(MethodImplOptions.InternalCall)] - internal static extern bool Internal_Export(string inputPath, string outputFolder); + [LibraryImport("FlaxEngine", EntryPoint = "FlaxEditor.Editor::Internal_Export", StringMarshalling = StringMarshalling.Custom, StringMarshallingCustomType = typeof(FlaxEngine.StringMarshaller))] + [return: MarshalAs(UnmanagedType.U1)] + internal static partial bool Internal_Export(string inputPath, string outputFolder); - [MethodImpl(MethodImplOptions.InternalCall)] - internal static extern bool Internal_GetIsEveryAssemblyLoaded(); + [LibraryImport("FlaxEngine", EntryPoint = "FlaxEditor.Editor::Internal_GetIsEveryAssemblyLoaded", StringMarshalling = StringMarshalling.Custom, StringMarshallingCustomType = typeof(FlaxEngine.StringMarshaller))] + [return: MarshalAs(UnmanagedType.U1)] + internal static partial bool Internal_GetIsEveryAssemblyLoaded(); - [MethodImpl(MethodImplOptions.InternalCall)] - internal static extern int Internal_GetLastProjectOpenedEngineBuild(); + [LibraryImport("FlaxEngine", EntryPoint = "FlaxEditor.Editor::Internal_GetLastProjectOpenedEngineBuild", StringMarshalling = StringMarshalling.Custom, StringMarshallingCustomType = typeof(FlaxEngine.StringMarshaller))] + internal static partial int Internal_GetLastProjectOpenedEngineBuild(); - [MethodImpl(MethodImplOptions.InternalCall)] - internal static extern bool Internal_GetIsCSGActive(); + [LibraryImport("FlaxEngine", EntryPoint = "FlaxEditor.Editor::Internal_GetIsCSGActive", StringMarshalling = StringMarshalling.Custom, StringMarshallingCustomType = typeof(FlaxEngine.StringMarshaller))] + [return: MarshalAs(UnmanagedType.U1)] + internal static partial bool Internal_GetIsCSGActive(); - [MethodImpl(MethodImplOptions.InternalCall)] - internal static extern void Internal_RunVisualScriptBreakpointLoopTick(float deltaTime); + [LibraryImport("FlaxEngine", EntryPoint = "FlaxEditor.Editor::Internal_RunVisualScriptBreakpointLoopTick", StringMarshalling = StringMarshalling.Custom, StringMarshallingCustomType = typeof(FlaxEngine.StringMarshaller))] + internal static partial void Internal_RunVisualScriptBreakpointLoopTick(float deltaTime); - [MethodImpl(MethodImplOptions.InternalCall)] - internal static extern VisualScriptLocal[] Internal_GetVisualScriptLocals(); + [LibraryImport("FlaxEngine", EntryPoint = "FlaxEditor.Editor::Internal_GetVisualScriptLocals", StringMarshalling = StringMarshalling.Custom, StringMarshallingCustomType = typeof(FlaxEngine.StringMarshaller))] + [return: MarshalUsing(typeof(FlaxEngine.ArrayMarshaller<,>), ConstantElementCount = 1)] + internal static partial VisualScriptLocal[] Internal_GetVisualScriptLocals(); - [MethodImpl(MethodImplOptions.InternalCall)] - internal static extern VisualScriptStackFrame[] Internal_GetVisualScriptStackFrames(); + [LibraryImport("FlaxEngine", EntryPoint = "FlaxEditor.Editor::Internal_GetVisualScriptStackFrames", StringMarshalling = StringMarshalling.Custom, StringMarshallingCustomType = typeof(FlaxEngine.StringMarshaller))] + [return: MarshalUsing(typeof(FlaxEngine.ArrayMarshaller<,>), ConstantElementCount = 1)] + internal static partial VisualScriptStackFrame[] Internal_GetVisualScriptStackFrames(); - [MethodImpl(MethodImplOptions.InternalCall)] - internal static extern VisualScriptStackFrame Internal_GetVisualScriptPreviousScopeFrame(); + [LibraryImport("FlaxEngine", EntryPoint = "FlaxEditor.Editor::Internal_GetVisualScriptPreviousScopeFrame", StringMarshalling = StringMarshalling.Custom, StringMarshallingCustomType = typeof(FlaxEngine.StringMarshaller))] + internal static partial VisualScriptStackFrame Internal_GetVisualScriptPreviousScopeFrame(); - [MethodImpl(MethodImplOptions.InternalCall)] - internal static extern bool Internal_EvaluateVisualScriptLocal(IntPtr script, ref VisualScriptLocal local); + [LibraryImport("FlaxEngine", EntryPoint = "FlaxEditor.Editor::Internal_EvaluateVisualScriptLocal", StringMarshalling = StringMarshalling.Custom, StringMarshallingCustomType = typeof(FlaxEngine.StringMarshaller))] + [return: MarshalAs(UnmanagedType.U1)] + internal static partial bool Internal_EvaluateVisualScriptLocal(IntPtr script, ref VisualScriptLocal local); - [MethodImpl(MethodImplOptions.InternalCall)] - internal static extern void Internal_DeserializeSceneObject(IntPtr sceneObject, string json); + [LibraryImport("FlaxEngine", EntryPoint = "FlaxEditor.Editor::Internal_DeserializeSceneObject", StringMarshalling = StringMarshalling.Custom, StringMarshallingCustomType = typeof(FlaxEngine.StringMarshaller))] + internal static partial void Internal_DeserializeSceneObject(IntPtr sceneObject, string json); - [MethodImpl(MethodImplOptions.InternalCall)] - internal static extern void Internal_LoadAsset(ref Guid id); + [LibraryImport("FlaxEngine", EntryPoint = "FlaxEditor.Editor::Internal_LoadAsset", StringMarshalling = StringMarshalling.Custom, StringMarshallingCustomType = typeof(FlaxEngine.StringMarshaller))] + internal static partial void Internal_LoadAsset(ref Guid id); - [MethodImpl(MethodImplOptions.InternalCall)] - internal static extern bool Internal_CanSetToRoot(IntPtr prefab, IntPtr newRoot); + [LibraryImport("FlaxEngine", EntryPoint = "FlaxEditor.Editor::Internal_CanSetToRoot", StringMarshalling = StringMarshalling.Custom, StringMarshallingCustomType = typeof(FlaxEngine.StringMarshaller))] + [return: MarshalAs(UnmanagedType.U1)] + internal static partial bool Internal_CanSetToRoot(IntPtr prefab, IntPtr newRoot); - [MethodImpl(MethodImplOptions.InternalCall)] - internal static extern float Internal_GetAnimationTime(IntPtr animatedModel); + [LibraryImport("FlaxEngine", EntryPoint = "FlaxEditor.Editor::Internal_GetAnimationTime", StringMarshalling = StringMarshalling.Custom, StringMarshallingCustomType = typeof(FlaxEngine.StringMarshaller))] + internal static partial float Internal_GetAnimationTime(IntPtr animatedModel); - [MethodImpl(MethodImplOptions.InternalCall)] - internal static extern void Internal_SetAnimationTime(IntPtr animatedModel, float time); + [LibraryImport("FlaxEngine", EntryPoint = "FlaxEditor.Editor::Internal_SetAnimationTime", StringMarshalling = StringMarshalling.Custom, StringMarshallingCustomType = typeof(FlaxEngine.StringMarshaller))] + internal static partial void Internal_SetAnimationTime(IntPtr animatedModel, float time); #endregion } diff --git a/Source/Editor/GUI/Timeline/ParticleSystemTimeline.cs b/Source/Editor/GUI/Timeline/ParticleSystemTimeline.cs index 246c33b4d..cd660daab 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/Managed/ManagedEditor.Internal.cpp b/Source/Editor/Managed/ManagedEditor.Internal.cpp index 3d236cd49..11844b23c 100644 --- a/Source/Editor/Managed/ManagedEditor.Internal.cpp +++ b/Source/Editor/Managed/ManagedEditor.Internal.cpp @@ -323,20 +323,25 @@ namespace CustomEditorsUtilInternal { MonoReflectionType* GetCustomEditor(MonoReflectionType* targetType) { + SCRIPTING_EXPORT("FlaxEditor.CustomEditors.CustomEditorsUtil::Internal_GetCustomEditor") return CustomEditorsUtil::GetCustomEditor(targetType); } } namespace LayersAndTagsSettingsInternal1 { - MonoArray* GetCurrentTags() + MonoArray* GetCurrentTags(int* tagsCount) { + SCRIPTING_EXPORT("FlaxEditor.Content.Settings.LayersAndTagsSettings::GetCurrentTags") + *tagsCount = Level::Tags.Count(); return MUtils::ToArray(Level::Tags); } - MonoArray* GetCurrentLayers() + MonoArray* GetCurrentLayers(int* layersCount) { - return MUtils::ToArray(Span(Level::Layers, Math::Max(1, Level::GetNonEmptyLayerNamesCount()))); + SCRIPTING_EXPORT("FlaxEditor.Content.Settings.LayersAndTagsSettings::GetCurrentLayers") + *layersCount = Math::Max(1, Level::GetNonEmptyLayerNamesCount()); + return MUtils::ToArray(Span(Level::Layers, *layersCount)); } } @@ -344,6 +349,7 @@ namespace GameSettingsInternal1 { void Apply() { + SCRIPTING_EXPORT("FlaxEditor.Content.Settings.GameSettings::Apply") LOG(Info, "Apply game settings"); GameSettings::Load(); } @@ -380,6 +386,7 @@ class ManagedEditorInternal public: static bool IsDevInstance() { + SCRIPTING_EXPORT("FlaxEditor.Editor::IsDevInstance") #if COMPILE_WITH_DEV_ENV return true; #else @@ -389,6 +396,7 @@ public: static bool IsOfficialBuild() { + SCRIPTING_EXPORT("FlaxEditor.Editor::IsOfficialBuild") #if OFFICIAL_BUILD return true; #else @@ -398,18 +406,20 @@ public: static bool IsPlayMode() { + SCRIPTING_EXPORT("FlaxEditor.Editor::Internal_IsPlayMode") return Editor::IsPlayMode; } - static int32 ReadOutputLogs(MonoArray* outMessages, MonoArray* outLogTypes, MonoArray* outLogTimes) + static int32 ReadOutputLogs(MonoArray** outMessages, MonoArray** outLogTypes, MonoArray** outLogTimes, int outArraySize) { + SCRIPTING_EXPORT("FlaxEditor.Editor::Internal_ReadOutputLogs") ScopeLock lock(CachedLogDataLocker); if (CachedLogData.IsEmpty() || CachedLogData.Get() == nullptr) return 0; int32 count = 0; - const int32 maxCount = (int32)mono_array_length(outMessages); + const int32 maxCount = outArraySize;//(int32)mono_array_length(*outMessages); byte* ptr = CachedLogData.Get(); byte* end = ptr + CachedLogData.Count(); @@ -429,9 +439,9 @@ public: auto msgObj = MUtils::ToString(StringView(msg, length)); - mono_array_setref(outMessages, count, msgObj); - mono_array_set(outLogTypes, byte, count, type); - mono_array_set(outLogTimes, int64, count, time); + mono_array_setref(*outMessages, count, msgObj); + mono_array_set(*outLogTypes, byte, count, type); + mono_array_set(*outLogTimes, int64, count, time); count++; } @@ -445,21 +455,25 @@ public: static void SetPlayMode(bool value) { + SCRIPTING_EXPORT("FlaxEditor.Editor::Internal_SetPlayMode") Editor::IsPlayMode = value; } static MonoString* GetProjectPath() { + SCRIPTING_EXPORT("FlaxEditor.Editor::Internal_GetProjectPath") return MUtils::ToString(Editor::Project->ProjectPath); } static void CloseSplashScreen() { + SCRIPTING_EXPORT("FlaxEditor.Editor::Internal_CloseSplashScreen") Editor::CloseSplashScreen(); } static bool CloneAssetFile(MonoString* dstPathObj, MonoString* srcPathObj, Guid* dstId) { + SCRIPTING_EXPORT("FlaxEditor.Editor::Internal_CloneAssetFile") // Get normalized paths String dstPath, srcPath; MUtils::ToString(dstPathObj, dstPath); @@ -489,6 +503,7 @@ public: static bool CreateAsset(NewAssetType type, MonoString* outputPathObj) { + SCRIPTING_EXPORT("FlaxEditor.Editor::Internal_CreateAsset") String tag; switch (type) { @@ -541,6 +556,7 @@ public: static bool CreateVisualScript(MonoString* outputPathObj, MonoString* baseTypenameObj) { + SCRIPTING_EXPORT("FlaxEditor.Editor::Internal_CreateVisualScript") String outputPath; MUtils::ToString(outputPathObj, outputPath); FileSystem::NormalizePath(outputPath); @@ -551,6 +567,7 @@ public: static MonoString* CanImport(MonoString* extensionObj) { + SCRIPTING_EXPORT("FlaxEditor.Editor::Internal_CanImport") String extension; MUtils::ToString(extensionObj, extension); if (extension.Length() > 0 && extension[0] == '.') @@ -561,6 +578,7 @@ public: static bool Import(MonoString* inputPathObj, MonoString* outputPathObj, void* arg) { + SCRIPTING_EXPORT("FlaxEditor.Editor::Internal_Import") String inputPath, outputPath; MUtils::ToString(inputPathObj, inputPath); MUtils::ToString(outputPathObj, outputPath); @@ -572,6 +590,7 @@ public: static bool ImportTexture(MonoString* inputPathObj, MonoString* outputPathObj, InternalTextureOptions* optionsObj) { + SCRIPTING_EXPORT("FlaxEditor.Editor::Internal_ImportTexture") ImportTexture::Options options; InternalTextureOptions::Convert(optionsObj, &options); @@ -580,6 +599,7 @@ public: static bool ImportModel(MonoString* inputPathObj, MonoString* outputPathObj, InternalModelOptions* optionsObj) { + SCRIPTING_EXPORT("FlaxEditor.Editor::Internal_ImportModel") ImportModelFile::Options options; InternalModelOptions::Convert(optionsObj, &options); @@ -588,6 +608,7 @@ public: static bool ImportAudio(MonoString* inputPathObj, MonoString* outputPathObj, InternalAudioOptions* optionsObj) { + SCRIPTING_EXPORT("FlaxEditor.Editor::Internal_ImportAudio") ImportAudio::Options options; InternalAudioOptions::Convert(optionsObj, &options); @@ -596,6 +617,7 @@ public: static void GetAudioClipMetadata(AudioClip* clip, int32* originalSize, int32* importedSize) { + SCRIPTING_EXPORT("FlaxEditor.Editor::Internal_GetAudioClipMetadata") INTERNAL_CALL_CHECK(clip); *originalSize = clip->AudioHeader.OriginalSize; *importedSize = clip->AudioHeader.ImportedSize; @@ -603,6 +625,7 @@ public: static bool SaveJsonAsset(MonoString* outputPathObj, MonoString* dataObj, MonoString* dataTypeNameObj) { + SCRIPTING_EXPORT("FlaxEditor.Editor::Internal_SaveJsonAsset") String outputPath; MUtils::ToString(outputPathObj, outputPath); FileSystem::NormalizePath(outputPath); @@ -623,6 +646,8 @@ public: static bool GetTextureImportOptions(MonoString* pathObj, InternalTextureOptions* result) { + SCRIPTING_EXPORT("FlaxEditor.Content.Import.TextureImportEntry::Internal_GetTextureImportOptions") + String path; MUtils::ToString(pathObj, path); FileSystem::NormalizePath(path); @@ -641,6 +666,7 @@ public: static void GetModelImportOptions(MonoString* pathObj, InternalModelOptions* result) { + SCRIPTING_EXPORT("FlaxEditor.Content.Import.ModelImportEntry::Internal_GetModelImportOptions") // Initialize defaults ImportModelFile::Options options; if (const auto* graphicsSettings = GraphicsSettings::Get()) @@ -660,6 +686,7 @@ public: static bool GetAudioImportOptions(MonoString* pathObj, InternalAudioOptions* result) { + SCRIPTING_EXPORT("FlaxEditor.Content.Import.AudioImportEntry::Internal_GetAudioImportOptions") String path; MUtils::ToString(pathObj, path); FileSystem::NormalizePath(path); @@ -678,6 +705,7 @@ public: static bool CanExport(MonoString* pathObj) { + SCRIPTING_EXPORT("FlaxEditor.Editor::Internal_CanExport") #if COMPILE_WITH_ASSETS_EXPORTER String path; MUtils::ToString(pathObj, path); @@ -691,6 +719,7 @@ public: static bool Export(MonoString* inputPathObj, MonoString* outputFolderObj) { + SCRIPTING_EXPORT("FlaxEditor.Editor::Internal_Export") #if COMPILE_WITH_ASSETS_EXPORTER String inputPath; MUtils::ToString(inputPathObj, inputPath); @@ -708,11 +737,13 @@ public: static void CopyCache(Guid* dstId, Guid* srcId) { + SCRIPTING_EXPORT("FlaxEditor.Editor::Internal_CopyCache") ShaderCacheManager::CopyCache(*dstId, *srcId); } static void BakeLightmaps(bool cancel) { + SCRIPTING_EXPORT("FlaxEditor.Editor::Internal_BakeLightmaps") auto builder = ShadowsOfMordor::Builder::Instance(); if (cancel) builder->CancelBuild(); @@ -722,6 +753,7 @@ public: static MonoString* GetShaderAssetSourceCode(BinaryAsset* obj) { + SCRIPTING_EXPORT("FlaxEditor.Editor::Internal_GetShaderAssetSourceCode") INTERNAL_CALL_CHECK_RETURN(obj, nullptr); if (obj->WaitForLoaded()) DebugLog::ThrowNullReference(); @@ -747,6 +779,7 @@ public: static bool CookMeshCollision(MonoString* pathObj, CollisionDataType type, ModelBase* modelObj, int32 modelLodIndex, uint32 materialSlotsMask, ConvexMeshGenerationFlags convexFlags, int32 convexVertexLimit) { + SCRIPTING_EXPORT("FlaxEditor.Editor::Internal_CookMeshCollision") #if COMPILE_WITH_PHYSICS_COOKING CollisionCooking::Argument arg; String path; @@ -767,6 +800,7 @@ public: static void GetCollisionWires(CollisionData* collisionData, MonoArray** triangles, MonoArray** indices) { + SCRIPTING_EXPORT("FlaxEditor.Editor::Internal_GetCollisionWires") if (!collisionData || collisionData->WaitForLoaded() || collisionData->GetOptions().Type == CollisionDataType::None) return; @@ -792,32 +826,38 @@ public: static void GetEditorBoxWithChildren(Actor* obj, BoundingBox* result) { + SCRIPTING_EXPORT("FlaxEditor.Editor::Internal_GetEditorBoxWithChildren") INTERNAL_CALL_CHECK(obj); *result = obj->GetEditorBoxChildren(); } static void SetOptions(ManagedEditor::InternalOptions* options) { + SCRIPTING_EXPORT("FlaxEditor.Editor::Internal_SetOptions") ManagedEditor::ManagedEditorOptions = *options; } static void DrawNavMesh() { + SCRIPTING_EXPORT("FlaxEditor.Editor::Internal_DrawNavMesh") Navigation::DrawNavMesh(); } static bool GetIsEveryAssemblyLoaded() { + SCRIPTING_EXPORT("FlaxEditor.Editor::Internal_GetIsEveryAssemblyLoaded") return Scripting::IsEveryAssemblyLoaded(); } static int32 GetLastProjectOpenedEngineBuild() { + SCRIPTING_EXPORT("FlaxEditor.Editor::Internal_GetLastProjectOpenedEngineBuild") return Editor::LastProjectOpenedEngineBuild; } static bool GetIsCSGActive() { + SCRIPTING_EXPORT("FlaxEditor.Editor::Internal_GetIsCSGActive") #if COMPILE_WITH_CSG_BUILDER return CSG::Builder::IsActive(); #else @@ -827,6 +867,7 @@ public: static void RunVisualScriptBreakpointLoopTick(float deltaTime) { + SCRIPTING_EXPORT("FlaxEditor.Editor::Internal_RunVisualScriptBreakpointLoopTick") // Update Platform::Tick(); Engine::HasFocus = (Engine::MainWindow && Engine::MainWindow->IsFocused()) || Platform::GetHasFocus(); @@ -921,6 +962,7 @@ public: static MonoArray* GetVisualScriptLocals() { + SCRIPTING_EXPORT("FlaxEditor.Editor::Internal_GetVisualScriptLocals") MonoArray* result = nullptr; const auto stack = VisualScripting::GetThreadStackTop(); if (stack && stack->Scope) @@ -969,6 +1011,7 @@ public: static MonoArray* GetVisualScriptStackFrames() { + SCRIPTING_EXPORT("FlaxEditor.Editor::Internal_GetVisualScriptStackFrames") MonoArray* result = nullptr; const auto stack = VisualScripting::GetThreadStackTop(); if (stack) @@ -1022,6 +1065,7 @@ public: static bool EvaluateVisualScriptLocal(VisualScript* script, VisualScriptLocalManaged* local) { + SCRIPTING_EXPORT("FlaxEditor.Editor::Internal_EvaluateVisualScriptLocal") Variant v; if (VisualScripting::Evaluate(script, VisualScripting::GetThreadStackTop()->Instance, local->NodeId, local->BoxId, v)) { @@ -1034,6 +1078,7 @@ public: static void DeserializeSceneObject(SceneObject* sceneObject, MonoString* jsonObj) { + SCRIPTING_EXPORT("FlaxEditor.Editor::Internal_DeserializeSceneObject") PROFILE_CPU_NAMED("DeserializeSceneObject"); StringAnsi json; @@ -1062,11 +1107,13 @@ public: static void LoadAsset(Guid* id) { + SCRIPTING_EXPORT("FlaxEditor.Editor::Internal_LoadAsset") Content::LoadAsync(*id); } static bool CanSetToRoot(Prefab* prefab, Actor* targetActor) { + SCRIPTING_EXPORT("FlaxEditor.Editor::Internal_CanSetToRoot") // Reference: Prefab::ApplyAll(Actor* targetActor) if (targetActor->GetPrefabID() != prefab->GetID()) return false; @@ -1089,11 +1136,13 @@ public: static float GetAnimationTime(AnimatedModel* animatedModel) { + SCRIPTING_EXPORT("FlaxEditor.Editor::Internal_GetAnimationTime") return animatedModel && animatedModel->GraphInstance.State.Count() == 1 ? animatedModel->GraphInstance.State[0].Animation.TimePosition : 0.0f; } static void SetAnimationTime(AnimatedModel* animatedModel, float time) { + SCRIPTING_EXPORT("FlaxEditor.Editor::Internal_SetAnimationTime") if (animatedModel && animatedModel->GraphInstance.State.Count() == 1) animatedModel->GraphInstance.State[0].Animation.TimePosition = time; } diff --git a/Source/Editor/Modules/ContentImportingModule.cs b/Source/Editor/Modules/ContentImportingModule.cs index 4597f8d74..fa990dd7a 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/CodeDocsModule.cs b/Source/Editor/Modules/SourceCodeEditing/CodeDocsModule.cs index 7042dfb92..07e29f506 100644 --- a/Source/Editor/Modules/SourceCodeEditing/CodeDocsModule.cs +++ b/Source/Editor/Modules/SourceCodeEditing/CodeDocsModule.cs @@ -257,7 +257,7 @@ namespace FlaxEditor.Modules.SourceCodeEditing { Profiler.BeginEvent("GetXmlDocs"); - var uri = new UriBuilder(assembly.CodeBase); + var uri = new UriBuilder(Utils.GetAssemblyLocation(assembly)); var path = Uri.UnescapeDataString(uri.Path); var name = assembly.GetName().Name; var xmlFilePath = Path.Combine(Path.GetDirectoryName(path), name + ".xml"); diff --git a/Source/Editor/Modules/SourceCodeEditing/CodeEditingModule.cs b/Source/Editor/Modules/SourceCodeEditing/CodeEditingModule.cs index 010cd4942..1e7583dbe 100644 --- a/Source/Editor/Modules/SourceCodeEditing/CodeEditingModule.cs +++ b/Source/Editor/Modules/SourceCodeEditing/CodeEditingModule.cs @@ -391,9 +391,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/Options/InputBinding.cs b/Source/Editor/Options/InputBinding.cs index e87cb24ae..2ef38ed84 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/Plugins/PluginUtils.cs b/Source/Editor/Plugins/PluginUtils.cs index 3c9765922..659e66db5 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) diff --git a/Source/Editor/Progress/Handlers/CompileScriptsProgress.cs b/Source/Editor/Progress/Handlers/CompileScriptsProgress.cs index 339ea60f2..9a4bd608f 100644 --- a/Source/Editor/Progress/Handlers/CompileScriptsProgress.cs +++ b/Source/Editor/Progress/Handlers/CompileScriptsProgress.cs @@ -41,8 +41,10 @@ namespace FlaxEditor.Progress.Handlers private void OnScriptsReload() { +#if !USE_NETCORE // Clear types cache Newtonsoft.Json.JsonSerializer.ClearCache(); +#endif } private void OnScriptsReloadEnd() diff --git a/Source/Editor/ProjectInfo.cs b/Source/Editor/ProjectInfo.cs index 0653fe101..64b425681 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/Scripting/ScriptsBuilder.cpp b/Source/Editor/Scripting/ScriptsBuilder.cpp index 25e22eaca..1f119f9af 100644 --- a/Source/Editor/Scripting/ScriptsBuilder.cpp +++ b/Source/Editor/Scripting/ScriptsBuilder.cpp @@ -176,6 +176,7 @@ bool ScriptsBuilder::IsReady() void ScriptsBuilder::MarkWorkspaceDirty() { + SCRIPTING_EXPORT("FlaxEditor.ScriptsBuilder::Internal_MarkWorkspaceDirty") ScopeLock scopeLock(_locker); _lastSourceCodeEdited = DateTime::Now(); _wasProjectStructureChanged = true; @@ -183,6 +184,7 @@ void ScriptsBuilder::MarkWorkspaceDirty() void ScriptsBuilder::CheckForCompile() { + SCRIPTING_EXPORT("FlaxEditor.ScriptsBuilder::Internal_CheckForCompile") ScopeLock scopeLock(_locker); if (IsSourceDirty()) Compile(); @@ -205,6 +207,7 @@ void ScriptsBuilderImpl::onScriptsReloadEnd() void ScriptsBuilder::Compile() { + SCRIPTING_EXPORT("FlaxEditor.ScriptsBuilder::Internal_Compile") ScopeLock scopeLock(_locker); // Request compile job diff --git a/Source/Editor/States/BuildingScenesState.cs b/Source/Editor/States/BuildingScenesState.cs index c6d749503..4261cc6d6 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 497b7679c..00bca5a21 100644 --- a/Source/Editor/States/EditingSceneState.cs +++ b/Source/Editor/States/EditingSceneState.cs @@ -1,6 +1,7 @@ // Copyright (c) 2012-2022 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 8676a2af4..d76963734 100644 --- a/Source/Editor/States/ReloadingScriptsState.cs +++ b/Source/Editor/States/ReloadingScriptsState.cs @@ -1,6 +1,7 @@ // Copyright (c) 2012-2022 Wojciech Figat. All rights reserved. using FlaxEngine; +using FlaxEditor.Utilities; using FlaxEngine.Utilities; namespace FlaxEditor.States diff --git a/Source/Editor/Surface/Archetypes/Function.cs b/Source/Editor/Surface/Archetypes/Function.cs index 5ff5d35d7..d96155584 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 cc9d77fa5..e3f79b148 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/AttributesEditor.cs b/Source/Editor/Surface/AttributesEditor.cs index 29f540e62..d7d98b2e1 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/SurfaceMeta.cs b/Source/Editor/Surface/SurfaceMeta.cs index 05003223b..1b07256b1 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/SurfaceUtils.cs b/Source/Editor/Surface/SurfaceUtils.cs index 59d90c916..20d80db7a 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 diff --git a/Source/Editor/Surface/VisjectSurfaceContext.Serialization.cs b/Source/Editor/Surface/VisjectSurfaceContext.Serialization.cs index 05c77ab3a..1d5977f3c 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/Tools/Terrain/Sculpt/NoiseMode.cs b/Source/Editor/Tools/Terrain/Sculpt/NoiseMode.cs index b6c7c160d..7b644623a 100644 --- a/Source/Editor/Tools/Terrain/Sculpt/NoiseMode.cs +++ b/Source/Editor/Tools/Terrain/Sculpt/NoiseMode.cs @@ -1,6 +1,7 @@ // Copyright (c) 2012-2022 Wojciech Figat. All rights reserved. using FlaxEngine; +using FlaxEditor.Utilities; using FlaxEngine.Utilities; namespace FlaxEditor.Tools.Terrain.Sculpt diff --git a/Source/Editor/Windows/OutputLogWindow.cs b/Source/Editor/Windows/OutputLogWindow.cs index c9f3ac6e2..9c8a97f95 100644 --- a/Source/Editor/Windows/OutputLogWindow.cs +++ b/Source/Editor/Windows/OutputLogWindow.cs @@ -445,7 +445,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++) { diff --git a/Source/Editor/Windows/Profiler/CPU.cs b/Source/Editor/Windows/Profiler/CPU.cs index 23f74aa94..070b3b9cb 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/Engine/Animations/AnimationGraph.cs b/Source/Engine/Animations/AnimationGraph.cs index c337bcb6d..4bebecab0 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(ContextMarshaler))] public struct Context { /// @@ -92,6 +94,61 @@ namespace FlaxEngine public AnimatedModel Instance; } + [CustomMarshaller(typeof(Context), MarshalMode.Default, typeof(ContextMarshaler))] + internal static class ContextMarshaler + { + [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. @@ -203,14 +260,16 @@ namespace FlaxEngine #region Internal Calls - [MethodImpl(MethodImplOptions.InternalCall)] - internal static extern bool Internal_HasConnection(ref CustomNode.Context context, int boxId); + [LibraryImport("FlaxEngine", EntryPoint = "FlaxEngine.AnimationGraph::Internal_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 = "FlaxEngine.AnimationGraph::Internal_GetInputValue")] + [return: MarshalUsing(typeof(FlaxEngine.GCHandleMarshaller))] + 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 = "FlaxEngine.AnimationGraph::Internal_GetOutputImpulseData")] + internal static partial IntPtr Internal_GetOutputImpulseData(ref AnimationGraph.CustomNode.Context context); #endregion } diff --git a/Source/Engine/Animations/Graph/AnimGraph.Custom.cpp b/Source/Engine/Animations/Graph/AnimGraph.Custom.cpp index 45141bd94..a975736a6 100644 --- a/Source/Engine/Animations/Graph/AnimGraph.Custom.cpp +++ b/Source/Engine/Animations/Graph/AnimGraph.Custom.cpp @@ -13,8 +13,8 @@ #include "Engine/Content/Assets/SkinnedModel.h" #if USE_MONO - #include +#endif struct InternalInitData { @@ -52,6 +52,7 @@ namespace AnimGraphInternal { bool HasConnection(InternalContext* context, int32 boxId) { + SCRIPTING_EXPORT("FlaxEngine.AnimationGraph::Internal_HasConnection") const auto box = context->Node->TryGetBox(boxId); if (box == nullptr) DebugLog::ThrowArgumentOutOfRange("boxId"); @@ -60,6 +61,7 @@ namespace AnimGraphInternal MonoObject* GetInputValue(InternalContext* context, int32 boxId) { + SCRIPTING_EXPORT("FlaxEngine.AnimationGraph::Internal_GetInputValue") const auto box = context->Node->TryGetBox(boxId); if (box == nullptr) DebugLog::ThrowArgumentOutOfRange("boxId"); @@ -77,14 +79,13 @@ namespace AnimGraphInternal AnimGraphImpulse* GetOutputImpulseData(InternalContext* context) { + SCRIPTING_EXPORT("FlaxEngine.AnimationGraph::Internal_GetOutputImpulseData") const auto nodes = context->Node->GetNodes(context->GraphExecutor); context->GraphExecutor->InitNodes(nodes); return nodes; } } -#endif - void AnimGraphExecutor::initRuntime() { #if USE_MONO @@ -122,7 +123,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 = MUtils::GetGCHandleTarget(data.Handle); if (obj == nullptr) { LOG(Warning, "Custom node instance is null."); @@ -166,7 +167,7 @@ void AnimGraph::ClearCustomNode(Node* node) if (data.Handle) { #if USE_MONO - mono_gchandle_free(data.Handle); + MUtils::FreeGCHandle(data.Handle); #endif data.Handle = 0; } @@ -216,7 +217,7 @@ bool AnimGraph::InitCustomNode(Node* node) // Allocate managed node object (create GC handle to prevent destruction) const auto obj = type->CreateInstance(); - const auto handleGC = mono_gchandle_new(obj, false); + const auto handleGC = MUtils::NewGCHandle(obj, false); // Initialize node InternalInitData initData; @@ -228,7 +229,7 @@ bool AnimGraph::InitCustomNode(Node* node) load->Invoke(obj, params, &exception); if (exception) { - mono_gchandle_free(handleGC); + MUtils::FreeGCHandle(handleGC); MException ex(exception); ex.Log(LogType::Warning, TEXT("AnimGraph")); diff --git a/Source/Engine/Animations/Graph/AnimGraph.h b/Source/Engine/Animations/Graph/AnimGraph.h index 74b512303..69855a2ba 100644 --- a/Source/Engine/Animations/Graph/AnimGraph.h +++ b/Source/Engine/Animations/Graph/AnimGraph.h @@ -516,7 +516,7 @@ public: /// /// The GC handle to the managed instance of the node object. /// - uint32 Handle; + gchandle Handle; }; struct CurveData diff --git a/Source/Engine/Content/Asset.cpp b/Source/Engine/Content/Asset.cpp index a7aeeab3d..502228824 100644 --- a/Source/Engine/Content/Asset.cpp +++ b/Source/Engine/Content/Asset.cpp @@ -13,6 +13,7 @@ #include "Engine/Threading/MainThreadTask.h" #include "Engine/Threading/ConcurrentTaskQueue.h" #if USE_MONO +#include "Engine/Scripting/ManagedCLR/MUtils.h" #include #endif @@ -270,7 +271,7 @@ void Asset::OnManagedInstanceDeleted() if (_gcHandle) { #if USE_MONO - mono_gchandle_free(_gcHandle); + MUtils::FreeGCHandle(_gcHandle); #endif _gcHandle = 0; } diff --git a/Source/Engine/Content/JsonAsset.cs b/Source/Engine/Content/JsonAsset.cs index 26a7d4f28..78c601e7c 100644 --- a/Source/Engine/Content/JsonAsset.cs +++ b/Source/Engine/Content/JsonAsset.cs @@ -36,7 +36,10 @@ namespace FlaxEngine var dataTypeName = DataTypeName; var assemblies = AppDomain.CurrentDomain.GetAssemblies(); - for (int i = 0; i < assemblies.Length; i++) + + // Going through the assemblies in order will return collected assemblies first, + // use reverse order instead to find the type currently loaded assemblies instead. + for (int i = assemblies.Length-1; i >= 0; i--) { var assembly = assemblies[i]; if (assembly != null) diff --git a/Source/Engine/Core/Compiler.h b/Source/Engine/Core/Compiler.h index fa6b77d05..afe545eb5 100644 --- a/Source/Engine/Core/Compiler.h +++ b/Source/Engine/Core/Compiler.h @@ -32,6 +32,8 @@ #pragma clang diagnostic ignored "-Wnull-dereference" #pragma clang diagnostic ignored "-Winvalid-noreturn" +#define SCRIPTING_EXPORT(name) + #elif defined(__GNUC__) #define DLLEXPORT __attribute__ ((__visibility__ ("default"))) @@ -86,6 +88,8 @@ #pragma warning(disable: 4251) +#define SCRIPTING_EXPORT(name) __pragma(comment(linker, "/EXPORT:" #name "=" __FUNCDNAME__)) + #else #pragma error "Unknown compiler." diff --git a/Source/Engine/Core/Config/GameSettings.cs b/Source/Engine/Core/Config/GameSettings.cs index eeb1d76b6..1145db264 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 @@ -562,8 +563,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 = "FlaxEditor.Content.Settings.GameSettings::Apply")] + public static partial void Apply(); #endif } } diff --git a/Source/Engine/Core/Config/LayersAndTagsSettings.cs b/Source/Engine/Core/Config/LayersAndTagsSettings.cs index 0396b7588..a6003595c 100644 --- a/Source/Engine/Core/Config/LayersAndTagsSettings.cs +++ b/Source/Engine/Core/Config/LayersAndTagsSettings.cs @@ -2,6 +2,8 @@ using System.Collections.Generic; using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Runtime.InteropServices.Marshalling; using FlaxEngine; namespace FlaxEditor.Content.Settings @@ -24,14 +26,26 @@ namespace FlaxEditor.Content.Settings /// Gets the current tags collection. /// /// The tags collection. - [MethodImpl(MethodImplOptions.InternalCall)] - internal static extern string[] GetCurrentTags(); + internal static string[] GetCurrentTags() + { + return GetCurrentTags(out int _); + } /// /// 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 = "FlaxEditor.Content.Settings.LayersAndTagsSettings::GetCurrentTags", StringMarshalling = StringMarshalling.Custom, StringMarshallingCustomType = typeof(FlaxEngine.StringMarshaller))] + [return: MarshalUsing(typeof(FlaxEngine.ArrayMarshaller<,>), CountElementName = "tagCount")] + internal static partial string[] GetCurrentTags(out int tagCount); + + [LibraryImport("FlaxEngine", EntryPoint = "FlaxEditor.Content.Settings.LayersAndTagsSettings::GetCurrentLayers", StringMarshalling = StringMarshalling.Custom, StringMarshallingCustomType = typeof(FlaxEngine.StringMarshaller))] + [return: MarshalUsing(typeof(FlaxEngine.ArrayMarshaller<,>), CountElementName = "layerCount")] + internal static partial string[] GetCurrentLayers(out int layerCount); } } diff --git a/Source/Engine/Core/Math/TypeConverters/ColorConverter.cs b/Source/Engine/Core/Math/TypeConverters/ColorConverter.cs index 2cf55e03e..4ad7ebfc0 100644 --- a/Source/Engine/Core/Math/TypeConverters/ColorConverter.cs +++ b/Source/Engine/Core/Math/TypeConverters/ColorConverter.cs @@ -18,6 +18,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 +35,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,7 +49,7 @@ 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); } diff --git a/Source/Engine/Core/Math/TypeConverters/Double2Converter.cs b/Source/Engine/Core/Math/TypeConverters/Double2Converter.cs index bde9d5bd5..3576862c0 100644 --- a/Source/Engine/Core/Math/TypeConverters/Double2Converter.cs +++ b/Source/Engine/Core/Math/TypeConverters/Double2Converter.cs @@ -18,13 +18,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,7 +45,7 @@ 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); } diff --git a/Source/Engine/Core/Math/TypeConverters/Double3Converter.cs b/Source/Engine/Core/Math/TypeConverters/Double3Converter.cs index ccd27261f..33c4748ae 100644 --- a/Source/Engine/Core/Math/TypeConverters/Double3Converter.cs +++ b/Source/Engine/Core/Math/TypeConverters/Double3Converter.cs @@ -18,13 +18,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,7 +45,7 @@ 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); } diff --git a/Source/Engine/Core/Math/TypeConverters/Double4Converter.cs b/Source/Engine/Core/Math/TypeConverters/Double4Converter.cs index ade244eb4..3123ffa42 100644 --- a/Source/Engine/Core/Math/TypeConverters/Double4Converter.cs +++ b/Source/Engine/Core/Math/TypeConverters/Double4Converter.cs @@ -18,13 +18,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,7 +45,7 @@ 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); } diff --git a/Source/Engine/Core/Math/TypeConverters/Float2Converter.cs b/Source/Engine/Core/Math/TypeConverters/Float2Converter.cs index eb7041058..41256852e 100644 --- a/Source/Engine/Core/Math/TypeConverters/Float2Converter.cs +++ b/Source/Engine/Core/Math/TypeConverters/Float2Converter.cs @@ -18,13 +18,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,7 +45,7 @@ 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); } diff --git a/Source/Engine/Core/Math/TypeConverters/Float3Converter.cs b/Source/Engine/Core/Math/TypeConverters/Float3Converter.cs index f6a7a5f73..a6de0b63c 100644 --- a/Source/Engine/Core/Math/TypeConverters/Float3Converter.cs +++ b/Source/Engine/Core/Math/TypeConverters/Float3Converter.cs @@ -18,13 +18,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,7 +45,7 @@ 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); } diff --git a/Source/Engine/Core/Math/TypeConverters/Float4Converter.cs b/Source/Engine/Core/Math/TypeConverters/Float4Converter.cs index dd69a5bea..611e0b499 100644 --- a/Source/Engine/Core/Math/TypeConverters/Float4Converter.cs +++ b/Source/Engine/Core/Math/TypeConverters/Float4Converter.cs @@ -18,13 +18,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,7 +45,7 @@ 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); } diff --git a/Source/Engine/Core/Math/TypeConverters/Int2Converter.cs b/Source/Engine/Core/Math/TypeConverters/Int2Converter.cs index 02b2b9a0c..30d6eac29 100644 --- a/Source/Engine/Core/Math/TypeConverters/Int2Converter.cs +++ b/Source/Engine/Core/Math/TypeConverters/Int2Converter.cs @@ -18,13 +18,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,7 +45,7 @@ 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); } diff --git a/Source/Engine/Core/Math/TypeConverters/Int3Converter.cs b/Source/Engine/Core/Math/TypeConverters/Int3Converter.cs index 78f3fe2e7..2e029adcf 100644 --- a/Source/Engine/Core/Math/TypeConverters/Int3Converter.cs +++ b/Source/Engine/Core/Math/TypeConverters/Int3Converter.cs @@ -18,13 +18,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,7 +45,7 @@ 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); } diff --git a/Source/Engine/Core/Math/TypeConverters/Int4Converter.cs b/Source/Engine/Core/Math/TypeConverters/Int4Converter.cs index 5f837e5ea..a222b6e3f 100644 --- a/Source/Engine/Core/Math/TypeConverters/Int4Converter.cs +++ b/Source/Engine/Core/Math/TypeConverters/Int4Converter.cs @@ -18,13 +18,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,7 +45,7 @@ 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); } diff --git a/Source/Engine/Core/Math/TypeConverters/QuaternionConverter.cs b/Source/Engine/Core/Math/TypeConverters/QuaternionConverter.cs index 8cc95fe20..afdfb83f0 100644 --- a/Source/Engine/Core/Math/TypeConverters/QuaternionConverter.cs +++ b/Source/Engine/Core/Math/TypeConverters/QuaternionConverter.cs @@ -18,13 +18,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,7 +45,7 @@ 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); } diff --git a/Source/Engine/Core/Math/TypeConverters/Vector2Converter.cs b/Source/Engine/Core/Math/TypeConverters/Vector2Converter.cs index 483e7a8ed..84bb24773 100644 --- a/Source/Engine/Core/Math/TypeConverters/Vector2Converter.cs +++ b/Source/Engine/Core/Math/TypeConverters/Vector2Converter.cs @@ -18,13 +18,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,7 +45,7 @@ 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); } diff --git a/Source/Engine/Core/Math/TypeConverters/Vector3Converter.cs b/Source/Engine/Core/Math/TypeConverters/Vector3Converter.cs index 70336deb9..e02f620b6 100644 --- a/Source/Engine/Core/Math/TypeConverters/Vector3Converter.cs +++ b/Source/Engine/Core/Math/TypeConverters/Vector3Converter.cs @@ -18,13 +18,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,7 +45,7 @@ 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); } diff --git a/Source/Engine/Core/Math/TypeConverters/Vector4Converter.cs b/Source/Engine/Core/Math/TypeConverters/Vector4Converter.cs index 8fcd5ba34..b19b2eaad 100644 --- a/Source/Engine/Core/Math/TypeConverters/Vector4Converter.cs +++ b/Source/Engine/Core/Math/TypeConverters/Vector4Converter.cs @@ -18,13 +18,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,7 +45,7 @@ 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); } diff --git a/Source/Engine/Core/Types/Variant.cpp b/Source/Engine/Core/Types/Variant.cpp index 5bf8dbf88..3f47dd7a5 100644 --- a/Source/Engine/Core/Types/Variant.cpp +++ b/Source/Engine/Core/Types/Variant.cpp @@ -619,7 +619,11 @@ Variant::Variant(Asset* v) Variant::Variant(_MonoObject* v) : Type(VariantType::ManagedObject, v ? mono_object_get_class(v) : nullptr) { - AsUint = v ? mono_gchandle_new(v, true) : 0; +#if USE_NETCORE + AsUint64 = v ? MUtils::NewGCHandle(v, true) : 0; +#else + AsUint = v ? MUtils::NewGCHandle(v, true) : 0; +#endif } #else @@ -957,9 +961,13 @@ Variant::~Variant() Delete(AsDictionary); break; case VariantType::ManagedObject: -#if USE_MONO +#if USE_NETCORE + if (AsUint64) + MUtils::FreeGCHandle(AsUint64); + break; +#elif USE_MONO if (AsUint) - mono_gchandle_free(AsUint); + MUtils::FreeGCHandle(AsUint); break; #endif default: ; @@ -1088,8 +1096,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 ? MUtils::NewGCHandle(MUtils::GetGCHandleTarget(other.AsUint64), true) : 0; +#elif USE_MONO + AsUint = other.AsUint ? MUtils::NewGCHandle(MUtils::GetGCHandleTarget(other.AsUint), true) : 0; #endif break; case VariantType::Null: @@ -1217,7 +1227,7 @@ bool Variant::operator==(const Variant& other) const case VariantType::ManagedObject: #if USE_MONO // TODO: invoke C# Equality logic? - return AsUint == other.AsUint || mono_gchandle_get_target(AsUint) == mono_gchandle_get_target(other.AsUint); + return AsUint == other.AsUint || MUtils::GetGCHandleTarget(AsUint) == MUtils::GetGCHandleTarget(other.AsUint); #endif default: return false; @@ -1308,8 +1318,10 @@ 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 && MUtils::GetGCHandleTarget(AsUint64) != nullptr; +#elif USE_MONO + return AsUint != 0 && MUtils::GetGCHandleTarget(AsUint) != nullptr; #endif default: return false; @@ -1578,8 +1590,10 @@ 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 ? MUtils::GetGCHandleTarget(AsUint64) : nullptr; +#elif USE_MONO + return AsUint ? MUtils::GetGCHandleTarget(AsUint) : nullptr; #endif default: return nullptr; @@ -1623,8 +1637,10 @@ Variant::operator ScriptingObject*() const Variant::operator _MonoObject*() 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 ? MUtils::GetGCHandleTarget(AsUint64) : nullptr; +#elif USE_MONO + return Type.Type == VariantType::ManagedObject && AsUint ? MUtils::GetGCHandleTarget(AsUint) : nullptr; #else return nullptr; #endif @@ -2337,9 +2353,14 @@ void Variant::SetType(const VariantType& type) Delete(AsDictionary); break; case VariantType::ManagedObject: -#if USE_MONO +#if USE_NETCORE + if (AsUint64) + MUtils::FreeGCHandle(AsUint64); + break; +#elif USE_MONO if (AsUint) - mono_gchandle_free(AsUint); + MUtils::FreeGCHandle(AsUint); + break; #endif break; default: ; @@ -2447,9 +2468,14 @@ void Variant::SetType(VariantType&& type) Delete(AsDictionary); break; case VariantType::ManagedObject: -#if USE_MONO +#if USE_NETCORE + if (AsUint64) + MUtils::FreeGCHandle(AsUint64); + break; +#elif USE_MONO if (AsUint) - mono_gchandle_free(AsUint); + MUtils::FreeGCHandle(AsUint); + break; #endif break; default: ; @@ -2632,7 +2658,11 @@ void Variant::SetManagedObject(_MonoObject* object) { if (Type.Type != VariantType::ManagedObject) SetType(VariantType(VariantType::ManagedObject, mono_object_get_class(object))); - AsUint = mono_gchandle_new(object, true); +#if USE_NETCORE + AsUint64 = MUtils::NewGCHandle(object, true); +#else + AsUint = MUtils::NewGCHandle(object, true); +#endif } else { @@ -2751,8 +2781,10 @@ 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(mono_object_to_string(MUtils::GetGCHandleTarget(AsUint64), nullptr))) : TEXT("null"); +#elif USE_MONO + return AsUint ? String(MUtils::ToString(mono_object_to_string(MUtils::GetGCHandleTarget(AsUint), nullptr))) : TEXT("null"); #endif default: return String::Empty; @@ -3671,7 +3703,12 @@ void Variant::AllocStructure() Platform::MemoryCopy(AsBlob.Data, data, AsBlob.Length); #else Type.Type = VariantType::ManagedObject; - AsUint = mono_gchandle_new(instance, true); + +#if USE_NETCORE + AsUint64 = MUtils::NewGCHandle(instance, true); +#else + AsUint = MUtils::NewGCHandle(instance, true); +#endif #endif } else @@ -3763,8 +3800,10 @@ 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)mono_object_hash(MUtils::GetGCHandleTarget(key.AsUint64)) : 0; +#elif USE_MONO + return key.AsUint ? (uint32)mono_object_hash(MUtils::GetGCHandleTarget(key.AsUint)) : 0; #endif default: return 0; diff --git a/Source/Engine/Engine/DebugLogHandler.cs b/Source/Engine/Engine/DebugLogHandler.cs index b7bc0d52e..288c5e0d0 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 = "FlaxEngine.DebugLogHandler::Internal_LogWrite", StringMarshalling = StringMarshalling.Custom, StringMarshallingCustomType = typeof(FlaxEngine.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 = "FlaxEngine.DebugLogHandler::Internal_Log", StringMarshalling = StringMarshalling.Custom, StringMarshallingCustomType = typeof(FlaxEngine.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 = "FlaxEngine.DebugLogHandler::Internal_LogException", StringMarshalling = StringMarshalling.Custom, StringMarshallingCustomType = typeof(FlaxEngine.StringMarshaller))] + internal static partial void Internal_LogException([MarshalUsing(typeof(FlaxEngine.ExceptionMarshaller))] Exception exception, IntPtr obj); [SecuritySafeCritical] public static string Internal_GetStackTrace() diff --git a/Source/Engine/Engine/NativeInterop.cs b/Source/Engine/Engine/NativeInterop.cs new file mode 100644 index 000000000..34daf9fcf --- /dev/null +++ b/Source/Engine/Engine/NativeInterop.cs @@ -0,0 +1,2256 @@ +#if USE_NETCORE +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using System.Linq.Expressions; +using System.Reflection; +using System.Runtime.InteropServices; +using System.Runtime.Loader; +using System.IO; +using System.Runtime.CompilerServices; +using FlaxEngine.Assertions; +using FlaxEngine.Utilities; +using System.Runtime.InteropServices.Marshalling; +using System.Reflection.Metadata; +using System.Reflection.PortableExecutable; +using FlaxEngine.Visject; +using System.Diagnostics; +using System.Collections; +using FlaxEditor.Content; + +#pragma warning disable CS1591 +#pragma warning disable CS8632 + +[assembly: DisableRuntimeMarshalling] + +namespace FlaxEngine +{ + #region Native structures + + [StructLayout(LayoutKind.Sequential)] + internal struct ManagedClass + { + internal IntPtr typeHandle; + internal IntPtr name; + internal IntPtr fullname; + internal IntPtr @namespace; + internal uint typeAttributes; + } + + [StructLayout(LayoutKind.Sequential)] + internal struct ClassMethod + { + internal IntPtr name; + internal int numParameters; + internal IntPtr typeHandle; + internal uint methodAttributes; + } + + [StructLayout(LayoutKind.Sequential)] + internal struct ClassField + { + internal IntPtr name; + internal IntPtr fieldHandle; + internal IntPtr fieldTypeHandle; + internal uint fieldAttributes; + } + + [StructLayout(LayoutKind.Sequential)] + internal struct ClassProperty + { + internal IntPtr name; + internal IntPtr getterHandle; + internal IntPtr setterHandle; + internal uint getterFlags; + internal uint setterFlags; + } + + [StructLayout(LayoutKind.Sequential)] + internal struct ClassAttribute + { + internal IntPtr name; + internal IntPtr attributeHandle; + internal IntPtr attributeTypeHandle; + } + + [StructLayout(LayoutKind.Explicit)] + internal struct VariantNative + { + [StructLayout(LayoutKind.Sequential)] + internal struct VariantNativeType + { + internal VariantUtils.VariantType types; + internal IntPtr TypeName; // char* + } + + [FieldOffset(0)] + VariantNativeType Type; + + [FieldOffset(8)] + byte AsBool; + + [FieldOffset(8)] + short AsInt16; + + [FieldOffset(8)] + ushort AsUint16; + + [FieldOffset(8)] + int AsInt; + + [FieldOffset(8)] + uint AsUint; + + [FieldOffset(8)] + long AsInt64; + + [FieldOffset(8)] + ulong AsUint64; + + [FieldOffset(8)] + float AsFloat; + + [FieldOffset(8)] + double AsDouble; + + [FieldOffset(8)] + IntPtr AsPointer; + + [FieldOffset(8)] + int AsData0; + + [FieldOffset(12)] + int AsData1; + + [FieldOffset(16)] + int AsData2; + + [FieldOffset(20)] + int AsData3; + + [FieldOffset(24)] + int AsData4; + + [FieldOffset(28)] + int AsData5; + } + +#if false + [StructLayout(LayoutKind.Sequential)] + internal struct GuidNative + { + internal int A; + internal short B; + internal short C; + internal byte D; + internal byte E; + internal byte F; + internal byte G; + internal byte H; + internal byte I; + internal byte J; + internal byte K; + + internal GuidNative(Guid guid) + { + byte[] bytes = guid.ToByteArray(); + A = MemoryMarshal.Cast(bytes.AsSpan())[0]; + B = MemoryMarshal.Cast(bytes.AsSpan())[4]; + C = MemoryMarshal.Cast(bytes.AsSpan())[6]; + D = bytes[8]; + E = bytes[9]; + F = bytes[10]; + G = bytes[11]; + H = bytes[12]; + I = bytes[13]; + J = bytes[14]; + K = bytes[15]; + } + + internal static implicit operator Guid(GuidNative guid) => new Guid(guid.A, guid.B, guid.C, guid.D, guid.E, guid.F, guid.G, guid.H, guid.I, guid.J, guid.K); + internal static explicit operator GuidNative(Guid guid) => new GuidNative(guid); + } +#endif + + [StructLayout(LayoutKind.Sequential)] + internal struct VersionNative + { + internal int _Major; + internal int _Minor; + internal int _Build; + internal int _Revision; + + internal VersionNative(Version ver) + { + _Major = ver.Major; + _Minor = ver.Minor; + _Build = ver.Build; + _Revision = ver.Revision; + } + + internal Version GetVersion() + { + return new Version(_Major, _Minor, _Build, _Revision); + } + } + + #endregion + + #region Wrappers + /// + /// Wrapper for managed arrays that needs to be pinned. + /// + internal class ManagedArray + { + internal Array array = null; + internal GCHandle handle; + internal int elementSize; + + internal ManagedArray(Array arr) + { + handle = GCHandle.Alloc(arr, GCHandleType.Pinned); + elementSize = Marshal.SizeOf(arr.GetType().GetElementType()); + array = arr; + } + + ~ManagedArray() + { + if (array != null) + Release(); + } + + internal void Release() + { + handle.Free(); + array = null; + } + + internal IntPtr PointerToPinnedArray => Marshal.UnsafeAddrOfPinnedArrayElement(array, 0); + + internal int Length => array.Length; + + internal static ManagedArray Get(ref T[] arr) + { + return new ManagedArray(arr); + } + + internal static ManagedArray Get(Array arr) + { + ManagedArray managedArray = new ManagedArray(arr); + return managedArray; + } + } + + internal static class ManagedString + { + internal static GCHandle EmptyStringHandle = GCHandle.Alloc(string.Empty); + + [System.Diagnostics.DebuggerStepThrough] + internal static unsafe IntPtr ToNative(string str) + { + if (str == null) + return IntPtr.Zero; + else if (str == string.Empty) + return GCHandle.ToIntPtr(EmptyStringHandle); +#if false + // HACK: Pin the string and pass the address to it including the length, no marshalling required + GCHandle handle = GCHandle.Alloc(str, GCHandleType.Pinned); + IntPtr ptr = handle.AddrOfPinnedObject() - sizeof(int); + + pinnedStrings.TryAdd(ptr, handle); + return ptr; +#endif + +#if false + // HACK: Return address to the allocated string structure which includes the string length. + // We assume the string content is copied in native side before GC frees it. + IntPtr ptr = (Unsafe.Read(Unsafe.AsPointer(ref str)) + sizeof(int) * 2); +#endif + IntPtr ptr = GCHandle.ToIntPtr(GCHandle.Alloc(str, GCHandleType.Weak)); + return ptr; + } + + [System.Diagnostics.DebuggerStepThrough] + internal static string ToManaged(IntPtr ptr) + { + if (ptr == IntPtr.Zero) + return null; + + return (string)GCHandle.FromIntPtr(ptr).Target; + } + + [System.Diagnostics.DebuggerStepThrough] + internal static void Free(IntPtr ptr) + { + if (ptr == IntPtr.Zero) + return; + + GCHandle handle = GCHandle.FromIntPtr(ptr); + if (handle == EmptyStringHandle) + return; + + handle.Free(); + } + } + + #endregion + + #region Marshallers + + [CustomMarshaller(typeof(object), MarshalMode.ManagedToUnmanagedIn, typeof(GCHandleMarshaller.ManagedToNative))] + [CustomMarshaller(typeof(object), MarshalMode.UnmanagedToManagedOut, typeof(GCHandleMarshaller.ManagedToNative))] + [CustomMarshaller(typeof(object), MarshalMode.ElementIn, typeof(GCHandleMarshaller.ManagedToNative))] + [CustomMarshaller(typeof(object), MarshalMode.ManagedToUnmanagedOut, typeof(GCHandleMarshaller.NativeToManaged))] + [CustomMarshaller(typeof(object), MarshalMode.UnmanagedToManagedIn, typeof(GCHandleMarshaller.NativeToManaged))] + [CustomMarshaller(typeof(object), MarshalMode.ElementOut, typeof(GCHandleMarshaller.NativeToManaged))] + [CustomMarshaller(typeof(object), MarshalMode.ManagedToUnmanagedRef, typeof(GCHandleMarshaller.Bidirectional))] + [CustomMarshaller(typeof(object), MarshalMode.UnmanagedToManagedRef, typeof(GCHandleMarshaller.Bidirectional))] + [CustomMarshaller(typeof(object), MarshalMode.ElementRef, typeof(GCHandleMarshaller))] + internal static class GCHandleMarshaller + { + public static class NativeToManaged + { + public static object ConvertToManaged(IntPtr unmanaged) => unmanaged == IntPtr.Zero ? null : GCHandle.FromIntPtr(unmanaged).Target; + public static void Free(IntPtr unmanaged) + { + // This is a permanent handle, do not release it + } + } + public static class ManagedToNative + { + public static IntPtr ConvertToUnmanaged(object managed) => managed != null ? GCHandle.ToIntPtr(GCHandle.Alloc(managed)) : IntPtr.Zero; + public static void Free(IntPtr unmanaged) + { + if (unmanaged == IntPtr.Zero) + return; + GCHandle.FromIntPtr(unmanaged).Free(); + } + } + public struct Bidirectional + { + object managed; + IntPtr unmanaged; + public void FromManaged(object managed) { this.managed = managed; } + public IntPtr ToUnmanaged() { unmanaged = GCHandleMarshaller.ToNative(managed); return unmanaged; } + public void FromUnmanaged(IntPtr unmanaged) { this.unmanaged = unmanaged; } + public object ToManaged() { managed = GCHandleMarshaller.ToManaged(unmanaged); unmanaged = IntPtr.Zero; return managed; } + public void Free() + { + // FIXME, might be a permanent handle or a temporary one + throw new NotImplementedException(); + /*if (unmanaged == IntPtr.Zero) + return; + GCHandle.FromIntPtr(unmanaged).Free();*/ + } + } + internal static object ConvertToManaged(IntPtr unmanaged) => ToManaged(unmanaged); + internal static IntPtr ConvertToUnmanaged(object managed) => ToNative(managed); + internal static object ToManaged(IntPtr managed) => managed != IntPtr.Zero ? GCHandle.FromIntPtr(managed).Target : null; + internal static IntPtr ToNative(object managed) => managed != null ? GCHandle.ToIntPtr(GCHandle.Alloc(managed)) : IntPtr.Zero; + internal static void Free(IntPtr unmanaged) + { + if (unmanaged == IntPtr.Zero) + return; + GCHandle.FromIntPtr(unmanaged).Free(); + } + } + + [CustomMarshaller(typeof(System.Type), MarshalMode.Default, typeof(SystemTypeMarshaller))] + internal static class SystemTypeMarshaller + { + internal static System.Type ConvertToManaged(IntPtr unmanaged) => (System.Type)GCHandleMarshaller.ConvertToManaged(unmanaged); + + internal static IntPtr ConvertToUnmanaged(System.Type managed) + { + if (managed == null) + return IntPtr.Zero; + + GCHandle handle = NativeInterop.GetOrAddTypeGCHandle(managed); + return GCHandle.ToIntPtr(handle); + } + + internal static void Free(IntPtr unmanaged) + { + // Cached handle, do not release + } + } + + [CustomMarshaller(typeof(Exception), MarshalMode.Default, typeof(ExceptionMarshaller))] + internal static class ExceptionMarshaller + { + internal static Exception ConvertToManaged(IntPtr unmanaged) => (Exception)GCHandleMarshaller.ConvertToManaged(unmanaged); + internal static IntPtr ConvertToUnmanaged(Exception managed) => GCHandleMarshaller.ConvertToUnmanaged(managed); + internal static void Free(IntPtr unmanaged) => GCHandleMarshaller.Free(unmanaged); + } + + [CustomMarshaller(typeof(FlaxEngine.Object), MarshalMode.ManagedToUnmanagedIn, typeof(ObjectMarshaller.ManagedToNative))] + [CustomMarshaller(typeof(FlaxEngine.Object), MarshalMode.UnmanagedToManagedOut, typeof(ObjectMarshaller.ManagedToNative))] + [CustomMarshaller(typeof(FlaxEngine.Object), MarshalMode.ElementIn, typeof(ObjectMarshaller.ManagedToNative))] + [CustomMarshaller(typeof(FlaxEngine.Object), MarshalMode.ManagedToUnmanagedOut, typeof(ObjectMarshaller.NativeToManaged))] + [CustomMarshaller(typeof(FlaxEngine.Object), MarshalMode.UnmanagedToManagedIn, typeof(ObjectMarshaller.NativeToManaged))] + [CustomMarshaller(typeof(FlaxEngine.Object), MarshalMode.ElementOut, typeof(ObjectMarshaller.NativeToManaged))] + internal static class ObjectMarshaller + { + public static class NativeToManaged + { + public static FlaxEngine.Object ConvertToManaged(IntPtr unmanaged) => unmanaged != IntPtr.Zero ? (FlaxEngine.Object)GCHandle.FromIntPtr(unmanaged).Target : null; + } + public static class ManagedToNative + { + public static IntPtr ConvertToUnmanaged(FlaxEngine.Object managed) => managed != null ? GCHandle.ToIntPtr(GCHandle.Alloc(managed)) : IntPtr.Zero; + } + } + + [CustomMarshaller(typeof(CultureInfo), MarshalMode.Default, typeof(CultureInfoMarshaller))] + internal static class CultureInfoMarshaller + { + internal static CultureInfo ConvertToManaged(IntPtr unmanaged) => (CultureInfo)GCHandleMarshaller.ConvertToManaged(unmanaged); + internal static IntPtr ConvertToUnmanaged(CultureInfo managed) => GCHandleMarshaller.ConvertToUnmanaged(managed); + internal static void Free(IntPtr unmanaged) => GCHandleMarshaller.Free(unmanaged); + } + + [CustomMarshaller(typeof(Array), MarshalMode.Default, typeof(SystemArrayMarshaller))] + internal static class SystemArrayMarshaller + { + internal static Array ConvertToManaged(IntPtr unmanaged) => (Array)GCHandleMarshaller.ConvertToManaged(unmanaged); + internal static IntPtr ConvertToUnmanaged(Array managed) => GCHandleMarshaller.ConvertToUnmanaged(managed); + internal static void Free(IntPtr unmanaged) => GCHandleMarshaller.Free(unmanaged); + } + + [CustomMarshaller(typeof(Dictionary<,>), MarshalMode.ManagedToUnmanagedIn, typeof(DictionaryMarshaller<,>.ManagedToNative))] + [CustomMarshaller(typeof(Dictionary<,>), MarshalMode.UnmanagedToManagedOut, typeof(DictionaryMarshaller<,>.ManagedToNative))] + [CustomMarshaller(typeof(Dictionary<,>), MarshalMode.ElementIn, typeof(DictionaryMarshaller<,>.ManagedToNative))] + [CustomMarshaller(typeof(Dictionary<,>), MarshalMode.ManagedToUnmanagedOut, typeof(DictionaryMarshaller<,>.NativeToManaged))] + [CustomMarshaller(typeof(Dictionary<,>), MarshalMode.UnmanagedToManagedIn, typeof(DictionaryMarshaller<,>.NativeToManaged))] + [CustomMarshaller(typeof(Dictionary<,>), MarshalMode.ElementOut, typeof(DictionaryMarshaller<,>.NativeToManaged))] + [CustomMarshaller(typeof(Dictionary<,>), MarshalMode.ManagedToUnmanagedRef, typeof(DictionaryMarshaller<,>.Bidirectional))] + [CustomMarshaller(typeof(Dictionary<,>), MarshalMode.UnmanagedToManagedRef, typeof(DictionaryMarshaller<,>.Bidirectional))] + [CustomMarshaller(typeof(Dictionary<,>), MarshalMode.ElementRef, typeof(DictionaryMarshaller<,>))] + internal static unsafe class DictionaryMarshaller + { + public static class NativeToManaged + { + public static Dictionary ConvertToManaged(IntPtr unmanaged) => DictionaryMarshaller.ToManaged(unmanaged); + public static void Free(IntPtr unmanaged) => DictionaryMarshaller.Free(unmanaged); + } + public static class ManagedToNative + { + public static IntPtr ConvertToUnmanaged(Dictionary managed) => DictionaryMarshaller.ToNative(managed); + public static void Free(IntPtr unmanaged) => DictionaryMarshaller.Free(unmanaged); + } + + public struct Bidirectional + { + Dictionary managed; + IntPtr unmanaged; + public void FromManaged(Dictionary managed) { this.managed = managed; } + public IntPtr ToUnmanaged() { unmanaged = DictionaryMarshaller.ToNative(managed); return unmanaged; } + public void FromUnmanaged(IntPtr unmanaged) { this.unmanaged = unmanaged; } + public Dictionary ToManaged() { managed = DictionaryMarshaller.ToManaged(unmanaged); unmanaged = IntPtr.Zero; return managed; } + public void Free() => DictionaryMarshaller.Free(unmanaged); + } + + public static Dictionary ConvertToManaged(IntPtr unmanaged) => unmanaged != IntPtr.Zero ? (Dictionary)GCHandle.FromIntPtr(unmanaged).Target : null; + public static IntPtr ConvertToUnmanaged(Dictionary managed) => managed != null ? GCHandle.ToIntPtr(GCHandle.Alloc(managed)) : IntPtr.Zero; + + public static Dictionary ToManaged(IntPtr unmanaged) => unmanaged != IntPtr.Zero ? (Dictionary)GCHandle.FromIntPtr(unmanaged).Target : null; + public static IntPtr ToNative(Dictionary managed) => managed != null ? GCHandle.ToIntPtr(GCHandle.Alloc(managed)) : IntPtr.Zero; + public static void Free(IntPtr unmanaged) + { + if (unmanaged != IntPtr.Zero) + GCHandle.FromIntPtr(unmanaged).Free(); + } + } + + [CustomMarshaller(typeof(CustomMarshallerAttribute.GenericPlaceholder[]), MarshalMode.ManagedToUnmanagedIn, typeof(ArrayMarshaller<,>.ManagedToNative))] + [CustomMarshaller(typeof(CustomMarshallerAttribute.GenericPlaceholder[]), MarshalMode.UnmanagedToManagedOut, typeof(ArrayMarshaller<,>.ManagedToNative))] + [CustomMarshaller(typeof(CustomMarshallerAttribute.GenericPlaceholder[]), MarshalMode.ElementIn, typeof(ArrayMarshaller<,>.ManagedToNative))] + [CustomMarshaller(typeof(CustomMarshallerAttribute.GenericPlaceholder[]), MarshalMode.ManagedToUnmanagedOut, typeof(ArrayMarshaller<,>.NativeToManaged))] + [CustomMarshaller(typeof(CustomMarshallerAttribute.GenericPlaceholder[]), MarshalMode.UnmanagedToManagedIn, typeof(ArrayMarshaller<,>.NativeToManaged))] + [CustomMarshaller(typeof(CustomMarshallerAttribute.GenericPlaceholder[]), MarshalMode.ElementOut, typeof(ArrayMarshaller<,>.NativeToManaged))] + [CustomMarshaller(typeof(CustomMarshallerAttribute.GenericPlaceholder[]), MarshalMode.ManagedToUnmanagedRef, typeof(ArrayMarshaller<,>.Bidirectional))] + [CustomMarshaller(typeof(CustomMarshallerAttribute.GenericPlaceholder[]), MarshalMode.UnmanagedToManagedRef, typeof(ArrayMarshaller<,>.Bidirectional))] + [CustomMarshaller(typeof(CustomMarshallerAttribute.GenericPlaceholder[]), MarshalMode.ElementRef, typeof(ArrayMarshaller<,>))] + [ContiguousCollectionMarshaller] + internal static unsafe class ArrayMarshaller + where TUnmanagedElement : unmanaged + { + public static class NativeToManaged + { + internal static T[]? AllocateContainerForManagedElements(TUnmanagedElement* unmanaged, int numElements) + { + if (unmanaged is null) + return null; + + ManagedArray array = (ManagedArray)GCHandle.FromIntPtr(new IntPtr(unmanaged)).Target; + return new T[numElements]; + } + + internal static Span GetManagedValuesDestination(T[]? managed) + => managed; + + internal static ReadOnlySpan GetUnmanagedValuesSource(TUnmanagedElement* unmanagedValue, int numElements) + { + if (unmanagedValue == null) + return ReadOnlySpan.Empty; + + ManagedArray array = (ManagedArray)GCHandle.FromIntPtr(new IntPtr(unmanagedValue)).Target; + return new ReadOnlySpan(array.array as TUnmanagedElement[]); + } + + internal static void Free(TUnmanagedElement* unmanaged) + { + if (unmanaged == null) + return; + + GCHandle handle = GCHandle.FromIntPtr(new IntPtr(unmanaged)); + ((ManagedArray)handle.Target).Release(); + handle.Free(); + } + + internal static Span GetUnmanagedValuesDestination(TUnmanagedElement* unmanaged, int numElements) + { + if (unmanaged == null) + return Span.Empty; + + ManagedArray array = (ManagedArray)GCHandle.FromIntPtr(new IntPtr(unmanaged)).Target; + return new Span(array.array as TUnmanagedElement[]); + } + } + public static class ManagedToNative + { + internal static TUnmanagedElement* AllocateContainerForUnmanagedElements(T[]? managed, out int numElements) + { + if (managed is null) + { + numElements = 0; + return null; + } + + numElements = managed.Length; + + ManagedArray array = ManagedArray.Get(new TUnmanagedElement[numElements]); + var ptr = GCHandle.ToIntPtr(GCHandle.Alloc(array)); + + return (TUnmanagedElement*)ptr; + } + + internal static ReadOnlySpan GetManagedValuesSource(T[]? managed) + => managed; + + internal static Span GetUnmanagedValuesDestination(TUnmanagedElement* unmanaged, int numElements) + { + if (unmanaged == null) + return Span.Empty; + + ManagedArray array = (ManagedArray)GCHandle.FromIntPtr(new IntPtr(unmanaged)).Target; + return new Span(array.array as TUnmanagedElement[]); + } + + internal static void Free(TUnmanagedElement* unmanaged) + { + if (unmanaged == null) + return; + + GCHandle handle = GCHandle.FromIntPtr(new IntPtr(unmanaged)); + ((ManagedArray)handle.Target).Release(); + handle.Free(); + } + } + public struct Bidirectional + { + T[] managedArray; + ManagedArray unmanagedArray; + GCHandle handle; + + public void FromManaged(T[]? managed) + { + if (managed == null) + return; + + managedArray = managed; + unmanagedArray = ManagedArray.Get(new TUnmanagedElement[managed.Length]); + handle = GCHandle.Alloc(unmanagedArray); + } + + public ReadOnlySpan GetManagedValuesSource() + => managedArray; + + public Span GetUnmanagedValuesDestination() + { + if (unmanagedArray == null) + return Span.Empty; + + return new Span(unmanagedArray.array as TUnmanagedElement[]); + } + + public TUnmanagedElement* ToUnmanaged() + { + return (TUnmanagedElement*)GCHandle.ToIntPtr(handle); + } + + public void FromUnmanaged(TUnmanagedElement* value) + { + ManagedArray arr = (ManagedArray)GCHandle.FromIntPtr(new IntPtr(value)).Target; + managedArray = new T[arr.Length]; + } + + public ReadOnlySpan GetUnmanagedValuesSource(int numElements) + { + if (unmanagedArray == null) + return ReadOnlySpan.Empty; + + return new ReadOnlySpan(unmanagedArray.array as TUnmanagedElement[]); + } + + public Span GetManagedValuesDestination(int numElements) + => managedArray; + + public T[] ToManaged() + => managedArray; + + public void Free() + { + unmanagedArray.Release(); + handle.Free(); + } + } + + internal static TUnmanagedElement* AllocateContainerForUnmanagedElements(T[]? managed, out int numElements) + { + if (managed is null) + { + numElements = 0; + return null; + } + + numElements = managed.Length; + + ManagedArray array = ManagedArray.Get(new TUnmanagedElement[numElements]); + var ptr = GCHandle.ToIntPtr(GCHandle.Alloc(array)); + + return (TUnmanagedElement*)ptr; + } + + internal static ReadOnlySpan GetManagedValuesSource(T[]? managed) + => managed; + + internal static Span GetUnmanagedValuesDestination(TUnmanagedElement* unmanaged, int numElements) + { + if (unmanaged == null) + return Span.Empty; + + ManagedArray array = (ManagedArray)GCHandle.FromIntPtr(new IntPtr(unmanaged)).Target; + return new Span(array.array as TUnmanagedElement[]); + } + + internal static T[]? AllocateContainerForManagedElements(TUnmanagedElement* unmanaged, int numElements) + { + if (unmanaged is null) + return null; + + ManagedArray array = (ManagedArray)GCHandle.FromIntPtr(new IntPtr(unmanaged)).Target; + return new T[numElements]; + } + + internal static Span GetManagedValuesDestination(T[]? managed) + => managed; + + internal static ReadOnlySpan GetUnmanagedValuesSource(TUnmanagedElement* unmanagedValue, int numElements) + { + if (unmanagedValue == null) + return ReadOnlySpan.Empty; + + ManagedArray array = (ManagedArray)GCHandle.FromIntPtr(new IntPtr(unmanagedValue)).Target; + return new ReadOnlySpan(array.array as TUnmanagedElement[]); + } + + internal static void Free(TUnmanagedElement* unmanaged) + { + if (unmanaged == null) + return; + + GCHandle handle = GCHandle.FromIntPtr(new IntPtr(unmanaged)); + ((ManagedArray)handle.Target).Release(); + handle.Free(); + } + + internal static ref T GetPinnableReference(T[]? array) + { + if (array is null) + return ref Unsafe.NullRef(); + + ManagedArray managedArray = ManagedArray.Get(array); + var ptr = GCHandle.ToIntPtr(GCHandle.Alloc(managedArray)); + return ref Unsafe.AsRef(ptr.ToPointer()); + } + } + + [CustomMarshaller(typeof(string), MarshalMode.ManagedToUnmanagedIn, typeof(StringMarshaller.ManagedToNative))] + [CustomMarshaller(typeof(string), MarshalMode.UnmanagedToManagedOut, typeof(StringMarshaller.ManagedToNative))] + [CustomMarshaller(typeof(string), MarshalMode.ElementIn, typeof(StringMarshaller.ManagedToNative))] + [CustomMarshaller(typeof(string), MarshalMode.ManagedToUnmanagedOut, typeof(StringMarshaller.NativeToManaged))] + [CustomMarshaller(typeof(string), MarshalMode.UnmanagedToManagedIn, typeof(StringMarshaller.NativeToManaged))] + [CustomMarshaller(typeof(string), MarshalMode.ElementOut, typeof(StringMarshaller.NativeToManaged))] + [CustomMarshaller(typeof(string), MarshalMode.ManagedToUnmanagedRef, typeof(StringMarshaller.Bidirectional))] + [CustomMarshaller(typeof(string), MarshalMode.UnmanagedToManagedRef, typeof(StringMarshaller.Bidirectional))] + [CustomMarshaller(typeof(string), MarshalMode.ElementRef, typeof(StringMarshaller))] + internal static class StringMarshaller + { + public static class NativeToManaged + { + public static string ConvertToManaged(IntPtr unmanaged) => ManagedString.ToManaged(unmanaged); + public static void Free(IntPtr unmanaged) => ManagedString.Free(unmanaged); + } + public static class ManagedToNative + { + public static unsafe IntPtr ConvertToUnmanaged(string managed) + { + if (managed == null) + return IntPtr.Zero; +#if false + // HACK: Pin the string and pass the address to it including the length, no marshalling required + GCHandle handle = GCHandle.Alloc(managed, GCHandleType.Pinned); + IntPtr ptr = handle.AddrOfPinnedObject() - sizeof(int); + + pinnedStrings.TryAdd(ptr, handle); + return ptr; +#endif + +#if false + // HACK: Return address to the allocated string structure which includes the string length. + // We assume the string content is copied in native side before GC frees it. + IntPtr ptr = (Unsafe.Read(Unsafe.AsPointer(ref managed)) + sizeof(int) * 2); +#endif + IntPtr ptr = GCHandle.ToIntPtr(GCHandle.Alloc(managed, GCHandleType.Pinned)); + return ptr; + } + public static void Free(IntPtr unmanaged) => ManagedString.Free(unmanaged); + } + public struct Bidirectional + { + string managed; + IntPtr unmanaged; + public void FromManaged(string managed) { this.managed = managed; } + public IntPtr ToUnmanaged() { unmanaged = ManagedString.ToNative(managed); return unmanaged; } + public void FromUnmanaged(IntPtr unmanaged) { this.unmanaged = unmanaged; } + public string ToManaged() { managed = ManagedString.ToManaged(unmanaged); unmanaged = IntPtr.Zero; return managed; } + public void Free() => ManagedString.Free(unmanaged); + } + internal static string ConvertToManaged(IntPtr unmanaged) => ManagedString.ToManaged(unmanaged); + internal static IntPtr ConvertToUnmanaged(string managed) => ManagedString.ToNative(managed); + internal static void Free(IntPtr unmanaged) => ManagedString.Free(unmanaged); + + internal static string ToManaged(IntPtr unmanaged) => ManagedString.ToManaged(unmanaged); + internal static IntPtr ToNative(string managed) => ManagedString.ToNative(managed); + } + + #endregion + + /// + /// Provides a Mono-like API for native code to access managed runtime. + /// + internal unsafe static partial class NativeInterop + { + internal static Dictionary AssemblyLocations = new Dictionary(); + + private static bool firstAssemblyLoaded = false; + + private static Dictionary typeCache = new Dictionary(); + + private static Dictionary marshallableStructCache = new Dictionary(); + + private static IntPtr boolTruePtr = GCHandle.ToIntPtr(GCHandle.Alloc((int)1, GCHandleType.Pinned)); + private static IntPtr boolFalsePtr = GCHandle.ToIntPtr(GCHandle.Alloc((int)0, GCHandleType.Pinned)); + + private static List methodHandles = new(); + private static List methodHandlesCollectible = new(); + private static Dictionary cachedDelegates = new Dictionary(); + private static Dictionary cachedDelegatesCollectible = new Dictionary(); + private static Dictionary typeHandleCache = new Dictionary(); + private static Dictionary typeHandleCacheCollectible = new Dictionary(); + private static List fieldHandleCache = new(); + private static List fieldHandleCacheCollectible = new(); + private static Dictionary classAttributesCacheCollectible = new(); + private static Dictionary assemblyHandles = new Dictionary(); + + private static string hostExecutable; + private static IntPtr hostExecutableHandle = IntPtr.Zero; + private static AssemblyLoadContext scriptingAssemblyLoadContext; + + [System.Diagnostics.DebuggerStepThrough] + private static IntPtr InternalDllResolver(string libraryName, Assembly assembly, DllImportSearchPath? dllImportSearchPath) + { + if (libraryName == "FlaxEngine") + { + if (hostExecutableHandle == IntPtr.Zero) + hostExecutableHandle = NativeLibrary.Load(hostExecutable, assembly, dllImportSearchPath); + return hostExecutableHandle; + } + return IntPtr.Zero; + } + + [UnmanagedCallersOnly] + internal static unsafe void Init(IntPtr hostExecutableName) + { + hostExecutable = Marshal.PtrToStringUni(hostExecutableName); + NativeLibrary.SetDllImportResolver(Assembly.GetExecutingAssembly(), InternalDllResolver); + + // TODO: benchmark collectible setting performance, maybe enable it only in editor builds? + scriptingAssemblyLoadContext = new AssemblyLoadContext(null, true); + + DelegateHelpers.Init(); + } + + [UnmanagedCallersOnly] + internal static unsafe void Exit() + { + } + + internal static T[] GCHandleArrayToManagedArray(ManagedArray array) + { + IntPtr[] ptrArray = (IntPtr[])array.array; + T[] managedArray = new T[array.Length]; + for (int i = 0; i < managedArray.Length; i++) + { + IntPtr ptr = ptrArray[i]; + managedArray[i] = ptr != IntPtr.Zero ? (T)GCHandle.FromIntPtr(ptr).Target : default(T); + } + return managedArray; + } + + internal static IntPtr[] ManagedArrayToGCHandleArray(Array array) + { + IntPtr[] pointerArray = new IntPtr[array.Length]; + for (int i = 0; i < pointerArray.Length; i++) + { + var obj = array.GetValue(i); + pointerArray.SetValue(obj != null ? GCHandle.ToIntPtr(GCHandle.Alloc(obj)) : IntPtr.Zero, i); + } + return pointerArray; + } + + internal static T[] NativeArrayToManagedArray(U[] nativeArray, Func toManagedFunc) + { + T[] managedArray = new T[nativeArray.Length]; + if (nativeArray.Length > 0) + { + Assert.IsTrue(toManagedFunc != null); + for (int i = 0; i < nativeArray.Length; i++) + managedArray[i] = toManagedFunc(nativeArray[i]); + } + + return managedArray; + } + + private static Type FindType(string typeName) + { + if (typeCache.TryGetValue(typeName, out Type type)) + return type; + + type = Type.GetType(typeName, ResolveAssemblyByName, null); + if (type == null) + { + foreach (var assembly in scriptingAssemblyLoadContext.Assemblies) + { + type = assembly.GetType(typeName); + if (type != null) + break; + } + } + + if (type == null) + { + string oldTypeName = typeName; + typeName = typeName.Substring(0, typeName.IndexOf(',')); + type = Type.GetType(typeName, ResolveAssemblyByName, null); + if (type == null) + { + foreach (var assembly in scriptingAssemblyLoadContext.Assemblies) + { + type = assembly.GetType(typeName); + if (type != null) + break; + } + } + typeName = oldTypeName; + } + + typeCache.Add(typeName, type); + + return type; + } + + private static Assembly ResolveAssemblyByName(AssemblyName assemblyName) + { + foreach (Assembly assembly in scriptingAssemblyLoadContext.Assemblies) + if (assembly.GetName() == assemblyName) + return assembly; + return null; + } + + internal static bool IsBlittable(Type type) + { + if (type.IsPrimitive || type.IsEnum) + return true; + if (type.IsArray && IsBlittable(type.GetElementType())) + return true; + if (type.IsPointer && type.HasElementType && type.GetElementType().IsPrimitive) + return true; + + if (type.IsClass) + return false; + + if (type.IsValueType) + { + var fields = type.GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); + foreach (FieldInfo field in fields) + { + if (!IsBlittable(field.FieldType)) + return false; + } + } + return true; + } + + /// + /// Returns blittable internal type for given type. + /// + internal static Type GetInternalType(Type type) + { + string[] splits = type.AssemblyQualifiedName.Split(','); + string @namespace = string.Join('.', splits[0].Split('.').SkipLast(1)); + string className = @namespace.Length > 0 ? splits[0].Substring(@namespace.Length + 1) : splits[0]; + string parentClassName = ""; + if (className.Contains("+")) + { + parentClassName = className.Substring(0, className.LastIndexOf('+') + 1); + className = className.Substring(parentClassName.Length); + } + string marshallerName = className + "Marshaller"; + string internalAssemblyQualifiedName = $"{@namespace}.{parentClassName}{marshallerName}+{className}Internal,{String.Join(',', splits.Skip(1))}"; + return FindType(internalAssemblyQualifiedName); + } + + private static IntPtr EnsureAlignment(IntPtr ptr, int alignment) + { + if (ptr % alignment != 0) + ptr = IntPtr.Add(ptr, alignment - (int)(ptr % alignment)); + return ptr; + } + + internal static void MarshalFromObject(object obj, Type objectType, ref IntPtr targetPtr) + { + int readOffset = 0; + if (objectType == typeof(IntPtr)) + { + targetPtr = EnsureAlignment(targetPtr, IntPtr.Size); + Marshal.WriteIntPtr(targetPtr, (IntPtr)obj); + readOffset = IntPtr.Size; + } + else if (objectType == typeof(byte)) + { + targetPtr = EnsureAlignment(targetPtr, sizeof(byte)); + Marshal.WriteByte(targetPtr, (byte)obj); + readOffset = sizeof(byte); + } + else if (objectType == typeof(bool)) + { + targetPtr = EnsureAlignment(targetPtr, sizeof(byte)); + Marshal.WriteByte(targetPtr, ((bool)obj) ? (byte)1 : (byte)0); + readOffset = sizeof(byte); + } + else if (objectType.IsEnum) + { + MarshalFromObject(obj, objectType.GetEnumUnderlyingType(), ref targetPtr); + } + else if (objectType == typeof(int) || objectType.Name == "Int32&") + { + targetPtr = EnsureAlignment(targetPtr, sizeof(int)); + Marshal.WriteInt32(targetPtr, (int)obj); + readOffset = sizeof(int); + } + else if (objectType == typeof(long)) + { + targetPtr = EnsureAlignment(targetPtr, sizeof(long)); + Marshal.WriteInt64(targetPtr, (long)obj); + readOffset = sizeof(long); + } + else if (objectType == typeof(short)) + { + targetPtr = EnsureAlignment(targetPtr, sizeof(short)); + Marshal.WriteInt16(targetPtr, (short)obj); + readOffset = sizeof(short); + } + else if (objectType == typeof(string)) + { + targetPtr = EnsureAlignment(targetPtr, IntPtr.Size); + IntPtr strPtr = ManagedString.ToNative(obj as string); + Marshal.WriteIntPtr(targetPtr, strPtr); + readOffset = IntPtr.Size; + } + else if (objectType.IsByRef) + throw new NotImplementedException(); + else if (objectType.IsClass) + { + if (objectType.IsPointer) + { + targetPtr = EnsureAlignment(targetPtr, IntPtr.Size); + var unboxed = Pointer.Unbox(obj); + if (unboxed == null) + Marshal.WriteIntPtr(targetPtr, IntPtr.Zero); + else if (obj is FlaxEngine.Object fobj) + Marshal.WriteIntPtr(targetPtr, fobj.__unmanagedPtr); + else + Marshal.WriteIntPtr(targetPtr, GCHandle.ToIntPtr(GCHandle.Alloc(obj, GCHandleType.Weak))); + readOffset = IntPtr.Size; + } + else + { + targetPtr = EnsureAlignment(targetPtr, IntPtr.Size); + Marshal.WriteIntPtr(targetPtr, obj != null ? GCHandle.ToIntPtr(GCHandle.Alloc(obj, GCHandleType.Weak)) : IntPtr.Zero); + readOffset = IntPtr.Size; + } + } + else if (objectType.IsValueType) + { + if (GetMarshallableStructFields(objectType, out FieldInfo[] fields)) + { + IntPtr origPtr = targetPtr; + + foreach (var field in fields) + MarshalFromObject(field.GetValue(obj), field.FieldType, ref targetPtr); + + Assert.IsTrue((targetPtr-origPtr) == Marshal.SizeOf(objectType)); + } + else + { + Marshal.StructureToPtr(obj, targetPtr, false); + readOffset = Marshal.SizeOf(objectType); + } + } + else + throw new NotImplementedException(objectType.FullName); + + targetPtr = IntPtr.Add(targetPtr, readOffset); + } + + private static bool GetMarshallableStructFields(Type type, out FieldInfo[] fields) + { + if (marshallableStructCache.TryGetValue(type, out fields)) + return fields != null; + + fields = type.GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); + if (!fields.Any(x => x.FieldType.IsClass || x.FieldType.Name == "Boolean")) + fields = null; + + marshallableStructCache.Add(type, fields); + return fields != null; + } + + internal static object MarshalToObject(Type type, IntPtr ptr, out int readSize) + { + readSize = 0; + if (type.Name == "IntPtr&" || type.Name == "Byte*" || type.Name == "IntPtr") + { + readSize += IntPtr.Size; + return ptr; + } + else if (type.IsByRef) + { + return MarshalToObject(type.GetElementType(), ptr, out readSize); + } + else if (type == typeof(string)) + { + readSize += IntPtr.Size; + return ManagedString.ToManaged(ptr); + } + else if (type == typeof(bool)) + { + readSize += sizeof(byte); + return Marshal.ReadByte(ptr) != 0; + } + else if (type.IsEnum) + { + return Enum.ToObject(type, MarshalToObject(type.GetEnumUnderlyingType(), ptr, out readSize)); + } + else if (type.IsArray) + { + readSize += IntPtr.Size; + if (ptr == IntPtr.Zero) + return null; + + Type elementType = type.GetElementType(); + ManagedArray arr = (ManagedArray)GCHandle.FromIntPtr(ptr).Target; + if (arr.array.GetType().GetElementType() == elementType) + return arr.array; + + IntPtr[] ptrs = (IntPtr[])arr.array; + Array marshalledArray = Array.CreateInstance(elementType, ptrs.Length); + for (int i = 0; i < marshalledArray.Length; i++) + marshalledArray.SetValue(MarshalToObject(elementType, ptrs[i], out _), i); + return marshalledArray; + } + else if (type.IsClass) + { + readSize += IntPtr.Size; + if (ptr == IntPtr.Zero) + return null; + + var obj = GCHandle.FromIntPtr(ptr).Target; + return obj; + } + else if (type.IsValueType) + { + if (GetMarshallableStructFields(type, out FieldInfo[] fields)) + { + object target = RuntimeHelpers.GetUninitializedObject(type); + IntPtr fieldPtr = ptr; + + foreach (var field in fields) + { + object fieldValue; + int size; + + if (field.FieldType.IsClass || field.FieldType == typeof(IntPtr)) + fieldValue = MarshalToObject(field.FieldType, Marshal.ReadIntPtr(fieldPtr), out size); + else + { + int fieldSize; + Type fieldType = field.FieldType; + if (field.FieldType.IsEnum) + fieldType = field.FieldType.GetEnumUnderlyingType(); + else if (field.FieldType == typeof(bool)) + fieldType = typeof(byte); + + if (fieldType.IsValueType && !fieldType.IsEnum && !fieldType.IsPrimitive) // Is it a structure? + fieldSize = 1; + else if (fieldType.IsClass || fieldType.IsPointer) + fieldSize = IntPtr.Size; + else + fieldSize = Marshal.SizeOf(fieldType); + fieldPtr = EnsureAlignment(fieldPtr, fieldSize); + + fieldValue = MarshalToObject(field.FieldType, fieldPtr, out size); + } + + field.SetValue(target, fieldValue); + + fieldPtr = IntPtr.Add(fieldPtr, size); + } + + var totalReadSize = fieldPtr.ToInt64() - ptr.ToInt64(); + Assert.IsTrue(totalReadSize == Marshal.SizeOf(type)); + readSize += (int)totalReadSize; + return target; + } + } + else if (type.Name == "IDictionary") + { + readSize += IntPtr.Size; + if (ptr == IntPtr.Zero) + return null; + + return GCHandle.FromIntPtr(ptr).Target; + } + + readSize += Marshal.SizeOf(type); + return Marshal.PtrToStructure(ptr, type); + } + + internal static GCHandle GetMethodGCHandle(MethodInfo method) + { + Type[] parameterTypes = method.GetParameters().Select(x => x.ParameterType).ToArray(); + + GCHandle delegTupleHandle = GCHandle.Alloc(new Tuple(parameterTypes, method)); + + if (parameterTypes.Any(x => x.IsCollectible) || method.IsCollectible) + methodHandlesCollectible.Add(delegTupleHandle); + else + methodHandles.Add(delegTupleHandle); + + return delegTupleHandle; + } + + internal static GCHandle GetAssemblyHandle(Assembly assembly) + { + if (!assemblyHandles.TryGetValue(assembly, out GCHandle handle)) + { + handle = GCHandle.Alloc(assembly); + assemblyHandles.Add(assembly, handle); + } + return handle; + } + + [UnmanagedCallersOnly] + internal static unsafe void GetManagedClasses(IntPtr assemblyHandle, ManagedClass** managedClasses, int* managedClassCount) + { + Assembly assembly = (Assembly)GCHandle.FromIntPtr(assemblyHandle).Target; + var assemblyTypes = GetAssemblyTypes(assembly); + + ManagedClass* arr = (ManagedClass*)Marshal.AllocCoTaskMem(Unsafe.SizeOf() * assemblyTypes.Length).ToPointer(); + + for (int i = 0; i < assemblyTypes.Length; i++) + { + var type = assemblyTypes[i]; + IntPtr ptr = IntPtr.Add(new IntPtr(arr), Unsafe.SizeOf() * i); + bool isStatic = type.IsAbstract && type.IsSealed; + bool isInterface = type.IsInterface; + bool isAbstract = type.IsAbstract; + + GCHandle typeHandle = GetOrAddTypeGCHandle(type); + + ManagedClass managedClass = new ManagedClass() + { + typeHandle = GCHandle.ToIntPtr(typeHandle), + name = Marshal.StringToCoTaskMemAnsi(type.Name), + fullname = Marshal.StringToCoTaskMemAnsi(type.FullName), + @namespace = Marshal.StringToCoTaskMemAnsi(type.Namespace ?? ""), + typeAttributes = (uint)type.Attributes, + }; + Marshal.StructureToPtr(managedClass, ptr, false); + } + + *managedClasses = arr; + *managedClassCount = assemblyTypes.Length; + } + + [UnmanagedCallersOnly] + internal static unsafe void GetManagedClassFromType(IntPtr typeHandle, ManagedClass* managedClass, IntPtr* assemblyHandle) + { + var type = (Type)GCHandle.FromIntPtr(typeHandle).Target; + + GCHandle classTypeHandle = GetOrAddTypeGCHandle(type); + + *managedClass = new ManagedClass() + { + typeHandle = GCHandle.ToIntPtr(classTypeHandle), + name = Marshal.StringToCoTaskMemAnsi(type.Name), + fullname = Marshal.StringToCoTaskMemAnsi(type.FullName), + @namespace = Marshal.StringToCoTaskMemAnsi(type.Namespace ?? ""), + typeAttributes = (uint)type.Attributes, + }; + *assemblyHandle = GCHandle.ToIntPtr(GetAssemblyHandle(type.Assembly)); + } + + [UnmanagedCallersOnly] + internal static void GetClassMethods(IntPtr typeHandle, ClassMethod** classMethods, int* classMethodsCount) + { + Type type = (Type)GCHandle.FromIntPtr(typeHandle).Target; + + List methods = new List(); + var staticMethods = type.GetMethods(BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.DeclaredOnly); + var instanceMethods = type.GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.DeclaredOnly); + foreach (MethodInfo methodInfo in staticMethods) + methods.Add(methodInfo); + foreach (MethodInfo methodInfo in instanceMethods) + methods.Add(methodInfo); + + ClassMethod* arr = (ClassMethod*)Marshal.AllocCoTaskMem(Unsafe.SizeOf() * methods.Count).ToPointer(); + for (int i = 0; i < methods.Count; i++) + { + IntPtr ptr = IntPtr.Add(new IntPtr(arr), Unsafe.SizeOf() * i); + ClassMethod classMethod = new ClassMethod() + { + name = Marshal.StringToCoTaskMemAnsi(methods[i].Name), + numParameters = methods[i].GetParameters().Length, + methodAttributes = (uint)methods[i].Attributes, + }; + classMethod.typeHandle = GCHandle.ToIntPtr(GetMethodGCHandle(methods[i])); + Marshal.StructureToPtr(classMethod, ptr, false); + } + *classMethods = arr; + *classMethodsCount = methods.Count; + } + + [UnmanagedCallersOnly] + internal static void GetClassFields(IntPtr typeHandle, ClassField** classFields, int* classFieldsCount) + { + Type type = (Type)GCHandle.FromIntPtr(typeHandle).Target; + var fields = type.GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); + + ClassField* arr = (ClassField*)Marshal.AllocCoTaskMem(Unsafe.SizeOf() * fields.Length).ToPointer(); + for (int i = 0; i < fields.Length; i++) + { + IntPtr ptr = IntPtr.Add(new IntPtr(arr), Unsafe.SizeOf() * i); + + GCHandle fieldHandle = GCHandle.Alloc(fields[i]); + if (type.IsCollectible) + fieldHandleCacheCollectible.Add(fieldHandle); + else + fieldHandleCache.Add(fieldHandle); + + ClassField classField = new ClassField() + { + name = Marshal.StringToCoTaskMemAnsi(fields[i].Name), + fieldHandle = GCHandle.ToIntPtr(fieldHandle), + fieldTypeHandle = GCHandle.ToIntPtr(GetOrAddTypeGCHandle(fields[i].FieldType)), + fieldAttributes = (uint)fields[i].Attributes, + }; + Marshal.StructureToPtr(classField, ptr, false); + } + *classFields = arr; + *classFieldsCount = fields.Length; + } + + [UnmanagedCallersOnly] + internal static void GetClassProperties(IntPtr typeHandle, ClassProperty** classProperties, int* classPropertiesCount) + { + Type type = (Type)GCHandle.FromIntPtr(typeHandle).Target; + var properties = type.GetProperties(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); + + ClassProperty* arr = (ClassProperty*)Marshal.AllocCoTaskMem(Unsafe.SizeOf() * properties.Length).ToPointer(); + for (int i = 0; i < properties.Length; i++) + { + IntPtr ptr = IntPtr.Add(new IntPtr(arr), Unsafe.SizeOf() * i); + + var getterMethod = properties[i].GetGetMethod(true); + var setterMethod = properties[i].GetSetMethod(true); + + ClassProperty classProperty = new ClassProperty() + { + name = Marshal.StringToCoTaskMemAnsi(properties[i].Name), + }; + if (getterMethod != null) + { + var getterHandle = GetMethodGCHandle(getterMethod); + classProperty.getterHandle = GCHandle.ToIntPtr(getterHandle); + } + if (setterMethod != null) + { + var setterHandle = GetMethodGCHandle(setterMethod); + classProperty.setterHandle = GCHandle.ToIntPtr(setterHandle); + } + Marshal.StructureToPtr(classProperty, ptr, false); + } + *classProperties = arr; + *classPropertiesCount = properties.Length; + } + + [UnmanagedCallersOnly] + internal static void GetClassAttributes(IntPtr typeHandle, ClassAttribute** classAttributes, int* classAttributesCount) + { + Type type = (Type)GCHandle.FromIntPtr(typeHandle).Target; + object[] attributeValues = type.GetCustomAttributes(false); + Type[] attributeTypes = type.GetCustomAttributes(false).Select(x => x.GetType()).ToArray(); + + ClassAttribute* arr = (ClassAttribute*)Marshal.AllocCoTaskMem(Unsafe.SizeOf() * attributeTypes.Length).ToPointer(); + for (int i = 0; i < attributeTypes.Length; i++) + { + IntPtr ptr = IntPtr.Add(new IntPtr(arr), Unsafe.SizeOf() * i); + + if (!classAttributesCacheCollectible.TryGetValue(attributeValues[i], out GCHandle attributeHandle)) + { + attributeHandle = GCHandle.Alloc(attributeValues[i]); + classAttributesCacheCollectible.Add(attributeValues[i], attributeHandle); + } + GCHandle attributeTypeHandle = GetOrAddTypeGCHandle(attributeTypes[i]); + + ClassAttribute classAttribute = new ClassAttribute() + { + name = Marshal.StringToCoTaskMemAnsi(attributeTypes[i].Name), + attributeTypeHandle = GCHandle.ToIntPtr(attributeTypeHandle), + attributeHandle = GCHandle.ToIntPtr(attributeHandle), + }; + Marshal.StructureToPtr(classAttribute, ptr, false); + } + *classAttributes = arr; + *classAttributesCount = attributeTypes.Length; + } + + [UnmanagedCallersOnly] + internal static void GetClassInterfaces(IntPtr typeHandle, IntPtr* classInterfaces, int* classInterfacesCount) + { + Type type = (Type)GCHandle.FromIntPtr(typeHandle).Target; + + var interfaces = type.GetInterfaces(); + + // mono doesn't seem to return any interfaces, or returns only "immediate" interfaces? // FIXME? + //foreach (Type interfaceType in type.BaseType.GetInterfaces()) + // interfaces.Remove(interfaceType.FullName); + + IntPtr arr = Marshal.AllocCoTaskMem(IntPtr.Size * interfaces.Length); + for (int i = 0; i < interfaces.Length; i++) + { + IntPtr ptr = IntPtr.Add(arr, IntPtr.Size * i); + + GCHandle handle = GetTypeGCHandle(interfaces[i]); + Marshal.WriteIntPtr(ptr, GCHandle.ToIntPtr(handle)); + } + *classInterfaces = arr; + *classInterfacesCount = interfaces.Length; + } + + [UnmanagedCallersOnly] + internal static IntPtr GetMethodReturnType(IntPtr methodHandle) + { + (Type[] parameterTypes, MethodInfo methodInfo) = (Tuple)GCHandle.FromIntPtr(methodHandle).Target; + Type returnType = methodInfo.ReturnType; + + return GCHandle.ToIntPtr(GetTypeGCHandle(returnType)); + } + + [UnmanagedCallersOnly] + internal static void GetMethodParameterTypes(IntPtr methodHandle, IntPtr* typeHandles) + { + (Type[] parameterTypes, MethodInfo methodInfo) = (Tuple)GCHandle.FromIntPtr(methodHandle).Target; + Type returnType = methodInfo.ReturnType; + + IntPtr arr = Marshal.AllocCoTaskMem(IntPtr.Size * parameterTypes.Length); + for (int i = 0; i < parameterTypes.Length; i++) + { + IntPtr ptr = IntPtr.Add(new IntPtr(arr), IntPtr.Size * i); + + GCHandle typeHandle = GetOrAddTypeGCHandle(parameterTypes[i]); + + Marshal.WriteIntPtr(ptr, GCHandle.ToIntPtr(typeHandle)); + } + *typeHandles = arr; + } + + /// + /// Returns pointer to the string's internal structure, containing the buffer and length of the string. + /// + [UnmanagedCallersOnly] + internal static IntPtr GetStringPointer(IntPtr stringHandle) + { + string str = (string)GCHandle.FromIntPtr(stringHandle).Target; + IntPtr ptr = (Unsafe.Read(Unsafe.AsPointer(ref str)) + sizeof(int) * 2); + return ptr; + } + + [UnmanagedCallersOnly] + internal static IntPtr NewObject(IntPtr typeHandle) + { + Type classType = (Type)GCHandle.FromIntPtr(typeHandle).Target; + + if (classType == typeof(Script)) + { + // FIXME: Script is an abstract type which can not be instantiated + classType = typeof(VisjectScript); + } + + object obj = RuntimeHelpers.GetUninitializedObject(classType); + + IntPtr handle = GCHandle.ToIntPtr(GCHandle.Alloc(obj)); + return handle; + } + + [UnmanagedCallersOnly] + internal static IntPtr NewArray(IntPtr typeHandle, long size) + { + Type type = (Type)GCHandle.FromIntPtr(typeHandle).Target; + Type elementType; + if (IsBlittable(type)) + elementType = type; + else + elementType = GetInternalType(type) ?? typeof(IntPtr); + Array arr = Array.CreateInstance(elementType, size); + ManagedArray managedArray = new ManagedArray(arr); + return GCHandle.ToIntPtr(GCHandle.Alloc(managedArray/*, GCHandleType.Weak*/)); + } + + [UnmanagedCallersOnly] + internal static unsafe IntPtr GetArrayPointerToElement(IntPtr arrayHandle, int size, int index) + { + object obj = GCHandle.FromIntPtr(arrayHandle).Target; + if (obj is ManagedArray managedArray) + { + if (managedArray.Length == 0) + return IntPtr.Zero; + Assert.IsTrue(index >= 0 && index < managedArray.Length); + IntPtr ptr = IntPtr.Add(managedArray.PointerToPinnedArray, size * index); + return ptr; + } + else + { + Array array = (Array)obj; + if (array.Length == 0) + return IntPtr.Zero; + Assert.IsTrue(index >= 0 && index < array.Length); + IntPtr ptr = Marshal.UnsafeAddrOfPinnedArrayElement(array, index); + return ptr; + } + } + + [UnmanagedCallersOnly] + internal static int GetArrayLength(IntPtr handlePtr) + { + object obj = GCHandle.FromIntPtr(handlePtr).Target; + if (obj is ManagedArray managedArray) + return managedArray.Length; + else + return ((Array)obj).Length; + } + + [UnmanagedCallersOnly] + internal static IntPtr GetStringEmpty() + { + return ManagedString.ToNative(string.Empty); + } + + [UnmanagedCallersOnly] + internal static IntPtr NewStringUTF16(char* text, int length) + { + return ManagedString.ToNative(new string(new ReadOnlySpan(text, length))); + } + + [UnmanagedCallersOnly] + internal static IntPtr NewString(sbyte* text) + { + return ManagedString.ToNative(new string(text)); + } + + [UnmanagedCallersOnly] + internal static IntPtr NewStringLength(sbyte* text, int length) + { + return ManagedString.ToNative(new string(text, 0, length)); + } + + /// + /// Creates a managed copy of the value, and stores it in a boxed reference. + /// + [UnmanagedCallersOnly] + internal static IntPtr BoxValue(IntPtr typeHandle, IntPtr valuePtr) + { + Type type = (Type)GCHandle.FromIntPtr(typeHandle).Target; + + object value = MarshalToObject(type, valuePtr, out _); + return GCHandle.ToIntPtr(GCHandle.Alloc(value, GCHandleType.Weak)); + } + + [UnmanagedCallersOnly] + internal static IntPtr GetObjectType(IntPtr handle) + { + var obj = GCHandle.FromIntPtr(handle).Target; + Type classType = obj.GetType(); + return GCHandle.ToIntPtr(GetOrAddTypeGCHandle(classType)); + } + + internal unsafe class StructUnboxer where T : struct + { + public StructUnboxer(object obj, IntPtr ptr) + { + IntPtr* pin = (IntPtr*)ptr; + *pin = new IntPtr(Unsafe.AsPointer(ref Unsafe.Unbox(obj))); + } + } + + [UnmanagedCallersOnly] + internal unsafe static IntPtr UnboxValue(IntPtr handlePtr) + { + GCHandle handle = GCHandle.FromIntPtr(handlePtr); + object obj = handle.Target; + Type type = obj.GetType(); + + if (!type.IsValueType) + return handlePtr; + + // HACK: Get the address of a non-pinned value + IntPtr ptr = IntPtr.Zero; + IntPtr* addr = &ptr; + Activator.CreateInstance(typeof(StructUnboxer<>).MakeGenericType(type), obj, new IntPtr(addr)); + + return ptr; + } + + [UnmanagedCallersOnly] + internal static void RaiseException(IntPtr exceptionHandle) + { + var exception = (Exception)GCHandle.FromIntPtr(exceptionHandle).Target; + throw exception; + } + + [UnmanagedCallersOnly] + internal static void ObjectInit(IntPtr handle) + { + object obj = GCHandle.FromIntPtr(handle).Target; + Type classType = obj.GetType(); + + var ctors = classType.GetMember(".ctor", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance); + var ctor = ctors[0] as ConstructorInfo; + ctor.Invoke(obj, null); + } + + [UnmanagedCallersOnly] + internal static IntPtr InvokeMethod(IntPtr instance, IntPtr methodHandle, IntPtr paramData, IntPtr exceptionPtr) + { + (Type[] parameterTypes, MethodInfo methodInfo) = (Tuple)GCHandle.FromIntPtr(methodHandle).Target; + int numParams = parameterTypes.Length; + + var intPtrParams = stackalloc IntPtr[numParams]; + var objParams = new object[numParams]; + + IntPtr curptr = paramData; + for (int i = 0; i < numParams; i++) + { + intPtrParams[i] = Marshal.ReadIntPtr(curptr); + curptr = IntPtr.Add(curptr, sizeof(IntPtr)); + objParams[i] = MarshalToObject(parameterTypes[i], intPtrParams[i], out _); + } + + object returnObject; + try + { + object inst = instance != IntPtr.Zero ? GCHandle.FromIntPtr(instance).Target : null; + returnObject = methodInfo.Invoke(inst, objParams); + } + catch (Exception exception) + { + // The internal exception thrown in MethodInfo.Invoke is caught here + Exception realException = exception; + if (exception.InnerException != null && exception.TargetSite.ReflectedType.Name == "MethodInvoker") + realException = exception.InnerException; + + if (exceptionPtr != IntPtr.Zero) + Marshal.WriteIntPtr(exceptionPtr, GCHandle.ToIntPtr(GCHandle.Alloc(realException, GCHandleType.Weak))); + else + throw realException; + return IntPtr.Zero; + } + + // Marshal reference parameters back to original unmanaged references + for (int i = 0; i < numParams; i++) + { + Type parameterType = parameterTypes[i]; + if (parameterType.IsByRef) + { + IntPtr paramPtr = intPtrParams[i]; + MarshalFromObject(objParams[i], parameterType.GetElementType(), ref paramPtr); + } + } + + if (returnObject is not null) + { + if (returnObject is string returnStr) + return ManagedString.ToNative(returnStr); + else if (returnObject is IntPtr returnPtr) + return returnPtr; + else if (returnObject is bool boolValue) + return boolValue ? boolTruePtr : boolFalsePtr; + else if (returnObject is Type typeValue) + return GCHandle.ToIntPtr(GetOrAddTypeGCHandle(typeValue)); + else if (returnObject is object[] objectArray) + return GCHandle.ToIntPtr(GCHandle.Alloc(ManagedArray.Get(ManagedArrayToGCHandleArray(objectArray)), GCHandleType.Weak)); + else + return GCHandle.ToIntPtr(GCHandle.Alloc(returnObject, GCHandleType.Weak)); + } + return IntPtr.Zero; + } + + [UnmanagedCallersOnly] + internal static IntPtr GetMethodUnmanagedFunctionPointer(IntPtr methodHandle) + { + (Type[] parameterTypes, MethodInfo methodInfo) = (Tuple)GCHandle.FromIntPtr(methodHandle).Target; + int numParams = parameterTypes.Length; + parameterTypes = parameterTypes.Append(methodInfo.ReturnType).ToArray(); + + // Wrap the method call, this is needed to get the object instance from GCHandle and to pass the exception back to native side + var invokeThunk = typeof(ThunkContext).GetMethod("InvokeThunk"); + parameterTypes = invokeThunk.GetParameters().Select(x => x.ParameterType).Append(invokeThunk.ReturnType).ToArray(); + Type delegateType = DelegateHelpers.NewDelegateType(parameterTypes); + + var context = new ThunkContext(); + context.methodInfo = methodInfo; + context.numParams = numParams; + context.objParams = new object[numParams]; + context.parameterTypes = methodInfo.GetParameters().Select(x => x.ParameterType).ToArray(); + + Delegate methodDelegate = Delegate.CreateDelegate(delegateType, context, invokeThunk); + + IntPtr functionPtr = Marshal.GetFunctionPointerForDelegate(methodDelegate); + + // Keep a reference to the delegate to prevent it from being garbage collected + var delegates = cachedDelegates; + if (parameterTypes.Any(x => scriptingAssemblyLoadContext.Assemblies.Contains(x.Module.Assembly))) + delegates = cachedDelegatesCollectible; + lock (delegates) + { + delegates[functionPtr] = methodDelegate; + } + + return functionPtr; + } + + [UnmanagedCallersOnly] + internal static void FieldSetValue(IntPtr handle, IntPtr fieldHandle, IntPtr value) + { + var obj = GCHandle.FromIntPtr(handle).Target; + FieldInfo fieldInfo = (FieldInfo)GCHandle.FromIntPtr(fieldHandle).Target; + + var val = Marshal.PtrToStructure(value, fieldInfo.FieldType); + fieldInfo.SetValue(obj, val); + } + + [UnmanagedCallersOnly] + internal static void FieldGetValue(IntPtr handle, IntPtr fieldHandle, IntPtr value_) + { + var obj = GCHandle.FromIntPtr(handle).Target; + FieldInfo fieldInfo = (FieldInfo)GCHandle.FromIntPtr(fieldHandle).Target; + object fieldValue = fieldInfo.GetValue(obj); + + IntPtr value; + if (fieldValue is Type type) + value = GCHandle.ToIntPtr(GetOrAddTypeGCHandle(type)); + else if (fieldInfo.FieldType == typeof(IntPtr)) + value = (IntPtr)fieldValue; + else + value = GCHandle.ToIntPtr(GCHandle.Alloc(fieldValue, GCHandleType.Weak)); + Marshal.WriteIntPtr(value_, value); + } + + [UnmanagedCallersOnly] + internal static void SetArrayValueReference(IntPtr arrayHandle, IntPtr elementPtr, IntPtr valueHandle) + { + ManagedArray arr = (ManagedArray)GCHandle.FromIntPtr(arrayHandle).Target; + int index = (int)(elementPtr.ToInt64() - arr.PointerToPinnedArray.ToInt64()) / arr.elementSize; + arr.array.SetValue(valueHandle, index); + } + + [UnmanagedCallersOnly] + internal static IntPtr LoadAssemblyFromPath(IntPtr assemblyPath_, IntPtr* assemblyName, IntPtr* assemblyFullName) + { + if (!firstAssemblyLoaded) + { + // This assembly was already loaded when initializing the host context + firstAssemblyLoaded = true; + + Assembly flaxEngineAssembly = AppDomain.CurrentDomain.GetAssemblies().First(x => x.GetName().Name == "FlaxEngine.CSharp"); + *assemblyName = Marshal.StringToCoTaskMemAnsi(flaxEngineAssembly.GetName().Name); + *assemblyFullName = Marshal.StringToCoTaskMemAnsi(flaxEngineAssembly.FullName); + return GCHandle.ToIntPtr(GetAssemblyHandle(flaxEngineAssembly)); + } + string assemblyPath = Marshal.PtrToStringAnsi(assemblyPath_); + + Assembly assembly = scriptingAssemblyLoadContext.LoadFromAssemblyPath(assemblyPath); + + *assemblyName = Marshal.StringToCoTaskMemAnsi(assembly.GetName().Name); + *assemblyFullName = Marshal.StringToCoTaskMemAnsi(assembly.FullName); + return GCHandle.ToIntPtr(GetAssemblyHandle(assembly)); + } + + [UnmanagedCallersOnly] + internal static IntPtr LoadAssemblyImage(IntPtr data, int len, IntPtr assemblyPath_, IntPtr* assemblyName, IntPtr* assemblyFullName) + { + if (!firstAssemblyLoaded) + { + // This assembly was already loaded when initializing the host context + firstAssemblyLoaded = true; + + Assembly flaxEngineAssembly = AppDomain.CurrentDomain.GetAssemblies().First(x => x.GetName().Name == "FlaxEngine.CSharp"); + *assemblyName = Marshal.StringToCoTaskMemAnsi(flaxEngineAssembly.GetName().Name); + *assemblyFullName = Marshal.StringToCoTaskMemAnsi(flaxEngineAssembly.FullName); + return GCHandle.ToIntPtr(GetAssemblyHandle(flaxEngineAssembly)); + } + + string assemblyPath = Marshal.PtrToStringAnsi(assemblyPath_); + + byte[] raw = new byte[len]; + Marshal.Copy(data, raw, 0, len); + + using MemoryStream stream = new MemoryStream(raw); + Assembly assembly = scriptingAssemblyLoadContext.LoadFromStream(stream); + + // Assemblies loaded via streams have no Location: https://github.com/dotnet/runtime/issues/12822 + AssemblyLocations.Add(assembly.FullName, assemblyPath); + + *assemblyName = Marshal.StringToCoTaskMemAnsi(assembly.GetName().Name); + *assemblyFullName = Marshal.StringToCoTaskMemAnsi(assembly.FullName); + return GCHandle.ToIntPtr(GetAssemblyHandle(assembly)); + } + + [UnmanagedCallersOnly] + internal static IntPtr GetAssemblyByName(IntPtr name_, IntPtr* assemblyName, IntPtr* assemblyFullName) + { + string name = Marshal.PtrToStringAnsi(name_); + + Assembly assembly = AppDomain.CurrentDomain.GetAssemblies().FirstOrDefault(x => x.GetName().Name == name); + if (assembly == null) + assembly = scriptingAssemblyLoadContext.Assemblies.FirstOrDefault(x => x.GetName().Name == name); + + *assemblyName = Marshal.StringToCoTaskMemAnsi(assembly.GetName().Name); + *assemblyFullName = Marshal.StringToCoTaskMemAnsi(assembly.FullName); + return GCHandle.ToIntPtr(GetAssemblyHandle(assembly)); + } + + [UnmanagedCallersOnly] + internal static IntPtr GetRuntimeInformation() + { + return Marshal.StringToCoTaskMemAnsi(System.Runtime.InteropServices.RuntimeInformation.FrameworkDescription); + } + + [UnmanagedCallersOnly] + internal static void CloseAssembly(IntPtr assemblyHandle) + { + Assembly assembly = (Assembly)GCHandle.FromIntPtr(assemblyHandle).Target; + GCHandle.FromIntPtr(assemblyHandle).Free(); + assemblyHandles.Remove(assembly); + + AssemblyLocations.Remove(assembly.FullName); + + // Clear all caches which might hold references to closing assembly + cachedDelegatesCollectible.Clear(); + typeCache.Clear(); + marshallableStructCache.Clear(); + + // Release all GCHandles in collectible ALC + foreach (var pair in typeHandleCacheCollectible) + pair.Value.Free(); + typeHandleCacheCollectible.Clear(); + + foreach (var handle in methodHandlesCollectible) + handle.Free(); + methodHandlesCollectible.Clear(); + + foreach (var handle in fieldHandleCacheCollectible) + handle.Free(); + fieldHandleCacheCollectible.Clear(); + + foreach (var pair in classAttributesCacheCollectible) + pair.Value.Free(); + classAttributesCacheCollectible.Clear(); + + // Unload the ALC + bool unloading = true; + scriptingAssemblyLoadContext.Unloading += (alc) => { unloading = false; }; + scriptingAssemblyLoadContext.Unload(); + + while (unloading) + System.Threading.Thread.Sleep(1); + + // TODO: benchmark collectible setting performance, maybe enable it only in editor builds? + scriptingAssemblyLoadContext = new AssemblyLoadContext(null, true); + } + + [UnmanagedCallersOnly] + internal static IntPtr GetAssemblyObject(IntPtr assemblyName_) + { + string assemblyName = Marshal.PtrToStringAnsi(assemblyName_); + Assembly assembly = null; + assembly = scriptingAssemblyLoadContext.Assemblies.FirstOrDefault(x => x.FullName == assemblyName); + if (assembly == null) + assembly = System.AppDomain.CurrentDomain.GetAssemblies().First(x => x.FullName == assemblyName); + + return GCHandle.ToIntPtr(GCHandle.Alloc(assembly)); + } + + [UnmanagedCallersOnly] + internal static unsafe int NativeSizeOf(IntPtr typeHandle, uint* align) + { + Type originalType = (Type)GCHandle.FromIntPtr(typeHandle).Target; + Type type = GetInternalType(originalType) ?? originalType; + + if (type == typeof(Version)) + type = typeof(VersionNative); + + int size = Marshal.SizeOf(type); + *align = (uint)size; // Is this correct? + return size; + } + + [UnmanagedCallersOnly] + internal static byte TypeIsSubclassOf(IntPtr typeHandle, IntPtr othertypeHandle, byte checkInterfaces) + { + if (typeHandle == othertypeHandle) + return 1; + + Type type = (Type)GCHandle.FromIntPtr(typeHandle).Target; + Type otherType = (Type)GCHandle.FromIntPtr(othertypeHandle).Target; + + if (type == otherType) + return 1; + + if (checkInterfaces != 0 && otherType.IsInterface) + { + if (type.GetInterfaces().Contains(otherType)) + return 1; + } + + return type.IsSubclassOf(otherType) ? (byte)1 : (byte)0; + } + + [UnmanagedCallersOnly] + internal static byte TypeIsValueType(IntPtr typeHandle) + { + Type type = (Type)GCHandle.FromIntPtr(typeHandle).Target; + return (byte)(type.IsValueType ? 1 : 0); + } + + [UnmanagedCallersOnly] + internal static byte TypeIsEnum(IntPtr typeHandle) + { + Type type = (Type)GCHandle.FromIntPtr(typeHandle).Target; + return (byte)(type.IsEnum ? 1 : 0); + } + + [UnmanagedCallersOnly] + internal static IntPtr GetClassParent(IntPtr typeHandle) + { + Type type = (Type)GCHandle.FromIntPtr(typeHandle).Target; + return GCHandle.ToIntPtr(GetTypeGCHandle(type.BaseType)); + } + + [UnmanagedCallersOnly] + internal static byte GetMethodParameterIsOut(IntPtr methodHandle, int parameterNum) + { + (Type[] parameterTypes, MethodInfo methodInfo) = (Tuple)GCHandle.FromIntPtr(methodHandle).Target; + ParameterInfo parameterInfo = methodInfo.GetParameters()[parameterNum]; + return (byte)(parameterInfo.IsOut ? 1 : 0); + } + + [UnmanagedCallersOnly] + internal static IntPtr GetNullReferenceException() + { + var exception = new NullReferenceException(); + return GCHandle.ToIntPtr(GCHandle.Alloc(exception, GCHandleType.Weak)); + } + + [UnmanagedCallersOnly] + internal static IntPtr GetNotSupportedException() + { + var exception = new NotSupportedException(); + return GCHandle.ToIntPtr(GCHandle.Alloc(exception, GCHandleType.Weak)); + } + + [UnmanagedCallersOnly] + internal static IntPtr GetArgumentNullException() + { + var exception = new ArgumentNullException(); + return GCHandle.ToIntPtr(GCHandle.Alloc(exception, GCHandleType.Weak)); + } + + [UnmanagedCallersOnly] + internal static IntPtr GetArgumentException() + { + var exception = new ArgumentException(); + return GCHandle.ToIntPtr(GCHandle.Alloc(exception, GCHandleType.Weak)); + } + + [UnmanagedCallersOnly] + internal static IntPtr GetArgumentOutOfRangeException() + { + var exception = new ArgumentOutOfRangeException(); + return GCHandle.ToIntPtr(GCHandle.Alloc(exception, GCHandleType.Weak)); + } + + [UnmanagedCallersOnly] + internal static IntPtr NewGCHandle(IntPtr ptr, byte pinned) + { + object obj = GCHandle.FromIntPtr(ptr).Target; + GCHandle handle = GCHandle.Alloc(obj, pinned != 0 ? GCHandleType.Pinned : GCHandleType.Normal); + return GCHandle.ToIntPtr(handle); + } + + [UnmanagedCallersOnly] + internal static IntPtr NewGCHandleWeakref(IntPtr ptr, byte track_resurrection) + { + object obj = GCHandle.FromIntPtr(ptr).Target; + GCHandle handle = GCHandle.Alloc(obj, track_resurrection != 0 ? GCHandleType.WeakTrackResurrection : GCHandleType.Weak); + return GCHandle.ToIntPtr(handle); + } + + [UnmanagedCallersOnly] + internal static void FreeGCHandle(IntPtr ptr) + { + GCHandle handle = GCHandle.FromIntPtr(ptr); + handle.Free(); + } + + internal enum MonoType + { + MONO_TYPE_END = 0x00, + MONO_TYPE_VOID = 0x01, + MONO_TYPE_BOOLEAN = 0x02, + MONO_TYPE_CHAR = 0x03, + MONO_TYPE_I1 = 0x04, + MONO_TYPE_U1 = 0x05, + MONO_TYPE_I2 = 0x06, + MONO_TYPE_U2 = 0x07, + MONO_TYPE_I4 = 0x08, + MONO_TYPE_U4 = 0x09, + MONO_TYPE_I8 = 0x0a, + MONO_TYPE_U8 = 0x0b, + MONO_TYPE_R4 = 0x0c, + MONO_TYPE_R8 = 0x0d, + MONO_TYPE_STRING = 0x0e, + MONO_TYPE_PTR = 0x0f, + MONO_TYPE_BYREF = 0x10, + MONO_TYPE_VALUETYPE = 0x11, + MONO_TYPE_CLASS = 0x12, + MONO_TYPE_VAR = 0x13, + MONO_TYPE_ARRAY = 0x14, + MONO_TYPE_GENERICINST = 0x15, + MONO_TYPE_TYPEDBYREF = 0x16, + MONO_TYPE_I = 0x18, + MONO_TYPE_U = 0x19, + MONO_TYPE_FNPTR = 0x1b, + MONO_TYPE_OBJECT = 0x1c, + MONO_TYPE_SZARRAY = 0x1d, + MONO_TYPE_MVAR = 0x1e, + MONO_TYPE_CMOD_REQD = 0x1f, + MONO_TYPE_CMOD_OPT = 0x20, + MONO_TYPE_INTERNAL = 0x21, + MONO_TYPE_MODIFIER = 0x40, + MONO_TYPE_SENTINEL = 0x41, + MONO_TYPE_PINNED = 0x45, + MONO_TYPE_ENUM = 0x55, + }; + + [UnmanagedCallersOnly] + internal static int GetTypeMonoTypeEnum(IntPtr typeHandle) + { + Type type = (Type)GCHandle.FromIntPtr(typeHandle).Target; + + MonoType monoType; + switch (type) + { + case Type _ when type == typeof(bool): + monoType = MonoType.MONO_TYPE_BOOLEAN; + break; + case Type _ when type == typeof(sbyte): + case Type _ when type == typeof(short): + monoType = MonoType.MONO_TYPE_I2; + break; + case Type _ when type == typeof(byte): + case Type _ when type == typeof(ushort): + monoType = MonoType.MONO_TYPE_U2; + break; + case Type _ when type == typeof(int): + monoType = MonoType.MONO_TYPE_I4; + break; + case Type _ when type == typeof(uint): + monoType = MonoType.MONO_TYPE_U4; + break; + case Type _ when type == typeof(long): + monoType = MonoType.MONO_TYPE_I8; + break; + case Type _ when type == typeof(ulong): + monoType = MonoType.MONO_TYPE_U8; + break; + case Type _ when type == typeof(float): + monoType = MonoType.MONO_TYPE_R4; + break; + case Type _ when type == typeof(double): + monoType = MonoType.MONO_TYPE_R8; + break; + case Type _ when type == typeof(string): + monoType = MonoType.MONO_TYPE_STRING; + break; + case Type _ when type == typeof(IntPtr): + monoType = MonoType.MONO_TYPE_PTR; + break; + case Type _ when type.IsEnum: + { + var elementType = type.GetEnumUnderlyingType(); + if (elementType == typeof(sbyte) || elementType == typeof(short)) + monoType = MonoType.MONO_TYPE_I2; + else if (elementType == typeof(byte) || elementType == typeof(ushort)) + monoType = MonoType.MONO_TYPE_U2; + else if (elementType == typeof(int)) + monoType = MonoType.MONO_TYPE_I4; + else if (elementType == typeof(uint)) + monoType = MonoType.MONO_TYPE_U4; + else if (elementType == typeof(long)) + monoType = MonoType.MONO_TYPE_I8; + else if (elementType == typeof(ulong)) + monoType = MonoType.MONO_TYPE_U8; + else + throw new Exception($"GetTypeMonoTypeEnum: Unsupported type '{type.FullName}'"); + break; + } + case Type _ when type.IsValueType && !type.IsEnum && !type.IsPrimitive: + monoType = MonoType.MONO_TYPE_VALUETYPE; + break; + case Type _ when type.IsClass: + monoType = MonoType.MONO_TYPE_OBJECT; + break; + case Type _ when type.IsGenericType: + monoType = MonoType.MONO_TYPE_GENERICINST; + break; + + default: throw new Exception($"GetTypeMonoTypeEnum: Unsupported type '{type.FullName}'"); + } + + return (int)monoType; + } + + /// + /// Returns all types that that owned by this assembly. + /// + private static Type[] GetAssemblyTypes(Assembly assembly) + { + var referencedAssemblies = assembly.GetReferencedAssemblies(); + var allAssemblies = AppDomain.CurrentDomain.GetAssemblies(); + + List referencedTypes = new List(); + foreach (var assemblyName in referencedAssemblies) + { + var asm = allAssemblies.FirstOrDefault(x => x.GetName().Name == assemblyName.Name); + if (asm == null) + continue; + referencedTypes.AddRange(asm.DefinedTypes.Select(x => x.FullName).ToArray()); + } + + // TODO: use MetadataReader to read types without loading any of the referenced assemblies? + // https://makolyte.com/csharp-get-a-list-of-types-defined-in-an-assembly-without-loading-it/ + + /*var assemblyPath = Utils.GetAssemblyLocation(assembly); + List types = new List(); + using (var sr = new StreamReader(assemblyPath)) + { + using (var portableExecutableReader = new PEReader(sr.BaseStream)) + { + var metadataReader = portableExecutableReader.GetMetadataReader(); + + foreach (var typeDefHandle in metadataReader.TypeDefinitions) + { + var typeDef = metadataReader.GetTypeDefinition(typeDefHandle); + + if (string.IsNullOrEmpty(metadataReader.GetString(typeDef.Namespace))) + continue; //if it's namespace is blank, it's not a user-defined type + + if (typeDef.Attributes.HasFlag(TypeAttributes.Abstract) || !typeDef.Attributes.HasFlag(TypeAttributes.Public)) + continue; //Not a internal concrete type + + types.Add(typeDef); + } + } + }*/ + + // We need private types of this assembly too, DefinedTypes contains a lot of types from other assemblies... + var types = referencedTypes.Any() ? assembly.DefinedTypes.Where(x => !referencedTypes.Contains(x.FullName)).ToArray() : assembly.DefinedTypes.ToArray(); + + Assert.IsTrue(AppDomain.CurrentDomain.GetAssemblies().Where(x => x.GetName().Name == "FlaxEngine.CSharp").Count() == 1); + + return types; + } + + internal static GCHandle GetOrAddTypeGCHandle(Type type) + { + GCHandle handle; + if (typeHandleCache.TryGetValue(type.AssemblyQualifiedName, out handle)) + return handle; + + if (typeHandleCacheCollectible.TryGetValue(type.AssemblyQualifiedName, out handle)) + return handle; + + handle = GCHandle.Alloc(type); + if (type.IsCollectible) // check if generic parameters are also collectible? + typeHandleCacheCollectible.Add(type.AssemblyQualifiedName, handle); + else + typeHandleCache.Add(type.AssemblyQualifiedName, handle); + + return handle; + } + + internal static GCHandle GetTypeGCHandle(Type type) + { + GCHandle handle; + if (typeHandleCache.TryGetValue(type.AssemblyQualifiedName, out handle)) + return handle; + + if (typeHandleCacheCollectible.TryGetValue(type.AssemblyQualifiedName, out handle)) + return handle; + + throw new Exception($"GCHandle not found for type '{type.FullName}'"); + } + + internal static class DelegateHelpers + { + private static readonly Func MakeNewCustomDelegate = + (Func)Delegate.CreateDelegate(typeof(Func), + typeof(Expression).Assembly.GetType("System.Linq.Expressions.Compiler.DelegateHelpers") + .GetMethod("MakeNewCustomDelegate", BindingFlags.NonPublic | BindingFlags.Static)); + + internal static void Init() + { + // Ensures the MakeNewCustomDelegate is put in the collectible ALC? + using var ctx = scriptingAssemblyLoadContext.EnterContextualReflection(); + + MakeNewCustomDelegate(new[] { typeof(void) }); + } + + internal static Type NewDelegateType(params Type[] parameters) + { + if (parameters.Any(x => scriptingAssemblyLoadContext.Assemblies.Contains(x.Module.Assembly))) + { + // Ensure the new delegate is placed in the collectible ALC + //using var ctx = scriptingAssemblyLoadContext.EnterContextualReflection(); + return MakeNewCustomDelegate(parameters); + } + + return MakeNewCustomDelegate(parameters); + } + } + + /// + /// Wrapper class for invoking function pointers from unmanaged code. + /// + internal class ThunkContext + { + internal MethodInfo methodInfo; + internal int numParams; + internal Type[] parameterTypes; + internal object[] objParams; + + private static MethodInfo castMethod = typeof(ThunkContext).GetMethod("Cast", BindingFlags.Static | BindingFlags.NonPublic); + + private static object CastParameter(Type type, IntPtr ptr) + { + if (type.IsValueType) + { + var closeCast = castMethod.MakeGenericMethod(type); + return closeCast.Invoke(ptr, new[] { (object)ptr }); + } + else if (type.IsClass) + return ptr == IntPtr.Zero ? null : GCHandle.FromIntPtr(ptr).Target; + return null; + } + + public IntPtr InvokeThunk(IntPtr instance, IntPtr param1, IntPtr param2, IntPtr param3, IntPtr param4, IntPtr param5, IntPtr param6, IntPtr param7) + { + var intPtrParams = stackalloc IntPtr[] { param1, param2, param3, param4, param5, param6, param7 }; + + for (int i = 0; i < numParams; i++) + objParams[i] = CastParameter(parameterTypes[i], intPtrParams[i]); + + object returnObject; + try + { + returnObject = methodInfo.Invoke(instance != IntPtr.Zero ? GCHandle.FromIntPtr(instance).Target : null, objParams); + } + catch (Exception ex) + { + IntPtr exceptionPtr = intPtrParams[numParams]; + Marshal.WriteIntPtr(exceptionPtr, GCHandle.ToIntPtr(GCHandle.Alloc(ex, GCHandleType.Weak))); + return IntPtr.Zero; + } + + // Marshal reference parameters + /*for (int i = 0; i < numParams; i++) + { + Type parameterType = parameterTypes[i]; + if (parameterType.IsByRef) + { + MarshalFromObject(parameterType.GetElementType(), objParams[i], intPtrParams[i], out int writeSize); + } + }*/ + + if (returnObject is not null) + { + if (returnObject is string returnStr) + return ManagedString.ToNative(returnStr); + else if (returnObject is IntPtr returnPtr) + return returnPtr; + else if (returnObject is bool boolValue) + return boolValue ? boolTruePtr : boolFalsePtr; + else + return GCHandle.ToIntPtr(GCHandle.Alloc(returnObject, GCHandleType.Weak)); + } + return IntPtr.Zero; + } + } + } +} +#endif diff --git a/Source/Engine/Level/Level.cpp b/Source/Engine/Level/Level.cpp index b5001a730..7f871df9d 100644 --- a/Source/Engine/Level/Level.cpp +++ b/Source/Engine/Level/Level.cpp @@ -1277,6 +1277,7 @@ bool Level::SaveAllScenes() void Level::SaveAllScenesAsync() { + SCRIPTING_EXPORT("FlaxEngine.Level::Internal_SaveAllScenesAsync") ScopeLock lock(_sceneActionsLocker); for (int32 i = 0; i < Scenes.Count(); i++) _sceneActions.Enqueue(New(Scenes[i])); @@ -1381,6 +1382,7 @@ bool Level::UnloadAllScenes() void Level::UnloadAllScenesAsync() { + SCRIPTING_EXPORT("FlaxEngine.Level::Internal_UnloadAllScenesAsync") ScopeLock lock(_sceneActionsLocker); _sceneActions.Enqueue(New()); } diff --git a/Source/Engine/Networking/NetworkManager.cpp b/Source/Engine/Networking/NetworkManager.cpp index 72634230a..750f9c2aa 100644 --- a/Source/Engine/Networking/NetworkManager.cpp +++ b/Source/Engine/Networking/NetworkManager.cpp @@ -303,6 +303,7 @@ bool NetworkManager::StartHost() void NetworkManager::Stop() { + SCRIPTING_EXPORT("FlaxEngine.Networking.NetworkManager::Internal_Stop"); if (Mode == NetworkManagerMode::Offline && State == NetworkConnectionState::Offline) return; PROFILE_CPU(); diff --git a/Source/Engine/Platform/Base/PlatformBase.h b/Source/Engine/Platform/Base/PlatformBase.h index 7693a3ea6..14513effe 100644 --- a/Source/Engine/Platform/Base/PlatformBase.h +++ b/Source/Engine/Platform/Base/PlatformBase.h @@ -175,6 +175,7 @@ public: /// Size of the memory to copy in bytes FORCE_INLINE static void MemoryCopy(void* dst, const void* src, uint64 size) { + SCRIPTING_EXPORT("FlaxEngine.Utils::MemoryCopy") memcpy(dst, src, static_cast(size)); } @@ -196,6 +197,7 @@ public: /// Size of the memory to clear in bytes FORCE_INLINE static void MemoryClear(void* dst, uint64 size) { + SCRIPTING_EXPORT("FlaxEngine.Utils::MemoryClear") memset(dst, 0, static_cast(size)); } @@ -207,6 +209,7 @@ public: /// Size of the memory to compare in bytes. FORCE_INLINE static int32 MemoryCompare(const void* buf1, const void* buf2, uint64 size) { + SCRIPTING_EXPORT("FlaxEngine.Utils::MemoryCompare") return memcmp(buf1, buf2, static_cast(size)); } diff --git a/Source/Engine/Platform/Linux/LinuxPlatform.cpp b/Source/Engine/Platform/Linux/LinuxPlatform.cpp index 50ac72381..c0b7a6bf8 100644 --- a/Source/Engine/Platform/Linux/LinuxPlatform.cpp +++ b/Source/Engine/Platform/Linux/LinuxPlatform.cpp @@ -1658,6 +1658,7 @@ DragDropEffect LinuxWindow::DoDragDrop(const StringView& data) void LinuxClipboard::Clear() { + SCRIPTING_EXPORT("FlaxEngine.Clipboard::Internal_Clear"); SetText(StringView::Empty); } diff --git a/Source/Engine/Platform/Mac/MacPlatform.cpp b/Source/Engine/Platform/Mac/MacPlatform.cpp index 19325bbc3..66d90e21a 100644 --- a/Source/Engine/Platform/Mac/MacPlatform.cpp +++ b/Source/Engine/Platform/Mac/MacPlatform.cpp @@ -110,6 +110,7 @@ Float2 MacUtils::GetScreensOrigin() void MacClipboard::Clear() { + SCRIPTING_EXPORT("FlaxEngine.Clipboard::Internal_Clear"); NSPasteboard* pasteboard = [NSPasteboard generalPasteboard]; [pasteboard clearContents]; } diff --git a/Source/Engine/Platform/Windows/WindowsClipboard.cpp b/Source/Engine/Platform/Windows/WindowsClipboard.cpp index 28f4c2188..0c9e46296 100644 --- a/Source/Engine/Platform/Windows/WindowsClipboard.cpp +++ b/Source/Engine/Platform/Windows/WindowsClipboard.cpp @@ -16,6 +16,7 @@ typedef struct _DROPFILES void WindowsClipboard::Clear() { + SCRIPTING_EXPORT("FlaxEngine.Clipboard::Internal_Clear"); OpenClipboard(nullptr); EmptyClipboard(); CloseClipboard(); diff --git a/Source/Engine/Render2D/Render2D.cpp b/Source/Engine/Render2D/Render2D.cpp index d929bdb5c..63562be0b 100644 --- a/Source/Engine/Render2D/Render2D.cpp +++ b/Source/Engine/Render2D/Render2D.cpp @@ -699,6 +699,7 @@ void Render2D::Begin(GPUContext* context, GPUTextureView* output, GPUTextureView void Render2D::End() { + SCRIPTING_EXPORT("FlaxEngine.Render2D::Internal_End") RENDER2D_CHECK_RENDERING_STATE; ASSERT(Context != nullptr && Output != nullptr); ASSERT(GUIShader != nullptr); @@ -814,6 +815,7 @@ void Render2D::PeekTransform(Matrix3x3& transform) void Render2D::PopTransform() { + SCRIPTING_EXPORT("FlaxEngine.Render2D::Internal_PopTransform") RENDER2D_CHECK_RENDERING_STATE; ASSERT(TransformLayersStack.HasItems()); @@ -855,6 +857,7 @@ void Render2D::PeekClip(Rectangle& clipRect) void Render2D::PopClip() { + SCRIPTING_EXPORT("FlaxEngine.Render2D::Internal_PopClip") RENDER2D_CHECK_RENDERING_STATE; ClipLayersStack.Pop(); @@ -876,6 +879,7 @@ void Render2D::PeekTint(Color& tint) void Render2D::PopTint() { + SCRIPTING_EXPORT("FlaxEngine.Render2D::Internal_PopTint") RENDER2D_CHECK_RENDERING_STATE; TintLayersStack.Pop(); diff --git a/Source/Engine/Render2D/Render2D.cs b/Source/Engine/Render2D/Render2D.cs index b642cc5c1..22ce6529b 100644 --- a/Source/Engine/Render2D/Render2D.cs +++ b/Source/Engine/Render2D/Render2D.cs @@ -64,8 +64,7 @@ namespace FlaxEngine /// The rectangle to draw. public static void DrawSprite(SpriteHandle spriteHandle, Rectangle rect) { - var color = Color.White; - Internal_DrawSprite(ref spriteHandle, ref rect, ref color); + DrawSprite(spriteHandle, rect, Color.White); } /// @@ -86,8 +85,7 @@ namespace FlaxEngine /// The rectangle to draw. public static void DrawSpritePoint(SpriteHandle spriteHandle, Rectangle rect) { - var color = Color.White; - Internal_DrawSpritePoint(ref spriteHandle, ref rect, ref color); + DrawSpritePoint(spriteHandle, rect, Color.White); } /// diff --git a/Source/Engine/Renderer/Renderer.cs b/Source/Engine/Renderer/Renderer.cs index 6c9b25dff..9efe13e75 100644 --- a/Source/Engine/Renderer/Renderer.cs +++ b/Source/Engine/Renderer/Renderer.cs @@ -15,7 +15,9 @@ namespace FlaxEngine /// The custom set of actors to render. If empty, the loaded scenes will be rendered. public static void DrawSceneDepth(GPUContext context, SceneRenderTask task, GPUTexture output, List customActors) { - Internal_DrawSceneDepth(FlaxEngine.Object.GetUnmanagedPtr(context), FlaxEngine.Object.GetUnmanagedPtr(task), FlaxEngine.Object.GetUnmanagedPtr(output), Utils.ExtractArrayFromList(customActors)); + var temp = Utils.ExtractArrayFromList(customActors); + var tempCount = temp.Length; + Internal_DrawSceneDepth(FlaxEngine.Object.GetUnmanagedPtr(context), FlaxEngine.Object.GetUnmanagedPtr(task), FlaxEngine.Object.GetUnmanagedPtr(output), ref temp, ref tempCount); } } } diff --git a/Source/Engine/Scripting/BinaryModule.cpp b/Source/Engine/Scripting/BinaryModule.cpp index eae964e29..53887c94b 100644 --- a/Source/Engine/Scripting/BinaryModule.cpp +++ b/Source/Engine/Scripting/BinaryModule.cpp @@ -820,22 +820,33 @@ MMethod* ManagedBinaryModule::FindMethod(MClass* mclass, const ScriptingTypeMeth { #if USE_MONO MonoMethodSignature* sig = mono_method_signature(method->GetNative()); - if (method->IsStatic() != signature.IsStatic || + /*if (method->IsStatic() != signature.IsStatic || method->GetName() != signature.Name || (int32)mono_signature_get_param_count(sig) != signature.Params.Count()) + continue;*/ + if (method->IsStatic() != signature.IsStatic) + continue; + if (method->GetName() != signature.Name) + continue; + if ((int32)mono_signature_get_param_count(sig) != signature.Params.Count()) continue; void* sigParams = nullptr; - mono_signature_get_params(sig, &sigParams); + MonoType* type = mono_signature_get_params(sig, &sigParams); bool isValid = true; - for (int32 paramIdx = 0; paramIdx < signature.Params.Count(); paramIdx++) + int paramIdx = 0; + while (type != nullptr) { auto& param = signature.Params[paramIdx]; if (param.IsOut != (mono_signature_param_is_out(sig, paramIdx) != 0) || - !VariantTypeEquals(param.Type, ((MonoType**)sigParams)[paramIdx])) + !VariantTypeEquals(param.Type, type)) { + auto asdf = VariantTypeEquals(param.Type, type); isValid = false; break; } + + type = mono_signature_get_params(sig, &sigParams); + paramIdx++; } if (isValid && VariantTypeEquals(signature.ReturnType, mono_signature_get_return_type(sig))) return method; @@ -1187,8 +1198,6 @@ bool ManagedBinaryModule::InvokeMethod(void* method, const Variant& instance, Sp #if USE_MONO const auto mMethod = (MMethod*)method; MonoMethodSignature* signature = mono_method_signature(mMethod->GetNative()); - void* signatureParams = nullptr; - mono_signature_get_params(signature, &signatureParams); const int32 parametersCount = mono_signature_get_param_count(signature); if (paramValues.Length() != parametersCount) { @@ -1222,20 +1231,25 @@ bool ManagedBinaryModule::InvokeMethod(void* method, const Variant& instance, Sp void** params = (void**)alloca(parametersCount * sizeof(void*)); bool failed = false; bool hasOutParams = false; - for (int32 paramIdx = 0; paramIdx < parametersCount; paramIdx++) + void* sigParams = nullptr; + MonoType* type = mono_signature_get_params(signature, &sigParams); + for (int paramIdx = 0; type != nullptr;) { auto& paramValue = paramValues[paramIdx]; const bool isOut = mono_signature_param_is_out(signature, paramIdx) != 0; hasOutParams |= isOut; // Marshal parameter for managed method - MType paramType(((MonoType**)signatureParams)[paramIdx]); + MType paramType(type); params[paramIdx] = MUtils::VariantToManagedArgPtr(paramValue, paramType, failed); if (failed) { LOG(Error, "Failed to marshal parameter {5}:{4} of method '{0}.{1}' (args count: {2}), value type: {6}, value: {3}", String(mMethod->GetParentClass()->GetFullName()), String(mMethod->GetName()), parametersCount, paramValue, paramType.ToString(), paramIdx, paramValue.Type); return true; } + + type = mono_signature_get_params(signature, &sigParams); + paramIdx++; } // Invoke the method diff --git a/Source/Engine/Scripting/DotNet/CoreCLR.cpp b/Source/Engine/Scripting/DotNet/CoreCLR.cpp new file mode 100644 index 000000000..4d7d53052 --- /dev/null +++ b/Source/Engine/Scripting/DotNet/CoreCLR.cpp @@ -0,0 +1,151 @@ +#include "CoreCLR.h" + +#include "Engine/Core/Log.h" +#include "Engine/Platform/Platform.h" +#include "Engine/Platform/FileSystem.h" +#include "Engine/Core/Types/DateTime.h" +#include "Engine/Debug/DebugLog.h" +#include "Engine/Core/Collections/Dictionary.h" + +#include +#include +#include + +#if PLATFORM_WINDOWS +#include // CoTask* +#undef SetEnvironmentVariable +#undef LoadLibrary +#endif + +#if COMPILE_WITH_PROFILER +#endif + +static Dictionary cachedFunctions; +static String assemblyName = TEXT("FlaxEngine.CSharp"); +#if PLATFORM_WINDOWS +static const char_t* typeName = TEXT("FlaxEngine.NativeInterop, FlaxEngine.CSharp"); +#else +static const char_t* typeName = "FlaxEngine.NativeInterop, FlaxEngine.CSharp"; +#endif + +hostfxr_initialize_for_runtime_config_fn hostfxr_initialize_for_runtime_config; +hostfxr_initialize_for_dotnet_command_line_fn hostfxr_initialize_for_dotnet_command_line; +hostfxr_get_runtime_delegate_fn hostfxr_get_runtime_delegate; +hostfxr_close_fn hostfxr_close; +load_assembly_and_get_function_pointer_fn load_assembly_and_get_function_pointer; +get_function_pointer_fn get_function_pointer; +hostfxr_set_error_writer_fn hostfxr_set_error_writer; +hostfxr_get_dotnet_environment_info_result_fn hostfxr_get_dotnet_environment_info_result; +hostfxr_run_app_fn hostfxr_run_app; + +bool CoreCLR::LoadHostfxr(const String& library_path_) +{ + const FLAX_CORECLR_STRING& library_path = FLAX_CORECLR_STRING(library_path_); + + Platform::SetEnvironmentVariable(TEXT("DOTNET_TieredPGO"), TEXT("1")); + Platform::SetEnvironmentVariable(TEXT("DOTNET_TC_QuickJitForLoops"), TEXT("1")); + Platform::SetEnvironmentVariable(TEXT("DOTNET_ReadyToRun"), TEXT("0")); + + char_t hostfxrPath[1024]; + size_t hostfxrPathSize = sizeof(hostfxrPath) / sizeof(char_t); + + get_hostfxr_parameters params; + params.size = sizeof(hostfxr_initialize_parameters); + params.assembly_path = library_path.Get(); + params.dotnet_root = nullptr;//dotnetRoot.Get(); + + int rc = get_hostfxr_path(hostfxrPath, &hostfxrPathSize, ¶ms); + if (rc != 0) + { + LOG(Error, "Failed to find hostfxr: {0:x}", (unsigned int)rc); + return false; + } + String path(hostfxrPath); + LOG(Info, "Found hostfxr in {0}", path); + + void *hostfxr = Platform::LoadLibrary(path.Get()); + hostfxr_initialize_for_runtime_config = (hostfxr_initialize_for_runtime_config_fn)Platform::GetProcAddress(hostfxr, "hostfxr_initialize_for_runtime_config"); + hostfxr_initialize_for_dotnet_command_line = (hostfxr_initialize_for_dotnet_command_line_fn)Platform::GetProcAddress(hostfxr, "hostfxr_initialize_for_dotnet_command_line"); + hostfxr_get_runtime_delegate = (hostfxr_get_runtime_delegate_fn)Platform::GetProcAddress(hostfxr, "hostfxr_get_runtime_delegate"); + hostfxr_close = (hostfxr_close_fn)Platform::GetProcAddress(hostfxr, "hostfxr_close"); + hostfxr_set_error_writer = (hostfxr_set_error_writer_fn)Platform::GetProcAddress(hostfxr, "hostfxr_set_error_writer"); + hostfxr_get_dotnet_environment_info_result = (hostfxr_get_dotnet_environment_info_result_fn)Platform::GetProcAddress(hostfxr, "hostfxr_get_dotnet_environment_info_result"); + hostfxr_run_app = (hostfxr_run_app_fn)Platform::GetProcAddress(hostfxr, "hostfxr_run_app"); + + return true; +} + +bool CoreCLR::InitHostfxr(const String& config_path, const String& library_path_) +{ + const FLAX_CORECLR_STRING& library_path = FLAX_CORECLR_STRING(library_path_); + const char_t* argv[1] = { library_path.Get() }; + + hostfxr_initialize_parameters params; + params.size = sizeof(hostfxr_initialize_parameters); + params.host_path = library_path.Get(); + params.dotnet_root = nullptr;//dotnetRoot.Get(); // This probably must be set + + hostfxr_handle handle = nullptr; + + // Initialize hosting component, hostfxr_initialize_for_dotnet_command_line is used here + // to allow self-contained engine installation to be used when needed. + + int rc = hostfxr_initialize_for_dotnet_command_line(1, argv, ¶ms, &handle); + if (rc != 0 || handle == nullptr) + { + LOG(Error, "Failed to initialize hostfxr: {0:x}", (unsigned int)rc); + hostfxr_close(handle); + return false; + } + + void* pget_function_pointer = nullptr; + rc = hostfxr_get_runtime_delegate(handle, hdt_get_function_pointer, &pget_function_pointer); + if (rc != 0 || pget_function_pointer == nullptr) + LOG(Error, "Failed to get runtime delegate hdt_get_function_pointer: {0:x}", (unsigned int)rc); + + hostfxr_close(handle); + get_function_pointer = (get_function_pointer_fn)pget_function_pointer; + + return true; +} + +void* CoreCLR::GetStaticMethodPointer(const String& methodName) +{ + void* fun; + if (cachedFunctions.TryGet(methodName, fun)) + return fun; + + int rc = get_function_pointer(typeName, FLAX_CORECLR_STRING(methodName).Get(), UNMANAGEDCALLERSONLY_METHOD, nullptr, nullptr, &fun); + if (rc != 0) + LOG(Fatal, "Failed to get unmanaged function pointer for method {0}: 0x{1:x}", methodName.Get(), (unsigned int)rc); + + cachedFunctions.Add(String(methodName), fun); + + return fun; +} + +void* CoreCLR::Allocate(int size) +{ +#if PLATFORM_WINDOWS + void* ptr = CoTaskMemAlloc(size); +#else + void* ptr = malloc(size); +#endif + +#if COMPILE_WITH_PROFILER + Platform::OnMemoryAlloc(ptr, size); +#endif + return ptr; +} + +void CoreCLR::Free(void* ptr) +{ +#if COMPILE_WITH_PROFILER + Platform::OnMemoryFree(ptr); +#endif +#if PLATFORM_WINDOWS + CoTaskMemFree(ptr); +#else + free(ptr); +#endif +} diff --git a/Source/Engine/Scripting/DotNet/CoreCLR.h b/Source/Engine/Scripting/DotNet/CoreCLR.h new file mode 100644 index 000000000..2f9c2a753 --- /dev/null +++ b/Source/Engine/Scripting/DotNet/CoreCLR.h @@ -0,0 +1,49 @@ +#pragma once + +// FIXME +#include + +#include "Engine/Core/Types/String.h" +#include "Engine/Scripting/Types.h" + +#if defined(_WIN32) +#define CORECLR_DELEGATE_CALLTYPE __stdcall +#define FLAX_CORECLR_STRING String +#else +#define CORECLR_DELEGATE_CALLTYPE +#define FLAX_CORECLR_STRING StringAnsi +#endif + +class CoreCLR +{ +private: +public: + static bool LoadHostfxr(const String& library_path); + static bool InitHostfxr(const String& config_path, const String& library_path); + + static void* GetStaticMethodPointer(const String& methodName); + + template + static RetType CallStaticMethodInternal(const String& methodName, Args... args) + { + typedef RetType(CORECLR_DELEGATE_CALLTYPE* fun)(Args...); + fun function = (fun)GetStaticMethodPointer(methodName); + return function(args...); + } + + template + static RetType CallStaticMethodInternalPointer(void* funPtr, Args... args) + { + typedef RetType(CORECLR_DELEGATE_CALLTYPE* fun)(Args...); + fun function = (fun)funPtr; + return function(args...); + } + + static const char* GetClassFullname(void* klass); + static void* Allocate(int size); + static void Free(void* ptr); + static gchandle NewGCHandle(void* obj, bool pinned); + static gchandle NewGCHandleWeakref(void* obj, bool track_resurrection); + static void* GetGCHandleTarget(const gchandle& gchandle); + static void FreeGCHandle(const gchandle& gchandle); +}; diff --git a/Source/Engine/Scripting/DotNet/MonoApi.cpp b/Source/Engine/Scripting/DotNet/MonoApi.cpp new file mode 100644 index 000000000..2d290b65e --- /dev/null +++ b/Source/Engine/Scripting/DotNet/MonoApi.cpp @@ -0,0 +1,1564 @@ +#include +#include + +#include "CoreCLR.h" +#include "Engine/Scripting/Types.h" +#include "Engine/Core/Collections/Dictionary.h" +#include "Engine/Graphics/RenderView.h" +#include "Engine/Core/Types/StringBuilder.h" + +#pragma warning(disable : 4297) + +struct CoreCLRClass* GetClass(void* type); +struct CoreCLRClass* GetOrCreateClass(void* type); + +struct _MonoString +{ + int32_t length; + mono_unichar2 chars[MONO_ZERO_LEN_ARRAY]; +}; + +struct CoreCLRAssembly; +struct CoreCLRMethod; +struct CoreCLRField; +struct CoreCLRCustomAttribute; +struct CoreCLRProperty; +struct CoreCLRClass; + +// Structures used to pass information from runtime, must match with the structures in managed side +struct ManagedClass +{ + void* typeHandle; + const char* name; + const char* fullname; + const char* namespace_; + uint32 typeAttributes; +}; + +struct ClassMethod +{ + const char* name; + int numParameters; + void* handle; + uint32 methodAttributes; +}; + +struct ClassField +{ + const char* name; + void* fieldHandle; + void* fieldType; + uint32 fieldAttributes; +}; + +struct ClassProperty +{ + const char* name; + void* getterHandle; + void* setterHandle; + uint32 getterFlags; + uint32 setterFlags; +}; + +struct ClassAttribute +{ + const char* name; + void* attributeHandle; + void* attributeTypeHandle; +}; + +Dictionary classHandles; +Dictionary assemblyHandles; +uint32 TypeTokenPool = 0; + +struct CoreCLRAssembly +{ +private: + StringAnsi _name; + StringAnsi _fullname; + Array _classes; + void* _assemblyHandle; + +public: + CoreCLRAssembly(void* assemblyHandle, const char* name, const char* fullname) + { + _assemblyHandle = assemblyHandle; + _name = name; + _fullname = fullname; + + ManagedClass* managedClasses; + int classCount; + + CoreCLR::CallStaticMethodInternal(TEXT("GetManagedClasses"), _assemblyHandle, &managedClasses, &classCount); + for (int i = 0; i < classCount; i++) + { + CoreCLRClass* mci = New(managedClasses[i].typeHandle, StringAnsi(managedClasses[i].name), StringAnsi(managedClasses[i].fullname), StringAnsi(managedClasses[i].namespace_), managedClasses[i].typeAttributes, this); + _classes.Add(mci); + + ASSERT(managedClasses[i].typeHandle != nullptr); + classHandles.Add(managedClasses[i].typeHandle, mci); + + CoreCLR::Free((void*)managedClasses[i].name); + CoreCLR::Free((void*)managedClasses[i].fullname); + CoreCLR::Free((void*)managedClasses[i].namespace_); + } + CoreCLR::Free(managedClasses); + + assemblyHandles.Add(_assemblyHandle, this); + } + + ~CoreCLRAssembly() + { + _classes.ClearDelete(); + assemblyHandles.Remove(_assemblyHandle); + } + + void* GetHandle() + { + return _assemblyHandle; + } + + const StringAnsi& GetName() + { + return _name; + } + + const StringAnsi& GetFullname() + { + return _fullname; + } + + Array GetClasses() + { + return _classes; + } + + void AddClass(CoreCLRClass* klass) + { + _classes.Add(klass); + } +}; + +struct CoreCLRClass +{ +private: + StringAnsi _fullname; + StringAnsi _name; + StringAnsi _namespace; + uint32 _typeAttributes; + CoreCLRAssembly* _image; + uint32 _typeToken; + uint32 _size; + void* _typeHandle; + bool _cachedMethods = false; + Array _methods; + bool _cachedFields = false; + Array _fields; + bool _cachedAttributes = false; + Array _attributes; + bool _cachedProperties = false; + Array _properties; + bool _cachedInterfaces = false; + Array _interfaces; + +public: + CoreCLRClass(void* typeHandle, StringAnsi name, StringAnsi fullname, StringAnsi namespace_, uint32 typeAttributes, CoreCLRAssembly* image) + : _typeHandle(typeHandle), _name(name), _fullname(fullname), _namespace(namespace_), _typeAttributes(typeAttributes), _image(image) + { + _typeToken = TypeTokenPool++; + } + + ~CoreCLRClass() + { + for (auto method : _methods) + { + //int rem = monoMethods.RemoveValue(method); + //ASSERT(rem > 0) + } + _methods.ClearDelete(); + _fields.ClearDelete(); + _attributes.ClearDelete(); + _properties.ClearDelete(); + _interfaces.Clear(); + + classHandles.Remove(_typeHandle); + } + + uint32 GetAttributes() + { + return _typeAttributes; + } + + uint32 GetTypeToken() + { + return _typeToken; + } + + int GetSize() + { + if (_size != 0) + return _size; + + uint32 dummy; + _size = mono_class_value_size((MonoClass*)this, &dummy); + + return _size; + } + + const StringAnsi& GetName() + { + return _name; + } + + const StringAnsi& GetFullname() + { + return _fullname; // FIXME: this should probably return the decorated C# name for generic types (foo) and not the IL-name (foo`1[[T) + } + + const StringAnsi& GetNamespace() + { + return _namespace; + } + + void* GetTypeHandle() + { + return _typeHandle; + } + + CoreCLRAssembly* GetAssembly() + { + return _image; + } + + Array GetMethods() + { + if (_cachedMethods) + return _methods; + + ClassMethod* foundMethods; + int numMethods; + + CoreCLR::CallStaticMethodInternal(TEXT("GetClassMethods"), _typeHandle, &foundMethods, &numMethods); + for (int i = 0; i < numMethods; i++) + { + CoreCLRMethod* method = New(StringAnsi(foundMethods[i].name), foundMethods[i].numParameters, foundMethods[i].handle, foundMethods[i].methodAttributes, this); + _methods.Add(method); + + CoreCLR::Free((void*)foundMethods[i].name); + } + CoreCLR::Free(foundMethods); + + _cachedMethods = true; + return _methods; + } + + Array GetFields() + { + if (_cachedFields) + return _fields; + + ClassField* foundFields; + int numFields; + + CoreCLR::CallStaticMethodInternal(TEXT("GetClassFields"), _typeHandle, &foundFields, &numFields); + for (int i = 0; i < numFields; i++) + { + CoreCLRField* field = New(StringAnsi(foundFields[i].name), foundFields[i].fieldHandle, foundFields[i].fieldType, foundFields[i].fieldAttributes, this); + _fields.Add(field); + + CoreCLR::Free((void*)foundFields[i].name); + } + CoreCLR::Free(foundFields); + + _cachedFields = true; + return _fields; + } + + Array GetProperties() + { + if (_cachedProperties) + return _properties; + + ClassProperty* foundProperties; + int numProperties; + + CoreCLR::CallStaticMethodInternal(TEXT("GetClassProperties"), _typeHandle, &foundProperties, &numProperties); + for (int i = 0; i < numProperties; i++) + { + CoreCLRProperty* prop = New(StringAnsi(foundProperties[i].name), foundProperties[i].getterHandle, foundProperties[i].setterHandle, foundProperties[i].getterFlags, foundProperties[i].setterFlags, this); + _properties.Add(prop); + + CoreCLR::Free((void*)foundProperties[i].name); + } + CoreCLR::Free(foundProperties); + + _cachedProperties = true; + return _properties; + } + + Array GetCustomAttributes() + { + if (_cachedAttributes) + return _attributes; + + ClassAttribute* foundAttributes; + int numAttributes; + + CoreCLR::CallStaticMethodInternal(TEXT("GetClassAttributes"), _typeHandle, &foundAttributes, &numAttributes); + for (int i = 0; i < numAttributes; i++) + { + CoreCLRClass* attributeClass = GetClass(foundAttributes[i].attributeTypeHandle); + CoreCLRCustomAttribute* attribute = New(StringAnsi(foundAttributes[i].name), foundAttributes[i].attributeHandle, this, attributeClass); + _attributes.Add(attribute); + + CoreCLR::Free((void*)foundAttributes[i].name); + } + CoreCLR::Free(foundAttributes); + + _cachedAttributes = true; + return _attributes; + } + + Array GetInterfaces() + { + if (_cachedInterfaces) + return _interfaces; + + void** foundInterfaces; + int numInterfaces; + + CoreCLR::CallStaticMethodInternal(TEXT("GetClassInterfaces"), _typeHandle, &foundInterfaces, &numInterfaces); + for (int i = 0; i < numInterfaces; i++) + { + CoreCLRClass* interfaceClass = classHandles[foundInterfaces[i]]; + _interfaces.Add(interfaceClass); + } + CoreCLR::Free(foundInterfaces); + + _cachedInterfaces = true; + return _interfaces; + } +}; + +struct CoreCLRMethod +{ +private: + StringAnsi _name; + int _numParams; + CoreCLRClass* _class; + void* _methodHandle; + bool _cachedParameters = false; + Array _parameterTypes; + void* _returnType; + uint32 _methodAttributes; + +public: + CoreCLRMethod(StringAnsi name, int numParams, void* methodHandle, uint32 flags, CoreCLRClass* klass) + :_name(name), _numParams(numParams), _methodHandle(methodHandle), _methodAttributes(flags), _class(klass) + { + } + + const StringAnsi& GetName() + { + return _name; + } + + CoreCLRClass* GetClass() + { + return _class; + } + + uint32 GetAttributes() + { + return _methodAttributes; + } + + int GetNumParameters() + { + return _numParams; + } + + void* GetMethodHandle() + { + return _methodHandle; + } + + Array GetParameterTypes() + { + if (!_cachedParameters) + CacheParameters(); + return _parameterTypes; + } + + void* GetReturnType() + { + if (!_cachedParameters) + CacheParameters(); + return _returnType; + } + + void CacheParameters() + { + _returnType = CoreCLR::CallStaticMethodInternal(TEXT("GetMethodReturnType"), _methodHandle); + + void** parameterTypeHandles; + CoreCLR::CallStaticMethodInternal(TEXT("GetMethodParameterTypes"), _methodHandle, ¶meterTypeHandles); + + _parameterTypes.SetCapacity(_numParams, false); + + for (int i = 0; i < _numParams; i++) + { + _parameterTypes.Add(parameterTypeHandles[i]); + } + CoreCLR::Free(parameterTypeHandles); + + _cachedParameters = true; + } +}; + +struct CoreCLRField +{ +private: + StringAnsi _name; + CoreCLRClass* _class; + void* _fieldHandle; + void* _fieldType; + uint32 _fieldAttributes; + +public: + CoreCLRField(StringAnsi name, void* fieldHandle, void* fieldType, uint32 fieldAttributes, CoreCLRClass* klass) + :_name(name), _fieldHandle(fieldHandle), _fieldType(fieldType), _fieldAttributes(fieldAttributes), _class(klass) + { + } + + const StringAnsi& GetName() + { + return _name; + } + + void* GetType() + { + return _fieldType; + } + + CoreCLRClass* GetClass() + { + return _class; + } + + uint32 GetAttributes() + { + return _fieldAttributes; + } + + void* GetHandle() + { + return _fieldHandle; + } +}; + +struct CoreCLRProperty +{ +private: + StringAnsi _name; + CoreCLRClass* _class; + CoreCLRMethod* _getMethod; + CoreCLRMethod* _setMethod; + +public: + CoreCLRProperty(StringAnsi name, void* getter, void* setter, uint32 getterFlags, uint32 setterFlags, CoreCLRClass* klass) + :_name(name), _class(klass) + { + if (getter != nullptr) + _getMethod = New(StringAnsi(_name + "Get"), 1, getter, getterFlags, klass); + if (setter != nullptr) + _setMethod = New(StringAnsi(_name + "Set"), 1, setter, setterFlags, klass); + } + + const StringAnsi& GetName() + { + return _name; + } + + CoreCLRClass* GetClass() + { + return _class; + } + + CoreCLRMethod* GetGetMethod() + { + return _getMethod; + } + + CoreCLRMethod* GetSetMethod() + { + return _setMethod; + } +}; + +struct CoreCLRCustomAttribute +{ +private: + StringAnsi _name; + void* _handle; + CoreCLRClass* _owningClass; + CoreCLRClass* _attributeClass; + +public: + CoreCLRCustomAttribute(StringAnsi name, void* handle, CoreCLRClass* owningClass, CoreCLRClass* attributeClass) + :_name(name), _handle(handle), _owningClass(owningClass), _attributeClass(attributeClass) + { + } + + void* GetHandle() + { + return _handle; + } + + CoreCLRClass* GetClass() + { + return _attributeClass; + } +}; + +CoreCLRAssembly* GetAssembly(void* assemblyHandle) +{ + CoreCLRAssembly* assembly; + if (assemblyHandles.TryGet(assemblyHandle, assembly)) + return assembly; + return nullptr; +} + +CoreCLRClass* GetClass(void* type) +{ + CoreCLRClass* klass; + if (classHandles.TryGet(type, klass)) + return klass; + return nullptr; +} + +CoreCLRClass* GetOrCreateClass(void* type) +{ + CoreCLRClass* klass; + if (!classHandles.TryGet(type, klass)) + { + ManagedClass classInfo; + void* assemblyHandle; + CoreCLR::CallStaticMethodInternal(TEXT("GetManagedClassFromType"), type, &classInfo, &assemblyHandle); + CoreCLRAssembly* image = GetAssembly(assemblyHandle); + klass = New(classInfo.typeHandle, StringAnsi(classInfo.name), StringAnsi(classInfo.fullname), StringAnsi(classInfo.namespace_), classInfo.typeAttributes, image); + if (image != nullptr) + image->AddClass(klass); + + if (type != classInfo.typeHandle) + CoreCLR::CallStaticMethodInternal(TEXT("GetManagedClassFromType"), type, &classInfo); + classHandles.Add(classInfo.typeHandle, klass); + + CoreCLR::Free((void*)classInfo.name); + CoreCLR::Free((void*)classInfo.fullname); + CoreCLR::Free((void*)classInfo.namespace_); + } + ASSERT(klass != nullptr); + return klass; +} + +gchandle CoreCLR::NewGCHandle(void* obj, bool pinned) +{ + return (gchandle)CoreCLR::CallStaticMethodInternal(TEXT("NewGCHandle"), obj, pinned); +} + +gchandle CoreCLR::NewGCHandleWeakref(void* obj, bool track_resurrection) +{ + return (gchandle)CoreCLR::CallStaticMethodInternal(TEXT("NewGCHandleWeakref"), obj, track_resurrection); +} + +void* CoreCLR::GetGCHandleTarget(const gchandle& gchandle) +{ + return (void*)gchandle; +} + +void CoreCLR::FreeGCHandle(const gchandle& gchandle) +{ + CoreCLR::CallStaticMethodInternal(TEXT("FreeGCHandle"), (void*)gchandle); +} + +const char* CoreCLR::GetClassFullname(void* klass) +{ + return ((CoreCLRClass*)klass)->GetFullname().Get(); +} + +/* + * loader.h +*/ + +MONO_API MonoMethodSignature* mono_method_signature(MonoMethod* method) +{ + return (MonoMethodSignature*)method; +} + +MONO_API const char* mono_method_get_name(MonoMethod* method) +{ + return ((CoreCLRMethod*)method)->GetName().Get(); +} + +MONO_API MonoClass* mono_method_get_class(MonoMethod* method) +{ + return (MonoClass*)((CoreCLRMethod*)method)->GetClass(); +} + +MONO_API uint32 mono_method_get_flags(MonoMethod* method, uint32* iflags) +{ + return ((CoreCLRMethod*)method)->GetAttributes(); +} + +MONO_API MONO_RT_EXTERNAL_ONLY void mono_add_internal_call(const char* name, const void* method) +{ + // Ignored, prevents the linker from removing unused functions +} + +/* + * objects.h +*/ + +MONO_API mono_unichar2* mono_string_chars(MonoString* s) +{ + _MonoString* str = (_MonoString*)CoreCLR::CallStaticMethodInternal(TEXT("GetStringPointer"), s); + return str->chars; +} + +MONO_API int mono_string_length(MonoString* s) +{ + _MonoString* str = (_MonoString*)CoreCLR::CallStaticMethodInternal(TEXT("GetStringPointer"), s); + return str->length; +} + +MONO_API MONO_RT_EXTERNAL_ONLY MonoObject* mono_object_new(MonoDomain* domain, MonoClass* klass) +{ + return (MonoObject*)CoreCLR::CallStaticMethodInternal(TEXT("NewObject"), ((CoreCLRClass*)klass)->GetTypeHandle()); +} + +MONO_API MONO_RT_EXTERNAL_ONLY MonoArray* mono_array_new(MonoDomain* domain, MonoClass* eclass, uintptr_t n) +{ + return (MonoArray*)CoreCLR::CallStaticMethodInternal(TEXT("NewArray"), ((CoreCLRClass*)eclass)->GetTypeHandle(), n); +} + +MONO_API char* mono_array_addr_with_size(MonoArray* array, int size, uintptr_t idx) +{ + return (char*)CoreCLR::CallStaticMethodInternal(TEXT("GetArrayPointerToElement"), array, size, (int)idx); +} + +MONO_API uintptr_t mono_array_length(MonoArray* array) +{ + return CoreCLR::CallStaticMethodInternal(TEXT("GetArrayLength"), array); +} + +MONO_API MonoString* mono_string_empty(MonoDomain* domain) +{ + return (MonoString*)CoreCLR::CallStaticMethodInternal(TEXT("GetStringEmpty")); +} + +MONO_API MONO_RT_EXTERNAL_ONLY MonoString* mono_string_new_utf16(MonoDomain* domain, const mono_unichar2* text, int32_t len) +{ + return (MonoString*)CoreCLR::CallStaticMethodInternal(TEXT("NewStringUTF16"), text, len); +} + +MONO_API MONO_RT_EXTERNAL_ONLY MonoString* mono_string_new(MonoDomain* domain, const char* text) +{ + return (MonoString*)CoreCLR::CallStaticMethodInternal(TEXT("NewString"), text); +} + +MONO_API MONO_RT_EXTERNAL_ONLY MonoString* mono_string_new_len(MonoDomain* domain, const char* text, unsigned int length) +{ + return (MonoString*)CoreCLR::CallStaticMethodInternal(TEXT("NewStringLength"), text, length); +} + +MONO_API MONO_RT_EXTERNAL_ONLY char* mono_string_to_utf8(MonoString* string_obj) +{ + Char* strw = string_obj != nullptr ? (Char*)mono_string_chars(string_obj) : nullptr; + auto len = string_obj != nullptr ? mono_string_length(string_obj) : 0; + ASSERT(len >= 0) + char* stra = (char*)CoreCLR::Allocate(sizeof(char) * (len + 1)); + StringUtils::ConvertUTF162UTF8(strw, stra, len, len); + stra[len] = 0; + + return stra; +} + +MONO_API MONO_RT_EXTERNAL_ONLY MonoString* mono_object_to_string(MonoObject* obj, MonoObject** exc) +{ + ASSERT(false); +} + +MONO_API int mono_object_hash(MonoObject* obj) +{ + ASSERT(false); +} + +MONO_API MONO_RT_EXTERNAL_ONLY MonoObject* mono_value_box(MonoDomain* domain, MonoClass* klass, void* val) +{ + return (MonoObject*)CoreCLR::CallStaticMethodInternal(TEXT("BoxValue"), ((CoreCLRClass*)klass)->GetTypeHandle(), val); +} + +MONO_API void mono_value_copy(void* dest, /*const*/ void* src, MonoClass* klass) +{ + CoreCLRClass* mci = (CoreCLRClass*)klass; + Platform::MemoryCopy(dest, src, mci->GetSize()); +} + +MONO_API MONO_RT_EXTERNAL_ONLY MonoClass* mono_object_get_class(MonoObject* obj) +{ + void* classHandle = CoreCLR::CallStaticMethodInternal(TEXT("GetObjectType"), obj); + + CoreCLRClass* mi = GetOrCreateClass((void*)classHandle); + + ASSERT(mi != nullptr) + return (MonoClass*)mi; +} + +MONO_API void* mono_object_unbox(MonoObject* obj) +{ + return CoreCLR::CallStaticMethodInternal(TEXT("UnboxValue"), obj); +} + +MONO_API MONO_RT_EXTERNAL_ONLY void mono_raise_exception(MonoException* ex) +{ + CoreCLR::CallStaticMethodInternal(TEXT("RaiseException"), ex); +} + +MONO_API MONO_RT_EXTERNAL_ONLY void mono_runtime_object_init(MonoObject* this_obj) +{ + CoreCLR::CallStaticMethodInternal(TEXT("ObjectInit"), this_obj); +} + +MONO_API MonoMethod* mono_object_get_virtual_method(MonoObject* obj, MonoMethod* method) +{ + return method; +} + +MONO_API MONO_RT_EXTERNAL_ONLY MonoObject* mono_runtime_invoke(MonoMethod* method, void* obj, void** params, MonoObject** exc) +{ + CoreCLRMethod* mi = (CoreCLRMethod*)method; + void* methodPtr = mi->GetMethodHandle(); + ASSERT(methodPtr != nullptr) + + static void* InvokeMethodPtr = CoreCLR::GetStaticMethodPointer(TEXT("InvokeMethod")); + return (MonoObject*)CoreCLR::CallStaticMethodInternalPointer(InvokeMethodPtr, obj, methodPtr, params, exc); +} + +MONO_API MONO_RT_EXTERNAL_ONLY void* mono_method_get_unmanaged_thunk(MonoMethod* method) +{ + CoreCLRMethod* mi = (CoreCLRMethod*)method; + void* methodPtr = mi->GetMethodHandle(); + ASSERT(methodPtr != nullptr) + + return CoreCLR::CallStaticMethodInternal(TEXT("GetMethodUnmanagedFunctionPointer"), methodPtr); +} + +MONO_API void mono_field_set_value(MonoObject* obj, MonoClassField* field, void* value) +{ + CoreCLR::CallStaticMethodInternal(TEXT("FieldSetValue"), obj, ((CoreCLRField*)field)->GetHandle(), value); +} + +MONO_API void mono_field_get_value(MonoObject* obj, MonoClassField* field, void* value) +{ + CoreCLR::CallStaticMethodInternal(TEXT("FieldGetValue"), obj, ((CoreCLRField*)field)->GetHandle(), value); +} + +MONO_API MONO_RT_EXTERNAL_ONLY MonoObject* mono_field_get_value_object(MonoDomain* domain, MonoClassField* field, MonoObject* obj) +{ + ASSERT(false); +} + +MONO_API MONO_RT_EXTERNAL_ONLY void mono_property_set_value(MonoProperty* prop, void* obj, void** params, MonoObject** exc) +{ + ASSERT(false); +} + +MONO_API MONO_RT_EXTERNAL_ONLY MonoObject* mono_property_get_value(MonoProperty* prop, void* obj, void** params, MonoObject** exc) +{ + ASSERT(false); +} + +MONO_API void mono_gc_wbarrier_set_field(MonoObject* obj, void* field_ptr, MonoObject* value) +{ + ASSERT(false); +} +MONO_API void mono_gc_wbarrier_set_arrayref(MonoArray* arr, void* slot_ptr, MonoObject* value) +{ + CoreCLR::CallStaticMethodInternal(TEXT("SetArrayValueReference"), arr, slot_ptr, value); +} +MONO_API void mono_gc_wbarrier_generic_store(void* ptr, MonoObject* value) +{ + // Ignored + *((void**)ptr) = value; +} +MONO_API void mono_gc_wbarrier_value_copy(void* dest, /*const*/ void* src, int count, MonoClass* klass) +{ + // Ignored + int size = ((CoreCLRClass*)klass)->GetSize(); + memcpy(dest, src, count * size); +} + +/* + * appdomain.h +*/ + +MonoDomain* currentDomain = nullptr; + +MONO_API MonoDomain* mono_domain_get(void) +{ + return currentDomain; +} + +MONO_API mono_bool mono_domain_set(MonoDomain* domain, mono_bool force) +{ + currentDomain = domain; + return true; +} + +MONO_API MonoAssembly* mono_domain_assembly_open(MonoDomain* domain, const char* path) +{ + const char* name; + const char* fullname; + void* assemblyHandle = CoreCLR::CallStaticMethodInternal(TEXT("LoadAssemblyFromPath"), path, &name, &fullname); + CoreCLRAssembly* assembly = New(assemblyHandle, name, fullname); + + CoreCLR::Free((void*)name); + CoreCLR::Free((void*)fullname); + + return (MonoAssembly*)assembly; +} + +static CoreCLRAssembly* corlibimage = nullptr; + +MONO_API MonoImage* mono_get_corlib(void) +{ + if (corlibimage == nullptr) + { + const char* name; + const char* fullname; + void* assemblyHandle = CoreCLR::CallStaticMethodInternal(TEXT("GetAssemblyByName"), "System.Private.CoreLib", &name, &fullname); + corlibimage = New(assemblyHandle, name, fullname); + + CoreCLR::Free((void*)name); + CoreCLR::Free((void*)fullname); + } + + return (MonoImage*)corlibimage; +} + +#define CACHE_CLASS_BY_NAME(name) \ +nullptr; \ +if (klass == nullptr) \ + for (CoreCLRClass* k : corlibimage->GetClasses()) \ + if (k->GetFullname() == name) \ + { \ + klass = k; \ + break; \ + } + +MONO_API MonoClass* mono_get_object_class(void) +{ + static CoreCLRClass* klass = CACHE_CLASS_BY_NAME("System.Object"); + return (MonoClass*)klass; +} + +MONO_API MonoClass* mono_get_byte_class(void) +{ + static CoreCLRClass* klass = CACHE_CLASS_BY_NAME("System.Byte"); + return (MonoClass*)klass; +} + +MONO_API MonoClass* mono_get_void_class(void) +{ + static CoreCLRClass* klass = CACHE_CLASS_BY_NAME("System.Void"); + return (MonoClass*)klass; +} + +MONO_API MonoClass* mono_get_boolean_class(void) +{ + static CoreCLRClass* klass = CACHE_CLASS_BY_NAME("System.Boolean"); + return (MonoClass*)klass; +} + +MONO_API MonoClass* mono_get_sbyte_class(void) +{ + static CoreCLRClass* klass = CACHE_CLASS_BY_NAME("System.SByte"); + return (MonoClass*)klass; +} + +MONO_API MonoClass* mono_get_int16_class(void) +{ + static CoreCLRClass* klass = CACHE_CLASS_BY_NAME("System.Int16"); + return (MonoClass*)klass; +} + +MONO_API MonoClass* mono_get_uint16_class(void) +{ + static CoreCLRClass* klass = CACHE_CLASS_BY_NAME("System.UInt16"); + return (MonoClass*)klass; +} + +MONO_API MonoClass* mono_get_int32_class(void) +{ + static CoreCLRClass* klass = CACHE_CLASS_BY_NAME("System.Int32"); + return (MonoClass*)klass; +} + +MONO_API MonoClass* mono_get_uint32_class(void) +{ + static CoreCLRClass* klass = CACHE_CLASS_BY_NAME("System.UInt32"); + return (MonoClass*)klass; +} + +MONO_API MonoClass* mono_get_intptr_class(void) +{ + static CoreCLRClass* klass = CACHE_CLASS_BY_NAME("System.IntPtr"); + return (MonoClass*)klass; +} + +MONO_API MonoClass* mono_get_uintptr_class(void) +{ + static CoreCLRClass* klass = CACHE_CLASS_BY_NAME("System.UIntPtr"); + return (MonoClass*)klass; +} + +MONO_API MonoClass* mono_get_int64_class(void) +{ + static CoreCLRClass* klass = CACHE_CLASS_BY_NAME("System.Int64"); + return (MonoClass*)klass; +} + +MONO_API MonoClass* mono_get_uint64_class(void) +{ + static CoreCLRClass* klass = CACHE_CLASS_BY_NAME("System.UInt64"); + return (MonoClass*)klass; +} + +MONO_API MonoClass* mono_get_single_class(void) +{ + static CoreCLRClass* klass = CACHE_CLASS_BY_NAME("System.Single"); + return (MonoClass*)klass; +} + +MONO_API MonoClass* mono_get_double_class(void) +{ + static CoreCLRClass* klass = CACHE_CLASS_BY_NAME("System.Double"); + return (MonoClass*)klass; +} + +MONO_API MonoClass* mono_get_char_class(void) +{ + static CoreCLRClass* klass = CACHE_CLASS_BY_NAME("System.Char"); + return (MonoClass*)klass; +} + +MONO_API MonoClass* mono_get_string_class(void) +{ + static CoreCLRClass* klass = CACHE_CLASS_BY_NAME("System.String"); + return (MonoClass*)klass; +} + +/* + * jit.h +*/ + +MONO_API char* mono_get_runtime_build_info(void) +{ + return CoreCLR::CallStaticMethodInternal(TEXT("GetRuntimeInformation")); +} + +/* + * assembly.h +*/ + +MONO_API MONO_RT_EXTERNAL_ONLY MonoAssembly* mono_assembly_load_from_full(MonoImage* image, const char* fname, MonoImageOpenStatus* status, mono_bool refonly) +{ + auto assembly = (MonoAssembly*)((CoreCLRAssembly*)image); + *status = MONO_IMAGE_OK; + return assembly; +} + +MONO_API void mono_assembly_close(MonoAssembly* assembly) +{ + CoreCLR::CallStaticMethodInternal(TEXT("CloseAssembly"), ((CoreCLRAssembly*)assembly)->GetHandle()); + + Delete((CoreCLRAssembly*)assembly); +} + +MONO_API MONO_RT_EXTERNAL_ONLY MonoImage* mono_assembly_get_image(MonoAssembly* assembly) +{ + return (MonoImage*)((CoreCLRAssembly*)assembly); +} + +/* + * threads.h +*/ + +static MonoThread* notImplMonoThreadValue = New(); + +MONO_API MonoThread* mono_thread_current(void) +{ + // Ignored + return notImplMonoThreadValue; +} + +MONO_API MonoThread* mono_thread_attach(MonoDomain* domain) +{ + // Ignored + return notImplMonoThreadValue; +} + +MONO_API void mono_thread_exit(void) +{ + // Ignored +} + +/* + * mono-debug.h +*/ + +MONO_API void mono_debug_open_image_from_memory(MonoImage* image, const mono_byte* raw_contents, int size) +{ + // Ignored +} + +/* + * reflection.h +*/ + +MONO_API MONO_RT_EXTERNAL_ONLY MonoReflectionAssembly* mono_assembly_get_object(MonoDomain* domain, MonoAssembly* assembly) +{ + return (MonoReflectionAssembly*)CoreCLR::CallStaticMethodInternal(TEXT("GetAssemblyObject"), ((CoreCLRAssembly*)assembly)->GetFullname().Get()); +} + +MONO_API MONO_RT_EXTERNAL_ONLY MonoReflectionType* mono_type_get_object(MonoDomain* domain, MonoType* type) +{ + return (MonoReflectionType*)type; +} + +MONO_API MONO_RT_EXTERNAL_ONLY MonoArray* mono_custom_attrs_construct(MonoCustomAttrInfo* cinfo) +{ + ASSERT(false); +} + +MONO_API MONO_RT_EXTERNAL_ONLY MonoCustomAttrInfo* mono_custom_attrs_from_method(MonoMethod* method) +{ + ASSERT(false); +} + +MONO_API MONO_RT_EXTERNAL_ONLY MonoCustomAttrInfo* mono_custom_attrs_from_class(MonoClass* klass) +{ + CoreCLRClass* mi = (CoreCLRClass*)klass; + MonoCustomAttrInfo* info = (MonoCustomAttrInfo*)New>(mi->GetCustomAttributes()); + return info; +} + +MONO_API MONO_RT_EXTERNAL_ONLY MonoCustomAttrInfo* mono_custom_attrs_from_property(MonoClass* klass, MonoProperty* property) +{ + ASSERT(false); +} +MONO_API MONO_RT_EXTERNAL_ONLY MonoCustomAttrInfo* mono_custom_attrs_from_event(MonoClass* klass, MonoEvent* event) +{ + ASSERT(false); +} +MONO_API MONO_RT_EXTERNAL_ONLY MonoCustomAttrInfo* mono_custom_attrs_from_field(MonoClass* klass, MonoClassField* field) +{ + ASSERT(false); +} + +MONO_API mono_bool mono_custom_attrs_has_attr(MonoCustomAttrInfo* ainfo, MonoClass* attr_klass) +{ + Array* attribs = (Array*)ainfo; + for (int i = 0; i < attribs->Count(); i++) + { + CoreCLRCustomAttribute* attrib = attribs->At(i); + if (attrib->GetClass() == (CoreCLRClass*)attr_klass) + return true; + } + return false; +} + +MONO_API MONO_RT_EXTERNAL_ONLY MonoObject* mono_custom_attrs_get_attr(MonoCustomAttrInfo* ainfo, MonoClass* attr_klass) +{ + Array* attribs = (Array*)ainfo; + for (int i = 0; i < attribs->Count(); i++) + { + CoreCLRCustomAttribute* attrib = attribs->At(i); + if (attrib->GetClass() == (CoreCLRClass*)attr_klass) + { + return (MonoObject*)(attrib)->GetHandle(); + } + } + return nullptr; +} + +MONO_API void mono_custom_attrs_free(MonoCustomAttrInfo* ainfo) +{ + Array* mcai = (Array*)ainfo; + Delete(mcai); +} + +MONO_API MONO_RT_EXTERNAL_ONLY MonoType* mono_reflection_type_get_type(MonoReflectionType* reftype) +{ + return (MonoType*)reftype; +} + +/* + * class.h +*/ + +MONO_API MONO_RT_EXTERNAL_ONLY MonoClass* mono_class_get(MonoImage* image, uint32 type_token) +{ + int index = type_token - 0x02000000 - 2; //MONO_TOKEN_TYPE_DEF + auto classes = ((CoreCLRAssembly*)image)->GetClasses(); + return (MonoClass*)classes[index]; +} + +MONO_API MONO_RT_EXTERNAL_ONLY MonoClass* mono_class_from_name(MonoImage* image, const char* name_space_, const char* name_) +{ + StringAnsi name_space(name_space_); + StringAnsi name(name_); + + CoreCLRAssembly* mi = (CoreCLRAssembly*)image; + for (auto klass : mi->GetClasses()) + { + if (klass->GetNamespace() == name_space && klass->GetName() == name) + return (MonoClass*)klass; + } + + return nullptr; +} + +MONO_API MONO_RT_EXTERNAL_ONLY MonoMethod* mono_class_inflate_generic_method(MonoMethod* method, MonoGenericContext* context) +{ + ASSERT(false); +} + +MONO_API MONO_RT_EXTERNAL_ONLY MonoClass* mono_array_class_get(MonoClass* element_class, uint32 rank) +{ + ASSERT(false); +} + +MONO_API MONO_RT_EXTERNAL_ONLY MonoClassField* mono_class_get_field_from_name(MonoClass* klass, const char* name) +{ + StringAnsi name2(name); + CoreCLRClass* mi = (CoreCLRClass*)klass; + for (auto field : mi->GetFields()) + { + if (field->GetName() == name2) + return (MonoClassField*)field; + } + return nullptr; +} + +MONO_API MonoProperty* mono_class_get_property_from_name(MonoClass* klass, const char* name) +{ + StringAnsi name2(name); + CoreCLRClass* mi = (CoreCLRClass*)klass; + for (auto prop : mi->GetProperties()) + { + if (prop->GetName() == name2) + return (MonoProperty*)prop; + } + return nullptr; +} + +MONO_API int32_t mono_class_instance_size(MonoClass* klass) +{ + ASSERT(false); +} + +MONO_API int32_t mono_class_value_size(MonoClass* klass, uint32* align) +{ + return CoreCLR::CallStaticMethodInternal(TEXT("NativeSizeOf"), ((CoreCLRClass*)klass)->GetTypeHandle(), align); +} + +MONO_API MonoClass* mono_class_from_mono_type(MonoType* type) +{ + CoreCLRClass* klass = GetOrCreateClass((void*)type); + return (MonoClass*)klass; +} + +MONO_API mono_bool mono_class_is_subclass_of(MonoClass* klass, MonoClass* klassc, mono_bool check_interfaces) +{ + return CoreCLR::CallStaticMethodInternal(TEXT("TypeIsSubclassOf"), ((CoreCLRClass*)klass)->GetTypeHandle(), ((CoreCLRClass*)klassc)->GetTypeHandle(), check_interfaces); +} + +MONO_API char* mono_type_get_name(MonoType* type) +{ + CoreCLRClass* mci = (CoreCLRClass*)mono_type_get_class(type); + return StringAnsi(mci->GetFullname()).Get(); +} + +MONO_API MonoImage* mono_class_get_image(MonoClass* klass) +{ + return (MonoImage*)((CoreCLRClass*)klass)->GetAssembly(); +} + +MONO_API MONO_RT_EXTERNAL_ONLY MonoClass* mono_class_get_element_class(MonoClass* klass) +{ + ASSERT(false); +} + +MONO_API MONO_RT_EXTERNAL_ONLY mono_bool mono_class_is_valuetype(MonoClass* klass) +{ + return (mono_bool)CoreCLR::CallStaticMethodInternal(TEXT("TypeIsValueType"), ((CoreCLRClass*)klass)->GetTypeHandle()); +} + +MONO_API MONO_RT_EXTERNAL_ONLY mono_bool mono_class_is_enum(MonoClass* klass) +{ + return (mono_bool)CoreCLR::CallStaticMethodInternal(TEXT("TypeIsEnum"), ((CoreCLRClass*)klass)->GetTypeHandle()); +} + +MONO_API MONO_RT_EXTERNAL_ONLY MonoClass* mono_class_get_parent(MonoClass* klass) +{ + void* parentHandle = CoreCLR::CallStaticMethodInternal(TEXT("GetClassParent"), ((CoreCLRClass*)klass)->GetTypeHandle()); + return (MonoClass*)classHandles[parentHandle]; +} + +MONO_API MonoClass* mono_class_get_nesting_type(MonoClass* klass) +{ + // Ignored + return nullptr; +} + +MONO_API uint32 mono_class_get_flags(MonoClass* klass) +{ + return ((CoreCLRClass*)klass)->GetAttributes(); +} + +MONO_API MONO_RT_EXTERNAL_ONLY const char* mono_class_get_name(MonoClass* klass) +{ + return ((CoreCLRClass*)klass)->GetName().Get(); +} + +MONO_API MONO_RT_EXTERNAL_ONLY const char* mono_class_get_namespace(MonoClass* klass) +{ + return ((CoreCLRClass*)klass)->GetNamespace().Get(); +} + +MONO_API MonoType* mono_class_get_type(MonoClass* klass) +{ + return (MonoType*)((CoreCLRClass*)klass)->GetTypeHandle(); +} + +MONO_API uint32 mono_class_get_type_token(MonoClass* klass) +{ + return ((CoreCLRClass*)klass)->GetTypeToken(); +} + +MONO_API MONO_RT_EXTERNAL_ONLY MonoClassField* mono_class_get_fields(MonoClass* klass, void** iter) +{ + auto fields = ((CoreCLRClass*)klass)->GetFields(); + uintptr_t index = (uintptr_t)(*iter); + if (index >= 0 && index < fields.Count()) + { + *iter = (void*)(index + 1); + return (MonoClassField*)fields[(int)index]; + } + *iter = nullptr; + return nullptr; +} + +MONO_API MonoMethod* mono_class_get_methods(MonoClass* klass, void** iter) +{ + auto methods = ((CoreCLRClass*)klass)->GetMethods(); + uintptr_t index = (uintptr_t)(*iter); + if (index >= 0 && index < methods.Count()) + { + *iter = (void*)(index + 1); + return (MonoMethod*)methods[(int)index]; + } + *iter = nullptr; + return nullptr; +} + +MONO_API MonoProperty* mono_class_get_properties(MonoClass* klass, void** iter) +{ + auto properties = ((CoreCLRClass*)klass)->GetProperties(); + uintptr_t index = (uintptr_t)(*iter); + if (index >= 0 && index < properties.Count()) + { + *iter = (void*)(index + 1); + return (MonoProperty*)properties[(int)index]; + } + *iter = nullptr; + return nullptr; +} + +MONO_API MonoEvent* mono_class_get_events(MonoClass* klass, void** iter) +{ + ASSERT(false); +} + +MONO_API MonoClass* mono_class_get_interfaces(MonoClass* klass, void** iter) +{ + auto interfaces = ((CoreCLRClass*)klass)->GetInterfaces(); + uintptr_t index = (uintptr_t)(*iter); + if (index >= 0 && index < interfaces.Count()) + { + *iter = (void*)(index + 1); + return (MonoClass*)interfaces[(int)index]; + } + *iter = nullptr; + return nullptr; +} + +MONO_API const char* mono_field_get_name(MonoClassField* field) +{ + return ((CoreCLRField*)field)->GetName().Get(); +} + +MONO_API MonoType* mono_field_get_type(MonoClassField* field) +{ + return (MonoType*)((CoreCLRField*)field)->GetClass()->GetTypeHandle(); +} + +MONO_API MonoClass* mono_field_get_parent(MonoClassField* field) +{ + ASSERT(false); +} + +MONO_API uint32 mono_field_get_flags(MonoClassField* field) +{ + return ((CoreCLRField*)field)->GetAttributes(); +} + +MONO_API uint32 mono_field_get_offset(MonoClassField* field) +{ + ASSERT(false); +} + +MONO_API const char* mono_property_get_name(MonoProperty* prop) +{ + return ((CoreCLRProperty*)prop)->GetName().Get(); +} + +MONO_API MonoMethod* mono_property_get_set_method(MonoProperty* prop) +{ + return (MonoMethod*)((CoreCLRProperty*)prop)->GetSetMethod(); +} + +MONO_API MonoMethod* mono_property_get_get_method(MonoProperty* prop) +{ + return (MonoMethod*)((CoreCLRProperty*)prop)->GetGetMethod(); +} + +MONO_API MonoClass* mono_property_get_parent(MonoProperty* prop) +{ + return (MonoClass*)((CoreCLRProperty*)prop)->GetClass(); +} + +MONO_API const char* mono_event_get_name(MonoEvent* event) +{ + ASSERT(false); +} + +MONO_API MonoMethod* mono_event_get_add_method(MonoEvent* event) +{ + ASSERT(false); +} + +MONO_API MonoMethod* mono_event_get_remove_method(MonoEvent* event) +{ + ASSERT(false); +} + +MONO_API MonoClass* mono_event_get_parent(MonoEvent* event) +{ + ASSERT(false); +} + +MONO_API MONO_RT_EXTERNAL_ONLY MonoMethod* mono_class_get_method_from_name(MonoClass* klass, const char* name, int param_count) +{ + for (CoreCLRMethod* method : ((CoreCLRClass*)klass)->GetMethods()) + { + if (method->GetName() == name && method->GetNumParameters() == param_count) + return (MonoMethod*)method; + } + + return nullptr; +} + +/* + * mono-publib.h +*/ + +MONO_API void mono_free(void* ptr) +{ + if (ptr != nullptr) + CoreCLR::Free(ptr); +} + +/* + * metadata.h +*/ + +MONO_API mono_bool mono_type_is_byref(MonoType* type) +{ + ASSERT(false); +} + +MONO_API int mono_type_get_type(MonoType* type) +{ + return CoreCLR::CallStaticMethodInternal(TEXT("GetTypeMonoTypeEnum"), type); +} + +MONO_API MonoClass* mono_type_get_class(MonoType* type) +{ + return (MonoClass*)classHandles[(void*)type]; +} + +MONO_API mono_bool mono_type_is_struct(MonoType* type) +{ + ASSERT(false); +} + +MONO_API mono_bool mono_type_is_void(MonoType* type) +{ + ASSERT(false); +} + +MONO_API mono_bool mono_type_is_pointer(MonoType* type) +{ + ASSERT(false); +} + +MONO_API mono_bool mono_type_is_reference(MonoType* type) +{ + ASSERT(false); +} + +MONO_API MonoType* mono_signature_get_return_type(MonoMethodSignature* sig) +{ + CoreCLRMethod* mi = (CoreCLRMethod*)sig; + return (MonoType*)mi->GetReturnType(); +} + +MONO_API MonoType* mono_signature_get_params(MonoMethodSignature* sig, void** iter) +{ + auto parameterTypes = ((CoreCLRMethod*)sig)->GetParameterTypes(); + uintptr_t index = (uintptr_t)(*iter); + if (index >= 0 && index < parameterTypes.Count()) + { + *iter = (void*)(index+1); + return (MonoType*)parameterTypes[(int)index]; + } + *iter = nullptr; + return nullptr; +} + +MONO_API uint32 mono_signature_get_param_count(MonoMethodSignature* sig) +{ + CoreCLRMethod* mi = (CoreCLRMethod*)sig; + return mi->GetNumParameters(); +} + +MONO_API mono_bool mono_signature_param_is_out(MonoMethodSignature* sig, int param_num) +{ + CoreCLRMethod* mi = (CoreCLRMethod*)sig; + return CoreCLR::CallStaticMethodInternal(TEXT("GetMethodParameterIsOut"), mi->GetMethodHandle(), param_num); +} + +MONO_API int mono_type_stack_size(MonoType* type, int* alignment) +{ + ASSERT(false); +} + +/* + * exception.h +*/ + +MONO_API MonoException* mono_exception_from_name_msg(MonoImage* image, const char* name_space, const char* name, const char* msg) +{ + ASSERT(false); +} + +MONO_API MonoException* mono_get_exception_null_reference(void) +{ + return (MonoException*)CoreCLR::CallStaticMethodInternal(TEXT("GetNullReferenceException")); +} + +MONO_API MonoException* mono_get_exception_not_supported(const char* msg) +{ + return (MonoException*)CoreCLR::CallStaticMethodInternal(TEXT("GetNotSupportedException")); +} + +MONO_API MONO_RT_EXTERNAL_ONLY MonoException* mono_get_exception_argument_null(const char* arg) +{ + return (MonoException*)CoreCLR::CallStaticMethodInternal(TEXT("GetArgumentNullException")); +} + +MONO_API MonoException* mono_get_exception_argument(const char* arg, const char* msg) +{ + return (MonoException*)CoreCLR::CallStaticMethodInternal(TEXT("GetArgumentException")); +} + +MONO_API MONO_RT_EXTERNAL_ONLY MonoException* mono_get_exception_argument_out_of_range(const char* arg) +{ + return (MonoException*)CoreCLR::CallStaticMethodInternal(TEXT("GetArgumentOutOfRangeException")); +} + +/* + * image.h +*/ + +MONO_API MONO_RT_EXTERNAL_ONLY MonoImage* mono_image_open_from_data_with_name(char* data, uint32 data_len, mono_bool need_copy, MonoImageOpenStatus* status, mono_bool refonly, const char* path) +{ + const char* name; + const char* fullname; + void* assemblyHandle = CoreCLR::CallStaticMethodInternal(TEXT("LoadAssemblyImage"), data, data_len, path, &name, &fullname); + CoreCLRAssembly* assembly = New(assemblyHandle, name, fullname); + + CoreCLR::Free((void*)name); + CoreCLR::Free((void*)fullname); + + *status = MONO_IMAGE_OK; + return (MonoImage*)assembly; +} + +MONO_API void mono_image_close(MonoImage* image) +{ + // Ignored +} +MONO_API const char* mono_image_get_name(MonoImage* image) +{ + return ((CoreCLRAssembly*)image)->GetName().Get(); +} +MONO_API MonoAssembly* mono_image_get_assembly(MonoImage* image) +{ + return (MonoAssembly*)image; +} +MONO_API int mono_image_get_table_rows(MonoImage* image, int table_id) +{ + return ((CoreCLRAssembly*)image)->GetClasses().Count() + 1; +} + +/* + * mono-gc.h +*/ + +MONO_API void mono_gc_collect(int generation) +{ + // Ignored +} + +MONO_API int mono_gc_max_generation(void) +{ + // Ignored + return 0; +} + +MONO_API MonoBoolean mono_gc_pending_finalizers(void) +{ + // Ignored + return false; +} + +MONO_API void mono_gc_finalize_notify(void) +{ + // Ignored +} + +#pragma warning(default : 4297) diff --git a/Source/Engine/Scripting/InternalCalls.h b/Source/Engine/Scripting/InternalCalls.h index 8e7ef9d27..2e8a09aa5 100644 --- a/Source/Engine/Scripting/InternalCalls.h +++ b/Source/Engine/Scripting/InternalCalls.h @@ -52,7 +52,9 @@ extern "C" FLAXENGINE_API void mono_add_internal_call(const char* name, const vo #else -#define ADD_INTERNAL_CALL(fullName, method) +extern void DotNetAddInternalCall(const wchar_t* fullName, void* function); + +#define ADD_INTERNAL_CALL(fullName, method) DotNetAddInternalCall(TEXT(fullName), (void*)(method)) #define INTERNAL_CALL_CHECK(obj) #define INTERNAL_CALL_CHECK_EXP(expression) #define INTERNAL_CALL_CHECK_RETURN(obj, defaultValue) diff --git a/Source/Engine/Scripting/InternalCalls/EngineInternalCalls.cpp b/Source/Engine/Scripting/InternalCalls/EngineInternalCalls.cpp index d1657d9f3..3e636a65d 100644 --- a/Source/Engine/Scripting/InternalCalls/EngineInternalCalls.cpp +++ b/Source/Engine/Scripting/InternalCalls/EngineInternalCalls.cpp @@ -6,17 +6,20 @@ #include "Engine/Scripting/MException.h" #include "Engine/Scripting/ManagedCLR/MUtils.h" -#if USE_MONO - namespace UtilsInternal { MonoObject* ExtractArrayFromList(MonoObject* obj) { +#if USE_MONO auto klass = mono_object_get_class(obj); auto field = mono_class_get_field_from_name(klass, "_items"); MonoObject* o; mono_field_get_value(obj, field, &o); return o; +#else + SCRIPTING_EXPORT("FlaxEngine.Utils::Internal_ExtractArrayFromList") + return nullptr; +#endif } } @@ -24,6 +27,7 @@ namespace DebugLogHandlerInternal { void LogWrite(LogType level, MonoString* msgObj) { + SCRIPTING_EXPORT("FlaxEngine.DebugLogHandler::Internal_LogWrite") StringView msg; MUtils::ToString(msgObj, msg); Log::Logger::Write(level, msg); @@ -31,6 +35,8 @@ namespace DebugLogHandlerInternal void Log(LogType level, MonoString* msgObj, ScriptingObject* obj, MonoString* stackTrace) { + SCRIPTING_EXPORT("FlaxEngine.DebugLogHandler::Internal_Log") + if (msgObj == nullptr) return; @@ -45,8 +51,11 @@ namespace DebugLogHandlerInternal Log::Logger::Write(level, msg); } + void LogException(MonoException* exception, ScriptingObject* obj) { + SCRIPTING_EXPORT("FlaxEngine.DebugLogHandler::Internal_LogException") +#if USE_MONO if (exception == nullptr) return; @@ -57,13 +66,16 @@ namespace DebugLogHandlerInternal // Print exception including inner exceptions // TODO: maybe option for build to threat warnings and errors as fatal errors? ex.Log(LogType::Warning, objName.GetText()); +#endif } + } namespace FlaxLogWriterInternal { void WriteStringToLog(MonoString* msgObj) { + SCRIPTING_EXPORT("FlaxEngine.FlaxLogWriter::Internal_WriteStringToLog") if (msgObj == nullptr) return; StringView msg; @@ -72,8 +84,6 @@ namespace FlaxLogWriterInternal } } -#endif - void registerFlaxEngineInternalCalls() { AnimGraphExecutor::initRuntime(); diff --git a/Source/Engine/Scripting/InternalCalls/ManagedDictionary.h b/Source/Engine/Scripting/InternalCalls/ManagedDictionary.h index 1d123e067..05548667b 100644 --- a/Source/Engine/Scripting/InternalCalls/ManagedDictionary.h +++ b/Source/Engine/Scripting/InternalCalls/ManagedDictionary.h @@ -86,7 +86,11 @@ struct FLAXENGINE_API ManagedDictionary CHECK_RETURN(makeGenericMethod, nullptr); auto genericType = MUtils::GetType(StdTypesContainer::Instance()->DictionaryClass->GetNative()); +#if USE_NETCORE + auto genericArgs = mono_array_new(domain, mono_get_intptr_class(), 2); +#else auto genericArgs = mono_array_new(domain, mono_get_object_class(), 2); +#endif mono_array_set(genericArgs, MonoReflectionType*, 0, mono_type_get_object(domain, keyType)); mono_array_set(genericArgs, MonoReflectionType*, 1, mono_type_get_object(domain, valueType)); diff --git a/Source/Engine/Scripting/ManagedCLR/MCore.cpp b/Source/Engine/Scripting/ManagedCLR/MCore.cpp index f7b46e5cc..cc0aca74f 100644 --- a/Source/Engine/Scripting/ManagedCLR/MCore.cpp +++ b/Source/Engine/Scripting/ManagedCLR/MCore.cpp @@ -12,6 +12,10 @@ #include "Engine/Platform/Thread.h" #include "Engine/Scripting/MException.h" #include "Engine/Profiler/ProfilerCPU.h" +#include "Engine/Platform/FileSystem.h" +#if USE_NETCORE +#include "Engine/Scripting/DotNet/CoreCLR.h" +#endif #if USE_MONO #ifdef USE_MONO_AOT_MODULE #include "Engine/Core/Types/TimeSpan.h" @@ -52,6 +56,9 @@ MDomain* MCore::GetActiveDomain() MDomain* MCore::CreateDomain(const MString& domainName) { +#if USE_NETCORE + return nullptr; +#else #if USE_MONO_AOT LOG(Fatal, "Scripts can run only in single domain mode with AOT mode enabled."); return nullptr; @@ -74,10 +81,13 @@ MDomain* MCore::CreateDomain(const MString& domainName) #endif MDomains.Add(domain); return domain; +#endif } void MCore::UnloadDomain(const MString& domainName) { +#if USE_NETCORE +#else int32 i = 0; for (; i < MDomains.Count(); i++) { @@ -103,9 +113,48 @@ void MCore::UnloadDomain(const MString& domainName) #endif Delete(domain); MDomains.RemoveAtKeepOrder(i); +#endif } -#if USE_MONO +#if USE_NETCORE +bool MCore::LoadEngine() +{ + const String csharpLibraryPath = Globals::BinariesFolder / TEXT("FlaxEngine.CSharp.dll"); + const String csharpRuntimeConfigPath = Globals::BinariesFolder / TEXT("FlaxEngine.CSharp.runtimeconfig.json"); + if (!FileSystem::FileExists(csharpLibraryPath)) + LOG(Fatal, "Failed to initialize managed runtime, FlaxEngine.CSharp.dll is missing."); + if (!FileSystem::FileExists(csharpRuntimeConfigPath)) + LOG(Fatal, "Failed to initialize managed runtime, FlaxEngine.CSharp.runtimeconfig.json is missing."); + + // Locate hostfxr and load it + if (!CoreCLR::LoadHostfxr(csharpLibraryPath)) + return false; + + // Initialize hosting component + if (!CoreCLR::InitHostfxr(csharpRuntimeConfigPath, csharpLibraryPath)) + return false; + + // Prepare managed side + const String hostExecutable = Platform::GetExecutableFilePath(); + CoreCLR::CallStaticMethodInternal(TEXT("Init"), hostExecutable.Get()); + + MRootDomain = New("Root"); + MDomains.Add(MRootDomain); + + char* buildInfo = mono_get_runtime_build_info(); + LOG(Info, ".NET runtime version: {0}", String(buildInfo)); + mono_free(buildInfo); + + return false; +} + +void MCore::UnloadEngine() +{ + CoreCLR::CallStaticMethodInternal(TEXT("Exit")); + MDomains.ClearDelete(); + MRootDomain = nullptr; +} +#elif USE_MONO #if 0 @@ -518,7 +567,7 @@ bool MCore::LoadEngine() } #endif - // Init Mono + // Init managed runtime #if PLATFORM_ANDROID const char* monoVersion = "mobile"; #else @@ -530,6 +579,7 @@ bool MCore::LoadEngine() MRootDomain->_monoDomain = monoRootDomain; MDomains.Add(MRootDomain); +#if !USE_NETCORE auto exePath = Platform::GetExecutableFilePath(); auto configDir = StringUtils::GetDirectoryName(exePath).ToStringAnsi(); auto configFilename = StringUtils::GetFileName(exePath).ToStringAnsi() + ".config"; @@ -542,10 +592,11 @@ bool MCore::LoadEngine() #endif mono_domain_set_config(monoRootDomain, configDir.Get(), configFilename.Get()); mono_thread_set_main(mono_thread_current()); +#endif // Info char* buildInfo = mono_get_runtime_build_info(); - LOG(Info, "Mono version: {0}", String(buildInfo)); + LOG(Info, "Mono runtime version: {0}", String(buildInfo)); mono_free(buildInfo); return false; @@ -672,7 +723,7 @@ void MCore::GC::WaitForPendingFinalizers() #endif } -#if USE_MONO && PLATFORM_WIN32 && !USE_MONO_DYNAMIC_LIB +#if USE_MONO && PLATFORM_WIN32 && !USE_MONO_DYNAMIC_LIB && !USE_NETCORE // Export Mono functions #pragma comment(linker, "/export:mono_add_internal_call") diff --git a/Source/Engine/Scripting/ManagedCLR/MMethod.h b/Source/Engine/Scripting/ManagedCLR/MMethod.h index 04d774c1e..eda326856 100644 --- a/Source/Engine/Scripting/ManagedCLR/MMethod.h +++ b/Source/Engine/Scripting/ManagedCLR/MMethod.h @@ -84,7 +84,7 @@ public: /// /// /// This is the fastest way of calling managed code. - /// Get thunk from class if you want to call static method. You to call it from method of a instance wrapper to call a specific instance. + /// Get thunk from class if you want to call static method. You need to call it from method of a instance wrapper to call a specific instance. /// /// The method thunk pointer. void* GetThunk(); diff --git a/Source/Engine/Scripting/ManagedCLR/MUtils.cpp b/Source/Engine/Scripting/ManagedCLR/MUtils.cpp index ce54b77ac..c3fc9bb83 100644 --- a/Source/Engine/Scripting/ManagedCLR/MUtils.cpp +++ b/Source/Engine/Scripting/ManagedCLR/MUtils.cpp @@ -24,6 +24,10 @@ #include "Engine/Utilities/StringConverter.h" #include "Engine/Content/Asset.h" +#if USE_NETCORE +#include "Engine/Scripting/DotNet/CoreCLR.h" +#endif + #if USE_MONO // Inlined mono private types to access MonoType internals @@ -356,11 +360,17 @@ Variant MUtils::UnboxVariant(MonoObject* value) return Variant::Null; const auto& stdTypes = *StdTypesContainer::Instance(); const auto klass = mono_object_get_class(value); + + MonoType* monoType = mono_class_get_type(klass); + const MonoTypeEnum monoTypeId = (MonoTypeEnum)mono_type_get_type(monoType); +#if USE_NETCORE + void* unboxed = mono_object_unbox(value); +#else void* unboxed = (byte*)value + sizeof(MonoObject); - const MonoType* monoType = mono_class_get_type(klass); +#endif // Fast type detection for in-built types - switch (monoType->type) + switch (monoTypeId) { case MONO_TYPE_VOID: return Variant(VariantType(VariantType::Void)); @@ -660,7 +670,11 @@ MonoObject* MUtils::BoxVariant(const Variant& value) case VariantType::Guid: return mono_value_box(mono_domain_get(), stdTypes.GuidClass->GetNative(), (void*)&value.AsData); case VariantType::String: +#if USE_NETCORE return (MonoObject*)MUtils::ToString((StringView)value); +#else + return (MonoObject*)MUtils::ToString((StringView)value); +#endif case VariantType::Quaternion: return mono_value_box(mono_domain_get(), stdTypes.QuaternionClass->GetNative(), (void*)&value.AsData); case VariantType::BoundingSphere: @@ -844,7 +858,11 @@ MonoObject* MUtils::BoxVariant(const Variant& value) return nullptr; } case VariantType::ManagedObject: - return value.AsUint ? mono_gchandle_get_target(value.AsUint) : nullptr; +#if USE_NETCORE + return value.AsUint64 ? MUtils::GetGCHandleTarget(value.AsUint64) : nullptr; +#else + return value.AsUint ? MUtils::GetGCHandleTarget(value.AsUint) : nullptr; +#endif case VariantType::Typename: { const auto klass = Scripting::FindClassNative((StringAnsiView)value); @@ -869,6 +887,9 @@ void MUtils::GetClassFullname(MonoObject* obj, MString& fullname) void MUtils::GetClassFullname(MonoClass* monoClass, MString& fullname) { +#if USE_NETCORE + fullname = CoreCLR::GetClassFullname(monoClass); +#else static MString plusStr("+"); static MString dotStr("."); @@ -906,6 +927,7 @@ void MUtils::GetClassFullname(MonoClass* monoClass, MString& fullname) } fullname += ']'; } +#endif } void MUtils::GetClassFullname(MonoReflectionType* type, MString& fullname) @@ -1138,7 +1160,8 @@ BytesContainer MUtils::LinkArray(MonoArray* arrayObj) void* MUtils::VariantToManagedArgPtr(Variant& value, const MType& type, bool& failed) { // Convert Variant into matching managed type and return pointer to data for the method invocation - switch (type.GetNative()->type) + MonoTypeEnum monoType = (MonoTypeEnum)mono_type_get_type(type.GetNative()); + switch (monoType) { case MONO_TYPE_BOOLEAN: if (value.Type.Type != VariantType::Bool) @@ -1183,7 +1206,11 @@ void* MUtils::VariantToManagedArgPtr(Variant& value, const MType& type, bool& fa return MUtils::ToString((StringView)value); case MONO_TYPE_VALUETYPE: { +#if !USE_NETCORE MonoClass* klass = type.GetNative()->data.klass; +#else + MonoClass* klass = mono_type_get_class(type.GetNative()); +#endif if (mono_class_is_enum(klass)) { if (value.Type.Type != VariantType::Enum) @@ -1305,15 +1332,52 @@ void* MUtils::VariantToManagedArgPtr(Variant& value, const MType& type, bool& fa MonoObject* MUtils::ToManaged(const Version& value) { +#if USE_NETCORE + auto scriptingClass = Scripting::GetStaticClass(); + CHECK_RETURN(scriptingClass, nullptr); + auto versionToManaged = scriptingClass->GetMethod("VersionToManaged", 4); + CHECK_RETURN(versionToManaged, nullptr); + + int major = value.Major(); + int minor = value.Minor(); + int build = value.Build(); + int revision = value.Revision(); + + void* params[4]; + params[0] = &major; + params[1] = &minor; + params[2] = &build; + params[3] = &revision; + auto obj = versionToManaged->Invoke(nullptr, params, nullptr); +#else auto obj = mono_object_new(mono_domain_get(), Scripting::FindClassNative("System.Version")); Platform::MemoryCopy((byte*)obj + sizeof(MonoObject), &value, sizeof(Version)); +#endif return obj; } Version MUtils::ToNative(MonoObject* value) { if (value) +#if USE_NETCORE + { + auto ver = Version(); + + auto scriptingClass = Scripting::GetStaticClass(); + CHECK_RETURN(scriptingClass, ver); + auto versionToNative = scriptingClass->GetMethod("VersionToNative", 2); + CHECK_RETURN(versionToNative, ver); + + void* params[2]; + params[0] = value; + params[1] = &ver; + versionToNative->Invoke(nullptr, params, nullptr); + return ver; + } + +#else return *(Version*)((byte*)value + sizeof(MonoObject)); +#endif return Version(); } diff --git a/Source/Engine/Scripting/ManagedCLR/MUtils.h b/Source/Engine/Scripting/ManagedCLR/MUtils.h index c257c4bb2..db2766dbe 100644 --- a/Source/Engine/Scripting/ManagedCLR/MUtils.h +++ b/Source/Engine/Scripting/ManagedCLR/MUtils.h @@ -14,6 +14,11 @@ #include #include +#if USE_NETCORE +#include "Engine/Scripting/DotNet/CoreCLR.h" +#include "Engine/Core/Collections/BitArray.h" +#endif + struct Version; namespace MUtils @@ -53,6 +58,19 @@ struct MConverter void ToNativeArray(Array& result, MonoArray* data, int32 length); }; +#if USE_NETCORE +// Pass-through converter for ScriptingObjects (passed as GCHandles) +template<> +struct MConverter +{ + void Unbox(void*& result, MonoObject* data) + { + CHECK(data); + result = data; + } +}; +#endif + // Converter for POD types (that can use raw memory copy). template struct MConverter, TNot::Type>>>::Value>::Type> @@ -86,12 +104,22 @@ struct MConverter { MonoObject* Box(const String& data, MonoClass* klass) { +#if USE_NETCORE + MonoString* str = MUtils::ToString(data); + return mono_value_box(nullptr, klass, str); +#else return (MonoObject*)MUtils::ToString(data); +#endif } void Unbox(String& result, MonoObject* data) { +#if USE_NETCORE + MonoString* str = (MonoString*)mono_object_unbox(data); + result = MUtils::ToString(str); +#else result = MUtils::ToString((MonoString*)data); +#endif } void ToManagedArray(MonoArray* result, const Span& data) @@ -564,6 +592,72 @@ namespace MUtils return ToArray(Span(data.Get(), data.Count()), mono_get_string_class()); } +#if USE_NETCORE + /// + /// Allocates new boolean array and copies data from the given unmanaged data container. + /// The managed runtime is responsible for releasing the returned array data. + /// + /// The input data. + /// The output array. + FORCE_INLINE bool* ToBoolArray(const Array& data) + { + bool* arr = (bool*)CoreCLR::Allocate(data.Count() * sizeof(bool)); + memcpy(arr, data.Get(), data.Count() * sizeof(bool)); + return arr; + } + + /// + /// Allocates new boolean array and copies data from the given unmanaged data container. + /// The managed runtime is responsible for releasing the returned array data. + /// + /// The input data. + /// The output array. + FORCE_INLINE bool* ToBoolArray(const BitArray<>& data) + { + bool* arr = (bool*)CoreCLR::Allocate(data.Count() * sizeof(bool)); + //memcpy(arr, data.Get(), data.Count() * sizeof(bool)); + for (int i = 0; i < data.Count(); i++) + arr[i] = data[i]; + return arr; + } +#endif + + FORCE_INLINE gchandle NewGCHandle(MonoObject* obj, bool pinned) + { +#if USE_NETCORE + return CoreCLR::NewGCHandle(obj, pinned); +#else + return mono_gchandle_new(obj, pinned); +#endif + } + + FORCE_INLINE gchandle NewGCHandleWeakref(MonoObject* obj, bool track_resurrection) + { +#if USE_NETCORE + return CoreCLR::NewGCHandleWeakref(obj, track_resurrection); +#else + return mono_gchandle_new_weak_ref(obj, track_resurrection); +#endif + } + + FORCE_INLINE MonoObject* GetGCHandleTarget(const gchandle& handle) + { +#if USE_NETCORE + return (MonoObject*)CoreCLR::GetGCHandleTarget(handle); +#else + return mono_gchandle_get_target(handle); +#endif + } + + FORCE_INLINE void FreeGCHandle(const gchandle& handle) + { +#if USE_NETCORE + CoreCLR::FreeGCHandle(handle); +#else + mono_gchandle_free(handle); +#endif + } + extern void* VariantToManagedArgPtr(Variant& value, const MType& type, bool& failed); extern MonoObject* ToManaged(const Version& value); extern Version ToNative(MonoObject* value); diff --git a/Source/Engine/Scripting/Object.cs b/Source/Engine/Scripting/Object.cs index cc4b7e6e5..5847bb78c 100644 --- a/Source/Engine/Scripting/Object.cs +++ b/Source/Engine/Scripting/Object.cs @@ -2,6 +2,8 @@ using System; using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Runtime.InteropServices.Marshalling; // ReSharper disable UnassignedReadonlyField // ReSharper disable InconsistentNaming @@ -13,13 +15,14 @@ namespace FlaxEngine /// Base class for all objects Flax can reference. Every object has unique identifier. /// [Serializable] - public abstract class Object + [NativeMarshalling(typeof(ObjectMarshaller))] + public abstract partial class Object { /// /// The pointer to the unmanaged object (native C++ instance). /// [NonSerialized] - protected readonly IntPtr __unmanagedPtr; + internal readonly IntPtr __unmanagedPtr; /// /// The object unique identifier. @@ -248,35 +251,35 @@ namespace FlaxEngine #region Internal Calls - [MethodImpl(MethodImplOptions.InternalCall)] - internal static extern Object Internal_Create1(Type type); + [LibraryImport("FlaxEngine", EntryPoint = "FlaxEngine.Object::Internal_Create1", StringMarshalling = StringMarshalling.Custom, StringMarshallingCustomType = typeof(FlaxEngine.StringMarshaller))] + internal static partial Object Internal_Create1([MarshalUsing(typeof(SystemTypeMarshaller))] Type type); - [MethodImpl(MethodImplOptions.InternalCall)] - internal static extern Object Internal_Create2(string typeName); + [LibraryImport("FlaxEngine", EntryPoint = "FlaxEngine.Object::Internal_Create2", StringMarshalling = StringMarshalling.Custom, StringMarshallingCustomType = typeof(FlaxEngine.StringMarshaller))] + internal static partial Object Internal_Create2(string typeName); - [MethodImpl(MethodImplOptions.InternalCall)] - internal static extern void Internal_ManagedInstanceCreated(Object managedInstance); + [LibraryImport("FlaxEngine", EntryPoint = "FlaxEngine.Object::Internal_ManagedInstanceCreated", StringMarshalling = StringMarshalling.Custom, StringMarshallingCustomType = typeof(FlaxEngine.StringMarshaller))] + internal static partial void Internal_ManagedInstanceCreated(Object managedInstance); - [MethodImpl(MethodImplOptions.InternalCall)] - internal static extern void Internal_ManagedInstanceDeleted(IntPtr nativeInstance); + [LibraryImport("FlaxEngine", EntryPoint = "FlaxEngine.Object::Internal_ManagedInstanceDeleted", StringMarshalling = StringMarshalling.Custom, StringMarshallingCustomType = typeof(FlaxEngine.StringMarshaller))] + internal static partial void Internal_ManagedInstanceDeleted(IntPtr nativeInstance); - [MethodImpl(MethodImplOptions.InternalCall)] - internal static extern void Internal_Destroy(IntPtr obj, float timeLeft); + [LibraryImport("FlaxEngine", EntryPoint = "FlaxEngine.Object::Internal_Destroy", StringMarshalling = StringMarshalling.Custom, StringMarshallingCustomType = typeof(FlaxEngine.StringMarshaller))] + internal static partial void Internal_Destroy(IntPtr obj, float timeLeft); - [MethodImpl(MethodImplOptions.InternalCall)] - internal static extern string Internal_GetTypeName(IntPtr obj); + [LibraryImport("FlaxEngine", EntryPoint = "FlaxEngine.Object::Internal_GetTypeName", StringMarshalling = StringMarshalling.Custom, StringMarshallingCustomType = typeof(FlaxEngine.StringMarshaller))] + internal static partial string Internal_GetTypeName(IntPtr obj); - [MethodImpl(MethodImplOptions.InternalCall)] - internal static extern Object Internal_FindObject(ref Guid id, Type type); + [LibraryImport("FlaxEngine", EntryPoint = "FlaxEngine.Object::Internal_FindObject", StringMarshalling = StringMarshalling.Custom, StringMarshallingCustomType = typeof(FlaxEngine.StringMarshaller))] + internal static partial Object Internal_FindObject(ref Guid id, [MarshalUsing(typeof(SystemTypeMarshaller))] Type type); - [MethodImpl(MethodImplOptions.InternalCall)] - internal static extern Object Internal_TryFindObject(ref Guid id, Type type); + [LibraryImport("FlaxEngine", EntryPoint = "FlaxEngine.Object::Internal_TryFindObject", StringMarshalling = StringMarshalling.Custom, StringMarshallingCustomType = typeof(FlaxEngine.StringMarshaller))] + internal static partial Object Internal_TryFindObject(ref Guid id, [MarshalUsing(typeof(SystemTypeMarshaller))] Type type); - [MethodImpl(MethodImplOptions.InternalCall)] - internal static extern void Internal_ChangeID(IntPtr obj, ref Guid id); + [LibraryImport("FlaxEngine", EntryPoint = "FlaxEngine.Object::Internal_ChangeID", StringMarshalling = StringMarshalling.Custom, StringMarshallingCustomType = typeof(FlaxEngine.StringMarshaller))] + internal static partial void Internal_ChangeID(IntPtr obj, ref Guid id); - [MethodImpl(MethodImplOptions.InternalCall)] - internal static extern IntPtr Internal_GetUnmanagedInterface(IntPtr obj, Type type); + [LibraryImport("FlaxEngine", EntryPoint = "FlaxEngine.Object::Internal_GetUnmanagedInterface", StringMarshalling = StringMarshalling.Custom, StringMarshallingCustomType = typeof(FlaxEngine.StringMarshaller))] + internal static partial IntPtr Internal_GetUnmanagedInterface(IntPtr obj, [MarshalUsing(typeof(SystemTypeMarshaller))] Type type); #endregion } diff --git a/Source/Engine/Scripting/Scripting.Build.cs b/Source/Engine/Scripting/Scripting.Build.cs index b18a9b1ef..2acb906d7 100644 --- a/Source/Engine/Scripting/Scripting.Build.cs +++ b/Source/Engine/Scripting/Scripting.Build.cs @@ -15,7 +15,10 @@ public class Scripting : EngineModule if (EngineConfiguration.WithCSharp(options)) { - options.PublicDependencies.Add("mono"); + if (EngineConfiguration.UseDotNet) + options.PublicDependencies.Add("nethost"); + else + options.PublicDependencies.Add("mono"); } options.PrivateDependencies.Add("Utilities"); diff --git a/Source/Engine/Scripting/Scripting.Internal.cpp b/Source/Engine/Scripting/Scripting.Internal.cpp index 61f110282..3b5abfd9d 100644 --- a/Source/Engine/Scripting/Scripting.Internal.cpp +++ b/Source/Engine/Scripting/Scripting.Internal.cpp @@ -40,6 +40,7 @@ namespace ProfilerInternal void BeginEvent(MonoString* nameObj) { + SCRIPTING_EXPORT("FlaxEngine.Profiler::BeginEvent") #if COMPILE_WITH_PROFILER const StringView name((const Char*)mono_string_chars(nameObj), mono_string_length(nameObj)); ProfilerCPU::BeginEvent(*name); @@ -78,6 +79,7 @@ namespace ProfilerInternal void EndEvent() { + SCRIPTING_EXPORT("FlaxEngine.Profiler::EndEvent") #if COMPILE_WITH_PROFILER #if TRACY_ENABLE tracy::ScopedZone::End(); @@ -88,6 +90,7 @@ namespace ProfilerInternal void BeginEventGPU(MonoString* nameObj) { + SCRIPTING_EXPORT("FlaxEngine.Profiler::BeginEventGPU") #if COMPILE_WITH_PROFILER const auto index = ProfilerGPU::BeginEvent((const Char*)mono_string_chars(nameObj)); ManagedEventsGPU.Push(index); @@ -96,6 +99,7 @@ namespace ProfilerInternal void EndEventGPU() { + SCRIPTING_EXPORT("FlaxEngine.Profiler::EndEventGPU") #if COMPILE_WITH_PROFILER const auto index = ManagedEventsGPU.Pop(); ProfilerGPU::EndEvent(index); @@ -111,16 +115,19 @@ public: #if USE_MONO static bool HasGameModulesLoaded() { + SCRIPTING_EXPORT("FlaxEngine.Scripting::HasGameModulesLoaded") return Scripting::HasGameModulesLoaded(); } static bool IsTypeFromGameScripts(MonoReflectionType* type) { + SCRIPTING_EXPORT("FlaxEngine.Scripting::IsTypeFromGameScripts") return Scripting::IsTypeFromGameScripts(Scripting::FindClass(MUtils::GetClass(type))); } static void FlushRemovedObjects() { + SCRIPTING_EXPORT("FlaxEngine.Scripting::FlushRemovedObjects") ASSERT(IsInMainThread()); ObjectsRemovalService::Flush(); } diff --git a/Source/Engine/Scripting/Scripting.cpp b/Source/Engine/Scripting/Scripting.cpp index ac7a6f8e8..c44f71823 100644 --- a/Source/Engine/Scripting/Scripting.cpp +++ b/Source/Engine/Scripting/Scripting.cpp @@ -903,6 +903,7 @@ ScriptingObject* Scripting::FindObject(const MObject* managedInstance) void Scripting::OnManagedInstanceDeleted(ScriptingObject* obj) { + SCRIPTING_EXPORT("FlaxEngine.Object::Internal_ManagedInstanceDeleted") PROFILE_CPU(); ASSERT(obj); diff --git a/Source/Engine/Scripting/Scripting.cs b/Source/Engine/Scripting/Scripting.cs index 6eff982d9..4a9276a83 100644 --- a/Source/Engine/Scripting/Scripting.cs +++ b/Source/Engine/Scripting/Scripting.cs @@ -5,6 +5,8 @@ using System.Collections; using System.Collections.Concurrent; using System.Collections.Generic; using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Runtime.InteropServices.Marshalling; using System.Threading; using System.Threading.Tasks; using FlaxEngine.GUI; @@ -65,7 +67,7 @@ namespace FlaxEngine /// /// C# scripting service. /// - public static class Scripting + public static partial class Scripting { private static readonly List UpdateActions = new List(); private static readonly MainThreadTaskScheduler MainThreadTaskScheduler = new MainThreadTaskScheduler(); @@ -207,6 +209,21 @@ namespace FlaxEngine return result; } + internal static IntPtr VersionToManaged(int major, int minor, int build, int revision) + { + Version version = new Version(major, minor, Math.Max(build, 0), Math.Max(revision, 0)); + return GCHandle.ToIntPtr(GCHandle.Alloc(version)); + } + + internal static void VersionToNative(IntPtr versionHandle, IntPtr nativePtr) + { + Version version = (Version)GCHandle.FromIntPtr(versionHandle).Target; + Marshal.WriteInt32(nativePtr, 0, version.Major); + Marshal.WriteInt32(nativePtr, 4, version.Minor); + Marshal.WriteInt32(nativePtr, 8, version.Build); + Marshal.WriteInt32(nativePtr, 12, version.Revision); + } + private static void CreateGuiStyle() { var style = new Style @@ -283,21 +300,23 @@ namespace FlaxEngine /// Returns true if game scripts assembly has been loaded. /// /// True if game scripts assembly is loaded, otherwise false. - [MethodImpl(MethodImplOptions.InternalCall)] - public static extern bool HasGameModulesLoaded(); + [LibraryImport("FlaxEngine", EntryPoint = "FlaxEngine.Scripting::HasGameModulesLoaded", StringMarshalling = StringMarshalling.Custom, StringMarshallingCustomType = typeof(FlaxEngine.StringMarshaller))] + [return: MarshalAs(UnmanagedType.U1)] + public static partial bool HasGameModulesLoaded(); /// /// Returns true if given type is from one of the game scripts assemblies. /// /// True if the type is from game assembly, otherwise false. - [MethodImpl(MethodImplOptions.InternalCall)] - public static extern bool IsTypeFromGameScripts(Type type); + [LibraryImport("FlaxEngine", EntryPoint = "FlaxEngine.Scripting::IsTypeFromGameScripts", StringMarshalling = StringMarshalling.Custom, StringMarshallingCustomType = typeof(FlaxEngine.StringMarshaller))] + [return: MarshalAs(UnmanagedType.U1)] + public static partial bool IsTypeFromGameScripts([MarshalUsing(typeof(SystemTypeMarshaller))] Type type); /// /// Flushes the removed objects (disposed objects using Object.Destroy). /// - [MethodImpl(MethodImplOptions.InternalCall)] - public static extern void FlushRemovedObjects(); + [LibraryImport("FlaxEngine", EntryPoint = "FlaxEngine.Scripting::FlushRemovedObjects", StringMarshalling = StringMarshalling.Custom, StringMarshallingCustomType = typeof(FlaxEngine.StringMarshaller))] + public static partial void FlushRemovedObjects(); } /// @@ -306,32 +325,32 @@ namespace FlaxEngine /// /// Profiler is available in the editor and Debug/Development builds. Release builds don't have profiling tools. /// - public static class Profiler + public static partial class Profiler { /// /// Begins profiling a piece of code with a custom label. /// /// The name of the event. - [MethodImpl(MethodImplOptions.InternalCall)] - public static extern void BeginEvent(string name); + [LibraryImport("FlaxEngine", EntryPoint = "FlaxEngine.Profiler::BeginEvent", StringMarshalling = StringMarshalling.Custom, StringMarshallingCustomType = typeof(FlaxEngine.StringMarshaller))] + public static partial void BeginEvent(string name); /// /// Ends profiling an event. /// - [MethodImpl(MethodImplOptions.InternalCall)] - public static extern void EndEvent(); + [LibraryImport("FlaxEngine", EntryPoint = "FlaxEngine.Profiler::EndEvent", StringMarshalling = StringMarshalling.Custom, StringMarshallingCustomType = typeof(FlaxEngine.StringMarshaller))] + public static partial void EndEvent(); /// /// Begins GPU profiling a piece of code with a custom label. /// /// The name of the event. - [MethodImpl(MethodImplOptions.InternalCall)] - public static extern void BeginEventGPU(string name); + [LibraryImport("FlaxEngine", EntryPoint = "FlaxEngine.Profiler::BeginEventGPU", StringMarshalling = StringMarshalling.Custom, StringMarshallingCustomType = typeof(FlaxEngine.StringMarshaller))] + public static partial void BeginEventGPU(string name); /// /// Ends GPU profiling an event. /// - [MethodImpl(MethodImplOptions.InternalCall)] - public static extern void EndEventGPU(); + [LibraryImport("FlaxEngine", EntryPoint = "FlaxEngine.Profiler::EndEventGPU", StringMarshalling = StringMarshalling.Custom, StringMarshallingCustomType = typeof(FlaxEngine.StringMarshaller))] + public static partial void EndEventGPU(); } } diff --git a/Source/Engine/Scripting/ScriptingObject.cpp b/Source/Engine/Scripting/ScriptingObject.cpp index ee3dfc438..0dd46fe81 100644 --- a/Source/Engine/Scripting/ScriptingObject.cpp +++ b/Source/Engine/Scripting/ScriptingObject.cpp @@ -68,9 +68,13 @@ ScriptingObject* ScriptingObject::NewObject(const ScriptingTypeHandle& typeHandl MObject* ScriptingObject::GetManagedInstance() const { +#if USE_NETCORE + const gchandle handle = Platform::AtomicRead((int64*)&_gcHandle); +#elif USE_MONO + const gchandle handle = Platform::AtomicRead((int32*)&_gcHandle); +#endif #if USE_MONO - const int32 handle = Platform::AtomicRead((int32*)&_gcHandle); - return handle ? mono_gchandle_get_target(handle) : nullptr; + return handle ? MUtils::GetGCHandleTarget(handle) : nullptr; #else return nullptr; #endif @@ -211,7 +215,7 @@ void ScriptingObject::OnManagedInstanceDeleted() if (_gcHandle) { #if USE_MONO - mono_gchandle_free(_gcHandle); + MUtils::FreeGCHandle(_gcHandle); #endif _gcHandle = 0; } @@ -236,10 +240,16 @@ bool ScriptingObject::CreateManaged() if (!managedInstance) return true; - // Prevent form object GC destruction - auto handle = mono_gchandle_new(managedInstance, false); + // Prevent from object GC destruction +#if USE_NETCORE + auto handle = (gchandle)managedInstance; + auto oldHandle = Platform::InterlockedCompareExchange((int64*)&_gcHandle, *(int64*)&handle, 0); + if (*(uint64*)&oldHandle != 0) +#else + auto handle = MUtils::NewGCHandle(managedInstance, false); auto oldHandle = Platform::InterlockedCompareExchange((int32*)&_gcHandle, *(int32*)&handle, 0); if (*(uint32*)&oldHandle != 0) +#endif { // Other thread already created the object before if (const auto monoClass = GetClass()) @@ -252,7 +262,7 @@ bool ScriptingObject::CreateManaged() monoUnmanagedPtrField->SetValue(managedInstance, ¶m); } } - mono_gchandle_free(handle); + MUtils::FreeGCHandle(handle); return true; } #endif @@ -337,7 +347,7 @@ void ScriptingObject::DestroyManaged() // Clear the handle if (_gcHandle) { - mono_gchandle_free(_gcHandle); + MUtils::FreeGCHandle(_gcHandle); _gcHandle = 0; } #else @@ -448,9 +458,15 @@ bool ManagedScriptingObject::CreateManaged() return true; // Cache the GC handle to the object (used to track the target object because it can be moved in a memory) - auto handle = mono_gchandle_new_weakref(managedInstance, false); +#if USE_NETCORE + auto handle = (gchandle)managedInstance; + auto oldHandle = Platform::InterlockedCompareExchange((int64*)&_gcHandle, *(int64*)&handle, 0); + if (*(uint64*)&oldHandle != 0) +#else + auto handle = MUtils::NewGCHandleWeakref(managedInstance, false); auto oldHandle = Platform::InterlockedCompareExchange((int32*)&_gcHandle, *(int32*)&handle, 0); if (*(uint32*)&oldHandle != 0) +#endif { // Other thread already created the object before if (const auto monoClass = GetClass()) @@ -463,7 +479,7 @@ bool ManagedScriptingObject::CreateManaged() monoUnmanagedPtrField->SetValue(managedInstance, ¶m); } } - mono_gchandle_free(handle); + MUtils::FreeGCHandle(handle); return true; } #endif @@ -487,6 +503,7 @@ public: static MonoObject* Create1(MonoReflectionType* type) { + SCRIPTING_EXPORT("FlaxEngine.Object::Internal_Create1") // Peek class for that type (handle generic class cases) if (!type) DebugLog::ThrowArgumentNull("type"); @@ -550,6 +567,7 @@ public: static MonoObject* Create2(MonoString* typeNameObj) { + SCRIPTING_EXPORT("FlaxEngine.Object::Internal_Create2") // Get typename if (typeNameObj == nullptr) DebugLog::ThrowArgumentNull("typeName"); @@ -587,6 +605,7 @@ public: static void ManagedInstanceCreated(MonoObject* managedInstance) { + SCRIPTING_EXPORT("FlaxEngine.Object::Internal_ManagedInstanceCreated") MonoClass* typeClass = mono_object_get_class(managedInstance); // Get the assembly with that class @@ -625,12 +644,20 @@ public: if (auto* managedScriptingObject = dynamic_cast(obj)) { // Managed - managedScriptingObject->_gcHandle = mono_gchandle_new_weakref(managedInstance, false); +#if USE_NETCORE + managedScriptingObject->_gcHandle = (gchandle)managedInstance; +#else + managedScriptingObject->_gcHandle = MUtils::NewGCHandleWeakref(managedInstance, false); +#endif } else { // Persistent - obj->_gcHandle = mono_gchandle_new(managedInstance, false); +#if USE_NETCORE + obj->_gcHandle = (gchandle)managedInstance; +#else + obj->_gcHandle = MUtils::NewGCHandle(managedInstance, false); +#endif } MClass* monoClass = obj->GetClass(); @@ -657,6 +684,7 @@ public: static void Destroy(ScriptingObject* obj, float timeLeft) { + SCRIPTING_EXPORT("FlaxEngine.Object::Internal_Destroy") // Use scaled game time for removing actors/scripts by the user (maybe expose it to the api?) const bool useGameTime = timeLeft > ZeroTolerance; @@ -666,12 +694,14 @@ public: static MonoString* GetTypeName(ScriptingObject* obj) { + SCRIPTING_EXPORT("FlaxEngine.Object::Internal_GetTypeName") INTERNAL_CALL_CHECK_RETURN(obj, nullptr); return MUtils::ToString(obj->GetType().Fullname); } static MonoObject* FindObject(Guid* id, MonoReflectionType* type) { + SCRIPTING_EXPORT("FlaxEngine.Object::Internal_FindObject") if (!id->IsValid()) return nullptr; auto klass = MUtils::GetClass(type); @@ -701,6 +731,7 @@ public: static MonoObject* TryFindObject(Guid* id, MonoReflectionType* type) { + SCRIPTING_EXPORT("FlaxEngine.Object::Internal_TryFindObject") ScriptingObject* obj = Scripting::TryFindObject(*id); if (obj && !obj->Is(MUtils::GetClass(type))) obj = nullptr; @@ -709,12 +740,14 @@ public: static void ChangeID(ScriptingObject* obj, Guid* id) { + SCRIPTING_EXPORT("FlaxEngine.Object::Internal_ChangeID") INTERNAL_CALL_CHECK(obj); obj->ChangeID(*id); } static void* GetUnmanagedInterface(ScriptingObject* obj, MonoReflectionType* type) { + SCRIPTING_EXPORT("FlaxEngine.Object::Internal_GetUnmanagedInterface") if (obj && type) { auto typeClass = MUtils::GetClass(type); diff --git a/Source/Engine/Scripting/ScriptingObject.h b/Source/Engine/Scripting/ScriptingObject.h index 7db036e17..4b51d83a9 100644 --- a/Source/Engine/Scripting/ScriptingObject.h +++ b/Source/Engine/Scripting/ScriptingObject.h @@ -21,7 +21,7 @@ public: protected: - uint32 _gcHandle; + gchandle _gcHandle; ScriptingTypeHandle _type; Guid _id; diff --git a/Source/Engine/Scripting/Types.h b/Source/Engine/Scripting/Types.h index e88ba9a35..db3a37e4b 100644 --- a/Source/Engine/Scripting/Types.h +++ b/Source/Engine/Scripting/Types.h @@ -31,7 +31,7 @@ typedef void MObject; #else #define USE_MONO 1 -#define USE_NETCORE 0 +#define USE_NETCORE 1 // Enables using single (root) app domain for the user scripts #define USE_SCRIPTING_SINGLE_DOMAIN 1 @@ -49,6 +49,17 @@ typedef void MObject; #define USE_MONO_AOT_MODE MONO_AOT_MODE_NONE #endif +#if USE_NETCORE +struct _MonoDomain {}; +struct _MonoThread {}; +#endif + +#if USE_NETCORE +typedef unsigned long long gchandle; +#else +typedef uint32 gchandle; +#endif + // Mono types declarations typedef struct _MonoClass MonoClass; typedef struct _MonoDomain MonoDomain; diff --git a/Source/Engine/Serialization/JsonConverters.cs b/Source/Engine/Serialization/JsonConverters.cs index 4de09cb5d..c194b766e 100644 --- a/Source/Engine/Serialization/JsonConverters.cs +++ b/Source/Engine/Serialization/JsonConverters.cs @@ -37,7 +37,11 @@ namespace FlaxEngine.Json { // Skip serialization as reference id for the root object serialization (eg. Script) var cache = JsonSerializer.Current.Value; +#if !USE_NETCORE if (cache != null && cache.IsDuringSerialization && cache.SerializerWriter.SerializeStackSize == 0) +#else + if (cache != null && cache.IsDuringSerialization) +#endif { return false; } @@ -142,6 +146,7 @@ namespace FlaxEngine.Json writer.WriteEndObject(); } +#if !USE_NETCORE /// public override void WriteJsonDiff(JsonWriter writer, object value, object other, Newtonsoft.Json.JsonSerializer serializer) { @@ -170,6 +175,7 @@ namespace FlaxEngine.Json } writer.WriteEndObject(); } +#endif /// public override object ReadJson(JsonReader reader, Type objectType, object existingValue, Newtonsoft.Json.JsonSerializer serializer) @@ -232,8 +238,10 @@ namespace FlaxEngine.Json /// public override bool CanWrite => true; +#if !USE_NETCORE /// public override bool CanWriteDiff => true; +#endif } /// diff --git a/Source/Engine/Serialization/JsonSerializer.cs b/Source/Engine/Serialization/JsonSerializer.cs index 29a5951f9..b22f92927 100644 --- a/Source/Engine/Serialization/JsonSerializer.cs +++ b/Source/Engine/Serialization/JsonSerializer.cs @@ -1,6 +1,7 @@ // Copyright (c) 2012-2022 Wojciech Figat. All rights reserved. using System; +using System.Collections.Generic; using System.Globalization; using System.IO; using System.Runtime.CompilerServices; @@ -11,7 +12,7 @@ using FlaxEngine.Json.JsonCustomSerializers; using FlaxEngine.Utilities; using Newtonsoft.Json; using Newtonsoft.Json.Converters; -using Newtonsoft.Json.Serialization; +using Newtonsoft.Json.Linq; namespace FlaxEngine.Json { @@ -23,7 +24,11 @@ namespace FlaxEngine.Json public StringBuilder StringBuilder; public StringWriter StringWriter; public JsonTextWriter JsonWriter; +#if !USE_NETCORE public JsonSerializerInternalWriter SerializerWriter; +#else + public /*JsonSerializerInternalWriter*/ object SerializerWriter; +#endif public UnmanagedMemoryStream MemoryStream; public StreamReader Reader; public bool IsDuringSerialization; @@ -32,9 +37,18 @@ namespace FlaxEngine.Json { JsonSerializer = Newtonsoft.Json.JsonSerializer.CreateDefault(settings); JsonSerializer.Formatting = Formatting.Indented; +#if USE_NETCORE + Type jsonSerializerInternalWriterType = + typeof(Newtonsoft.Json.Serialization.IValueProvider).Assembly.GetType( + "Newtonsoft.Json.Serialization.JsonSerializerInternalWriter"); + System.Reflection.ConstructorInfo ctor = jsonSerializerInternalWriterType.GetConstructors + (System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Public)[0]; + SerializerWriter = ctor.Invoke(new object[] { JsonSerializer }); +#else + SerializerWriter = new JsonSerializerInternalWriter(JsonSerializer); +#endif StringBuilder = new StringBuilder(256); StringWriter = new StringWriter(StringBuilder, CultureInfo.InvariantCulture); - SerializerWriter = new JsonSerializerInternalWriter(JsonSerializer); MemoryStream = new UnmanagedMemoryStream((byte*)0, 0); Reader = new StreamReader(MemoryStream, Encoding.UTF8, false); JsonWriter = new JsonTextWriter(StringWriter) @@ -107,7 +121,72 @@ namespace FlaxEngine.Json return sceneObjA.PrefabObjectID == sceneObjB.PrefabObjectID; }*/ - return Newtonsoft.Json.Utilities.MiscellaneousUtils.DefaultValueEquals(objA, objB); + // Based on Newtonsoft.Json MiscellaneousUtils-class ValueEquals-method + bool DefaultValueEquals(object objA_, object objB_) + { + bool IsInteger(object value) + { + var type = value.GetType(); + return type == typeof(SByte) || + type == typeof(Byte) || + type == typeof(Int16) || + type == typeof(UInt16) || + type == typeof(Int32) || + type == typeof(UInt32) || + type == typeof(Int64) || + type == typeof(SByte) || + type == typeof(UInt64); + } + + if (objA_ == objB_) + { + return true; + } + if (objA_ == null || objB_ == null) + { + return false; + } + + // comparing an Int32 and Int64 both of the same value returns false + // make types the same then compare + if (objA_.GetType() != objB_.GetType()) + { + if (IsInteger(objA_) && IsInteger(objB_)) + { + return Convert.ToDecimal(objA_, CultureInfo.CurrentCulture).Equals(Convert.ToDecimal(objB_, CultureInfo.CurrentCulture)); + } + else if ((objA_ is double || objA_ is float || objA_ is decimal) && (objB_ is double || objB_ is float || objB_ is decimal)) + { + return Mathd.NearEqual(Convert.ToDouble(objA_, CultureInfo.CurrentCulture), Convert.ToDouble(objB_, CultureInfo.CurrentCulture)); + } + else + { + return false; + } + } + + // Diff on collections + if (objA_ is System.Collections.IList aList && objB_ is System.Collections.IList bList) + { + if (aList.Count != bList.Count) + return false; + } + if (objA_ is System.Collections.IEnumerable aEnumerable && objB_ is System.Collections.IEnumerable bEnumerable) + { + var aEnumerator = aEnumerable.GetEnumerator(); + var bEnumerator = bEnumerable.GetEnumerator(); + while (aEnumerator.MoveNext()) + { + if (!bEnumerator.MoveNext() || !ValueEquals(aEnumerator.Current, bEnumerator.Current)) + return false; + } + return !bEnumerator.MoveNext(); + } + + return objA_.Equals(objB_); + } + + return /*Newtonsoft.Json.Utilities.MiscellaneousUtils.*/DefaultValueEquals(objA, objB); } /// @@ -124,7 +203,17 @@ namespace FlaxEngine.Json cache.StringBuilder.Clear(); cache.IsDuringSerialization = true; +#if !USE_NETCORE cache.SerializerWriter.Serialize(cache.JsonWriter, obj, type); +#else + Type jsonSerializerInternalWriterType = + typeof(Newtonsoft.Json.Serialization.IValueProvider).Assembly.GetType( + "Newtonsoft.Json.Serialization.JsonSerializerInternalWriter"); + + System.Reflection.MethodInfo Serialize = jsonSerializerInternalWriterType.GetMethod("Serialize", + System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Public); + Serialize.Invoke(cache.SerializerWriter, new object[] { cache.JsonWriter, obj, type }); +#endif return cache.StringBuilder.ToString(); } @@ -143,7 +232,17 @@ namespace FlaxEngine.Json cache.StringBuilder.Clear(); cache.IsDuringSerialization = true; +#if !USE_NETCORE cache.SerializerWriter.Serialize(cache.JsonWriter, obj, type); +#else + Type jsonSerializerInternalWriterType = + typeof(Newtonsoft.Json.Serialization.IValueProvider).Assembly.GetType( + "Newtonsoft.Json.Serialization.JsonSerializerInternalWriter"); + + System.Reflection.MethodInfo Serialize = jsonSerializerInternalWriterType.GetMethod("Serialize", + System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Public); + Serialize.Invoke(cache.SerializerWriter, new object[] { cache.JsonWriter, obj, type }); +#endif return cache.StringBuilder.ToString(); } @@ -157,15 +256,25 @@ namespace FlaxEngine.Json /// The output json string. public static string SerializeDiff(object obj, object other, bool isManagedOnly = false) { - Type type = obj.GetType(); var cache = isManagedOnly ? CacheManagedOnly.Value : Cache.Value; Current.Value = cache; cache.StringBuilder.Clear(); cache.IsDuringSerialization = true; - cache.SerializerWriter.SerializeDiff(cache.JsonWriter, obj, type, other); - return cache.StringBuilder.ToString(); + JObject jObj = JObject.FromObject(obj, cache.JsonSerializer); + JObject jOther = JObject.FromObject(other, cache.JsonSerializer); + JObject diff = new JObject(); + foreach (KeyValuePair prop in jObj) + { + JProperty otherProp = jOther.Property(prop.Key); + if (JToken.DeepEquals(prop.Value, otherProp.Value)) + continue; + + diff.Add(prop.Key, prop.Value); + } + + return diff.ToString(); } /// diff --git a/Source/Engine/UI/UIControl.cs b/Source/Engine/UI/UIControl.cs index bd1d2e795..9b32774ff 100644 --- a/Source/Engine/UI/UIControl.cs +++ b/Source/Engine/UI/UIControl.cs @@ -6,7 +6,9 @@ using System.IO; using System.Text; using FlaxEngine.GUI; using Newtonsoft.Json; +using Newtonsoft.Json.Linq; using Newtonsoft.Json.Serialization; +using System.Collections.Generic; namespace FlaxEngine { @@ -340,10 +342,22 @@ namespace FlaxEngine jsonWriter.StringEscapeHandling = jsonSerializer.StringEscapeHandling; jsonWriter.Culture = jsonSerializer.Culture; jsonWriter.DateFormatString = jsonSerializer.DateFormatString; - +#if !USE_NETCORE JsonSerializerInternalWriter serializerWriter = new JsonSerializerInternalWriter(jsonSerializer); serializerWriter.Serialize(jsonWriter, _control, type); +#else + Type jsonSerializerInternalWriterType = + typeof(Newtonsoft.Json.Serialization.IValueProvider).Assembly.GetType( + "Newtonsoft.Json.Serialization.JsonSerializerInternalWriter"); + System.Reflection.ConstructorInfo ctor = jsonSerializerInternalWriterType.GetConstructors + (System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Public)[0]; + object serializerWriter = ctor.Invoke(new object[] { jsonSerializer }); + + System.Reflection.MethodInfo Serialize = jsonSerializerInternalWriterType.GetMethod("Serialize", + System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Public); + Serialize.Invoke(serializerWriter, new object[] { jsonWriter, _control, type }); +#endif } controlType = type.FullName; @@ -380,10 +394,25 @@ namespace FlaxEngine jsonWriter.StringEscapeHandling = jsonSerializer.StringEscapeHandling; jsonWriter.Culture = jsonSerializer.Culture; jsonWriter.DateFormatString = jsonSerializer.DateFormatString; - +#if !USE_NETCORE JsonSerializerInternalWriter serializerWriter = new JsonSerializerInternalWriter(jsonSerializer); serializerWriter.SerializeDiff(jsonWriter, _control, type, other._control); +#else + JObject jObj = JObject.FromObject(_control, jsonSerializer); + JObject jOther = JObject.FromObject(other._control, jsonSerializer); + JObject diff = new JObject(); + foreach (KeyValuePair prop in jObj) + { + JProperty otherProp = jOther.Property(prop.Key); + if (JToken.DeepEquals(prop.Value, otherProp.Value)) + continue; + + diff.Add(prop.Key, prop.Value); + } + + diff.WriteTo(jsonWriter); +#endif } controlType = string.Empty; diff --git a/Source/Engine/Utilities/Utils.cs b/Source/Engine/Utilities/Utils.cs index b5213e2c3..60eaad2d1 100644 --- a/Source/Engine/Utilities/Utils.cs +++ b/Source/Engine/Utilities/Utils.cs @@ -6,13 +6,15 @@ using System.IO; using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Runtime.InteropServices.Marshalling; namespace FlaxEngine { /// /// Class with helper functions. /// - public static class Utils + public static partial class Utils { /// /// Copies data from one memory location to another using an unmanaged memory pointers. @@ -35,8 +37,8 @@ namespace FlaxEngine /// The source location. /// The destination location. /// The length (amount of bytes to copy). - [MethodImpl(MethodImplOptions.InternalCall)] - public static extern void MemoryCopy(IntPtr destination, IntPtr source, ulong length); + [LibraryImport("FlaxEngine", EntryPoint = "FlaxEngine.Utils::MemoryCopy")] + public static partial void MemoryCopy(IntPtr destination, IntPtr source, ulong length); /// /// Clears the memory region with zeros. @@ -44,8 +46,8 @@ namespace FlaxEngine /// Uses low-level platform impl. /// Destination memory address /// Size of the memory to clear in bytes - [MethodImpl(MethodImplOptions.InternalCall)] - public static extern void MemoryClear(IntPtr dst, ulong size); + [LibraryImport("FlaxEngine", EntryPoint = "FlaxEngine.Utils::MemoryClear")] + public static partial void MemoryClear(IntPtr dst, ulong size); /// /// Compares two blocks of the memory. @@ -54,8 +56,8 @@ namespace FlaxEngine /// The first buffer address. /// The second buffer address. /// Size of the memory to compare in bytes. - [MethodImpl(MethodImplOptions.InternalCall)] - public static extern int MemoryCompare(IntPtr buf1, IntPtr buf2, ulong size); + [LibraryImport("FlaxEngine", EntryPoint = "FlaxEngine.Utils::MemoryCompare")] + public static partial int MemoryCompare(IntPtr buf1, IntPtr buf2, ulong size); /// /// Rounds the floating point value up to 1 decimal place. @@ -94,7 +96,11 @@ namespace FlaxEngine /// The empty array object. public static T[] GetEmptyArray() { +#if USE_NETCORE + return Array.Empty(); +#else return Enumerable.Empty() as T[]; +#endif } /// @@ -209,10 +215,50 @@ namespace FlaxEngine return result; } + /// + /// Gets the location of the assembly. + /// + /// The assembly. + /// Path in the filesystem + public static string GetAssemblyLocation(Assembly assembly) + { +#if USE_NETCORE + if (!string.IsNullOrEmpty(assembly.Location)) + return assembly.Location; + + if (NativeInterop.AssemblyLocations.TryGetValue(assembly.FullName, out string assemblyLocation)) + return assemblyLocation; + + return null; +#else + return assembly.Location; +#endif + } + +#if USE_MONO internal static T[] ExtractArrayFromList(List list) { return list != null ? (T[])Internal_ExtractArrayFromList(list) : null; } +#else + private class ExtractArrayFromListContext + { + public static FieldInfo? itemsField; + } + internal static T[] ExtractArrayFromList(List list) + { + if (list == null) + return null; + + if (ExtractArrayFromListContext.itemsField == null) + { + Type listType = typeof(List); + ExtractArrayFromListContext.itemsField = listType.GetField("_items", BindingFlags.NonPublic | BindingFlags.Instance); + } + + return (T[])ExtractArrayFromListContext.itemsField.GetValue(list); // boxing is slower; + } +#endif internal static Float2[] ConvertCollection(Vector2[] v) { @@ -295,8 +341,9 @@ namespace FlaxEngine return result; } - [MethodImpl(MethodImplOptions.InternalCall)] - internal static extern Array Internal_ExtractArrayFromList(object list); + [LibraryImport("FlaxEngine", EntryPoint = "FlaxEngine.Utils::Internal_ExtractArrayFromList")] + [return: MarshalUsing(typeof(FlaxEngine.SystemArrayMarshaller))] + internal static partial Array Internal_ExtractArrayFromList([MarshalUsing(typeof(FlaxEngine.GCHandleMarshaller))] object list); /// /// Reads the color from the binary stream. diff --git a/Source/Editor/Utilities/VariantUtils.cs b/Source/Engine/Utilities/VariantUtils.cs similarity index 99% rename from Source/Editor/Utilities/VariantUtils.cs rename to Source/Engine/Utilities/VariantUtils.cs index 5f2c524ac..30dce5990 100644 --- a/Source/Editor/Utilities/VariantUtils.cs +++ b/Source/Engine/Utilities/VariantUtils.cs @@ -4,11 +4,14 @@ using System; using System.Collections; using System.Collections.Generic; using System.IO; +#if FLAX_EDITOR using FlaxEditor.Scripting; +using FlaxEditor.Utilities; +#endif using FlaxEngine; using Newtonsoft.Json; -namespace FlaxEditor.Utilities +namespace FlaxEngine.Utilities { /// /// Editor utilities and helper functions for Variant type. @@ -76,6 +79,7 @@ namespace FlaxEditor.Utilities #endif } +#if FLAX_EDITOR internal static VariantType ToVariantType(this Type type) { VariantType variantType; @@ -107,7 +111,9 @@ namespace FlaxEditor.Utilities variantType = VariantType.Pointer; else if (type == typeof(string)) variantType = VariantType.String; - else if (type == typeof(Type) || type == typeof(ScriptType)) + else if (type == typeof(Type)) + variantType = VariantType.Typename; + else if (type == typeof(ScriptType)) variantType = VariantType.Typename; else if (typeof(Asset).IsAssignableFrom(type)) variantType = VariantType.Asset; @@ -1286,5 +1292,6 @@ namespace FlaxEditor.Utilities stream.WriteEndObject(); } +#endif } } diff --git a/Source/Engine/Visject/VisjectScript.cpp b/Source/Engine/Visject/VisjectScript.cpp new file mode 100644 index 000000000..1ae6cafda --- /dev/null +++ b/Source/Engine/Visject/VisjectScript.cpp @@ -0,0 +1,6 @@ +#include "VisjectScript.h" + +VisjectScript::VisjectScript(const SpawnParams& params) + : Script(params) +{ +} diff --git a/Source/Engine/Visject/VisjectScript.h b/Source/Engine/Visject/VisjectScript.h new file mode 100644 index 000000000..1e5389a77 --- /dev/null +++ b/Source/Engine/Visject/VisjectScript.h @@ -0,0 +1,14 @@ +// Copyright (c) 2012-2022 Wojciech Figat. All rights reserved. + +#pragma once + +#include "Engine/Scripting/Script.h" + +/// +/// +/// +API_CLASS(Namespace = "FlaxEngine.Visject") class FLAXENGINE_API VisjectScript : public Script +{ + API_AUTO_SERIALIZATION(); + DECLARE_SCRIPTING_TYPE(VisjectScript); +}; diff --git a/Source/ThirdParty/nethost/FlaxEngine.CSharp.runtimeconfig.json b/Source/ThirdParty/nethost/FlaxEngine.CSharp.runtimeconfig.json new file mode 100644 index 000000000..9c7f14dd0 --- /dev/null +++ b/Source/ThirdParty/nethost/FlaxEngine.CSharp.runtimeconfig.json @@ -0,0 +1,9 @@ +{ + "runtimeOptions": { + "tfm": "net7.0", + "framework": { + "name": "Microsoft.NETCore.App", + "version": "7.0.0-rc.2.22472.3" + } + } +} \ No newline at end of file diff --git a/Source/ThirdParty/nethost/LICENSE.TXT b/Source/ThirdParty/nethost/LICENSE.TXT new file mode 100644 index 000000000..a616ed188 --- /dev/null +++ b/Source/ThirdParty/nethost/LICENSE.TXT @@ -0,0 +1,23 @@ +The MIT License (MIT) + +Copyright (c) .NET Foundation and Contributors + +All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/Source/ThirdParty/nethost/nethost.Build.cs b/Source/ThirdParty/nethost/nethost.Build.cs new file mode 100644 index 000000000..de8fa2650 --- /dev/null +++ b/Source/ThirdParty/nethost/nethost.Build.cs @@ -0,0 +1,112 @@ +// Copyright (c) 2012-2022 Wojciech Figat. All rights reserved. + +using System.Collections.Generic; +using System.IO; +using System; +using Flax.Build; +using Flax.Build.NativeCpp; +using Flax.Build.Platforms; +using Microsoft.Win32; +using System.Linq; + +/// +/// Module for nethost (.NET runtime host library) +/// +public class nethost : ThirdPartyModule +{ + /// + public override void Init() + { + base.Init(); + + LicenseType = LicenseTypes.MIT; + LicenseFilePath = "LICENSE.TXT"; + + // Merge third-party modules into engine binary + BinaryModuleName = "FlaxEngine"; + } + + /// + private static Version ParseVersion(string version) + { + // Give precedence to final releases over release candidate / beta releases + int rev = 9999; + if (version.Contains("-")) // e.g. 7.0.0-rc.2.22472.3 + { + version = version.Substring(0, version.IndexOf("-")); + rev = 0; + } + Version ver = new Version(version); + return new Version(ver.Major, ver.Minor, ver.Build, rev); + } + + /// + public override void Setup(BuildOptions options) + { + base.Setup(options); + + options.SourceFiles.Clear(); + + string arch = "x64"; //options.Architecture == TargetArchitecture.x64 ? "x64" : "x86"; + + string dotnetVersion; + string appHostRuntimePath; + + // NOTE: nethost is bundled with SDK, not runtime. Should C# scripting have a hard requirement for SDK to be installed? + + if (options.Platform.Target == TargetPlatform.Windows) + { + string os = $"win-{arch}"; + + using RegistryKey baseKey = RegistryKey.OpenBaseKey(RegistryHive.LocalMachine, RegistryView.Registry64); + + using RegistryKey hostKey = baseKey.OpenSubKey(@$"SOFTWARE\dotnet\Setup\InstalledVersions\{arch}\sharedhost"); + string dotnetPath = (string)hostKey.GetValue("Path"); + + using RegistryKey runtimeKey = baseKey.OpenSubKey(@$"SOFTWARE\WOW6432Node\dotnet\Setup\InstalledVersions\{arch}\sharedfx\Microsoft.NETCore.App"); + string[] versions = runtimeKey.GetValueNames(); + + dotnetVersion = versions.OrderByDescending(x => ParseVersion(x)).FirstOrDefault(); + + if (string.IsNullOrEmpty(dotnetPath)) + dotnetPath = Environment.GetEnvironmentVariable("DOTNET_ROOT"); + + if (string.IsNullOrEmpty(dotnetPath) || string.IsNullOrEmpty(dotnetVersion)) + throw new Exception("Failed to find dotnet installation"); + + int majorVersion = int.Parse(dotnetVersion.Substring(0, dotnetVersion.IndexOf("."))); + if (majorVersion < 7) + throw new Exception($"Unsupported dotnet version found, minimum version required is .NET 7 (found {dotnetVersion})"); + + appHostRuntimePath = String.Format("{0}packs\\Microsoft.NETCore.App.Host.{1}\\{2}\\runtimes\\{1}\\native", dotnetPath, os, dotnetVersion); + options.OutputFiles.Add(Path.Combine(appHostRuntimePath, "nethost.lib")); + options.DependencyFiles.Add(Path.Combine(appHostRuntimePath, "nethost.dll")); + options.PublicIncludePaths.Add(appHostRuntimePath); + } + else if (options.Platform.Target == TargetPlatform.Linux) + { + // TODO: Support /etc/dotnet/install_location + string dotnetPath = "/usr/share/dotnet/"; + string os = $"linux-{arch}"; + + string[] versions = Directory.GetDirectories($"{dotnetPath}host/fxr/").Select(x => Path.GetFileName(x)).ToArray(); + + dotnetVersion = versions.OrderByDescending(x => ParseVersion(x)).FirstOrDefault(); + + int majorVersion = int.Parse(dotnetVersion.Substring(0, dotnetVersion.IndexOf("."))); + if (majorVersion < 7) + throw new Exception($"Unsupported dotnet version found, minimum version required is .NET 7 (found {dotnetVersion})"); + + appHostRuntimePath = String.Format("{0}packs/Microsoft.NETCore.App.Host.{1}/{2}/runtimes/{1}/native", dotnetPath, os, dotnetVersion); + options.OutputFiles.Add(Path.Combine(appHostRuntimePath, "libnethost.a")); + options.DependencyFiles.Add(Path.Combine(appHostRuntimePath, "libnethost.so")); + options.PublicIncludePaths.Add(appHostRuntimePath); + } + else + throw new InvalidPlatformException(options.Platform.Target); + + options.PublicIncludePaths.Add(appHostRuntimePath); + options.ScriptingAPI.Defines.Add("USE_NETCORE"); + options.DependencyFiles.Add(Path.Combine(FolderPath, "FlaxEngine.CSharp.runtimeconfig.json")); + } +} diff --git a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Api.cs b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Api.cs index e4f8eb2aa..d42f662a9 100644 --- a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Api.cs +++ b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Api.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Text; using BuildData = Flax.Build.Builder.BuildData; namespace Flax.Build.Bindings @@ -171,7 +172,11 @@ namespace Flax.Build.Bindings // Skip for collections if ((typeInfo.Type == "Array" || typeInfo.Type == "Span" || typeInfo.Type == "DataContainer" || typeInfo.Type == "Dictionary" || typeInfo.Type == "HashSet") && typeInfo.GenericArgs != null) +#if !USE_NETCORE return false; +#else + return true; +#endif // Skip for special types if (typeInfo.GenericArgs == null) @@ -219,6 +224,40 @@ namespace Flax.Build.Bindings return false; } +#if USE_NETCORE + /// + /// Check if structure contains unblittable types that would require custom marshaller for the structure. + /// + public static bool UseCustomMarshalling(BuildData buildData, StructureInfo structureInfo, ApiTypeInfo caller) + { + if (structureInfo.Fields.Any(x => !x.IsStatic && + (x.Type.IsObjectRef || x.Type.Type == "Dictionary" || x.Type.Type == "Version") + && x.Type.Type != "uint8" && x.Type.Type != "byte")) + { + return true; + } + + foreach (var field in structureInfo.Fields) + { + if (field.Type.Type == structureInfo.FullNameNative) + continue; + if (field.IsStatic) + continue; + + if (field.Type.Type == "String") + return true; + + var fieldApiType = FindApiTypeInfo(buildData, field.Type, caller); + if (fieldApiType is StructureInfo fieldStructureInfo && UseCustomMarshalling(buildData, fieldStructureInfo, caller)) + return true; + else if (fieldApiType is ClassInfo) + return true; + } + + return false; + } +#endif + /// /// Finds the API type information. /// diff --git a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.CSharp.cs b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.CSharp.cs index 59a4df55e..77f217ea8 100644 --- a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.CSharp.cs +++ b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.CSharp.cs @@ -278,15 +278,15 @@ namespace Flax.Build.Bindings } // Object reference property - if ((typeInfo.Type == "ScriptingObjectReference" || - typeInfo.Type == "AssetReference" || - typeInfo.Type == "WeakAssetReference" || - typeInfo.Type == "SoftAssetReference" || - typeInfo.Type == "SoftObjectReference") && typeInfo.GenericArgs != null) + if (typeInfo.IsObjectRef) return GenerateCSharpNativeToManaged(buildData, typeInfo.GenericArgs[0], caller); // Array or Span or DataContainer +#if USE_NETCORE + if ((typeInfo.Type == "Array" || typeInfo.Type == "Span" || typeInfo.Type == "DataContainer" || typeInfo.Type == "MonoArray") && typeInfo.GenericArgs != null) +#else if ((typeInfo.Type == "Array" || typeInfo.Type == "Span" || typeInfo.Type == "DataContainer") && typeInfo.GenericArgs != null) +#endif return GenerateCSharpNativeToManaged(buildData, typeInfo.GenericArgs[0], caller) + "[]"; // Dictionary @@ -361,11 +361,7 @@ namespace Flax.Build.Bindings } // Object reference property - if ((typeInfo.Type == "ScriptingObjectReference" || - typeInfo.Type == "AssetReference" || - typeInfo.Type == "WeakAssetReference" || - typeInfo.Type == "SoftAssetReference" || - typeInfo.Type == "SoftObjectReference") && typeInfo.GenericArgs != null) + if (typeInfo.IsObjectRef) return "IntPtr"; // Function @@ -419,11 +415,7 @@ namespace Flax.Build.Bindings } // Object reference property - if ((typeInfo.Type == "ScriptingObjectReference" || - typeInfo.Type == "AssetReference" || - typeInfo.Type == "WeakAssetReference" || - typeInfo.Type == "SoftAssetReference" || - typeInfo.Type == "SoftObjectReference") && typeInfo.GenericArgs != null) + if (typeInfo.IsObjectRef) return "FlaxEngine.Object.GetUnmanagedPtr({0})"; // Default @@ -443,8 +435,38 @@ namespace Flax.Build.Bindings returnValueType = GenerateCSharpNativeToManaged(buildData, functionInfo.ReturnType, caller); } +#if USE_NETCORE + string returnMarshalType = ""; + if (returnValueType == "bool") + returnMarshalType = "MarshalAs(UnmanagedType.U1)"; + else if (returnValueType == "System.Type") + returnMarshalType = "MarshalUsing(typeof(FlaxEngine.SystemTypeMarshaller))"; + else if (returnValueType == "CultureInfo") + returnMarshalType = "MarshalUsing(typeof(FlaxEngine.CultureInfoMarshaller))"; + else if (functionInfo.ReturnType.Type == "Variant") + returnMarshalType = "MarshalUsing(typeof(FlaxEngine.GCHandleMarshaller))"; + else if (FindApiTypeInfo(buildData, functionInfo.ReturnType, caller)?.IsInterface ?? false) + { + // Interfaces are not supported by NativeMarshallingAttribute, marshal the parameter + returnMarshalType = $"MarshalUsing(typeof({returnValueType}Marshaller))"; + } + else if (returnValueType == "byte[]") + returnMarshalType = $"MarshalUsing(typeof(FlaxEngine.ArrayMarshaller<,>), CountElementName = \"returnCount\")"; + else if (returnValueType == "bool[]") + { + // Boolean arrays does not support custom marshalling for some unkown reason... + returnMarshalType = $"MarshalAs(UnmanagedType.LPArray, ArraySubType = UnmanagedType.U1, SizeParamIndex = {functionInfo.Parameters.Count + (functionInfo.Glue.CustomParameters?.Count ?? 0)})"; + } +#endif +#if !USE_NETCORE contents.AppendLine().Append(indent).Append("[MethodImpl(MethodImplOptions.InternalCall)]"); - contents.AppendLine().Append(indent).Append("internal static extern "); + contents.AppendLine().Append(indent).Append("internal static partial "); +#else + contents.AppendLine().Append(indent).Append($"[LibraryImport(\"FlaxEngine\", EntryPoint = \"{caller.FullNameManaged}::Internal_{functionInfo.UniqueName}\", StringMarshalling = StringMarshalling.Custom, StringMarshallingCustomType = typeof(FlaxEngine.StringMarshaller))]"); + if (!string.IsNullOrEmpty(returnMarshalType)) + contents.AppendLine().Append(indent).Append($"[return: {returnMarshalType}]"); + contents.AppendLine().Append(indent).Append("internal static partial "); +#endif contents.Append(returnValueType).Append(" Internal_").Append(functionInfo.UniqueName).Append('('); var separator = false; @@ -461,6 +483,30 @@ namespace Flax.Build.Bindings separator = true; var nativeType = GenerateCSharpManagedToNativeType(buildData, parameterInfo.Type, caller); +#if USE_NETCORE + string parameterMarshalType = ""; + if (nativeType == "System.Type") + parameterMarshalType = "MarshalUsing(typeof(FlaxEngine.SystemTypeMarshaller))"; + else if (parameterInfo.Type.Type == "CultureInfo") + parameterMarshalType = "MarshalUsing(typeof(FlaxEngine.CultureInfoMarshaller))"; + else if (parameterInfo.Type.Type == "Variant") // object + parameterMarshalType = "MarshalUsing(typeof(FlaxEngine.GCHandleMarshaller))"; + else if (parameterInfo.Type.Type == "MonoArray") + parameterMarshalType = "MarshalUsing(typeof(FlaxEngine.SystemArrayMarshaller))"; + else if (parameterInfo.Type.Type == "Array" && parameterInfo.Type.GenericArgs.Count > 0 && parameterInfo.Type.GenericArgs[0].Type == "bool") + parameterMarshalType = $"MarshalAs(UnmanagedType.LPArray, ArraySubType = UnmanagedType.U1, SizeParamIndex = {(!functionInfo.IsStatic ? 1 : 0) + functionInfo.Parameters.Count + (functionInfo.Glue.CustomParameters.FindIndex(x => x.Name == parameterInfo.Name + "Count"))})"; + else if (parameterInfo.Type.Type == "Array" || parameterInfo.Type.Type == "Span" || parameterInfo.Type.Type == "DataContainer" || parameterInfo.Type.Type == "BytesContainer" || nativeType == "Array") + parameterMarshalType = $"MarshalUsing(typeof(FlaxEngine.ArrayMarshaller<,>), CountElementName = \"{parameterInfo.Name}Count\")"; + else if (parameterInfo.Type.Type == "Dictionary") + parameterMarshalType = $"MarshalUsing(typeof(FlaxEngine.DictionaryMarshaller<,>), ConstantElementCount = 0)"; + else if (nativeType == "bool") + parameterMarshalType = "MarshalAs(UnmanagedType.U1)"; + else if (nativeType == "char") + parameterMarshalType = "MarshalAs(UnmanagedType.I2)"; + + if (!string.IsNullOrEmpty(parameterMarshalType)) + contents.Append($"[{parameterMarshalType}] "); +#endif if (parameterInfo.IsOut) contents.Append("out "); else if (parameterInfo.IsRef || UsePassByReference(buildData, parameterInfo.Type, caller)) @@ -484,6 +530,21 @@ namespace Flax.Build.Bindings separator = true; var nativeType = GenerateCSharpManagedToNativeType(buildData, parameterInfo.Type, caller); +#if USE_NETCORE + string parameterMarshalType = ""; + if (parameterInfo.IsOut && parameterInfo.DefaultValue == "var resultAsRef") + { + if (functionInfo.Glue.UseResultReferenceCount) + parameterMarshalType = $"MarshalUsing(typeof(FlaxEngine.ArrayMarshaller<,>), CountElementName = \"{parameterInfo.Name}Count\")"; + else if (parameterInfo.Type.Type == "Dictionary") + parameterMarshalType = $"MarshalUsing(typeof(FlaxEngine.DictionaryMarshaller<,>), ConstantElementCount = 0)"; + } + if (nativeType == "System.Type") + parameterMarshalType = "MarshalUsing(typeof(FlaxEngine.SystemTypeMarshaller))"; + + if (!string.IsNullOrEmpty(parameterMarshalType)) + contents.Append($"[{parameterMarshalType}] "); +#endif if (parameterInfo.IsOut) contents.Append("out "); else if (parameterInfo.IsRef || UsePassByReference(buildData, parameterInfo.Type, caller)) @@ -500,6 +561,17 @@ namespace Flax.Build.Bindings private static void GenerateCSharpWrapperFunctionCall(BuildData buildData, StringBuilder contents, ApiTypeInfo caller, FunctionInfo functionInfo, bool isSetter = false) { +#if USE_NETCORE + for (var i = 0; i < functionInfo.Parameters.Count; i++) + { + var parameterInfo = functionInfo.Parameters[i]; + if (parameterInfo.Type.IsArray || parameterInfo.Type.Type == "Array" || parameterInfo.Type.Type == "Span" || parameterInfo.Type.Type == "BytesContainer" || parameterInfo.Type.Type == "DataContainer" || parameterInfo.Type.Type == "BitArray") + { + if (!parameterInfo.IsOut) + contents.Append($"var {parameterInfo.Name}Count = {(isSetter ? "value" : parameterInfo.Name)}?.Length ?? 0; "); + } + } +#endif if (functionInfo.Glue.UseReferenceForResult) { } @@ -613,6 +685,9 @@ namespace Flax.Build.Bindings { // Write attribute for C++ calling code contents.Append(indent).AppendLine("[Unmanaged]"); + + // Skip boilerplate code when using debugger + //contents.Append(indent).AppendLine("[System.Diagnostics.DebuggerStepThrough]"); } if (isDeprecated || apiTypeInfo.IsDeprecated) { @@ -698,6 +773,15 @@ namespace Flax.Build.Bindings // Class begin GenerateCSharpAttributes(buildData, contents, indent, classInfo, useUnmanaged); +#if USE_NETCORE + string marshallerName = ""; + if (!classInfo.IsStatic) + { + marshallerName = classInfo.Name + "Marshaller"; + contents.Append(indent).AppendLine($"[NativeMarshalling(typeof({marshallerName}))]"); + //contents.Append(indent).AppendLine($"[NativeMarshalling(typeof(FlaxEngine.GCHandleMarshaller2<{classInfo.Name}>.GCHandleMarshaller3<{classInfo.Name}>))]"); + } +#endif contents.Append(indent); GenerateCSharpAccessLevel(contents, classInfo.Access); if (classInfo.IsStatic) @@ -862,11 +946,19 @@ namespace Flax.Build.Bindings contents.Append(indent).Append('}').AppendLine(); contents.AppendLine(); +#if !USE_NETCORE contents.Append(indent).Append("[MethodImpl(MethodImplOptions.InternalCall)]").AppendLine(); contents.Append(indent).Append($"internal static extern void Internal_{eventInfo.Name}_Bind("); if (!eventInfo.IsStatic) contents.Append("IntPtr obj, "); contents.Append("bool bind);"); +#else + contents.Append(indent).Append($"[LibraryImport(\"FlaxEngine\", EntryPoint = \"{classInfo.FullNameManaged}::Internal_{eventInfo.Name}_Bind\", StringMarshalling = StringMarshalling.Custom, StringMarshallingCustomType = typeof(FlaxEngine.StringMarshaller))]").AppendLine(); + contents.Append(indent).Append($"internal static partial void Internal_{eventInfo.Name}_Bind("); + if (!eventInfo.IsStatic) + contents.Append("IntPtr obj, "); + contents.Append("[MarshalAs(UnmanagedType.U1)] bool bind);"); +#endif contents.AppendLine(); } @@ -1100,6 +1192,51 @@ namespace Flax.Build.Bindings indent = indent.Substring(0, indent.Length - 4); contents.AppendLine(indent + "}"); +#if USE_NETCORE + if (!string.IsNullOrEmpty(marshallerName)) + { + string marshallerDefinition = $$""" + [CustomMarshaller(typeof({{classInfo.Name}}), MarshalMode.ManagedToUnmanagedIn, typeof({{marshallerName}}.ManagedToNative))] + [CustomMarshaller(typeof({{classInfo.Name}}), MarshalMode.UnmanagedToManagedOut, typeof({{marshallerName}}.ManagedToNative))] + [CustomMarshaller(typeof({{classInfo.Name}}), MarshalMode.ElementIn, typeof({{marshallerName}}.ManagedToNative))] + [CustomMarshaller(typeof({{classInfo.Name}}), MarshalMode.ManagedToUnmanagedOut, typeof({{marshallerName}}.NativeToManaged))] + [CustomMarshaller(typeof({{classInfo.Name}}), MarshalMode.UnmanagedToManagedIn, typeof({{marshallerName}}.NativeToManaged))] + [CustomMarshaller(typeof({{classInfo.Name}}), MarshalMode.ElementOut, typeof({{marshallerName}}.NativeToManaged))] + [CustomMarshaller(typeof({{classInfo.Name}}), MarshalMode.ManagedToUnmanagedRef, typeof({{marshallerName}}.Bidirectional))] + [CustomMarshaller(typeof({{classInfo.Name}}), MarshalMode.UnmanagedToManagedRef, typeof({{marshallerName}}.Bidirectional))] + [CustomMarshaller(typeof({{classInfo.Name}}), MarshalMode.ElementRef, typeof({{marshallerName}}))] + internal static class {{marshallerName}} + { + public static class NativeToManaged + { + public static {{classInfo.Name}} ConvertToManaged(IntPtr unmanaged) => ({{classInfo.Name}})GCHandleMarshaller.NativeToManaged.ConvertToManaged(unmanaged); + public static void Free(IntPtr unmanaged) => GCHandleMarshaller.NativeToManaged.Free(unmanaged); + } + public static class ManagedToNative + { + public static IntPtr ConvertToUnmanaged({{classInfo.Name}} managed) => GCHandleMarshaller.ManagedToNative.ConvertToUnmanaged(managed); + public static void Free(IntPtr unmanaged) => GCHandleMarshaller.ManagedToNative.Free(unmanaged); + } + public struct Bidirectional + { + GCHandleMarshaller.Bidirectional marsh; + public void FromManaged({{classInfo.Name}} managed) => marsh.FromManaged(managed); + public IntPtr ToUnmanaged() => marsh.ToUnmanaged(); + public void FromUnmanaged(IntPtr unmanaged) => marsh.FromUnmanaged(unmanaged); + public {{classInfo.Name}} ToManaged() => ({{classInfo.Name}})marsh.ToManaged(); + public void Free() => marsh.Free(); + } + internal static {{classInfo.Name}} ConvertToManaged(IntPtr unmanaged) => ({{classInfo.Name}})GCHandleMarshaller.ConvertToManaged(unmanaged); + internal static IntPtr ConvertToUnmanaged({{classInfo.Name}} managed) => GCHandleMarshaller.ConvertToUnmanaged(managed); + internal static void Free(IntPtr unmanaged) => GCHandleMarshaller.Free(unmanaged); + + internal static {{classInfo.Name}} ToManaged(IntPtr managed) => ({{classInfo.Name}})GCHandleMarshaller.ToManaged(managed); + internal static IntPtr ToNative({{classInfo.Name}} managed) => GCHandleMarshaller.ToNative(managed); + } + """; + contents.AppendLine(marshallerDefinition); + } +#endif // Namespace end if (!string.IsNullOrEmpty(classInfo.Namespace)) { @@ -1119,13 +1256,330 @@ namespace Flax.Build.Bindings contents.AppendLine("{"); indent += " "; } +#if USE_NETCORE + // Generate blittable structure + string structNativeMarshaling = ""; + if (UseCustomMarshalling(buildData, structureInfo, structureInfo)) + { + string marshallerName = structureInfo.Name + "Marshaller"; + structNativeMarshaling = $"[NativeMarshalling(typeof({marshallerName}))]"; + contents.Append(indent).AppendLine($"/// "); + contents.Append(indent).AppendLine($"/// Marshaller for unblittable type ."); + contents.Append(indent).AppendLine($"/// "); + contents.Append(indent).AppendLine($"[CustomMarshaller(typeof({structureInfo.Name}), MarshalMode.ManagedToUnmanagedIn, typeof({marshallerName}.ManagedToNative))]"); + contents.Append(indent).AppendLine($"[CustomMarshaller(typeof({structureInfo.Name}), MarshalMode.UnmanagedToManagedOut, typeof({marshallerName}.ManagedToNative))]"); + contents.Append(indent).AppendLine($"[CustomMarshaller(typeof({structureInfo.Name}), MarshalMode.ElementIn, typeof({marshallerName}.ManagedToNative))]"); + contents.Append(indent).AppendLine($"[CustomMarshaller(typeof({structureInfo.Name}), MarshalMode.ManagedToUnmanagedOut, typeof({marshallerName}.NativeToManaged))]"); + contents.Append(indent).AppendLine($"[CustomMarshaller(typeof({structureInfo.Name}), MarshalMode.UnmanagedToManagedIn, typeof({marshallerName}.NativeToManaged))]"); + contents.Append(indent).AppendLine($"[CustomMarshaller(typeof({structureInfo.Name}), MarshalMode.ElementOut, typeof({marshallerName}.NativeToManaged))]"); + contents.Append(indent).AppendLine($"[CustomMarshaller(typeof({structureInfo.Name}), MarshalMode.ManagedToUnmanagedRef, typeof({marshallerName}.Bidirectional))]"); + contents.Append(indent).AppendLine($"[CustomMarshaller(typeof({structureInfo.Name}), MarshalMode.UnmanagedToManagedRef, typeof({marshallerName}.Bidirectional))]"); + contents.Append(indent).AppendLine($"[CustomMarshaller(typeof({structureInfo.Name}), MarshalMode.ElementRef, typeof({marshallerName}))]"); + contents.Append(indent).AppendLine($"internal static unsafe class {marshallerName}"); + contents.Append(indent).AppendLine("{"); + + indent += " "; + + StringBuilder toManagedContent = new StringBuilder(); + StringBuilder toNativeContent = new StringBuilder(); + StringBuilder freeContents = new StringBuilder(); + StringBuilder freeContents2 = new StringBuilder(); + + { + // Native struct begin + GenerateCSharpAttributes(buildData, contents, indent, structureInfo, true); + contents.Append(indent).AppendLine("[StructLayout(LayoutKind.Sequential)]"); + contents.Append(indent); + contents.Append("internal struct ").Append(structureInfo.Name).Append("Internal"); + if (structureInfo.BaseType != null && structureInfo.IsPod) + contents.Append(" : ").Append(GenerateCSharpNativeToManaged(buildData, new TypeInfo { Type = structureInfo.BaseType.Name }, structureInfo)); + contents.AppendLine(); + contents.Append(indent + "{"); + indent += " "; + + toNativeContent.Append($"return new {structureInfo.Name}Internal() {{ "); + toManagedContent.Append($"return new {structureInfo.Name}() {{ "); + + bool useSeparator = false; + foreach (var fieldInfo in structureInfo.Fields) + { + if (fieldInfo.IsStatic || fieldInfo.IsConstexpr) + continue; + + contents.AppendLine(); + + string type, originalType; + if (fieldInfo.Type.IsArray && (fieldInfo.NoArray || structureInfo.IsPod)) + { + // Fixed-size array that needs to be inlined into structure instead of passing it as managed array + fieldInfo.Type.IsArray = false; + originalType = type = GenerateCSharpNativeToManaged(buildData, fieldInfo.Type, structureInfo); + fieldInfo.Type.IsArray = true; + } + else + originalType = type = GenerateCSharpNativeToManaged(buildData, fieldInfo.Type, structureInfo); + + contents.Append(indent); + if (fieldInfo.Access == AccessLevel.Public) + contents.Append("public "); + else if (fieldInfo.Access == AccessLevel.Protected) + contents.Append("protected "); + else if (fieldInfo.Access == AccessLevel.Private) + contents.Append("private "); + if (fieldInfo.IsConstexpr) + contents.Append("const "); + else if (fieldInfo.IsStatic) + contents.Append("static "); + + var apiType = FindApiTypeInfo(buildData, fieldInfo.Type, structureInfo); + bool internalType = apiType is StructureInfo fieldStructureInfo && UseCustomMarshalling(buildData, fieldStructureInfo, structureInfo); + string internalTypeMarshaler = ""; + + if (fieldInfo.Type.IsArray && (fieldInfo.NoArray || structureInfo.IsPod)) + { + contents.Append(type).Append(' ').Append(fieldInfo.Name + "0;").AppendLine(); + for (int i = 1; i < fieldInfo.Type.ArraySize; i++) + { + contents.AppendLine(); + GenerateCSharpAttributes(buildData, contents, indent, structureInfo, fieldInfo, fieldInfo.IsStatic); + contents.Append(indent); + if (fieldInfo.Access == AccessLevel.Public) + contents.Append("public "); + else if (fieldInfo.Access == AccessLevel.Protected) + contents.Append("protected "); + else if (fieldInfo.Access == AccessLevel.Private) + contents.Append("private "); + if (fieldInfo.IsStatic) + contents.Append("static "); + contents.Append(type).Append(' ').Append(fieldInfo.Name + i).Append(';').AppendLine(); + } + } + else + { + if (fieldInfo.Type.IsObjectRef || fieldInfo.Type.Type == "Dictionary") + type = "IntPtr"; + else if (fieldInfo.Type.IsPtr && !originalType.EndsWith("*")) + type = "IntPtr"; + else if (fieldInfo.Type.Type == "Array") + { + type = "IntPtr"; + apiType = FindApiTypeInfo(buildData, fieldInfo.Type.GenericArgs[0], structureInfo); + internalType = apiType is StructureInfo elementStructureInfo && UseCustomMarshalling(buildData, elementStructureInfo, structureInfo); + } + else if (fieldInfo.Type.Type == "Version") + type = "IntPtr"; + else if (type == "string") + type = "IntPtr"; + else if (type == "bool") + type = "byte"; + else if (type == "object") + type = "VariantNative"; + else if (internalType) + { + internalTypeMarshaler = type + "Marshaller"; + type = $"{internalTypeMarshaler}.{type}Internal"; + } + //else if (type == "Guid") + // type = "GuidNative"; + + contents.Append(type).Append(' ').Append(fieldInfo.Name); + contents.Append(';').AppendLine(); + } + + // Generate struct constructor/getter and deconstructor/setter function + if (fieldInfo.NoArray && fieldInfo.Type.IsArray) + continue; + + if (type == "VariantNative") + continue; // FIXME + + if (useSeparator) + { + toManagedContent.Append(", "); + toNativeContent.Append(", "); + freeContents2.Append(""); + freeContents.Append(""); + } + useSeparator = true; + + toManagedContent.Append(fieldInfo.Name); + toManagedContent.Append(" = "); + + toNativeContent.Append(fieldInfo.Name); + toNativeContent.Append(" = "); + + if (fieldInfo.Type.IsObjectRef) + { + toManagedContent.Append($"managed.{fieldInfo.Name} != IntPtr.Zero ? ({fieldInfo.Type.GenericArgs[0].Type})GCHandle.FromIntPtr(managed.{fieldInfo.Name}).Target : null"); + toNativeContent.Append($"managed.{fieldInfo.Name} != null ? GCHandle.ToIntPtr(GCHandle.Alloc(managed.{fieldInfo.Name}, GCHandleType.Weak)) : IntPtr.Zero"); + freeContents.AppendLine($"if (unmanaged.{fieldInfo.Name} != IntPtr.Zero) {{ GCHandle.FromIntPtr(unmanaged.{fieldInfo.Name}).Free(); }}"); + + // Permanent ScriptingObject handle is passed from native side, do not release it + //freeContents2.AppendLine($"if (unmanaged.{fieldInfo.Name} != IntPtr.Zero) {{ GCHandle.FromIntPtr(unmanaged.{fieldInfo.Name}).Free(); }}"); + } + else if (fieldInfo.Type.Type == "ScriptingObject") + { + toManagedContent.Append($"managed.{fieldInfo.Name} != IntPtr.Zero ? (FlaxEngine.Object)GCHandle.FromIntPtr(managed.{fieldInfo.Name}).Target : null"); + toNativeContent.Append($"managed.{fieldInfo.Name} != null ? GCHandle.ToIntPtr(GCHandle.Alloc(managed.{fieldInfo.Name}, GCHandleType.Weak)) : IntPtr.Zero"); + freeContents.AppendLine($"if (unmanaged.{fieldInfo.Name} != IntPtr.Zero) {{ GCHandle.FromIntPtr(unmanaged.{fieldInfo.Name}).Free(); }}"); + + // Permanent ScriptingObject handle is passed from native side, do not release it + //freeContents2.AppendLine($"if (unmanaged.{fieldInfo.Name} != IntPtr.Zero) {{ GCHandle.FromIntPtr(unmanaged.{fieldInfo.Name}).Free(); }}"); + } + else if (fieldInfo.Type.IsPtr && originalType != "IntPtr" && !originalType.EndsWith("*")) + { + toManagedContent.Append($"managed.{fieldInfo.Name} != IntPtr.Zero ? ({originalType})GCHandle.FromIntPtr(managed.{fieldInfo.Name}).Target : null"); + toNativeContent.Append($"managed.{fieldInfo.Name} != null ? GCHandle.ToIntPtr(GCHandle.Alloc(managed.{fieldInfo.Name}, GCHandleType.Weak)) : IntPtr.Zero"); + freeContents.AppendLine($"if (unmanaged.{fieldInfo.Name} != IntPtr.Zero) {{ GCHandle.FromIntPtr(unmanaged.{fieldInfo.Name}).Free(); }}"); + + // Permanent ScriptingObject handle is passed from native side, do not release it + //freeContents2.AppendLine($"if (unmanaged.{fieldInfo.Name} != IntPtr.Zero) {{ GCHandle.FromIntPtr(unmanaged.{fieldInfo.Name}).Free(); }}"); + } + else if (fieldInfo.Type.Type == "Dictionary") + { + string dictionaryArgs = String.Join(", ", + fieldInfo.Type.GenericArgs.Select(x => CSharpNativeToManagedBasicTypes.ContainsKey(x.Type) ? CSharpNativeToManagedBasicTypes[x.Type] : x.Type).ToArray()); + toManagedContent.Append( + $"managed.{fieldInfo.Name} != IntPtr.Zero ? (System.Collections.Generic.{fieldInfo.Type.Type}<{dictionaryArgs}>)GCHandle.FromIntPtr(managed.{fieldInfo.Name}).Target : null"); + toNativeContent.Append( + $"GCHandle.ToIntPtr(GCHandle.Alloc(managed.{fieldInfo.Name}, GCHandleType.Weak))"); + freeContents.AppendLine($"if (unmanaged.{fieldInfo.Name} != IntPtr.Zero) {{ GCHandle.FromIntPtr(unmanaged.{fieldInfo.Name}).Free(); }}"); + freeContents2.AppendLine($"if (unmanaged.{fieldInfo.Name} != IntPtr.Zero) {{ GCHandle.FromIntPtr(unmanaged.{fieldInfo.Name}).Free(); }}"); + } + else if (fieldInfo.Type.Type == "Array") + { + if (internalType) + { + // Marshal blittable array elements back to original non-blittable elements + string originalElementType = originalType.Substring(0, originalType.Length - 2); + string originalElementTypeMarshaller = originalElementType + "Marshaller"; + string internalElementType = $"{originalElementTypeMarshaller}.{originalElementType}Internal"; + toManagedContent.Append($"managed.{fieldInfo.Name} != IntPtr.Zero ? NativeInterop.NativeArrayToManagedArray<{originalElementType}, {internalElementType}>(((ManagedArray)GCHandle.FromIntPtr(managed.{fieldInfo.Name}).Target).array as {internalElementType}[], {originalElementTypeMarshaller}.ToManaged) : null"); + toNativeContent.Append($"GCHandle.ToIntPtr(GCHandle.Alloc(ManagedArray.Get(managed.{fieldInfo.Name}), GCHandleType.Weak))"); + freeContents.AppendLine($"if (unmanaged.{fieldInfo.Name} != IntPtr.Zero) {{ GCHandle handle = GCHandle.FromIntPtr(unmanaged.{fieldInfo.Name}); {internalElementType}[] values = ({internalElementType}[])(((ManagedArray)handle.Target).array); foreach (var value in values) {{ {originalElementTypeMarshaller}.Free(value); }} ((ManagedArray)handle.Target).Release(); handle.Free(); }}"); + freeContents2.AppendLine($"if (unmanaged.{fieldInfo.Name} != IntPtr.Zero) {{ GCHandle handle = GCHandle.FromIntPtr(unmanaged.{fieldInfo.Name}); {internalElementType}[] values = ({internalElementType}[])(((ManagedArray)handle.Target).array); foreach (var value in values) {{ {originalElementTypeMarshaller}.Free(value); }} ((ManagedArray)handle.Target).Release(); handle.Free(); }}"); + } + else if (fieldInfo.Type.GenericArgs[0].IsObjectRef) + { + // Array elements passed as GCHandles + string originalElementType = originalType.Substring(0, originalType.Length - 2); + toManagedContent.Append($"managed.{fieldInfo.Name} != IntPtr.Zero ? NativeInterop.GCHandleArrayToManagedArray<{originalElementType}>((ManagedArray)GCHandle.FromIntPtr(managed.{fieldInfo.Name}).Target) : null"); + toNativeContent.Append($"managed.{fieldInfo.Name}?.Length > 0 ? GCHandle.ToIntPtr(GCHandle.Alloc(ManagedArray.Get(NativeInterop.ManagedArrayToGCHandleArray(managed.{fieldInfo.Name})), GCHandleType.Weak)) : IntPtr.Zero"); + freeContents.AppendLine($"if (unmanaged.{fieldInfo.Name} != IntPtr.Zero) {{ GCHandle handle = GCHandle.FromIntPtr(unmanaged.{fieldInfo.Name}); IntPtr[] ptrs = (IntPtr[])(((ManagedArray)handle.Target).array); foreach (var ptr in ptrs) {{ if (ptr != IntPtr.Zero) {{ GCHandle.FromIntPtr(ptr).Free(); }} }} ((ManagedArray)handle.Target).Release(); handle.Free(); }}"); + freeContents2.AppendLine($"if (unmanaged.{fieldInfo.Name} != IntPtr.Zero) {{ GCHandle handle = GCHandle.FromIntPtr(unmanaged.{fieldInfo.Name}); IntPtr[] ptrs = (IntPtr[])(((ManagedArray)handle.Target).array); foreach (var ptr in ptrs) {{ if (ptr != IntPtr.Zero) {{ GCHandle.FromIntPtr(ptr).Free(); }} }} ((ManagedArray)handle.Target).Release(); handle.Free(); }}"); + } + else + { + // Blittable array elements + toManagedContent.Append($"managed.{fieldInfo.Name} != IntPtr.Zero ? ({originalType})(((ManagedArray)GCHandle.FromIntPtr(managed.{fieldInfo.Name}).Target).array) : null"); + toNativeContent.Append($"managed.{fieldInfo.Name}?.Length > 0 ? GCHandle.ToIntPtr(GCHandle.Alloc(ManagedArray.Get(managed.{fieldInfo.Name}), GCHandleType.Weak)) : IntPtr.Zero"); + freeContents.AppendLine($"if (unmanaged.{fieldInfo.Name} != IntPtr.Zero) {{ GCHandle handle = GCHandle.FromIntPtr(unmanaged.{fieldInfo.Name}); ((ManagedArray)handle.Target).Release(); handle.Free(); }}"); + freeContents2.AppendLine($"if (unmanaged.{fieldInfo.Name} != IntPtr.Zero) {{ GCHandle handle = GCHandle.FromIntPtr(unmanaged.{fieldInfo.Name}); ((ManagedArray)handle.Target).Release(); handle.Free(); }}"); + } + } + else if (fieldInfo.Type.Type == "Version") + { + toManagedContent.Append($"managed.{fieldInfo.Name} != IntPtr.Zero ? ({originalType})GCHandle.FromIntPtr(managed.{fieldInfo.Name}).Target : null"); + toNativeContent.Append($"GCHandle.ToIntPtr(GCHandle.Alloc(managed.{fieldInfo.Name}, GCHandleType.Weak))"); + freeContents.AppendLine($"if (unmanaged.{fieldInfo.Name} != IntPtr.Zero) {{ GCHandle.FromIntPtr(unmanaged.{fieldInfo.Name}).Free(); }}"); + freeContents2.AppendLine($"if (unmanaged.{fieldInfo.Name} != IntPtr.Zero) {{ GCHandle.FromIntPtr(unmanaged.{fieldInfo.Name}).Free(); }}"); + } + else if (originalType == "string") + { + toManagedContent.Append($"ManagedString.ToManaged(managed.{fieldInfo.Name})"); + toNativeContent.Append($"ManagedString.ToNative(managed.{fieldInfo.Name})"); + freeContents.AppendLine($"ManagedString.Free(unmanaged.{fieldInfo.Name});"); + freeContents2.AppendLine($"ManagedString.Free(unmanaged.{fieldInfo.Name});"); + } + else if (originalType == "bool") + { + toManagedContent.Append($"managed.{fieldInfo.Name} != 0"); + toNativeContent.Append($"managed.{fieldInfo.Name} ? (byte)1 : (byte)0"); + } + else if (internalType) + { + toManagedContent.Append($"{internalTypeMarshaler}.ToManaged(managed.{fieldInfo.Name})"); + toNativeContent.Append($"{internalTypeMarshaler}.ToNative(managed.{fieldInfo.Name})"); + freeContents.AppendLine($"{internalTypeMarshaler}.Free(unmanaged.{fieldInfo.Name});"); + freeContents2.AppendLine($"{internalTypeMarshaler}.Free(unmanaged.{fieldInfo.Name});"); + } + /*else if (originalType == "Guid") + { + toManagedContent.Append("(Guid)managed.").Append(fieldInfo.Name); + toNativeContent.Append("(GuidNative)managed.").Append(fieldInfo.Name); + }*/ + else + { + toManagedContent.Append("managed.").Append(fieldInfo.Name); + toNativeContent.Append("managed.").Append(fieldInfo.Name); + } + } + + // Native struct end + indent = indent.Substring(0, indent.Length - 4); + contents.AppendLine(indent + "}").AppendLine(); + + toManagedContent.AppendLine(" };"); + toNativeContent.AppendLine(" };"); + } + + var indent2 = indent + " "; + var indent3 = indent2 + " "; + + // NativeToManaged stateless shape + // NOTE: GCHandles of FlaxEngine.Object must not be released in this case + contents.Append(indent).AppendLine($"public static class NativeToManaged").Append(indent).AppendLine("{"); + contents.Append(indent2).AppendLine($"public static {structureInfo.Name} ConvertToManaged({structureInfo.Name}Internal unmanaged) => {marshallerName}.ToManaged(unmanaged);"); + contents.Append(indent2).AppendLine($"public static void Free({structureInfo.Name}Internal unmanaged)"); + contents.Append(indent2).AppendLine("{").Append(indent3).AppendLine(freeContents2.Replace("\n", "\n" + indent3).ToString().TrimEnd()).Append(indent2).AppendLine("}"); + contents.Append(indent).AppendLine("}"); + + // ManagedToNative stateless shape + contents.Append(indent).AppendLine($"public static class ManagedToNative").Append(indent).AppendLine("{"); + contents.Append(indent2).AppendLine($"public static {structureInfo.Name}Internal ConvertToUnmanaged({structureInfo.Name} managed) => {marshallerName}.ToNative(managed);"); + contents.Append(indent2).AppendLine($"public static void Free({structureInfo.Name}Internal unmanaged) => {marshallerName}.Free(unmanaged);"); + contents.Append(indent).AppendLine("}"); + + // Bidirectional stateful shape + // NOTE: GCHandles of FlaxEngine.Object must not be released unless they were allocated by this marshaller + contents.Append(indent).AppendLine($"public struct Bidirectional").Append(indent).AppendLine("{"); + contents.Append(indent2).AppendLine($"{structureInfo.Name} managed;"); + contents.Append(indent2).AppendLine($"{structureInfo.Name}Internal? unmanaged;"); + contents.Append(indent2).AppendLine($"public void FromManaged({structureInfo.Name} managed) {{ this.managed = managed; }}"); + contents.Append(indent2).AppendLine($"public {structureInfo.Name}Internal ToUnmanaged() {{ unmanaged = {marshallerName}.ToNative(managed); return unmanaged.Value; }}"); + //contents.Append(indent2).AppendLine($"public void FromUnmanaged({structureInfo.Name}Internal unmanaged) {{ {marshallerName}.Free(this.unmanaged.Value); this.unmanaged = unmanaged; }}"); + contents.Append(indent2).AppendLine($"public void FromUnmanaged({structureInfo.Name}Internal unmanaged) {{ this.unmanaged = unmanaged; }}"); + contents.Append(indent2).AppendLine($"public {structureInfo.Name} ToManaged() {{ managed = {marshallerName}.ToManaged(unmanaged.Value); return managed; }}"); + contents.Append(indent2).AppendLine($"public void Free() {{ if (unmanaged.HasValue) {{ NativeToManaged.Free(unmanaged.Value); unmanaged = null; }} }}"); + contents.Append(indent).AppendLine("}"); + + // Bidirectional stateless shape + contents.Append(indent).AppendLine($"internal static {structureInfo.Name} ConvertToManaged({structureInfo.Name}Internal unmanaged) => ToManaged(unmanaged);"); + contents.Append(indent).AppendLine($"internal static {structureInfo.Name}Internal ConvertToUnmanaged({structureInfo.Name} managed) => ToNative(managed);"); + contents.Append(indent).AppendLine($"internal static void Free({structureInfo.Name}Internal unmanaged)"); + contents.Append(indent).AppendLine("{").Append(indent2).AppendLine(freeContents.Replace("\n", "\n" + indent2).ToString().TrimEnd()).Append(indent).AppendLine("}"); + + // Managed/native converters + contents.Append(indent).AppendLine($"internal static {structureInfo.Name} ToManaged({structureInfo.Name}Internal managed)"); + contents.Append(indent).AppendLine("{").Append(indent2).AppendLine(toManagedContent.Replace("\n", "\n" + indent2).ToString().TrimEnd()).Append(indent).AppendLine("}"); + contents.Append(indent).AppendLine($"internal static {structureInfo.Name}Internal ToNative({structureInfo.Name} managed)"); + contents.Append(indent).AppendLine("{").Append(indent2).AppendLine(toNativeContent.Replace("\n", "\n" + indent2).ToString().TrimEnd()).Append(indent).AppendLine("}"); + + indent = indent.Substring(0, indent.Length - 4); + contents.Append(indent).AppendLine("}"); + } +#endif // Struct docs GenerateCSharpComment(contents, indent, structureInfo.Comment); // Struct begin GenerateCSharpAttributes(buildData, contents, indent, structureInfo, true); contents.Append(indent).AppendLine("[StructLayout(LayoutKind.Sequential)]"); +#if USE_NETCORE + if (!string.IsNullOrEmpty(structNativeMarshaling)) + contents.Append(indent).AppendLine(structNativeMarshaling); +#endif contents.Append(indent); GenerateCSharpAccessLevel(contents, structureInfo.Access); contents.Append("unsafe partial struct ").Append(structureInfo.Name); @@ -1142,6 +1596,12 @@ namespace Flax.Build.Bindings contents.AppendLine(); GenerateCSharpComment(contents, indent, fieldInfo.Comment); GenerateCSharpAttributes(buildData, contents, indent, structureInfo, fieldInfo, fieldInfo.IsStatic, fieldInfo.DefaultValue, fieldInfo.Type); +#if USE_NETCORE + if (fieldInfo.Type.Type == "String") + contents.Append(indent).AppendLine("[MarshalAs(UnmanagedType.LPWStr)]"); + else if (fieldInfo.Type.Type == "bool") + contents.Append(indent).AppendLine("[MarshalAs(UnmanagedType.U1)]"); +#endif contents.Append(indent); GenerateCSharpAccessLevel(contents, fieldInfo.Access); if (fieldInfo.IsConstexpr) @@ -1153,21 +1613,32 @@ namespace Flax.Build.Bindings if (fieldInfo.Type.IsArray && (fieldInfo.NoArray || structureInfo.IsPod)) { - // Fixed-size array that needs to be inlined into structure instead of passing it as managed array fieldInfo.Type.IsArray = false; type = GenerateCSharpNativeToManaged(buildData, fieldInfo.Type, structureInfo); fieldInfo.Type.IsArray = true; - contents.Append(type).Append(' ').Append(fieldInfo.Name + "0;").AppendLine(); - for (int i = 1; i < fieldInfo.Type.ArraySize; i++) +#if USE_NETCORE + // Use fixed statement with primitive types of buffers + if (type == "char") { - contents.AppendLine(); - GenerateCSharpComment(contents, indent, fieldInfo.Comment); - GenerateCSharpAttributes(buildData, contents, indent, structureInfo, fieldInfo, fieldInfo.IsStatic); - contents.Append(indent); - GenerateCSharpAccessLevel(contents, fieldInfo.Access); - if (fieldInfo.IsStatic) - contents.Append("static "); - contents.Append(type).Append(' ').Append(fieldInfo.Name + i).Append(';').AppendLine(); + // char's are not blittable, store as short instead + contents.Append($"fixed short {fieldInfo.Name}0[{fieldInfo.Type.ArraySize}]; // {type}*").AppendLine(); + } + else +#endif + { + // Padding in structs for fixed-size array + contents.Append(type).Append(' ').Append(fieldInfo.Name + "0;").AppendLine(); + for (int i = 1; i < fieldInfo.Type.ArraySize; i++) + { + contents.AppendLine(); + GenerateCSharpComment(contents, indent, fieldInfo.Comment); + GenerateCSharpAttributes(buildData, contents, indent, structureInfo, fieldInfo, fieldInfo.IsStatic); + contents.Append(indent); + GenerateCSharpAccessLevel(contents, fieldInfo.Access); + if (fieldInfo.IsStatic) + contents.Append("static "); + contents.Append(type).Append(' ').Append(fieldInfo.Name + i).Append(';').AppendLine(); + } } continue; } @@ -1393,6 +1864,23 @@ namespace Flax.Build.Bindings contents.AppendLine(indent + "}"); if (!string.IsNullOrEmpty(interfaceInfo.Namespace)) contents.AppendLine("}"); +#if USE_NETCORE + { + string marshallerName = interfaceInfo.Name + "Marshaller"; + contents.AppendLine(); + + contents.Append(indent).AppendLine("/// "); + contents.Append(indent).AppendLine("/// "); + contents.Append(indent).AppendLine("/// "); + contents.Append(indent).AppendLine($"[CustomMarshaller(typeof({interfaceInfo.Name}), MarshalMode.Default, typeof({marshallerName}))]"); + contents.Append(indent).AppendLine($"internal static class {marshallerName}"); + contents.Append(indent).AppendLine("{"); + contents.Append(indent + " ").AppendLine($"internal static {interfaceInfo.Name} ConvertToManaged(IntPtr unmanaged) => ({interfaceInfo.Name})GCHandleMarshaller.ConvertToManaged(unmanaged);"); + contents.Append(indent + " ").AppendLine($"internal static IntPtr ConvertToUnmanaged({interfaceInfo.Name} managed) => GCHandleMarshaller.ConvertToUnmanaged(managed);"); + contents.Append(indent + " ").AppendLine("internal static void Free(IntPtr unmanaged) => GCHandleMarshaller.Free(unmanaged);"); + contents.Append(indent).AppendLine("}"); + } +#endif } private static bool GenerateCSharpType(BuildData buildData, StringBuilder contents, string indent, object type) @@ -1456,6 +1944,9 @@ namespace Flax.Build.Bindings CSharpUsedNamespaces.Add("System.Globalization"); CSharpUsedNamespaces.Add("System.Runtime.CompilerServices"); CSharpUsedNamespaces.Add("System.Runtime.InteropServices"); +#if USE_NETCORE + CSharpUsedNamespaces.Add("System.Runtime.InteropServices.Marshalling"); +#endif CSharpUsedNamespaces.Add("FlaxEngine"); // Process all API types from the file diff --git a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cpp.cs b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cpp.cs index 1b54c9d81..f16ef5391 100644 --- a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cpp.cs +++ b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cpp.cs @@ -440,6 +440,13 @@ namespace Flax.Build.Bindings // Array or Span or DataContainer if ((typeInfo.Type == "Array" || typeInfo.Type == "Span" || typeInfo.Type == "DataContainer") && typeInfo.GenericArgs != null) { +#if USE_NETCORE + if (typeInfo.GenericArgs[0].Type == "bool") + { + type = "bool*"; + return "MUtils::ToBoolArray({0})"; + } +#endif type = "MonoArray*"; return "MUtils::ToArray({0}, " + GenerateCppGetNativeClass(buildData, typeInfo.GenericArgs[0], caller, functionInfo) + ")"; } @@ -472,8 +479,13 @@ namespace Flax.Build.Bindings if (typeInfo.Type == "BitArray" && typeInfo.GenericArgs != null) { CppIncludeFiles.Add("Engine/Scripting/InternalCalls/ManagedBitArray.h"); +#if USE_NETCORE + type = "bool*"; + return "MUtils::ToBoolArray({0})"; +#else type = "MonoObject*"; return "ManagedBitArray::ToManaged({0})"; +#endif } // Function @@ -817,6 +829,7 @@ namespace Flax.Build.Bindings return true; if (typeInfo.IsPtr || typeInfo.IsRef || typeInfo.IsArray || typeInfo.IsBitField || (typeInfo.GenericArgs != null && typeInfo.GenericArgs.Count != 0)) return false; +#if !USE_NETCORE if (CSharpNativeToManagedBasicTypes.ContainsKey(typeInfo.Type) || CSharpNativeToManagedBasicTypes.ContainsValue(typeInfo.Type)) return true; var apiType = FindApiTypeInfo(buildData, typeInfo, caller); @@ -825,6 +838,7 @@ namespace Flax.Build.Bindings if (apiType.IsEnum) return true; } +#endif return false; } @@ -880,7 +894,38 @@ namespace Flax.Build.Bindings }, IsOut = true, }); +#if USE_NETCORE + if (functionInfo.ReturnType.Type == "Array" || functionInfo.ReturnType.Type == "Span" || functionInfo.ReturnType.Type == "DataContainer") + { + functionInfo.Glue.UseResultReferenceCount = true; + functionInfo.Glue.CustomParameters.Add(new FunctionInfo.ParameterInfo + { + Name = "resultAsRefCount", + DefaultValue = "var resultAsRefCount", + Type = new TypeInfo + { + Type = "int" + }, + IsOut = true, + }); + } +#endif } +#if USE_NETCORE + else if (functionInfo.ReturnType.Type == "BitArray" || functionInfo.ReturnType.Type == "BytesContainer") + { + functionInfo.Glue.CustomParameters.Add(new FunctionInfo.ParameterInfo + { + Name = "returnCount", + DefaultValue = "var returnCount", + Type = new TypeInfo + { + Type = "int" + }, + IsOut = true, + }); + } +#endif CppInternalCalls.Add(new KeyValuePair(functionInfo.UniqueName, functionInfo.UniqueName)); contents.AppendFormat(" static {0} {1}(", returnValueType, functionInfo.UniqueName); @@ -945,6 +990,23 @@ namespace Flax.Build.Bindings CppParamsThatNeedConversionWrappers[i] = GenerateCppWrapperNativeToManaged(buildData, parameterInfo.Type, caller, out CppParamsThatNeedConversionTypes[i], functionInfo); } } +#if USE_NETCORE + if (parameterInfo.Type.IsArray || parameterInfo.Type.Type == "Array" || parameterInfo.Type.Type == "Span" || parameterInfo.Type.Type == "BytesContainer" || parameterInfo.Type.Type == "DataContainer" || parameterInfo.Type.Type == "BitArray") + { + // We need additional output parameters for array sizes + functionInfo.Glue.CustomParameters.Add(new FunctionInfo.ParameterInfo + { + Name = parameterInfo.Name + "Count", + DefaultValue = parameterInfo.IsOut ? "int _" : parameterInfo.Name + "Count", + Type = new TypeInfo + { + Type = "int" + }, + IsOut = parameterInfo.IsOut, + IsRef = isRefOut || parameterInfo.Type.IsRef, + }); + } +#endif } for (var i = 0; i < functionInfo.Glue.CustomParameters.Count; i++) @@ -965,6 +1027,9 @@ namespace Flax.Build.Bindings contents.Append(')'); contents.AppendLine(); contents.AppendLine(" {"); +#if USE_NETCORE + contents.AppendLine(String.Format(" SCRIPTING_EXPORT(\"{0}\")", caller.FullNameManaged + "::Internal_" + functionInfo.UniqueName)); +#endif if (!functionInfo.IsStatic) contents.AppendLine(" if (obj == nullptr) DebugLog::ThrowNullReference();"); @@ -981,6 +1046,25 @@ namespace Flax.Build.Bindings callBegin += "auto __result = "; } +#if USE_NETCORE + string callBegin2 = ""; + if (functionInfo.Glue.UseResultReferenceCount) + { + callBegin2 = " "; + if (functionInfo.ReturnType.Type == "Span" || functionInfo.ReturnType.Type == "BytesContainer") + callBegin2 += "*resultAsRefCount = {0}.Length();"; + else + callBegin2 += "*resultAsRefCount = {0}.Count();"; + } + else if (functionInfo.ReturnType.Type == "BitArray" || functionInfo.ReturnType.Type == "BytesContainer") + { + callBegin2 = " "; + if (functionInfo.ReturnType.Type == "Span" || functionInfo.ReturnType.Type == "BytesContainer") + callBegin2 += "*returnCount = {0}.Length();"; + else + callBegin2 += "*returnCount = {0}.Count();"; + } +#endif string call; if (functionInfo.IsStatic) { @@ -1017,6 +1101,16 @@ namespace Flax.Build.Bindings } else { +#if USE_NETCORE + // FIXME + if (parameterInfo.Type.Type == "Span" || + parameterInfo.Type.Type == "Array" || + parameterInfo.Type.Type == "DataContainer" || + parameterInfo.Type.Type == "Dictionary") + { + name = '*' + name; + } +#endif // Convert value param += string.Format(CppParamsWrappersCache[i], name); } @@ -1059,8 +1153,21 @@ namespace Flax.Build.Bindings } } - contents.Append(callBegin); - call = string.Format(callFormat, call, callParams); +#if USE_NETCORE + if (!string.IsNullOrEmpty(callBegin2)) + { + contents.Append(" ").Append("const auto& callTemp = ").Append(string.Format(callFormat, call, callParams)).Append(";").AppendLine(); + call = "callTemp"; + contents.Append(string.Format(callBegin2, call)); + contents.AppendLine(); + contents.Append(callBegin); + } + else +#endif + { + contents.Append(callBegin); + call = string.Format(callFormat, call, callParams); + } if (!string.IsNullOrEmpty(returnValueConvert)) { contents.AppendFormat(returnValueConvert, call); @@ -1094,6 +1201,12 @@ namespace Flax.Build.Bindings if (apiType.IsClass) { contents.AppendFormat(" mono_gc_wbarrier_generic_store({0}, (MonoObject*){1});", parameterInfo.Name, value).AppendLine(); +#if USE_NETCORE + if (parameterInfo.Type.Type == "Array") + { + contents.AppendFormat(" *{0}Count = {1}.Count();", parameterInfo.Name, parameterInfo.Name + "Temp").AppendLine(); + } +#endif continue; } if (apiType.IsStruct && !apiType.IsPod) @@ -1109,9 +1222,10 @@ namespace Flax.Build.Bindings if (parameterInfo.Type.Type == "BytesContainer" && parameterInfo.Type.GenericArgs == null) { contents.AppendFormat(" mono_gc_wbarrier_generic_store({0}, (MonoObject*){1});", parameterInfo.Name, value).AppendLine(); + contents.AppendFormat(" *{0}Count = {1}.Length();", parameterInfo.Name, parameterInfo.Name + "Temp").AppendLine(); continue; } - + throw new Exception($"Unsupported type of parameter '{parameterInfo}' in method '{functionInfo}' to be passed using 'out'"); } } @@ -1646,6 +1760,9 @@ namespace Flax.Build.Bindings contents.AppendFormat("{0}* obj, ", classTypeNameNative); contents.Append("bool bind)").AppendLine(); contents.Append(" {").AppendLine(); +#if USE_NETCORE + contents.AppendLine(String.Format(" SCRIPTING_EXPORT(\"{0}\")", classTypeNameManagedInternalCall + "::Internal_" + eventInfo.Name + "_Bind")); +#endif contents.Append(" Function CustomParameters; } diff --git a/Source/Tools/Flax.Build/Bindings/TypeInfo.cs b/Source/Tools/Flax.Build/Bindings/TypeInfo.cs index 5f3e774c9..d52f34538 100644 --- a/Source/Tools/Flax.Build/Bindings/TypeInfo.cs +++ b/Source/Tools/Flax.Build/Bindings/TypeInfo.cs @@ -33,6 +33,15 @@ namespace Flax.Build.Bindings /// public bool IsConstRef => IsRef && IsConst; + /// + /// Gets a value indicating whether this type is a reference to another object. + /// + public bool IsObjectRef => (Type == "ScriptingObjectReference" || + Type == "AssetReference" || + Type == "WeakAssetReference" || + Type == "SoftAssetReference" || + Type == "SoftObjectReference") && GenericArgs != null; + public TypeInfo() { } diff --git a/Source/Tools/Flax.Build/Build/Assembler.cs b/Source/Tools/Flax.Build/Build/Assembler.cs index 0ac982667..3930d25f1 100644 --- a/Source/Tools/Flax.Build/Build/Assembler.cs +++ b/Source/Tools/Flax.Build/Build/Assembler.cs @@ -1,10 +1,13 @@ // Copyright (c) 2012-2020 Flax Engine. All rights reserved. -using System; -using System.CodeDom.Compiler; -using System.Collections.Generic; -using System.Linq; +using Microsoft.CodeAnalysis; +using System.Diagnostics; using System.Reflection; +using System.Runtime.Loader; +using System.Text; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.Emit; +using Microsoft.CodeAnalysis.Text; namespace Flax.Build { @@ -18,7 +21,7 @@ namespace Flax.Build /// public static readonly Assembly[] DefaultReferences = { - //typeof(IntPtr).Assembly, // mscorlib.dll + typeof(IntPtr).Assembly, // mscorlib.dll typeof(Enumerable).Assembly, // System.Linq.dll typeof(ISet<>).Assembly, // System.dll typeof(Builder).Assembly, // Flax.Build.exe @@ -29,6 +32,13 @@ namespace Flax.Build /// public string OutputPath = null; + /// + /// + /// + public string CachePath = null; + + private string CacheAssemblyPath = null; + /// /// The source files for compilation. /// @@ -44,6 +54,13 @@ namespace Flax.Build /// public readonly List References = new List(); + public Assembler(List sourceFiles, string cachePath = null) + { + SourceFiles.AddRange(sourceFiles); + CachePath = cachePath; + CacheAssemblyPath = cachePath != null ? Path.Combine(Directory.GetParent(cachePath).FullName, "BuilderRulesCache.dll") : null; + } + /// /// Builds the assembly. /// @@ -51,9 +68,28 @@ namespace Flax.Build /// The created and loaded assembly. public Assembly Build() { - Dictionary providerOptions = new Dictionary(); - providerOptions.Add("CompilerVersion", "v4.0"); - CodeDomProvider provider = new Microsoft.CSharp.CSharpCodeProvider(providerOptions); + DateTime recentWriteTime = DateTime.MinValue; + if (CachePath != null) + { + foreach (var sourceFile in SourceFiles) + { + // FIXME: compare and cache individual write times! + DateTime lastWriteTime = File.GetLastWriteTime(sourceFile); + if (lastWriteTime > recentWriteTime) + recentWriteTime = lastWriteTime; + } + + DateTime cacheTime = File.Exists(CachePath) + ? DateTime.FromBinary(long.Parse(File.ReadAllText(CachePath))) + : DateTime.MinValue; + if (recentWriteTime <= cacheTime && File.Exists(CacheAssemblyPath)) + { + Assembly cachedAssembly = AssemblyLoadContext.Default.LoadFromAssemblyPath(CacheAssemblyPath); + return cachedAssembly; + } + } + + Stopwatch sw = Stopwatch.StartNew(); // Collect references HashSet references = new HashSet(); @@ -66,49 +102,74 @@ namespace Flax.Build if (!assembly.IsDynamic) references.Add(assembly.Location); } - - // Setup compilation options - CompilerParameters cp = new CompilerParameters(); - cp.GenerateExecutable = false; - cp.WarningLevel = 4; - cp.TreatWarningsAsErrors = false; - cp.ReferencedAssemblies.AddRange(references.ToArray()); - if (string.IsNullOrEmpty(OutputPath)) - { - cp.GenerateInMemory = true; - cp.IncludeDebugInformation = false; - } - else - { - cp.GenerateInMemory = false; - cp.IncludeDebugInformation = true; - cp.OutputAssembly = OutputPath; - } + references.Add(Path.Combine(Directory.GetParent(DefaultReferences[0].Location).FullName, "System.dll")); + references.Add(Path.Combine(Directory.GetParent(DefaultReferences[0].Location).FullName, "System.Runtime.dll")); + references.Add(Path.Combine(Directory.GetParent(DefaultReferences[0].Location).FullName, "System.Collections.dll")); + references.Add(Path.Combine(Directory.GetParent(DefaultReferences[0].Location).FullName, "Microsoft.Win32.Registry.dll")); // HACK: C# will give compilation errors if a LIB variable contains non-existing directories Environment.SetEnvironmentVariable("LIB", null); + CSharpCompilationOptions defaultCompilationOptions = + new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary) + .WithUsings(new[] { "System", }) + .WithPlatform(Microsoft.CodeAnalysis.Platform.AnyCpu) + .WithOptimizationLevel(OptimizationLevel.Debug); + + List defaultReferences = new List(); + foreach (var r in references) + defaultReferences.Add(MetadataReference.CreateFromFile(r)); + // Run the compilation - CompilerResults cr = provider.CompileAssemblyFromFile(cp, SourceFiles.ToArray()); + using var memoryStream = new MemoryStream(); + + var syntaxTrees = new List(); + foreach (var sourceFile in SourceFiles) + { + var stringText = SourceText.From(File.ReadAllText(sourceFile), Encoding.UTF8); + var parsedSyntaxTree = SyntaxFactory.ParseSyntaxTree(stringText, + CSharpParseOptions.Default.WithLanguageVersion(LanguageVersion.CSharp9), sourceFile); + syntaxTrees.Add(parsedSyntaxTree); + } + + var compilation = CSharpCompilation.Create("BuilderRulesCache.dll", syntaxTrees.ToArray(), defaultReferences, defaultCompilationOptions); + EmitResult emitResult = compilation.Emit(memoryStream); // Process warnings and errors - bool hasError = false; - foreach (CompilerError ce in cr.Errors) + foreach (var diagnostic in emitResult.Diagnostics) { - if (ce.IsWarning) - { - Log.Warning(string.Format("{0} at {1}: {2}", ce.FileName, ce.Line, ce.ErrorText)); - } - else - { - Log.Error(string.Format("{0} at line {1}: {2}", ce.FileName, ce.Line, ce.ErrorText)); - hasError = true; - } + var msg = diagnostic.ToString(); + if (diagnostic.Severity == DiagnosticSeverity.Warning) + Log.Warning(msg); + else if (diagnostic.IsWarningAsError || diagnostic.Severity == DiagnosticSeverity.Error) + Log.Error(msg); } - if (hasError) + if (!emitResult.Success) throw new Exception("Failed to build assembly."); - return cr.CompiledAssembly; + memoryStream.Seek(0, SeekOrigin.Begin); + Assembly compiledAssembly = AssemblyLoadContext.Default.LoadFromStream(memoryStream); + + if (CachePath != null && CacheAssemblyPath != null) + { + memoryStream.Seek(0, SeekOrigin.Begin); + + var cacheDirectory = Path.GetDirectoryName(CacheAssemblyPath); + if (!Directory.Exists(cacheDirectory)) + Directory.CreateDirectory(cacheDirectory); + + using (FileStream fileStream = File.Open(CacheAssemblyPath, FileMode.Create, FileAccess.Write)) + { + memoryStream.CopyTo(fileStream); + fileStream.Close(); + } + + File.WriteAllText(CachePath, recentWriteTime.ToBinary().ToString()); + } + + sw.Stop(); + Log.Info("Assembler time: " + sw.Elapsed.TotalSeconds.ToString() + "s"); + return compiledAssembly; } } } diff --git a/Source/Tools/Flax.Build/Build/Builder.Projects.cs b/Source/Tools/Flax.Build/Build/Builder.Projects.cs index bb645a940..6a7fe0b87 100644 --- a/Source/Tools/Flax.Build/Build/Builder.Projects.cs +++ b/Source/Tools/Flax.Build/Build/Builder.Projects.cs @@ -196,7 +196,7 @@ namespace Flax.Build var workspaceRoot = rootProject.ProjectFolderPath; var projectsRoot = Path.Combine(workspaceRoot, "Cache", "Projects"); var projects = new List(); - var dotNetProjectGenerator = ProjectGenerator.Create(projectFormat, TargetType.DotNet); + var dotNetProjectGenerator = ProjectGenerator.Create(projectFormat, TargetType.DotNetCore); var projectToBinaryModule = new Dictionary>>(); var projectToModulesBuildOptions = new Dictionary>(); Project mainSolutionProject = null; @@ -367,7 +367,7 @@ namespace Flax.Build // Create project description var project = dotNetProjectGenerator.CreateProject(); - project.Type = TargetType.DotNet; + project.Type = TargetType.DotNetCore; project.Name = project.BaseName = binaryModuleName; if (mainSolutionProject != null && projectInfo == rootProject) project.Name += ".CSharp"; // Prevent overlapping name with native code project @@ -480,7 +480,7 @@ namespace Flax.Build using (new ProfileEventScope("CreateProject")) { project = dotNetProjectGenerator.CreateProject(); - project.Type = TargetType.DotNet; + project.Type = TargetType.DotNetCore; project.Name = project.BaseName = rulesProjectName; project.Targets = new[] { target }; project.SearchPaths = new string[0]; diff --git a/Source/Tools/Flax.Build/Build/Builder.Rules.cs b/Source/Tools/Flax.Build/Build/Builder.Rules.cs index 9470e3e19..43666837d 100644 --- a/Source/Tools/Flax.Build/Build/Builder.Rules.cs +++ b/Source/Tools/Flax.Build/Build/Builder.Rules.cs @@ -188,8 +188,8 @@ namespace Flax.Build Assembly assembly; using (new ProfileEventScope("CompileRules")) { - var assembler = new Assembler(); - assembler.SourceFiles.AddRange(files); + var assembler = new Assembler(files, Path.Combine(Globals.Root, Configuration.IntermediateFolder, "BuilderRules.cache")); + //var assembler = new Assembler(files); assembly = assembler.Build(); } diff --git a/Source/Tools/Flax.Build/Build/Builder.cs b/Source/Tools/Flax.Build/Build/Builder.cs index 5139ed621..00c43876e 100644 --- a/Source/Tools/Flax.Build/Build/Builder.cs +++ b/Source/Tools/Flax.Build/Build/Builder.cs @@ -334,6 +334,7 @@ namespace Flax.Build BuildTargetNativeCppBindingsOnly(rules, graph, target, buildContext, platform, architecture, configuration); break; case TargetType.DotNet: + case TargetType.DotNetCore: BuildTargetDotNet(rules, graph, target, platform, configuration); break; default: throw new ArgumentOutOfRangeException(); @@ -354,6 +355,7 @@ namespace Flax.Build BuildTargetNativeCpp(rules, graph, target, buildContext, toolchain, configuration); break; case TargetType.DotNet: + case TargetType.DotNetCore: BuildTargetDotNet(rules, graph, target, toolchain.Platform, configuration); break; default: throw new ArgumentOutOfRangeException(); diff --git a/Source/Tools/Flax.Build/Build/DotNet/Builder.DotNet.cs b/Source/Tools/Flax.Build/Build/DotNet/Builder.DotNet.cs index 47402131d..eae56e0a4 100644 --- a/Source/Tools/Flax.Build/Build/DotNet/Builder.DotNet.cs +++ b/Source/Tools/Flax.Build/Build/DotNet/Builder.DotNet.cs @@ -5,7 +5,10 @@ using System.Collections.Generic; using System.IO; using System.Linq; using Flax.Build.Graph; +using Flax.Build.Platforms; using Flax.Deploy; +using Microsoft.Win32; +using Task = Flax.Build.Graph.Task; namespace Flax.Build { @@ -146,12 +149,26 @@ namespace Flax.Build private static void BuildDotNet(TaskGraph graph, BuildData buildData, NativeCpp.BuildOptions buildOptions, string name, List sourceFiles, HashSet fileReferences = null, IGrouping binaryModule = null) { +#if USE_NETCORE + static Version ParseVersion(string version) + { + // Give precedence to final releases over release candidate / beta releases + int rev = 9999; + if (version.Contains("-")) // e.g. 7.0.0-rc.2.22472.3 + { + version = version.Substring(0, version.IndexOf("-")); + rev = 0; + } + Version ver = new Version(version); + return new Version(ver.Major, ver.Minor, ver.Build, rev); + } +#endif // Setup build options var buildPlatform = Platform.BuildTargetPlatform; var outputPath = Path.GetDirectoryName(buildData.Target.GetOutputFilePath(buildOptions)); var outputFile = Path.Combine(outputPath, name + ".dll"); var outputDocFile = Path.Combine(outputPath, name + ".xml"); - string monoRoot, monoPath, cscPath; + string monoRoot, monoPath, cscPath, referenceAssemblies, referenceAnalyzers; switch (buildPlatform) { case TargetPlatform.Windows: @@ -162,27 +179,78 @@ namespace Flax.Build monoPath = null; cscPath = Path.Combine(Path.GetDirectoryName(VCEnvironment.MSBuildPath), "Roslyn", "csc.exe"); - if (!File.Exists(cscPath)) +#if USE_NETCORE + // dotnet + if (WindowsPlatformBase.TryReadDirRegistryKey(@"HKEY_LOCAL_MACHINE\SOFTWARE\dotnet\Setup\InstalledVersions\x64\sharedhost", "Path", out string dotnetPath)) + { +#pragma warning disable CA1416 + string arch = "x64"; + using RegistryKey baseKey = RegistryKey.OpenBaseKey(RegistryHive.LocalMachine, RegistryView.Registry64); + using RegistryKey sdkVersionsKey = baseKey.OpenSubKey($@"SOFTWARE\WOW6432Node\dotnet\Setup\InstalledVersions\{arch}\sdk"); + using RegistryKey sharedfxVersionsKey = baseKey.OpenSubKey($@"SOFTWARE\WOW6432Node\dotnet\Setup\InstalledVersions\{arch}\sharedfx\Microsoft.NETCore.App"); + using RegistryKey hostfxrKey = baseKey.OpenSubKey($@"SOFTWARE\WOW6432Node\dotnet\Setup\InstalledVersions\{arch}\hostfxr"); + + string dotnetSdkVersion = sdkVersionsKey.GetValueNames().OrderByDescending(x => ParseVersion(x)).FirstOrDefault(); + string dotnetSharedfxVersion = sharedfxVersionsKey.GetValueNames().OrderByDescending(x => ParseVersion(x)).FirstOrDefault(); +#pragma warning restore CA1416 + cscPath = @$"{dotnetPath}sdk\{dotnetSdkVersion}\Roslyn\bincore\csc.dll"; + referenceAssemblies = @$"{dotnetPath}shared\Microsoft.NETCore.App\{dotnetSharedfxVersion}\"; + referenceAnalyzers = @$"{dotnetPath}packs\Microsoft.NETCore.App.Ref\{dotnetSharedfxVersion}\analyzers\dotnet\cs\"; + } + else //if (!File.Exists(cscPath)) +#endif { // Fallback to Mono binaries monoPath = Path.Combine(monoRoot, "bin", "mono.exe"); cscPath = Path.Combine(monoRoot, "lib", "mono", "4.5", "csc.exe"); + referenceAssemblies = Path.Combine(monoRoot, "lib", "mono", "4.5-api"); + referenceAnalyzers = ""; } break; } case TargetPlatform.Linux: - monoRoot = Path.Combine(Globals.EngineRoot, "Source", "Platforms", "Editor", "Linux", "Mono"); - monoPath = Path.Combine(monoRoot, "bin", "mono"); - cscPath = Path.Combine(monoRoot, "lib", "mono", "4.5", "csc.exe"); + { +#if USE_NETCORE + // TODO: Support /etc/dotnet/install_location + string dotnetPath = "/usr/share/dotnet/"; + string arch = "x64"; + string os = $"linux-{arch}"; + monoPath = null; + + string[] sharedfxVersions = Directory.GetDirectories($"{dotnetPath}shared/Microsoft.NETCore.App/").Select(x => Path.GetFileName(x)).ToArray(); + string dotnetSharedfxVersion = sharedfxVersions.OrderByDescending(x => ParseVersion(x)).FirstOrDefault(); + + string[] sdkVersions = Directory.GetDirectories($"{dotnetPath}sdk/").Select(x => Path.GetFileName(x)).ToArray(); + string dotnetSdkVersion = sdkVersions.OrderByDescending(x => ParseVersion(x)).FirstOrDefault(); + + int majorVersion = int.Parse(dotnetSdkVersion.Substring(0, dotnetSdkVersion.IndexOf("."))); + if (majorVersion >= 7) + { + cscPath = @$"{dotnetPath}sdk/{dotnetSdkVersion}/Roslyn/bincore/csc.dll"; + referenceAssemblies = @$"{dotnetPath}shared/Microsoft.NETCore.App/{dotnetSharedfxVersion}/"; + referenceAnalyzers = @$"{dotnetPath}packs/Microsoft.NETCore.App.Ref/{dotnetSharedfxVersion}/analyzers/dotnet/cs/"; + } + else +#endif + { + monoRoot = Path.Combine(Globals.EngineRoot, "Source", "Platforms", "Editor", "Linux", "Mono"); + monoPath = Path.Combine(monoRoot, "bin", "mono"); + cscPath = Path.Combine(monoRoot, "lib", "mono", "4.5", "csc.exe"); + referenceAssemblies = Path.Combine(monoRoot, "lib", "mono", "4.5-api"); + referenceAnalyzers = ""; + } break; + } case TargetPlatform.Mac: monoRoot = Path.Combine(Globals.EngineRoot, "Source", "Platforms", "Editor", "Mac", "Mono"); monoPath = Path.Combine(monoRoot, "bin", "mono"); cscPath = Path.Combine(monoRoot, "lib", "mono", "4.5", "csc.exe"); + referenceAssemblies = Path.Combine(monoRoot, "lib", "mono", "4.5-api"); + referenceAnalyzers = ""; break; default: throw new InvalidPlatformException(buildPlatform); } - var referenceAssemblies = Path.Combine(monoRoot, "lib", "mono", "4.5-api"); + if (fileReferences == null) fileReferences = buildOptions.ScriptingAPI.FileReferences; else @@ -206,21 +274,36 @@ namespace Flax.Build args.Add("/warn:4"); args.Add("/unsafe"); args.Add("/fullpaths"); + args.Add("/filealign:512"); +#if USE_NETCORE + args.Add("/langversion:latest"); + args.Add("-nowarn:8632"); // Nullable +#else args.Add("/langversion:7.3"); +#endif if (buildOptions.ScriptingAPI.IgnoreMissingDocumentationWarnings) args.Add("-nowarn:1591"); +#if USE_NETCORE + // Optimizations prevent debugging, only enable in release builds + args.Add(buildData.Configuration == TargetConfiguration.Release ? "/optimize+" : "/optimize-"); +#else args.Add(buildData.Configuration == TargetConfiguration.Debug ? "/optimize-" : "/optimize+"); +#endif args.Add(string.Format("/out:\"{0}\"", outputFile)); args.Add(string.Format("/doc:\"{0}\"", outputDocFile)); if (buildOptions.ScriptingAPI.Defines.Count != 0) args.Add("/define:" + string.Join(";", buildOptions.ScriptingAPI.Defines)); if (buildData.Configuration == TargetConfiguration.Debug) args.Add("/define:DEBUG"); - args.Add(string.Format("/reference:\"{0}{1}mscorlib.dll\"", referenceAssemblies, Path.DirectorySeparatorChar)); + args.Add(string.Format("/reference:\"{0}mscorlib.dll\"", referenceAssemblies)); foreach (var reference in buildOptions.ScriptingAPI.SystemReferences) - args.Add(string.Format("/reference:\"{0}{2}{1}.dll\"", referenceAssemblies, reference, Path.DirectorySeparatorChar)); + args.Add(string.Format("/reference:\"{0}{1}.dll\"", referenceAssemblies, reference)); foreach (var reference in fileReferences) args.Add(string.Format("/reference:\"{0}\"", reference)); +#if USE_NETCORE + foreach (var analyzer in buildOptions.ScriptingAPI.SystemAnalyzers) + args.Add(string.Format("/analyzer:\"{0}{1}.dll\"", referenceAnalyzers, analyzer)); +#endif foreach (var sourceFile in sourceFiles) args.Add("\"" + sourceFile + "\""); @@ -248,8 +331,13 @@ namespace Flax.Build // The "/shared" flag enables the compiler server support: // https://github.com/dotnet/roslyn/blob/main/docs/compilers/Compiler%20Server.md +#if USE_NETCORE + task.CommandPath = "dotnet"; + task.CommandArguments = $"exec \"{cscPath}\" /noconfig /shared @\"{responseFile}\""; +#else task.CommandPath = cscPath; task.CommandArguments = $"/noconfig /shared @\"{responseFile}\""; +#endif } BuildDotNetAssembly?.Invoke(graph, buildData, buildOptions, task, binaryModule); diff --git a/Source/Tools/Flax.Build/Build/EngineTarget.cs b/Source/Tools/Flax.Build/Build/EngineTarget.cs index 98c87358f..0e5fee7fe 100644 --- a/Source/Tools/Flax.Build/Build/EngineTarget.cs +++ b/Source/Tools/Flax.Build/Build/EngineTarget.cs @@ -4,6 +4,7 @@ using System; using System.IO; using Flax.Build.Graph; using Flax.Build.NativeCpp; +using Task = Flax.Build.Graph.Task; namespace Flax.Build { diff --git a/Source/Tools/Flax.Build/Build/Graph/LocalExecutor.cs b/Source/Tools/Flax.Build/Build/Graph/LocalExecutor.cs index 20aa2e502..7a5b61e01 100644 --- a/Source/Tools/Flax.Build/Build/Graph/LocalExecutor.cs +++ b/Source/Tools/Flax.Build/Build/Graph/LocalExecutor.cs @@ -6,6 +6,7 @@ using System.Diagnostics; using System.IO; using System.Threading; using Flax.Build.Graph; +using Task = Flax.Build.Graph.Task; namespace Flax.Build.BuildSystem.Graph { diff --git a/Source/Tools/Flax.Build/Build/Graph/TaskGraph.cs b/Source/Tools/Flax.Build/Build/Graph/TaskGraph.cs index 9d307cebd..e4add39e9 100644 --- a/Source/Tools/Flax.Build/Build/Graph/TaskGraph.cs +++ b/Source/Tools/Flax.Build/Build/Graph/TaskGraph.cs @@ -87,7 +87,7 @@ namespace Flax.Build.Graph else { task.CommandPath = "cp"; - task.CommandArguments = string.Format("\'{0}\' \'{1}\'", srcFile, outputPath); + task.CommandArguments = string.Format("\"{0}\" \"{1}\"", srcFile, outputPath); } Tasks.Add(task); diff --git a/Source/Tools/Flax.Build/Build/NativeCpp/BuildOptions.cs b/Source/Tools/Flax.Build/Build/NativeCpp/BuildOptions.cs index 6cad222ba..c759719c3 100644 --- a/Source/Tools/Flax.Build/Build/NativeCpp/BuildOptions.cs +++ b/Source/Tools/Flax.Build/Build/NativeCpp/BuildOptions.cs @@ -156,6 +156,11 @@ namespace Flax.Build.NativeCpp /// public HashSet FileReferences; + /// + /// The .Net libraries references (dll or exe files paths). + /// + public HashSet SystemAnalyzers; + /// /// True if ignore compilation warnings due to missing code documentation comments. /// @@ -182,9 +187,53 @@ namespace Flax.Build.NativeCpp Defines = new HashSet(), SystemReferences = new HashSet { + "Microsoft.CSharp", "System", - "System.Xml", + + "System.Collections", + "System.Collections.Concurrent", + "System.Collections.NonGeneric", + "System.Collections.Specialized", + "System.Collections.Immutable", + "System.ComponentModel", + "System.ComponentModel.DataAnnotations", + "System.ComponentModel.Primitives", + "System.ComponentModel.TypeConverter", + "System.Console", "System.Core", + "System.Globalization", + "System.IO", + "System.IO.Compression", + "System.IO.FileSystem.Watcher", + "System.Linq", + "System.Linq.Expressions", + "System.Net.Http", + "System.Net.Primitives", + "System.ObjectModel", + "System.Private.CoreLib", + "System.Private.Uri", + "System.Private.Xml", + + "System.Reflection", + "System.Runtime", + "System.Runtime.CompilerServices.Unsafe", + "System.Runtime.InteropServices", + "System.Runtime.InteropServices.RuntimeInformation", + "System.Runtime.Serialization.Formatters", // BinaryFormatter + "System.Security.Cryptography", + "System.Security.Cryptography.Algorithms", + "System.Security.Cryptography.Primitives", + "System.Text.RegularExpressions", + "System.Threading.Tasks.Parallel", + "System.Xml", + + "System.Reflection.Metadata", + "netstandard", + }, + SystemAnalyzers = new HashSet + { + @"Microsoft.Interop.LibraryImportGenerator", + @"Microsoft.Interop.SourceGeneration", }, FileReferences = new HashSet(), }; diff --git a/Source/Tools/Flax.Build/Build/Plugins/NetworkingPlugin.cs b/Source/Tools/Flax.Build/Build/Plugins/NetworkingPlugin.cs index 236ac1113..f034817f1 100644 --- a/Source/Tools/Flax.Build/Build/Plugins/NetworkingPlugin.cs +++ b/Source/Tools/Flax.Build/Build/Plugins/NetworkingPlugin.cs @@ -9,6 +9,7 @@ using Flax.Build.Graph; using Flax.Build.Bindings; using Mono.Cecil; using Mono.Cecil.Cil; +using Task = Flax.Build.Graph.Task; namespace Flax.Build.Plugins { diff --git a/Source/Tools/Flax.Build/Build/Target.cs b/Source/Tools/Flax.Build/Build/Target.cs index d78b6ade0..535217dc3 100644 --- a/Source/Tools/Flax.Build/Build/Target.cs +++ b/Source/Tools/Flax.Build/Build/Target.cs @@ -19,9 +19,14 @@ namespace Flax.Build NativeCpp, /// - /// The C# project. + /// The C# Mono project. /// DotNet, + + /// + /// The C# .NET SDK project. + /// + DotNetCore, } /// diff --git a/Source/Tools/Flax.Build/Configuration.cs b/Source/Tools/Flax.Build/Configuration.cs index 4c3f63b0c..33b40cf05 100644 --- a/Source/Tools/Flax.Build/Configuration.cs +++ b/Source/Tools/Flax.Build/Configuration.cs @@ -242,6 +242,12 @@ namespace Flax.Build [CommandLine("useCSharp", "0 to disable C# support in build")] public static bool UseCSharp = true; + /// + /// True if .NET support should be enabled. + /// + [CommandLine("useDotNet", "1 to enable .NET support in build, 0 to enable Mono support in build")] + public static bool UseDotNet = true; + public static bool WithCSharp(NativeCpp.BuildOptions options) { if (options.Platform.Target == TargetPlatform.PS5) @@ -254,5 +260,10 @@ namespace Flax.Build // This can be used to selectively control 64-bit coordinates per-platform or build configuration return UseLargeWorlds; } + + public static bool WithDotNet(NativeCpp.BuildOptions options) + { + return UseDotNet; + } } } diff --git a/Source/Tools/Flax.Build/Deploy/VCEnvironment.cs b/Source/Tools/Flax.Build/Deploy/VCEnvironment.cs index 0ca955324..5a0d96b27 100644 --- a/Source/Tools/Flax.Build/Deploy/VCEnvironment.cs +++ b/Source/Tools/Flax.Build/Deploy/VCEnvironment.cs @@ -9,6 +9,8 @@ using Flax.Build.Platforms; using Flax.Build.Projects.VisualStudio; using Microsoft.Win32; +#pragma warning disable CA1416 + namespace Flax.Deploy { /// @@ -286,3 +288,5 @@ namespace Flax.Deploy } } } + +#pragma warning restore CA1416 diff --git a/Source/Tools/Flax.Build/Flax.Build.Build.cs b/Source/Tools/Flax.Build/Flax.Build.Build.cs index b5d2b8fcd..9d77f4707 100644 --- a/Source/Tools/Flax.Build/Flax.Build.Build.cs +++ b/Source/Tools/Flax.Build/Flax.Build.Build.cs @@ -19,7 +19,7 @@ public class FlaxBuildTarget : Target base.Init(); IsPreBuilt = false; - Type = TargetType.DotNet; + Type = TargetType.DotNetCore; OutputType = TargetOutputType.Library; Platforms = new[] { diff --git a/Source/Tools/Flax.Build/Flax.Build.csproj b/Source/Tools/Flax.Build/Flax.Build.csproj index e91be5b0c..a03d701c2 100644 --- a/Source/Tools/Flax.Build/Flax.Build.csproj +++ b/Source/Tools/Flax.Build/Flax.Build.csproj @@ -1,189 +1,37 @@ - - - + - Debug - AnyCPU - {C99AAF92-D4AD-4847-9EE0-B11E68E93E1E} Exe - Flax.Build - Flax.Build - v4.5.2 - 7.3 - 512 - true - + net7.0 + enable + disable + Debug;Release + ..\..\..\Binaries\Tools + ..\..\..\Binaries\Tools + false + true + USE_NETCORE - - AnyCPU - true - full - false - false - ..\..\..\Binaries\Tools\ - ..\..\..\Binaries\Tools\Flax.Build.xml - ..\..\..\Cache\Intermediate\Flax.Build\Debug - DEBUG;TRACE - prompt - 4 - true + + true - 1591 - - AnyCPU - pdbonly - true - false - ..\..\..\Binaries\Tools\ - ..\..\..\Binaries\Tools\Flax.Build.xml - ..\..\..\Cache\Intermediate\Flax.Build\Release - TRACE - prompt - 4 - true + + true - 1591 + - - ..\..\..\Source\Platforms\DotNet\Ionic.Zip.Reduced.dll - - - ..\..\..\Source\Platforms\DotNet\Mono.Cecil.dll - - - ..\..\..\Source\Platforms\DotNet\Microsoft.VisualStudio.Setup.Configuration.Interop.dll - True - - - False - ..\..\..\Source\Platforms\DotNet\Newtonsoft.Json.dll - - - - - + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + - - - \ No newline at end of file + + diff --git a/Source/Tools/Flax.Build/Platforms/Android/AndroidToolchain.cs b/Source/Tools/Flax.Build/Platforms/Android/AndroidToolchain.cs index 2c8016a2a..3d5627c1e 100644 --- a/Source/Tools/Flax.Build/Platforms/Android/AndroidToolchain.cs +++ b/Source/Tools/Flax.Build/Platforms/Android/AndroidToolchain.cs @@ -5,6 +5,7 @@ using System.Collections.Generic; using System.IO; using Flax.Build.Graph; using Flax.Build.NativeCpp; +using Task = Flax.Build.Graph.Task; namespace Flax.Build { diff --git a/Source/Tools/Flax.Build/Platforms/Linux/LinuxToolchain.cs b/Source/Tools/Flax.Build/Platforms/Linux/LinuxToolchain.cs index 2904b89a7..4170651f4 100644 --- a/Source/Tools/Flax.Build/Platforms/Linux/LinuxToolchain.cs +++ b/Source/Tools/Flax.Build/Platforms/Linux/LinuxToolchain.cs @@ -4,6 +4,7 @@ using System.Collections.Generic; using System.IO; using Flax.Build.Graph; using Flax.Build.NativeCpp; +using Task = Flax.Build.Graph.Task; namespace Flax.Build.Platforms { diff --git a/Source/Tools/Flax.Build/Platforms/Mac/MacToolchain.cs b/Source/Tools/Flax.Build/Platforms/Mac/MacToolchain.cs index 5c934ad0d..c7cf4ccd8 100644 --- a/Source/Tools/Flax.Build/Platforms/Mac/MacToolchain.cs +++ b/Source/Tools/Flax.Build/Platforms/Mac/MacToolchain.cs @@ -5,6 +5,7 @@ using System.IO; using System.Collections.Generic; using Flax.Build.Graph; using Flax.Build.NativeCpp; +using Task = Flax.Build.Graph.Task; namespace Flax.Build { diff --git a/Source/Tools/Flax.Build/Platforms/Unix/UnixPlatform.cs b/Source/Tools/Flax.Build/Platforms/Unix/UnixPlatform.cs index 62180306c..e71dd1947 100644 --- a/Source/Tools/Flax.Build/Platforms/Unix/UnixPlatform.cs +++ b/Source/Tools/Flax.Build/Platforms/Unix/UnixPlatform.cs @@ -54,7 +54,7 @@ namespace Flax.Build.Platforms StartInfo = { FileName = "/bin/sh", - Arguments = string.Format("-c 'which {0}'", name), + ArgumentList = { "-c", $"which {name}" }, UseShellExecute = false, CreateNoWindow = true, RedirectStandardOutput = true, diff --git a/Source/Tools/Flax.Build/Platforms/Unix/UnixToolchain.cs b/Source/Tools/Flax.Build/Platforms/Unix/UnixToolchain.cs index 5418575c6..1fe421470 100644 --- a/Source/Tools/Flax.Build/Platforms/Unix/UnixToolchain.cs +++ b/Source/Tools/Flax.Build/Platforms/Unix/UnixToolchain.cs @@ -7,6 +7,7 @@ using System.IO; using System.Text.RegularExpressions; using Flax.Build.Graph; using Flax.Build.NativeCpp; +using Task = Flax.Build.Graph.Task; namespace Flax.Build.Platforms { diff --git a/Source/Tools/Flax.Build/Platforms/Windows/WindowsPlatformBase.cs b/Source/Tools/Flax.Build/Platforms/Windows/WindowsPlatformBase.cs index 2673ec817..2416a614a 100644 --- a/Source/Tools/Flax.Build/Platforms/Windows/WindowsPlatformBase.cs +++ b/Source/Tools/Flax.Build/Platforms/Windows/WindowsPlatformBase.cs @@ -11,6 +11,8 @@ using Flax.Build.Projects.VisualStudio; using Flax.Build.Projects.VisualStudioCode; using Microsoft.Win32; +#pragma warning disable CA1416 + namespace Flax.Build.Platforms { /// @@ -523,3 +525,5 @@ namespace Flax.Build.Platforms } } } + +#pragma warning restore CA1416 diff --git a/Source/Tools/Flax.Build/Platforms/Windows/WindowsToolchainBase.cs b/Source/Tools/Flax.Build/Platforms/Windows/WindowsToolchainBase.cs index 3919fcafa..e6eea8639 100644 --- a/Source/Tools/Flax.Build/Platforms/Windows/WindowsToolchainBase.cs +++ b/Source/Tools/Flax.Build/Platforms/Windows/WindowsToolchainBase.cs @@ -9,6 +9,7 @@ using System.Xml; using Flax.Build.Graph; using Flax.Build.NativeCpp; using Flax.Build.Projects.VisualStudio; +using Task = Flax.Build.Graph.Task; // ReSharper disable InconsistentNaming diff --git a/Source/Tools/Flax.Build/ProjectInfo.cs b/Source/Tools/Flax.Build/ProjectInfo.cs index 803f8b5dd..50aa94698 100644 --- a/Source/Tools/Flax.Build/ProjectInfo.cs +++ b/Source/Tools/Flax.Build/ProjectInfo.cs @@ -8,6 +8,107 @@ using Newtonsoft.Json; namespace Flax.Build { + public class FlaxVersionConverter : JsonConverter + { + /// + /// 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 +255,7 @@ namespace Flax.Build /// public void Save() { - var contents = JsonConvert.SerializeObject(this); + var contents = JsonConvert.SerializeObject(this, new JsonSerializerSettings() { Converters = new[] { new FlaxVersionConverter() } }); File.WriteAllText(ProjectPath, contents); } @@ -180,7 +281,7 @@ namespace Flax.Build // Load Log.Verbose("Loading project file from \"" + path + "\"..."); 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 = Path.GetDirectoryName(path); diff --git a/Source/Tools/Flax.Build/Projects/ProjectGenerator.cs b/Source/Tools/Flax.Build/Projects/ProjectGenerator.cs index cb0e5311e..a9fb2b309 100644 --- a/Source/Tools/Flax.Build/Projects/ProjectGenerator.cs +++ b/Source/Tools/Flax.Build/Projects/ProjectGenerator.cs @@ -135,6 +135,7 @@ namespace Flax.Build.Projects { case TargetType.NativeCpp: return new VCProjectGenerator(vsVersion); case TargetType.DotNet: return new CSProjectGenerator(vsVersion); + case TargetType.DotNetCore: return new CSSDKProjectGenerator(vsVersion); default: throw new ArgumentOutOfRangeException(nameof(type), type, null); } } diff --git a/Source/Tools/Flax.Build/Projects/VisualStudio/CSSDKProjectGenerator.cs b/Source/Tools/Flax.Build/Projects/VisualStudio/CSSDKProjectGenerator.cs new file mode 100644 index 000000000..f864d2778 --- /dev/null +++ b/Source/Tools/Flax.Build/Projects/VisualStudio/CSSDKProjectGenerator.cs @@ -0,0 +1,258 @@ +// Copyright (c) 2012-2022 Wojciech Figat. All rights reserved. + +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; + +namespace Flax.Build.Projects.VisualStudio +{ + /// + /// The Visual Studio project generator for C# projects (.NET SDK .csproj). + /// + /// + public class CSSDKProjectGenerator : VisualStudioProjectGenerator + { + /// + public CSSDKProjectGenerator(VisualStudioVersion version) : base(version) + { + } + + /// + public override string ProjectFileExtension => "csproj"; + + /// + public override TargetType? Type => TargetType.DotNetCore; + + /// + public override void GenerateProject(Project project) + { + var csProjectFileContent = new StringBuilder(); + + var vsProject = (VisualStudioProject)project; + var projectFileToolVersion = ProjectFileToolVersion; + var projectDirectory = Path.GetDirectoryName(project.Path); + var defaultTarget = project.Targets[0]; + foreach (var target in project.Targets) + { + // Pick the Editor-related target + if (target.IsEditor) + { + defaultTarget = target; + break; + } + } + var defaultConfiguration = TargetConfiguration.Debug; + var defaultArchitecture = TargetArchitecture.AnyCPU; + var projectTypes = ProjectTypeGuids.ToOption(ProjectTypeGuids.WindowsCSharp); + if (vsProject.CSharp.UseFlaxVS && VisualStudioInstance.HasFlaxVS) + projectTypes = ProjectTypeGuids.ToOption(ProjectTypeGuids.FlaxVS) + ';' + projectTypes; + + // Header + csProjectFileContent.AppendLine(""); + csProjectFileContent.AppendLine(""); + + //csProjectFileContent.AppendLine(string.Format("", projectFileToolVersion)); + //csProjectFileContent.AppendLine(" "); + + // Properties + + csProjectFileContent.AppendLine(" "); + + switch (project.OutputType ?? defaultTarget.OutputType) + { + case TargetOutputType.Executable: + csProjectFileContent.AppendLine(" Exe"); + break; + case TargetOutputType.Library: + csProjectFileContent.AppendLine(" Library"); + break; + default: throw new ArgumentOutOfRangeException(); + } + + var baseConfiguration = project.Configurations.First(); + var baseOutputDir = Utilities.MakePathRelativeTo(project.CSharp.OutputPath ?? baseConfiguration.TargetBuildOptions.OutputFolder, projectDirectory); + var baseIntermediateOutputPath = Utilities.MakePathRelativeTo(project.CSharp.IntermediateOutputPath ?? Path.Combine(baseConfiguration.TargetBuildOptions.IntermediateFolder, "CSharp"), projectDirectory); + var baseConfigurations = project.Configurations.Select(x => x.Name.Split('|')[0]).Distinct().ToArray(); + + csProjectFileContent.AppendLine(" net7.0"); + csProjectFileContent.AppendLine(" enable"); + csProjectFileContent.AppendLine(" disable"); + csProjectFileContent.AppendLine(string.Format(" {0}", string.Join(";", baseConfigurations))); + csProjectFileContent.AppendLine(" false"); // ? + csProjectFileContent.AppendLine(" true"); // Needed for Hostfxr + csProjectFileContent.AppendLine(" true"); // ? + csProjectFileContent.AppendLine(" false"); // Prevents outputting the file under net7.0 subdirectory + csProjectFileContent.AppendLine(" $(MSBuildProjectName).CSharp"); // For backwards compatibility, keep the filename same + csProjectFileContent.AppendLine(" false"); // Prevents AssemblyInfo.cs generation (causes duplicate attributes) + csProjectFileContent.AppendLine(" false"); + csProjectFileContent.AppendLine(string.Format(" {0}", baseOutputDir)); // This needs to be set here to fix errors in VS + csProjectFileContent.AppendLine(string.Format(" {0}", baseIntermediateOutputPath)); // This needs to be set here to fix errors in VS + + csProjectFileContent.AppendLine(" "); + csProjectFileContent.AppendLine(""); + + // Default configuration + { + var configuration = project.Configurations.First(); + foreach (var e in project.Configurations) + { + if (e.Configuration == defaultConfiguration && e.Target == defaultTarget && e.Platform == Platform.BuildTargetPlatform) + { + configuration = e; + break; + } + } + var defines = string.Join(";", project.Defines); + if (configuration.TargetBuildOptions.ScriptingAPI.Defines.Count != 0) + { + if (defines.Length != 0) + defines += ";"; + defines += string.Join(";", configuration.TargetBuildOptions.ScriptingAPI.Defines); + } + var outputPath = Utilities.MakePathRelativeTo(project.CSharp.OutputPath ?? configuration.TargetBuildOptions.OutputFolder, projectDirectory); + var intermediateOutputPath = Utilities.MakePathRelativeTo(project.CSharp.IntermediateOutputPath ?? Path.Combine(configuration.TargetBuildOptions.IntermediateFolder, "CSharp"), projectDirectory); + + csProjectFileContent.AppendLine(string.Format(" ", defaultConfiguration, defaultArchitecture)); + csProjectFileContent.AppendLine(" true"); + csProjectFileContent.AppendLine(" portable"); + csProjectFileContent.AppendLine(string.Format(" {0}", defaultConfiguration == TargetConfiguration.Debug ? "false" : "true")); + csProjectFileContent.AppendLine(string.Format(" {0}\\", outputPath)); + csProjectFileContent.AppendLine(string.Format(" {0}\\", intermediateOutputPath)); + csProjectFileContent.AppendLine(string.Format(" {0}\\", intermediateOutputPath)); + csProjectFileContent.AppendLine(string.Format(" {0}", defines)); + csProjectFileContent.AppendLine(" prompt"); + csProjectFileContent.AppendLine(" 4"); + csProjectFileContent.AppendLine(" true"); + if (configuration.TargetBuildOptions.ScriptingAPI.IgnoreMissingDocumentationWarnings) + csProjectFileContent.AppendLine(" 1591"); + csProjectFileContent.AppendLine(string.Format(" {0}\\{1}.CSharp.xml", outputPath, project.Name)); + csProjectFileContent.AppendLine(" true"); + csProjectFileContent.AppendLine(" "); + } + + // Configurations + foreach (var configuration in project.Configurations) + { + var defines = string.Join(";", project.Defines); + if (configuration.TargetBuildOptions.ScriptingAPI.Defines.Count != 0) + { + if (defines.Length != 0) + defines += ";"; + defines += string.Join(";", configuration.TargetBuildOptions.ScriptingAPI.Defines); + } + var outputPath = Utilities.MakePathRelativeTo(project.CSharp.OutputPath ?? configuration.TargetBuildOptions.OutputFolder, projectDirectory); + var intermediateOutputPath = Utilities.MakePathRelativeTo(project.CSharp.IntermediateOutputPath ?? Path.Combine(configuration.TargetBuildOptions.IntermediateFolder, "CSharp"), projectDirectory); + + csProjectFileContent.AppendLine(string.Format(" ", configuration.Name, configuration.Name.Replace(configuration.ArchitectureName, "AnyCPU"))); + csProjectFileContent.AppendLine(" true"); + csProjectFileContent.AppendLine(" portable"); + csProjectFileContent.AppendLine(string.Format(" {0}", configuration.Configuration == TargetConfiguration.Release ? "true" : "false")); + csProjectFileContent.AppendLine(string.Format(" {0}\\", outputPath)); + csProjectFileContent.AppendLine(string.Format(" {0}\\", intermediateOutputPath)); + csProjectFileContent.AppendLine(string.Format(" {0}\\", intermediateOutputPath)); + csProjectFileContent.AppendLine(string.Format(" {0}", defines)); + csProjectFileContent.AppendLine(" prompt"); + csProjectFileContent.AppendLine(" 4"); + csProjectFileContent.AppendLine(" true"); + if (configuration.TargetBuildOptions.ScriptingAPI.IgnoreMissingDocumentationWarnings) + csProjectFileContent.AppendLine(" 1591"); + csProjectFileContent.AppendLine(string.Format(" {0}\\{1}.CSharp.xml", outputPath, project.Name)); + csProjectFileContent.AppendLine(" true"); + csProjectFileContent.AppendLine(" "); + } + + // Nuget Package References + // TODO: Support custom Nuget package references + csProjectFileContent.AppendLine(" "); + csProjectFileContent.AppendLine(" "); + csProjectFileContent.AppendLine(" "); + csProjectFileContent.AppendLine(" "); + csProjectFileContent.AppendLine(" "); + csProjectFileContent.AppendLine(" "); + csProjectFileContent.AppendLine(" "); + csProjectFileContent.AppendLine(" "); + + // References + + csProjectFileContent.AppendLine(" "); + + foreach (var reference in project.CSharp.SystemReferences) + { + csProjectFileContent.AppendLine(string.Format(" ", reference)); + } + + foreach (var reference in project.CSharp.FileReferences) + { + csProjectFileContent.AppendLine(string.Format(" ", Path.GetFileNameWithoutExtension(reference))); + csProjectFileContent.AppendLine(string.Format(" {0}", Utilities.MakePathRelativeTo(reference, projectDirectory))); + csProjectFileContent.AppendLine(" "); + } + + foreach (var dependency in project.Dependencies) + { + csProjectFileContent.AppendLine(string.Format(" ", Utilities.MakePathRelativeTo(dependency.Path, projectDirectory))); + csProjectFileContent.AppendLine(string.Format(" {0}", ((VisualStudioProject)dependency).ProjectGuid.ToString("B").ToUpperInvariant())); + csProjectFileContent.AppendLine(string.Format(" {0}", dependency.Name)); + csProjectFileContent.AppendLine(" "); + } + + csProjectFileContent.AppendLine(" "); + + // Files and folders + + csProjectFileContent.AppendLine(" "); + + var files = new List(); + if (project.SourceFiles != null) + files.AddRange(project.SourceFiles); + if (project.SourceDirectories != null) + { + foreach (var folder in project.SourceDirectories) + { + files.AddRange(Directory.GetFiles(folder, "*", SearchOption.AllDirectories)); + } + } + + foreach (var file in files) + { + string fileType; + if (file.EndsWith(".cs", StringComparison.OrdinalIgnoreCase)) + fileType = "Compile"; + else + fileType = "None"; + + var projectPath = Utilities.MakePathRelativeTo(file, projectDirectory); + csProjectFileContent.AppendLine(string.Format(" <{0} Include=\"{1}\" />", fileType, projectPath)); + } + + if (project.GeneratedSourceFiles != null) + { + foreach (var file in project.GeneratedSourceFiles) + { + string fileType; + if (file.EndsWith(".cs", StringComparison.OrdinalIgnoreCase)) + fileType = "Compile"; + else + fileType = "None"; + + csProjectFileContent.AppendLine(string.Format(" <{0} Visible=\"false\" Include=\"{1}\" />", fileType, file)); + } + } + + csProjectFileContent.AppendLine(" "); + + // End + + //csProjectFileContent.AppendLine(" "); + csProjectFileContent.AppendLine(""); + + if (defaultTarget.CustomExternalProjectFilePath == null) + { + // Save the files + Utilities.WriteFileIfChanged(project.Path, csProjectFileContent.ToString()); + } + } + } +} diff --git a/Source/Tools/Flax.Build/Projects/VisualStudio/VCProjectGenerator.cs b/Source/Tools/Flax.Build/Projects/VisualStudio/VCProjectGenerator.cs index 42f6095c3..440ec7c7b 100644 --- a/Source/Tools/Flax.Build/Projects/VisualStudio/VCProjectGenerator.cs +++ b/Source/Tools/Flax.Build/Projects/VisualStudio/VCProjectGenerator.cs @@ -103,6 +103,7 @@ namespace Flax.Build.Projects.VisualStudio vcProjectFileContent.AppendLine(" Native"); vcProjectFileContent.AppendLine(" Unicode"); vcProjectFileContent.AppendLine(" MakeFileProj"); + vcProjectFileContent.AppendLine(" false"); vcProjectFileContent.AppendLine(" "); // Default properties @@ -133,7 +134,7 @@ namespace Flax.Build.Projects.VisualStudio vcProjectFileContent.AppendLine(" "); // Per configuration options - var buildToolPath = Utilities.MakePathRelativeTo(typeof(Builder).Assembly.Location, projectDirectory); + var buildToolPath = Path.ChangeExtension(Utilities.MakePathRelativeTo(typeof(Builder).Assembly.Location, projectDirectory), null); var preprocessorDefinitions = new HashSet(); var includePaths = new HashSet(); foreach (var configuration in project.Configurations) diff --git a/Source/Tools/Flax.Build/Projects/VisualStudio/VisualStudioInstance.cs b/Source/Tools/Flax.Build/Projects/VisualStudio/VisualStudioInstance.cs index b719fc17f..28e11c124 100644 --- a/Source/Tools/Flax.Build/Projects/VisualStudio/VisualStudioInstance.cs +++ b/Source/Tools/Flax.Build/Projects/VisualStudio/VisualStudioInstance.cs @@ -6,6 +6,7 @@ using System.IO; using System.Linq; using Flax.Build.Platforms; using Microsoft.VisualStudio.Setup.Configuration; +using Task = Flax.Build.Graph.Task; namespace Flax.Build.Projects.VisualStudio { diff --git a/Source/Tools/Flax.Build/Projects/VisualStudio/VisualStudioProject.cs b/Source/Tools/Flax.Build/Projects/VisualStudio/VisualStudioProject.cs index 816155f75..f546d71a4 100644 --- a/Source/Tools/Flax.Build/Projects/VisualStudio/VisualStudioProject.cs +++ b/Source/Tools/Flax.Build/Projects/VisualStudio/VisualStudioProject.cs @@ -31,6 +31,7 @@ namespace Flax.Build.Projects.VisualStudio { case TargetType.NativeCpp: return VisualStudioProjectGenerator.ProjectTypeGuids.WindowsVisualCpp; case TargetType.DotNet: return VisualStudioProjectGenerator.ProjectTypeGuids.WindowsCSharp; + case TargetType.DotNetCore: return VisualStudioProjectGenerator.ProjectTypeGuids.WindowsCSharp; default: throw new ArgumentOutOfRangeException(); } } diff --git a/Source/Tools/Flax.Build/Projects/VisualStudioCode/VisualStudioCodeProjectGenerator.cs b/Source/Tools/Flax.Build/Projects/VisualStudioCode/VisualStudioCodeProjectGenerator.cs index 930915a79..c97731007 100644 --- a/Source/Tools/Flax.Build/Projects/VisualStudioCode/VisualStudioCodeProjectGenerator.cs +++ b/Source/Tools/Flax.Build/Projects/VisualStudioCode/VisualStudioCodeProjectGenerator.cs @@ -428,7 +428,7 @@ namespace Flax.Build.Projects.VisualStudioCode } } // C# project - else if (project.Type == TargetType.DotNet) + else if (project.Type == TargetType.DotNet || project.Type == TargetType.DotNetCore) { foreach (var configuration in project.Configurations) { diff --git a/Source/Tools/Flax.Build/Properties/AssemblyInfo.cs b/Source/Tools/Flax.Build/Properties/AssemblyInfo.cs deleted file mode 100644 index 48e41e772..000000000 --- a/Source/Tools/Flax.Build/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,17 +0,0 @@ -// Copyright (c) 2012-2022 Wojciech Figat. All rights reserved. - -using System.Reflection; -using System.Runtime.InteropServices; - -[assembly: AssemblyTitle("Flax.Build")] -[assembly: AssemblyDescription("")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("Wojciech Figat")] -[assembly: AssemblyProduct("Flax.Build")] -[assembly: AssemblyCopyright("Copyright © 2012-2022 Wojciech Figat")] -[assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("")] -[assembly: ComVisible(false)] -[assembly: Guid("c99aaf92-d4ad-4847-9ee0-b11e68e93e1e")] -[assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/Source/Tools/Flax.Build/Utilities/Utilities.cs b/Source/Tools/Flax.Build/Utilities/Utilities.cs index a9a97bac0..b61019af5 100644 --- a/Source/Tools/Flax.Build/Utilities/Utilities.cs +++ b/Source/Tools/Flax.Build/Utilities/Utilities.cs @@ -41,7 +41,11 @@ namespace Flax.Build /// The empty array object. public static T[] GetEmptyArray() { +#if USE_NETCORE + return Array.Empty(); +#else return Enumerable.Empty() as T[]; +#endif } ///