diff --git a/GenerateProjectFiles.bat b/GenerateProjectFiles.bat index 10a06ab94..71dba41d1 100644 --- a/GenerateProjectFiles.bat +++ b/GenerateProjectFiles.bat @@ -15,7 +15,7 @@ if errorlevel 1 goto BuildToolFailed :: Build bindings for all editor configurations echo Building C# bindings... -Binaries\Tools\Flax.Build.exe -build -BuildBindingsOnly -arch=x64 -platform=Windows --buildTargets=FlaxEditor +Binaries\Tools\Flax.Build.exe -build -BuildBindingsOnly -arch=x64 -platform=Windows --buildTargets=FlaxEditor,FlaxGame popd echo Done! diff --git a/Source/Editor/Managed/ManagedEditor.cpp b/Source/Editor/Managed/ManagedEditor.cpp index b4178fb89..93d1a58d3 100644 --- a/Source/Editor/Managed/ManagedEditor.cpp +++ b/Source/Editor/Managed/ManagedEditor.cpp @@ -334,7 +334,7 @@ Window* ManagedEditor::GetMainWindow() ASSERT(HasManagedInstance()); const auto method = GetClass()->GetMethod("GetMainWindowPtr"); ASSERT(method); - return (Window*)MUtils::Unbox(method->Invoke(GetManagedInstance(), nullptr, nullptr)); + return (Window*)MUtils::Unbox(method->Invoke(GetManagedInstance(), nullptr, nullptr), true); } bool ManagedEditor::CanReloadScripts() @@ -346,7 +346,7 @@ bool ManagedEditor::CanReloadScripts() Internal_CanReloadScripts = GetClass()->GetMethod("Internal_CanReloadScripts"); ASSERT(Internal_CanReloadScripts); } - return MUtils::Unbox(Internal_CanReloadScripts->Invoke(GetManagedInstance(), nullptr, nullptr)); + return MUtils::Unbox(Internal_CanReloadScripts->Invoke(GetManagedInstance(), nullptr, nullptr), true); } bool ManagedEditor::CanAutoBuildCSG() @@ -365,7 +365,7 @@ bool ManagedEditor::CanAutoBuildCSG() Internal_CanAutoBuildCSG = GetClass()->GetMethod("Internal_CanAutoBuildCSG"); ASSERT(Internal_CanAutoBuildCSG); } - return MUtils::Unbox(Internal_CanAutoBuildCSG->Invoke(GetManagedInstance(), nullptr, nullptr)); + return MUtils::Unbox(Internal_CanAutoBuildCSG->Invoke(GetManagedInstance(), nullptr, nullptr), true); } bool ManagedEditor::CanAutoBuildNavMesh() @@ -384,7 +384,7 @@ bool ManagedEditor::CanAutoBuildNavMesh() Internal_CanAutoBuildNavMesh = GetClass()->GetMethod("Internal_CanAutoBuildNavMesh"); ASSERT(Internal_CanAutoBuildNavMesh); } - return MUtils::Unbox(Internal_CanAutoBuildNavMesh->Invoke(GetManagedInstance(), nullptr, nullptr)); + return MUtils::Unbox(Internal_CanAutoBuildNavMesh->Invoke(GetManagedInstance(), nullptr, nullptr), true); } bool ManagedEditor::HasGameViewportFocus() const @@ -397,7 +397,7 @@ bool ManagedEditor::HasGameViewportFocus() const Internal_HasGameViewportFocus = GetClass()->GetMethod("Internal_HasGameViewportFocus"); ASSERT(Internal_HasGameViewportFocus); } - result = MUtils::Unbox(Internal_HasGameViewportFocus->Invoke(GetManagedInstance(), nullptr, nullptr)); + result = MUtils::Unbox(Internal_HasGameViewportFocus->Invoke(GetManagedInstance(), nullptr, nullptr), true); } return result; } @@ -495,7 +495,7 @@ bool ManagedEditor::OnAppExit() Internal_OnAppExit = GetClass()->GetMethod("Internal_OnAppExit"); ASSERT(Internal_OnAppExit); } - return MUtils::Unbox(Internal_OnAppExit->Invoke(GetManagedInstance(), nullptr, nullptr)); + return MUtils::Unbox(Internal_OnAppExit->Invoke(GetManagedInstance(), nullptr, nullptr), true); } void ManagedEditor::RequestStartPlayOnEditMode() diff --git a/Source/Editor/Windows/PluginsWindow.cs b/Source/Editor/Windows/PluginsWindow.cs index 7545331ad..3e13f064d 100644 --- a/Source/Editor/Windows/PluginsWindow.cs +++ b/Source/Editor/Windows/PluginsWindow.cs @@ -12,6 +12,7 @@ using FlaxEditor.GUI; using FlaxEditor.GUI.ContextMenu; using FlaxEditor.GUI.Tabs; using FlaxEngine; +using FlaxEngine.Assertions; using FlaxEngine.GUI; using FlaxEngine.Json; @@ -972,6 +973,9 @@ namespace FlaxEditor.Windows _cloneProjectButton.Clicked -= OnCloneProjectButtonClicked; PluginManager.PluginsChanged -= OnPluginsChanged; + Assert.IsTrue(!_entries.Any()); + _entries.Clear(); + base.OnDestroy(); } } diff --git a/Source/Engine/AI/BehaviorKnowledge.cpp b/Source/Engine/AI/BehaviorKnowledge.cpp index 4d8e2eed9..405e6fe22 100644 --- a/Source/Engine/AI/BehaviorKnowledge.cpp +++ b/Source/Engine/AI/BehaviorKnowledge.cpp @@ -71,7 +71,7 @@ bool AccessVariant(Variant& instance, const StringAnsiView& member, Variant& val if (set) mField->SetValue(instanceObject, MUtils::VariantToManagedArgPtr(value, mField->GetType(), failed)); else - value = MUtils::UnboxVariant(mField->GetValueBoxed(instanceObject)); + value = MUtils::UnboxVariant(mField->GetValueBoxed(instanceObject), true); return !failed; } else if (const auto mProperty = mClass->GetProperty(member.Get())) @@ -79,7 +79,7 @@ bool AccessVariant(Variant& instance, const StringAnsiView& member, Variant& val if (set) mProperty->SetValue(instanceObject, MUtils::VariantToManagedArgPtr(value, mProperty->GetType(), failed), nullptr); else - value = MUtils::UnboxVariant(mProperty->GetValue(instanceObject, nullptr)); + value = MUtils::UnboxVariant(mProperty->GetValue(instanceObject, nullptr), true); return !failed; } } diff --git a/Source/Engine/Animations/Graph/AnimGraph.Custom.cpp b/Source/Engine/Animations/Graph/AnimGraph.Custom.cpp index 4c11f4481..c127e86f3 100644 --- a/Source/Engine/Animations/Graph/AnimGraph.Custom.cpp +++ b/Source/Engine/Animations/Graph/AnimGraph.Custom.cpp @@ -134,7 +134,7 @@ void AnimGraphExecutor::ProcessGroupCustom(Box* boxBase, Node* nodeBase, Value& } // Extract result - value = MUtils::UnboxVariant(result); + value = MUtils::UnboxVariant(result, true); context.ValueCache.Add(boxBase, value); #endif } diff --git a/Source/Engine/Engine/NativeInterop.Invoker.cs b/Source/Engine/Engine/NativeInterop.Invoker.cs index 4cf6ab31e..8c3cd4f6f 100644 --- a/Source/Engine/Engine/NativeInterop.Invoker.cs +++ b/Source/Engine/Engine/NativeInterop.Invoker.cs @@ -74,7 +74,7 @@ namespace FlaxEngine.Interop internal static IntPtr MarshalReturnValueString(ref string returnValue) { - return returnValue != null ? ManagedString.ToNativeWeak(returnValue) : IntPtr.Zero; + return returnValue != null ? ManagedString.ToNative/*Weak*/(returnValue) : IntPtr.Zero; } internal static IntPtr MarshalReturnValueManagedHandle(ref ManagedHandle returnValue) @@ -157,7 +157,7 @@ namespace FlaxEngine.Interop if (returnObject == null) return IntPtr.Zero; if (returnType == typeof(string)) - return ManagedString.ToNativeWeak(Unsafe.As(returnObject)); + return ManagedString.ToNative/*Weak*/(Unsafe.As(returnObject)); if (returnType == typeof(ManagedHandle)) return ManagedHandle.ToIntPtr((ManagedHandle)(object)returnObject); if (returnType == typeof(bool)) @@ -168,7 +168,7 @@ namespace FlaxEngine.Interop return ManagedHandle.ToIntPtr(ManagedArray.WrapNewArray(Unsafe.As(returnObject)), GCHandleType.Weak); if (returnType.IsArray) return ManagedHandle.ToIntPtr(ManagedArrayToGCHandleWrappedArray(Unsafe.As(returnObject)), GCHandleType.Weak); - return ManagedHandle.ToIntPtr(returnObject, GCHandleType.Weak); + return ManagedHandle.ToIntPtr(returnObject/*, GCHandleType.Weak*/); } internal static IntPtr MarshalReturnValueThunk(ref TRet returnValue) @@ -181,7 +181,7 @@ namespace FlaxEngine.Interop if (returnObject == null) return IntPtr.Zero; if (returnType == typeof(string)) - return ManagedString.ToNativeWeak(Unsafe.As(returnObject)); + return ManagedString.ToNative/*Weak*/(Unsafe.As(returnObject)); if (returnType == typeof(IntPtr)) return (IntPtr)(object)returnObject; if (returnType == typeof(ManagedHandle)) @@ -210,7 +210,7 @@ namespace FlaxEngine.Interop return (IntPtr)new UIntPtr((ulong)(System.UInt32)(object)returnObject); if (returnType == typeof(System.UInt64)) return (IntPtr)new UIntPtr((ulong)(System.UInt64)(object)returnObject); - return ManagedHandle.ToIntPtr(returnObject, GCHandleType.Weak); + return ManagedHandle.ToIntPtr(returnObject/*, GCHandleType.Weak*/); } #if !USE_AOT diff --git a/Source/Engine/Engine/NativeInterop.Managed.cs b/Source/Engine/Engine/NativeInterop.Managed.cs index 43cfdbb05..b91a98ed4 100644 --- a/Source/Engine/Engine/NativeInterop.Managed.cs +++ b/Source/Engine/Engine/NativeInterop.Managed.cs @@ -1,5 +1,8 @@ // Copyright (c) Wojciech Figat. All rights reserved. +#define USE_CONCURRENT_DICT +#define USE_GCHANDLE + #if USE_NETCORE using System; using System.Collections.Generic; @@ -8,6 +11,7 @@ using System.Runtime.InteropServices; using System.Runtime.CompilerServices; using System.Threading; using FlaxEngine.Assertions; +using System.Collections.Concurrent; #pragma warning disable 1591 @@ -356,18 +360,77 @@ namespace FlaxEngine.Interop #endif public struct ManagedHandle { +#if USE_GCHANDLE + private GCHandle handle; + + private static HashSet _weakHandles = new HashSet(); + private static ConcurrentDictionary _handles = new(); + private static ConcurrentDictionary _handles2 = new(); + + private ManagedHandle(IntPtr handle) => this.handle = GCHandle.FromIntPtr(handle); + + private ManagedHandle(object value, GCHandleType type) //=> handle = GCHandle.Alloc(value, type); + { + if (type == GCHandleType.Weak || type == GCHandleType.WeakTrackResurrection) + { + type = GCHandleType.Normal; + } + handle = GCHandle.Alloc(value, type); + if (type == GCHandleType.Weak || type == GCHandleType.WeakTrackResurrection) + _weakHandles.Add((IntPtr)handle); + else if (_handles.Count < 14000) + _handles.TryAdd((IntPtr)handle, value?.GetType().FullName ?? ""); + else + { + if (_handles2.Count > 12) + type = type; + if (value?.GetType() == typeof(string)) + type = type; + _handles2.TryAdd((IntPtr)handle, value?.GetType().FullName ?? ""); + } + } +#else private IntPtr handle; private ManagedHandle(IntPtr handle) => this.handle = handle; private ManagedHandle(object value, GCHandleType type) => handle = ManagedHandlePool.AllocateHandle(value, type); +#endif [MethodImpl(MethodImplOptions.AggressiveInlining)] public static ManagedHandle Alloc(object value) => new ManagedHandle(value, GCHandleType.Normal); [MethodImpl(MethodImplOptions.AggressiveInlining)] public static ManagedHandle Alloc(object value, GCHandleType type) => new ManagedHandle(value, type); +#if USE_GCHANDLE + public void Free()// => handle.Free(); + { + if ((IntPtr)handle == IntPtr.Zero) + return; + if (_weakHandles.Remove((IntPtr)handle)) + { + if (!handle.IsAllocated) + handle = handle; + handle = handle; + } + else if (_handles.Remove((IntPtr)handle, out _)) + { + handle = handle; + } + else if (_handles2.Remove((IntPtr)handle, out _)) + { + handle = handle; + } + + handle.Free(); + } + + + public object Target => handle.Target; + + public bool IsAllocated => handle.IsAllocated; +#else public void Free() { if (handle == IntPtr.Zero) @@ -383,6 +446,7 @@ namespace FlaxEngine.Interop } public bool IsAllocated => handle != IntPtr.Zero; +#endif [MethodImpl(MethodImplOptions.AggressiveInlining)] public static explicit operator ManagedHandle(IntPtr value) => FromIntPtr(value); @@ -393,6 +457,16 @@ namespace FlaxEngine.Interop [MethodImpl(MethodImplOptions.AggressiveInlining)] public static explicit operator IntPtr(ManagedHandle value) => ToIntPtr(value); +#if USE_GCHANDLE + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static IntPtr ToIntPtr(object value) => (IntPtr)Alloc(value, GCHandleType.Normal).handle; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static IntPtr ToIntPtr(object value, GCHandleType type) => (IntPtr)Alloc(value, type).handle; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static IntPtr ToIntPtr(ManagedHandle value) => (IntPtr)value.handle; +#else [MethodImpl(MethodImplOptions.AggressiveInlining)] public static IntPtr ToIntPtr(object value) => ManagedHandlePool.AllocateHandle(value, GCHandleType.Normal); @@ -401,6 +475,7 @@ namespace FlaxEngine.Interop [MethodImpl(MethodImplOptions.AggressiveInlining)] public static IntPtr ToIntPtr(ManagedHandle value) => value.handle; +#endif public override int GetHashCode() => handle.GetHashCode(); @@ -412,6 +487,15 @@ namespace FlaxEngine.Interop public static bool operator !=(ManagedHandle a, ManagedHandle b) => a.handle != b.handle; +#if USE_GCHANDLE + internal static class ManagedHandlePool + { + internal static void TryCollectWeakHandles(bool force = false) + { + } + } +#else + internal static class ManagedHandlePool { private const int WeakPoolCollectionSizeThreshold = 10000000; @@ -425,17 +509,31 @@ namespace FlaxEngine.Interop // Dictionaries for storing the valid handles. // Note: Using locks seems to be generally the fastest when adding or fetching from the dictionary. // Concurrent dictionaries could also be considered, but they perform much slower when adding to the dictionary. +#if USE_CONCURRENT_DICT + private static ConcurrentDictionary persistentPool = new(); + private static ConcurrentDictionary pinnedPool = new(); +#else private static Dictionary persistentPool = new(); private static Dictionary pinnedPool = new(); + private static Lock persistentPoolLock = new(); + private static Lock pinnedPoolLock = new(); +#endif // TODO: Performance of pinned handles are poor at the moment due to GCHandle wrapping. // TODO: .NET8: Experiment with pinned arrays for faster pinning: https://github.com/dotnet/runtime/pull/89293 // Manage double-buffered pool for weak handles in order to avoid collecting in-flight handles. // Periodically when the pools are being accessed and conditions are met, the other pool is cleared and swapped. - private static Dictionary weakPool = new(); - private static Dictionary weakPoolOther = new(); - private static object weakPoolLock = new object(); + private static int weakPoolSize = 1; + private static int weakPoolOtherSize = 1; +#if USE_CONCURRENT_DICT + private static ConcurrentDictionary weakPool = new(-1, weakPoolSize); + private static ConcurrentDictionary weakPoolOther = new(-1, weakPoolOtherSize); +#else + private static Dictionary weakPool = new(weakPoolSize); + private static Dictionary weakPoolOther = new(weakPoolOtherSize); +#endif + private static Lock weakPoolLock = new(); private static ulong nextWeakPoolCollection; private static int nextWeakPoolGCCollection; private static long lastWeakPoolCollectionTime; @@ -466,6 +564,19 @@ namespace FlaxEngine.Interop // Swap the pools and release the oldest pool for GC (weakPool, weakPoolOther) = (weakPoolOther, weakPool); + (weakPoolSize, weakPoolOtherSize) = (weakPoolOtherSize, weakPoolSize); +#if USE_CONCURRENT_DICT + if (weakPool.Count > weakPoolSize) + { + var newMax = Math.Max(Math.Max(weakPoolSize, weakPoolOtherSize), weakPool.Count); + //FlaxEditor.Editor.Log($"growth from {weakPoolSize} to {weakPool.Count}, max {newMax}"); + weakPoolSize = newMax; + weakPool = new(-1, weakPoolSize); + } + //else if (weakPool.Count > 0) + // weakPool.Clear(); + //else +#endif weakPool.Clear(); } @@ -485,14 +596,43 @@ namespace FlaxEngine.Interop internal static IntPtr AllocateHandle(object value, GCHandleType type) { IntPtr handle = NewHandle(type); +#if USE_CONCURRENT_DICT switch (type) { case GCHandleType.Normal: - lock (persistentPool) - persistentPool.Add(handle, value); + //lock (persistentPoolLock) + persistentPool.TryAdd(handle, value); + //if (value?.GetType().Name.Contains("RenderContext") ?? false) + // value = value; break; case GCHandleType.Pinned: - lock (pinnedPool) + //lock (pinnedPoolLock) + pinnedPool.TryAdd(handle, GCHandle.Alloc(value, GCHandleType.Pinned)); + break; + case GCHandleType.Weak: + case GCHandleType.WeakTrackResurrection: + lock (weakPoolLock) + { + TryCollectWeakHandles(); + //weakPool.TryAdd(handle, value); + weakPool[handle] = value; + + //if (value?.GetType().Name.Contains("RenderContext") ?? false) + // value = value; + } + break; + } +#else + switch (type) + { + case GCHandleType.Normal: + lock (persistentPoolLock) + persistentPool.Add(handle, value); + //if (value?.GetType().Name.Contains("RenderContext") ?? false) + // value = value; + break; + case GCHandleType.Pinned: + lock (pinnedPoolLock) pinnedPool.Add(handle, GCHandle.Alloc(value, GCHandleType.Pinned)); break; case GCHandleType.Weak: @@ -501,25 +641,36 @@ namespace FlaxEngine.Interop { TryCollectWeakHandles(); weakPool.Add(handle, value); + //weakPool[handle] = value; + + //if (value?.GetType().Name.Contains("RenderContext") ?? false) + // value = value; } break; } +#endif return handle; } internal static object GetObject(IntPtr handle) { - switch (GetHandleType(handle)) + GCHandleType type = GetHandleType(handle); + + switch (type) { case GCHandleType.Normal: - lock (persistentPool) +#if !USE_CONCURRENT_DICT + lock (persistentPoolLock) +#endif { if (persistentPool.TryGetValue(handle, out object value)) return value; } break; case GCHandleType.Pinned: - lock (pinnedPool) +#if !USE_CONCURRENT_DICT + lock (pinnedPoolLock) +#endif { if (pinnedPool.TryGetValue(handle, out GCHandle gcHandle)) return gcHandle.Target; @@ -537,15 +688,98 @@ namespace FlaxEngine.Interop } break; } - throw new NativeInteropException("Invalid ManagedHandle"); + throw new NativeInteropException($"Invalid ManagedHandle of type '{type}'"); } internal static void SetObject(IntPtr handle, object value) { - switch (GetHandleType(handle)) + GCHandleType type = GetHandleType(handle); +#if USE_CONCURRENT_DICT + switch (type) { case GCHandleType.Normal: - lock (persistentPool) + //lock (persistentPoolLock) + { + //ref object obj = ref CollectionsMarshal.GetValueRefOrNullRef(persistentPool, handle); + if (persistentPool.TryGetValue(handle, out var oldValue)) + { + if (persistentPool.TryUpdate(handle, value, oldValue)) + //if (!Unsafe.IsNullRef(ref obj)) + { + //obj = value; + return; + } + } + } + break; + case GCHandleType.Pinned: + //lock (pinnedPoolLock) + { + /*ref GCHandle gcHandle = ref CollectionsMarshal.GetValueRefOrNullRef(pinnedPool, handle); + if (!Unsafe.IsNullRef(ref gcHandle)) + { + gcHandle.Target = value; + return; + }*/ + if (pinnedPool.TryGetValue(handle, out var gcHandle)) + { + gcHandle.Target = value; + //if (pinnedPool.TryUpdate(handle, value, oldValue)) + //if (!Unsafe.IsNullRef(ref obj)) + //{ + //obj = value; + return; + } + //} + } + break; + case GCHandleType.Weak: + case GCHandleType.WeakTrackResurrection: + lock (weakPoolLock) + { + TryCollectWeakHandles(); + if (weakPool.TryGetValue(handle, out var oldValue)) + { + if (weakPool.TryUpdate(handle, value, oldValue)) + //if (!Unsafe.IsNullRef(ref obj)) + { + //obj = value; + return; + } + } + if (weakPoolOther.TryGetValue(handle, out oldValue)) + { + if (weakPoolOther.TryUpdate(handle, value, oldValue)) + //if (!Unsafe.IsNullRef(ref obj)) + { + //obj = value; + return; + } + } + /*{ + ref object obj = ref CollectionsMarshal.GetValueRefOrNullRef(weakPool, handle); + if (!Unsafe.IsNullRef(ref obj)) + { + obj = value; + return; + } + } + { + ref object obj = ref CollectionsMarshal.GetValueRefOrNullRef(weakPoolOther, handle); + if (!Unsafe.IsNullRef(ref obj)) + { + obj = value; + return; + } + }*/ + } + break; + } +#else + switch (type) + { + case GCHandleType.Normal: + lock (persistentPoolLock) { ref object obj = ref CollectionsMarshal.GetValueRefOrNullRef(persistentPool, handle); if (!Unsafe.IsNullRef(ref obj)) @@ -556,7 +790,7 @@ namespace FlaxEngine.Interop } break; case GCHandleType.Pinned: - lock (pinnedPool) + lock (pinnedPoolLock) { ref GCHandle gcHandle = ref CollectionsMarshal.GetValueRefOrNullRef(pinnedPool, handle); if (!Unsafe.IsNullRef(ref gcHandle)) @@ -590,22 +824,32 @@ namespace FlaxEngine.Interop } break; } - throw new NativeInteropException("Invalid ManagedHandle"); +#endif + throw new NativeInteropException($"Invalid ManagedHandle of type '{type}'"); } internal static void FreeHandle(IntPtr handle) { - switch (GetHandleType(handle)) + GCHandleType type = GetHandleType(handle); + switch (type) { case GCHandleType.Normal: - lock (persistentPool) +#if !USE_CONCURRENT_DICT + lock (persistentPoolLock) +#endif { - if (persistentPool.Remove(handle)) + if (persistentPool.Remove(handle, out _)) + { + //if (value?.GetType().Name.Contains("RenderContext") ?? false) + // value = value; return; + } } break; case GCHandleType.Pinned: - lock (pinnedPool) +#if !USE_CONCURRENT_DICT + lock (pinnedPoolLock) +#endif { if (pinnedPool.Remove(handle, out GCHandle gcHandle)) { @@ -617,12 +861,19 @@ namespace FlaxEngine.Interop case GCHandleType.Weak: case GCHandleType.WeakTrackResurrection: lock (weakPoolLock) + { TryCollectWeakHandles(); - return; + if (weakPool.Remove(handle, out _)) + return; + else if (weakPoolOther.Remove(handle, out _)) + return; + return; + } } - throw new NativeInteropException("Invalid ManagedHandle"); + throw new NativeInteropException($"Invalid ManagedHandle of type '{type}'"); } } +#endif } } diff --git a/Source/Engine/Engine/NativeInterop.Marshallers.cs b/Source/Engine/Engine/NativeInterop.Marshallers.cs index 0f3c9beee..d9cc1a6c2 100644 --- a/Source/Engine/Engine/NativeInterop.Marshallers.cs +++ b/Source/Engine/Engine/NativeInterop.Marshallers.cs @@ -59,8 +59,9 @@ namespace FlaxEngine.Interop #endif public struct ManagedToNativeState { - ManagedArray managedArray; - IntPtr handle; + ManagedArray _pooledManagedArray; + ManagedArray _managedArray; + ManagedHandle handle; public void FromManaged(object managed) { @@ -73,29 +74,30 @@ namespace FlaxEngine.Interop if (NativeInterop.ArrayFactory.GetMarshalledType(elementType) == elementType) { // Use pooled managed array wrapper to be passed around as handle to it - (ManagedHandle tmp, managedArray) = ManagedArray.WrapPooledArray(arr); - handle = ManagedHandle.ToIntPtr(tmp); + (handle, _pooledManagedArray) = ManagedArray.WrapPooledArray(arr); } else { // Convert array contents to be properly accessed by the native code (as GCHandles array) - managedArray = NativeInterop.ManagedArrayToGCHandleWrappedArray(arr); - handle = ManagedHandle.ToIntPtr(ManagedHandle.Alloc(managedArray)); - managedArray = null; // It's not pooled + _managedArray = NativeInterop.ManagedArrayToGCHandleWrappedArray(arr); + handle = ManagedHandle.Alloc(_pooledManagedArray); } } else - handle = ManagedHandle.ToIntPtr(managed, GCHandleType.Weak); + handle = ManagedHandle.Alloc(managed/*, GCHandleType.Weak*/); } - public IntPtr ToUnmanaged() - { - return handle; - } + public IntPtr ToUnmanaged() => ManagedHandle.ToIntPtr(handle); public void Free() { - managedArray?.FreePooled(); + if (_pooledManagedArray != null) + _pooledManagedArray.FreePooled(); + else + { + handle.Free(); + _managedArray?.Free(); + } } } @@ -109,6 +111,7 @@ namespace FlaxEngine.Interop public static void Free(IntPtr unmanaged) { + ManagedHandle.FromIntPtr(unmanaged).Free(); } } @@ -409,7 +412,7 @@ namespace FlaxEngine.Interop public static void Free(IntPtr unmanaged) { - //DictionaryMarshaller.Free(unmanaged); // No need to free weak handles + DictionaryMarshaller.Free(unmanaged); // No need to free weak handles } } @@ -678,7 +681,7 @@ namespace FlaxEngine.Interop public static class NativeToManaged { public static string ConvertToManaged(IntPtr unmanaged) => ManagedString.ToManaged(unmanaged); - public static unsafe IntPtr ConvertToUnmanaged(string managed) => managed == null ? IntPtr.Zero : ManagedHandle.ToIntPtr(managed, GCHandleType.Weak); + public static unsafe IntPtr ConvertToUnmanaged(string managed) => managed == null ? IntPtr.Zero : ManagedHandle.ToIntPtr(managed/*, GCHandleType.Weak*/); public static void Free(IntPtr unmanaged) => ManagedString.Free(unmanaged); } @@ -688,11 +691,11 @@ namespace FlaxEngine.Interop public static class ManagedToNative { public static string ConvertToManaged(IntPtr unmanaged) => ManagedString.ToManaged(unmanaged); - public static unsafe IntPtr ConvertToUnmanaged(string managed) => managed == null ? IntPtr.Zero : ManagedHandle.ToIntPtr(managed, GCHandleType.Weak); + public static unsafe IntPtr ConvertToUnmanaged(string managed) => managed == null ? IntPtr.Zero : ManagedHandle.ToIntPtr(managed/*, GCHandleType.Weak*/); public static void Free(IntPtr unmanaged) { - //ManagedString.Free(unmanaged); // No need to free weak handles + ManagedString.Free(unmanaged); // No need to free weak handles } } diff --git a/Source/Engine/Engine/NativeInterop.Unmanaged.cs b/Source/Engine/Engine/NativeInterop.Unmanaged.cs index 7af32fc9b..385984665 100644 --- a/Source/Engine/Engine/NativeInterop.Unmanaged.cs +++ b/Source/Engine/Engine/NativeInterop.Unmanaged.cs @@ -9,6 +9,8 @@ using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Runtime.Loader; +using System.Threading; +using FlaxEngine.Assertions; using FlaxEngine.Utilities; #pragma warning disable 1591 @@ -602,13 +604,13 @@ namespace FlaxEngine.Interop [UnmanagedCallersOnly] internal static IntPtr NewStringUTF16(char* text, int length) { - return ManagedString.ToNativeWeak(new string(new ReadOnlySpan(text, length))); + return ManagedString.ToNative/*Weak*/(new string(new ReadOnlySpan(text, length))); } [UnmanagedCallersOnly] internal static IntPtr NewStringUTF8(sbyte* text, int length) { - return ManagedString.ToNativeWeak(new string(text, 0, length, System.Text.Encoding.UTF8)); + return ManagedString.ToNative/*Weak*/(new string(text, 0, length, System.Text.Encoding.UTF8)); } [UnmanagedCallersOnly] @@ -661,7 +663,7 @@ namespace FlaxEngine.Interop { Type type = Unsafe.As(typeHandle.Target); object value = MarshalToManaged(valuePtr, type); - return ManagedHandle.Alloc(value, GCHandleType.Weak); + return ManagedHandle.Alloc(value/*, GCHandleType.Weak*/); } /// @@ -714,7 +716,7 @@ namespace FlaxEngine.Interop internal static IntPtr InvokeMethod(ManagedHandle instanceHandle, ManagedHandle methodHandle, IntPtr paramPtr, IntPtr exceptionPtr) { MethodHolder methodHolder = Unsafe.As(methodHandle.Target); -#if !USE_AOT +#if false//#if !USE_AOT if (methodHolder.TryGetDelegate(out var methodDelegate, out var methodDelegateContext)) { // Fast path, invoke the method with minimal allocations @@ -737,7 +739,7 @@ namespace FlaxEngine.Interop // Slow path, method parameters needs to be stored in heap object returnObject; int numParams = methodHolder.parameterTypes.Length; - object[] methodParameters = new object[numParams]; + object[] methodParameters = new object[numParams];//ObjectArrayPool.Rent(numParams);//new object[numParams]; for (int i = 0; i < numParams; i++) { @@ -748,6 +750,17 @@ namespace FlaxEngine.Interop try { returnObject = methodHolder.method.Invoke(instanceHandle.IsAllocated ? instanceHandle.Target : null, methodParameters); + + // Marshal reference parameters back to original unmanaged references + for (int i = 0; i < numParams; i++) + { + Type parameterType = methodHolder.parameterTypes[i]; + if (parameterType.IsByRef) + { + IntPtr nativePtr = Unsafe.Read((IntPtr.Add(paramPtr, sizeof(IntPtr) * i)).ToPointer()); + MarshalToNative(methodParameters[i], nativePtr, parameterType.GetElementType()); + } + } } catch (Exception exception) { @@ -762,19 +775,11 @@ namespace FlaxEngine.Interop throw realException; return IntPtr.Zero; } - - // Marshal reference parameters back to original unmanaged references - for (int i = 0; i < numParams; i++) + finally { - Type parameterType = methodHolder.parameterTypes[i]; - if (parameterType.IsByRef) - { - IntPtr nativePtr = Unsafe.Read((IntPtr.Add(paramPtr, sizeof(IntPtr) * i)).ToPointer()); - MarshalToNative(methodParameters[i], nativePtr, parameterType.GetElementType()); - } + //ObjectArrayPool.Return(methodParameters); } - // Return value return Invoker.MarshalReturnValueGeneric(methodHolder.returnType, returnObject); } } @@ -908,7 +913,7 @@ namespace FlaxEngine.Interop if (File.Exists(pdbPath)) { // Load including debug symbols - using FileStream pdbStream = new FileStream(Path.ChangeExtension(assemblyPath, "pdb"), FileMode.Open); + using FileStream pdbStream = new FileStream(Path.ChangeExtension(assemblyPath, "pdb"), FileMode.Open, FileAccess.Read); assembly = scriptingAssemblyLoadContext.LoadFromStream(stream, pdbStream); } else @@ -998,6 +1003,8 @@ namespace FlaxEngine.Interop Debug.Logger.LogHandler.LogWrite(LogType.Warning, "Scripting AssemblyLoadContext was not unloaded."); weakRef.Free(); + Assert.IsFalse(AssemblyLoadContext.All.Any(x => x.Name == "Flax")); + static bool IsHandleAlive(GCHandle weakRef) { // Checking the target in scope somehow holds a reference to it...? @@ -1026,6 +1033,7 @@ namespace FlaxEngine.Interop // Clear all caches which might hold references to assemblies in collectible ALC cachedDelegatesCollectible.Clear(); + cachedDelegatesCollectible = new(); foreach (var pair in managedTypesCollectible) pair.Value.handle.Free(); managedTypesCollectible.Clear(); @@ -1115,6 +1123,9 @@ namespace FlaxEngine.Interop } } + GC.Collect(); + GC.WaitForPendingFinalizers(); + // Unload the ALC scriptingAssemblyLoadContext.Unload(); scriptingAssemblyLoadContext.Resolving -= OnScriptingAssemblyLoadContextResolving; diff --git a/Source/Engine/Engine/NativeInterop.cs b/Source/Engine/Engine/NativeInterop.cs index 564c53801..26b2cb41f 100644 --- a/Source/Engine/Engine/NativeInterop.cs +++ b/Source/Engine/Engine/NativeInterop.cs @@ -18,6 +18,7 @@ using System.Collections.Concurrent; using System.IO; using System.Text; using System.Threading; +using System.Buffers; namespace FlaxEngine.Interop { @@ -1244,7 +1245,7 @@ namespace FlaxEngine.Interop internal static void ToNativeString(ref string managedValue, IntPtr nativePtr) { - Unsafe.Write(nativePtr.ToPointer(), ManagedString.ToNativeWeak(managedValue)); + Unsafe.Write(nativePtr.ToPointer(), ManagedString.ToNative/*Weak*/(managedValue)); } internal static void ToNativeType(ref Type managedValue, IntPtr nativePtr) @@ -1291,14 +1292,14 @@ namespace FlaxEngine.Interop } else managedArray = ManagedArrayToGCHandleWrappedArray(arr); - managedPtr = ManagedHandle.ToIntPtr(managedArray, GCHandleType.Weak); + managedPtr = ManagedHandle.ToIntPtr(managedArray/*, GCHandleType.Weak*/); } Unsafe.Write(nativePtr.ToPointer(), managedPtr); } internal static void ToNative(ref T managedValue, IntPtr nativePtr) { - Unsafe.Write(nativePtr.ToPointer(), managedValue != null ? ManagedHandle.ToIntPtr(managedValue, GCHandleType.Weak) : IntPtr.Zero); + Unsafe.Write(nativePtr.ToPointer(), managedValue != null ? ManagedHandle.ToIntPtr(managedValue/*, GCHandleType.Weak*/) : IntPtr.Zero); } } @@ -1529,11 +1530,12 @@ namespace FlaxEngine.Interop private static GCHandle[] pinnedBoxedValues = new GCHandle[256]; private static uint pinnedBoxedValuesPointer = 0; private static (IntPtr ptr, int size)[] pinnedAllocations = new (IntPtr ptr, int size)[256]; + private static Action[] pinnedNativeFreeDelegates = new Action[256]; private static uint pinnedAllocationsPointer = 0; - private delegate IntPtr UnboxerDelegate(object value, object converter); + private delegate IntPtr UnboxerDelegate(object value, object toNativeConverter, object nativeFree); - private static ConcurrentDictionary unboxers = new(1, 3); + private static ConcurrentDictionary unboxers = new(1, 3); private static MethodInfo unboxerMethod = typeof(ValueTypeUnboxer).GetMethod(nameof(ValueTypeUnboxer.UnboxPointer), BindingFlags.Static | BindingFlags.NonPublic); private static MethodInfo unboxerToNativeMethod = typeof(ValueTypeUnboxer).GetMethod(nameof(ValueTypeUnboxer.UnboxPointerWithConverter), BindingFlags.Static | BindingFlags.NonPublic); @@ -1546,8 +1548,12 @@ namespace FlaxEngine.Interop var toNativeMethod = attr?.NativeType.GetMethod("ToNative", BindingFlags.Static | BindingFlags.NonPublic); if (toNativeMethod != null) { - tuple.deleg = unboxerToNativeMethod.MakeGenericMethod(type, toNativeMethod.ReturnType).CreateDelegate(); - tuple.toNativeDeleg = toNativeMethod.CreateDelegate(typeof(ToNativeDelegate<,>).MakeGenericType(type, toNativeMethod.ReturnType)); + Type internalType = toNativeMethod.ReturnType; + tuple.deleg = unboxerToNativeMethod.MakeGenericMethod(type, internalType).CreateDelegate(); + tuple.toNativeConverter = toNativeMethod.CreateDelegate(typeof(ToNativeDelegate<,>).MakeGenericType(type, internalType)); + + MethodInfo freeNativeMethod = attr.NativeType.GetMethod("Free", BindingFlags.Static | BindingFlags.NonPublic); + tuple.nativeFree = freeNativeMethod?.CreateDelegate(typeof(Action<>).MakeGenericType(internalType)); } else { @@ -1555,7 +1561,7 @@ namespace FlaxEngine.Interop } tuple = unboxers.GetOrAdd(type, tuple); } - return tuple.deleg(value, tuple.toNativeDeleg); + return tuple.deleg(value, tuple.toNativeConverter, tuple.nativeFree); } private static void PinValue(object value) @@ -1569,12 +1575,15 @@ namespace FlaxEngine.Interop handle = GCHandle.Alloc(value, GCHandleType.Pinned); } - private static IntPtr PinValue(T value) where T : struct + private static IntPtr PinValue(T value, out uint index) where T : struct { // Store the converted value in unmanaged memory so it will not be relocated by the garbage collector. int size = TypeHelpers.MarshalSize; - uint index = Interlocked.Increment(ref pinnedAllocationsPointer) % (uint)pinnedAllocations.Length; + index = Interlocked.Increment(ref pinnedAllocationsPointer) % (uint)pinnedAllocations.Length; ref (IntPtr ptr, int size) alloc = ref pinnedAllocations[index]; + Action freeNativeDelegate = pinnedNativeFreeDelegates[index]; + if (freeNativeDelegate != null) + freeNativeDelegate(); if (alloc.size < size) { if (alloc.ptr != IntPtr.Zero) @@ -1586,7 +1595,7 @@ namespace FlaxEngine.Interop return alloc.ptr; } - private static IntPtr UnboxPointer(object value, object converter) where T : struct + private static IntPtr UnboxPointer(object value, object toNativeConverter, object nativeFreeDelegate) where T : struct { if (RuntimeHelpers.IsReferenceOrContainsReferences()) // Cannot pin structure with references return IntPtr.Zero; @@ -1594,11 +1603,16 @@ namespace FlaxEngine.Interop return new IntPtr(Unsafe.AsPointer(ref Unsafe.Unbox(value))); } - private static IntPtr UnboxPointerWithConverter(object value, object converter) where T : struct - where TInternal : struct + private static IntPtr UnboxPointerWithConverter( + object value, object nativeConverterDelegate, object nativeFreeDelegate) where T : struct + where TInternal : struct { - ToNativeDelegate toNative = Unsafe.As>(converter); - return PinValue(toNative(Unsafe.Unbox(value))); + ToNativeDelegate toNative = Unsafe.As>(nativeConverterDelegate); + TInternal nativeValue = toNative(Unsafe.Unbox(value)); + IntPtr pinnedPtr = PinValue(nativeValue, out uint index); + Action freeNative = Unsafe.As>(nativeFreeDelegate); + //pinnedNativeFreeDelegates[index] = () => freeNative(nativeValue); + return pinnedPtr; } } @@ -1743,25 +1757,37 @@ namespace FlaxEngine.Interop internal static void InitMethods() { + return; MakeNewCustomDelegateFunc = typeof(Expression).Assembly.GetType("System.Linq.Expressions.Compiler.DelegateHelpers") .GetMethod("MakeNewCustomDelegate", BindingFlags.NonPublic | BindingFlags.Static).CreateDelegate>(); + MakeNewCustomDelegateFunc(new[] { typeof(void) }); #if FLAX_EDITOR // Load System.Linq.Expressions assembly to collectible ALC. // The dynamic assembly where delegates are stored is cached in the DelegateHelpers class, so we should // use the DelegateHelpers in collectible ALC to make sure the delegates are also stored in the same ALC. - Assembly assembly = scriptingAssemblyLoadContext.LoadFromAssemblyPath(typeof(Expression).Assembly.Location); + //using var _ = scriptingAssemblyLoadContext.EnterContextualReflection(); + + //Debug.Logger.LogHandler.LogWrite(LogType.Warning, "InitMethods."); + + var asdfa = AssemblyLoadContext.All.ToArray(); + + var asma = typeof(Expression).Assembly; + var loc = asma.Location; + using FileStream stream = new FileStream(loc, FileMode.Open, FileAccess.Read); + Assembly assembly = scriptingAssemblyLoadContext.LoadFromAssemblyPath(loc); MakeNewCustomDelegateFuncCollectible = assembly.GetType("System.Linq.Expressions.Compiler.DelegateHelpers") .GetMethod("MakeNewCustomDelegate", BindingFlags.NonPublic | BindingFlags.Static).CreateDelegate>(); - // Create dummy delegates to force the dynamic Snippets assembly to be loaded in correcet ALCs - MakeNewCustomDelegateFunc(new[] { typeof(void) }); + Debug.Logger.LogHandler.LogWrite(LogType.Warning, "InitMethods ok ."); + { - // Ensure the new delegate is placed in the collectible ALC + // Ensure any future delegates is placed in the collectible ALC using var ctx = scriptingAssemblyLoadContext.EnterContextualReflection(); - MakeNewCustomDelegateFuncCollectible(new[] { typeof(void) }); + var ret = MakeNewCustomDelegateFuncCollectible(new[] { typeof(void) }); + Assert.IsTrue(ret.IsCollectible); } #endif } @@ -1785,6 +1811,102 @@ namespace FlaxEngine.Interop #endif } + internal static class ObjectArrayPool + { + [ThreadStatic] + private static List<(bool InUse, object[] Array)> _pool; + + [ThreadStatic] + private static List<(bool InUse, object[] Array)> _pool2; + + /// + /// Rents an array from the pool. + /// + /// Exact size of the array. + internal static object[] Rent(int size) + { + if (size == 0) + return Array.Empty(); + + if (_pool == null) + _pool = new(16); + + foreach (ref var tuple in CollectionsMarshal.AsSpan(_pool)) + { + if (tuple.InUse) + continue; + + if (tuple.Array.Length != size) + continue; + + tuple.InUse = true; + return tuple.Array; + } + + var newTuple = (InUse: true, Array: new object[size]); + _pool.Add(newTuple); + return newTuple.Array; + } + + internal static object[] Rent2() + { + if (_pool2 == null) + _pool2 = new(16); + + foreach (ref var tuple in CollectionsMarshal.AsSpan(_pool2)) + { + if (tuple.InUse) + continue; + + tuple.InUse = true; + return tuple.Array; + } + + var newTuple = (InUse: true, Array: new object[8]); + _pool2.Add(newTuple); + return newTuple.Array; + } + + /// + /// Returns the rented object array back to the pool. + /// + /// The array rented from the pool. + internal static void Return(object[] array) + { + if (array.Length == 0) + return; + + foreach (ref var tuple in CollectionsMarshal.AsSpan(_pool)) + { + if (tuple.Array != array) + continue; + + tuple.InUse = false; + for (int i = 0; i < array.Length; i++) + array[i] = null; + return; + } + + throw new NativeInteropException("Tried to free non-pooled object array as pooled ManagedArray"); + } + + internal static void Return2(object[] array) + { + foreach (ref var tuple in CollectionsMarshal.AsSpan(_pool2)) + { + if (tuple.Array != array) + continue; + + tuple.InUse = false; + for (int i = 0; i < array.Length; i++) + array[i] = null; + return; + } + + throw new NativeInteropException("Tried to free non-pooled object array as pooled ManagedArray"); + } + } + #if !USE_AOT /// /// Wrapper class for invoking function pointers from unmanaged code. @@ -1792,13 +1914,18 @@ namespace FlaxEngine.Interop internal class ThunkContext { internal MethodInfo method; + internal MethodInvoker invoker; internal Type[] parameterTypes; internal Invoker.InvokeThunkDelegate methodDelegate; internal object methodDelegateContext; + internal static object[] objectPool = new object[128]; + internal static int objectPoolIndex = 0; + internal ThunkContext(MethodInfo method) { this.method = method; + invoker = MethodInvoker.Create(method); parameterTypes = method.GetParameterTypes(); // Thunk delegates don't support IsByRef parameters (use generic invocation that handles 'out' and 'ref' prams) @@ -1826,6 +1953,7 @@ namespace FlaxEngine.Interop genericParamTypes.Add(type); } +#if false string invokerTypeName = $"{typeof(Invoker).FullName}+Invoker{(method.IsStatic ? "Static" : "")}{(method.ReturnType != typeof(void) ? "Ret" : "NoRet")}{parameterTypes.Length}{(genericParamTypes.Count > 0 ? "`" + genericParamTypes.Count : "")}"; Type invokerType = Type.GetType(invokerTypeName); if (invokerType != null) @@ -1838,11 +1966,13 @@ namespace FlaxEngine.Interop if (methodDelegate != null) Assert.IsTrue(methodDelegateContext != null); +#endif } public IntPtr InvokeThunk(ManagedHandle instanceHandle, IntPtr param1, IntPtr param2, IntPtr param3, IntPtr param4, IntPtr param5, IntPtr param6, IntPtr param7) { IntPtr* nativePtrs = stackalloc IntPtr[] { param1, param2, param3, param4, param5, param6, param7 }; +#if false if (methodDelegate != null) { IntPtr returnValue; @@ -1861,36 +1991,69 @@ namespace FlaxEngine.Interop return returnValue; } else +#endif { // The parameters are wrapped (boxed) in GCHandles object returnObject; int numParams = parameterTypes.Length; - object[] methodParameters = new object[numParams]; + //object[] methodParameters = ObjectArrayPool.Rent(numParams);//new object[numParams]; // TODO: object array pool + if (objectPoolIndex + numParams > 128) + objectPoolIndex = 0; + var paramSpan = objectPool.AsSpan(objectPoolIndex, numParams); + objectPoolIndex += numParams; for (int i = 0; i < numParams; i++) { IntPtr nativePtr = nativePtrs[i]; - object managed = null; if (nativePtr != IntPtr.Zero) { + object managed = null; Type type = parameterTypes[i]; - Type elementType = type.GetElementType(); if (type.IsByRef) { // References use indirection to support value returning nativePtr = Unsafe.Read(nativePtr.ToPointer()); - type = elementType; + type = type.GetElementType(); } if (type.IsArray) managed = MarshalToManaged(nativePtr, type); // Array might be in internal format of custom structs so unbox if need to + else if (type == typeof(Type)) + managed = Unsafe.As(ManagedHandle.FromIntPtr(nativePtr).Target).type; else managed = ManagedHandle.FromIntPtr(nativePtr).Target; + paramSpan[i] = managed; } - methodParameters[i] = managed; } try { - returnObject = method.Invoke(instanceHandle.IsAllocated ? instanceHandle.Target : null, methodParameters); + returnObject = invoker.Invoke(instanceHandle.IsAllocated ? instanceHandle.Target : null, paramSpan); + //returnObject = method.Invoke(instanceHandle.IsAllocated ? instanceHandle.Target : null, methodParameters); + + // Marshal reference parameters back to original unmanaged references + for (int i = 0; i < numParams; i++) + { + IntPtr nativePtr = nativePtrs[i]; + Type type = parameterTypes[i]; + if (nativePtr != IntPtr.Zero && type.IsByRef) + { + object managed = paramSpan[i]; + type = type.GetElementType(); + if (managed == null) + Unsafe.Write(nativePtr.ToPointer(), IntPtr.Zero); + else if (type.IsArray) + MarshalToNative(managed, nativePtr, type); + else + { + var ptr = nativePtr.ToPointer(); + if (type == typeof(Type)) + managed = GetTypeHolder(Unsafe.As(managed)); + var handle = ManagedHandle.Alloc(managed/*, GCHandleType.Weak*/); + var handleptr = ManagedHandle.ToIntPtr(handle); + Unsafe.Write(ptr, handleptr); + //Unsafe.Write(nativePtr.ToPointer(), ManagedHandle.ToIntPtr(ManagedHandle.Alloc(managed, GCHandleType.Weak))); + } + } + } } catch (Exception exception) { @@ -1900,32 +2063,19 @@ namespace FlaxEngine.Interop Unsafe.Write(exceptionPtr.ToPointer(), ManagedHandle.ToIntPtr(exception, GCHandleType.Weak)); return IntPtr.Zero; } - - // Marshal reference parameters back to original unmanaged references - for (int i = 0; i < numParams; i++) + finally { - IntPtr nativePtr = nativePtrs[i]; - Type type = parameterTypes[i]; - object managed = methodParameters[i]; - if (nativePtr != IntPtr.Zero && type.IsByRef) - { - type = type.GetElementType(); - if (managed == null) - Unsafe.Write(nativePtr.ToPointer(), IntPtr.Zero); - else if (type.IsArray) - MarshalToNative(managed, nativePtr, type); - else - Unsafe.Write(nativePtr.ToPointer(), ManagedHandle.ToIntPtr(ManagedHandle.Alloc(managed, GCHandleType.Weak))); - } + //ObjectArrayPool.Return(methodParameters); + for (int i = 0; i < numParams; i++) + paramSpan[i] = null; } - // Return value return Invoker.MarshalReturnValueThunkGeneric(method.ReturnType, returnObject); } } } #endif - } + } internal class NativeInteropException : Exception { diff --git a/Source/Engine/Platform/Base/WindowBase.cpp b/Source/Engine/Platform/Base/WindowBase.cpp index cd96dca41..10f9d8cb6 100644 --- a/Source/Engine/Platform/Base/WindowBase.cpp +++ b/Source/Engine/Platform/Base/WindowBase.cpp @@ -74,7 +74,7 @@ MObject* exception = nullptr; \ auto resultObj = _method_##name->Invoke(GetManagedInstance(), params, &exception); \ if (resultObj) \ - result = (DragDropEffect)MUtils::Unbox(resultObj); \ + result = (DragDropEffect)MUtils::Unbox(resultObj, true); \ END_INVOKE_EVENT(name) #else #define INVOKE_EVENT(name, paramsCount, param0, param1, param2) diff --git a/Source/Engine/Scripting/BinaryModule.cpp b/Source/Engine/Scripting/BinaryModule.cpp index 07feedf10..d035eeed7 100644 --- a/Source/Engine/Scripting/BinaryModule.cpp +++ b/Source/Engine/Scripting/BinaryModule.cpp @@ -1332,7 +1332,7 @@ bool ManagedBinaryModule::InvokeMethod(void* method, const Variant& instance, Sp } // Unbox result - result = MUtils::UnboxVariant(resultObject); + result = MUtils::UnboxVariant(resultObject, true); #if 0 // Helper method invocations logging @@ -1366,7 +1366,7 @@ bool ManagedBinaryModule::InvokeMethod(void* method, const Variant& instance, Sp paramValue.SetString(MUtils::ToString((MString*)param)); break; case VariantType::Object: - paramValue = MUtils::UnboxVariant((MObject*)param); + paramValue = MUtils::UnboxVariant((MObject*)param, true); break; case VariantType::Structure: { @@ -1383,7 +1383,7 @@ bool ManagedBinaryModule::InvokeMethod(void* method, const Variant& instance, Sp { MType* paramType = mMethod->GetParameterType(paramIdx); if (MCore::Type::IsReference(paramType) && MCore::Type::GetType(paramType) == MTypes::Object) - paramValue = MUtils::UnboxVariant((MObject*)outParams[paramIdx]); + paramValue = MUtils::UnboxVariant((MObject*)outParams[paramIdx], true); break; } } @@ -1527,7 +1527,7 @@ bool ManagedBinaryModule::GetFieldValue(void* field, const Variant& instance, Va const auto mField = (MField*)field; resultObject = mField->GetValueBoxed(instanceObject); } - result = MUtils::UnboxVariant(resultObject); + result = MUtils::UnboxVariant(resultObject, true); return false; #else return true; diff --git a/Source/Engine/Scripting/ManagedCLR/MUtils.cpp b/Source/Engine/Scripting/ManagedCLR/MUtils.cpp index 69fb5aa08..7a4215430 100644 --- a/Source/Engine/Scripting/ManagedCLR/MUtils.cpp +++ b/Source/Engine/Scripting/ManagedCLR/MUtils.cpp @@ -304,7 +304,7 @@ MTypeObject* MUtils::BoxVariantType(const VariantType& value) return INTERNAL_TYPE_GET_OBJECT(mType); } -Variant MUtils::UnboxVariant(MObject* value) +Variant MUtils::UnboxVariant(MObject* value, bool releaseHandle) { if (value == nullptr) return Variant::Null; @@ -345,7 +345,11 @@ Variant MUtils::UnboxVariant(MObject* value) case MTypes::R8: return *static_cast(unboxed); case MTypes::String: + { + if (releaseHandle) + MUtils::FreeManaged(value); return Variant(MUtils::ToString((MString*)value)); + } case MTypes::Ptr: return *static_cast(unboxed); case MTypes::ValueType: @@ -400,6 +404,8 @@ Variant MUtils::UnboxVariant(MObject* value) { Variant v; v.SetBlob(ptr, MCore::Array::GetLength((MArray*)value)); + if (releaseHandle) + MUtils::FreeManaged>(value); return v; } const StringAnsiView fullname = arrayClass->GetFullName(); @@ -508,7 +514,10 @@ Variant MUtils::UnboxVariant(MObject* value) { // Array of Objects for (int32 i = 0; i < array.Count(); i++) - array[i] = UnboxVariant(((MObject**)ptr)[i]); + array[i] = UnboxVariant(((MObject**)ptr)[i], releaseHandle); + + if (releaseHandle) + MUtils::FreeManaged>(value); } return v; } @@ -527,10 +536,13 @@ Variant MUtils::UnboxVariant(MObject* value) { MObject* keyManaged = managedKeysPtr[i]; MObject* valueManaged = managed.GetValue(keyManaged); - native.Add(UnboxVariant(keyManaged), UnboxVariant(valueManaged)); + native.Add(UnboxVariant(keyManaged, releaseHandle), UnboxVariant(valueManaged, releaseHandle)); } Variant v(MoveTemp(native)); v.Type.SetTypeName(klass->GetFullName()); + if (releaseHandle) + MCore::GCHandle::Free(*(MGCHandle*)&value); + //MUtils::FreeManaged>(value); return v; } break; @@ -548,6 +560,8 @@ Variant MUtils::UnboxVariant(MObject* value) v.Type = MoveTemp(VariantType(VariantType::Enum, fullname)); // TODO: what about 64-bit enum? use enum size with memcpy v.AsUint64 = *static_cast(MCore::Object::Unbox(value)); + if (releaseHandle) + MUtils::FreeManaged(value); return v; } if (klass->IsValueType()) @@ -565,10 +579,12 @@ Variant MUtils::UnboxVariant(MObject* value) type.Struct.Unbox(v.AsBlob.Data, value); return v; } - return Variant(value); } - return Variant(value); + auto variant = Variant(value); + if (releaseHandle) + MUtils::FreeManaged(value); + return variant; } MObject* MUtils::BoxVariant(const Variant& value) diff --git a/Source/Engine/Scripting/ManagedCLR/MUtils.h b/Source/Engine/Scripting/ManagedCLR/MUtils.h index f642681d2..77d079efe 100644 --- a/Source/Engine/Scripting/ManagedCLR/MUtils.h +++ b/Source/Engine/Scripting/ManagedCLR/MUtils.h @@ -39,7 +39,7 @@ namespace MUtils extern FLAXENGINE_API MTypeObject* BoxScriptingTypeHandle(const ScriptingTypeHandle& value); extern FLAXENGINE_API VariantType UnboxVariantType(MType* type); extern FLAXENGINE_API MTypeObject* BoxVariantType(const VariantType& value); - extern FLAXENGINE_API Variant UnboxVariant(MObject* value); + extern FLAXENGINE_API Variant UnboxVariant(MObject* value, bool releaseHandle = false); extern FLAXENGINE_API MObject* BoxVariant(const Variant& value); } @@ -51,8 +51,41 @@ struct MConverter void Unbox(T& result, MObject* data); void ToManagedArray(MArray* result, const Span& data); void ToNativeArray(Span& result, const MArray* data); + void FreeManaged(MObject* data); }; +#if USE_NETCORE +// Special case for boolean values, the handles are fixed and should not be removed +template<> +struct MConverter +{ + MObject* Box(const bool& data, const MClass* klass) + { + return MCore::Object::Box((void*)&data, klass); + } + + void Unbox(bool& result, MObject* data) + { + if (data) + Platform::MemoryCopy(&result, MCore::Object::Unbox(data), sizeof(bool)); + } + + void ToManagedArray(MArray* result, const Span& data) + { + Platform::MemoryCopy(MCore::Array::GetAddress(result), data.Get(), data.Length() * sizeof(bool)); + } + + void ToNativeArray(Span& result, const MArray* data) + { + Platform::MemoryCopy(result.Get(), MCore::Array::GetAddress(data), result.Length() * sizeof(bool)); + } + + void FreeManaged(MObject* data) + { + } +}; +#endif + // Converter for POD types (that can use raw memory copy). template struct MConverter, TNot::Type>>>::Value>::Type> @@ -77,6 +110,11 @@ struct MConverter, TNot { MObject* Box(const String& data, const MClass* klass) { -#if USE_NETCORE +/*#if USE_NETCORE MString* str = MUtils::ToString(data); return MCore::Object::Box(str, klass); -#else +#else*/ return (MObject*)MUtils::ToString(data); -#endif +//#endif } void Unbox(String& result, MObject* data) { -#if USE_NETCORE +/*#if USE_NETCORE MString* str = (MString*)MCore::Object::Unbox(data); result = MUtils::ToString(str); -#else +#else*/ result = MUtils::ToString((MString*)data); -#endif +//#endif } void ToManagedArray(MArray* result, const Span& data) @@ -120,6 +158,13 @@ struct MConverter for (int32 i = 0; i < result.Length(); i++) MUtils::ToString(dataPtr[i], result.Get()[i]); } + + void FreeManaged(MObject* data) + { + //MString* str = (MString*)MCore::Object::Unbox(data); + //MCore::GCHandle::Free(*(MGCHandle*)&str); + MCore::GCHandle::Free(*(MGCHandle*)&data); + } }; // Converter for StringAnsi. @@ -153,6 +198,11 @@ struct MConverter for (int32 i = 0; i < result.Length(); i++) MUtils::ToString(dataPtr[i], result.Get()[i]); } + + void FreeManaged(MObject* data) + { + MCore::GCHandle::Free(*(MGCHandle*)&data); + } }; // Converter for StringView. @@ -186,6 +236,11 @@ struct MConverter for (int32 i = 0; i < result.Length(); i++) MUtils::ToString(dataPtr[i], result.Get()[i]); } + + void FreeManaged(MObject* data) + { + MCore::GCHandle::Free(*(MGCHandle*)&data); + } }; // Converter for Variant. @@ -219,6 +274,10 @@ struct MConverter for (int32 i = 0; i < result.Length(); i++) result.Get()[i] = MUtils::UnboxVariant(dataPtr[i]); } + + void FreeManaged(MObject* data) + { + } }; // Converter for Scripting Objects (collection of pointers). @@ -252,6 +311,10 @@ struct MConverter::Va for (int32 i = 0; i < result.Length(); i++) result.Get()[i] = (T*)ScriptingObject::ToNative(dataPtr[i]); } + + void FreeManaged(MObject* data) + { + } }; // Converter for Scripting Objects (collection of values). @@ -273,6 +336,10 @@ struct MConverter::Val MCore::GC::WriteArrayRef(result, Span(objects, data.Length())); Allocator::Free(objects); } + + void FreeManaged(MObject* data) + { + } }; // Converter for ScriptingObject References. @@ -309,6 +376,10 @@ struct MConverter> for (int32 i = 0; i < result.Length(); i++) result.Get()[i] = (T*)ScriptingObject::ToNative(dataPtr[i]); } + + void FreeManaged(MObject* data) + { + } }; // Converter for Asset References. @@ -345,6 +416,10 @@ struct MConverter> for (int32 i = 0; i < result.Length(); i++) result.Get()[i] = (T*)ScriptingObject::ToNative(dataPtr[i]); } + + void FreeManaged(MObject* data) + { + } }; // TODO: use MarshalAs=Guid on SoftAssetReference to pass guid over bindings and not load asset in glue code @@ -370,6 +445,10 @@ struct MConverter> for (int32 i = 0; i < result.Length(); i++) result.Get()[i] = (T*)ScriptingObject::ToNative(dataPtr[i]); } + + void FreeManaged(MObject* data) + { + } }; // Converter for Array. @@ -395,6 +474,10 @@ struct MConverter> Span resultSpan(result.Get(), length); converter.ToNativeArray(resultSpan, array); } + + void FreeManaged(MObject* data) + { + } }; namespace MUtils @@ -435,6 +518,7 @@ namespace MUtils /// /// Unboxes MObject to the native value of the given type. /// + /// template T Unbox(MObject* object) { @@ -444,6 +528,32 @@ namespace MUtils return result; } + /// + /// Unboxes MObject to the native value of the given type. + /// + /// The object. + /// True if boxed managed handle should be released. + template + T Unbox(MObject* object, bool releaseHandle) + { + MConverter converter; + T result; + converter.Unbox(result, object); + if (releaseHandle) + converter.FreeManaged(object); + return result; + } + + /// + /// Releases the managed resources of a boxed object. + /// + template + void FreeManaged(MObject* object) + { + MConverter converter; + converter.FreeManaged(object); + } + /// /// Links managed array data to the unmanaged BytesContainer. /// diff --git a/Source/Engine/Scripting/Runtime/DotNet.cpp b/Source/Engine/Scripting/Runtime/DotNet.cpp index 1c8c2bcdd..59c8d7f14 100644 --- a/Source/Engine/Scripting/Runtime/DotNet.cpp +++ b/Source/Engine/Scripting/Runtime/DotNet.cpp @@ -883,7 +883,6 @@ bool MAssembly::LoadCorlib() bool MAssembly::LoadImage(const String& assemblyPath, const StringView& nativePath) { - // TODO: Use new hostfxr delegate load_assembly_bytes? (.NET 8+) // Open .Net assembly static void* LoadAssemblyImagePtr = GetStaticMethodPointer(TEXT("LoadAssemblyImage")); _handle = CallStaticMethod(LoadAssemblyImagePtr, assemblyPath.Get()); diff --git a/Source/Engine/Tests/TestScripting.cpp b/Source/Engine/Tests/TestScripting.cpp index 506291b36..23c99ef88 100644 --- a/Source/Engine/Tests/TestScripting.cpp +++ b/Source/Engine/Tests/TestScripting.cpp @@ -38,7 +38,7 @@ TEST_CASE("Scripting") CHECK(method); MObject* result = method->Invoke(nullptr, nullptr, nullptr); CHECK(result); - int32 resultValue = MUtils::Unbox(result); + int32 resultValue = MUtils::Unbox(result, true); CHECK(resultValue == 0); } diff --git a/Source/Engine/Visject/VisjectGraph.cpp b/Source/Engine/Visject/VisjectGraph.cpp index 7d3505d41..680db2137 100644 --- a/Source/Engine/Visject/VisjectGraph.cpp +++ b/Source/Engine/Visject/VisjectGraph.cpp @@ -745,7 +745,7 @@ void VisjectExecutor::ProcessGroupPacking(Box* box, Node* node, Value& value) StringAsANSI<40> fieldNameAnsi(*fieldName, fieldName.Length()); auto field = mclass->GetField(fieldNameAnsi.Get()); if (field) - value = MUtils::UnboxVariant(field->GetValueBoxed(instance)); + value = MUtils::UnboxVariant(field->GetValueBoxed(instance), true); break; } } diff --git a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.CSharp.cs b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.CSharp.cs index 4812853ad..3468fab4c 100644 --- a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.CSharp.cs +++ b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.CSharp.cs @@ -711,7 +711,12 @@ namespace Flax.Build.Bindings if (parameterInfo.IsOut) contents.Append("out "); else if (parameterInfo.IsRef || UsePassByReference(buildData, parameterInfo.Type, caller)) - contents.Append("ref "); + { + if (parameterInfo.IsConst || parameterInfo.Type.IsConst) + contents.Append("in "); + else + contents.Append("ref "); + } // Out parameters that need additional converting will be converted at the native side (eg. object reference) if (parameterInfo.IsOut && !string.IsNullOrEmpty(GenerateCSharpManagedToNativeConverter(buildData, parameterInfo.Type, caller))) @@ -754,7 +759,12 @@ namespace Flax.Build.Bindings if (parameterInfo.IsOut) contents.Append("out "); else if (parameterInfo.IsRef || UsePassByReference(buildData, parameterInfo.Type, caller)) - contents.Append("ref "); + { + if (parameterInfo.IsConst || parameterInfo.Type.IsConst) + contents.Append("in "); + else + contents.Append("/*xcvg1*/ ref "); + } contents.Append(nativeType); contents.Append(' '); contents.Append(parameterInfo.Name); @@ -802,7 +812,17 @@ namespace Flax.Build.Bindings if (parameterInfo.IsOut) contents.Append("out "); else if (parameterInfo.IsRef || UsePassByReference(buildData, parameterInfo.Type, caller)) - contents.Append("ref "); + { + if (isSetter || parameterInfo.Type.IsConst) + contents.Append("in "); + else + { + //CanUpdate(BehaviorUpdateContext + if (parameterInfo.Name == "context" && functionInfo.Name == "CanUpdate") + separator = separator; + contents.Append("/*xcvg2*/ ref "); + } + } var convertFunc = GenerateCSharpManagedToNativeConverter(buildData, parameterInfo.Type, caller); var paramName = isSetter ? "value" : parameterInfo.Name; @@ -836,7 +856,12 @@ namespace Flax.Build.Bindings if (parameterInfo.IsOut) contents.Append("out "); else if (parameterInfo.IsRef || UsePassByReference(buildData, parameterInfo.Type, caller)) - contents.Append("ref "); + { + if (parameterInfo.IsConst ||parameterInfo.Type.IsConst) + contents.Append("in "); + else + contents.Append("ref "); + } // Pass value contents.Append(parameterInfo.DefaultValue); @@ -1369,7 +1394,12 @@ namespace Flax.Build.Bindings if (parameterInfo.IsOut) contents.Append("out "); else if (parameterInfo.IsRef) - contents.Append("ref "); + { + if (parameterInfo.IsConst || parameterInfo.Type.IsConst) + contents.Append("in "); + else + contents.Append("/*faffaf1*/ ref "); + } else if (parameterInfo.IsThis) contents.Append("this "); else if (parameterInfo.IsParams) @@ -1431,7 +1461,7 @@ namespace Flax.Build.Bindings if (parameterInfo.IsOut) contents.Append("out "); else if (parameterInfo.IsRef) - contents.Append("ref "); + contents.Append("/*faffaf2*/ ref "); else if (parameterInfo.IsThis) contents.Append("this "); else if (parameterInfo.IsParams) @@ -1522,8 +1552,8 @@ namespace Flax.Build.Bindings public static class ManagedToNative { public static {{classInfo.Name}} ConvertToManaged(IntPtr unmanaged) => Unsafe.As<{{classInfo.Name}}>(ManagedHandleMarshaller.NativeToManaged.ConvertToManaged(unmanaged)); - public static IntPtr ConvertToUnmanaged({{classInfo.Name}} managed) => managed != null ? ManagedHandle.ToIntPtr(managed, GCHandleType.Weak) : IntPtr.Zero; - public static void Free(IntPtr unmanaged) {} + public static IntPtr ConvertToUnmanaged({{classInfo.Name}} managed) => managed != null ? ManagedHandle.ToIntPtr(managed/*, GCHandleType.Weak*/) : IntPtr.Zero; + public static void Free(IntPtr unmanaged) => ManagedHandleMarshaller.Free(unmanaged); } #if FLAX_EDITOR [HideInEditor] @@ -1708,7 +1738,7 @@ namespace Flax.Build.Bindings { var managedType = GenerateCSharpNativeToManaged(buildData, marshalType.GenericArgs[0], structureInfo); toManagedContent.AppendLine($"unmanaged.{fieldInfo.Name} != IntPtr.Zero ? Unsafe.As<{managedType}>(ManagedHandle.FromIntPtr(unmanaged.{fieldInfo.Name}).Target) : null;"); - toNativeContent.AppendLine($"managed.{fieldInfo.Name} != null ? ManagedHandle.ToIntPtr(managed.{fieldInfo.Name}, GCHandleType.Weak) : IntPtr.Zero;"); + toNativeContent.AppendLine($"managed.{fieldInfo.Name} != null ? ManagedHandle.ToIntPtr(managed.{fieldInfo.Name}/*, GCHandleType.Weak*/) : IntPtr.Zero; // plaa 1"); freeContents.AppendLine($"if (unmanaged.{fieldInfo.Name} != IntPtr.Zero) {{ ManagedHandle.FromIntPtr(unmanaged.{fieldInfo.Name}).Free(); }}"); // Permanent ScriptingObject handle is passed from native side, do not release it @@ -1717,7 +1747,7 @@ namespace Flax.Build.Bindings else if (marshalType.Type == "ScriptingObject") { toManagedContent.AppendLine($"unmanaged.{fieldInfo.Name} != IntPtr.Zero ? Unsafe.As(ManagedHandle.FromIntPtr(unmanaged.{fieldInfo.Name}).Target) : null;"); - toNativeContent.AppendLine($"managed.{fieldInfo.Name} != null ? ManagedHandle.ToIntPtr(managed.{fieldInfo.Name}, GCHandleType.Weak) : IntPtr.Zero;"); + toNativeContent.AppendLine($"managed.{fieldInfo.Name} != null ? ManagedHandle.ToIntPtr(managed.{fieldInfo.Name}/*, GCHandleType.Weak*/) : IntPtr.Zero; // plaa 2"); freeContents.AppendLine($"if (unmanaged.{fieldInfo.Name} != IntPtr.Zero) {{ ManagedHandle.FromIntPtr(unmanaged.{fieldInfo.Name}).Free(); }}"); // Permanent ScriptingObject handle is passed from native side, do not release it @@ -1726,7 +1756,7 @@ namespace Flax.Build.Bindings else if (marshalType.IsPtr && originalType != "IntPtr" && !originalType.EndsWith("*")) { toManagedContent.AppendLine($"unmanaged.{fieldInfo.Name} != IntPtr.Zero ? Unsafe.As<{originalType}>(ManagedHandle.FromIntPtr(unmanaged.{fieldInfo.Name}).Target) : null;"); - toNativeContent.AppendLine($"managed.{fieldInfo.Name} != null ? ManagedHandle.ToIntPtr(managed.{fieldInfo.Name}, GCHandleType.Weak) : IntPtr.Zero;"); + toNativeContent.AppendLine($"managed.{fieldInfo.Name} != null ? ManagedHandle.ToIntPtr(managed.{fieldInfo.Name}/*, GCHandleType.Weak*/) : IntPtr.Zero; // plaa 3"); freeContents.AppendLine($"if (unmanaged.{fieldInfo.Name} != IntPtr.Zero) {{ ManagedHandle.FromIntPtr(unmanaged.{fieldInfo.Name}).Free(); }}"); // Permanent ScriptingObject handle is passed from native side, do not release it @@ -1735,7 +1765,7 @@ namespace Flax.Build.Bindings else if (marshalType.Type == "Dictionary") { toManagedContent.AppendLine($"unmanaged.{fieldInfo.Name} != IntPtr.Zero ? Unsafe.As<{originalType}>(ManagedHandle.FromIntPtr(unmanaged.{fieldInfo.Name}).Target) : null;"); - toNativeContent.AppendLine($"ManagedHandle.ToIntPtr(managed.{fieldInfo.Name}, GCHandleType.Weak);"); + toNativeContent.AppendLine($"ManagedHandle.ToIntPtr(managed.{fieldInfo.Name}/*, GCHandleType.Weak*/);"); freeContents.AppendLine($"if (unmanaged.{fieldInfo.Name} != IntPtr.Zero) {{ ManagedHandle.FromIntPtr(unmanaged.{fieldInfo.Name}).Free(); }}"); freeContents2.AppendLine($"if (unmanaged.{fieldInfo.Name} != IntPtr.Zero) {{ ManagedHandle.FromIntPtr(unmanaged.{fieldInfo.Name}).Free(); }}"); } @@ -1757,7 +1787,7 @@ namespace Flax.Build.Bindings { // Array elements passed as GCHandles toManagedContent.AppendLine($"unmanaged.{fieldInfo.Name} != IntPtr.Zero ? NativeInterop.GCHandleArrayToManagedArray<{originalElementType}>(Unsafe.As(ManagedHandle.FromIntPtr(unmanaged.{fieldInfo.Name}).Target)) : null;"); - toNativeContent.AppendLine($"managed.{fieldInfo.Name}?.Length > 0 ? ManagedHandle.ToIntPtr(NativeInterop.ManagedArrayToGCHandleWrappedArray(managed.{fieldInfo.Name}), GCHandleType.Weak) : IntPtr.Zero;"); + toNativeContent.AppendLine($"managed.{fieldInfo.Name}?.Length > 0 ? ManagedHandle.ToIntPtr(NativeInterop.ManagedArrayToGCHandleWrappedArray(managed.{fieldInfo.Name})/*, GCHandleType.Weak*/) : IntPtr.Zero;"); freeContents.AppendLine($"if (unmanaged.{fieldInfo.Name} != IntPtr.Zero) {{ ManagedHandle handle = ManagedHandle.FromIntPtr(unmanaged.{fieldInfo.Name}); Span ptrs = (Unsafe.As(handle.Target)).ToSpan(); foreach (var ptr in ptrs) {{ if (ptr != IntPtr.Zero) {{ ManagedHandle.FromIntPtr(ptr).Free(); }} }} (Unsafe.As(handle.Target)).Free(); handle.Free(); }}"); // Permanent ScriptingObject handle is passed from native side, do not release it @@ -1775,7 +1805,7 @@ namespace Flax.Build.Bindings else if (marshalType.Type == "Version") { toManagedContent.AppendLine($"unmanaged.{fieldInfo.Name} != IntPtr.Zero ? Unsafe.As<{originalType}>(ManagedHandle.FromIntPtr(unmanaged.{fieldInfo.Name}).Target) : null;"); - toNativeContent.AppendLine($"ManagedHandle.ToIntPtr(managed.{fieldInfo.Name}, GCHandleType.Weak);"); + toNativeContent.AppendLine($"ManagedHandle.ToIntPtr(managed.{fieldInfo.Name}/*, GCHandleType.Weak*/);"); freeContents.AppendLine($"if (unmanaged.{fieldInfo.Name} != IntPtr.Zero) {{ ManagedHandle.FromIntPtr(unmanaged.{fieldInfo.Name}).Free(); }}"); freeContents2.AppendLine($"if (unmanaged.{fieldInfo.Name} != IntPtr.Zero) {{ ManagedHandle.FromIntPtr(unmanaged.{fieldInfo.Name}).Free(); }}"); } @@ -1824,66 +1854,77 @@ namespace Flax.Build.Bindings toManagedContent.Append("return managed;"); } + string hideInEditorAttribute = (buildData.Target != null & buildData.Target.IsEditor) ? "[HideInEditor]" : ""; + string marshalManagedType = structureInfo.Name; + string marshalNativeType = $"{structureInfo.Name}Internal"; contents.AppendLine(string.Join(Environment.NewLine + indent, (indent + $$""" /// - /// Marshaller for type . + /// Marshaller for type . /// - {{InsertHideInEditorSection()}} - [CustomMarshaller(typeof({{structureInfo.Name}}), MarshalMode.ManagedToUnmanagedIn, typeof({{marshallerFullName}}.ManagedToNative))] - [CustomMarshaller(typeof({{structureInfo.Name}}), MarshalMode.UnmanagedToManagedOut, typeof({{marshallerFullName}}.ManagedToNative))] - [CustomMarshaller(typeof({{structureInfo.Name}}), MarshalMode.ElementIn, typeof({{marshallerFullName}}.ManagedToNative))] - [CustomMarshaller(typeof({{structureInfo.Name}}), MarshalMode.ManagedToUnmanagedOut, typeof({{marshallerFullName}}.NativeToManaged))] - [CustomMarshaller(typeof({{structureInfo.Name}}), MarshalMode.UnmanagedToManagedIn, typeof({{marshallerFullName}}.NativeToManaged))] - [CustomMarshaller(typeof({{structureInfo.Name}}), MarshalMode.ElementOut, typeof({{marshallerFullName}}.NativeToManaged))] - [CustomMarshaller(typeof({{structureInfo.Name}}), MarshalMode.ManagedToUnmanagedRef, typeof({{marshallerFullName}}.Bidirectional))] - [CustomMarshaller(typeof({{structureInfo.Name}}), MarshalMode.UnmanagedToManagedRef, typeof({{marshallerFullName}}.Bidirectional))] - [CustomMarshaller(typeof({{structureInfo.Name}}), MarshalMode.ElementRef, typeof({{marshallerFullName}}))] + {{hideInEditorAttribute}} + [CustomMarshaller(typeof({{marshalManagedType}}), MarshalMode.ManagedToUnmanagedIn, typeof({{marshallerFullName}}.ManagedToNative))] + [CustomMarshaller(typeof({{marshalManagedType}}), MarshalMode.UnmanagedToManagedOut, typeof({{marshallerFullName}}.ManagedToNative))] + [CustomMarshaller(typeof({{marshalManagedType}}), MarshalMode.ElementIn, typeof({{marshallerFullName}}.ManagedToNative))] + [CustomMarshaller(typeof({{marshalManagedType}}), MarshalMode.ManagedToUnmanagedOut, typeof({{marshallerFullName}}.NativeToManaged))] + [CustomMarshaller(typeof({{marshalManagedType}}), MarshalMode.UnmanagedToManagedIn, typeof({{marshallerFullName}}.NativeToManaged))] + [CustomMarshaller(typeof({{marshalManagedType}}), MarshalMode.ElementOut, typeof({{marshallerFullName}}.NativeToManaged))] + [CustomMarshaller(typeof({{marshalManagedType}}), MarshalMode.ManagedToUnmanagedRef, typeof({{marshallerFullName}}.Bidirectional))] + [CustomMarshaller(typeof({{marshalManagedType}}), MarshalMode.UnmanagedToManagedRef, typeof({{marshallerFullName}}.Bidirectional))] + [CustomMarshaller(typeof({{marshalManagedType}}), MarshalMode.ElementRef, typeof({{marshallerFullName}}))] {{GenerateCSharpAccessLevel(structureInfo.Access)}}static unsafe class {{marshallerName}} { #pragma warning disable 1591 #pragma warning disable 618 {{structContents.Replace("\n", Environment.NewLine + " ").ToString().TrimEnd()}} - {{InsertHideInEditorSection()}} + {{hideInEditorAttribute}} public static class NativeToManaged { - public static {{structureInfo.Name}} ConvertToManaged({{structureInfo.Name}}Internal unmanaged) => {{marshallerFullName}}.ToManaged(unmanaged); - public static {{structureInfo.Name}}Internal ConvertToUnmanaged({{structureInfo.Name}} managed) => {{marshallerFullName}}.ToNative(managed); - public static void Free({{structureInfo.Name}}Internal unmanaged) + public static {{marshalManagedType}} ConvertToManaged({{marshalNativeType}} unmanaged) => {{marshallerFullName}}.ToManaged(unmanaged); + public static {{marshalNativeType}} ConvertToUnmanaged({{marshalManagedType}} managed) => {{marshallerFullName}}.ToNative(managed); + public static void Free({{marshalNativeType}} unmanaged) { {{freeContents2.Replace("\n", Environment.NewLine + " ").ToString().TrimEnd()}} } } - {{InsertHideInEditorSection()}} + {{hideInEditorAttribute}} public static class ManagedToNative { - public static {{structureInfo.Name}} ConvertToManaged({{structureInfo.Name}}Internal unmanaged) => {{marshallerFullName}}.ToManaged(unmanaged); - public static {{structureInfo.Name}}Internal ConvertToUnmanaged({{structureInfo.Name}} managed) => {{marshallerFullName}}.ToNative(managed); - public static void Free({{structureInfo.Name}}Internal unmanaged) => {{marshallerFullName}}.Free(unmanaged); + public static {{marshalManagedType}} ConvertToManaged({{marshalNativeType}} unmanaged) => {{marshallerFullName}}.ToManaged(unmanaged); + public static {{marshalNativeType}} ConvertToUnmanaged({{marshalManagedType}} managed) => {{marshallerFullName}}.ToNative(managed); + public static void Free({{marshalNativeType}} unmanaged) => {{marshallerFullName}}.Free(unmanaged); } - {{InsertHideInEditorSection()}} - public struct Bidirectional + {{hideInEditorAttribute}} + public ref struct Bidirectional { - {{structureInfo.Name}} managed; - {{structureInfo.Name}}Internal unmanaged; - public void FromManaged({{structureInfo.Name}} managed) => this.managed = managed; - public {{structureInfo.Name}}Internal ToUnmanaged() { unmanaged = {{marshallerFullName}}.ToNative(managed); return unmanaged; } - public void FromUnmanaged({{structureInfo.Name}}Internal unmanaged) => this.unmanaged = unmanaged; - public {{structureInfo.Name}} ToManaged() { managed = {{marshallerFullName}}.ToManaged(unmanaged); return managed; } - public void Free() => NativeToManaged.Free(unmanaged); + {{marshalManagedType}} managed; + {{marshalNativeType}} unmanaged; + public void FromManaged({{marshalManagedType}} managed) => this.managed = managed; + public {{marshalNativeType}} ToUnmanaged() { unmanaged = {{marshallerFullName}}.ToNative(managed); return unmanaged; } + public void FromUnmanaged({{marshalNativeType}} unmanaged) + { + if (!unmanaged.Equals(this.unmanaged)) + {{marshallerName}}.Free(this.unmanaged); // Release temporary handles before replacing them with permanent handles + this.unmanaged = unmanaged; + } + public {{marshalManagedType}} ToManaged() { managed = {{marshallerFullName}}.ToManaged(unmanaged); return managed; } + public void Free() + { + NativeToManaged.Free(unmanaged); + } } - internal static {{structureInfo.Name}} ConvertToManaged({{structureInfo.Name}}Internal unmanaged) => ToManaged(unmanaged); - internal static {{structureInfo.Name}}Internal ConvertToUnmanaged({{structureInfo.Name}} managed) => ToNative(managed); - internal static void Free({{structureInfo.Name}}Internal unmanaged) + internal static {{marshalManagedType}} ConvertToManaged({{marshalNativeType}} unmanaged) => ToManaged(unmanaged); + internal static {{marshalNativeType}} ConvertToUnmanaged({{marshalManagedType}} managed) => ToNative(managed); + internal static void Free({{marshalNativeType}} unmanaged) { {{freeContents.Replace("\n", Environment.NewLine + " ").ToString().TrimEnd()}} } - internal static {{structureInfo.Name}} ToManaged({{structureInfo.Name}}Internal unmanaged) + internal static {{marshalManagedType}} ToManaged({{marshalNativeType}} unmanaged) { {{toManagedContent.Replace("\n", Environment.NewLine + " ").ToString().TrimEnd()}} } - internal static {{structureInfo.Name}}Internal ToNative({{structureInfo.Name}} managed) + internal static {{marshalNativeType}} ToNative({{marshalManagedType}} managed) { {{toNativeContent.Replace("\n", Environment.NewLine + " ").ToString().TrimEnd()}} } @@ -1892,13 +1933,6 @@ namespace Flax.Build.Bindings } """).Split(new char[] { '\n', '\r' }, StringSplitOptions.RemoveEmptyEntries))); - string InsertHideInEditorSection() - { - return (buildData.Target != null & buildData.Target.IsEditor) ? $$""" - [HideInEditor] - """ : ""; - } - PutStringBuilder(toManagedContent); PutStringBuilder(toNativeContent); PutStringBuilder(freeContents); @@ -2231,7 +2265,7 @@ namespace Flax.Build.Bindings if (parameterInfo.IsOut) contents.Append("out "); else if (parameterInfo.IsRef) - contents.Append("ref "); + contents.Append("/*faffaf3*/ ref "); else if (parameterInfo.IsThis) contents.Append("this "); else if (parameterInfo.IsParams) diff --git a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cpp.cs b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cpp.cs index 570ca4dcc..7c7d13198 100644 --- a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cpp.cs +++ b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cpp.cs @@ -119,7 +119,8 @@ namespace Flax.Build.Bindings // Pass as pointer to local variable converted for managed runtime if (paramType.IsPtr) result = string.Format(nativeToManaged, '*' + paramName); - contents.Append($" auto __param_{paramName} = {result};").AppendLine(); + contents.Append($" auto __param_orig_{paramName} = {result};").AppendLine(); + contents.Append($" auto __param_{paramName} = __param_orig_{paramName};").AppendLine(); result = $"&__param_{paramName}"; useLocalVar = true; } @@ -998,6 +999,11 @@ namespace Flax.Build.Bindings } #endif + if (functionInfo.UniqueName == "SetView" && callerName == "SceneRenderTaskInternal") + callerName = callerName; + if (functionInfo.UniqueName == "SetDriveControl") + callerName = callerName; + // Setup function binding glue to ensure that wrapper method signature matches for C++ and C# functionInfo.Glue = new FunctionInfo.GlueInfo { @@ -1155,6 +1161,7 @@ namespace Flax.Build.Bindings }, IsOut = parameterInfo.IsOut, IsRef = isRefOut || parameterInfo.Type.IsRef, + IsConst = true, }); } #endif @@ -1560,7 +1567,8 @@ namespace Flax.Build.Bindings if (paramIsRef) { // Pass as pointer to value when using ref/out parameter - contents.Append($" auto __param_{parameterInfo.Name} = {paramValue};").AppendLine(); + contents.Append($" auto __param_orig_{parameterInfo.Name} = {paramValue};").AppendLine(); + contents.Append($" auto __param_{parameterInfo.Name} = __param_orig_{parameterInfo.Name};").AppendLine(); paramValue = $"&__param_{parameterInfo.Name}"; useLocalVar = true; } @@ -1640,6 +1648,78 @@ namespace Flax.Build.Bindings else if (!passAsParamPtr) paramValue = '*' + paramValue; contents.Append($" {parameterInfo.Name} = {paramValue};").AppendLine(); + + // Release temporary GCHandles + /*if (passAsParamPtr) + { + //contents.Append($" if (params[{i}] != (void*)&__param_{parameterInfo.Name})").AppendLine(); + contents.Append($" {{").AppendLine(); + contents.Append($" auto __param{i}_handle = *(MGCHandle*)¶ms[{i}];").AppendLine(); + contents.Append($" MCore::GCHandle::Free(__param{i}_handle);").AppendLine(); + contents.Append($" }}").AppendLine(); + } + else + { + //contents.Append($" if (params[{i}] != (void*)&__param_{parameterInfo.Name})").AppendLine(); + contents.Append($" {{").AppendLine(); + contents.Append($" auto __param{i}_handle = *(MGCHandle*)¶ms[{i}];").AppendLine(); + contents.Append($" MCore::GCHandle::Free(__param{i}_handle);").AppendLine(); + contents.Append($" }}").AppendLine(); + }*/ + } + } + } + + // Release GCHandles of boxed values + for (var i = 0; i < functionInfo.Parameters.Count; i++) + { + var parameterInfo = functionInfo.Parameters[i]; + var paramValue = GenerateCppWrapperNativeToBox(buildData, parameterInfo.Type, classInfo, out var apiType, parameterInfo.Name); + var paramIsRef = parameterInfo.IsRef || parameterInfo.IsOut; + + if (paramValue.Contains("MUtils::Box<")) // FIXME + { + if (paramIsRef) + { + //contents.Append($" MUtils::FreeManaged<{parameterInfo.Type.ToString(false)}>((MObject*)__param_{parameterInfo.Name});").AppendLine(); + /*if (useThunk) + { + // Release the original handle + contents.Append($" if (__param_orig_{parameterInfo.Name} != __param_{parameterInfo.Name}) //asdf1a").AppendLine(); + contents.Append($" MUtils::FreeManaged<{parameterInfo.Type.ToString(false)}>((MObject*)__param_{parameterInfo.Name});").AppendLine(); + }*/ + contents.Append($" MUtils::FreeManaged<{parameterInfo.Type.Type}>((MObject*)__param_{parameterInfo.Name});").AppendLine(); + if (useThunk) + { + // Release the original handle + contents.Append($" if (__param_orig_{parameterInfo.Name} != __param_{ parameterInfo.Name})").AppendLine(); + contents.Append($" MUtils::FreeManaged<{parameterInfo.Type.Type}>((MObject*)__param_orig_{parameterInfo.Name});").AppendLine(); + } + } + else if (apiType != null && !apiType.IsInBuild) + { + // int: ispod, isvaluetype + // vector: ispod, isvaluetype, isstruct + // guid: ispod, isvaluetype, isstruct, isinbuild + contents.Append($" MUtils::FreeManaged<{parameterInfo.Type.Type}>((MObject*)params[{i}]); //asdf1b").AppendLine(); + //contents.Append($" auto __param{i}_handle = *(MGCHandle*)¶ms[{i}]; // asdf1b").AppendLine(); + //contents.Append($" ASSERT((((unsigned long long)__param{i}_handle & 0xC000000000000000) >> 62) == 0);").AppendLine(); + //contents.Append($" MCore::GCHandle::Free(__param{i}_handle);").AppendLine(); + } + else if (apiType != null && !apiType.IsValueType) + { + if (parameterInfo.Type.Type.ToLower().Contains("string")) + apiType = apiType; + contents.Append($" MUtils::FreeManaged<{parameterInfo.Type.Type}>((MObject*)params[{i}]); //asdf1d").AppendLine(); + } + else //if (apiType != null) + { + if (parameterInfo.Type.Type.ToLower().Contains("string")) + apiType = apiType; + //contents.Append($" //asdf1c {parameterInfo.Type.Type}").AppendLine(); + contents.Append($" auto __param{i}_handle = *(MGCHandle*)¶ms[{i}]; // asdf1c").AppendLine(); + //contents.Append($" ASSERT((((unsigned long long)__param{i}_handle & 0xC000000000000000) >> 62) == 0);").AppendLine(); + contents.Append($" MCore::GCHandle::Free(__param{i}_handle);").AppendLine(); } } } @@ -2095,6 +2175,22 @@ namespace Flax.Build.Bindings else if (!passAsParamPtr) paramValue = '*' + paramValue; contents.Append($" arg{i} = {paramValue};").AppendLine(); + + // Release temporary GCHandles + if (CppParamsThatNeedConversion[i]) + { + // Release the original handle + contents.Append($" if (&__param_orig_arg{i} != &__param_arg{i})").AppendLine(); + contents.Append($" FreeManaged(({managedType})__param_arg{i});").AppendLine(); + } + /*contents.Append($" FreeManaged(({managedType})__param_arg{i}); //hmm1b").AppendLine(); + if (CppParamsThatNeedConversion[i]) + { + // Release the original handle + contents.Append($" if (&__param_orig_arg{i} != &__param_arg{i})").AppendLine(); + contents.Append($" FreeManaged(({managedType})__param_orig_arg{i});").AppendLine(); + }*/ + paramType.IsRef = true; } } @@ -2392,6 +2488,7 @@ namespace Flax.Build.Bindings { Type = fieldInfo.Type, Name = "value", + IsConst = true, }, }, ReturnType = new TypeInfo @@ -3001,6 +3098,7 @@ namespace Flax.Build.Bindings header.AppendLine("namespace {"); header.AppendFormat("{0} ToManaged(const {1}& value);", wrapperName, fullName).AppendLine(); header.AppendFormat("{1} ToNative(const {0}& value);", wrapperName, fullName).AppendLine(); + header.AppendFormat("void FreeManaged(const {0}& value);", wrapperName).AppendLine(); header.AppendLine("}"); // Generate MConverter for a structure @@ -3016,7 +3114,10 @@ namespace Flax.Build.Bindings header.AppendFormat(" DLLEXPORT USED void Unbox({0}& result, MObject* data)", fullName).AppendLine(); header.Append(" {").AppendLine(); - header.AppendFormat(" result = ToNative(*reinterpret_cast<{0}*>(MCore::Object::Unbox(data)));", wrapperName).AppendLine(); + //header.AppendFormat(" result = ToNative(*reinterpret_cast<{0}*>(MCore::Object::Unbox(data)));", wrapperName).AppendLine(); + header.AppendFormat(" auto managed = *reinterpret_cast<{0}*>(MCore::Object::Unbox(data));", wrapperName).AppendLine(); + header.AppendFormat(" result = ToNative(managed);", wrapperName).AppendLine(); + header.AppendFormat(" ::FreeManaged(managed);", wrapperName).AppendLine(); header.Append(" }").AppendLine(); header.AppendFormat(" DLLEXPORT USED void ToManagedArray(MArray* result, const Span<{0}>& data)", fullName).AppendLine(); @@ -3036,6 +3137,13 @@ namespace Flax.Build.Bindings header.Append(" for (int32 i = 0; i < result.Length(); i++)").AppendLine(); header.Append(" result[i] = ToNative(dataPtr[i]);").AppendLine(); header.Append(" }").AppendLine(); + header.AppendFormat(" void FreeManaged(MObject* data)", fullName).AppendLine(); + header.Append(" {").AppendLine(); + //header.AppendFormat(" auto managed = MCore::Object::Unbox(data);", wrapperName).AppendLine(); + //header.AppendFormat(" ::FreeManaged(*reinterpret_cast<{0}*>(managed));", wrapperName).AppendLine(); + header.AppendFormat(" MCore::GCHandle::Free(*(MGCHandle*)&data);", wrapperName).AppendLine(); + header.Append(" }").AppendLine(); + header.Append('}').Append(';').AppendLine(); // Generate converting function native -> managed @@ -3110,8 +3218,47 @@ namespace Flax.Build.Bindings header.AppendFormat(" result.{0} = value.{0};", fieldInfo.Name).AppendLine(); else header.AppendFormat(" result.{0} = {1};", fieldInfo.Name, string.Format(wrapper, string.Format("value.{0}", fieldInfo.Name))).AppendLine(); + /*if (wrapper.Contains("::ToNative(")) // FIXME + { + header.Append($" auto __param{fieldInfo.Name}_handle = *(MGCHandle*)&value.{fieldInfo.Name};").AppendLine(); + header.Append($" ASSERT((((unsigned long long)__param{fieldInfo.Name}_handle & 0xC000000000000000) >> 62) == 0); // asdf3").AppendLine(); + header.Append($" MCore::GCHandle::Free(__param{fieldInfo.Name}_handle);").AppendLine(); + }*/ } header.Append(" return result;").AppendLine(); + header.Append('}').AppendLine(); + + // Generate release function for temporary managed handles + header.AppendLine(); + header.AppendFormat("void FreeManaged(const {0}& value)", wrapperName).AppendLine(); + header.Append('{').AppendLine(); + for (var i = 0; i < fields.Count; i++) + { + var fieldInfo = fields[i]; + if (fieldInfo.IsStatic || fieldInfo.IsConstexpr) + continue; + + CppNonPodTypesConvertingGeneration = true; + var wrapper = GenerateCppWrapperManagedToNative(buildData, fieldInfo.Type, apiType, out var managedType, out var fieldApiType, null, out _); + CppNonPodTypesConvertingGeneration = false; + + if (wrapper.Contains("::ToNative(")) // FIXME fieldApiType.IsScriptingObject + { + header.Append($" auto __param{fieldInfo.Name}_handle = *(MGCHandle*)&value.{fieldInfo.Name};").AppendLine(); + //header.Append($" ASSERT((((unsigned long long)__param{fieldInfo.Name}_handle & 0xC000000000000000) >> 62) == 0); // asdf3").AppendLine(); + header.Append($" if ((((unsigned long long)__param{fieldInfo.Name}_handle & 0xC000000000000000) >> 62) != 0) // asdf3").AppendLine(); + header.Append($" __param{fieldInfo.Name}_handle = __param{fieldInfo.Name}_handle;").AppendLine(); + + header.Append($" MCore::GCHandle::Free(__param{fieldInfo.Name}_handle);").AppendLine(); + } + } + if (classInfo != null) + { + header.Append($" auto __value_handle = *(MGCHandle*)&value;").AppendLine(); + header.Append($" ASSERT((((unsigned long long)__value_handle & 0xC000000000000000) >> 62) == 0); // asdf4").AppendLine(); + header.Append($" MCore::GCHandle::Free(__value_handle);").AppendLine(); + } + header.Append('}').AppendLine(); header.AppendLine("}"); } @@ -3190,6 +3337,11 @@ namespace Flax.Build.Bindings header.AppendFormat(" Unbox(result[i], dataPtr[i]);", fullName).AppendLine(); header.Append(" }").AppendLine(); + header.AppendFormat(" void FreeManaged(MObject* data) // honk1", fullName).AppendLine(); + header.Append(" {").AppendLine(); + //header.Append(" ::FreeManaged(reinterpret_cast(MCore::Object::Unbox(data)));").AppendLine(); + header.Append(" }").AppendLine(); + header.Append('}').Append(';').AppendLine(); } }