You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
using Prowl.Runtime;using Prowl.Runtime.Utils;using Debug = Prowl.Runtime.Debug;namespace Prowl.Editor.Assets
{[FilePath("Library/LastWriteTimes.cache", FilePathAttribute.Location.Data)]publicclassLastWriteTimesCache:ScriptableSingleton<LastWriteTimesCache>{publicreadonlyDictionary<string,DateTime>fileLastWriteTimes=[];}publicstaticpartialclassAssetDatabase{
#region Properties
publicstaticGuidLastLoadedAssetID{get;privateset;}= Guid.Empty;publicstaticstringTempAssetDirectory=> Path.Combine(Project.ProjectDirectory,"Library/AssetDatabase");
#endregion
#region Events
publicstaticeventAction<Guid,FileInfo>?AssetRemoved;publicstaticeventAction<FileInfo>?Pinged;
#endregion
#region Private Fields
staticreadonlyList<(DirectoryInfo, AssetDirectoryCache)>rootFolders=[];staticdoubleRefreshTimer=0f;staticboollastFocused=false;staticreadonlyDictionary<string,MetaFile>assetPathToMeta=new(StringComparer.OrdinalIgnoreCase);staticreadonlyDictionary<Guid,MetaFile>assetGuidToMeta=[];staticreadonlyDictionary<Guid,SerializedAsset>guidToAssetData=[];
#endregion
#region Public Methods
internalstaticvoidInternalUpdate(){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>publicstatic AssetDirectoryCache GetRootFolderCache(intindex){if(index<0||index >= rootFolders.Count)thrownew 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>publicstaticList<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>publicstaticvoidAddRootFolder(stringrootFolder){
ArgumentNullException.ThrowIfNullOrEmpty(rootFolder);varrootPath= Path.Combine(Project.ProjectDirectory, rootFolder);varinfo=new DirectoryInfo(rootPath);if(!info.Exists)
info.Create();if(rootFolders.Any(x => x.Item1.FullName.Equals(info.FullName, StringComparison.OrdinalIgnoreCase)))thrownew 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>publicstaticvoidUpdate(booldoUnload=true,boolforceCacheUpdate=false){if(!Project.HasProject)return;RefreshTimer=0f;HashSet<string>currentFiles=[];List<string>toReimport=[];boolcacheModified=false;foreach(var root in rootFolders){varfiles= 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 ignoredif(ImporterAttribute.SupportsExtension(Path.GetExtension(file))){
currentFiles.Add(file);if(!LastWriteTimesCache.Instance.fileLastWriteTimes.TryGetValue(file,outvar 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);}elseif(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);}elseif(!assetPathToMeta.TryGetValue(file,outvar meta)){// File hasent changed but we dont have it in the cache, process it but dont reimport
Debug.Log("Asset Found: "+file);
ProcessFile(file,outvar metaOutdated);if(metaOutdated)
toReimport.Add(file);}}}}// Defer the Reimports untill after all Meta files are loaded/updatedforeach(var file in toReimport){
Reimport(new(file));
Debug.Log("Imported: "+$"{ToRelativePath(new(file))}!");}if(doUnload){// Check for missing pathsvarmissingPaths= LastWriteTimesCache.Instance.fileLastWriteTimes.Keys.Except(currentFiles).ToList();foreach(var file in missingPaths){
LastWriteTimesCache.Instance.fileLastWriteTimes.Remove(file);cacheModified=true;boolhasMeta= assetPathToMeta.TryGetValue(file,outvar 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 goneif(assetGuidToMeta.TryGetValue(meta.guid,outvar 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 UIif(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>staticboolProcessFile(stringfile,outboolmetaOutdated){
ArgumentNullException.ThrowIfNullOrEmpty(file);varfileInfo=new FileInfo(file);metaOutdated=false;varmeta= MetaFile.Load(fileInfo);if(meta!=null){// Update the cache to record this new Meta file, Guid and PathboolhasMetaAlready= assetGuidToMeta.ContainsKey(meta.guid);
assetGuidToMeta[meta.guid]=meta;
assetPathToMeta[fileInfo.FullName]=meta;if(meta.version != MetaFile.MetaVersion){metaOutdated=true;returntrue;// Meta version is outdated}if(hasMetaAlready){if(meta.lastModified != fileInfo.LastWriteTimeUtc){// File modified, reimportreturntrue;}}else{// New file with meta, importreturnfalse;}}else{// No meta file, create and importvarnewMeta=new MetaFile(fileInfo);if(newMeta.importer ==null){
Debug.LogError($"No importer found for file:\n{fileInfo.FullName}");returnfalse;}
newMeta.Save();
assetGuidToMeta[newMeta.guid]=newMeta;
assetPathToMeta[fileInfo.FullName]=newMeta;returntrue;}returnfalse;// 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>publicstaticboolTryGetGuid(FileInfofile,outGuidguid){guid= Guid.Empty;
ArgumentNullException.ThrowIfNull(file);if(!File.Exists(file.FullName))returnfalse;if(assetPathToMeta.TryGetValue(file.FullName,outvar meta)){guid= meta.guid;returntrue;}returnfalse;}/// <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>publicstaticboolTryGetFile(Guidguid,outFileInfo?file){file=null;if(guid== Guid.Empty)thrownew ArgumentException("Asset Guid cannot be empty", nameof(guid));if(assetGuidToMeta.TryGetValue(guid,outvar meta)){file=new FileInfo(meta.AssetPath.FullName);returntrue;}returnfalse;}/// <summary>/// Reimports all assets in the AssetDatabase./// </summary>publicstaticvoidReimportAll(){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>publicstaticvoidReimportFolder(DirectoryInfodirectory){
ArgumentNullException.ThrowIfNull(directory);varfiles= 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>publicstaticboolReimport(FileInfoassetFile,booldisposeExisting=true){
Debug.Log($"Attempting to Import {Path.GetRelativePath(Project.ProjectDirectory, assetFile.FullName)}!");
ArgumentNullException.ThrowIfNull(assetFile);// Dispose if we already have itif(disposeExisting)if(TryGetGuid(assetFile,outvar assetGuid))
DestroyStoredAsset(assetGuid);// make sure path existsif(!File.Exists(assetFile.FullName)){
Debug.LogError($"Failed to import {ToRelativePath(assetFile)}. Asset does not exist.");returnfalse;}varmeta= MetaFile.Load(assetFile);if(meta==null){
Debug.LogError($"No valid meta file found for asset: {ToRelativePath(assetFile)}");returnfalse;}if(meta.importer ==null){
Debug.LogError($"No valid importer found for asset: {ToRelativePath(assetFile)}");returnfalse;}// Import the assetSerializedAssetctx=new(meta.guid);try{
meta.importer.Import(ctx, assetFile);}catch(Exceptione){
Debug.LogError($"Failed to import {ToRelativePath(assetFile)}. Reason: {e.Message}");returnfalse;// Import failed}if(!ctx.HasMain){
Debug.LogError($"Failed to import {ToRelativePath(assetFile)}. No main object found.");returnfalse;// Import failed no Main Object}// Delete the old imported asset if it existsvarserialized= GetSerializedFile(meta.guid);if(File.Exists(serialized.FullName))
serialized.Delete();// Save the asset
ctx.SaveToFile(serialized,outvar dependencies);// Update the meta file (LastModified is set by MetaFile.Load)
meta.assetTypes =newstring[ctx.SubAssets.Count +1];
meta.assetTypes[0]= ctx.Main.GetType().FullName!;for(inti=0;i< ctx.SubAssets.Count;i++)
meta.assetTypes[i+1]= ctx.SubAssets[i].GetType().FullName!;
meta.assetNames =newstring[ctx.SubAssets.Count +1];
meta.assetNames[0]= ctx.Main.Name;for(inti=0;i< ctx.SubAssets.Count;i++)
meta.assetNames[i+1]= ctx.SubAssets[i].Name;
meta.dependencies = dependencies.ToList();
meta.Save();returntrue;}/// <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>publicstaticT?LoadAsset<T>(FileInfoassetPath,ushortfileID)whereT:EngineObject{
ArgumentNullException.ThrowIfNull(assetPath);if(TryGetGuid(assetPath,outvar guid))returnLoadAsset<T>(guid, fileID);returnnull;}/// <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>publicstaticT?LoadAsset<T>(GuidassetGuid,ushortfileID)whereT:EngineObject{if(assetGuid== Guid.Empty)thrownew ArgumentException("Asset Guid cannot be empty", nameof(assetGuid));try{varserialized= LoadAsset(assetGuid);if(serialized==null)returnnull;T?asset=null;if(fileID==0){// Main Assetif(serialized.Main is not T)returnnull;asset=(T)serialized.Main;}else{// Sub Assetif(serialized.SubAssets[fileID-1]is not T)returnnull;asset=(T)serialized.SubAssets[fileID-1];}
asset.AssetID =assetGuid;
asset.FileID =(ushort)fileID;returnasset;}catch(Exceptione){
Console.WriteLine(e.ToString());thrownew 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>publicstaticSerializedAsset?LoadAsset(FileInfoassetPath){
ArgumentNullException.ThrowIfNull(assetPath);if(TryGetGuid(assetPath,outvar guid))return LoadAsset(guid);returnnull;}/// <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>publicstaticSerializedAsset?LoadAsset(GuidassetGuid){if(assetGuid== Guid.Empty)thrownew ArgumentException("Asset Guid cannot be empty", nameof(assetGuid));if(guidToAssetData.TryGetValue(assetGuid,outSerializedAsset? value))returnvalue;if(!TryGetFile(assetGuid,outvar asset))returnnull;if(!File.Exists(asset!.FullName))thrownew FileNotFoundException("Asset file does not exist.", asset.FullName);FileInfoserializedAssetPath= GetSerializedFile(assetGuid);if(!File.Exists(serializedAssetPath.FullName))if(!Reimport(asset)){
Debug.LogError($"Failed to import {serializedAssetPath.FullName}!");thrownew Exception($"Failed to import {serializedAssetPath.FullName}");}try{varserializedAsset= SerializedAsset.FromSerializedAsset(serializedAssetPath.FullName);
serializedAsset.Guid =assetGuid;
serializedAsset.Main.AssetID =assetGuid;
serializedAsset.Main.FileID =0;for(inti=0;i< serializedAsset.SubAssets.Count;i++){
serializedAsset.SubAssets[i].AssetID =assetGuid;
serializedAsset.SubAssets[i].FileID =(ushort)(i+1);}
guidToAssetData[assetGuid]=serializedAsset;returnserializedAsset;}catch{
Debug.LogError($"Failed to load serialized asset {serializedAssetPath.FullName}!");returnnull;// Failed file might be in use?}}
#endregion
#region Private Methods
privatestaticvoidDestroyStoredAsset(Guidguid){if(guidToAssetData.TryGetValue(guid,outSerializedAsset? value)){
value.Destroy();
guidToAssetData.Remove(guid);}}
#endregion
}}
The text was updated successfully, but these errors were encountered:
https://github.com/michaelsakharov/Prowl/tree/NewUI-PersistentLayout
The text was updated successfully, but these errors were encountered: