using System; using System.Collections.Generic; using UnityEngine.Networking; using UnityEngine.ResourceManagement.AsyncOperations; using UnityEngine.ResourceManagement.Exceptions; using UnityEngine.ResourceManagement.ResourceLocations; using UnityEngine.ResourceManagement.ResourceProviders; using UnityEngine.ResourceManagement.Util; using UnityEngine.SceneManagement; namespace UnityEngine.ResourceManagement { /// <summary> /// Entry point for ResourceManager API /// </summary> public class ResourceManager : IDisposable { /// <summary> /// Options for event types that will be sent by the ResourceManager /// </summary> public enum DiagnosticEventType { /// <summary> /// Use to indicate that an operation failed. /// </summary> AsyncOperationFail, /// <summary> /// Use to indicate that an operation was created. /// </summary> AsyncOperationCreate, /// <summary> /// Use to indicate the percentage of completion for an operation was updated. /// </summary> AsyncOperationPercentComplete, /// <summary> /// Use to indicate that an operation has completed. /// </summary> AsyncOperationComplete, /// <summary> /// Use to indicate that the reference count of an operation was modified. /// </summary> AsyncOperationReferenceCount, /// <summary> /// Use to indicate that an operation was destroyed. /// </summary> AsyncOperationDestroy, } internal bool postProfilerEvents = false; /// <summary> /// Container for information associated with a Diagnostics event. /// </summary> public struct DiagnosticEventContext { /// <summary> /// Operation handle for the event. /// </summary> public AsyncOperationHandle OperationHandle { get; } /// <summary> /// The type of diagnostic event. /// </summary> public DiagnosticEventType Type { get; } /// <summary> /// The value for this event. /// </summary> public int EventValue { get; } /// <summary> /// The IResourceLocation being provided by the operation triggering this event. /// This value is null if the event is not while providing a resource. /// </summary> public IResourceLocation Location { get; } /// <summary> /// Addition data included with this event. /// </summary> public object Context { get; } /// <summary> /// Any error that occured. /// </summary> public string Error { get; } /// <summary> /// Construct a new DiagnosticEventContext. /// </summary> /// <param name="op">Operation handle for the event.</param> /// <param name="type">The type of diagnostic event.</param> /// <param name="eventValue">The value for this event.</param> /// <param name="error">Any error that occured.</param> /// <param name="context">Additional context data.</param> public DiagnosticEventContext(AsyncOperationHandle op, DiagnosticEventType type, int eventValue = 0, string error = null, object context = null) { OperationHandle = op; Type = type; EventValue = eventValue; Location = op.m_InternalOp != null && op.m_InternalOp is IGenericProviderOperation gen ? gen.Location : null; Error = error; Context = context; } } /// <summary> /// Global exception handler. This will be called whenever an IAsyncOperation.OperationException is set to a non-null value. /// </summary> /// <example> /// <code source="../../Tests/Editor/DocExampleCode/AddExceptionHandler.cs" region="doc_AddExceptionHandler" title="Adding a global exception hanlder"/> /// </example> public static Action<AsyncOperationHandle, Exception> ExceptionHandler { get; set; } /// <summary> /// Functor to transform internal ids before being used by the providers. /// </summary> /// <remarks> /// Used to assign a function to the [ResourceManager](xref:UnityEngine.ResourceManagement.ResourceManager) that replaces location identifiers used at runtime. /// This is useful when you want to load assets from a different location than the one specified in the content catalog, /// for example downloading a remote AssetBundle from a different URL. /// /// Assigning this value through the <see cref="Addressables"/> object will set the value on the <see cref="ResourceManager"/>. /// /// The example below instantiates a GameObject from a local AssetBundle. The location identifier of the bundle is replaced with a file URI, and so the bundle is loaded via UnityWebRequest. /// <code source="../../Tests/Editor/DocExampleCode/ScriptReference/UsingInternalIdTransformFunc.cs" region="SAMPLE"/>/// /// </remarks> /// <value>A function taking an [IResourceLocation](xref:UnityEngine.ResourceManagement.ResourceLocations.IResourceLocation) and returning a transformed string location.</value> /// <seealso href="xref:addressables-api-transform-internal-id">Transforming resource URLs</seealso> public Func<IResourceLocation, string> InternalIdTransformFunc { get; set; } /// <summary> /// Checks for an internal id transform function and uses it to modify the internal id value. /// </summary> /// <param name="location">The location to transform the internal id of.</param> /// <returns>If a transform func is set, use it to pull the local id; otherwise, the InternalId property of the location is used.</returns> public string TransformInternalId(IResourceLocation location) { return InternalIdTransformFunc == null ? location.InternalId : InternalIdTransformFunc(location); } /// <summary> /// Delegate that can be used to override the web request options before being sent. /// </summary> /// <remarks> /// You can assign a function to the <see cref="Addressables"/> object's WebRequestOverride property to individually modify the <see cref="UnityWebRequest"/> which is used to download files. /// /// This can be used to add custom request headers or query strings. /// /// This affects all downloads through Addressables including catalog files and asset bundles. /// /// Assigning this value through the <see cref="Addressables"/> object will set the value on the <see cref="ResourceManager"/>. /// /// For example you could add an Authorization header to authenticate with Cloud Content Delivery's private buckets. /// <code source="../../Tests/Editor/DocExampleCode/ScriptReference/UsingWebRequestOverride.cs" region="SAMPLE" /> /// </remarks> /// <seealso href="xref:addressables-api-transform-internal-id#webrequest-override">Transforming resource URLs</seealso> public Action<UnityWebRequest> WebRequestOverride { get; set; } internal bool CallbackHooksEnabled = true; // tests might need to disable the callback hooks to manually pump updating ListWithEvents<IResourceProvider> m_ResourceProviders = new ListWithEvents<IResourceProvider>(); IAllocationStrategy m_allocator; // list of all the providers in s_ResourceProviders that implement IUpdateReceiver internal ListWithEvents<IUpdateReceiver> m_UpdateReceivers = new ListWithEvents<IUpdateReceiver>(); List<IUpdateReceiver> m_UpdateReceiversToRemove = null; //this prevents removing receivers during iteration bool m_UpdatingReceivers = false; //this prevents re-entrance into the Update method, which can cause stack overflow and infinite loops bool m_InsideUpdateMethod = false; internal int OperationCacheCount { get { return m_AssetOperationCache.Count; } } internal int InstanceOperationCount { get { return m_TrackedInstanceOperations.Count; } } //cache of type + providerId to IResourceProviders for faster lookup internal Dictionary<int, IResourceProvider> m_providerMap = new Dictionary<int, IResourceProvider>(); Dictionary<IOperationCacheKey, IAsyncOperation> m_AssetOperationCache = new Dictionary<IOperationCacheKey, IAsyncOperation>(); HashSet<InstanceOperation> m_TrackedInstanceOperations = new HashSet<InstanceOperation>(); internal DelegateList<float> m_UpdateCallbacks = DelegateList<float>.CreateWithGlobalCache(); List<IAsyncOperation> m_DeferredCompleteCallbacks = new List<IAsyncOperation>(); HashSet<IResourceProvider> m_AsestBundleProviders = new HashSet<IResourceProvider>(); bool m_InsideExecuteDeferredCallbacksMethod = false; List<DeferredCallbackRegisterRequest> m_DeferredCallbacksToRegister = null; private struct DeferredCallbackRegisterRequest { internal IAsyncOperation operation; internal bool incrementRefCount; } Action<AsyncOperationHandle, DiagnosticEventType, int, object> m_obsoleteDiagnosticsHandler; // For use in working with Obsolete RegisterDiagnosticCallback method. Action<DiagnosticEventContext> m_diagnosticsHandler; Action<IAsyncOperation> m_ReleaseOpNonCached; Action<IAsyncOperation> m_ReleaseOpCached; Action<IAsyncOperation> m_ReleaseInstanceOp; static int s_GroupOperationTypeHash = typeof(GroupOperation).GetHashCode(); static int s_InstanceOperationTypeHash = typeof(InstanceOperation).GetHashCode(); /// <summary> /// Add an update reveiver. /// </summary> /// <param name="receiver">The object to add. The Update method will be called until the object is removed. </param> public void AddUpdateReceiver(IUpdateReceiver receiver) { if (receiver == null) return; m_UpdateReceivers.Add(receiver); } /// <summary> /// Remove update receiver. /// </summary> /// <param name="receiver">The object to remove.</param> public void RemoveUpdateReciever(IUpdateReceiver receiver) { if (receiver == null) return; if (m_UpdatingReceivers) { if (m_UpdateReceiversToRemove == null) m_UpdateReceiversToRemove = new List<IUpdateReceiver>(); m_UpdateReceiversToRemove.Add(receiver); } else { m_UpdateReceivers.Remove(receiver); } } /// <summary> /// The allocation strategy object. /// </summary> public IAllocationStrategy Allocator { get { return m_allocator; } set { m_allocator = value; } } /// <summary> /// Gets the list of configured <see cref="IResourceProvider"/> objects. Resource Providers handle load and release operations for <see cref="IResourceLocation"/> objects. /// </summary> /// <value>The resource providers list.</value> public IList<IResourceProvider> ResourceProviders { get { return m_ResourceProviders; } } /// <summary> /// The CertificateHandler instance object. /// </summary> public CertificateHandler CertificateHandlerInstance { get; set; } /// <summary> /// Constructor for the resource manager. /// </summary> /// <param name="alloc">The allocation strategy to use.</param> public ResourceManager(IAllocationStrategy alloc = null) { m_ReleaseOpNonCached = OnOperationDestroyNonCached; m_ReleaseOpCached = OnOperationDestroyCached; m_ReleaseInstanceOp = OnInstanceOperationDestroy; m_allocator = alloc == null ? new LRUCacheAllocationStrategy(1000, 1000, 100, 10) : alloc; m_ResourceProviders.OnElementAdded += OnObjectAdded; m_ResourceProviders.OnElementRemoved += OnObjectRemoved; m_UpdateReceivers.OnElementAdded += x => RegisterForCallbacks(); #if ENABLE_ADDRESSABLE_PROFILER Profiling.ProfilerRuntime.Initialise(); #endif } private void OnObjectAdded(object obj) { IUpdateReceiver updateReceiver = obj as IUpdateReceiver; if (updateReceiver != null) AddUpdateReceiver(updateReceiver); } private void OnObjectRemoved(object obj) { IUpdateReceiver updateReceiver = obj as IUpdateReceiver; if (updateReceiver != null) RemoveUpdateReciever(updateReceiver); } bool m_RegisteredForCallbacks = false; internal void RegisterForCallbacks() { if (CallbackHooksEnabled && !m_RegisteredForCallbacks) { m_RegisteredForCallbacks = true; MonoBehaviourCallbackHooks.Instance.OnUpdateDelegate += Update; } } /// <summary> /// Clears out the diagnostics callback handler. /// </summary> [Obsolete("ClearDiagnosticsCallback is Obsolete, use ClearDiagnosticCallbacks instead.")] public void ClearDiagnosticsCallback() { m_diagnosticsHandler = null; m_obsoleteDiagnosticsHandler = null; } /// <summary> /// Clears out the diagnostics callbacks handler. /// </summary> public void ClearDiagnosticCallbacks() { m_diagnosticsHandler = null; m_obsoleteDiagnosticsHandler = null; } /// <summary> /// Unregister a handler for diagnostic events. /// </summary> /// <param name="func">The event handler function.</param> public void UnregisterDiagnosticCallback(Action<DiagnosticEventContext> func) { if (m_diagnosticsHandler != null) m_diagnosticsHandler -= func; else Debug.LogError("No Diagnostic callbacks registered, cannot remove callback."); } /// <summary> /// Register a handler for diagnostic events. /// </summary> /// <param name="func">The event handler function.</param> [Obsolete] public void RegisterDiagnosticCallback(Action<AsyncOperationHandle, ResourceManager.DiagnosticEventType, int, object> func) { m_obsoleteDiagnosticsHandler = func; } /// <summary> /// Register a handler for diagnostic events. /// </summary> /// <param name="func">The event handler function.</param> public void RegisterDiagnosticCallback(Action<DiagnosticEventContext> func) { m_diagnosticsHandler += func; } internal void PostDiagnosticEvent(DiagnosticEventContext context) { m_diagnosticsHandler?.Invoke(context); if (m_obsoleteDiagnosticsHandler == null) return; m_obsoleteDiagnosticsHandler(context.OperationHandle, context.Type, context.EventValue, string.IsNullOrEmpty(context.Error) ? context.Context : context.Error); } /// <summary> /// Gets the appropriate <see cref="IResourceProvider"/> for the given <paramref name="location"/> and <paramref name="type"/>. /// </summary> /// <returns>The resource provider. Or null if an appropriate provider cannot be found</returns> /// <param name="t">The desired object type to be loaded from the provider.</param> /// <param name="location">The resource location.</param> public IResourceProvider GetResourceProvider(Type t, IResourceLocation location) { if (location != null) { IResourceProvider prov = null; var hash = location.ProviderId.GetHashCode() * 31 + (t == null ? 0 : t.GetHashCode()); if (!m_providerMap.TryGetValue(hash, out prov)) { for (int i = 0; i < ResourceProviders.Count; i++) { var p = ResourceProviders[i]; if (p.ProviderId.Equals(location.ProviderId, StringComparison.Ordinal) && (t == null || p.CanProvide(t, location))) { m_providerMap.Add(hash, prov = p); break; } } } return prov; } return null; } Type GetDefaultTypeForLocation(IResourceLocation loc) { var provider = GetResourceProvider(null, loc); if (provider == null) return typeof(object); Type t = provider.GetDefaultType(loc); return t != null ? t : typeof(object); } private int CalculateLocationsHash(IList<IResourceLocation> locations, Type t = null) { if (locations == null || locations.Count == 0) return 0; int hash = 17; foreach (var loc in locations) { Type t2 = t != null ? t : GetDefaultTypeForLocation(loc); hash = hash * 31 + loc.Hash(t2); } return hash; } /// <summary> /// Load the <typeparamref name="TObject"/> at the specified <paramref name="location"/>. /// </summary> /// <returns>An async operation.</returns> /// <param name="location">Location to load.</param> /// <param name="releaseDependenciesOnFailure">When true, if the operation fails, dependencies will be released.</param> /// <typeparam name="TObject">Object type to load.</typeparam> private AsyncOperationHandle ProvideResource(IResourceLocation location, Type desiredType = null, bool releaseDependenciesOnFailure = true) { if (location == null) throw new ArgumentNullException("location"); IResourceProvider provider = null; if (desiredType == null) { provider = GetResourceProvider(desiredType, location); if (provider == null) { var ex = new UnknownResourceProviderException(location); return CreateCompletedOperationInternal<object>(null, false, ex, releaseDependenciesOnFailure); } desiredType = provider.GetDefaultType(location); } if (provider == null) provider = GetResourceProvider(desiredType, location); IAsyncOperation op; IOperationCacheKey key = CreateCacheKeyForLocation(provider, location, desiredType); if (m_AssetOperationCache.TryGetValue(key, out op)) { op.IncrementReferenceCount(); return new AsyncOperationHandle(op, location.ToString()); ; } Type provType; if (!m_ProviderOperationTypeCache.TryGetValue(desiredType, out provType)) m_ProviderOperationTypeCache.Add(desiredType, provType = typeof(ProviderOperation<>).MakeGenericType(new Type[] { desiredType })); op = CreateOperation<IAsyncOperation>(provType, provType.GetHashCode(), key, m_ReleaseOpCached); // Calculate the hash of the dependencies int depHash = location.DependencyHashCode; var depOp = location.HasDependencies ? ProvideResourceGroupCached(location.Dependencies, depHash, null, null, releaseDependenciesOnFailure) : default(AsyncOperationHandle<IList<AsyncOperationHandle>>); ((IGenericProviderOperation)op).Init(this, provider, location, depOp, releaseDependenciesOnFailure); var handle = StartOperation(op, depOp); handle.LocationName = location.ToString(); if (depOp.IsValid()) depOp.Release(); return handle; } internal IAsyncOperation GetOperationFromCache(IResourceLocation location, Type desiredType) { IResourceProvider provider = GetResourceProvider(desiredType, location); IOperationCacheKey key = CreateCacheKeyForLocation(provider, location, desiredType); if (m_AssetOperationCache.TryGetValue(key, out IAsyncOperation op)) return op; return null; } /// <summary> /// Creates the appropriate cache key for a given location + provider. AssetBundleProviders need to use a different /// cache key than regular locations. /// </summary> /// <param name="provider">The ResourceProvider for the given location</param> /// <param name="location">The location of the asset we're attempting to request</param> /// <param name="desiredType">The desired type of the asset we're attempting to request.</param> /// <returns>An IOperationCacheKey for use with the async operation cache.</returns> internal IOperationCacheKey CreateCacheKeyForLocation(IResourceProvider provider, IResourceLocation location, Type desiredType = null) { IOperationCacheKey key = null; bool isAssetBundleProvider = false; if (provider != null) { if (m_AsestBundleProviders.Contains(provider)) isAssetBundleProvider = true; else if (typeof(AssetBundleProvider).IsAssignableFrom(provider.GetType())) { isAssetBundleProvider = true; m_AsestBundleProviders.Add(provider); } } //If the location uses the AssetBundleProvider, we need to transform the ID first //so we don't try and load the same bundle twice if the user is manipulating the path at runtime. if (isAssetBundleProvider) key = new IdCacheKey(TransformInternalId(location)); else key = new LocationCacheKey(location, desiredType); return key; } Dictionary<Type, Type> m_ProviderOperationTypeCache = new Dictionary<Type, Type>(); /// <summary> /// Load the <typeparamref name="TObject"/> at the specified <paramref name="location"/>. /// </summary> /// <returns>An async operation.</returns> /// <param name="location">Location to load.</param> /// <typeparam name="TObject">Object type to load.</typeparam> public AsyncOperationHandle<TObject> ProvideResource<TObject>(IResourceLocation location) { AsyncOperationHandle handle = ProvideResource(location, typeof(TObject)); return handle.Convert<TObject>(); } /// <summary> /// Registers an operation with the ResourceManager. The operation will be executed when the <paramref name="dependency"/> completes. /// This should only be used when creating custom operations. /// </summary> /// <returns>The AsyncOperationHandle used to access the result and status of the operation.</returns> /// <param name="operation">The custom AsyncOperationBase object</param> /// <param name="dependency">Execution of the operation will not occur until this handle completes. A default handle can be passed if no dependency is required.</param> /// <typeparam name="TObject">Object type associated with this operation.</typeparam> public AsyncOperationHandle<TObject> StartOperation<TObject>(AsyncOperationBase<TObject> operation, AsyncOperationHandle dependency) { operation.Start(this, dependency, m_UpdateCallbacks); return operation.Handle; } internal AsyncOperationHandle StartOperation(IAsyncOperation operation, AsyncOperationHandle dependency) { operation.Start(this, dependency, m_UpdateCallbacks); return operation.Handle; } class CompletedOperation<TObject> : AsyncOperationBase<TObject> { bool m_Success; Exception m_Exception; bool m_ReleaseDependenciesOnFailure; public CompletedOperation() { } public void Init(TObject result, bool success, string errorMsg, bool releaseDependenciesOnFailure = true) { Init(result, success, !string.IsNullOrEmpty(errorMsg) ? new Exception(errorMsg) : null, releaseDependenciesOnFailure); } public void Init(TObject result, bool success, Exception exception, bool releaseDependenciesOnFailure = true) { Result = result; m_Success = success; m_Exception = exception; m_ReleaseDependenciesOnFailure = releaseDependenciesOnFailure; } protected override string DebugName { get { return "CompletedOperation"; } } ///<inheritdoc /> protected override bool InvokeWaitForCompletion() { m_RM?.Update(Time.unscaledDeltaTime); if (!HasExecuted) InvokeExecute(); return true; } protected override void Execute() { Complete(Result, m_Success, m_Exception, m_ReleaseDependenciesOnFailure); } } void OnInstanceOperationDestroy(IAsyncOperation o) { m_TrackedInstanceOperations.Remove(o as InstanceOperation); Allocator.Release(o.GetType().GetHashCode(), o); } void OnOperationDestroyNonCached(IAsyncOperation o) { Allocator.Release(o.GetType().GetHashCode(), o); } void OnOperationDestroyCached(IAsyncOperation o) { Allocator.Release(o.GetType().GetHashCode(), o); var cachable = o as ICachable; if (cachable?.Key != null) RemoveOperationFromCache(cachable.Key); } internal T CreateOperation<T>(Type actualType, int typeHash, IOperationCacheKey cacheKey, Action<IAsyncOperation> onDestroyAction) where T : IAsyncOperation { if (cacheKey == null) { var op = (T)Allocator.New(actualType, typeHash); op.OnDestroy = onDestroyAction; return op; } else { var op = (T)Allocator.New(actualType, typeHash); op.OnDestroy = onDestroyAction; if (op is ICachable cachable) { cachable.Key = cacheKey; AddOperationToCache(cacheKey, op); } return op; } } internal void AddOperationToCache(IOperationCacheKey key, IAsyncOperation operation) { if (!IsOperationCached(key)) m_AssetOperationCache.Add(key, operation); } internal bool RemoveOperationFromCache(IOperationCacheKey key) { if (!IsOperationCached(key)) return true; return m_AssetOperationCache.Remove(key); } internal bool IsOperationCached(IOperationCacheKey key) { return m_AssetOperationCache.ContainsKey(key); } internal int CachedOperationCount() { return m_AssetOperationCache.Count; } /// <summary> /// Creates an operation that has already completed with a specified result and error message./>. /// </summary> /// <param name="result">The result that the operation will provide.</param> /// <param name="errorMsg">The error message if the operation should be in the failed state. Otherwise null or empty string.</param> /// <typeparam name="TObject">Object type.</typeparam> /// <returns>The operation handle used for the completed operation.</returns> public AsyncOperationHandle<TObject> CreateCompletedOperation<TObject>(TObject result, string errorMsg) { var success = string.IsNullOrEmpty(errorMsg); return CreateCompletedOperationInternal(result, success, !success ? new Exception(errorMsg) : null); } /// <summary> /// Creates an operation that has already completed with a specified result and error message./>. /// </summary> /// <param name="result">The result that the operation will provide.</param> /// <param name="exception">The exception with an error message if the operation should be in the failed state. Otherwise null.</param> /// <typeparam name="TObject">Object type.</typeparam> /// <returns>The operation handle used for the completed operation.</returns> public AsyncOperationHandle<TObject> CreateCompletedOperationWithException<TObject>(TObject result, Exception exception) { return CreateCompletedOperationInternal(result, exception == null, exception); } internal AsyncOperationHandle<TObject> CreateCompletedOperationInternal<TObject>(TObject result, bool success, Exception exception, bool releaseDependenciesOnFailure = true) { var cop = CreateOperation<CompletedOperation<TObject>>(typeof(CompletedOperation<TObject>), typeof(CompletedOperation<TObject>).GetHashCode(), null, m_ReleaseOpNonCached); cop.Init(result, success, exception, releaseDependenciesOnFailure); return StartOperation(cop, default(AsyncOperationHandle)); } /// <summary> /// Release the operation associated with the specified handle /// </summary> /// <param name="handle">The handle to release.</param> public void Release(AsyncOperationHandle handle) { handle.Release(); } /// <summary> /// Increment reference count of operation handle. /// </summary> /// <param name="handle">The handle to the resource to increment the reference count for.</param> public void Acquire(AsyncOperationHandle handle) { handle.Acquire(); } private GroupOperation AcquireGroupOpFromCache(IOperationCacheKey key) { IAsyncOperation opGeneric; if (m_AssetOperationCache.TryGetValue(key, out opGeneric)) { opGeneric.IncrementReferenceCount(); return (GroupOperation)opGeneric; } return null; } /// <summary> /// Create a group operation for a set of locations. /// </summary> /// <typeparam name="T">The expected object type for the operations.</typeparam> /// <param name="locations">The list of locations to load.</param> /// <returns>The operation for the entire group.</returns> public AsyncOperationHandle<IList<AsyncOperationHandle>> CreateGroupOperation<T>(IList<IResourceLocation> locations) { var op = CreateOperation<GroupOperation>(typeof(GroupOperation), s_GroupOperationTypeHash, null, m_ReleaseOpNonCached); var ops = new List<AsyncOperationHandle>(locations.Count); foreach (var loc in locations) ops.Add(ProvideResource<T>(loc)); op.Init(ops); return StartOperation(op, default); } /// <summary> /// Create a group operation for a set of locations. /// </summary> /// <typeparam name="T">The expected object type for the operations.</typeparam> /// <param name="locations">The list of locations to load.</param> /// <param name="allowFailedDependencies">The operation succeeds if any grouped locations fail.</param> /// <returns>The operation for the entire group.</returns> internal AsyncOperationHandle<IList<AsyncOperationHandle>> CreateGroupOperation<T>(IList<IResourceLocation> locations, bool allowFailedDependencies) { var op = CreateOperation<GroupOperation>(typeof(GroupOperation), s_GroupOperationTypeHash, null, m_ReleaseOpNonCached); var ops = new List<AsyncOperationHandle>(locations.Count); foreach (var loc in locations) ops.Add(ProvideResource<T>(loc)); GroupOperation.GroupOperationSettings settings = GroupOperation.GroupOperationSettings.None; if (allowFailedDependencies) settings |= GroupOperation.GroupOperationSettings.AllowFailedDependencies; op.Init(ops, settings); return StartOperation(op, default); } /// <summary> /// Create a group operation for a set of AsyncOperationHandles /// </summary> /// <param name="operations">The list of operations that need to complete.</param> /// <param name="releasedCachedOpOnComplete">Determine if the cached operation should be released or not.</param> /// <returns>The operation for the entire group</returns> public AsyncOperationHandle<IList<AsyncOperationHandle>> CreateGenericGroupOperation(List<AsyncOperationHandle> operations, bool releasedCachedOpOnComplete = false) { var op = CreateOperation<GroupOperation>(typeof(GroupOperation), s_GroupOperationTypeHash, new AsyncOpHandlesCacheKey(operations), releasedCachedOpOnComplete ? m_ReleaseOpCached : m_ReleaseOpNonCached); op.Init(operations); return StartOperation(op, default); } internal AsyncOperationHandle<IList<AsyncOperationHandle>> ProvideResourceGroupCached( IList<IResourceLocation> locations, int groupHash, Type desiredType, Action<AsyncOperationHandle> callback, bool releaseDependenciesOnFailure = true) { var depsKey = new DependenciesCacheKey(locations, groupHash); GroupOperation op = AcquireGroupOpFromCache(depsKey); AsyncOperationHandle<IList<AsyncOperationHandle>> handle; if (op == null) { op = CreateOperation<GroupOperation>(typeof(GroupOperation), s_GroupOperationTypeHash, depsKey, m_ReleaseOpCached); var ops = new List<AsyncOperationHandle>(locations.Count); foreach (var loc in locations) ops.Add(ProvideResource(loc, desiredType, releaseDependenciesOnFailure)); op.Init(ops, releaseDependenciesOnFailure); handle = StartOperation(op, default(AsyncOperationHandle)); } else { handle = op.Handle; } if (callback != null) { var depOps = op.GetDependentOps(); for (int i = 0; i < depOps.Count; i++) { depOps[i].Completed += callback; } } return handle; } /// <summary> /// Asynchronously load all objects in the given collection of <paramref name="locations"/>. /// If any matching location fails, all loads and dependencies will be released. The returned .Result will be null, and .Status will be Failed. /// </summary> /// <returns>An async operation that will complete when all individual async load operations are complete.</returns> /// <param name="locations">locations to load.</param> /// <param name="callback">This callback will be invoked once for each object that is loaded.</param> /// <typeparam name="TObject">Object type to load.</typeparam> public AsyncOperationHandle<IList<TObject>> ProvideResources<TObject>(IList<IResourceLocation> locations, Action<TObject> callback = null) { return ProvideResources(locations, true, callback); } /// <summary> /// Asynchronously load all objects in the given collection of <paramref name="locations"/>. /// </summary> /// <returns>An async operation that will complete when all individual async load operations are complete.</returns> /// <param name="locations">locations to load.</param> /// <param name="releaseDependenciesOnFailure"> /// If all matching locations succeed, this parameter is ignored. /// When true, if any matching location fails, all loads and dependencies will be released. The returned .Result will be null, and .Status will be Failed. /// When false, if any matching location fails, the returned .Result will be an IList of size equal to the number of locations attempted. Any failed location will /// correlate to a null in the IList, while successful loads will correlate to a TObject in the list. The .Status will still be Failed. /// When true, op does not need to be released if anything fails, when false, it must always be released. /// </param> /// <param name="callback">This callback will be invoked once for each object that is loaded.</param> /// <typeparam name="TObject">Object type to load.</typeparam> public AsyncOperationHandle<IList<TObject>> ProvideResources<TObject>(IList<IResourceLocation> locations, bool releaseDependenciesOnFailure, Action<TObject> callback = null) { if (locations == null) return CreateCompletedOperation<IList<TObject>>(null, "Null Location"); Action<AsyncOperationHandle> callbackGeneric = null; if (callback != null) { callbackGeneric = (x) => callback((TObject)(x.Result)); } var typelessHandle = ProvideResourceGroupCached(locations, CalculateLocationsHash(locations, typeof(TObject)), typeof(TObject), callbackGeneric, releaseDependenciesOnFailure); var chainOp = CreateChainOperation<IList<TObject>>(typelessHandle, (resultHandle) => { AsyncOperationHandle<IList<AsyncOperationHandle>> handleToHandles = resultHandle.Convert<IList<AsyncOperationHandle>>(); var list = new List<TObject>(); Exception exception = null; if (handleToHandles.Status == AsyncOperationStatus.Succeeded) { foreach (var r in handleToHandles.Result) list.Add(r.Convert<TObject>().Result); } else { bool foundSuccess = false; if (!releaseDependenciesOnFailure) { foreach (AsyncOperationHandle handle in handleToHandles.Result) { if (handle.Status == AsyncOperationStatus.Succeeded) { list.Add(handle.Convert<TObject>().Result); foundSuccess = true; } else list.Add(default(TObject)); } } if (!foundSuccess) { list = null; exception = new ResourceManagerException("ProvideResources failed", handleToHandles.OperationException); } else { exception = new ResourceManagerException("Partial success in ProvideResources. Some items failed to load. See earlier logs for more info.", handleToHandles.OperationException); } } return CreateCompletedOperationInternal<IList<TObject>>(list, exception == null, exception, releaseDependenciesOnFailure); }, releaseDependenciesOnFailure); // chain operation holds the dependency typelessHandle.Release(); return chainOp; } /// <summary> /// Create a chain operation to handle dependencies. /// </summary> /// <typeparam name="TObject">The type of operation handle to return.</typeparam> /// <typeparam name="TObjectDependency">The type of the dependency operation.</typeparam> /// <param name="dependentOp">The dependency operation.</param> /// <param name="callback">The callback method that will create the dependent operation from the dependency operation.</param> /// <returns>The operation handle.</returns> public AsyncOperationHandle<TObject> CreateChainOperation<TObject, TObjectDependency>(AsyncOperationHandle<TObjectDependency> dependentOp, Func<AsyncOperationHandle<TObjectDependency>, AsyncOperationHandle<TObject>> callback) { var op = CreateOperation<ChainOperation<TObject, TObjectDependency>>(typeof(ChainOperation<TObject, TObjectDependency>), typeof(ChainOperation<TObject, TObjectDependency>).GetHashCode(), null, null); op.Init(dependentOp, callback, true); return StartOperation(op, dependentOp); } /// <summary> /// Create a chain operation to handle dependencies. /// </summary> /// <typeparam name="TObject">The type of operation handle to return.</typeparam> /// <param name="dependentOp">The dependency operation.</param> /// <param name="callback">The callback method that will create the dependent operation from the dependency operation.</param> /// <returns>The operation handle.</returns> public AsyncOperationHandle<TObject> CreateChainOperation<TObject>(AsyncOperationHandle dependentOp, Func<AsyncOperationHandle, AsyncOperationHandle<TObject>> callback) { var cOp = new ChainOperationTypelessDepedency<TObject>(); cOp.Init(dependentOp, callback, true); return StartOperation(cOp, dependentOp); } /// <summary> /// Create a chain operation to handle dependencies. /// </summary> /// <typeparam name="TObject">The type of operation handle to return.</typeparam> /// <typeparam name="TObjectDependency">The type of the dependency operation.</typeparam> /// <param name="dependentOp">The dependency operation.</param> /// <param name="callback">The callback method that will create the dependent operation from the dependency operation.</param> /// <param name="releaseDependenciesOnFailure"> Whether to release dependencies if the created operation has failed.</param> /// <returns>The operation handle.</returns> public AsyncOperationHandle<TObject> CreateChainOperation<TObject, TObjectDependency>(AsyncOperationHandle<TObjectDependency> dependentOp, Func<AsyncOperationHandle<TObjectDependency>, AsyncOperationHandle<TObject>> callback, bool releaseDependenciesOnFailure = true) { var op = CreateOperation<ChainOperation<TObject, TObjectDependency>>(typeof(ChainOperation<TObject, TObjectDependency>), typeof(ChainOperation<TObject, TObjectDependency>).GetHashCode(), null, null); op.Init(dependentOp, callback, releaseDependenciesOnFailure); return StartOperation(op, dependentOp); } /// <summary> /// Create a chain operation to handle dependencies. /// </summary> /// <typeparam name="TObject">The type of operation handle to return.</typeparam> /// <param name="dependentOp">The dependency operation.</param> /// <param name="callback">The callback method that will create the dependent operation from the dependency operation.</param> /// <param name="releaseDependenciesOnFailure"> Whether to release dependencies if the created operation has failed.</param> /// <returns>The operation handle.</returns> public AsyncOperationHandle<TObject> CreateChainOperation<TObject>(AsyncOperationHandle dependentOp, Func<AsyncOperationHandle, AsyncOperationHandle<TObject>> callback, bool releaseDependenciesOnFailure = true) { var cOp = new ChainOperationTypelessDepedency<TObject>(); cOp.Init(dependentOp, callback, releaseDependenciesOnFailure); return StartOperation(cOp, dependentOp); } internal class InstanceOperation : AsyncOperationBase<GameObject> { AsyncOperationHandle<GameObject> m_dependency; InstantiationParameters m_instantiationParams; IInstanceProvider m_instanceProvider; GameObject m_instance; Scene m_scene; public void Init(ResourceManager rm, IInstanceProvider instanceProvider, InstantiationParameters instantiationParams, AsyncOperationHandle<GameObject> dependency) { m_RM = rm; m_dependency = dependency; m_instanceProvider = instanceProvider; m_instantiationParams = instantiationParams; m_scene = default(Scene); } internal override DownloadStatus GetDownloadStatus(HashSet<object> visited) { return m_dependency.IsValid() ? m_dependency.InternalGetDownloadStatus(visited) : new DownloadStatus() { IsDone = IsDone }; } /// <inheritdoc /> public override void GetDependencies(List<AsyncOperationHandle> deps) { deps.Add(m_dependency); } protected override string DebugName { get { if (m_instanceProvider == null) return "Instance<Invalid>"; return string.Format("Instance<{0}>({1}", m_instanceProvider.GetType().Name, m_dependency.IsValid() ? m_dependency.DebugName : "Invalid"); } } public Scene InstanceScene() => m_scene; protected override void Destroy() { m_instanceProvider.ReleaseInstance(m_RM, m_instance); } protected override float Progress { get { return m_dependency.PercentComplete; } } ///<inheritdoc /> protected override bool InvokeWaitForCompletion() { if (m_dependency.IsValid() && !m_dependency.IsDone) m_dependency.WaitForCompletion(); m_RM?.Update(Time.unscaledDeltaTime); if (m_instance == null && !HasExecuted) InvokeExecute(); return IsDone; } protected override void Execute() { Exception e = m_dependency.OperationException; if (m_dependency.Status == AsyncOperationStatus.Succeeded) { m_instance = m_instanceProvider.ProvideInstance(m_RM, m_dependency, m_instantiationParams); if (m_instance != null) m_scene = m_instance.scene; Complete(m_instance, true, null); } else { Complete(m_instance, false, string.Format("Dependency operation failed with {0}.", e)); } } } /// <summary> /// Load a scene at a specificed resource location. /// </summary> /// <param name="sceneProvider">The scene provider instance.</param> /// <param name="location">The location of the scene.</param> /// <param name="loadSceneMode">The load mode for the scene.</param> /// <param name="activateOnLoad">If false, the scene will be loaded in the background and not activated when complete.</param> /// <param name="priority">The priority for the load operation.</param> /// <returns>Async operation handle that will complete when the scene is loaded. If activateOnLoad is false, then Activate() will need to be called on the SceneInstance returned.</returns> public AsyncOperationHandle<SceneInstance> ProvideScene(ISceneProvider sceneProvider, IResourceLocation location, LoadSceneMode loadSceneMode, bool activateOnLoad, int priority) { if (sceneProvider == null) throw new NullReferenceException("sceneProvider is null"); return sceneProvider.ProvideScene(this, location, new LoadSceneParameters(loadSceneMode), activateOnLoad, priority); } /// <summary> /// Load a scene at a specificed resource location. /// </summary> /// <param name="sceneProvider">The scene provider instance.</param> /// <param name="location">The location of the scene.</param> /// <param name="loadSceneParameters">The load parameters for the scene.</param> /// <param name="activateOnLoad">If false, the scene will be loaded in the background and not activated when complete.</param> /// <param name="priority">The priority for the load operation.</param> /// <returns>Async operation handle that will complete when the scene is loaded. If activateOnLoad is false, then Activate() will need to be called on the SceneInstance returned.</returns> public AsyncOperationHandle<SceneInstance> ProvideScene(ISceneProvider sceneProvider, IResourceLocation location, LoadSceneParameters loadSceneParameters, bool activateOnLoad, int priority) { if (sceneProvider == null) throw new NullReferenceException("sceneProvider is null"); return sceneProvider.ProvideScene(this, location, loadSceneParameters, activateOnLoad, priority); } /// <summary> /// Release a scene. /// </summary> /// <param name="sceneProvider">The scene provider.</param> /// <param name="sceneLoadHandle">The operation handle used to load the scene.</param> /// <returns>An operation handle for the unload.</returns> public AsyncOperationHandle<SceneInstance> ReleaseScene(ISceneProvider sceneProvider, AsyncOperationHandle<SceneInstance> sceneLoadHandle) { if (sceneProvider == null) throw new NullReferenceException("sceneProvider is null"); // if (sceneLoadHandle.ReferenceCount == 0) // return CreateCompletedOperation<SceneInstance>(default(SceneInstance), ""); return sceneProvider.ReleaseScene(this, sceneLoadHandle); } /// <summary> /// Asynchronously instantiate a prefab (GameObject) at the specified <paramref name="location"/>. /// </summary> /// <returns>Async operation that will complete when the prefab is instantiated.</returns> /// <param name="provider">An implementation of IInstanceProvider that will be used to instantiate and destroy the GameObject.</param> /// <param name="location">Location of the prefab.</param> /// <param name="instantiateParameters">A struct containing the parameters to pass the the Instantiation call.</param> public AsyncOperationHandle<GameObject> ProvideInstance(IInstanceProvider provider, IResourceLocation location, InstantiationParameters instantiateParameters) { if (provider == null) throw new NullReferenceException("provider is null. Assign a valid IInstanceProvider object before using."); if (location == null) throw new ArgumentNullException("location"); var depOp = ProvideResource<GameObject>(location); var baseOp = CreateOperation<InstanceOperation>(typeof(InstanceOperation), s_InstanceOperationTypeHash, null, m_ReleaseInstanceOp); baseOp.Init(this, provider, instantiateParameters, depOp); m_TrackedInstanceOperations.Add(baseOp); return StartOperation<GameObject>(baseOp, depOp); } /// <summary> /// Releases all instances the given scence. /// </summary> /// <param name="scene">The scene whose instances should be released.</param> public void CleanupSceneInstances(Scene scene) { List<InstanceOperation> handlesToRelease = null; foreach (var h in m_TrackedInstanceOperations) { if (h.Result == null && scene == h.InstanceScene()) { if (handlesToRelease == null) handlesToRelease = new List<InstanceOperation>(); handlesToRelease.Add(h); } } if (handlesToRelease != null) { foreach (var h in handlesToRelease) { m_TrackedInstanceOperations.Remove(h); h.DecrementReferenceCount(); } } } private void ExecuteDeferredCallbacks() { m_InsideExecuteDeferredCallbacksMethod = true; for (int i = 0; i < m_DeferredCompleteCallbacks.Count; i++) { m_DeferredCompleteCallbacks[i].InvokeCompletionEvent(); m_DeferredCompleteCallbacks[i].DecrementReferenceCount(); } m_DeferredCompleteCallbacks.Clear(); m_InsideExecuteDeferredCallbacksMethod = false; } internal void RegisterForDeferredCallback(IAsyncOperation op, bool incrementRefCount = true) { if (CallbackHooksEnabled && m_InsideExecuteDeferredCallbacksMethod) { if (m_DeferredCallbacksToRegister == null) m_DeferredCallbacksToRegister = new List<DeferredCallbackRegisterRequest>(); m_DeferredCallbacksToRegister.Add ( new DeferredCallbackRegisterRequest() { operation = op, incrementRefCount = incrementRefCount } ); } else { if (incrementRefCount) op.IncrementReferenceCount(); m_DeferredCompleteCallbacks.Add(op); RegisterForCallbacks(); } } internal void Update(float unscaledDeltaTime) { if (m_InsideUpdateMethod) throw new Exception("Reentering the Update method is not allowed. This can happen when calling WaitForCompletion on an operation while inside of a callback."); m_InsideUpdateMethod = true; m_UpdateCallbacks.Invoke(unscaledDeltaTime); m_UpdatingReceivers = true; for (int i = 0; i < m_UpdateReceivers.Count; i++) m_UpdateReceivers[i].Update(unscaledDeltaTime); m_UpdatingReceivers = false; if (m_UpdateReceiversToRemove != null) { foreach (var r in m_UpdateReceiversToRemove) m_UpdateReceivers.Remove(r); m_UpdateReceiversToRemove = null; } if (m_DeferredCallbacksToRegister != null) { foreach (DeferredCallbackRegisterRequest callback in m_DeferredCallbacksToRegister) RegisterForDeferredCallback(callback.operation, callback.incrementRefCount); m_DeferredCallbacksToRegister = null; } ExecuteDeferredCallbacks(); m_InsideUpdateMethod = false; } /// <summary> /// Disposes internal resources used by the resource manager /// </summary> public void Dispose() { if (MonoBehaviourCallbackHooks.Exists && m_RegisteredForCallbacks) { MonoBehaviourCallbackHooks.Instance.OnUpdateDelegate -= Update; m_RegisteredForCallbacks = false; } } } }