Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

MacOS Silicon M2 Error loading unmanaged library from path: libassimp.dylib #8

Open
PaperPrototype opened this issue Jun 21, 2024 · 0 comments

Comments

@PaperPrototype
Copy link

https://github.com/michaelsakharov/Prowl/tree/NewUI-PersistentLayout

using Prowl.Runtime;
using Prowl.Runtime.Utils;
using Debug = Prowl.Runtime.Debug;

namespace Prowl.Editor.Assets
{
    [FilePath("Library/LastWriteTimes.cache", FilePathAttribute.Location.Data)]
    public class LastWriteTimesCache : ScriptableSingleton<LastWriteTimesCache>
    {
        public readonly Dictionary<string, DateTime> fileLastWriteTimes = [];
    }

    public static partial class AssetDatabase
    {
        #region Properties
        public static Guid LastLoadedAssetID { get; private set; } = Guid.Empty;
        public static string TempAssetDirectory => Path.Combine(Project.ProjectDirectory, "Library/AssetDatabase");

        #endregion

        #region Events

        public static event Action<Guid, FileInfo>? AssetRemoved;
        public static event Action<FileInfo>? Pinged;

        #endregion

        #region Private Fields

        static readonly List<(DirectoryInfo, AssetDirectoryCache)> rootFolders = [];
        static double RefreshTimer = 0f;
        static bool lastFocused = false;
        static readonly Dictionary<string, MetaFile> assetPathToMeta = new(StringComparer.OrdinalIgnoreCase);
        static readonly Dictionary<Guid, MetaFile> assetGuidToMeta = [];
        static readonly Dictionary<Guid, SerializedAsset> guidToAssetData = [];

        #endregion


        #region Public Methods

        internal static void InternalUpdate()
        {
            if (Window.IsFocused)
            {
                RefreshTimer += Time.deltaTime;
                if (!lastFocused || RefreshTimer > 5f)
                    Update();
            }
            lastFocused = Window.IsFocused;
        }

        /// <summary>
        /// Gets the root folder cache at the specified index.
        /// </summary>
        public static AssetDirectoryCache GetRootFolderCache(int index)
        {
            if (index < 0 || index >= rootFolders.Count)
                throw new IndexOutOfRangeException("Index out of range");
            return rootFolders[index].Item2;
        }

        /// <summary>
        /// Gets the list of root folders in the AssetDatabase.
        /// </summary>
        /// <returns>List of root folders.</returns>
        public static List<DirectoryInfo> GetRootFolders() => rootFolders.Select(x => x.Item1).ToList();

        /// <summary>
        /// Adds a root folder to the AssetDatabase.
        /// </summary>
        /// <param name="rootFolder">The path to the root folder.</param>
        public static void AddRootFolder(string rootFolder)
        {
            ArgumentNullException.ThrowIfNullOrEmpty(rootFolder);

            var rootPath = Path.Combine(Project.ProjectDirectory, rootFolder);
            var info = new DirectoryInfo(rootPath);

            if (!info.Exists)
                info.Create();

            if (rootFolders.Any(x => x.Item1.FullName.Equals(info.FullName, StringComparison.OrdinalIgnoreCase)))
                throw new ArgumentException("Root Folder already exists in the Asset Database");

            rootFolders.Add((info, new AssetDirectoryCache(info)));
        }

