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 { /// /// Entry point for ResourceManager API /// public class ResourceManager : IDisposable { /// /// Options for event types that will be sent by the ResourceManager /// public enum DiagnosticEventType { /// /// Use to indicate that an operation failed. /// AsyncOperationFail, /// /// Use to indicate that an operation was created. /// AsyncOperationCreate, /// /// Use to indicate the percentage of completion for an operation was updated. /// AsyncOperationPercentComplete, /// /// Use to indicate that an operation has completed. /// AsyncOperationComplete, /// /// Use to indicate that the reference count of an operation was modified. /// AsyncOperationReferenceCount, /// /// Use to indicate that an operation was destroyed. /// AsyncOperationDestroy, } internal bool postProfilerEvents = false; /// /// Container for information associated with a Diagnostics event. /// public struct DiagnosticEventContext { /// /// Operation handle for the event. /// public AsyncOperationHandle OperationHandle { get; } /// /// The type of diagnostic event. /// public DiagnosticEventType Type { get; } /// /// The value for this event. /// public int EventValue { get; } /// /// The IResourceLocation being provided by the operation triggering this event. /// This value is null if the event is not while providing a resource. /// public IResourceLocation Location { get; } /// /// Addition data included with this event. /// public object Context { get; } /// /// Any error that occured. /// public string Error { get; } /// /// Construct a new DiagnosticEventContext. /// /// Operation handle for the event. /// The type of diagnostic event. /// The value for this event. /// Any error that occured. /// Additional context data. 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; } } /// /// Global exception handler. This will be called whenever an IAsyncOperation.OperationException is set to a non-null value. /// /// /// /// public static Action ExceptionHandler { get; set; } /// /// Functor to transform internal ids before being used by the providers. /// /// /// 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 object will set the value on the . /// /// 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. /// /// /// /// A function taking an [IResourceLocation](xref:UnityEngine.ResourceManagement.ResourceLocations.IResourceLocation) and returning a transformed string location. /// Transforming resource URLs public Func InternalIdTransformFunc { get; set; } /// /// Checks for an internal id transform function and uses it to modify the internal id value. /// /// The location to transform the internal id of. /// If a transform func is set, use it to pull the local id; otherwise, the InternalId property of the location is used. public string TransformInternalId(IResourceLocation location) { return InternalIdTransformFunc == null ? location.InternalId : InternalIdTransformFunc(location); } /// /// Delegate that can be used to override the web request options before being sent. /// /// /// You can assign a function to the object's WebRequestOverride property to individually modify the 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 object will set the value on the . /// /// For example you could add an Authorization header to authenticate with Cloud Content Delivery's private buckets. /// /// /// Transforming resource URLs public Action WebRequestOverride { get; set; } internal bool CallbackHooksEnabled = true; // tests might need to disable the callback hooks to manually pump updating ListWithEvents m_ResourceProviders = new ListWithEvents(); IAllocationStrategy m_allocator; // list of all the providers in s_ResourceProviders that implement IUpdateReceiver internal ListWithEvents m_UpdateReceivers = new ListWithEvents(); List 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 m_providerMap = new Dictionary(); Dictionary m_AssetOperationCache = new Dictionary(); HashSet m_TrackedInstanceOperations = new HashSet(); internal DelegateList m_UpdateCallbacks = DelegateList.CreateWithGlobalCache(); List m_DeferredCompleteCallbacks = new List(); HashSet m_AsestBundleProviders = new HashSet(); bool m_InsideExecuteDeferredCallbacksMethod = false; List m_DeferredCallbacksToRegister = null; private struct DeferredCallbackRegisterRequest { internal IAsyncOperation operation; internal bool incrementRefCount; } Action m_obsoleteDiagnosticsHandler; // For use in working with Obsolete RegisterDiagnosticCallback method. Action m_diagnosticsHandler; Action m_ReleaseOpNonCached; Action m_ReleaseOpCached; Action m_ReleaseInstanceOp; static int s_GroupOperationTypeHash = typeof(GroupOperation).GetHashCode(); static int s_InstanceOperationTypeHash = typeof(InstanceOperation).GetHashCode(); /// /// Add an update reveiver. /// /// The object to add. The Update method will be called until the object is removed. public void AddUpdateReceiver(IUpdateReceiver receiver) { if (receiver == null) return; m_UpdateReceivers.Add(receiver); } /// /// Remove update receiver. /// /// The object to remove. public void RemoveUpdateReciever(IUpdateReceiver receiver) { if (receiver == null) return; if (m_UpdatingReceivers) { if (m_UpdateReceiversToRemove == null) m_UpdateReceiversToRemove = new List(); m_UpdateReceiversToRemove.Add(receiver); } else { m_UpdateReceivers.Remove(receiver); } } /// /// The allocation strategy object. /// public IAllocationStrategy Allocator { get { return m_allocator; } set { m_allocator = value; } } /// /// Gets the list of configured objects. Resource Providers handle load and release operations for objects. /// /// The resource providers list. public IList ResourceProviders { get { return m_ResourceProviders; } } /// /// The CertificateHandler instance object. /// public CertificateHandler CertificateHandlerInstance { get; set; } /// /// Constructor for the resource manager. /// /// The allocation strategy to use. 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; } } /// /// Clears out the diagnostics callback handler. /// [Obsolete("ClearDiagnosticsCallback is Obsolete, use ClearDiagnosticCallbacks instead.")] public void ClearDiagnosticsCallback() { m_diagnosticsHandler = null; m_obsoleteDiagnosticsHandler = null; } /// /// Clears out the diagnostics callbacks handler. /// public void ClearDiagnosticCallbacks() { m_diagnosticsHandler = null; m_obsoleteDiagnosticsHandler = null; } /// /// Unregister a handler for diagnostic events. /// /// The event handler function. public void UnregisterDiagnosticCallback(Action func) { if (m_diagnosticsHandler != null) m_diagnosticsHandler -= func; else Debug.LogError("No Diagnostic callbacks registered, cannot remove callback."); } /// /// Register a handler for diagnostic events. /// /// The event handler function. [Obsolete] public void RegisterDiagnosticCallback(Action func) { m_obsoleteDiagnosticsHandler = func; } /// /// Register a handler for diagnostic events. /// /// The event handler function. public void RegisterDiagnosticCallback(Action 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); } /// /// Gets the appropriate for the given and . /// /// The resource provider. Or null if an appropriate provider cannot be found /// The desired object type to be loaded from the provider. /// The resource location. 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 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; } /// /// Load the at the specified . /// /// An async operation. /// Location to load. /// When true, if the operation fails, dependencies will be released. /// Object type to load. 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(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(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>); ((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; } /// /// Creates the appropriate cache key for a given location + provider. AssetBundleProviders need to use a different /// cache key than regular locations. /// /// The ResourceProvider for the given location /// The location of the asset we're attempting to request /// The desired type of the asset we're attempting to request. /// An IOperationCacheKey for use with the async operation cache. 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 m_ProviderOperationTypeCache = new Dictionary(); /// /// Load the at the specified . /// /// An async operation. /// Location to load. /// Object type to load. public AsyncOperationHandle ProvideResource(IResourceLocation location) { AsyncOperationHandle handle = ProvideResource(location, typeof(TObject)); return handle.Convert(); } /// /// Registers an operation with the ResourceManager. The operation will be executed when the completes. /// This should only be used when creating custom operations. /// /// The AsyncOperationHandle used to access the result and status of the operation. /// The custom AsyncOperationBase object /// Execution of the operation will not occur until this handle completes. A default handle can be passed if no dependency is required. /// Object type associated with this operation. public AsyncOperationHandle StartOperation(AsyncOperationBase 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 : AsyncOperationBase { 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"; } } /// 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(Type actualType, int typeHash, IOperationCacheKey cacheKey, Action 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; } /// /// Creates an operation that has already completed with a specified result and error message./>. /// /// The result that the operation will provide. /// The error message if the operation should be in the failed state. Otherwise null or empty string. /// Object type. /// The operation handle used for the completed operation. public AsyncOperationHandle CreateCompletedOperation(TObject result, string errorMsg) { var success = string.IsNullOrEmpty(errorMsg); return CreateCompletedOperationInternal(result, success, !success ? new Exception(errorMsg) : null); } /// /// Creates an operation that has already completed with a specified result and error message./>. /// /// The result that the operation will provide. /// The exception with an error message if the operation should be in the failed state. Otherwise null. /// Object type. /// The operation handle used for the completed operation. public AsyncOperationHandle CreateCompletedOperationWithException(TObject result, Exception exception) { return CreateCompletedOperationInternal(result, exception == null, exception); } internal AsyncOperationHandle CreateCompletedOperationInternal(TObject result, bool success, Exception exception, bool releaseDependenciesOnFailure = true) { var cop = CreateOperation>(typeof(CompletedOperation), typeof(CompletedOperation).GetHashCode(), null, m_ReleaseOpNonCached); cop.Init(result, success, exception, releaseDependenciesOnFailure); return StartOperation(cop, default(AsyncOperationHandle)); } /// /// Release the operation associated with the specified handle /// /// The handle to release. public void Release(AsyncOperationHandle handle) { handle.Release(); } /// /// Increment reference count of operation handle. /// /// The handle to the resource to increment the reference count for. 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; } /// /// Create a group operation for a set of locations. /// /// The expected object type for the operations. /// The list of locations to load. /// The operation for the entire group. public AsyncOperationHandle> CreateGroupOperation(IList locations) { var op = CreateOperation(typeof(GroupOperation), s_GroupOperationTypeHash, null, m_ReleaseOpNonCached); var ops = new List(locations.Count); foreach (var loc in locations) ops.Add(ProvideResource(loc)); op.Init(ops); return StartOperation(op, default); } /// /// Create a group operation for a set of locations. /// /// The expected object type for the operations. /// The list of locations to load. /// The operation succeeds if any grouped locations fail. /// The operation for the entire group. internal AsyncOperationHandle> CreateGroupOperation(IList locations, bool allowFailedDependencies) { var op = CreateOperation(typeof(GroupOperation), s_GroupOperationTypeHash, null, m_ReleaseOpNonCached); var ops = new List(locations.Count); foreach (var loc in locations) ops.Add(ProvideResource(loc)); GroupOperation.GroupOperationSettings settings = GroupOperation.GroupOperationSettings.None; if (allowFailedDependencies) settings |= GroupOperation.GroupOperationSettings.AllowFailedDependencies; op.Init(ops, settings); return StartOperation(op, default); } /// /// Create a group operation for a set of AsyncOperationHandles /// /// The list of operations that need to complete. /// Determine if the cached operation should be released or not. /// The operation for the entire group public AsyncOperationHandle> CreateGenericGroupOperation(List operations, bool releasedCachedOpOnComplete = false) { var op = CreateOperation(typeof(GroupOperation), s_GroupOperationTypeHash, new AsyncOpHandlesCacheKey(operations), releasedCachedOpOnComplete ? m_ReleaseOpCached : m_ReleaseOpNonCached); op.Init(operations); return StartOperation(op, default); } internal AsyncOperationHandle> ProvideResourceGroupCached( IList locations, int groupHash, Type desiredType, Action callback, bool releaseDependenciesOnFailure = true) { var depsKey = new DependenciesCacheKey(locations, groupHash); GroupOperation op = AcquireGroupOpFromCache(depsKey); AsyncOperationHandle> handle; if (op == null) { op = CreateOperation(typeof(GroupOperation), s_GroupOperationTypeHash, depsKey, m_ReleaseOpCached); var ops = new List(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; } /// /// Asynchronously load all objects in the given collection of . /// If any matching location fails, all loads and dependencies will be released. The returned .Result will be null, and .Status will be Failed. /// /// An async operation that will complete when all individual async load operations are complete. /// locations to load. /// This callback will be invoked once for each object that is loaded. /// Object type to load. public AsyncOperationHandle> ProvideResources(IList locations, Action callback = null) { return ProvideResources(locations, true, callback); } /// /// Asynchronously load all objects in the given collection of . /// /// An async operation that will complete when all individual async load operations are complete. /// locations to load. /// /// 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. /// /// This callback will be invoked once for each object that is loaded. /// Object type to load. public AsyncOperationHandle> ProvideResources(IList locations, bool releaseDependenciesOnFailure, Action callback = null) { if (locations == null) return CreateCompletedOperation>(null, "Null Location"); Action 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>(typelessHandle, (resultHandle) => { AsyncOperationHandle> handleToHandles = resultHandle.Convert>(); var list = new List(); Exception exception = null; if (handleToHandles.Status == AsyncOperationStatus.Succeeded) { foreach (var r in handleToHandles.Result) list.Add(r.Convert().Result); } else { bool foundSuccess = false; if (!releaseDependenciesOnFailure) { foreach (AsyncOperationHandle handle in handleToHandles.Result) { if (handle.Status == AsyncOperationStatus.Succeeded) { list.Add(handle.Convert().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>(list, exception == null, exception, releaseDependenciesOnFailure); }, releaseDependenciesOnFailure); // chain operation holds the dependency typelessHandle.Release(); return chainOp; } /// /// Create a chain operation to handle dependencies. /// /// The type of operation handle to return. /// The type of the dependency operation. /// The dependency operation. /// The callback method that will create the dependent operation from the dependency operation. /// The operation handle. public AsyncOperationHandle CreateChainOperation(AsyncOperationHandle dependentOp, Func, AsyncOperationHandle> callback) { var op = CreateOperation>(typeof(ChainOperation), typeof(ChainOperation).GetHashCode(), null, null); op.Init(dependentOp, callback, true); return StartOperation(op, dependentOp); } /// /// Create a chain operation to handle dependencies. /// /// The type of operation handle to return. /// The dependency operation. /// The callback method that will create the dependent operation from the dependency operation. /// The operation handle. public AsyncOperationHandle CreateChainOperation(AsyncOperationHandle dependentOp, Func> callback) { var cOp = new ChainOperationTypelessDepedency(); cOp.Init(dependentOp, callback, true); return StartOperation(cOp, dependentOp); } /// /// Create a chain operation to handle dependencies. /// /// The type of operation handle to return. /// The type of the dependency operation. /// The dependency operation. /// The callback method that will create the dependent operation from the dependency operation. /// Whether to release dependencies if the created operation has failed. /// The operation handle. public AsyncOperationHandle CreateChainOperation(AsyncOperationHandle dependentOp, Func, AsyncOperationHandle> callback, bool releaseDependenciesOnFailure = true) { var op = CreateOperation>(typeof(ChainOperation), typeof(ChainOperation).GetHashCode(), null, null); op.Init(dependentOp, callback, releaseDependenciesOnFailure); return StartOperation(op, dependentOp); } /// /// Create a chain operation to handle dependencies. /// /// The type of operation handle to return. /// The dependency operation. /// The callback method that will create the dependent operation from the dependency operation. /// Whether to release dependencies if the created operation has failed. /// The operation handle. public AsyncOperationHandle CreateChainOperation(AsyncOperationHandle dependentOp, Func> callback, bool releaseDependenciesOnFailure = true) { var cOp = new ChainOperationTypelessDepedency(); cOp.Init(dependentOp, callback, releaseDependenciesOnFailure); return StartOperation(cOp, dependentOp); } internal class InstanceOperation : AsyncOperationBase { AsyncOperationHandle m_dependency; InstantiationParameters m_instantiationParams; IInstanceProvider m_instanceProvider; GameObject m_instance; Scene m_scene; public void Init(ResourceManager rm, IInstanceProvider instanceProvider, InstantiationParameters instantiationParams, AsyncOperationHandle dependency) { m_RM = rm; m_dependency = dependency; m_instanceProvider = instanceProvider; m_instantiationParams = instantiationParams; m_scene = default(Scene); } internal override DownloadStatus GetDownloadStatus(HashSet visited) { return m_dependency.IsValid() ? m_dependency.InternalGetDownloadStatus(visited) : new DownloadStatus() { IsDone = IsDone }; } /// public override void GetDependencies(List deps) { deps.Add(m_dependency); } protected override string DebugName { get { if (m_instanceProvider == null) return "Instance"; 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; } } /// 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)); } } } /// /// Load a scene at a specificed resource location. /// /// The scene provider instance. /// The location of the scene. /// The load mode for the scene. /// If false, the scene will be loaded in the background and not activated when complete. /// The priority for the load operation. /// 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. public AsyncOperationHandle 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); } /// /// Load a scene at a specificed resource location. /// /// The scene provider instance. /// The location of the scene. /// The load parameters for the scene. /// If false, the scene will be loaded in the background and not activated when complete. /// The priority for the load operation. /// 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. public AsyncOperationHandle 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); } /// /// Release a scene. /// /// The scene provider. /// The operation handle used to load the scene. /// An operation handle for the unload. public AsyncOperationHandle ReleaseScene(ISceneProvider sceneProvider, AsyncOperationHandle sceneLoadHandle) { if (sceneProvider == null) throw new NullReferenceException("sceneProvider is null"); // if (sceneLoadHandle.ReferenceCount == 0) // return CreateCompletedOperation(default(SceneInstance), ""); return sceneProvider.ReleaseScene(this, sceneLoadHandle); } /// /// Asynchronously instantiate a prefab (GameObject) at the specified . /// /// Async operation that will complete when the prefab is instantiated. /// An implementation of IInstanceProvider that will be used to instantiate and destroy the GameObject. /// Location of the prefab. /// A struct containing the parameters to pass the the Instantiation call. public AsyncOperationHandle 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(location); var baseOp = CreateOperation(typeof(InstanceOperation), s_InstanceOperationTypeHash, null, m_ReleaseInstanceOp); baseOp.Init(this, provider, instantiateParameters, depOp); m_TrackedInstanceOperations.Add(baseOp); return StartOperation(baseOp, depOp); } /// /// Releases all instances the given scence. /// /// The scene whose instances should be released. public void CleanupSceneInstances(Scene scene) { List handlesToRelease = null; foreach (var h in m_TrackedInstanceOperations) { if (h.Result == null && scene == h.InstanceScene()) { if (handlesToRelease == null) handlesToRelease = new List(); 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(); 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; } /// /// Disposes internal resources used by the resource manager /// public void Dispose() { if (MonoBehaviourCallbackHooks.Exists && m_RegisteredForCallbacks) { MonoBehaviourCallbackHooks.Instance.OnUpdateDelegate -= Update; m_RegisteredForCallbacks = false; } } } }