Merge branch 'GoaLitiuM-weak_handle_improvements' into 1.6

This commit is contained in:
Wojtek Figat
2023-04-29 12:18:39 +02:00
9 changed files with 147 additions and 88 deletions

View File

@@ -69,7 +69,7 @@ void iOSPlatformTools::OnBuildStarted(CookingData& data)
{
// Adjust the cooking output folders for packaging app
const auto appName = GetAppName();
String contents = appName + TEXT(".app/");
String contents = String(TEXT("/Payload/")) / appName + TEXT(".app/");
data.DataOutputPath /= contents;
data.NativeCodeOutputPath /= contents;
data.ManagedCodeOutputPath /= contents;
@@ -160,6 +160,13 @@ bool iOSPlatformTools::OnPostProcess(CookingData& data)
return true;
}
FileSystem::DeleteDirectory(tmpFolderPath);
String iTunesArtworkPath = data.OriginalOutputPath / TEXT("iTunesArtwork.png");
if (EditorUtilities::ExportApplicationImage(iconData, 512, 512, PixelFormat::R8G8B8A8_UNorm, iTunesArtworkPath))
{
LOG(Error, "Failed to export application icon.");
return true;
}
FileSystem::MoveFile(data.OriginalOutputPath / TEXT("iTunesArtwork"), iTunesArtworkPath, true);
}
// Create PkgInfo file
@@ -238,27 +245,24 @@ bool iOSPlatformTools::OnPostProcess(CookingData& data)
}
}
// TODO: sign binaries
// TODO: expose event to inject custom post-processing before app packaging (eg. third-party plugins)
// Package application
/*const auto buildSettings = BuildSettings::Get();
const auto buildSettings = BuildSettings::Get();
if (buildSettings->SkipPackaging)
return false;
GameCooker::PackageFiles();
LOG(Info, "Building app package...");
const String dmgPath = data.OriginalOutputPath / appName + TEXT(".dmg");
const String dmgCommand = String::Format(TEXT("hdiutil create {0}.dmg -volname {0} -fs HFS+ -srcfolder {0}.app"), appName);
const int32 result = Platform::RunProcess(dmgCommand, data.OriginalOutputPath);
const String ipaPath = data.OriginalOutputPath / appName + TEXT(".ipa");
const String ipaCommand = String::Format(TEXT("zip -r -X {0}.ipa Payload iTunesArtwork"), appName);
const int32 result = Platform::RunProcess(ipaCommand, data.OriginalOutputPath);
if (result != 0)
{
data.Error(TEXT("Failed to package app (result code: {0}). See log for more info."), result);
return true;
}
// TODO: sign dmg
LOG(Info, "Output application package: {0} (size: {1} MB)", dmgPath, FileSystem::GetFileSize(dmgPath) / 1024 / 1024);
*/
LOG(Info, "Output application package: {0} (size: {1} MB)", ipaPath, FileSystem::GetFileSize(ipaPath) / 1024 / 1024);
return false;
}

View File