        /// <summary>
        /// Checks for changes in the AssetDatabase.
        /// Call manually when you make changes to the asset files to ensure the changes are loaded
        /// </summary>
        public static void Update(bool doUnload = true, bool forceCacheUpdate = false)
        {
            if (!Project.HasProject) return;

            RefreshTimer = 0f;

            HashSet<string> currentFiles = [];
            List<string> toReimport = [];
            bool cacheModified = false;
            foreach (var root in rootFolders)
            {
                var files = Directory.GetFiles(root.Item1.FullName, "*", SearchOption.AllDirectories)
                    .Where(file => !file.EndsWith(".meta"));

                foreach (var file in files)
                {
                    // Only process files that are supported by an importer, the rest are ignored
                    if (ImporterAttribute.SupportsExtension(Path.GetExtension(file)))
                    {
                        currentFiles.Add(file);

                        if (!LastWriteTimesCache.Instance.fileLastWriteTimes.TryGetValue(file, out var lastWriteTime)
                            || !MetaFile.HasMeta(new FileInfo(file)))
                        {
                            // New file
                            Debug.Log("Asset Added: " + file);
                            lastWriteTime = File.GetLastWriteTime(file);
                            LastWriteTimesCache.Instance.fileLastWriteTimes[file] = lastWriteTime;
                            cacheModified = true;
                            if (ProcessFile(file, out _))
                                toReimport.Add(file);
                        }
                        else if (File.GetLastWriteTime(file) != lastWriteTime)
                        {
                            // File modified
                            Debug.Log("Asset Updated: " + file);
                            lastWriteTime = File.GetLastWriteTime(file);
                            LastWriteTimesCache.Instance.fileLastWriteTimes[file] = lastWriteTime;
                            cacheModified = true;
                            if (ProcessFile(file, out _))
                                toReimport.Add(file);
                        }
                        else if (!assetPathToMeta.TryGetValue(file, out var meta))
                        {
                            // File hasent changed but we dont have it in the cache, process it but dont reimport
                            Debug.Log("Asset Found: " + file);
                            ProcessFile(file, out var metaOutdated);
                            if (metaOutdated)
                                toReimport.Add(file);
                        }
                    }
                }
            }

            // Defer the Reimports untill after all Meta files are loaded/updated
            foreach (var file in toReimport)
            {
                Reimport(new(file));
                Debug.Log("Imported: " + $"{ToRelativePath(new(file))}!");
            }

            if (doUnload)
            {
                // Check for missing paths
                var missingPaths = LastWriteTimesCache.Instance.fileLastWriteTimes.Keys.Except(currentFiles).ToList();
                foreach (var file in missingPaths)
                {
                    LastWriteTimesCache.Instance.fileLastWriteTimes.Remove(file);
                    cacheModified = true;
                    bool hasMeta = assetPathToMeta.TryGetValue(file, out var meta);

                    if (hasMeta)
                    {
                        assetPathToMeta.Remove(file);

                        // The asset could have moved, in which case that's all we need todo
                        // But, if the guid leads to a meta file which has THIS asset path, then no new asset exists
                        // As it would have been updated from the code above and ProcessFile()
                        // Which means it didn't just move somewhere else, its gone
                        if (assetGuidToMeta.TryGetValue(meta.guid, out var existingMeta))
                        {
                            if (existingMeta.AssetPath.FullName.Equals(file, StringComparison.OrdinalIgnoreCase))
                            {
                                assetGuidToMeta.Remove(meta.guid);
                                DestroyStoredAsset(meta.guid);

                                Debug.Log("Asset Deleted: " + file);
                                AssetRemoved?.Invoke(meta.guid, new FileInfo(file));
                            }
                        }
                    }

                }
            }

            // If anything changed update DirectoryCaches for Editor UI
            if (forceCacheUpdate || cacheModified || toReimport.Count > 0)
                foreach (var root in rootFolders)
                    root.Item2.Refresh();

            if (cacheModified)
                LastWriteTimesCache.Instance.Save();

        }

        /// <summary>
        /// Process a File Change
        /// </summary>
        /// <param name="file"></param>
        /// <returns>True if a reimport is needed</returns>
        static bool ProcessFile(string file, out bool metaOutdated)
        {
            ArgumentNullException.ThrowIfNullOrEmpty(file);
            var fileInfo = new FileInfo(file);
            metaOutdated = false;

            var meta = MetaFile.Load(fileInfo);
            if (meta != null)
            {
                // Update the cache to record this new Meta file, Guid and Path
                bool hasMetaAlready = assetGuidToMeta.ContainsKey(meta.guid);
                assetGuidToMeta[meta.guid] = meta;
                assetPathToMeta[fileInfo.FullName] = meta;

                if (meta.version != MetaFile.MetaVersion)
                {
                    metaOutdated = true;
                    return true; // Meta version is outdated
                }

                if (hasMetaAlready)
                {
                    if (meta.lastModified != fileInfo.LastWriteTimeUtc)
                    {
                        // File modified, reimport
                        return true;
                    }
                }
                else
                {
                    // New file with meta, import
                    return false;
                }
            }
            else
            {
                // No meta file, create and import
                var newMeta = new MetaFile(fileInfo);
                if (newMeta.importer == null)
                {
                    Debug.LogError($"No importer found for file:\n{fileInfo.FullName}");
                    return false;
                }
                newMeta.Save();

                assetGuidToMeta[newMeta.guid] = newMeta;
                assetPathToMeta[fileInfo.FullName] = newMeta;
                return true;
            }
            return false; // No need to reimport, nothing changed
        }

        /// <summary>
        /// Tries to get the GUID of a file.
        /// </summary>
        /// <param name="file">The file to get the GUID for.</param>
        /// <param name="guid">The GUID of the file.</param>
        /// <returns>True if the GUID was found, false otherwise.</returns>
        public static bool TryGetGuid(FileInfo file, out Guid guid)
        {
            guid = Guid.Empty;
            ArgumentNullException.ThrowIfNull(file);
            if (!File.Exists(file.FullName)) return false;
            if (assetPathToMeta.TryGetValue(file.FullName, out var meta))
            {
                guid = meta.guid;
                return true;
            }
            return false;
        }

