using System; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq; using UnityEngine.AddressableAssets; using UnityEngine; using UnityEngine.AddressableAssets.ResourceLocators; using Cysharp.Threading.Tasks; using SLZ.Marrow.Forklift.Model; using SLZ.Marrow.Utilities; using UnityEngine.LowLevel; using Debug = UnityEngine.Debug; using Object = UnityEngine.Object; #if UNITY_EDITOR using SLZ.MarrowEditor; using UnityEditor; #endif namespace SLZ.Marrow.Warehouse { public class AssetWarehouse { public static AssetWarehouse Instance; public static bool ready = false; private static Action _onReady; private bool _initializing = false; public bool Initializing { get => _initializing; private set => _initializing = value; } private bool initialized = false; private readonly Dictionary palletManifests = new Dictionary(); private readonly Dictionary modioPalletManifestsLookup = new Dictionary(); private readonly List loadedCatalogs = new List(); private HashSet gamePallets = new HashSet(); private readonly Dictionary marrowGames = new Dictionary(); private readonly Dictionary marrowGameLocations = new Dictionary(); private readonly Dictionary palletToMarrowGameLookup = new Dictionary(); private readonly Dictionary _inventoryRegistry = new Dictionary(); private readonly Dictionary _oldBarcodeInventoryRegistry = new Dictionary(); private readonly Dictionary _palletRegistry = new Dictionary(); private readonly Dictionary _crateRegistry = new Dictionary(); private readonly Dictionary _dataCardRegistry = new Dictionary(); public Dictionary InventoryRegistry { get { return _inventoryRegistry; } } [ReadOnly] [SerializeField] private List _allTags = new List(); public List AllTags { get { return _allTags; } private set { _allTags = value; } } [SerializeField] [ReadOnly] private bool _initialLoaded = false; public bool InitialLoaded { get { return _initialLoaded; } private set { _initialLoaded = value; } } #if UNITY_EDITOR private Dictionary _editorObjectCrateLookup = new Dictionary(); public Dictionary EditorObjectCrateLookup { get => _editorObjectCrateLookup; private set => _editorObjectCrateLookup = value; } private Dictionary _editorObjectGuidCrateLookup = new Dictionary(); public Dictionary EditorObjectGuidCrateLookup { get => _editorObjectGuidCrateLookup; private set => _editorObjectGuidCrateLookup = value; } private readonly Dictionary _workingPallets = new Dictionary(); public Dictionary WorkingPallets { get { return _workingPallets; } } private string _editorGameInstallDirectory = ""; public string EditorGameInstallDirectory { get { if (string.IsNullOrEmpty(_editorGameInstallDirectory)) { _editorGameInstallDirectory = EditorPrefs.GetString("GameInstallDirectory", ""); } return _editorGameInstallDirectory; } set => _editorGameInstallDirectory = value; } #endif private Stopwatch palletPackStopWatch = new Stopwatch(); private Stopwatch palletManifestPackStopWatch = new Stopwatch(); public static void OnReady(Action callbackWhenReady) { if (ready) { callbackWhenReady?.Invoke(); return; } _onReady += callbackWhenReady; } public async UniTask InitAsync() { if (!initialized) { LogVerbose($"Init {UnityEngine.Random.Range(1, 100)}"); Instance = this; Initializing = true; AssetWarehouseMetrics.Reset(); palletPackStopWatch.Restart(); palletManifestPackStopWatch.Restart(); var initStopWatch = new Stopwatch(); initStopWatch.Start(); #if UNITY_EDITOR if (EditorObjectCrateLookup == null) EditorObjectCrateLookup = new Dictionary(); else EditorObjectCrateLookup.Clear(); if (EditorObjectGuidCrateLookup == null) EditorObjectGuidCrateLookup = new Dictionary(); else EditorObjectGuidCrateLookup.Clear(); AssemblyReloadEvents.beforeAssemblyReload += OnBeforeDomainReload; SDKProjectPreferences.LoadFromFile(); if (!Application.isBatchMode && Directory.Exists("Assets/AddressableAssetsData") && SDKProjectPreferences.LoadMarrowGames) { LogVerbose($"Initialize Addressables"); var settingsJsonPath = $"{Addressables.BuildPath}/settings.json"; if (!string.IsNullOrEmpty(settingsJsonPath) && settingsJsonPath.Contains("com.unity.addressables") && File.Exists(settingsJsonPath)) { File.Delete(settingsJsonPath); LogVerbose("Delete Addressables settings.json"); } await Addressables.InitializeAsync(); LogVerbose($"Addressables Initialized"); } #endif await LoadInitialPalletsAsync(); LogVerbose($"Time spent on Pallet Unpacking: {palletPackStopWatch.Elapsed:h\\:mm\\:ss\\.fff}"); initStopWatch.Stop(); LogVerbose($"Finish init, took {initStopWatch.Elapsed:h\\:mm\\:ss\\.fff}"); ready = true; _onReady?.Invoke(); _onReady = null; initialized = true; Initializing = false; } } private void CleanupPalletManifests() { Stopwatch stopwatch = new Stopwatch(); stopwatch.Start(); string[] palletManifestJsonPaths = Directory.GetFiles(MarrowSDK.RuntimeModsPath, $"*.{PalletManifest.EXTENSION_NAME}", SearchOption.AllDirectories); foreach (var palletManifestJsonPath in palletManifestJsonPaths) { PalletPacker.TryUnpackManifestJsonFromFile(palletManifestJsonPath, out var palletManifest, out _); if (palletManifest == null || !palletManifests.ContainsKey(palletManifest.PalletBarcode)) { LogVerbose($"AssetWarehouse: PURGE MANIFEST: {palletManifest.PalletBarcode} ({palletManifestJsonPath})"); File.Delete(palletManifestJsonPath); } } LogVerbose($"AssetWarehouse: Finish CleanupPalletManifests{palletManifestJsonPaths.Count()}, took {stopwatch.Elapsed:h\\:mm\\:ss}"); } private void Init() { InitAsync().Forget(); } public AssetWarehouse(bool autoInit = true) { if (autoInit) Init(); } ~AssetWarehouse() { } public void Clear() { LogVerbose("Clear Started"); UnloadAllPallets(); _inventoryRegistry.Clear(); _oldBarcodeInventoryRegistry.Clear(); _palletRegistry.Clear(); _crateRegistry.Clear(); _dataCardRegistry.Clear(); palletManifests.Clear(); modioPalletManifestsLookup.Clear(); gamePallets.Clear(); marrowGames.Clear(); palletToMarrowGameLookup.Clear(); marrowGameLocations.Clear(); InitialLoaded = false; ready = false; initialized = false; Initializing = false; _onReady = null; OnChanged = null; OnPalletAdded = null; OnCrateAdded = null; OnDataCardAdded = null; AssetWarehouseMetrics.Reset(); #if UNITY_EDITOR EditorObjectCrateLookup.Clear(); EditorObjectGuidCrateLookup.Clear(); WorkingPallets.Clear(); OnClearEditor?.Invoke(); #endif OnChanged?.Invoke(); LogVerbose("Clear Finished"); } #if UNITY_EDITOR [InitializeOnLoadMethod] private static void EditorInitialize() { var loop = PlayerLoop.GetCurrentPlayerLoop(); Cysharp.Threading.Tasks.PlayerLoopHelper.Initialize(ref loop); LogVerbose("InitializeOnLoadMethod"); LogVerbose("EditorInitialize" + (EditorApplication.isPlayingOrWillChangePlaymode ? " isPlayingOrWillChangePlaymode" : "") + (Application.isPlaying ? " isPlaying" : "")); if (Application.isPlaying || EditorApplication.isPlayingOrWillChangePlaymode) { LogVerbose("EditorInitialize: playing"); } else { LogVerbose("EditorInitialize: not playing"); if (AssetWarehouse.Instance == null && !AssetWarehouse.ready) { WarehousePathResolver.Setup(); var ass = new AssetWarehouse(false); ass.InitAsyncLoadOnProjectOpen().Forget(); } } } private async UniTask InitAsyncLoadOnProjectOpen() { QueueEditorUpdateLoop.StartEditorUpdateLoop(); await UniTask.WaitUntil(() => !EditorApplication.isUpdating); await InitAsync(); QueueEditorUpdateLoop.StopEditorUpdateLoop(); } #endif private async UniTask LoadInitialPalletsAsync() { LogVerbose("Load Initial Pallets", true); #if UNITY_EDITOR await LoadInitialPalletsAsync_Editor(); #else #endif InitialLoaded = true; LogVerbose("Initial Pallets loaded", true); } private async UniTask LoadInitialPalletsAsync_Editor() { #if UNITY_EDITOR var stopwatch = new Stopwatch(); stopwatch.Start(); if (EditorApplication.isPlayingOrWillChangePlaymode) { #if false #endif } else { SDKProjectPreferences.LoadFromFile(); if (!Application.isBatchMode && Directory.Exists("Assets/AddressableAssetsData") && SDKProjectPreferences.LoadMarrowGames) await EditorLoadBuiltMarrowGames(); await LoadPalletsFromAssetDatabase(); if (!Application.isBatchMode && Directory.Exists("Assets/AddressableAssetsData")) await LoadPalletEditorDependencies(); } #endif } public Action OnChanged; public Action OnPalletAdded; public Action OnCrateAdded; public Action OnDataCardAdded; #if UNITY_EDITOR public Action OnClearEditor; #endif public bool TryGetPallet(Barcode barcode, out Pallet pallet) { if (TryGetScannable(barcode, out Pallet foundPallet)) { pallet = foundPallet; return true; } pallet = null; return false; } public bool TryGetCrate(Barcode barcode, out Crate crate) { if (TryGetCrate(barcode, out Crate foundCrate)) { crate = foundCrate; return true; } crate = null; return false; } public bool TryGetCrate(Barcode barcode, out T crateT) where T : Crate { if (TryGetScannable(barcode, out T foundCrate)) { crateT = foundCrate; return true; } crateT = null; return false; } public bool TryGetDataCard(Barcode barcode, out DataCard dataCard) { if (TryGetScannable(barcode, out DataCard foundDataCard)) { dataCard = foundDataCard; return true; } dataCard = null; return false; } public bool TryGetDataCard(Barcode barcode, out T dataCardT) where T : DataCard { if (TryGetScannable(barcode, out T foundDataCard)) { dataCardT = foundDataCard; return true; } dataCardT = null; return false; } public bool TryGetScannable(Barcode barcode, out Scannable scannable) { if (TryGetScannable(barcode, out Scannable item)) { scannable = item; return true; } scannable = null; return false; } public bool TryGetScannable(Barcode barcode, out T scannableT) where T : Scannable { scannableT = null; bool found = false; if (Barcode.IsValid(barcode)) { found = _inventoryRegistry.TryGetValue(barcode, out Scannable scannable) && scannable != null && scannable is T; if (!found) { found = _oldBarcodeInventoryRegistry.TryGetValue(barcode, out scannable) && scannable != null && scannable is T; } if (found) { scannableT = (T)scannable; } } return found; } public bool HasScannable(Barcode barcode) where T : Scannable { return TryGetScannable(barcode, out _); } public bool HasScannable(Barcode barcode) { return HasScannable(barcode); } public bool HasCrate(Barcode barcode) where T : Crate { return HasScannable(barcode); } public bool HasCrate(Barcode barcode) { return HasCrate(barcode); } public bool HasDataCard(Barcode barcode) where T : DataCard { return HasScannable(barcode); } public bool HasDataCard(Barcode barcode) { return HasDataCard(barcode); } public bool HasPallet(Barcode barcode) { return HasScannable(barcode); } public bool UnloadCrateAsset(Barcode barcode, bool forcedUnload = false) { bool unloaded = false; if (TryGetCrate(barcode, out var crate)) { unloaded = UnloadCrateAsset(crate); } return unloaded; } public bool UnloadCrateAsset(Crate crate, bool forcedUnload = false) { foreach (var packedAsset in crate.ExportPackedAssets()) { if (packedAsset.marrowAsset != null) { packedAsset.marrowAsset.UnloadAsset(true); } if (packedAsset.subAssets == null) { } else { foreach (var packedSubAsset in packedAsset.subAssets) { packedSubAsset.subAsset.UnloadAsset(true); } } } return crate.MainAsset.UnloadAsset(forcedUnload); } public bool UnloadDataCardAsset(DataCard dataCard, bool forcedUnload = false) { foreach (var packedAsset in dataCard.ExportPackedAssets()) { if (packedAsset.marrowAsset != null) { packedAsset.marrowAsset.UnloadAsset(true); } if (packedAsset.subAssets == null) { } else { foreach (var packedSubAsset in packedAsset.subAssets) { packedSubAsset.subAsset.UnloadAsset(true); } } } if (dataCard.IsBundledDataCard()) return dataCard.DataCardAsset.UnloadAsset(forcedUnload); else return false; } public int UnloadAllCrateAssets() { int unloadCount = 0; var crates = GetCrates(); foreach (var crate in crates) { if (UnloadCrateAsset(crate, true)) unloadCount++; } return unloadCount; } public int UnloadAllCrateAssets(Barcode excludeBarcode) { int unloadCount = 0; var crates = GetCrates(); foreach (var crate in crates) { if (!Barcode.IsValid(excludeBarcode) || crate.Barcode != excludeBarcode) { if (UnloadCrateAsset(crate, true)) unloadCount++; } } return unloadCount; } public int UnloadAllCrateAssets(params Barcode[] excludeBarcodes) { int unloadCount = 0; var crates = GetCrates(); foreach (var crate in crates) { bool excluded = false; foreach (var excludeBarcode in excludeBarcodes) { if (!Barcode.IsValid(excludeBarcode) || crate.Barcode != excludeBarcode) { excluded = true; break; } } if (!excluded) { if (UnloadCrateAsset(crate, true)) unloadCount++; } } return unloadCount; } public int UnloadAllDataCardAssets() { int unloadCount = 0; var dataCards = GetDataCards(); foreach (var dataCard in dataCards) { if (UnloadDataCardAsset(dataCard, true)) unloadCount++; } return unloadCount; } public int UnloadAllDataCardAssets(Barcode excludeBarcode) { int unloadCount = 0; var dataCards = GetDataCards(); foreach (var dataCard in dataCards) { if (!Barcode.IsValid(excludeBarcode) || dataCard.Barcode != excludeBarcode) { if (UnloadDataCardAsset(dataCard, true)) unloadCount++; } } return unloadCount; } public int UnloadAllDataCardAssets(Barcode[] excludeBarcodes) { int unloadCount = 0; var dataCards = GetDataCards(); foreach (var dataCard in dataCards) { bool excluded = false; foreach (var excludeBarcode in excludeBarcodes) { if (!Barcode.IsValid(excludeBarcode) || dataCard.Barcode != excludeBarcode) { excluded = true; break; } } if (!excluded) { if (UnloadDataCardAsset(dataCard, true)) unloadCount++; } } return unloadCount; } public void UnloadCrate(Barcode barcode) { if (TryGetCrate(barcode, out var crate)) { UnloadCrate(crate); } } public void UnloadCrate(Crate crate) { UnloadCrateAsset(crate, true); InventoryRegistry.Remove(crate.Barcode); _crateRegistry.Remove(crate.Barcode); AssetWarehouseMetrics.LoadedScannableCount.Value--; AssetWarehouseMetrics.LoadedCrateCount.Value--; #if UNITY_EDITOR Object removeItem = null; foreach (var objectCrateKVP in EditorObjectCrateLookup) { if (objectCrateKVP.Value == crate) { removeItem = objectCrateKVP.Key; } } if (removeItem != null) EditorObjectCrateLookup.Remove(removeItem); string removeItem2 = null; foreach (var objectCrateKVP in EditorObjectGuidCrateLookup) { if (objectCrateKVP.Value == crate) { removeItem2 = objectCrateKVP.Key; } } if (removeItem2 != null) EditorObjectGuidCrateLookup.Remove(removeItem2); #endif DestroyRuntimeCreatedScannable(crate); } public void UnloadDataCard(Barcode barcode) { if (TryGetDataCard(barcode, out var dataCard)) { UnloadDataCard(dataCard); } } public void UnloadDataCard(DataCard dataCard) { UnloadDataCardAsset(dataCard, true); InventoryRegistry.Remove(dataCard.Barcode); _dataCardRegistry.Remove(dataCard.Barcode); AssetWarehouseMetrics.LoadedScannableCount.Value--; AssetWarehouseMetrics.LoadedDataCardCount.Value--; DestroyRuntimeCreatedScannable(dataCard); } private void DestroyRuntimeCreatedScannable(Scannable scannable) { #if UNITY_EDITOR if (string.IsNullOrEmpty(AssetDatabase.GetAssetPath(scannable))) { Object.DestroyImmediate(scannable); } #else #endif } #if UNITY_EDITOR private struct EditorCatalogEntry { public string guid; public string address; public Object obj; public Type assetType; public string assetPath; public EditorCatalogEntry(string guid, string address, Object obj, Type assetType) { this.guid = guid; this.address = address; this.obj = obj; this.assetType = assetType; assetPath = AssetDatabase.GUIDToAssetPath(this.guid); if (this.obj == null) { this.obj = AssetDatabase.LoadAssetAtPath(assetPath, this.assetType); } } } private async UniTask LoadEditorPallet(Pallet pallet, bool addPallet = true, bool loadPalletJson = true) { Pallet loadedPallet = pallet; if (Application.isPlaying && loadPalletJson) { #if false #endif } else { LoadAndUpdatePalletManifest(pallet, null, string.Empty, string.Empty); if (addPallet) AddPallet(pallet); } return loadedPallet; } #endif private void AddPallet(Pallet pallet) { if (pallet == null) { Debug.LogWarning("AssetWarehouse: Cannot add null pallet"); return; } AddScannable(pallet); _palletRegistry[pallet.Barcode] = pallet; foreach (var crate in pallet.Crates) { AddCrate(crate); if (crate != null && crate.Pallet == null) crate.Pallet = pallet; } foreach (var dataCard in pallet.DataCards) { AddDataCard(dataCard); if (dataCard != null && dataCard.Pallet == null) dataCard.Pallet = pallet; } OnChanged?.Invoke(); OnPalletAdded?.Invoke(pallet.Barcode); } private void AddCrate(Crate crate) { if (crate == null) { Debug.LogWarning("AssetWarehouse: Cannot add null crate"); return; } AddScannable(crate); AddTags(crate.Tags); _crateRegistry[crate.Barcode] = crate; #if UNITY_EDITOR if (crate.MainAsset.EditorAsset != null) { EditorObjectCrateLookup[crate.MainAsset.EditorAsset] = crate; } EditorObjectGuidCrateLookup[crate.MainAsset.AssetGUID] = crate; #endif OnCrateAdded?.Invoke(crate.Barcode); } private void AddDataCard(DataCard dataCard) { if (dataCard == null) { Debug.LogWarning("AssetWarehouse: Cannot add null datacard"); return; } AddScannable(dataCard); _dataCardRegistry[dataCard.Barcode] = dataCard; OnDataCardAdded?.Invoke(dataCard.Barcode); } private bool AddScannable(Scannable item) { bool added = false; if (Barcode.IsValid(item.Barcode)) { _inventoryRegistry[item.Barcode] = item; added = true; } if (!added) { Debug.LogError("AssetWarehouse: Cannot add item " + item.Title + ", invalid barcode"); } else { AssetWarehouseMetrics.LoadedScannableCount.Value++; if (item is Pallet) AssetWarehouseMetrics.LoadedPalletCount.Value++; if (item is Crate) AssetWarehouseMetrics.LoadedCrateCount.Value++; if (item is DataCard) AssetWarehouseMetrics.LoadedDataCardCount.Value++; } return added; } public void ReloadPallet(Barcode barcode) { if (TryGetPallet(barcode, out var pallet) && palletManifests.TryGetValue((Barcode)barcode, out var palletManifest)) { string palletPath = Path.GetDirectoryName(palletManifest.CatalogPath); UnloadPallet(pallet); LoadPalletFromFolderAsync(palletPath, true).Forget(); } } public void UnloadPallet(Barcode barcode) { if (TryGetPallet(barcode, out var pallet)) { UnloadPallet(pallet); } } public void UnloadPallet(Pallet pallet) { LogVerbose($"UnloadPallet({pallet.Title})"); foreach (var crate in pallet.Crates) { UnloadCrate(crate); } foreach (var dataCard in pallet.DataCards) { UnloadDataCard(dataCard); } InventoryRegistry.Remove(pallet.Barcode); _palletRegistry.Remove(pallet.Barcode); AssetWarehouseMetrics.LoadedScannableCount.Value--; AssetWarehouseMetrics.LoadedPalletCount.Value--; if (palletManifests.TryGetValue(pallet.Barcode, out var palletManifest)) { palletManifests.Remove(pallet.Barcode); if (loadedCatalogs.Contains(palletManifest.CatalogPath)) { loadedCatalogs.Remove(palletManifest.CatalogPath); Addressables.RemoveResourceLocator(palletManifest.Catalog); } } DestroyRuntimeCreatedScannable(pallet); } public void UnloadAllPallets() { foreach (var pallet in GetPallets()) { if (pallet != null) { UnloadPallet(pallet); } } } public void DeletePallet(Barcode barcode) { if (TryGetPallet(barcode, out var pallet)) { DeletePallet(pallet); } } public void DeletePallet(Pallet pallet) { PalletManifest palletManifest; palletManifests.TryGetValue(pallet.Barcode, out palletManifest); UnloadPallet(pallet); if (!string.IsNullOrEmpty(palletManifest?.PalletPath) && PathContainsPath(MarrowSDK.RuntimeModsPath, palletManifest?.PalletPath) && palletManifest.PalletPath.Contains(MarrowSDK.RUNTIME_MODS_DIRECTORY_NAME)) { string palletDirectory = Path.GetDirectoryName(palletManifest.PalletPath); Debug.Log("AssetWarehouse: Deleting entire mod directory at \"" + palletDirectory + "\""); Directory.Delete(palletDirectory, true); File.Delete(palletManifest.ManifestPath); } } private static bool PathContainsPath(string path, string subPath) { string relativePath = Path.GetRelativePath(path.Replace('\\', '/'), subPath.Replace('\\', '/')); return relativePath != "." && relativePath != ".." && !relativePath.StartsWith("../") && !Path.IsPathRooted(relativePath); } private void AddTags(List newTags) { bool added = false; foreach (var tag in newTags) { if (!AllTags.Contains(tag)) { AllTags.Add(tag); added = true; } } if (added) { AllTags.Sort(); } } public int PalletCount() { return _palletRegistry.Count; } public List GetPallets() { List pallets = new List(); foreach (var palletManifest in palletManifests.Values) { pallets.Add(palletManifest.Pallet); } return pallets; } public void GetPallets(ref List pallets) { if (pallets == null) pallets = new List(); else pallets.Clear(); foreach (var palletManifest in palletManifests.Values) { pallets.Add(palletManifest.Pallet); } } public int CrateCount() { return _crateRegistry.Count; } public List GetCrates() { List crates = new List(); foreach (var scannable in _inventoryRegistry.Values) { if (scannable is Crate crate && !crates.Contains(scannable)) { crates.Add(crate); } } return crates; } public void GetCrates(in List crates) { if (crates.Count > 0) crates.Clear(); foreach (var scannable in _inventoryRegistry.Values) { if (scannable is Crate crate && !crates.Contains(scannable)) { crates.Add(crate); } } } public int DataCardCount() { return _dataCardRegistry.Count; } public List GetDataCards() { List dataCards = new List(); foreach (var scannable in _inventoryRegistry.Values) { if (scannable is DataCard crate && !dataCards.Contains(scannable)) { dataCards.Add(crate); } } return dataCards; } public void GetDataCards(in List dataCards) { if (dataCards.Count > 0) dataCards.Clear(); foreach (var scannable in _inventoryRegistry.Values) { if (scannable is DataCard dataCard && !dataCards.Contains(scannable)) { dataCards.Add(dataCard); } } } public class HideLevelCrateFilter : ICrateFilter { public bool Filter(LevelCrate crate) { return !crate.Redacted; } } public List FilterCrates(ref List crateList, ICrateFilter crateFilter) where T : Crate { for (int i = crateList.Count - 1; i >= 0; i--) { if (crateFilter == null || crateFilter.Filter(crateList[i])) { crateList.RemoveAt(i); } } return crateList; } public List GetCrates(ICrateFilter crateFilter = null) where T : Crate { List crates = new List(); foreach (var scannable in _inventoryRegistry.Values) { if (scannable is T crate && (crateFilter == null || crateFilter.Filter(crate))) { crates.Add(crate); } } return crates; } public void TestQueries() { var pallets = GetPallets(); var allCrates = GetCrates(); var allSpawnableCrates = GetCrates(); var allSceneCrates = GetCrates(); var testFilterCrates = GetCrates(new HideLevelCrateFilter()); var testFilterCrates2 = AssetWarehouse.Instance.GetCrates().Filter(new HideLevelCrateFilter()); } public static void LogVerbose(string text, bool logInRuntime = false) { bool logIt = false; #if UNITY_EDITOR if (EditorPrefs.GetBool("VerboseWarehouseLogging", false)) { logIt = true; } #else #endif if (logIt) { Debug.Log("AssetWarehouse: " + text); } } #if UNITY_EDITOR private bool loadingFromAssetDatabase = false; public async UniTask LoadPalletsFromAssetDatabase(bool clear = false) { if (!loadingFromAssetDatabase) { loadingFromAssetDatabase = true; LogVerbose("LoadPalletsFromAssetDatabase"); if (clear) { _inventoryRegistry.Clear(); _palletRegistry.Clear(); palletManifests.Clear(); } List loadedPallets = new List(); var foundAssets = AssetDatabase.FindAssets("t:Pallet"); foreach (var guid in foundAssets) { string path = AssetDatabase.GUIDToAssetPath(guid); Pallet pallet = AssetDatabase.LoadAssetAtPath(path); string fullPalletPath = Path.GetFullPath(path); bool validSDKPath = fullPalletPath.Contains(MarrowSDK.PackagePath) || fullPalletPath.Contains(Path.Combine("PackageCache", MarrowSDK.PACKAGE_NAME)); bool validPackagePath = path.StartsWith("Packages/"); bool validPathDirectory = fullPalletPath.Contains(Path.GetFullPath(MarrowSDK.GetMarrowAssetsPath("_Pallets"))); if (!HasPallet(pallet.Barcode) && (validSDKPath || validPackagePath || validPathDirectory)) { var loadedPallet = await LoadEditorPallet(pallet, addPallet: false); if (loadedPallet != null) { loadedPallets.Add(loadedPallet); if (validPathDirectory) { WorkingPallets.TryAdd(pallet.Barcode, pallet); } LogVerbose($"Loaded Pallet from AssetDatabase: {loadedPallet.Barcode}"); } } } foreach (var loadedPallet in loadedPallets) { if (loadedPallet != null) { await LoadPalletDataCards(loadedPallet, false); AddPallet(loadedPallet); } } loadingFromAssetDatabase = false; LogVerbose("LoadPalletsFromAssetDatabase: Done"); } else { LogVerbose("LoadPalletsFromAssetDatabase: Skipping, already loading"); } } private async UniTask LoadPalletEditorDependencies() { LogVerbose("LoadPalletEditorDependencies"); string[] palletManifestJsonPaths = Directory.GetFiles(MarrowSDK.RuntimeModsPath, $"*.{PalletManifest.EXTENSION_NAME}", SearchOption.AllDirectories); var unloadedPalletManifests = new Dictionary(); foreach (var palletManifestJsonPath in palletManifestJsonPaths) { if (PalletPacker.TryUnpackManifestJsonFromFile(palletManifestJsonPath, out var palletManifest, out _)) { Barcode palletBarcode = (Barcode)palletManifest.PalletBarcode; if (!_palletRegistry.ContainsKey(palletBarcode) && !palletManifests.ContainsKey(palletBarcode) && !string.IsNullOrEmpty(palletManifest.PalletPath)) { unloadedPalletManifests[palletBarcode] = palletManifest; } } } foreach (var pallet in _palletRegistry.Values.ToArray()) { foreach (var palletDependencyRef in pallet.PalletDependencies) { var palletRefBarcode = palletDependencyRef.Barcode; if (palletRefBarcode.IsValid() && !palletRefBarcode.ID.Contains("SLZ.BONELAB")) { if (!_palletRegistry.ContainsKey(palletRefBarcode) && unloadedPalletManifests.TryGetValue(palletRefBarcode, out var palletManifest)) { var palletPath = palletManifest.PalletPath; LogVerbose($"LoadPalletEditorDependencies: Load pallet dependency {palletRefBarcode} for {pallet.Barcode}"); await LoadPalletFromFolderAsync(palletPath); } } } } LogVerbose("LoadPalletEditorDependencies: Done"); } #endif #if UNITY_EDITOR public async UniTask EditorLoadBuiltMarrowGames() { SDKProjectPreferences.LoadFromFile(); foreach (var marrowGameInstallPath in SDKProjectPreferences.MarrowGameInstallPaths) { await EditorLoadBuiltMarrowGame(marrowGameInstallPath); } } public async UniTask EditorLoadBuiltMarrowGame(string gamePath) { MarrowSettings marrowGame; if (string.IsNullOrEmpty(gamePath) || !Directory.Exists(gamePath)) { UnityEngine.Debug.LogError($"AssetWarehouse: EditorLoadBuiltMarrowGame: Failed to load \"{gamePath}\""); } else { if (!string.IsNullOrEmpty(gamePath) && Directory.Exists(gamePath)) { string contentDirectory = ""; var dirs = Directory.GetDirectories(gamePath, "StandaloneWindows64", SearchOption.AllDirectories); foreach (var dir in dirs) { var parent = Directory.GetParent(dir); var parentParent = parent == null ? null : Directory.GetParent(parent.FullName); if (parent != null && parentParent != null && parent.Name == "aa" && parentParent.Name == "StreamingAssets") { contentDirectory = Path.GetFullPath(dir); break; } } gamePath = contentDirectory; string marrowGamePath = Path.Combine(gamePath, "MarrowGame.json"); if (File.Exists(marrowGamePath) && PalletPacker.TryUnpackMarrowGameJsonFromFile(marrowGamePath, out marrowGame, out _)) { string palletsString = "[ "; foreach (var gamePallet in marrowGame.GamePallets) { palletToMarrowGameLookup[gamePallet.Barcode] = marrowGame; palletsString += $"{gamePallet.Barcode} "; } palletsString += "]"; await LoadMarrowGame(marrowGame, gamePath); await LoadGamePallets(marrowGame, gamePath, true); LogVerbose($"Loaded Built Game for {marrowGame.GameTitle} at \"{gamePath}\" with pallets: {palletsString}"); } } } } #endif public async UniTask LoadMarrowGame(MarrowSettings marrowGame, string contentPath) { if (!marrowGames.ContainsKey(marrowGame.GameTitle)) { marrowGames[marrowGame.GameTitle] = marrowGame; if (!string.IsNullOrEmpty(contentPath)) { marrowGameLocations[marrowGame.GameTitle] = contentPath; } } } public async UniTask LoadGamePallets(MarrowSettings marrowGame, string contentPath, bool loadStandalonePallets = true) { await LoadMarrowGame(marrowGame, contentPath); string palletListString = string.Join(", ", marrowGame.GamePallets); LogVerbose($"Load Game Pallets: {marrowGame.GameTitle}[{palletListString}]", true); if (loadStandalonePallets) { foreach (var pallet in marrowGame.StandalonePallets) { string palletPath = WarehousePathResolver.EnsureValidPath(Path.Combine(contentPath, $"{pallet.Barcode}.pallet.json")); LogVerbose($"Load Standalone Game Pallet {pallet.Barcode}: {palletPath}", true); var loaded = await LoadPalletFromFolderAsync(palletPath); if (loaded) { gamePallets.Add(pallet.Barcode); palletToMarrowGameLookup[pallet.Barcode] = marrowGame; } } } foreach (var pallet in marrowGame.GamePallets) { string palletPath = WarehousePathResolver.EnsureValidPath(Path.Combine(contentPath, $"{pallet.Barcode}.pallet.json")); LogVerbose($"Load Game Pallet {pallet.Barcode}: {palletPath}", true); var loaded = await LoadPalletFromFolderAsync(palletPath, customCatalog: marrowGame.GameTitle); if (loaded) { gamePallets.Add(pallet.Barcode); palletToMarrowGameLookup[pallet.Barcode] = marrowGame; } } } public async UniTask LoadPalletFromMarrowGame(Barcode palletBarcode, string palletPath) { bool success = false; if (palletToMarrowGameLookup.TryGetValue(palletBarcode, out var marrowGame)) { success = await LoadPalletFromFolderAsync(palletPath, customCatalog: marrowGame.GameTitle); } return success; } public async UniTask LoadPalletsFromFolderAsync(string path) { if (Directory.Exists(path)) { string[] palletJsons = Directory.GetFiles(path, $"*{Pallet.PALLET_JSON_FILENAME}", SearchOption.AllDirectories); foreach (var palletJsonPath in palletJsons) { bool success = await LoadPalletFromFolderAsync(palletJsonPath); } } else { Debug.LogWarning("AssetWarehouse: Cannot load pallets from folder, missing folder: " + (path == null ? "null" : path)); } } public async UniTask LoadPalletFromFolderAsync(string palletPath, bool updateCatalog = false, string customCatalog = null, ModListing modListing = null) { palletPackStopWatch.Start(); Pallet loadedPallet = null; #if !UNITY_EDITOR && UNITY_ANDROID #else loadedPallet = PalletPacker.UnpackJsonFromFile(palletPath); #endif palletPackStopWatch.Stop(); if (loadedPallet != null) { return await LoadPalletAsync(loadedPallet, palletPath, updateCatalog, customCatalog, modListing); } else { Debug.LogWarning("AssetWarehouse: Cannot read " + Pallet.PALLET_JSON_FILENAME + " at " + palletPath); return false; } } public async UniTask LoadPalletAsync(Pallet pallet, string palletPath, bool updateCatalog = false, string customCatalog = null, ModListing modListing = null, bool loadDataCards = true) { bool success = false; if (!string.IsNullOrEmpty(palletPath)) { string catalogName = string.IsNullOrEmpty(customCatalog) ? pallet.Barcode.ID : customCatalog; string catalogPath = palletPath.Replace($"{pallet.Barcode}.pallet.json", $"catalog_{catalogName}.json"); catalogPath = catalogPath.Replace($"pallet.json", $"catalog_{catalogName}.json"); WarehousePathResolver.EnsureValidPath(ref catalogPath); bool catalogExists = false; #if !UNITY_EDITOR && UNITY_ANDROID #else catalogExists = File.Exists(catalogPath); #endif if (catalogExists) { LogVerbose($"Found catalog for pallet {pallet.Barcode}: {catalogPath}", true); bool runtimeUpdate = loadedCatalogs.Contains(catalogPath); bool catalogLoaded = false; IResourceLocator catalogLocator = null; if (runtimeUpdate) { var checkForCatalogOp = Addressables.CheckForCatalogUpdates(false); var updatableCatalogs = await checkForCatalogOp; string updateCatalogPath = null; if (updatableCatalogs != null && updatableCatalogs.Count > 0) { foreach (var catalog in updatableCatalogs) { if (Path.GetFullPath(catalogPath) == Path.GetFullPath(catalog)) { updateCatalogPath = catalog; } } if (!string.IsNullOrEmpty(updateCatalogPath)) { var catalogs = await Addressables.UpdateCatalogs(false, new[] { updateCatalogPath }, true); catalogLoaded = catalogs != null && catalogs.Count > 0; if (catalogLoaded) { LogVerbose($"Updated catalog {catalogPath}: {catalogs[0].Keys.Count()} keys", true); } else { Debug.LogError($"AssetWarehouse: Failed to update catalog for pallet {pallet.Barcode} at {catalogPath}, {updateCatalogPath}"); } } } Addressables.Release(checkForCatalogOp); } else { var operationHandle = Addressables.LoadContentCatalogAsync(catalogPath, true); #if UNITY_EDITOR EditorApplication.QueuePlayerLoopUpdate(); #endif catalogLocator = await operationHandle; #if UNITY_EDITOR EditorApplication.QueuePlayerLoopUpdate(); #endif catalogLoaded = catalogLocator != null; if (catalogLoaded) { loadedCatalogs.Add(catalogPath); LogVerbose($"Loaded catalog {catalogPath}: {catalogLocator.Keys.Count()} keys", true); } else { Debug.LogError($"AssetWarehouse: Failed to open catalog for pallet {pallet.Barcode} at {catalogPath}"); } } if (catalogLoaded || runtimeUpdate) { LoadAndUpdatePalletManifest(pallet, modListing, palletPath, catalogPath, catalogLocator); if (loadDataCards) await LoadPalletDataCards(pallet, true); AddPallet(pallet); success = true; } } else { Debug.LogError($"AssetWarehouse: Cannot load catalog from path {catalogPath} for pallet {pallet.Barcode}"); } } else if (string.IsNullOrEmpty(palletPath)) { Debug.LogError($"AssetWarehouse: Cannot load pallet {pallet.Barcode} with null/empty pallet path"); } return await UniTask.FromResult(success); } private void LoadAndUpdatePalletManifest(Pallet pallet, ModListing modListing, string palletPath, string catalogPath, IResourceLocator catalogLocator = null) { var manifestPath = PalletManifest.GetManifestPath(pallet); bool updateManifest = TryLoadCachedPalletManifest(out var loadedPalletManifest, manifestPath, out var loadedPalletManifestJson); var palletManifest = updateManifest ? new PalletManifest(loadedPalletManifest) : loadedPalletManifest; palletManifest.Pallet = pallet; palletManifest.PalletBarcode = pallet.Barcode; palletManifest.PalletPath = palletPath; palletManifest.CatalogPath = catalogPath; if (catalogLocator != null) palletManifest.Catalog = catalogLocator; if (string.IsNullOrEmpty(palletManifest.InstalledDate)) { if (updateManifest) { if (!string.IsNullOrEmpty(loadedPalletManifest.InstalledDate)) { palletManifest.InstalledDate = loadedPalletManifest.InstalledDate; } } else { palletManifest.SetInstalledDateToNow(); } } if (string.IsNullOrEmpty(palletManifest.UpdatedDate)) { if (updateManifest) { if (!string.IsNullOrEmpty(loadedPalletManifest.UpdatedDate)) { palletManifest.UpdatedDate = loadedPalletManifest.UpdatedDate; } } else { palletManifest.UpdatedDate = palletManifest.InstalledDate; } } if (pallet.Version != palletManifest.Version) { palletManifest.Version = pallet.Version; } if (modListing != null) { if (!Barcode.IsValid(modListing.Barcode)) modListing.Barcode = pallet.Barcode; palletManifest.ModListing = modListing; } else { if (updateManifest && loadedPalletManifest.ModListing != null) { palletManifest.ModListing = loadedPalletManifest.ModListing; } } try { var newJson = PalletPacker.PackManifestIntoJson(palletManifest); if (!updateManifest || string.IsNullOrEmpty(loadedPalletManifestJson) || !newJson.Equals(loadedPalletManifestJson, StringComparison.InvariantCulture)) { UpdatePalletManifest(palletManifest, newJson); } CachePalletManifest(palletManifest); } catch (Exception e) { Debug.LogError($"AssetWarehouse: Failed to update pallet manifest for pallet: {pallet.name}: {pallet.Barcode}"); Debug.LogException(e); } } private bool TryLoadCachedPalletManifest(out PalletManifest palletManifest, string manifestPath, out string palletManifestJson) { palletManifestPackStopWatch.Start(); if (PalletPacker.TryUnpackManifestJsonFromFile(manifestPath, out palletManifest, out palletManifestJson)) { palletManifestPackStopWatch.Stop(); return true; } else { palletManifestPackStopWatch.Stop(); palletManifest = new PalletManifest(); return false; } } public bool TryGetPalletManifest(Barcode barcode, out PalletManifest palletManifest) { return palletManifests.TryGetValue(barcode, out palletManifest); } public bool UpdatePalletManifest(PalletManifest palletManifest, string json = "") { bool success = PalletPacker.TryPackManifestAndSaveToJson(palletManifest, palletManifest.ManifestPath, json); return success; } private void CachePalletManifest(PalletManifest palletManifest) { palletManifests[palletManifest.Pallet.Barcode] = palletManifest; } public List GetPalletManifests() { return palletManifests.Values.ToList(); } public async UniTask LoadPalletDataCards(Pallet pallet, bool mutablePallet) { bool isPlaying = Application.isPlaying; #if UNITY_EDITOR isPlaying = isPlaying || EditorApplication.isPlayingOrWillChangePlaymode || mutablePallet; async UniTask BufferLoadDataCard(DataCard dataCard) { DataCard loadedDataCard = dataCard; try { LogVerbose($"Load DataCard<{loadedDataCard.GetType().Name}> {loadedDataCard.Title}"); var loadCard = await dataCard.DataCardAsset.LoadAssetAsync(); if (loadCard != null) { loadedDataCard = loadCard; if (isPlaying) { if (!string.IsNullOrEmpty(AssetDatabase.GetAssetPath(loadCard))) { loadedDataCard = Object.Instantiate(loadCard); loadedDataCard.ExportPackedAssets(); } } loadedDataCard.DataCardAsset = dataCard.DataCardAsset; } } catch (Exception e) { Debug.LogError($"AssetWarehouse: Failed to load DataCard({dataCard.GetType().Name}) \"{dataCard.Title}\" from Pallet \"{dataCard.Pallet.Title}\""); loadedDataCard = null; } return loadedDataCard; } Stopwatch dataCardStopWatch = new Stopwatch(); dataCardStopWatch.Start(); if (isPlaying) { var bundledDataCards = new List(); var nonBundledDataCards = new List(); foreach (var dataCard in pallet.DataCards) { if (dataCard.IsBundledDataCard()) bundledDataCards.Add(dataCard); else nonBundledDataCards.Add(dataCard); } var loadedDataCards = await UniTask.WhenAll(bundledDataCards.Select(BufferLoadDataCard)); pallet.DataCards.Clear(); pallet.DataCards.AddRange(nonBundledDataCards); pallet.DataCards.AddRange(loadedDataCards); } dataCardStopWatch.Stop(); LogVerbose($"Finish load DataCards for {pallet.Title}, took {dataCardStopWatch.Elapsed:h\\:mm\\:ss\\.fff}"); #endif #if !UNITY_EDITOR #endif } #if UNITY_EDITOR public void ClearPallets() { _inventoryRegistry.Clear(); _palletRegistry.Clear(); palletManifests.Clear(); OnChanged?.Invoke(); } private void OnBeforeDomainReload() { Clear(); } #endif } public static class AssetWarehouseExtensions { public static List Filter(this List crateList, ICrateFilter crateFilter) where T : Crate { return AssetWarehouse.Instance.FilterCrates(ref crateList, crateFilter); } } }