@@ -305,13 +305,11 @@ namespace FlaxEngine.Interop
public void Free()
{
if (handle != IntPtr.Zero)
{
ManagedHandlePool.FreeHandle(handle);
handle = IntPtr.Zero;
}
if (handle == IntPtr.Zero)
return;
ManagedHandlePool.TryCollectWeakHandles();
ManagedHandlePool.FreeHandle(handle);
handle = IntPtr.Zero;
}
public object Target
@@ -352,6 +350,8 @@ namespace FlaxEngine.Interop
private static class ManagedHandlePool
{
private const int WeakPoolCollectionThreshold = 10000000;
private static ulong normalHandleAccumulator = 0;
private static ulong pinnedHandleAccumulator = 0;
private static ulong weakHandleAccumulator = 0;
@@ -360,31 +360,39 @@ namespace FlaxEngine.Interop
private static Dictionary<IntPtr, object> persistentPool = new Dictionary<nint, object>();
private static Dictionary<IntPtr, GCHandle> pinnedPool = new Dictionary<nint, GCHandle>();
private static Dictionary<IntPtr, object> weakPool1 = new Dictionary<nint, object>();
private static Dictionary<IntPtr, object> weakPool2 = new Dictionary<nint, object>();
private static Dictionary<IntPtr, object> weakPool = weakPool1;
private static Dictionary<IntPtr, object> weakPoolOther = weakPool2;
private static int nextCollection = GC.CollectionCount(0) + 1;
// Manage double-buffered pool for weak handles in order to avoid collecting in-flight handles
[ThreadStatic] private static Dictionary<IntPtr, object> weakPool;
[ThreadStatic] private static Dictionary<IntPtr, object> weakPoolOther;
[ThreadStatic] private static ulong nextWeakPoolCollection;
[ThreadStatic] private static int nextWeakPoolGCCollection;
/// <summary>
/// Tries to free all references to old weak handles so GC can collect them.
/// </summary>
internal static void TryCollectWeakHandles()
private static void TryCollectWeakHandles()
{
if (GC.CollectionCount(0) < nextCollection)
if (weakHandleAccumulator < nextWeakPoolCollection)
return;
lock (poolLock)
nextWeakPoolCollection = weakHandleAccumulator + 1000;
if (weakPool == null)
{
nextCollection = GC.CollectionCount(0) + 1;
var swap = weakPoolOther;
weakPoolOther = weakPool;
weakPool = swap;
weakPool.Clear();
weakPool = new Dictionary<nint, object>();
weakPoolOther = new Dictionary<nint, object>();
nextWeakPoolGCCollection = GC.CollectionCount(0);
return;
}
// Collect right after garbage collection or whenever the pool gets too large
var gc0CollectionCount = GC.CollectionCount(0);
if (gc0CollectionCount < nextWeakPoolGCCollection && weakPool.Count < WeakPoolCollectionThreshold)
return;
nextWeakPoolGCCollection = gc0CollectionCount + 1;
var swap = weakPoolOther;
weakPoolOther = weakPool;
weakPool = swap;
weakPool.Clear();
}
private static IntPtr NewHandle(GCHandleType type)
@@ -410,71 +418,107 @@ namespace FlaxEngine.Interop
internal static IntPtr AllocateHandle(object value, GCHandleType type)
{
TryCollectWeakHandles();
IntPtr handle = NewHandle(type);
lock (poolLock)
if (type == GCHandleType.Normal)
{
if (type == GCHandleType.Normal)
lock (poolLock)
persistentPool.Add(handle, value);
else if (type == GCHandleType.Pinned)
pinnedPool.Add(handle, GCHandle.Alloc(value, GCHandleType.Pinned));
else if (type == GCHandleType.Weak || type == GCHandleType.WeakTrackResurrection)
weakPool.Add(handle, value);
}
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);
lock (poolLock)
if (type == GCHandleType.Normal)
{
if (type == GCHandleType.Normal && persistentPool.TryGetValue(handle, out value))
return value;
else if (type == GCHandleType.Pinned && 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;
lock (poolLock)
{
if (persistentPool.TryGetValue(handle, out value))
return value;
}
}
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 Exception("Invalid ManagedHandle");
}
internal static void SetObject(IntPtr handle, object value)
{
TryCollectWeakHandles();
GCHandleType type = GetHandleType(handle);
lock (poolLock)
if (type == GCHandleType.Normal)
{
if (type == GCHandleType.Normal && persistentPool.ContainsKey(handle))
persistentPool[handle] = value;
else if (type == GCHandleType.Pinned && 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;
lock (poolLock)
{
if (persistentPool.ContainsKey(handle))
persistentPool[handle] = value;
}
}
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 Exception("Invalid ManagedHandle");
}
internal static void FreeHandle(IntPtr handle)
{
TryCollectWeakHandles();
GCHandleType type = GetHandleType(handle);
lock (poolLock)
if (type == GCHandleType.Normal)
{
if (type == GCHandleType.Normal && persistentPool.Remove(handle))
return;
else if (type == GCHandleType.Pinned && pinnedPool.Remove(handle, out GCHandle gchandle))
lock (poolLock)
{
gchandle.Free();
return;
if (persistentPool.Remove(handle))
return;
}
else if (weakPool.Remove(handle))
return;
else if (weakPoolOther.Remove(handle))
return;
}
else if (type == GCHandleType.Pinned)
{
lock (poolLock)
{
if (pinnedPool.Remove(handle, out GCHandle gchandle))
{
gchandle.Free();
return;
}
}
}
else
return;
throw new Exception("Invalid ManagedHandle");
}
}

View File

@@ -5,6 +5,7 @@ using System;
using System.Collections.Generic;
using System.Globalization;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.InteropServices.Marshalling;
#pragma warning disable 1591
@@ -34,13 +35,16 @@ namespace FlaxEngine.Interop
public static class ManagedToNative
{
public static IntPtr ConvertToUnmanaged(object managed) => managed != null ? ManagedHandle.ToIntPtr(managed) : IntPtr.Zero;
public static IntPtr ConvertToUnmanaged(object managed) => managed != null ? ManagedHandle.ToIntPtr(managed, GCHandleType.Weak) : IntPtr.Zero;
public static void Free(IntPtr unmanaged)
{
// This is a weak handle, no need to free it
/*
if (unmanaged == IntPtr.Zero)
return;
ManagedHandle.FromIntPtr(unmanaged).Free();
*/
}
}
@@ -170,7 +174,7 @@ namespace FlaxEngine.Interop
{
if (managedArray == null)
return IntPtr.Zero;
handle = ManagedHandle.Alloc(managedArray);
handle = ManagedHandle.Alloc(managedArray, GCHandleType.Weak);
return ManagedHandle.ToIntPtr(handle);
}
@@ -179,7 +183,7 @@ namespace FlaxEngine.Interop
if (managedArray == null)
return;
managedArray.FreePooled();
handle.Free();
//handle.Free(); // No need to free weak handles
}
}
@@ -232,8 +236,11 @@ namespace FlaxEngine.Interop
public static class ManagedToNative
{
public static IntPtr ConvertToUnmanaged(Dictionary<T, U> managed) => DictionaryMarshaller<T, U>.ToNative(managed);
public static void Free(IntPtr unmanaged) => DictionaryMarshaller<T, U>.Free(unmanaged);
public static IntPtr ConvertToUnmanaged(Dictionary<T, U> managed) => DictionaryMarshaller<T, U>.ToNative(managed, GCHandleType.Weak);
public static void Free(IntPtr unmanaged)
{
//DictionaryMarshaller<T, U>.Free(unmanaged); // No need to free weak handles
}
}
public struct Bidirectional
@@ -265,7 +272,7 @@ namespace FlaxEngine.Interop
public static IntPtr ConvertToUnmanaged(Dictionary<T, U> managed) => managed != null ? ManagedHandle.ToIntPtr(managed) : IntPtr.Zero;
public static Dictionary<T, U> ToManaged(IntPtr unmanaged) => unmanaged != IntPtr.Zero ? Unsafe.As<Dictionary<T, U>>(ManagedHandle.FromIntPtr(unmanaged).Target) : null;
public static IntPtr ToNative(Dictionary<T, U> managed) => managed != null ? ManagedHandle.ToIntPtr(managed) : IntPtr.Zero;
public static IntPtr ToNative(Dictionary<T, U> managed, GCHandleType handleType = GCHandleType.Normal) => managed != null ? ManagedHandle.ToIntPtr(managed, handleType) : IntPtr.Zero;
public static void Free(IntPtr unmanaged)
{
@@ -334,8 +341,7 @@ namespace FlaxEngine.Interop
}
numElements = managed.Length;
ManagedArray managedArray = ManagedArray.AllocatePooledArray<TUnmanagedElement>(managed.Length);
var ptr = ManagedHandle.ToIntPtr(managedArray);
return (TUnmanagedElement*)ptr;
return (TUnmanagedElement*)ManagedHandle.ToIntPtr(managedArray, GCHandleType.Weak);
}
public static ReadOnlySpan<T> GetManagedValuesSource(T[]? managed) => managed;
@@ -354,7 +360,7 @@ namespace FlaxEngine.Interop
return;
ManagedHandle handle = ManagedHandle.FromIntPtr(new IntPtr(unmanaged));
(Unsafe.As<ManagedArray>(handle.Target)).FreePooled();
handle.Free();
//handle.Free(); // No need to free weak handles
}
}
@@ -475,10 +481,13 @@ namespace FlaxEngine.Interop
{
public static unsafe IntPtr ConvertToUnmanaged(string managed)
{
return managed == null ? IntPtr.Zero : ManagedHandle.ToIntPtr(managed);
return managed == null ? IntPtr.Zero : ManagedHandle.ToIntPtr(managed, GCHandleType.Weak);
}
public static void Free(IntPtr unmanaged) => ManagedString.Free(unmanaged);
public static void Free(IntPtr unmanaged)
{
//ManagedString.Free(unmanaged); // No need to free weak handles
}
}
public struct Bidirectional