        /// <summary>
        /// Tries to get the file with the specified GUID.
        /// </summary>
        /// <param name="guid">The GUID of the file.</param>
        /// <param name="file">The file with the specified GUID.</param>
        /// <returns>True if the file was found, false otherwise.</returns>
        public static bool TryGetFile(Guid guid, out FileInfo? file)
        {
            file = null;
            if (guid == Guid.Empty) throw new ArgumentException("Asset Guid cannot be empty", nameof(guid));
            if (assetGuidToMeta.TryGetValue(guid, out var meta))
            {
                file = new FileInfo(meta.AssetPath.FullName);
                return true;
            }
            return false;
        }

        /// <summary>
        /// Reimports all assets in the AssetDatabase.
        /// </summary>
        public static void ReimportAll()
        {
            foreach (var root in rootFolders)
                ReimportFolder(root.Item1);
        }

        /// <summary>
        /// Reimports all assets in the specified directory.
        /// </summary>
        /// <param name="directory">The directory to reimport assets from.</param>
        public static void ReimportFolder(DirectoryInfo directory)
        {
            ArgumentNullException.ThrowIfNull(directory);
            var files = directory.GetFiles("*", SearchOption.AllDirectories)
                .Where(file => !file.FullName.EndsWith(".meta"));
            foreach (var file in files)
                if (ImporterAttribute.SupportsExtension(file.Extension))
                    Reimport(file);
        }

        /// <summary>
        /// Reimports an asset from the specified file.
        /// </summary>
        /// <param name="assetFile">The asset file to reimport.</param>
        /// <param name="disposeExisting">Whether to dispose the existing asset in memory before reimporting.</param>
        /// <returns>True if the asset was reimported successfully, false otherwise.</returns>
        public static bool Reimport(FileInfo assetFile, bool disposeExisting = true)
        {
            Debug.Log($"Attempting to Import {Path.GetRelativePath(Project.ProjectDirectory, assetFile.FullName)}!");
            ArgumentNullException.ThrowIfNull(assetFile);

            // Dispose if we already have it
            if (disposeExisting)
                if (TryGetGuid(assetFile, out var assetGuid))
                    DestroyStoredAsset(assetGuid);

            // make sure path exists
            if (!File.Exists(assetFile.FullName))
            {
                Debug.LogError($"Failed to import {ToRelativePath(assetFile)}. Asset does not exist.");
                return false;
            }

            var meta = MetaFile.Load(assetFile);
            if (meta == null)
            {
                Debug.LogError($"No valid meta file found for asset: {ToRelativePath(assetFile)}");
                return false;
            }
            if (meta.importer == null)
            {
                Debug.LogError($"No valid importer found for asset: {ToRelativePath(assetFile)}");
                return false;
            }

            // Import the asset
            SerializedAsset ctx = new(meta.guid);
            try
            {
                meta.importer.Import(ctx, assetFile);
            }
            catch (Exception e)
            {
                Debug.LogError($"Failed to import {ToRelativePath(assetFile)}. Reason: {e.Message}");
                return false; // Import failed
            }

            if (!ctx.HasMain)
            {
                Debug.LogError($"Failed to import {ToRelativePath(assetFile)}. No main object found.");
                return false; // Import failed no Main Object
            }

            // Delete the old imported asset if it exists
            var serialized = GetSerializedFile(meta.guid);
            if (File.Exists(serialized.FullName))
                serialized.Delete();

            // Save the asset
            ctx.SaveToFile(serialized, out var dependencies);

            // Update the meta file (LastModified is set by MetaFile.Load)
            meta.assetTypes = new string[ctx.SubAssets.Count + 1];
            meta.assetTypes[0] = ctx.Main.GetType().FullName!;
            for (int i = 0; i < ctx.SubAssets.Count; i++)
                meta.assetTypes[i + 1] = ctx.SubAssets[i].GetType().FullName!;

            meta.assetNames = new string[ctx.SubAssets.Count + 1];
            meta.assetNames[0] = ctx.Main.Name;
            for (int i = 0; i < ctx.SubAssets.Count; i++)
                meta.assetNames[i + 1] = ctx.SubAssets[i].Name;

            meta.dependencies = dependencies.ToList();
            meta.Save();
            return true;
        }

