WuhuIslandTesting/Library/PackageCache/com.stresslevelzero.marrow.sdk@1.2.0/Scripts/SLZ.Marrow/SLZ.Marrow.Warehouse/AssetWarehouse.cs
2025-01-07 02:06:59 +01:00

1638 lines
No EOL
58 KiB
C#

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<Barcode, PalletManifest> palletManifests = new Dictionary<Barcode, PalletManifest>();
private readonly Dictionary<int, PalletManifest> modioPalletManifestsLookup = new Dictionary<int, PalletManifest>();
private readonly List<string> loadedCatalogs = new List<string>();
private HashSet<Barcode> gamePallets = new HashSet<Barcode>();
private readonly Dictionary<string, MarrowSettings> marrowGames = new Dictionary<string, MarrowSettings>();
private readonly Dictionary<string, string> marrowGameLocations = new Dictionary<string, string>();
private readonly Dictionary<Barcode, MarrowSettings> palletToMarrowGameLookup = new Dictionary<Barcode, MarrowSettings>();
private readonly Dictionary<Barcode, Scannable> _inventoryRegistry = new Dictionary<Barcode, Scannable>();
private readonly Dictionary<Barcode, Scannable> _oldBarcodeInventoryRegistry = new Dictionary<Barcode, Scannable>();
private readonly Dictionary<Barcode, Pallet> _palletRegistry = new Dictionary<Barcode, Pallet>();
private readonly Dictionary<Barcode, Crate> _crateRegistry = new Dictionary<Barcode, Crate>();
private readonly Dictionary<Barcode, DataCard> _dataCardRegistry = new Dictionary<Barcode, DataCard>();
public Dictionary<Barcode, Scannable> InventoryRegistry
{
get
{
return _inventoryRegistry;
}
}
[ReadOnly]
[SerializeField]
private List<string> _allTags = new List<string>();
public List<string> 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<Object, Crate> _editorObjectCrateLookup = new Dictionary<Object, Crate>();
public Dictionary<Object, Crate> EditorObjectCrateLookup { get => _editorObjectCrateLookup; private set => _editorObjectCrateLookup = value; }
private Dictionary<string, Crate> _editorObjectGuidCrateLookup = new Dictionary<string, Crate>();
public Dictionary<string, Crate> EditorObjectGuidCrateLookup { get => _editorObjectGuidCrateLookup; private set => _editorObjectGuidCrateLookup = value; }
private readonly Dictionary<Barcode, Pallet> _workingPallets = new Dictionary<Barcode, Pallet>();
public Dictionary<Barcode, Pallet> 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<Object, Crate>();
else
EditorObjectCrateLookup.Clear();
if (EditorObjectGuidCrateLookup == null)
EditorObjectGuidCrateLookup = new Dictionary<string, Crate>();
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<Barcode> OnPalletAdded;
public Action<Barcode> OnCrateAdded;
public Action<Barcode> OnDataCardAdded;
#if UNITY_EDITOR
public Action OnClearEditor;
#endif
public bool TryGetPallet(Barcode barcode, out Pallet pallet)
{
if (TryGetScannable<Pallet>(barcode, out Pallet foundPallet))
{
pallet = foundPallet;
return true;
}
pallet = null;
return false;
}
public bool TryGetCrate(Barcode barcode, out Crate crate)
{
if (TryGetCrate<Crate>(barcode, out Crate foundCrate))
{
crate = foundCrate;
return true;
}
crate = null;
return false;
}
public bool TryGetCrate<T>(Barcode barcode, out T crateT)
where T : Crate
{
if (TryGetScannable<T>(barcode, out T foundCrate))
{
crateT = foundCrate;
return true;
}
crateT = null;
return false;
}
public bool TryGetDataCard(Barcode barcode, out DataCard dataCard)
{
if (TryGetScannable<DataCard>(barcode, out DataCard foundDataCard))
{
dataCard = foundDataCard;
return true;
}
dataCard = null;
return false;
}
public bool TryGetDataCard<T>(Barcode barcode, out T dataCardT)
where T : DataCard
{
if (TryGetScannable<T>(barcode, out T foundDataCard))
{
dataCardT = foundDataCard;
return true;
}
dataCardT = null;
return false;
}
public bool TryGetScannable(Barcode barcode, out Scannable scannable)
{
if (TryGetScannable<Scannable>(barcode, out Scannable item))
{
scannable = item;
return true;
}
scannable = null;
return false;
}
public bool TryGetScannable<T>(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<T>(Barcode barcode)
where T : Scannable
{
return TryGetScannable<T>(barcode, out _);
}
public bool HasScannable(Barcode barcode)
{
return HasScannable<Scannable>(barcode);
}
public bool HasCrate<T>(Barcode barcode)
where T : Crate
{
return HasScannable<T>(barcode);
}
public bool HasCrate(Barcode barcode)
{
return HasCrate<Crate>(barcode);
}
public bool HasDataCard<T>(Barcode barcode)
where T : DataCard
{
return HasScannable<T>(barcode);
}
public bool HasDataCard(Barcode barcode)
{
return HasDataCard<DataCard>(barcode);
}
public bool HasPallet(Barcode barcode)
{
return HasScannable<Pallet>(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<Pallet> 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<string> 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<Pallet> GetPallets()
{
List<Pallet> pallets = new List<Pallet>();
foreach (var palletManifest in palletManifests.Values)
{
pallets.Add(palletManifest.Pallet);
}
return pallets;
}
public void GetPallets(ref List<Pallet> pallets)
{
if (pallets == null)
pallets = new List<Pallet>();
else
pallets.Clear();
foreach (var palletManifest in palletManifests.Values)
{
pallets.Add(palletManifest.Pallet);
}
}
public int CrateCount()
{
return _crateRegistry.Count;
}
public List<Crate> GetCrates()
{
List<Crate> crates = new List<Crate>();
foreach (var scannable in _inventoryRegistry.Values)
{
if (scannable is Crate crate && !crates.Contains(scannable))
{
crates.Add(crate);
}
}
return crates;
}
public void GetCrates(in List<Crate> 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<DataCard> GetDataCards()
{
List<DataCard> dataCards = new List<DataCard>();
foreach (var scannable in _inventoryRegistry.Values)
{
if (scannable is DataCard crate && !dataCards.Contains(scannable))
{
dataCards.Add(crate);
}
}
return dataCards;
}
public void GetDataCards(in List<DataCard> 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<LevelCrate>
{
public bool Filter(LevelCrate crate)
{
return !crate.Redacted;
}
}
public List<T> FilterCrates<T>(ref List<T> crateList, ICrateFilter<T> 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<T> GetCrates<T>(ICrateFilter<T> crateFilter = null)
where T : Crate
{
List<T> crates = new List<T>();
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<SpawnableCrate>();
var allSceneCrates = GetCrates<LevelCrate>();
var testFilterCrates = GetCrates(new HideLevelCrateFilter());
var testFilterCrates2 = AssetWarehouse.Instance.GetCrates<LevelCrate>().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<Pallet> loadedPallets = new List<Pallet>();
var foundAssets = AssetDatabase.FindAssets("t:Pallet");
foreach (var guid in foundAssets)
{
string path = AssetDatabase.GUIDToAssetPath(guid);
Pallet pallet = AssetDatabase.LoadAssetAtPath<Pallet>(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<Barcode, PalletManifest>();
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<bool> 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<bool> 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<bool> 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<PalletManifest> 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<DataCard> 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<DataCard>();
var nonBundledDataCards = new List<DataCard>();
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<T> Filter<T>(this List<T> crateList, ICrateFilter<T> crateFilter)
where T : Crate
{
return AssetWarehouse.Instance.FilterCrates(ref crateList, crateFilter);
}
}
}