diff --git a/Source/Editor/Cooker/Platform/iOS/iOSPlatformTools.cpp b/Source/Editor/Cooker/Platform/iOS/iOSPlatformTools.cpp index 1f8c84551..9f2087cac 100644 --- a/Source/Editor/Cooker/Platform/iOS/iOSPlatformTools.cpp +++ b/Source/Editor/Cooker/Platform/iOS/iOSPlatformTools.cpp @@ -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; } diff --git a/Source/Engine/Engine/NativeInterop.Managed.cs b/Source/Engine/Engine/NativeInterop.Managed.cs index 9ff5939d4..495222373 100644 --- a/Source/Engine/Engine/NativeInterop.Managed.cs +++ b/Source/Engine/Engine/NativeInterop.Managed.cs @@ -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 persistentPool = new Dictionary(); private static Dictionary pinnedPool = new Dictionary(); - private static Dictionary weakPool1 = new Dictionary(); - private static Dictionary weakPool2 = new Dictionary(); - private static Dictionary weakPool = weakPool1; - private static Dictionary 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 weakPool; + [ThreadStatic] private static Dictionary weakPoolOther; + [ThreadStatic] private static ulong nextWeakPoolCollection; + [ThreadStatic] private static int nextWeakPoolGCCollection; /// /// Tries to free all references to old weak handles so GC can collect them. /// - 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(); + weakPoolOther = new Dictionary(); + 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"); } } diff --git a/Source/Engine/Engine/NativeInterop.Marshallers.cs b/Source/Engine/Engine/NativeInterop.Marshallers.cs index 96ba7edf5..e6a96677f 100644 --- a/Source/Engine/Engine/NativeInterop.Marshallers.cs +++ b/Source/Engine/Engine/NativeInterop.Marshallers.cs @@ -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 managed) => DictionaryMarshaller.ToNative(managed); - public static void Free(IntPtr unmanaged) => DictionaryMarshaller.Free(unmanaged); + public static IntPtr ConvertToUnmanaged(Dictionary managed) => DictionaryMarshaller.ToNative(managed, GCHandleType.Weak); + public static void Free(IntPtr unmanaged) + { + //DictionaryMarshaller.Free(unmanaged); // No need to free weak handles + } } public struct Bidirectional @@ -265,7 +272,7 @@ namespace FlaxEngine.Interop public static IntPtr ConvertToUnmanaged(Dictionary managed) => managed != null ? ManagedHandle.ToIntPtr(managed) : IntPtr.Zero; public static Dictionary ToManaged(IntPtr unmanaged) => unmanaged != IntPtr.Zero ? Unsafe.As>(ManagedHandle.FromIntPtr(unmanaged).Target) : null; - public static IntPtr ToNative(Dictionary managed) => managed != null ? ManagedHandle.ToIntPtr(managed) : IntPtr.Zero; + public static IntPtr ToNative(Dictionary 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(managed.Length); - var ptr = ManagedHandle.ToIntPtr(managedArray); - return (TUnmanagedElement*)ptr; + return (TUnmanagedElement*)ManagedHandle.ToIntPtr(managedArray, GCHandleType.Weak); } public static ReadOnlySpan GetManagedValuesSource(T[]? managed) => managed; @@ -354,7 +360,7 @@ namespace FlaxEngine.Interop return; ManagedHandle handle = ManagedHandle.FromIntPtr(new IntPtr(unmanaged)); (Unsafe.As(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 diff --git a/Source/Engine/Platform/Android/AndroidFileSystem.cpp b/Source/Engine/Platform/Android/AndroidFileSystem.cpp index 09ee35a0f..78fa8953d 100644 --- a/Source/Engine/Platform/Android/AndroidFileSystem.cpp +++ b/Source/Engine/Platform/Android/AndroidFileSystem.cpp @@ -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 diff --git a/Source/Engine/Platform/Apple/AppleFileSystem.cpp b/Source/Engine/Platform/Apple/AppleFileSystem.cpp index e1b19ef56..cc18fc3ed 100644 --- a/Source/Engine/Platform/Apple/AppleFileSystem.cpp +++ b/Source/Engine/Platform/Apple/AppleFileSystem.cpp @@ -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; diff --git a/Source/Engine/Platform/Linux/LinuxFileSystem.cpp b/Source/Engine/Platform/Linux/LinuxFileSystem.cpp index e713f5777..f0d5ff499 100644 --- a/Source/Engine/Platform/Linux/LinuxFileSystem.cpp +++ b/Source/Engine/Platform/Linux/LinuxFileSystem.cpp @@ -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; diff --git a/Source/Tools/Flax.Build/Build/Assembler.cs b/Source/Tools/Flax.Build/Build/Assembler.cs index da22b3865..be4990087 100644 --- a/Source/Tools/Flax.Build/Build/Assembler.cs +++ b/Source/Tools/Flax.Build/Build/Assembler.cs @@ -20,6 +20,7 @@ namespace Flax.Build /// public class Assembler { + internal static string CacheFileName = "BuilderRules.dll"; private string _cacheFolderPath; /// @@ -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 diff --git a/Source/Tools/Flax.Build/Build/DotNet/DotNetAOT.cs b/Source/Tools/Flax.Build/Build/DotNet/DotNetAOT.cs index a4aa722cc..8a7050c9c 100644 --- a/Source/Tools/Flax.Build/Build/DotNet/DotNetAOT.cs +++ b/Source/Tools/Flax.Build/Build/DotNet/DotNetAOT.cs @@ -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 diff --git a/Source/Tools/Flax.Build/Program.cs b/Source/Tools/Flax.Build/Program.cs index 17bd2050c..5e7cf926c 100644 --- a/Source/Tools/Flax.Build/Program.cs +++ b/Source/Tools/Flax.Build/Program.cs @@ -172,7 +172,7 @@ namespace Flax.Build catch (Exception ex) { Log.Exception(ex); - return 1; + failed = true; } finally {