        /// <summary>
        /// Loads an asset of the specified type from the specified file path and file ID.
        /// </summary>
        /// <typeparam name="T">The type of the asset to load.</typeparam>
        /// <param name="assetPath">The file path of the asset to load.</param>
        /// <param name="fileID">The file ID of the asset to load.</param>
        /// <returns>The loaded asset, or null if the asset could not be loaded.</returns>
        public static T? LoadAsset<T>(FileInfo assetPath, ushort fileID) where T : EngineObject
        {
            ArgumentNullException.ThrowIfNull(assetPath);
            if (TryGetGuid(assetPath, out var guid))
                return LoadAsset<T>(guid, fileID);
            return null;
        }

        /// <summary>
        /// Loads an asset of the specified type from the specified GUID and file ID.
        /// </summary>
        /// <typeparam name="T">The type of the asset to load.</typeparam>
        /// <param name="assetGuid">The GUID of the asset to load.</param>
        /// <param name="fileID">The file ID of the asset to load.</param>
        /// <returns>The loaded asset, or null if the asset could not be loaded.</returns>
        public static T? LoadAsset<T>(Guid assetGuid, ushort fileID) where T : EngineObject
        {
            if (assetGuid == Guid.Empty) throw new ArgumentException("Asset Guid cannot be empty", nameof(assetGuid));

            try
            {
                var serialized = LoadAsset(assetGuid);
                if (serialized == null) return null;
                T? asset = null;
                if (fileID == 0)
                {
                    // Main Asset
                    if (serialized.Main is not T) return null;
                    asset = (T)serialized.Main;
                }
                else
                {
                    // Sub Asset
                    if (serialized.SubAssets[fileID - 1] is not T) return null;
                    asset = (T)serialized.SubAssets[fileID - 1];
                }
                asset.AssetID = assetGuid;
                asset.FileID = (ushort)fileID;
                return asset;
            }
            catch (Exception e)
            {
                Console.WriteLine(e.ToString());
                throw new InvalidCastException($"Something went wrong loading asset.");
            }
        }

        /// <summary>
        /// Loads a serialized asset from the specified file path.
        /// </summary>
        /// <param name="assetPath">The file path of the serialized asset to load.</param>
        /// <returns>The loaded serialized asset, or null if the asset could not be loaded.</returns>
        public static SerializedAsset? LoadAsset(FileInfo assetPath)
        {
            ArgumentNullException.ThrowIfNull(assetPath);
            if (TryGetGuid(assetPath, out var guid))
                return LoadAsset(guid);
            return null;
        }

        /// <summary>
        /// Loads a serialized asset from the specified GUID.
        /// </summary>
        /// <param name="assetGuid">The GUID of the serialized asset to load.</param>
        /// <returns>The loaded serialized asset, or null if the asset could not be loaded.</returns>
        public static SerializedAsset? LoadAsset(Guid assetGuid)
        {
            if (assetGuid == Guid.Empty) throw new ArgumentException("Asset Guid cannot be empty", nameof(assetGuid));

            if (guidToAssetData.TryGetValue(assetGuid, out SerializedAsset? value))
                return value;

            if (!TryGetFile(assetGuid, out var asset))
                return null;

            if (!File.Exists(asset!.FullName)) throw new FileNotFoundException("Asset file does not exist.", asset.FullName);

            FileInfo serializedAssetPath = GetSerializedFile(assetGuid);
            if (!File.Exists(serializedAssetPath.FullName))
                if (!Reimport(asset))
                {
                    Debug.LogError($"Failed to import {serializedAssetPath.FullName}!");
                    throw new Exception($"Failed to import {serializedAssetPath.FullName}");
                }
            try
            {
                var serializedAsset = SerializedAsset.FromSerializedAsset(serializedAssetPath.FullName);
                serializedAsset.Guid = assetGuid;
                serializedAsset.Main.AssetID = assetGuid;
                serializedAsset.Main.FileID = 0;
                for (int i = 0; i < serializedAsset.SubAssets.Count; i++)
                {
                    serializedAsset.SubAssets[i].AssetID = assetGuid;
                    serializedAsset.SubAssets[i].FileID = (ushort)(i + 1);
                }
                guidToAssetData[assetGuid] = serializedAsset;
                return serializedAsset;
            }
            catch
            {
                Debug.LogError($"Failed to load serialized asset {serializedAssetPath.FullName}!");
                return null; // Failed file might be in use?
            }
        }

        #endregion

        #region Private Methods

        private static void DestroyStoredAsset(Guid guid)
        {
            if (guidToAssetData.TryGetValue(guid, out SerializedAsset? value))
            {
                value.Destroy();
                guidToAssetData.Remove(guid);
            }
        }

        #endregion
    }
}

Screenshot 2024-06-20 at 9 58 54 PM

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant