From 10c8454e8fadcbfd1e4875a8077d3f4b47bfe4ab Mon Sep 17 00:00:00 2001 From: Chandler Cox Date: Thu, 27 Jul 2023 10:30:27 -0500 Subject: [PATCH 01/19] Paste at same level of selected actor parent node instead of as child of selected actor for better behavior. --- Source/Editor/Modules/SceneEditingModule.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/Editor/Modules/SceneEditingModule.cs b/Source/Editor/Modules/SceneEditingModule.cs index 58c2f21dc..2b8bf718e 100644 --- a/Source/Editor/Modules/SceneEditingModule.cs +++ b/Source/Editor/Modules/SceneEditingModule.cs @@ -503,7 +503,7 @@ namespace FlaxEditor.Modules // Set paste target if only one actor is selected and no target provided if (pasteTargetActor == null && SelectionCount == 1 && Selection[0] is ActorNode actorNode) { - pasteTargetActor = actorNode.Actor; + pasteTargetActor = actorNode.Actor.Scene == actorNode.Actor ? actorNode.Actor : actorNode.Actor.Parent; } // Create paste action From d6bfcbcc3dd757abed8b5311b79c794cb24b37e5 Mon Sep 17 00:00:00 2001 From: Chandler Cox Date: Thu, 27 Jul 2023 10:49:29 -0500 Subject: [PATCH 02/19] Add for prefab --- Source/Editor/Windows/Assets/PrefabWindow.Actions.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/Editor/Windows/Assets/PrefabWindow.Actions.cs b/Source/Editor/Windows/Assets/PrefabWindow.Actions.cs index 3246e51b5..6de553a30 100644 --- a/Source/Editor/Windows/Assets/PrefabWindow.Actions.cs +++ b/Source/Editor/Windows/Assets/PrefabWindow.Actions.cs @@ -144,7 +144,7 @@ namespace FlaxEditor.Windows.Assets // Set paste target if only one actor is selected and no target provided if (pasteTargetActor == null && Selection.Count == 1 && Selection[0] is ActorNode actorNode) { - pasteTargetActor = actorNode.Actor; + pasteTargetActor = actorNode.Actor.IsPrefabRoot ? actorNode.Actor : actorNode.Actor.Parent; } // Create paste action From b35065ab3f0cdd211a4dcbf9d426af3802de59c2 Mon Sep 17 00:00:00 2001 From: Wiktor Kocielski Date: Mon, 7 Aug 2023 10:16:50 +0300 Subject: [PATCH 03/19] Fix annoying error that happens due to an oversight --- Source/Engine/Networking/NetworkReplicator.cpp | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/Source/Engine/Networking/NetworkReplicator.cpp b/Source/Engine/Networking/NetworkReplicator.cpp index 279391e31..4a0f9437d 100644 --- a/Source/Engine/Networking/NetworkReplicator.cpp +++ b/Source/Engine/Networking/NetworkReplicator.cpp @@ -1127,13 +1127,6 @@ bool NetworkReplicator::EndInvokeRPC(ScriptingObject* obj, const ScriptingTypeHa rpc.Info = *info; rpc.ArgsData.Copy(Span(argsStream->GetBuffer(), argsStream->GetPosition())); rpc.Targets.Copy(targetIds); -#if USE_EDITOR || !BUILD_RELEASE - auto it = Objects.Find(obj->GetID()); - if (it == Objects.End()) - { - LOG(Error, "Cannot invoke RPC method '{0}.{1}' on object '{2}' that is not registered in networking (use 'NetworkReplicator.AddObject').", type.ToString(), String(name), obj->GetID()); - } -#endif ObjectsLock.Unlock(); // Check if skip local execution (eg. server rpc called from client or client rpc with specific targets) @@ -1554,7 +1547,13 @@ void NetworkInternal::NetworkReplicatorUpdate() continue; auto it = Objects.Find(obj->GetID()); if (it == Objects.End()) + { +#if USE_EDITOR || !BUILD_RELEASE + if(!DespawnedObjects.Contains(obj->GetID())) + LOG(Error, "Cannot invoke RPC method '{0}.{1}' on object '{2}' that is not registered in networking (use 'NetworkReplicator.AddObject').", e.Name.First.ToString(), String(e.Name.Second), obj->GetID()); +#endif continue; + } auto& item = it->Item; // Send RPC message From f44156eb80edd04999e1a26aa077db8429a76671 Mon Sep 17 00:00:00 2001 From: Wiktor Kocielski <118038102+Withaust@users.noreply.github.com> Date: Sun, 13 Aug 2023 07:39:03 +0300 Subject: [PATCH 04/19] Remove another unnecessary exposed CommonValue.h --- Source/Engine/Particles/Graph/ParticleEmitterGraph.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Source/Engine/Particles/Graph/ParticleEmitterGraph.h b/Source/Engine/Particles/Graph/ParticleEmitterGraph.h index e184b0660..cfe259a72 100644 --- a/Source/Engine/Particles/Graph/ParticleEmitterGraph.h +++ b/Source/Engine/Particles/Graph/ParticleEmitterGraph.h @@ -8,7 +8,8 @@ #include "Engine/Particles/Types.h" #include "Engine/Particles/ParticlesSimulation.h" #include "Engine/Particles/ParticlesData.h" -#include "Engine/Core/Types/CommonValue.h" +#include "Engine/Core/Math/Vector4.h" +#include "Engine/Core/Types/BaseTypes.h" class ParticleEffect; From 3df044d07bd73888ca5aa4154419bf61ce4a976c Mon Sep 17 00:00:00 2001 From: Ari Vuollet Date: Sat, 12 Aug 2023 14:34:06 +0300 Subject: [PATCH 05/19] Add build option to change code optimization level in C# modules --- .../Flax.Build/Build/DotNet/Builder.DotNet.cs | 10 ++++++---- .../Flax.Build/Build/NativeCpp/BuildOptions.cs | 15 +++++++++++++-- .../Build/NativeCpp/Builder.NativeCpp.cs | 4 ++-- 3 files changed, 21 insertions(+), 8 deletions(-) diff --git a/Source/Tools/Flax.Build/Build/DotNet/Builder.DotNet.cs b/Source/Tools/Flax.Build/Build/DotNet/Builder.DotNet.cs index d6772379f..43c54a92d 100644 --- a/Source/Tools/Flax.Build/Build/DotNet/Builder.DotNet.cs +++ b/Source/Tools/Flax.Build/Build/DotNet/Builder.DotNet.cs @@ -255,11 +255,13 @@ namespace Flax.Build #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+"); + var optimize = buildData.Configuration == TargetConfiguration.Release; + if (buildData.TargetOptions.ScriptingAPI.Optimization.HasValue) + optimize = buildData.TargetOptions.ScriptingAPI.Optimization.Value; + args.Add(optimize ? "/optimize+" : "/optimize-"); +#if !USE_NETCORE args.Add(string.Format("/reference:\"{0}mscorlib.dll\"", referenceAssemblies)); #endif args.Add(string.Format("/out:\"{0}\"", outputFile)); diff --git a/Source/Tools/Flax.Build/Build/NativeCpp/BuildOptions.cs b/Source/Tools/Flax.Build/Build/NativeCpp/BuildOptions.cs index e612c0a95..72cefbfca 100644 --- a/Source/Tools/Flax.Build/Build/NativeCpp/BuildOptions.cs +++ b/Source/Tools/Flax.Build/Build/NativeCpp/BuildOptions.cs @@ -185,7 +185,7 @@ namespace Flax.Build.NativeCpp public string DepsFolder => Path.Combine(Globals.EngineRoot, "Source", "Platforms", Platform.Target.ToString(), "Binaries", "ThirdParty", Architecture.ToString()); /// - /// The scripting API building options. + /// The C# scripting API building options. /// public struct ScriptingAPIOptions { @@ -224,6 +224,11 @@ namespace Flax.Build.NativeCpp /// public CSharpNullableReferences CSharpNullableReferences = CSharpNullableReferences.Disable; + /// + /// Enable code optimization. + /// + public bool? Optimization; + public ScriptingAPIOptions() { } @@ -232,13 +237,19 @@ namespace Flax.Build.NativeCpp /// Adds the other options into this. /// /// The other. - public void Add(ScriptingAPIOptions other) + public void Add(ScriptingAPIOptions other, bool addBuildOptions = true) { Defines.AddRange(other.Defines); SystemReferences.AddRange(other.SystemReferences); FileReferences.AddRange(other.FileReferences); Analyzers.AddRange(other.Analyzers); IgnoreMissingDocumentationWarnings |= other.IgnoreMissingDocumentationWarnings; + + if (addBuildOptions) + { + if (other.Optimization.HasValue) + Optimization |= other.Optimization; + } } } diff --git a/Source/Tools/Flax.Build/Build/NativeCpp/Builder.NativeCpp.cs b/Source/Tools/Flax.Build/Build/NativeCpp/Builder.NativeCpp.cs index 225e46ba7..1e8550c4e 100644 --- a/Source/Tools/Flax.Build/Build/NativeCpp/Builder.NativeCpp.cs +++ b/Source/Tools/Flax.Build/Build/NativeCpp/Builder.NativeCpp.cs @@ -403,7 +403,7 @@ namespace Flax.Build moduleOptions.PrivateIncludePaths.AddRange(dependencyOptions.PublicIncludePaths); moduleOptions.Libraries.AddRange(dependencyOptions.Libraries); moduleOptions.DelayLoadLibraries.AddRange(dependencyOptions.DelayLoadLibraries); - moduleOptions.ScriptingAPI.Add(dependencyOptions.ScriptingAPI); + moduleOptions.ScriptingAPI.Add(dependencyOptions.ScriptingAPI, false); moduleOptions.ExternalModules.AddRange(dependencyOptions.ExternalModules); } } @@ -418,7 +418,7 @@ namespace Flax.Build moduleOptions.PublicIncludePaths.AddRange(dependencyOptions.PublicIncludePaths); moduleOptions.Libraries.AddRange(dependencyOptions.Libraries); moduleOptions.DelayLoadLibraries.AddRange(dependencyOptions.DelayLoadLibraries); - moduleOptions.ScriptingAPI.Add(dependencyOptions.ScriptingAPI); + moduleOptions.ScriptingAPI.Add(dependencyOptions.ScriptingAPI, false); moduleOptions.ExternalModules.AddRange(dependencyOptions.ExternalModules); } } From 4ce1f31f12d512010a86542c098742e0453d7335 Mon Sep 17 00:00:00 2001 From: Ari Vuollet Date: Sat, 12 Aug 2023 14:34:42 +0300 Subject: [PATCH 06/19] Enable optimizations for Editor module in Development builds --- Source/Editor/Editor.Build.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Source/Editor/Editor.Build.cs b/Source/Editor/Editor.Build.cs index cc7c48d23..a971dbefa 100644 --- a/Source/Editor/Editor.Build.cs +++ b/Source/Editor/Editor.Build.cs @@ -42,6 +42,10 @@ public class Editor : EditorModule options.ScriptingAPI.SystemReferences.Add("System.Text.RegularExpressions"); options.ScriptingAPI.SystemReferences.Add("System.ComponentModel.TypeConverter"); + // Enable optimizations for Editor, disable this for debugging the editor + if (options.Configuration == TargetConfiguration.Development) + options.ScriptingAPI.Optimization = true; + options.PublicDependencies.Add("Engine"); options.PrivateDependencies.Add("pugixml"); options.PrivateDependencies.Add("curl"); From e6878942f909e691cda5b56e0ba9282d7b009a32 Mon Sep 17 00:00:00 2001 From: Ari Vuollet Date: Sat, 12 Aug 2023 02:33:54 +0300 Subject: [PATCH 07/19] Fix missing C++ standard version in VC++ projects intellisense options --- .../VisualStudio/VCProjectGenerator.cs | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/Source/Tools/Flax.Build/Projects/VisualStudio/VCProjectGenerator.cs b/Source/Tools/Flax.Build/Projects/VisualStudio/VCProjectGenerator.cs index 4538ea8c1..76a0cf04b 100644 --- a/Source/Tools/Flax.Build/Projects/VisualStudio/VCProjectGenerator.cs +++ b/Source/Tools/Flax.Build/Projects/VisualStudio/VCProjectGenerator.cs @@ -1,5 +1,6 @@ // Copyright (c) 2012-2023 Wojciech Figat. All rights reserved. +using Flax.Build.NativeCpp; using System; using System.Collections.Generic; using System.IO; @@ -317,12 +318,31 @@ namespace Flax.Build.Projects.VisualStudio vcFiltersFileContent.AppendLine(" "); // IntelliSense information + + List additionalOptions = new List(); + switch (project.Configurations[0].TargetBuildOptions.CompileEnv.CppVersion) + { + case CppVersion.Cpp14: + additionalOptions.Add("/std:c++14"); + break; + case CppVersion.Cpp17: + additionalOptions.Add("/std:c++17"); + break; + case CppVersion.Cpp20: + additionalOptions.Add("/std:c++20"); + break; + case CppVersion.Latest: + additionalOptions.Add("/std:c++latest"); + break; + } + vcProjectFileContent.AppendLine(" "); vcProjectFileContent.AppendLine(string.Format(" $(NMakePreprocessorDefinitions){0}", (project.Defines.Count > 0 ? (";" + string.Join(";", project.Defines)) : ""))); vcProjectFileContent.AppendLine(string.Format(" $(NMakeIncludeSearchPath){0}", (project.SearchPaths.Length > 0 ? (";" + string.Join(";", project.SearchPaths)) : ""))); vcProjectFileContent.AppendLine(" $(NMakeForcedIncludes)"); vcProjectFileContent.AppendLine(" $(NMakeAssemblySearchPath)"); vcProjectFileContent.AppendLine(" $(NMakeForcedUsingAssemblies)"); + vcProjectFileContent.AppendLine(string.Format(" {0}", string.Join(" ", additionalOptions))); vcProjectFileContent.AppendLine(" "); foreach (var platform in platforms) From 44b70d87e5980ffd0c3c8c9558e9f26ecdf6dd13 Mon Sep 17 00:00:00 2001 From: Ari Vuollet Date: Fri, 4 Aug 2023 21:38:05 +0300 Subject: [PATCH 08/19] Cache MakeArrayType results in native interop --- Source/Engine/Engine/NativeInterop.Managed.cs | 21 ++++++++++--------- .../Engine/Engine/NativeInterop.Unmanaged.cs | 4 ++-- Source/Engine/Engine/NativeInterop.cs | 10 +++++++++ 3 files changed, 23 insertions(+), 12 deletions(-) diff --git a/Source/Engine/Engine/NativeInterop.Managed.cs b/Source/Engine/Engine/NativeInterop.Managed.cs index 95146a95c..86b41f458 100644 --- a/Source/Engine/Engine/NativeInterop.Managed.cs +++ b/Source/Engine/Engine/NativeInterop.Managed.cs @@ -91,18 +91,19 @@ namespace FlaxEngine.Interop internal void Allocate(int length) where T : unmanaged { + _length = length; + _arrayType = typeof(T[]); + _elementType = typeof(T); + _elementSize = Unsafe.SizeOf(); + // Try to reuse existing allocated buffer - if (length * Unsafe.SizeOf() > _unmanagedAllocationSize) + if (length * _elementSize > _unmanagedAllocationSize) { if (_unmanagedAllocationSize > 0) NativeInterop.NativeFree(_unmanagedData.ToPointer()); - _unmanagedData = (IntPtr)NativeInterop.NativeAlloc(length, Unsafe.SizeOf()); - _unmanagedAllocationSize = Unsafe.SizeOf() * length; + _unmanagedData = (IntPtr)NativeInterop.NativeAlloc(length, _elementSize); + _unmanagedAllocationSize = _elementSize * length; } - _length = length; - _arrayType = typeof(T).MakeArrayType(); - _elementType = typeof(T); - _elementSize = Unsafe.SizeOf(); } private ManagedArray() @@ -112,12 +113,12 @@ namespace FlaxEngine.Interop private ManagedArray(IntPtr ptr, int length, Type arrayType, Type elementType) { Assert.IsTrue(arrayType.IsArray); + _elementType = elementType; + _elementSize = NativeInterop.GetTypeSize(elementType); _unmanagedData = ptr; - _unmanagedAllocationSize = Marshal.SizeOf(elementType) * length; + _unmanagedAllocationSize = _elementSize * length; _length = length; _arrayType = arrayType; - _elementType = elementType; - _elementSize = NativeInterop.GetTypeSize(_elementType); } ~ManagedArray() diff --git a/Source/Engine/Engine/NativeInterop.Unmanaged.cs b/Source/Engine/Engine/NativeInterop.Unmanaged.cs index d2a8ca55e..86fe3036e 100644 --- a/Source/Engine/Engine/NativeInterop.Unmanaged.cs +++ b/Source/Engine/Engine/NativeInterop.Unmanaged.cs @@ -526,7 +526,7 @@ namespace FlaxEngine.Interop { Type elementType = Unsafe.As(typeHandle.Target); Type marshalledType = ArrayFactory.GetMarshalledType(elementType); - Type arrayType = elementType.MakeArrayType(); + Type arrayType = ArrayFactory.GetArrayType(elementType); if (marshalledType.IsValueType) { ManagedArray managedArray = ManagedArray.AllocateNewArray((int)size, arrayType, marshalledType); @@ -544,7 +544,7 @@ namespace FlaxEngine.Interop internal static ManagedHandle GetArrayTypeFromElementType(ManagedHandle elementTypeHandle) { Type elementType = Unsafe.As(elementTypeHandle.Target); - Type classType = elementType.MakeArrayType(); + Type classType = ArrayFactory.GetArrayType(elementType); return GetTypeGCHandle(classType); } diff --git a/Source/Engine/Engine/NativeInterop.cs b/Source/Engine/Engine/NativeInterop.cs index da372ac90..0949533ce 100644 --- a/Source/Engine/Engine/NativeInterop.cs +++ b/Source/Engine/Engine/NativeInterop.cs @@ -967,6 +967,7 @@ namespace FlaxEngine.Interop private delegate Array CreateArrayDelegate(long size); private static ConcurrentDictionary marshalledTypes = new ConcurrentDictionary(1, 3); + private static ConcurrentDictionary arrayTypes = new ConcurrentDictionary(1, 3); private static ConcurrentDictionary createArrayDelegates = new ConcurrentDictionary(1, 3); internal static Type GetMarshalledType(Type elementType) @@ -986,6 +987,15 @@ namespace FlaxEngine.Interop return marshalledTypes.GetOrAdd(elementType, Factory); } + internal static Type GetArrayType(Type elementType) + { + static Type Factory(Type type) => type.MakeArrayType(); + + if (arrayTypes.TryGetValue(elementType, out var arrayType)) + return arrayType; + return arrayTypes.GetOrAdd(elementType, Factory); + } + internal static Array CreateArray(Type type, long size) { static CreateArrayDelegate Factory(Type type) From 1254af8bbba136c5b0244ea93d93541f7c5b870c Mon Sep 17 00:00:00 2001 From: Ari Vuollet Date: Sun, 13 Aug 2023 14:18:57 +0300 Subject: [PATCH 09/19] Optimize UnboxValue performance, safety and memory usage - Avoids unnecessary boxing of the converted values by storing them in unmanaged memory. - Wrap ToNative-method in a delegate and cache it - Fixes returning address to unpinned memory by pinning POD-types for a short period of time. --- .../Engine/Engine/NativeInterop.Unmanaged.cs | 1 - Source/Engine/Engine/NativeInterop.cs | 63 ++++++++++++++----- 2 files changed, 49 insertions(+), 15 deletions(-) diff --git a/Source/Engine/Engine/NativeInterop.Unmanaged.cs b/Source/Engine/Engine/NativeInterop.Unmanaged.cs index d2a8ca55e..af739b55e 100644 --- a/Source/Engine/Engine/NativeInterop.Unmanaged.cs +++ b/Source/Engine/Engine/NativeInterop.Unmanaged.cs @@ -650,7 +650,6 @@ namespace FlaxEngine.Interop if (!type.IsValueType) return ManagedHandle.ToIntPtr(handle); - // HACK: Get the address of a non-pinned value return ValueTypeUnboxer.GetPointer(value, type); } diff --git a/Source/Engine/Engine/NativeInterop.cs b/Source/Engine/Engine/NativeInterop.cs index da372ac90..de2afec04 100644 --- a/Source/Engine/Engine/NativeInterop.cs +++ b/Source/Engine/Engine/NativeInterop.cs @@ -17,6 +17,7 @@ using FlaxEngine.Assertions; using System.Collections.Concurrent; using System.IO; using System.Text; +using System.Threading; namespace FlaxEngine.Interop { @@ -1009,44 +1010,78 @@ namespace FlaxEngine.Interop internal static class ValueTypeUnboxer { - private delegate IntPtr UnboxerDelegate(object value); + 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 uint pinnedAllocationsPointer = 0; + + private delegate TInternal ToNativeDelegate(T value); + private delegate IntPtr UnboxerDelegate(object value, object converter); - private static ConcurrentDictionary unboxers = new ConcurrentDictionary(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); internal static IntPtr GetPointer(object value, Type type) { - if (!unboxers.TryGetValue(type, out var deleg)) + if (!unboxers.TryGetValue(type, out var tuple)) { // Non-POD structures use internal layout (eg. SpriteHandleManaged in C++ with SpriteHandleMarshaller.SpriteHandleInternal in C#) so convert C# data into C++ data var attr = type.GetCustomAttribute(); var toNativeMethod = attr?.NativeType.GetMethod("ToNative", BindingFlags.Static | BindingFlags.NonPublic); if (toNativeMethod != null) { - deleg = unboxerToNativeMethod.MakeGenericMethod(toNativeMethod.ReturnType).CreateDelegate(); + tuple.deleg = unboxerToNativeMethod.MakeGenericMethod(type, toNativeMethod.ReturnType).CreateDelegate(); + tuple.toNativeDeleg = toNativeMethod.CreateDelegate(typeof(ToNativeDelegate<,>).MakeGenericType(type, toNativeMethod.ReturnType)); } else { - deleg = unboxerMethod.MakeGenericMethod(type).CreateDelegate(); + tuple.deleg = unboxerMethod.MakeGenericMethod(type).CreateDelegate(); } - deleg = unboxers.GetOrAdd(type, deleg); + tuple = unboxers.GetOrAdd(type, tuple); } - return deleg(value); + return tuple.deleg(value, tuple.toNativeDeleg); } - private static IntPtr UnboxPointer(object value) where T : struct + private static void PinValue(object value) { + // Prevent garbage collector from relocating the boxed value by pinning it temporarily. + // The pointer should remain valid quite long time but will be eventually unpinned. + uint index = Interlocked.Increment(ref pinnedBoxedValuesPointer) % (uint)pinnedBoxedValues.Length; + ref GCHandle handle = ref pinnedBoxedValues[index]; + if (handle.IsAllocated) + handle.Free(); + handle = GCHandle.Alloc(value, GCHandleType.Pinned); + } + + private static IntPtr PinValue(T value) where T : struct + { + // Store the converted value in unmanaged memory so it will not be relocated by the garbage collector. + int size = Unsafe.SizeOf(); + uint index = Interlocked.Increment(ref pinnedAllocationsPointer) % (uint)pinnedAllocations.Length; + ref (IntPtr ptr, int size) alloc = ref pinnedAllocations[index]; + if (alloc.size < size) + { + if (alloc.ptr != IntPtr.Zero) + NativeFree(alloc.ptr.ToPointer()); + alloc.ptr = new IntPtr(NativeAlloc(size)); + alloc.size = size; + } + + Unsafe.Write(alloc.ptr.ToPointer(), value); + return alloc.ptr; + } + + private static IntPtr UnboxPointer(object value, object converter) where T : struct + { + PinValue(value); return new IntPtr(Unsafe.AsPointer(ref Unsafe.Unbox(value))); } - private static IntPtr UnboxPointerWithConverter(object value) where T : struct + private static IntPtr UnboxPointerWithConverter(object value, object converter) where T : struct where TInternal : struct { - var type = value.GetType(); - var attr = type.GetCustomAttribute(); - var toNative = attr.NativeType.GetMethod("ToNative", BindingFlags.Static | BindingFlags.NonPublic); - value = toNative.Invoke(null, new[] { value }); - return new IntPtr(Unsafe.AsPointer(ref Unsafe.Unbox(value))); + ToNativeDelegate toNative = Unsafe.As>(converter); + return PinValue(toNative(Unsafe.Unbox(value))); } } From 27e1401fc7452b61e92f815c11d5e1097af05c93 Mon Sep 17 00:00:00 2001 From: Ari Vuollet Date: Sun, 13 Aug 2023 14:24:48 +0300 Subject: [PATCH 10/19] Slightly improve `MClass::GetMethod` method iteration Check the number of parameters first before expensive string comparison --- Source/Engine/Scripting/Runtime/DotNet.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/Engine/Scripting/Runtime/DotNet.cpp b/Source/Engine/Scripting/Runtime/DotNet.cpp index 36f1f16af..f4ae5b1ec 100644 --- a/Source/Engine/Scripting/Runtime/DotNet.cpp +++ b/Source/Engine/Scripting/Runtime/DotNet.cpp @@ -878,7 +878,7 @@ MMethod* MClass::GetMethod(const char* name, int32 numParams) const GetMethods(); for (int32 i = 0; i < _methods.Count(); i++) { - if (_methods[i]->GetName() == name && _methods[i]->GetParametersCount() == numParams) + if (_methods[i]->GetParametersCount() == numParams && _methods[i]->GetName() == name) return _methods[i]; } return nullptr; From d9ee8f46654767809e03c332e1fd4c300e538c73 Mon Sep 17 00:00:00 2001 From: Ari Vuollet Date: Sat, 5 Aug 2023 15:55:27 +0300 Subject: [PATCH 11/19] Cache pooled ManagedArray managed handles --- Source/Engine/Engine/NativeInterop.Managed.cs | 40 +++++++-- .../Engine/NativeInterop.Marshallers.cs | 88 +++++++------------ 2 files changed, 67 insertions(+), 61 deletions(-) diff --git a/Source/Engine/Engine/NativeInterop.Managed.cs b/Source/Engine/Engine/NativeInterop.Managed.cs index 95146a95c..0a2ce5e31 100644 --- a/Source/Engine/Engine/NativeInterop.Managed.cs +++ b/Source/Engine/Engine/NativeInterop.Managed.cs @@ -29,6 +29,8 @@ namespace FlaxEngine.Interop private int _elementSize; private int _length; + [ThreadStatic] private static Dictionary pooledArrayHandles; + public static ManagedArray WrapNewArray(Array arr) => new ManagedArray(arr, arr.GetType()); public static ManagedArray WrapNewArray(Array arr, Type arrayType) => new ManagedArray(arr, arrayType); @@ -37,22 +39,38 @@ namespace FlaxEngine.Interop /// Returns an instance of ManagedArray from shared pool. /// /// The resources must be released by calling FreePooled() instead of Free()-method. - public static ManagedArray WrapPooledArray(Array arr) + public static (ManagedHandle managedHandle, ManagedArray managedArray) WrapPooledArray(Array arr) { ManagedArray managedArray = ManagedArrayPool.Get(); managedArray.WrapArray(arr, arr.GetType()); - return managedArray; + + if (pooledArrayHandles == null) + pooledArrayHandles = new(); + if (!pooledArrayHandles.TryGetValue(managedArray, out ManagedHandle handle)) + { + handle = ManagedHandle.Alloc(managedArray); + pooledArrayHandles.Add(managedArray, handle); + } + return (handle, managedArray); } /// /// Returns an instance of ManagedArray from shared pool. /// /// The resources must be released by calling FreePooled() instead of Free()-method. - public static ManagedArray WrapPooledArray(Array arr, Type arrayType) + public static ManagedHandle WrapPooledArray(Array arr, Type arrayType) { ManagedArray managedArray = ManagedArrayPool.Get(arr.Length * NativeInterop.GetTypeSize(arr.GetType().GetElementType())); managedArray.WrapArray(arr, arrayType); - return managedArray; + + if (pooledArrayHandles == null) + pooledArrayHandles = new(); + if (!pooledArrayHandles.TryGetValue(managedArray, out ManagedHandle handle)) + { + handle = ManagedHandle.Alloc(managedArray); + pooledArrayHandles.Add(managedArray, handle); + } + return handle; } internal static ManagedArray AllocateNewArray(int length, Type arrayType, Type elementType) @@ -64,12 +82,20 @@ namespace FlaxEngine.Interop /// /// Returns an instance of ManagedArray from shared pool. /// - /// The resources must be released by calling FreePooled() instead of Free()-method. - public static ManagedArray AllocatePooledArray(int length) where T : unmanaged + /// The resources must be released by calling FreePooled() instead of Free()-method. Do not release the returned ManagedHandle. + public static (ManagedHandle managedHandle, ManagedArray managedArray) AllocatePooledArray(int length) where T : unmanaged { ManagedArray managedArray = ManagedArrayPool.Get(length * Unsafe.SizeOf()); managedArray.Allocate(length); - return managedArray; + + if (pooledArrayHandles == null) + pooledArrayHandles = new(); + if (!pooledArrayHandles.TryGetValue(managedArray, out ManagedHandle handle)) + { + handle = ManagedHandle.Alloc(managedArray); + pooledArrayHandles.Add(managedArray, handle); + } + return (handle, managedArray); } public ManagedArray(Array arr, Type elementType) => WrapArray(arr, elementType); diff --git a/Source/Engine/Engine/NativeInterop.Marshallers.cs b/Source/Engine/Engine/NativeInterop.Marshallers.cs index 2e3bc9183..0f7d238bc 100644 --- a/Source/Engine/Engine/NativeInterop.Marshallers.cs +++ b/Source/Engine/Engine/NativeInterop.Marshallers.cs @@ -200,14 +200,13 @@ namespace FlaxEngine.Interop public void FromManaged(Array managed) { if (managed != null) - managedArray = ManagedArray.WrapPooledArray(managed); + (handle, managedArray) = ManagedArray.WrapPooledArray(managed); } public IntPtr ToUnmanaged() { if (managedArray == null) return IntPtr.Zero; - handle = ManagedHandle.Alloc(managedArray, GCHandleType.Weak); return ManagedHandle.ToIntPtr(handle); } @@ -216,7 +215,6 @@ namespace FlaxEngine.Interop if (managedArray == null) return; managedArray.FreePooled(); - //handle.Free(); // No need to free weak handles } } @@ -335,7 +333,6 @@ namespace FlaxEngine.Interop #endif [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))] @@ -388,38 +385,28 @@ namespace FlaxEngine.Interop #if FLAX_EDITOR [HideInEditor] #endif - public static class ManagedToNative + public ref struct ManagedToNative { - public static TUnmanagedElement* AllocateContainerForUnmanagedElements(T[] managed, out int numElements) + T[] sourceArray; + ManagedArray managedArray; + ManagedHandle managedHandle; + + public void FromManaged(T[] managed) { if (managed is null) - { - numElements = 0; - return null; - } - numElements = managed.Length; - ManagedArray managedArray = ManagedArray.AllocatePooledArray(managed.Length); - return (TUnmanagedElement*)ManagedHandle.ToIntPtr(managedArray, GCHandleType.Normal); - } - - public static ReadOnlySpan GetManagedValuesSource(T[] managed) => managed; - - public static Span GetUnmanagedValuesDestination(TUnmanagedElement* unmanaged, int numElements) - { - if (unmanaged == null) - return Span.Empty; - ManagedArray managedArray = Unsafe.As(ManagedHandle.FromIntPtr(new IntPtr(unmanaged)).Target); - return managedArray.ToSpan(); - } - - public static void Free(TUnmanagedElement* unmanaged) - { - if (unmanaged == null) return; - ManagedHandle handle = ManagedHandle.FromIntPtr(new IntPtr(unmanaged)); - (Unsafe.As(handle.Target)).FreePooled(); - //handle.Free(); // No need to free weak handles + + sourceArray = managed; + (managedHandle, managedArray) = ManagedArray.AllocatePooledArray(managed.Length); } + + public ReadOnlySpan GetManagedValuesSource() => sourceArray; + + public Span GetUnmanagedValuesDestination() => managedArray.ToSpan(); + + public TUnmanagedElement* ToUnmanaged() => (TUnmanagedElement*)ManagedHandle.ToIntPtr(managedHandle); + + public void Free() => managedArray.FreePooled(); } #if FLAX_EDITOR @@ -427,26 +414,25 @@ namespace FlaxEngine.Interop #endif public struct Bidirectional { - T[] managedArray; - ManagedArray unmanagedArray; + T[] sourceArray; + ManagedArray managedArray; ManagedHandle handle; public void FromManaged(T[] managed) { if (managed == null) return; - managedArray = managed; - unmanagedArray = ManagedArray.AllocatePooledArray(managed.Length); - handle = ManagedHandle.Alloc(unmanagedArray); + sourceArray = managed; + (handle, managedArray) = ManagedArray.AllocatePooledArray(managed.Length); } - public ReadOnlySpan GetManagedValuesSource() => managedArray; + public ReadOnlySpan GetManagedValuesSource() => sourceArray; public Span GetUnmanagedValuesDestination() { - if (unmanagedArray == null) + if (managedArray == null) return Span.Empty; - return unmanagedArray.ToSpan(); + return managedArray.ToSpan(); } public TUnmanagedElement* ToUnmanaged() => (TUnmanagedElement*)ManagedHandle.ToIntPtr(handle); @@ -454,26 +440,22 @@ namespace FlaxEngine.Interop public void FromUnmanaged(TUnmanagedElement* unmanaged) { ManagedArray arr = Unsafe.As(ManagedHandle.FromIntPtr(new IntPtr(unmanaged)).Target); - if (managedArray == null || managedArray.Length != arr.Length) - managedArray = new T[arr.Length]; + if (sourceArray == null || sourceArray.Length != arr.Length) + sourceArray = new T[arr.Length]; } public ReadOnlySpan GetUnmanagedValuesSource(int numElements) { - if (unmanagedArray == null) + if (managedArray == null) return ReadOnlySpan.Empty; - return unmanagedArray.ToSpan(); + return managedArray.ToSpan(); } - public Span GetManagedValuesDestination(int numElements) => managedArray; + public Span GetManagedValuesDestination(int numElements) => sourceArray; - public T[] ToManaged() => managedArray; + public T[] ToManaged() => sourceArray; - public void Free() - { - unmanagedArray.FreePooled(); - handle.Free(); - } + public void Free() => managedArray.FreePooled(); } public static TUnmanagedElement* AllocateContainerForUnmanagedElements(T[] managed, out int numElements) @@ -484,9 +466,8 @@ namespace FlaxEngine.Interop return null; } numElements = managed.Length; - ManagedArray managedArray = ManagedArray.AllocatePooledArray(managed.Length); - IntPtr handle = ManagedHandle.ToIntPtr(managedArray); - return (TUnmanagedElement*)handle; + (ManagedHandle managedArrayHandle, _) = ManagedArray.AllocatePooledArray(managed.Length); + return (TUnmanagedElement*)ManagedHandle.ToIntPtr(managedArrayHandle); } public static ReadOnlySpan GetManagedValuesSource(T[] managed) => managed; @@ -517,7 +498,6 @@ namespace FlaxEngine.Interop return; ManagedHandle handle = ManagedHandle.FromIntPtr(new IntPtr(unmanaged)); Unsafe.As(handle.Target).FreePooled(); - handle.Free(); } } From 65de284a66e11ae729bae909395efe9fb76768e0 Mon Sep 17 00:00:00 2001 From: Ari Vuollet Date: Sat, 5 Aug 2023 17:36:35 +0300 Subject: [PATCH 12/19] Optimize managed method invokers return value marshalling --- Source/Engine/Engine/NativeInterop.Invoker.cs | 264 ++++++++++++------ 1 file changed, 175 insertions(+), 89 deletions(-) diff --git a/Source/Engine/Engine/NativeInterop.Invoker.cs b/Source/Engine/Engine/NativeInterop.Invoker.cs index 2fc54597c..ade1e3570 100644 --- a/Source/Engine/Engine/NativeInterop.Invoker.cs +++ b/Source/Engine/Engine/NativeInterop.Invoker.cs @@ -8,6 +8,7 @@ using System.Reflection; using System.Runtime.InteropServices; using System.Runtime.CompilerServices; using System.Diagnostics; +using System.Collections.Generic; namespace FlaxEngine.Interop { @@ -20,28 +21,145 @@ namespace FlaxEngine.Interop { // TODO: Use .NET8 Unsafe.BitCast(returnValue) for more efficient casting of value types over boxing cast - internal static IntPtr MarshalReturnValue(ref TRet returnValue) + internal static class InvokerMarshallers + { + internal delegate IntPtr Delegate(ref T value); + internal static Delegate deleg; + internal static Delegate delegThunk; + + static InvokerMarshallers() + { + Type type = typeof(T); + if (type == typeof(string)) + deleg = typeof(Invoker).GetMethod(nameof(MarshalReturnValueString), BindingFlags.Static | BindingFlags.NonPublic).CreateDelegate(); + else if (type == typeof(ManagedHandle)) + deleg = typeof(Invoker).GetMethod(nameof(MarshalReturnValueManagedHandle), BindingFlags.Static | BindingFlags.NonPublic).CreateDelegate(); + else if (type == typeof(Type)) + deleg = typeof(Invoker).GetMethod(nameof(MarshalReturnValueType), BindingFlags.Static | BindingFlags.NonPublic).CreateDelegate(); + else if (type.IsArray) + deleg = typeof(Invoker).GetMethod(nameof(MarshalReturnValueArray), BindingFlags.Static | BindingFlags.NonPublic).MakeGenericMethod(type).CreateDelegate(); + else if (type == typeof(bool)) + deleg = typeof(Invoker).GetMethod(nameof(MarshalReturnValueBool), BindingFlags.Static | BindingFlags.NonPublic).CreateDelegate(); + else + deleg = typeof(Invoker).GetMethod(nameof(MarshalReturnValueWrapped), BindingFlags.Static | BindingFlags.NonPublic).MakeGenericMethod(type).CreateDelegate(); + + if (type == typeof(string)) + delegThunk = typeof(Invoker).GetMethod(nameof(MarshalReturnValueString), BindingFlags.Static | BindingFlags.NonPublic).CreateDelegate(); + else if (type == typeof(ManagedHandle)) + delegThunk = typeof(Invoker).GetMethod(nameof(MarshalReturnValueManagedHandle), BindingFlags.Static | BindingFlags.NonPublic).CreateDelegate(); + else if (type == typeof(Type)) + delegThunk = typeof(Invoker).GetMethod(nameof(MarshalReturnValueType), BindingFlags.Static | BindingFlags.NonPublic).CreateDelegate(); + else if (type.IsArray) + delegThunk = typeof(Invoker).GetMethod(nameof(MarshalReturnValueArray), BindingFlags.Static | BindingFlags.NonPublic).MakeGenericMethod(type).CreateDelegate(); + else if (type == typeof(System.Boolean)) + delegThunk = typeof(Invoker).GetMethod(nameof(MarshalReturnValueMonoBoolean), BindingFlags.Static | BindingFlags.NonPublic).CreateDelegate(); + else if (type == typeof(IntPtr)) + delegThunk = typeof(Invoker).GetMethod(nameof(MarshalReturnValueIntPtr), BindingFlags.Static | BindingFlags.NonPublic).CreateDelegate(); + else if (type == typeof(System.Int16)) + delegThunk = typeof(Invoker).GetMethod(nameof(MarshalReturnValueInt16), BindingFlags.Static | BindingFlags.NonPublic).CreateDelegate(); + else if (type == typeof(System.Int32)) + delegThunk = typeof(Invoker).GetMethod(nameof(MarshalReturnValueInt32), BindingFlags.Static | BindingFlags.NonPublic).CreateDelegate(); + else if (type == typeof(System.Int64)) + delegThunk = typeof(Invoker).GetMethod(nameof(MarshalReturnValueInt64), BindingFlags.Static | BindingFlags.NonPublic).CreateDelegate(); + else if (type == typeof(System.UInt16)) + delegThunk = typeof(Invoker).GetMethod(nameof(MarshalReturnValueUInt16), BindingFlags.Static | BindingFlags.NonPublic).CreateDelegate(); + else if (type == typeof(System.UInt32)) + delegThunk = typeof(Invoker).GetMethod(nameof(MarshalReturnValueUInt32), BindingFlags.Static | BindingFlags.NonPublic).CreateDelegate(); + else if (type == typeof(System.UInt64)) + delegThunk = typeof(Invoker).GetMethod(nameof(MarshalReturnValueUInt64), BindingFlags.Static | BindingFlags.NonPublic).CreateDelegate(); + else + delegThunk = typeof(Invoker).GetMethod(nameof(MarshalReturnValueWrapped), BindingFlags.Static | BindingFlags.NonPublic).MakeGenericMethod(type).CreateDelegate(); + } + } + + internal static IntPtr MarshalReturnValueString(ref string returnValue) + { + if (returnValue == null) + return IntPtr.Zero; + return ManagedString.ToNativeWeak(returnValue); + } + + internal static IntPtr MarshalReturnValueManagedHandle(ref ManagedHandle returnValue) + { + if (returnValue == null) + return IntPtr.Zero; + return ManagedHandle.ToIntPtr(returnValue); + } + + internal static IntPtr MarshalReturnValueType(ref Type returnValue) + { + if (returnValue == null) + return IntPtr.Zero; + return ManagedHandle.ToIntPtr(GetTypeGCHandle(returnValue)); + } + + internal static IntPtr MarshalReturnValueArray(ref TRet returnValue) + { + if (returnValue == null) + return IntPtr.Zero; + var elementType = typeof(TRet).GetElementType(); + if (ArrayFactory.GetMarshalledType(elementType) == elementType) + return ManagedHandle.ToIntPtr(ManagedArray.WrapNewArray(Unsafe.As(returnValue)), GCHandleType.Weak); + return ManagedHandle.ToIntPtr(ManagedArrayToGCHandleWrappedArray(Unsafe.As(returnValue)), GCHandleType.Weak); + } + + internal static IntPtr MarshalReturnValueBool(ref bool returnValue) + { + return returnValue ? boolTruePtr : boolFalsePtr; + } + + internal static IntPtr MarshalReturnValueIntPtr(ref IntPtr returnValue) + { + return returnValue; + } + + internal static IntPtr MarshalReturnValueMonoBoolean(ref bool returnValue) + { + return returnValue ? 1 : 0; + } + + internal static IntPtr MarshalReturnValueInt16(ref Int16 returnValue) + { + return returnValue; + } + + internal static IntPtr MarshalReturnValueInt32(ref Int32 returnValue) + { + return returnValue; + } + + internal static IntPtr MarshalReturnValueInt64(ref Int64 returnValue) + { + return new IntPtr(returnValue); + } + + internal static IntPtr MarshalReturnValueUInt16(ref UInt16 returnValue) + { + return returnValue; + } + + internal static IntPtr MarshalReturnValueUInt32(ref UInt32 returnValue) + { + return new IntPtr(returnValue); + } + + internal static IntPtr MarshalReturnValueUInt64(ref UInt64 returnValue) + { + return new IntPtr((long)returnValue); + } + + internal static IntPtr MarshalReturnValueWrapped(ref TRet returnValue) { if (returnValue == null) return IntPtr.Zero; - if (typeof(TRet) == typeof(string)) - return ManagedString.ToNativeWeak(Unsafe.As(returnValue)); - if (typeof(TRet) == typeof(ManagedHandle)) - return ManagedHandle.ToIntPtr((ManagedHandle)(object)returnValue); - if (typeof(TRet) == typeof(bool)) - return (bool)(object)returnValue ? boolTruePtr : boolFalsePtr; - if (typeof(TRet) == typeof(Type)) - return ManagedHandle.ToIntPtr(GetTypeGCHandle(Unsafe.As(returnValue))); - if (typeof(TRet).IsArray) - { - var elementType = typeof(TRet).GetElementType(); - if (ArrayFactory.GetMarshalledType(elementType) == elementType) - return ManagedHandle.ToIntPtr(ManagedArray.WrapNewArray(Unsafe.As(returnValue)), GCHandleType.Weak); - return ManagedHandle.ToIntPtr(ManagedArrayToGCHandleWrappedArray(Unsafe.As(returnValue)), GCHandleType.Weak); - } return ManagedHandle.ToIntPtr(returnValue, GCHandleType.Weak); } + internal static IntPtr MarshalReturnValue(ref TRet returnValue) + { + return InvokerMarshallers.deleg(ref returnValue); + } + internal static IntPtr MarshalReturnValueGeneric(Type returnType, object returnObject) { if (returnObject == null) @@ -63,39 +181,7 @@ namespace FlaxEngine.Interop internal static IntPtr MarshalReturnValueThunk(ref TRet returnValue) { - if (returnValue == null) - return IntPtr.Zero; - if (typeof(TRet) == typeof(string)) - return ManagedString.ToNativeWeak(Unsafe.As(returnValue)); - if (typeof(TRet) == typeof(IntPtr)) - return (IntPtr)(object)returnValue; - if (typeof(TRet) == typeof(ManagedHandle)) - return ManagedHandle.ToIntPtr((ManagedHandle)(object)returnValue); - if (typeof(TRet) == typeof(Type)) - return ManagedHandle.ToIntPtr(GetTypeGCHandle(Unsafe.As(returnValue))); - if (typeof(TRet).IsArray) - { - var elementType = typeof(TRet).GetElementType(); - if (ArrayFactory.GetMarshalledType(elementType) == elementType) - return ManagedHandle.ToIntPtr(ManagedArray.WrapNewArray(Unsafe.As(returnValue)), GCHandleType.Weak); - return ManagedHandle.ToIntPtr(ManagedArrayToGCHandleWrappedArray(Unsafe.As(returnValue)), GCHandleType.Weak); - } - // Match Mono bindings and pass value as pointer to prevent boxing it - if (typeof(TRet) == typeof(System.Boolean)) - return new IntPtr(((System.Boolean)(object)returnValue) ? 1 : 0); - if (typeof(TRet) == typeof(System.Int16)) - return new IntPtr((int)(System.Int16)(object)returnValue); - if (typeof(TRet) == typeof(System.Int32)) - return new IntPtr((int)(System.Int32)(object)returnValue); - if (typeof(TRet) == typeof(System.Int64)) - return new IntPtr((long)(System.Int64)(object)returnValue); - if (typeof(TRet) == typeof(System.UInt16)) - return (IntPtr)new UIntPtr((ulong)(System.UInt16)(object)returnValue); - if (typeof(TRet) == typeof(System.UInt32)) - return (IntPtr)new UIntPtr((ulong)(System.UInt32)(object)returnValue); - if (typeof(TRet) == typeof(System.UInt64)) - return (IntPtr)new UIntPtr((ulong)(System.UInt64)(object)returnValue); - return ManagedHandle.ToIntPtr(returnValue, GCHandleType.Weak); + return InvokerMarshallers.delegThunk(ref returnValue); } internal static IntPtr MarshalReturnValueThunkGeneric(Type returnType, object returnObject) @@ -205,7 +291,7 @@ namespace FlaxEngine.Interop return Unsafe.As(CreateDelegateFromMethod(method, false)); } - [DebuggerStepThrough] + //[DebuggerStepThrough] internal static IntPtr MarshalAndInvoke(object delegateContext, ManagedHandle instancePtr, IntPtr paramPtr) { (Type[] types, InvokerDelegate deleg) = (Tuple)(delegateContext); @@ -215,7 +301,7 @@ namespace FlaxEngine.Interop return IntPtr.Zero; } - [DebuggerStepThrough] + //[DebuggerStepThrough] internal static unsafe IntPtr InvokeThunk(object delegateContext, ManagedHandle instancePtr, IntPtr* paramPtrs) { ThunkInvokerDelegate deleg = Unsafe.As(delegateContext); @@ -242,7 +328,7 @@ namespace FlaxEngine.Interop return Unsafe.As(CreateDelegateFromMethod(method, false)); } - [DebuggerStepThrough] + //[DebuggerStepThrough] internal static IntPtr MarshalAndInvoke(object delegateContext, ManagedHandle instancePtr, IntPtr paramPtr) { (Type[] types, InvokerDelegate deleg) = (Tuple)(delegateContext); @@ -262,7 +348,7 @@ namespace FlaxEngine.Interop return IntPtr.Zero; } - [DebuggerStepThrough] + //[DebuggerStepThrough] internal static unsafe IntPtr InvokeThunk(object delegateContext, ManagedHandle instancePtr, IntPtr* paramPtrs) { ThunkInvokerDelegate deleg = Unsafe.As(delegateContext); @@ -291,7 +377,7 @@ namespace FlaxEngine.Interop return Unsafe.As(CreateDelegateFromMethod(method, false)); } - [DebuggerStepThrough] + //[DebuggerStepThrough] internal static IntPtr MarshalAndInvoke(object delegateContext, ManagedHandle instancePtr, IntPtr paramPtr) { (Type[] types, InvokerDelegate deleg) = (Tuple)(delegateContext); @@ -317,7 +403,7 @@ namespace FlaxEngine.Interop return IntPtr.Zero; } - [DebuggerStepThrough] + //[DebuggerStepThrough] internal static unsafe IntPtr InvokeThunk(object delegateContext, ManagedHandle instancePtr, IntPtr* paramPtrs) { ThunkInvokerDelegate deleg = Unsafe.As(delegateContext); @@ -347,7 +433,7 @@ namespace FlaxEngine.Interop return Unsafe.As(CreateDelegateFromMethod(method, false)); } - [DebuggerStepThrough] + //[DebuggerStepThrough] internal static IntPtr MarshalAndInvoke(object delegateContext, ManagedHandle instancePtr, IntPtr paramPtr) { (Type[] types, InvokerDelegate deleg) = (Tuple)(delegateContext); @@ -379,7 +465,7 @@ namespace FlaxEngine.Interop return IntPtr.Zero; } - [DebuggerStepThrough] + //[DebuggerStepThrough] internal static unsafe IntPtr InvokeThunk(object delegateContext, ManagedHandle instancePtr, IntPtr* paramPtrs) { ThunkInvokerDelegate deleg = Unsafe.As(delegateContext); @@ -410,7 +496,7 @@ namespace FlaxEngine.Interop return Unsafe.As(CreateDelegateFromMethod(method, false)); } - [DebuggerStepThrough] + //[DebuggerStepThrough] internal static IntPtr MarshalAndInvoke(object delegateContext, ManagedHandle instancePtr, IntPtr paramPtr) { (Type[] types, InvokerDelegate deleg) = (Tuple)(delegateContext); @@ -448,7 +534,7 @@ namespace FlaxEngine.Interop return IntPtr.Zero; } - [DebuggerStepThrough] + //[DebuggerStepThrough] internal static unsafe IntPtr InvokeThunk(object delegateContext, ManagedHandle instancePtr, IntPtr* paramPtrs) { ThunkInvokerDelegate deleg = Unsafe.As(delegateContext); @@ -480,7 +566,7 @@ namespace FlaxEngine.Interop return Unsafe.As(CreateDelegateFromMethod(method, false)); } - [DebuggerStepThrough] + //[DebuggerStepThrough] internal static IntPtr MarshalAndInvoke(object delegateContext, ManagedHandle instancePtr, IntPtr paramPtr) { (Type[] types, InvokerDelegate deleg) = (Tuple)(delegateContext); @@ -490,7 +576,7 @@ namespace FlaxEngine.Interop return IntPtr.Zero; } - [DebuggerStepThrough] + //[DebuggerStepThrough] internal static unsafe IntPtr InvokeThunk(object delegateContext, ManagedHandle instancePtr, IntPtr* paramPtrs) { ThunkInvokerDelegate deleg = Unsafe.As(delegateContext); @@ -517,7 +603,7 @@ namespace FlaxEngine.Interop return Unsafe.As(CreateDelegateFromMethod(method, false)); } - [DebuggerStepThrough] + //[DebuggerStepThrough] internal static IntPtr MarshalAndInvoke(object delegateContext, ManagedHandle instancePtr, IntPtr paramPtr) { (Type[] types, InvokerDelegate deleg) = (Tuple)(delegateContext); @@ -537,7 +623,7 @@ namespace FlaxEngine.Interop return IntPtr.Zero; } - [DebuggerStepThrough] + //[DebuggerStepThrough] internal static unsafe IntPtr InvokeThunk(object delegateContext, ManagedHandle instancePtr, IntPtr* paramPtrs) { ThunkInvokerDelegate deleg = Unsafe.As(delegateContext); @@ -566,7 +652,7 @@ namespace FlaxEngine.Interop return Unsafe.As(CreateDelegateFromMethod(method, false)); } - [DebuggerStepThrough] + //[DebuggerStepThrough] internal static IntPtr MarshalAndInvoke(object delegateContext, ManagedHandle instancePtr, IntPtr paramPtr) { (Type[] types, InvokerDelegate deleg) = (Tuple)(delegateContext); @@ -592,7 +678,7 @@ namespace FlaxEngine.Interop return IntPtr.Zero; } - [DebuggerStepThrough] + //[DebuggerStepThrough] internal static unsafe IntPtr InvokeThunk(object delegateContext, ManagedHandle instancePtr, IntPtr* paramPtrs) { ThunkInvokerDelegate deleg = Unsafe.As(delegateContext); @@ -622,7 +708,7 @@ namespace FlaxEngine.Interop return Unsafe.As(CreateDelegateFromMethod(method, false)); } - [DebuggerStepThrough] + //[DebuggerStepThrough] internal static IntPtr MarshalAndInvoke(object delegateContext, ManagedHandle instancePtr, IntPtr paramPtr) { (Type[] types, InvokerDelegate deleg) = (Tuple)(delegateContext); @@ -654,7 +740,7 @@ namespace FlaxEngine.Interop return IntPtr.Zero; } - [DebuggerStepThrough] + //[DebuggerStepThrough] internal static unsafe IntPtr InvokeThunk(object delegateContext, ManagedHandle instancePtr, IntPtr* paramPtrs) { ThunkInvokerDelegate deleg = Unsafe.As(delegateContext); @@ -685,7 +771,7 @@ namespace FlaxEngine.Interop return Unsafe.As(CreateDelegateFromMethod(method, false)); } - [DebuggerStepThrough] + //[DebuggerStepThrough] internal static IntPtr MarshalAndInvoke(object delegateContext, ManagedHandle instancePtr, IntPtr paramPtr) { (Type[] types, InvokerDelegate deleg) = (Tuple)(delegateContext); @@ -723,7 +809,7 @@ namespace FlaxEngine.Interop return IntPtr.Zero; } - [DebuggerStepThrough] + //[DebuggerStepThrough] internal static unsafe IntPtr InvokeThunk(object delegateContext, ManagedHandle instancePtr, IntPtr* paramPtrs) { ThunkInvokerDelegate deleg = Unsafe.As(delegateContext); @@ -755,7 +841,7 @@ namespace FlaxEngine.Interop return Unsafe.As(CreateDelegateFromMethod(method, false)); } - [DebuggerStepThrough] + //[DebuggerStepThrough] internal static IntPtr MarshalAndInvoke(object delegateContext, ManagedHandle instancePtr, IntPtr paramPtr) { (Type[] types, InvokerDelegate deleg) = (Tuple)(delegateContext); @@ -765,7 +851,7 @@ namespace FlaxEngine.Interop return MarshalReturnValue(ref ret); } - [DebuggerStepThrough] + //[DebuggerStepThrough] internal static unsafe IntPtr InvokeThunk(object delegateContext, ManagedHandle instancePtr, IntPtr* paramPtrs) { ThunkInvokerDelegate deleg = Unsafe.As(delegateContext); @@ -792,7 +878,7 @@ namespace FlaxEngine.Interop return Unsafe.As(CreateDelegateFromMethod(method, false)); } - [DebuggerStepThrough] + //[DebuggerStepThrough] internal static IntPtr MarshalAndInvoke(object delegateContext, ManagedHandle instancePtr, IntPtr paramPtr) { (Type[] types, InvokerDelegate deleg) = (Tuple)(delegateContext); @@ -812,7 +898,7 @@ namespace FlaxEngine.Interop return MarshalReturnValue(ref ret); } - [DebuggerStepThrough] + //[DebuggerStepThrough] internal static unsafe IntPtr InvokeThunk(object delegateContext, ManagedHandle instancePtr, IntPtr* paramPtrs) { ThunkInvokerDelegate deleg = Unsafe.As(delegateContext); @@ -841,7 +927,7 @@ namespace FlaxEngine.Interop return Unsafe.As(CreateDelegateFromMethod(method, false)); } - [DebuggerStepThrough] + //[DebuggerStepThrough] internal static IntPtr MarshalAndInvoke(object delegateContext, ManagedHandle instancePtr, IntPtr paramPtr) { (Type[] types, InvokerDelegate deleg) = (Tuple)(delegateContext); @@ -867,7 +953,7 @@ namespace FlaxEngine.Interop return MarshalReturnValue(ref ret); } - [DebuggerStepThrough] + //[DebuggerStepThrough] internal static unsafe IntPtr InvokeThunk(object delegateContext, ManagedHandle instancePtr, IntPtr* paramPtrs) { ThunkInvokerDelegate deleg = Unsafe.As(delegateContext); @@ -897,7 +983,7 @@ namespace FlaxEngine.Interop return Unsafe.As(CreateDelegateFromMethod(method, false)); } - [DebuggerStepThrough] + //[DebuggerStepThrough] internal static IntPtr MarshalAndInvoke(object delegateContext, ManagedHandle instancePtr, IntPtr paramPtr) { (Type[] types, InvokerDelegate deleg) = (Tuple)(delegateContext); @@ -929,7 +1015,7 @@ namespace FlaxEngine.Interop return MarshalReturnValue(ref ret); } - [DebuggerStepThrough] + //[DebuggerStepThrough] internal static unsafe IntPtr InvokeThunk(object delegateContext, ManagedHandle instancePtr, IntPtr* paramPtrs) { ThunkInvokerDelegate deleg = Unsafe.As(delegateContext); @@ -960,7 +1046,7 @@ namespace FlaxEngine.Interop return Unsafe.As(CreateDelegateFromMethod(method, false)); } - [DebuggerStepThrough] + //[DebuggerStepThrough] internal static IntPtr MarshalAndInvoke(object delegateContext, ManagedHandle instancePtr, IntPtr paramPtr) { (Type[] types, InvokerDelegate deleg) = (Tuple)(delegateContext); @@ -998,7 +1084,7 @@ namespace FlaxEngine.Interop return MarshalReturnValue(ref ret); } - [DebuggerStepThrough] + //[DebuggerStepThrough] internal static unsafe IntPtr InvokeThunk(object delegateContext, ManagedHandle instancePtr, IntPtr* paramPtrs) { ThunkInvokerDelegate deleg = Unsafe.As(delegateContext); @@ -1030,7 +1116,7 @@ namespace FlaxEngine.Interop return Unsafe.As(CreateDelegateFromMethod(method, false)); } - [DebuggerStepThrough] + //[DebuggerStepThrough] internal static IntPtr MarshalAndInvoke(object delegateContext, ManagedHandle instancePtr, IntPtr paramPtr) { (Type[] types, InvokerDelegate deleg) = (Tuple)(delegateContext); @@ -1040,7 +1126,7 @@ namespace FlaxEngine.Interop return MarshalReturnValue(ref ret); } - [DebuggerStepThrough] + //[DebuggerStepThrough] internal static unsafe IntPtr InvokeThunk(object delegateContext, ManagedHandle instancePtr, IntPtr* paramPtrs) { ThunkInvokerDelegate deleg = Unsafe.As(delegateContext); @@ -1067,7 +1153,7 @@ namespace FlaxEngine.Interop return Unsafe.As(CreateDelegateFromMethod(method, false)); } - [DebuggerStepThrough] + //[DebuggerStepThrough] internal static IntPtr MarshalAndInvoke(object delegateContext, ManagedHandle instancePtr, IntPtr paramPtr) { (Type[] types, InvokerDelegate deleg) = (Tuple)(delegateContext); @@ -1087,7 +1173,7 @@ namespace FlaxEngine.Interop return MarshalReturnValue(ref ret); } - [DebuggerStepThrough] + //[DebuggerStepThrough] internal static unsafe IntPtr InvokeThunk(object delegateContext, ManagedHandle instancePtr, IntPtr* paramPtrs) { ThunkInvokerDelegate deleg = Unsafe.As(delegateContext); @@ -1116,7 +1202,7 @@ namespace FlaxEngine.Interop return Unsafe.As(CreateDelegateFromMethod(method, false)); } - [DebuggerStepThrough] + //[DebuggerStepThrough] internal static IntPtr MarshalAndInvoke(object delegateContext, ManagedHandle instancePtr, IntPtr paramPtr) { (Type[] types, InvokerDelegate deleg) = (Tuple)(delegateContext); @@ -1142,7 +1228,7 @@ namespace FlaxEngine.Interop return MarshalReturnValue(ref ret); } - [DebuggerStepThrough] + //[DebuggerStepThrough] internal static unsafe IntPtr InvokeThunk(object delegateContext, ManagedHandle instancePtr, IntPtr* paramPtrs) { ThunkInvokerDelegate deleg = Unsafe.As(delegateContext); @@ -1172,7 +1258,7 @@ namespace FlaxEngine.Interop return Unsafe.As(CreateDelegateFromMethod(method, false)); } - [DebuggerStepThrough] + //[DebuggerStepThrough] internal static IntPtr MarshalAndInvoke(object delegateContext, ManagedHandle instancePtr, IntPtr paramPtr) { (Type[] types, InvokerDelegate deleg) = (Tuple)(delegateContext); @@ -1204,7 +1290,7 @@ namespace FlaxEngine.Interop return MarshalReturnValue(ref ret); } - [DebuggerStepThrough] + //[DebuggerStepThrough] internal static unsafe IntPtr InvokeThunk(object delegateContext, ManagedHandle instancePtr, IntPtr* paramPtrs) { ThunkInvokerDelegate deleg = Unsafe.As(delegateContext); @@ -1235,7 +1321,7 @@ namespace FlaxEngine.Interop return Unsafe.As(CreateDelegateFromMethod(method, false)); } - [DebuggerStepThrough] + //[DebuggerStepThrough] internal static IntPtr MarshalAndInvoke(object delegateContext, ManagedHandle instancePtr, IntPtr paramPtr) { (Type[] types, InvokerDelegate deleg) = (Tuple)(delegateContext); @@ -1273,7 +1359,7 @@ namespace FlaxEngine.Interop return MarshalReturnValue(ref ret); } - [DebuggerStepThrough] + //[DebuggerStepThrough] internal static unsafe IntPtr InvokeThunk(object delegateContext, ManagedHandle instancePtr, IntPtr* paramPtrs) { ThunkInvokerDelegate deleg = Unsafe.As(delegateContext); From 1b0976d99b4f828c41fabe0b5e58479d49e997c8 Mon Sep 17 00:00:00 2001 From: Ari Vuollet Date: Fri, 4 Aug 2023 22:48:28 +0300 Subject: [PATCH 13/19] Refactor ManagedHandlePool Weak handles are now stored in one sets of dictionarys synchronized with other threads. This so far seems the fastest way to manage the dictionaries for now. --- Source/Engine/Engine/NativeInterop.Managed.cs | 269 ++++++++++-------- 1 file changed, 147 insertions(+), 122 deletions(-) diff --git a/Source/Engine/Engine/NativeInterop.Managed.cs b/Source/Engine/Engine/NativeInterop.Managed.cs index 95146a95c..78aa4dc37 100644 --- a/Source/Engine/Engine/NativeInterop.Managed.cs +++ b/Source/Engine/Engine/NativeInterop.Managed.cs @@ -330,15 +330,9 @@ namespace FlaxEngine.Interop { private IntPtr handle; - private ManagedHandle(IntPtr handle) - { - this.handle = handle; - } + private ManagedHandle(IntPtr handle) => this.handle = handle; - private ManagedHandle(object value, GCHandleType type) - { - handle = ManagedHandlePool.AllocateHandle(value, type); - } + private ManagedHandle(object value, GCHandleType type) => handle = ManagedHandlePool.AllocateHandle(value, type); [MethodImpl(MethodImplOptions.AggressiveInlining)] public static ManagedHandle Alloc(object value) => new ManagedHandle(value, GCHandleType.Normal); @@ -383,7 +377,7 @@ namespace FlaxEngine.Interop public override int GetHashCode() => handle.GetHashCode(); - public override bool Equals(object o) => o is ManagedHandle other && Equals(other); + public override bool Equals(object obj) => obj is ManagedHandle other && handle == other.handle; public bool Equals(ManagedHandle other) => handle == other.handle; @@ -391,42 +385,44 @@ namespace FlaxEngine.Interop public static bool operator !=(ManagedHandle a, ManagedHandle b) => a.handle != b.handle; - private static class ManagedHandlePool + internal static class ManagedHandlePool { private const int WeakPoolCollectionSizeThreshold = 10000000; private const int WeakPoolCollectionTimeThreshold = 500; - private static ulong normalHandleAccumulator = 0; - private static ulong pinnedHandleAccumulator = 0; - private static ulong weakHandleAccumulator = 0; + // Rolling numbers for handles, two bits reserved for the type + private static ulong normalHandleAccumulator = ((ulong)GCHandleType.Normal << 62) & 0xC000000000000000; + private static ulong pinnedHandleAccumulator = ((ulong)GCHandleType.Pinned << 62) & 0xC000000000000000; + private static ulong weakHandleAccumulator = ((ulong)GCHandleType.Weak << 62) & 0xC000000000000000; - private static object poolLock = new object(); - private static Dictionary persistentPool = new Dictionary(); - private static Dictionary pinnedPool = new Dictionary(); + // 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. + private static Dictionary persistentPool = new(); + private static Dictionary pinnedPool = new(); - // Manage double-buffered pool for weak handles in order to avoid collecting in-flight handles - [ThreadStatic] private static Dictionary weakPool; - [ThreadStatic] private static Dictionary weakPoolOther; - [ThreadStatic] private static ulong nextWeakPoolCollection; - [ThreadStatic] private static int nextWeakPoolGCCollection; - [ThreadStatic] private static long lastWeakPoolCollectionTime; + // 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 ulong nextWeakPoolCollection; + private static int nextWeakPoolGCCollection; + private static long lastWeakPoolCollectionTime; /// /// Tries to free all references to old weak handles so GC can collect them. /// - private static void TryCollectWeakHandles() + internal static void TryCollectWeakHandles() { if (weakHandleAccumulator < nextWeakPoolCollection) return; nextWeakPoolCollection = weakHandleAccumulator + 1000; - if (weakPool == null) - { - weakPool = new Dictionary(); - weakPoolOther = new Dictionary(); - nextWeakPoolGCCollection = GC.CollectionCount(0) + 1; - return; - } + // Try to swap pools after garbage collection or whenever the pool gets too large var gc0CollectionCount = GC.CollectionCount(0); if (gc0CollectionCount < nextWeakPoolGCCollection && weakPool.Count < WeakPoolCollectionSizeThreshold) @@ -443,130 +439,159 @@ namespace FlaxEngine.Interop weakPool.Clear(); } - private static IntPtr NewHandle(GCHandleType type) + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static IntPtr NewHandle(GCHandleType type) => type switch { - IntPtr handle; - if (type == GCHandleType.Normal) - handle = (IntPtr)Interlocked.Increment(ref normalHandleAccumulator); - else if (type == GCHandleType.Pinned) - handle = (IntPtr)Interlocked.Increment(ref pinnedHandleAccumulator); - else //if (type == GCHandleType.Weak || type == GCHandleType.WeakTrackResurrection) - handle = (IntPtr)Interlocked.Increment(ref weakHandleAccumulator); - - // Two bits reserved for the type - handle |= (IntPtr)(((ulong)type << 62) & 0xC000000000000000); - return handle; - } + GCHandleType.Normal => (IntPtr)Interlocked.Increment(ref normalHandleAccumulator), + GCHandleType.Pinned => (IntPtr)Interlocked.Increment(ref pinnedHandleAccumulator), + GCHandleType.Weak => (IntPtr)Interlocked.Increment(ref weakHandleAccumulator), + GCHandleType.WeakTrackResurrection => (IntPtr)Interlocked.Increment(ref weakHandleAccumulator), + _ => throw new NotImplementedException(type.ToString()) + }; [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static GCHandleType GetHandleType(IntPtr handle) - { - return (GCHandleType)(((ulong)handle & 0xC000000000000000) >> 62); - } + private static GCHandleType GetHandleType(IntPtr handle) => (GCHandleType)(((ulong)handle & 0xC000000000000000) >> 62); internal static IntPtr AllocateHandle(object value, GCHandleType type) { - TryCollectWeakHandles(); IntPtr handle = NewHandle(type); - if (type == GCHandleType.Normal) + switch (type) { - lock (poolLock) - persistentPool.Add(handle, value); + case GCHandleType.Normal: + lock (persistentPool) + persistentPool.Add(handle, value); + break; + case GCHandleType.Pinned: + lock (pinnedPool) + pinnedPool.Add(handle, GCHandle.Alloc(value, GCHandleType.Pinned)); + break; + case GCHandleType.Weak: + case GCHandleType.WeakTrackResurrection: + lock (weakPoolLock) + { + TryCollectWeakHandles(); + weakPool.Add(handle, value); + } + break; } - else if (type == GCHandleType.Pinned) - { - lock (poolLock) - pinnedPool.Add(handle, GCHandle.Alloc(value, GCHandleType.Pinned)); - } - else if (type == GCHandleType.Weak || type == GCHandleType.WeakTrackResurrection) - weakPool.Add(handle, value); - return handle; } internal static object GetObject(IntPtr handle) { - TryCollectWeakHandles(); - object value; - GCHandleType type = GetHandleType(handle); - if (type == GCHandleType.Normal) + switch (GetHandleType(handle)) { - lock (poolLock) - { - if (persistentPool.TryGetValue(handle, out value)) - return value; - } + case GCHandleType.Normal: + lock (persistentPool) + { + if (persistentPool.TryGetValue(handle, out object value)) + return value; + } + break; + case GCHandleType.Pinned: + lock (pinnedPool) + { + if (pinnedPool.TryGetValue(handle, out GCHandle gchandle)) + return gchandle.Target; + } + break; + case GCHandleType.Weak: + case GCHandleType.WeakTrackResurrection: + { + lock (weakPoolLock) + { + TryCollectWeakHandles(); + if (weakPool.TryGetValue(handle, out object value)) + return value; + else if (weakPoolOther.TryGetValue(handle, out value)) + return value; + } + } + break; } - else if (type == GCHandleType.Pinned) - { - lock (poolLock) - { - if (pinnedPool.TryGetValue(handle, out GCHandle gchandle)) - return gchandle.Target; - } - } - else if (weakPool.TryGetValue(handle, out value)) - return value; - else if (weakPoolOther.TryGetValue(handle, out value)) - return value; - throw new NativeInteropException("Invalid ManagedHandle"); } internal static void SetObject(IntPtr handle, object value) { - TryCollectWeakHandles(); - GCHandleType type = GetHandleType(handle); - if (type == GCHandleType.Normal) + switch (GetHandleType(handle)) { - lock (poolLock) - { - if (persistentPool.ContainsKey(handle)) - persistentPool[handle] = value; - } + case GCHandleType.Normal: + lock (persistentPool) + { + ref object obj = ref CollectionsMarshal.GetValueRefOrNullRef(persistentPool, handle); + if (!Unsafe.IsNullRef(ref obj)) + { + obj = value; + return; + } + } + break; + case GCHandleType.Pinned: + lock (pinnedPool) + { + ref GCHandle gchandle = ref CollectionsMarshal.GetValueRefOrNullRef(pinnedPool, handle); + if (!Unsafe.IsNullRef(ref gchandle)) + { + gchandle.Target = value; + return; + } + } + break; + case GCHandleType.Weak: + case GCHandleType.WeakTrackResurrection: + lock (weakPoolLock) + { + TryCollectWeakHandles(); + { + 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 if (type == GCHandleType.Pinned) - { - lock (poolLock) - { - if (pinnedPool.TryGetValue(handle, out GCHandle gchandle)) - gchandle.Target = value; - } - } - else if (weakPool.ContainsKey(handle)) - weakPool[handle] = value; - else if (weakPoolOther.ContainsKey(handle)) - weakPoolOther[handle] = value; - throw new NativeInteropException("Invalid ManagedHandle"); } internal static void FreeHandle(IntPtr handle) { - TryCollectWeakHandles(); - GCHandleType type = GetHandleType(handle); - if (type == GCHandleType.Normal) + switch (GetHandleType(handle)) { - lock (poolLock) - { - if (persistentPool.Remove(handle)) - return; - } - } - else if (type == GCHandleType.Pinned) - { - lock (poolLock) - { - if (pinnedPool.Remove(handle, out GCHandle gchandle)) + case GCHandleType.Normal: + lock (persistentPool) { - gchandle.Free(); - return; + if (persistentPool.Remove(handle)) + return; } - } + break; + case GCHandleType.Pinned: + lock (pinnedPool) + { + if (pinnedPool.Remove(handle, out GCHandle gchandle)) + { + gchandle.Free(); + return; + } + } + break; + case GCHandleType.Weak: + case GCHandleType.WeakTrackResurrection: + lock (weakPoolLock) + TryCollectWeakHandles(); + return; } - else - return; - throw new NativeInteropException("Invalid ManagedHandle"); } } From cd56101aa3ebbb269d72274bd7138d5380ffab82 Mon Sep 17 00:00:00 2001 From: Ari Vuollet Date: Mon, 7 Aug 2023 21:51:45 +0300 Subject: [PATCH 14/19] Expose `Object::DeleteObjectNow` as `Object.DestroyNow` in managed scripting --- Source/Engine/Scripting/Object.cs | 15 +++++++++++++++ Source/Engine/Scripting/ScriptingObject.cpp | 7 +++++++ 2 files changed, 22 insertions(+) diff --git a/Source/Engine/Scripting/Object.cs b/Source/Engine/Scripting/Object.cs index 0180c0935..170367b7e 100644 --- a/Source/Engine/Scripting/Object.cs +++ b/Source/Engine/Scripting/Object.cs @@ -179,6 +179,18 @@ namespace FlaxEngine Internal_Destroy(GetUnmanagedPtr(obj), timeLeft); } + /// + /// Destroys the specified object and clears the reference variable. + /// The object obj will be destroyed immediately. + /// If obj is a Script it will be removed from the Actor and deleted. + /// If obj is an Actor it will be removed from the Scene and deleted as well as all its Scripts and all children of the Actor. + /// + /// The object to destroy. + public static void DestroyNow(Object obj) + { + Internal_DestroyNow(GetUnmanagedPtr(obj)); + } + /// /// Destroys the specified object and clears the reference variable. /// The object obj will be destroyed now or after the time specified in seconds from now. @@ -316,6 +328,9 @@ namespace FlaxEngine [LibraryImport("FlaxEngine", EntryPoint = "ObjectInternal_Destroy", StringMarshalling = StringMarshalling.Custom, StringMarshallingCustomType = typeof(Interop.StringMarshaller))] internal static partial void Internal_Destroy(IntPtr obj, float timeLeft); + [LibraryImport("FlaxEngine", EntryPoint = "ObjectInternal_DestroyNow", StringMarshalling = StringMarshalling.Custom, StringMarshallingCustomType = typeof(Interop.StringMarshaller))] + internal static partial void Internal_DestroyNow(IntPtr obj); + [LibraryImport("FlaxEngine", EntryPoint = "ObjectInternal_GetTypeName", StringMarshalling = StringMarshalling.Custom, StringMarshallingCustomType = typeof(Interop.StringMarshaller))] internal static partial string Internal_GetTypeName(IntPtr obj); diff --git a/Source/Engine/Scripting/ScriptingObject.cpp b/Source/Engine/Scripting/ScriptingObject.cpp index 9ece58f2a..12144385d 100644 --- a/Source/Engine/Scripting/ScriptingObject.cpp +++ b/Source/Engine/Scripting/ScriptingObject.cpp @@ -681,6 +681,12 @@ DEFINE_INTERNAL_CALL(void) ObjectInternal_Destroy(ScriptingObject* obj, float ti obj->DeleteObject(timeLeft, useGameTime); } +DEFINE_INTERNAL_CALL(void) ObjectInternal_DestroyNow(ScriptingObject* obj) +{ + if (obj) + obj->DeleteObjectNow(); +} + DEFINE_INTERNAL_CALL(MString*) ObjectInternal_GetTypeName(ScriptingObject* obj) { INTERNAL_CALL_CHECK_RETURN(obj, nullptr); @@ -777,6 +783,7 @@ public: ADD_INTERNAL_CALL("FlaxEngine.Object::Internal_ManagedInstanceCreated", &ObjectInternal_ManagedInstanceCreated); ADD_INTERNAL_CALL("FlaxEngine.Object::Internal_ManagedInstanceDeleted", &ObjectInternal_ManagedInstanceDeleted); ADD_INTERNAL_CALL("FlaxEngine.Object::Internal_Destroy", &ObjectInternal_Destroy); + ADD_INTERNAL_CALL("FlaxEngine.Object::Internal_DestroyNow", &ObjectInternal_DestroyNow); ADD_INTERNAL_CALL("FlaxEngine.Object::Internal_GetTypeName", &ObjectInternal_GetTypeName); ADD_INTERNAL_CALL("FlaxEngine.Object::Internal_FindObject", &ObjectInternal_FindObject); ADD_INTERNAL_CALL("FlaxEngine.Object::Internal_TryFindObject", &ObjectInternal_TryFindObject); From 5ae27a0e92e2940e98d873b941935585a57265c1 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Mon, 14 Aug 2023 15:25:12 +0200 Subject: [PATCH 15/19] Code style fix --- .../VisualStudio/VCProjectGenerator.cs | 28 +++++++++---------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/Source/Tools/Flax.Build/Projects/VisualStudio/VCProjectGenerator.cs b/Source/Tools/Flax.Build/Projects/VisualStudio/VCProjectGenerator.cs index 76a0cf04b..3e2a6c9b7 100644 --- a/Source/Tools/Flax.Build/Projects/VisualStudio/VCProjectGenerator.cs +++ b/Source/Tools/Flax.Build/Projects/VisualStudio/VCProjectGenerator.cs @@ -108,7 +108,7 @@ namespace Flax.Build.Projects.VisualStudio vcProjectFileContent.AppendLine(" false"); vcProjectFileContent.AppendLine(" ./"); vcProjectFileContent.AppendLine(" "); - + // Default properties vcProjectFileContent.AppendLine(" "); @@ -319,21 +319,21 @@ namespace Flax.Build.Projects.VisualStudio // IntelliSense information - List additionalOptions = new List(); + var additionalOptions = new List(); switch (project.Configurations[0].TargetBuildOptions.CompileEnv.CppVersion) { - case CppVersion.Cpp14: - additionalOptions.Add("/std:c++14"); - break; - case CppVersion.Cpp17: - additionalOptions.Add("/std:c++17"); - break; - case CppVersion.Cpp20: - additionalOptions.Add("/std:c++20"); - break; - case CppVersion.Latest: - additionalOptions.Add("/std:c++latest"); - break; + case CppVersion.Cpp14: + additionalOptions.Add("/std:c++14"); + break; + case CppVersion.Cpp17: + additionalOptions.Add("/std:c++17"); + break; + case CppVersion.Cpp20: + additionalOptions.Add("/std:c++20"); + break; + case CppVersion.Latest: + additionalOptions.Add("/std:c++latest"); + break; } vcProjectFileContent.AppendLine(" "); From 13e0582ef20ab15358d126bbe9a1358896fb4125 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Mon, 14 Aug 2023 15:36:19 +0200 Subject: [PATCH 16/19] Codestyle fixes --- Source/Engine/Engine/NativeInterop.Invoker.cs | 16 ++++------------ 1 file changed, 4 insertions(+), 12 deletions(-) diff --git a/Source/Engine/Engine/NativeInterop.Invoker.cs b/Source/Engine/Engine/NativeInterop.Invoker.cs index ade1e3570..381e6dac0 100644 --- a/Source/Engine/Engine/NativeInterop.Invoker.cs +++ b/Source/Engine/Engine/NativeInterop.Invoker.cs @@ -74,23 +74,17 @@ namespace FlaxEngine.Interop internal static IntPtr MarshalReturnValueString(ref string returnValue) { - if (returnValue == null) - return IntPtr.Zero; - return ManagedString.ToNativeWeak(returnValue); + return returnValue != null ? ManagedString.ToNativeWeak(returnValue) : IntPtr.Zero; } internal static IntPtr MarshalReturnValueManagedHandle(ref ManagedHandle returnValue) { - if (returnValue == null) - return IntPtr.Zero; - return ManagedHandle.ToIntPtr(returnValue); + return returnValue != null ? ManagedHandle.ToIntPtr(returnValue) : IntPtr.Zero; } internal static IntPtr MarshalReturnValueType(ref Type returnValue) { - if (returnValue == null) - return IntPtr.Zero; - return ManagedHandle.ToIntPtr(GetTypeGCHandle(returnValue)); + return returnValue != null ? ManagedHandle.ToIntPtr(GetTypeGCHandle(returnValue)) : IntPtr.Zero; } internal static IntPtr MarshalReturnValueArray(ref TRet returnValue) @@ -150,9 +144,7 @@ namespace FlaxEngine.Interop internal static IntPtr MarshalReturnValueWrapped(ref TRet returnValue) { - if (returnValue == null) - return IntPtr.Zero; - return ManagedHandle.ToIntPtr(returnValue, GCHandleType.Weak); + return returnValue != null ? ManagedHandle.ToIntPtr(returnValue, GCHandleType.Weak) : IntPtr.Zero; } internal static IntPtr MarshalReturnValue(ref TRet returnValue) From 860dce487f6e923537ae2dc16cb3428761f4bbd9 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Mon, 14 Aug 2023 15:47:24 +0200 Subject: [PATCH 17/19] Codestyle fixes --- Source/Engine/Engine/NativeInterop.Managed.cs | 192 +++++++++--------- 1 file changed, 95 insertions(+), 97 deletions(-) diff --git a/Source/Engine/Engine/NativeInterop.Managed.cs b/Source/Engine/Engine/NativeInterop.Managed.cs index 3e1d0fa8c..e4dc449bc 100644 --- a/Source/Engine/Engine/NativeInterop.Managed.cs +++ b/Source/Engine/Engine/NativeInterop.Managed.cs @@ -62,7 +62,7 @@ namespace FlaxEngine.Interop { ManagedArray managedArray = ManagedArrayPool.Get(arr.Length * NativeInterop.GetTypeSize(arr.GetType().GetElementType())); managedArray.WrapArray(arr, arrayType); - + if (pooledArrayHandles == null) pooledArrayHandles = new(); if (!pooledArrayHandles.TryGetValue(managedArray, out ManagedHandle handle)) @@ -484,22 +484,22 @@ namespace FlaxEngine.Interop IntPtr handle = NewHandle(type); switch (type) { - case GCHandleType.Normal: - lock (persistentPool) - persistentPool.Add(handle, value); - break; - case GCHandleType.Pinned: - lock (pinnedPool) - pinnedPool.Add(handle, GCHandle.Alloc(value, GCHandleType.Pinned)); - break; - case GCHandleType.Weak: - case GCHandleType.WeakTrackResurrection: - lock (weakPoolLock) - { - TryCollectWeakHandles(); - weakPool.Add(handle, value); - } - break; + case GCHandleType.Normal: + lock (persistentPool) + persistentPool.Add(handle, value); + break; + case GCHandleType.Pinned: + lock (pinnedPool) + pinnedPool.Add(handle, GCHandle.Alloc(value, GCHandleType.Pinned)); + break; + case GCHandleType.Weak: + case GCHandleType.WeakTrackResurrection: + lock (weakPoolLock) + { + TryCollectWeakHandles(); + weakPool.Add(handle, value); + } + break; } return handle; } @@ -508,33 +508,31 @@ namespace FlaxEngine.Interop { switch (GetHandleType(handle)) { - case GCHandleType.Normal: - lock (persistentPool) - { - if (persistentPool.TryGetValue(handle, out object value)) - return value; - } - break; - case GCHandleType.Pinned: - lock (pinnedPool) - { - if (pinnedPool.TryGetValue(handle, out GCHandle gchandle)) - return gchandle.Target; - } - break; - case GCHandleType.Weak: - case GCHandleType.WeakTrackResurrection: - { - lock (weakPoolLock) - { - TryCollectWeakHandles(); - if (weakPool.TryGetValue(handle, out object value)) - return value; - else if (weakPoolOther.TryGetValue(handle, out value)) - return value; - } - } - break; + case GCHandleType.Normal: + lock (persistentPool) + { + if (persistentPool.TryGetValue(handle, out object value)) + return value; + } + break; + case GCHandleType.Pinned: + lock (pinnedPool) + { + if (pinnedPool.TryGetValue(handle, out GCHandle gcHandle)) + return gcHandle.Target; + } + break; + case GCHandleType.Weak: + case GCHandleType.WeakTrackResurrection: + lock (weakPoolLock) + { + TryCollectWeakHandles(); + if (weakPool.TryGetValue(handle, out object value)) + return value; + else if (weakPoolOther.TryGetValue(handle, out value)) + return value; + } + break; } throw new NativeInteropException("Invalid ManagedHandle"); } @@ -543,51 +541,51 @@ namespace FlaxEngine.Interop { switch (GetHandleType(handle)) { - case GCHandleType.Normal: - lock (persistentPool) + case GCHandleType.Normal: + lock (persistentPool) + { + ref object obj = ref CollectionsMarshal.GetValueRefOrNullRef(persistentPool, handle); + if (!Unsafe.IsNullRef(ref obj)) { - ref object obj = ref CollectionsMarshal.GetValueRefOrNullRef(persistentPool, handle); + obj = value; + return; + } + } + break; + case GCHandleType.Pinned: + lock (pinnedPool) + { + ref GCHandle gcHandle = ref CollectionsMarshal.GetValueRefOrNullRef(pinnedPool, handle); + if (!Unsafe.IsNullRef(ref gcHandle)) + { + gcHandle.Target = value; + return; + } + } + break; + case GCHandleType.Weak: + case GCHandleType.WeakTrackResurrection: + lock (weakPoolLock) + { + TryCollectWeakHandles(); + { + ref object obj = ref CollectionsMarshal.GetValueRefOrNullRef(weakPool, handle); if (!Unsafe.IsNullRef(ref obj)) { obj = value; return; } } - break; - case GCHandleType.Pinned: - lock (pinnedPool) { - ref GCHandle gchandle = ref CollectionsMarshal.GetValueRefOrNullRef(pinnedPool, handle); - if (!Unsafe.IsNullRef(ref gchandle)) + ref object obj = ref CollectionsMarshal.GetValueRefOrNullRef(weakPoolOther, handle); + if (!Unsafe.IsNullRef(ref obj)) { - gchandle.Target = value; + obj = value; return; } } - break; - case GCHandleType.Weak: - case GCHandleType.WeakTrackResurrection: - lock (weakPoolLock) - { - TryCollectWeakHandles(); - { - 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; + } + break; } throw new NativeInteropException("Invalid ManagedHandle"); } @@ -596,28 +594,28 @@ namespace FlaxEngine.Interop { switch (GetHandleType(handle)) { - case GCHandleType.Normal: - lock (persistentPool) + case GCHandleType.Normal: + lock (persistentPool) + { + if (persistentPool.Remove(handle)) + return; + } + break; + case GCHandleType.Pinned: + lock (pinnedPool) + { + if (pinnedPool.Remove(handle, out GCHandle gcHandle)) { - if (persistentPool.Remove(handle)) - return; + gcHandle.Free(); + return; } - break; - case GCHandleType.Pinned: - lock (pinnedPool) - { - if (pinnedPool.Remove(handle, out GCHandle gchandle)) - { - gchandle.Free(); - return; - } - } - break; - case GCHandleType.Weak: - case GCHandleType.WeakTrackResurrection: - lock (weakPoolLock) - TryCollectWeakHandles(); - return; + } + break; + case GCHandleType.Weak: + case GCHandleType.WeakTrackResurrection: + lock (weakPoolLock) + TryCollectWeakHandles(); + return; } throw new NativeInteropException("Invalid ManagedHandle"); } From 66042845e4a4016c49cca3a1b61240a51e4702be Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Mon, 14 Aug 2023 16:13:55 +0200 Subject: [PATCH 18/19] Codestyle fixe --- Source/Engine/Networking/NetworkReplicator.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/Engine/Networking/NetworkReplicator.cpp b/Source/Engine/Networking/NetworkReplicator.cpp index 4a0f9437d..c9651a055 100644 --- a/Source/Engine/Networking/NetworkReplicator.cpp +++ b/Source/Engine/Networking/NetworkReplicator.cpp @@ -1549,7 +1549,7 @@ void NetworkInternal::NetworkReplicatorUpdate() if (it == Objects.End()) { #if USE_EDITOR || !BUILD_RELEASE - if(!DespawnedObjects.Contains(obj->GetID())) + if (!DespawnedObjects.Contains(obj->GetID())) LOG(Error, "Cannot invoke RPC method '{0}.{1}' on object '{2}' that is not registered in networking (use 'NetworkReplicator.AddObject').", e.Name.First.ToString(), String(e.Name.Second), obj->GetID()); #endif continue; From 5e3018817cb47f66ca5df56110e4e9daba40b061 Mon Sep 17 00:00:00 2001 From: Ari Vuollet Date: Mon, 14 Aug 2023 18:24:53 +0300 Subject: [PATCH 19/19] Fix marshalling issue with PostFxMaterialSettings Materials --- Source/Tools/Flax.Build/Bindings/BindingsGenerator.CSharp.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.CSharp.cs b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.CSharp.cs index fc3e6a534..f399dd67c 100644 --- a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.CSharp.cs +++ b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.CSharp.cs @@ -1596,7 +1596,9 @@ namespace Flax.Build.Bindings toManagedContent.Append($"managed.{fieldInfo.Name} != IntPtr.Zero ? NativeInterop.GCHandleArrayToManagedArray<{originalElementType}>(Unsafe.As(ManagedHandle.FromIntPtr(managed.{fieldInfo.Name}).Target)) : null"); toNativeContent.Append($"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(); }}"); - freeContents2.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 + //freeContents2.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(); }}"); } else {