View File

@@ -249,13 +249,13 @@ bool AndroidFileSystem::DeleteFile(const StringView& path)
uint64 AndroidFileSystem::GetFileSize(const StringView& path)
{
struct stat fileInfo;
fileInfo.st_size = -1;
fileInfo.st_size = 0;
const StringAsANSI<> pathANSI(*path, path.Length());
if (stat(pathANSI.Get(), &fileInfo) != -1)
{
if (S_ISDIR(fileInfo.st_mode))
{
fileInfo.st_size = -1;
fileInfo.st_size = 0;
}
}
else

View File

@@ -223,14 +223,14 @@ bool AppleFileSystem::DeleteFile(const StringView& path)
uint64 AppleFileSystem::GetFileSize(const StringView& path)
{
struct stat fileInfo;
fileInfo.st_size = -1;
fileInfo.st_size = 0;
const StringAsANSI<> pathANSI(*path, path.Length());
if (stat(pathANSI.Get(), &fileInfo) != -1)
{
// Check for directories
if (S_ISDIR(fileInfo.st_mode))
{
fileInfo.st_size = -1;
fileInfo.st_size = 0;
}
}
return fileInfo.st_size;

View File

@@ -346,14 +346,14 @@ bool LinuxFileSystem::DeleteFile(const StringView& path)
uint64 LinuxFileSystem::GetFileSize(const StringView& path)
{
struct stat fileInfo;
fileInfo.st_size = -1;
fileInfo.st_size = 0;
const StringAsANSI<> pathANSI(*path, path.Length());
if (stat(pathANSI.Get(), &fileInfo) != -1)
{
// Check for directories
if (S_ISDIR(fileInfo.st_mode))
{
fileInfo.st_size = -1;
fileInfo.st_size = 0;
}
}
return fileInfo.st_size;

View File

@@ -20,6 +20,7 @@ namespace Flax.Build
/// </summary>
public class Assembler
{
internal static string CacheFileName = "BuilderRules.dll";
private string _cacheFolderPath;
/// <summary>
@@ -70,7 +71,7 @@ namespace Flax.Build
string cacheAssemblyPath = null, cacheInfoPath = null, buildInfo = null;
if (_cacheFolderPath != null)
{
cacheAssemblyPath = Path.Combine(_cacheFolderPath, "BuilderRules.dll");
cacheAssemblyPath = Path.Combine(_cacheFolderPath, CacheFileName);
cacheInfoPath = Path.Combine(_cacheFolderPath, "BuilderRulesInfo.txt");
foreach (var sourceFile in SourceFiles)
@@ -162,7 +163,7 @@ namespace Flax.Build
syntaxTrees.Add(parsedSyntaxTree);
}
var compilation = CSharpCompilation.Create("BuilderRulesCache.dll", syntaxTrees.ToArray(), defaultReferences, defaultCompilationOptions);
var compilation = CSharpCompilation.Create(CacheFileName, syntaxTrees.ToArray(), defaultReferences, defaultCompilationOptions);
EmitResult emitResult = compilation.Emit(memoryStream);
// Process warnings and errors

View File

@@ -500,7 +500,8 @@ namespace Flax.Build
return true;
// Skip Flax.Build rules assembly
if (Path.GetFileName(x) == "BuilderRulesCache.dll")
var fileName = Path.GetFileName(x);
if (fileName == Assembler.CacheFileName || fileName == "BuilderRulesCache.dll")
return true;
// Skip non-C# DLLs

View File

@@ -172,7 +172,7 @@ namespace Flax.Build
catch (Exception ex)
{
Log.Exception(ex);
return 1;
failed = true;
}
finally
{