using System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using HG; using HG.AsyncOperations; using RoR2.EntitlementManagement; using RoR2.ExpansionManagement; using RoR2.Skills; using UnityEngine; using UnityEngine.AddressableAssets; using UnityEngine.AddressableAssets.ResourceLocators; using UnityEngine.ResourceManagement; using UnityEngine.ResourceManagement.AsyncOperations; using UnityEngine.ResourceManagement.ResourceLocations; namespace RoR2.ContentManagement; public class AddressablesLoadHelper { private class Operation { public float weight; public IEnumerator coroutine; public float progress; } public class AddressablesLoadAsyncOperationWrapper : BaseAsyncOperation where TAsset : UnityEngine.Object { private AsyncOperationHandle>[] handles; private int completionCount; public override float progress => 0f; public AddressablesLoadAsyncOperationWrapper(IReadOnlyList>> handles) { if (handles.Count == 0) { this.handles = Array.Empty>>(); Complete(Array.Empty()); return; } Action>> value = OnChildOperationCompleted; this.handles = new AsyncOperationHandle>[handles.Count]; for (int i = 0; i < handles.Count; i++) { this.handles[i] = handles[i]; AsyncOperationHandle> asyncOperationHandle = handles[i]; asyncOperationHandle.Completed += value; } } private void OnChildOperationCompleted(AsyncOperationHandle> completedOperationHandle) { completionCount++; if (completionCount != handles.Length) { return; } List list = new List(); AsyncOperationHandle>[] array = handles; for (int i = 0; i < array.Length; i++) { AsyncOperationHandle> asyncOperationHandle = array[i]; if (asyncOperationHandle.Result != null) { list.AddRange(asyncOperationHandle.Result); } } Complete(list.ToArray()); } } private readonly IResourceLocator[] resourceLocators = Array.Empty(); private readonly object[] requiredKeys = Array.Empty(); public int maxConcurrentOperations = 2; public float timeoutDuration = float.PositiveInfinity; private const int loadOperationsStartedMax = 28; private int loadOperationsStartedCount; private readonly List allOperations = new List(); private readonly Queue pendingLoadOperations = new Queue(); private readonly List runningLoadOperations = new List(); private readonly List allGenericOperations = new List(); public ReadableProgress progress { get; private set; } public IEnumerator coroutine { get; private set; } private float loadOperationsStartedProgress01 => Mathf.Clamp01((float)loadOperationsStartedCount / 28f); public AddressablesLoadHelper(IReadOnlyList resourceLocators, object[] requiredKeys = null) { ArrayUtils.CloneTo(resourceLocators, ref this.resourceLocators); if (requiredKeys != null) { ArrayUtils.CloneTo(requiredKeys, ref this.requiredKeys); } progress = new ReadableProgress(); coroutine = Coroutine(progress); } public AddressablesLoadHelper(IResourceLocator resourceLocator, object[] requiredKeys = null) : this(new IResourceLocator[1] { resourceLocator }, requiredKeys) { } public static AddressablesLoadHelper CreateUsingDefaultResourceLocator(object[] requiredKeys = null) { return new AddressablesLoadHelper(Addressables.ResourceLocators.First(), requiredKeys); } public static AddressablesLoadHelper CreateUsingDefaultResourceLocator(object requiredKey) { return CreateUsingDefaultResourceLocator(new object[1] { requiredKey }); } public IEnumerator AddContentPackLoadOperationWithYields(ContentPack contentPack) { yield return FindLocationsThenAddLoadOperation(AddressablesLabels.characterBody, contentPack.bodyPrefabs.Add); yield return FindLocationsThenAddLoadOperation(AddressablesLabels.characterMaster, contentPack.masterPrefabs.Add); yield return FindLocationsThenAddLoadOperation(AddressablesLabels.projectile, contentPack.projectilePrefabs.Add); yield return FindLocationsThenAddLoadOperation(AddressablesLabels.gameMode, contentPack.gameModePrefabs.Add); yield return FindLocationsThenAddLoadOperation(AddressablesLabels.networkedObject, contentPack.networkedObjectPrefabs.Add); yield return FindLocationsThenAddLoadOperation(AddressablesLabels.skillFamily, contentPack.skillFamilies.Add); yield return FindLocationsThenAddLoadOperation(AddressablesLabels.skillDef, contentPack.skillDefs.Add); yield return FindLocationsThenAddLoadOperation(AddressablesLabels.unlockableDef, contentPack.unlockableDefs.Add); yield return FindLocationsThenAddLoadOperation(AddressablesLabels.surfaceDef, contentPack.surfaceDefs.Add); yield return FindLocationsThenAddLoadOperation(AddressablesLabels.sceneDef, contentPack.sceneDefs.Add); yield return FindLocationsThenAddLoadOperation(AddressablesLabels.networkSoundEventDef, contentPack.networkSoundEventDefs.Add); yield return FindLocationsThenAddLoadOperation(AddressablesLabels.musicTrackDef, contentPack.musicTrackDefs.Add); yield return FindLocationsThenAddLoadOperation(AddressablesLabels.gameEndingDef, contentPack.gameEndingDefs.Add); yield return FindLocationsThenAddLoadOperation(AddressablesLabels.itemDef, contentPack.itemDefs.Add); yield return FindLocationsThenAddLoadOperation(AddressablesLabels.itemTierDef, contentPack.itemTierDefs.Add); yield return FindLocationsThenAddLoadOperation(AddressablesLabels.itemRelationshipProvider, contentPack.itemRelationshipProviders.Add); yield return FindLocationsThenAddLoadOperation(AddressablesLabels.itemRelationshipType, contentPack.itemRelationshipTypes.Add); yield return FindLocationsThenAddLoadOperation(AddressablesLabels.equipmentDef, contentPack.equipmentDefs.Add); yield return FindLocationsThenAddLoadOperation(AddressablesLabels.miscPickupDef, contentPack.miscPickupDefs.Add); yield return FindLocationsThenAddLoadOperation(AddressablesLabels.buffDef, contentPack.buffDefs.Add); yield return FindLocationsThenAddLoadOperation(AddressablesLabels.eliteDef, contentPack.eliteDefs.Add); yield return FindLocationsThenAddLoadOperation(AddressablesLabels.survivorDef, contentPack.survivorDefs.Add); yield return FindLocationsThenAddLoadOperation(AddressablesLabels.artifactDef, contentPack.artifactDefs.Add); yield return FindLocationsThenAddLoadOperation(AddressablesLabels.effect, contentPack.effectDefs.Add, (GameObject asset) => new EffectDef(asset)); yield return FindLocationsThenAddLoadOperation(AddressablesLabels.entityStateConfiguration, contentPack.entityStateConfigurations.Add); yield return FindLocationsThenAddLoadOperation(AddressablesLabels.expansionDef, contentPack.expansionDefs.Add); yield return FindLocationsThenAddLoadOperation(AddressablesLabels.entitlementDef, contentPack.entitlementDefs.Add); loadOperationsStartedCount = 28; AddGenericOperation(AddEntityStateTypes(), 0.05f); yield return null; IEnumerator AddEntityStateTypes() { contentPack.entityStateTypes.Add((from v in contentPack.entityStateConfigurations select (Type)v.targetType into v where v != null select v).ToArray()); yield return null; } } public IEnumerator FindLocationsThenAddLoadOperation(string key, Action onComplete, float weight = 1f) where TAssetSrc : UnityEngine.Object { return FindLocationsThenAddLoadOperation(key, onComplete, null, weight); } public IEnumerator FindLocationsThenAddLoadOperation(string key, Action onComplete, Func selector = null, float weight = 1f) where TAssetSrc : UnityEngine.Object { List list = new List(); ListUtils.AddRange(list, requiredKeys); list.Add(key); AsyncOperationHandle> locationAsyncOperationHandle = Addressables.LoadResourceLocationsAsync((IEnumerable)list, Addressables.MergeMode.Intersection, (Type)null); while (!locationAsyncOperationHandle.IsDone) { yield return null; } IList result = locationAsyncOperationHandle.Result; AddLoadOperation(result, onComplete, selector, weight); loadOperationsStartedCount++; } public void AddLoadOperation(IList locations, Action onComplete, float weight = 1f) where TAssetSrc : UnityEngine.Object { AddLoadOperation(locations, onComplete, null, weight); } public void AddLoadOperation(IList locations, Action onComplete, Func selector = null, float weight = 1f) where TAssetSrc : UnityEngine.Object { Operation loadOperation = new Operation { weight = weight }; loadOperation.coroutine = Coroutine(); allOperations.Add(loadOperation); pendingLoadOperations.Enqueue(loadOperation); IEnumerator Coroutine() { yield return null; List>> underlyingLoadOperations = new List>>(resourceLocators.Length); int j = 0; while (j < resourceLocators.Length) { Action oldResourceManagerExceptionHandler = ResourceManager.ExceptionHandler; ResourceManager.ExceptionHandler = ResourceManagerExceptionHandler; try { List locationsList = locations as List; int sliceStart = 0; int arrayLength = locations.Count; do { int count = Mathf.Min(5, arrayLength - sliceStart); AsyncOperationHandle> item = Addressables.LoadAssetsAsync(locationsList.GetRange(sliceStart, count), null); underlyingLoadOperations.Add(item); sliceStart += 5; yield return null; } while (sliceStart < arrayLength); Addressables.Release(locations); } finally { ResourceManager.ExceptionHandler = oldResourceManagerExceptionHandler; } int num = j + 1; j = num; void ResourceManagerExceptionHandler(AsyncOperationHandle operationHandle, Exception exception) { if (!(exception is InvalidKeyException)) { oldResourceManagerExceptionHandler?.Invoke(operationHandle, exception); } } } AddressablesLoadAsyncOperationWrapper combinedLoadOperation = new AddressablesLoadAsyncOperationWrapper(underlyingLoadOperations); Stopwatch stopwatch = new Stopwatch(); stopwatch.Start(); while (!combinedLoadOperation.isDone) { loadOperation.progress = combinedLoadOperation.progress; float num2 = (float)stopwatch.Elapsed.TotalSeconds; if (num2 > timeoutDuration) { throw new Exception($"Loading exceeded timeout. elapsedSeconds={num2} timeoutDuration={timeoutDuration}"); } yield return null; } TAssetSrc[] loadedAssets = combinedLoadOperation.result.Where((TAssetSrc asset) => asset).ToArray(); loadOperation.progress = 0.97f; yield return null; if (onComplete != null) { TAssetDest[] convertedAssets; if (selector == null) { if (!(typeof(TAssetSrc) == typeof(TAssetDest))) { throw new ArgumentNullException("selector", "Converter must be provided when TAssetSrc and TAssetDest differ."); } convertedAssets = (TAssetDest[])(object)loadedAssets; } else { convertedAssets = new TAssetDest[loadedAssets.Length]; j = 0; while (j < loadedAssets.Length) { yield return null; convertedAssets[j] = selector(loadedAssets[j]); int num = j + 1; j = num; } } yield return null; string[] assetNames = new string[loadedAssets.Length]; for (int k = 0; k < loadedAssets.Length; k++) { assetNames[k] = loadedAssets[k].name; } yield return null; Array.Sort(assetNames, convertedAssets, StringComparer.Ordinal); onComplete?.Invoke(convertedAssets); } loadOperation.progress = 1f; } } public void AddGenericOperation(IEnumerator coroutine, float weight = 1f) { Operation item = new Operation { weight = weight, coroutine = coroutine }; allOperations.Add(item); allGenericOperations.Add(item); } public void AddGenericOperation(IEnumerable coroutineProvider, float weight = 1f) { AddGenericOperation(coroutineProvider.GetEnumerator(), weight); } public void AddGenericOperation(Func coroutineMethod, float weight = 1f) { AddGenericOperation(coroutineMethod(), weight); } public void AddGenericOperation(Action action, float weight = 1f) { AddGenericOperation(Coroutine(), weight); IEnumerator Coroutine() { action(); yield return null; } } private IEnumerator Coroutine(IProgress progressReceiver) { while (pendingLoadOperations.Count > 0 || runningLoadOperations.Count > 0) { while (pendingLoadOperations.Count > 0 && runningLoadOperations.Count < maxConcurrentOperations) { runningLoadOperations.Add(pendingLoadOperations.Dequeue()); } int i = 0; while (i < runningLoadOperations.Count) { Operation operation = runningLoadOperations[i]; int num; if (operation.coroutine.MoveNext()) { UpdateProgress(); yield return operation.coroutine.Current; } else { runningLoadOperations.RemoveAt(i); num = i - 1; i = num; } num = i + 1; i = num; } } foreach (Operation genericOperation in allGenericOperations) { while (genericOperation.coroutine.MoveNext()) { UpdateProgress(); yield return genericOperation.coroutine.Current; } } progressReceiver.Report(1f); void UpdateProgress() { float num2 = 0f; float num3 = 0f; for (int j = 0; j < allOperations.Count; j++) { Operation operation2 = allOperations[j]; num2 += operation2.weight; num3 += operation2.progress * operation2.weight; } if (num2 == 0f) { num2 = 1f; num3 = 0.5f; } float num4 = loadOperationsStartedProgress01; float num5 = num3 / num2; progressReceiver.Report(num4 * 0.25f + num5 * 0.75f); } } }