using System; using System.Collections; using System.Collections.Generic; using System.Threading; using Cysharp.Threading.Tasks; using UnityEngine; namespace AssetBundleHub { /// /// AssetBundleからロードしたAssetのキャッシュ /// public class ABAssetRepository { readonly IAssetBundleCache assetBundleCache; // ロードしたAssetのキャッシュ ダウンキャストのコストが気になるようになった場合にはTypeごとにキャッシュするように修正する想定 Dictionary loadedAssets = new Dictionary(); HashSet loadingAssets = new HashSet(); // AssetBundleからロード中のassetの名前 // AssetBundleのAssetロード状態を記録 key: assetBundleName, value: ロードされたassetNames Dictionary> loadedAssetBundles = new Dictionary>(); public ABAssetRepository(IAssetBundleCache assetBundleCache) { this.assetBundleCache = assetBundleCache; } /// /// AssetBundleを介してAssetをロード /// 参照カウントを増やす /// /// ビルド時に設定したaddressableName /// キャンセルしても参照カウントはするのでUnload呼ぶ必要あり public async UniTask LoadAsync(string assetName, CancellationToken cancellationToken = default(CancellationToken)) where T : UnityEngine.Object { if (!assetBundleCache.TryGetAssetBundleName(assetName, out string assetBundleName)) { throw new Exception($"get assetBundleName failed {assetName}"); } if (loadingAssets.Contains(assetName)) { await UniTask.WaitUntil(() => !loadingAssets.Contains(assetName)); // NOTE: キャンセルしても参照カウントは増やしたいのでcancellationTokenを伝搬しない } loadingAssets.Add(assetName); T asset = null; try { var assetBundle = await assetBundleCache.LoadAsync(assetBundleName, cancellationToken); // 参照カウントを増やす asset = await LoadAssetAsync(assetName, assetBundle, cancellationToken); loadedAssets[assetName] = asset; if (!loadedAssetBundles.ContainsKey(assetBundleName)) { loadedAssetBundles[assetBundleName] = new List(); } loadedAssetBundles[assetBundleName].Add(assetName); } finally { loadingAssets.Remove(assetName); } return asset; } async UniTask LoadAssetAsync(string assetName, AssetBundle assetBundle, CancellationToken cancellationToken = default(CancellationToken)) where T : UnityEngine.Object { var assetObject = await assetBundle.LoadAssetAsync(assetName).ToUniTask(cancellationToken: cancellationToken); if (assetObject == null) { throw new Exception($"loaded asset is null : {assetName}"); } var asset = assetObject as T; if (asset == null) { throw new Exception($"loaded asset type is not {typeof(T).Name}"); } return asset; } /// /// ロード済みのAssetを取得 /// 参照カウントを増やさない /// public T GetAsset(string assetName) where T : UnityEngine.Object { T asset = null; if (!loadedAssets.TryGetValue(assetName, out var assetObject)) { return null; } if (assetObject == null) { throw new Exception($"asset unloaded but key exists : {assetName}"); } asset = assetObject as T; if (asset == null) { throw new Exception($"asset type is not {typeof(T).Name}"); } return asset; } /// /// AssetをUnload。AssetBundleが複数のAssetを持っている場合には全てのAssetが消えるまでUnity側のメモリにキャッシュはされてる。 /// 2箇所でロードされたとしても、1箇所でUnloadしたら本クラスのキャッシュからは消す /// (ただし、メモリに残っていたらLoadAssetで同期的に取得は可能) /// /// trueにすると属するAssetBundleを参照カウントを無視して解放する。関連するロードされていたAssetが残っていた場合nullになる public void Unload(string assetName, bool ignoreRefCount = false) { if (loadingAssets.Contains(assetName)) { // ロード中にUnloadは禁止。 Debug.LogWarning($"unload failed. loading asset can't unload assetName: {assetName}"); return; } if (!assetBundleCache.TryGetAssetBundleName(assetName, out string assetBundleName)) { throw new Exception($"get assetBundleName failed {assetName}"); } if (!assetBundleCache.TryGetRef(assetBundleName, out var abRef)) { return; // NOTE: Unload済み } // AssetBundle.LoadAssetを使わない方針のため、AssetBundle解放のタイミングまではAssetのキャッシュを解放しない。 if (abRef.Count <= 1 || ignoreRefCount) { foreach (var removeAssetName in loadedAssetBundles[assetBundleName]) { loadedAssets[removeAssetName] = null; // nullをいれないと解放されないケースがMonoBehaviourにおいてはあるので念の為 loadedAssets.Remove(removeAssetName); } loadedAssetBundles.Remove(assetBundleName); } assetBundleCache.Unload(assetBundleName, ignoreRefCount); } } }