310 lines
9.7 KiB
C#
310 lines
9.7 KiB
C#
using System;
|
|
using System.Collections;
|
|
using System.Collections.Generic;
|
|
using UnityEngine;
|
|
using UnityEngine.AddressableAssets;
|
|
using UnityEngine.AddressableAssets.ResourceLocators;
|
|
using UnityEngine.ResourceManagement.AsyncOperations;
|
|
|
|
public static class DlcRemoteContentService
|
|
{
|
|
private sealed class RuntimeLoadedState
|
|
{
|
|
public readonly List<AsyncOperationHandle> retainedAssetHandles = new List<AsyncOperationHandle>();
|
|
public readonly List<AsyncOperationHandle> retainedCatalogHandles = new List<AsyncOperationHandle>();
|
|
public readonly List<DlcRuntimePackage> packages = new List<DlcRuntimePackage>();
|
|
}
|
|
|
|
private sealed class DlcRemoteContentRunner : MonoBehaviour
|
|
{
|
|
private void Start()
|
|
{
|
|
if (DlcRemoteContentService.autoRefreshOnStart)
|
|
{
|
|
DlcRemoteContentService.RefreshInstalledDlc();
|
|
}
|
|
}
|
|
}
|
|
|
|
private static DlcRemoteContentRunner runner;
|
|
private static RuntimeLoadedState activeState = new RuntimeLoadedState();
|
|
private static bool autoRefreshOnStart = true;
|
|
private static bool refreshRequestedOnBoot;
|
|
|
|
public static bool IsRefreshing { get; private set; }
|
|
|
|
public static event Action<bool> RefreshCompleted;
|
|
|
|
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSceneLoad)]
|
|
private static void EnsureRunner()
|
|
{
|
|
if (runner != null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
GameObject go = new GameObject(nameof(DlcRemoteContentService));
|
|
UnityEngine.Object.DontDestroyOnLoad(go);
|
|
runner = go.AddComponent<DlcRemoteContentRunner>();
|
|
refreshRequestedOnBoot = false;
|
|
}
|
|
|
|
public static void SetAutoRefreshOnStart(bool enabled)
|
|
{
|
|
autoRefreshOnStart = enabled;
|
|
}
|
|
|
|
public static void RefreshInstalledDlc()
|
|
{
|
|
EnsureRunner();
|
|
if (runner == null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (IsRefreshing)
|
|
{
|
|
refreshRequestedOnBoot = true;
|
|
return;
|
|
}
|
|
|
|
runner.StartCoroutine(RefreshInstalledDlcRoutine());
|
|
}
|
|
|
|
public static void ClearRuntimeDlc()
|
|
{
|
|
ReleaseState(activeState);
|
|
activeState = new RuntimeLoadedState();
|
|
DlcRuntimeRegistry.ClearAll();
|
|
InvalidateRuntimeCaches();
|
|
}
|
|
|
|
private static IEnumerator RefreshInstalledDlcRoutine()
|
|
{
|
|
if (IsRefreshing)
|
|
{
|
|
yield break;
|
|
}
|
|
|
|
IsRefreshing = true;
|
|
refreshRequestedOnBoot = false;
|
|
|
|
List<DlcManifestRuntimeEntry> manifests = DlcManifestService.LoadInstalledEntries();
|
|
RuntimeLoadedState nextState = new RuntimeLoadedState();
|
|
bool success = true;
|
|
|
|
for (int i = 0; i < manifests.Count; i++)
|
|
{
|
|
DlcManifestRuntimeEntry manifest = manifests[i];
|
|
if (manifest == null || manifest.entry == null)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
yield return LoadManifestEntry(manifest, nextState, result => success &= result);
|
|
}
|
|
|
|
if (success)
|
|
{
|
|
RuntimeLoadedState oldState = activeState;
|
|
activeState = nextState;
|
|
DlcRuntimeRegistry.ReplaceAll(nextState.packages);
|
|
InvalidateRuntimeCaches();
|
|
ReleaseState(oldState);
|
|
}
|
|
else
|
|
{
|
|
ReleaseState(nextState);
|
|
}
|
|
|
|
IsRefreshing = false;
|
|
RefreshCompleted?.Invoke(success);
|
|
|
|
if (refreshRequestedOnBoot)
|
|
{
|
|
RefreshInstalledDlc();
|
|
}
|
|
}
|
|
|
|
private static IEnumerator LoadManifestEntry(
|
|
DlcManifestRuntimeEntry manifest,
|
|
RuntimeLoadedState state,
|
|
Action<bool> reportResult)
|
|
{
|
|
bool localSuccess = true;
|
|
AsyncOperationHandle<IResourceLocator> catalogHandle = Addressables.LoadContentCatalogAsync(manifest.resolvedCatalogPath, false);
|
|
yield return catalogHandle;
|
|
|
|
if (catalogHandle.Status != AsyncOperationStatus.Succeeded)
|
|
{
|
|
Debug.LogWarning("[DLC] Failed to load catalog: " + manifest.resolvedCatalogPath);
|
|
reportResult?.Invoke(false);
|
|
yield break;
|
|
}
|
|
|
|
state.retainedCatalogHandles.Add(catalogHandle);
|
|
|
|
if (manifest.entry.autoDownloadDependencies)
|
|
{
|
|
string[] dependencyKeys = manifest.entry.GetDependencyKeys();
|
|
for (int i = 0; i < dependencyKeys.Length; i++)
|
|
{
|
|
string dependencyKey = dependencyKeys[i];
|
|
if (string.IsNullOrWhiteSpace(dependencyKey))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
AsyncOperationHandle downloadHandle = Addressables.DownloadDependenciesAsync(dependencyKey, false);
|
|
yield return downloadHandle;
|
|
|
|
if (downloadHandle.Status == AsyncOperationStatus.Succeeded)
|
|
{
|
|
state.retainedAssetHandles.Add(downloadHandle);
|
|
}
|
|
else
|
|
{
|
|
localSuccess = false;
|
|
Debug.LogWarning("[DLC] Failed to download dependencies for key '" + dependencyKey + "'.");
|
|
Addressables.Release(downloadHandle);
|
|
}
|
|
}
|
|
}
|
|
|
|
List<dlcData> loadedDlcs = new List<dlcData>();
|
|
List<SongData> loadedSongs = new List<SongData>();
|
|
List<SongDlcContentSO> loadedSongContents = new List<SongDlcContentSO>();
|
|
List<HeroSkinSO> loadedHeroSkins = new List<HeroSkinSO>();
|
|
|
|
yield return LoadAssetsByLabels(manifest.entry.GetDlcDataLabels(), loadedDlcs, state.retainedAssetHandles, result => localSuccess &= result);
|
|
yield return LoadAssetsByLabels(manifest.entry.GetSongLabels(), loadedSongs, state.retainedAssetHandles, result => localSuccess &= result);
|
|
yield return LoadAssetsByLabels(manifest.entry.GetSongContentLabels(), loadedSongContents, state.retainedAssetHandles, result => localSuccess &= result);
|
|
yield return LoadAssetsByLabels(manifest.entry.GetHeroSkinLabels(), loadedHeroSkins, state.retainedAssetHandles, result => localSuccess &= result);
|
|
|
|
DlcRuntimePackage package = new DlcRuntimePackage
|
|
{
|
|
packageId = manifest.GetRuntimePackageId(),
|
|
dlcKey = manifest.entry.GetSafeDlcKey(),
|
|
version = manifest.entry.version ?? string.Empty,
|
|
sourceCatalogPath = manifest.resolvedCatalogPath,
|
|
dlcs = loadedDlcs.ToArray(),
|
|
songs = loadedSongs.ToArray(),
|
|
songContents = loadedSongContents.ToArray(),
|
|
heroSkins = loadedHeroSkins.ToArray()
|
|
};
|
|
|
|
state.packages.Add(package);
|
|
reportResult?.Invoke(localSuccess);
|
|
}
|
|
|
|
private static IEnumerator LoadAssetsByLabels<T>(
|
|
string[] labels,
|
|
List<T> output,
|
|
List<AsyncOperationHandle> retainedHandles,
|
|
Action<bool> reportResult) where T : UnityEngine.Object
|
|
{
|
|
if (labels == null || labels.Length == 0)
|
|
{
|
|
reportResult?.Invoke(true);
|
|
yield break;
|
|
}
|
|
|
|
List<object> keys = new List<object>(labels.Length);
|
|
for (int i = 0; i < labels.Length; i++)
|
|
{
|
|
if (!string.IsNullOrWhiteSpace(labels[i]))
|
|
{
|
|
keys.Add(labels[i].Trim());
|
|
}
|
|
}
|
|
|
|
if (keys.Count == 0)
|
|
{
|
|
reportResult?.Invoke(true);
|
|
yield break;
|
|
}
|
|
|
|
AsyncOperationHandle<IList<T>> loadHandle = Addressables.LoadAssetsAsync<T>(keys, null, Addressables.MergeMode.Union, false);
|
|
yield return loadHandle;
|
|
|
|
if (loadHandle.Status != AsyncOperationStatus.Succeeded)
|
|
{
|
|
Debug.LogWarning("[DLC] Failed to load runtime assets of type " + typeof(T).Name + ".");
|
|
Addressables.Release(loadHandle);
|
|
reportResult?.Invoke(false);
|
|
yield break;
|
|
}
|
|
|
|
retainedHandles.Add(loadHandle);
|
|
|
|
IList<T> result = loadHandle.Result;
|
|
if (result != null)
|
|
{
|
|
for (int i = 0; i < result.Count; i++)
|
|
{
|
|
T asset = result[i];
|
|
if (asset != null && !output.Contains(asset))
|
|
{
|
|
output.Add(asset);
|
|
}
|
|
}
|
|
}
|
|
|
|
reportResult?.Invoke(true);
|
|
}
|
|
|
|
private static void InvalidateRuntimeCaches()
|
|
{
|
|
RuntimeResourcesCache.InvalidateAll();
|
|
SongDlcContentResolver.InvalidateCache();
|
|
HeroSkinResolver.InvalidateCache();
|
|
DlcOwnershipService.InvalidateCache();
|
|
|
|
SongDataLibrary library = SongDataLibrary.Instance;
|
|
if (library != null)
|
|
{
|
|
library.RefreshLibrary();
|
|
}
|
|
|
|
loadDlcListPrefab[] uiPanels = UnityEngine.Object.FindObjectsByType<loadDlcListPrefab>(FindObjectsInactive.Include, FindObjectsSortMode.None);
|
|
for (int i = 0; i < uiPanels.Length; i++)
|
|
{
|
|
if (uiPanels[i] != null && uiPanels[i].isActiveAndEnabled)
|
|
{
|
|
uiPanels[i].RefreshUI();
|
|
}
|
|
}
|
|
}
|
|
|
|
private static void ReleaseState(RuntimeLoadedState state)
|
|
{
|
|
if (state == null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
for (int i = 0; i < state.retainedAssetHandles.Count; i++)
|
|
{
|
|
AsyncOperationHandle handle = state.retainedAssetHandles[i];
|
|
if (handle.IsValid())
|
|
{
|
|
Addressables.Release(handle);
|
|
}
|
|
}
|
|
|
|
state.retainedAssetHandles.Clear();
|
|
|
|
for (int i = 0; i < state.retainedCatalogHandles.Count; i++)
|
|
{
|
|
AsyncOperationHandle handle = state.retainedCatalogHandles[i];
|
|
if (handle.IsValid())
|
|
{
|
|
Addressables.Release(handle);
|
|
}
|
|
}
|
|
|
|
state.retainedCatalogHandles.Clear();
|
|
state.packages.Clear();
|
|
}
|
|
}
|