using System; using System.Collections.Generic; using System.Runtime.CompilerServices; using UnityEngine.Networking; using UnityEngine.ResourceManagement.AsyncOperations; using UnityEngine.ResourceManagement.Diagnostics; using UnityEngine.ResourceManagement.Exceptions; using UnityEngine.ResourceManagement.ResourceLocations; using UnityEngine.ResourceManagement.ResourceProviders; using UnityEngine.ResourceManagement.Util; using UnityEngine.SceneManagement; [assembly: InternalsVisibleTo("Unity.ResourceManager.Tests")] [assembly: InternalsVisibleTo("Unity.Addressables.Tests")] namespace UnityEngine.ResourceManagement { /// /// Entry point for ResourceManager API /// public class ResourceManager : IDisposable { /// /// Event types that will be sent by the ResourceManager /// public enum DiagnosticEventType { EventCount, FrameRate, HeapSize, AsyncOperationFail, AsyncOperationCreate, AsyncOperationPercentComplete, AsyncOperationComplete, AsyncOperationReferenceCount, AsyncOperationDestroy, } /// /// Global exception handler. This will be called whenever an IAsyncOperation.OperationException is set to a non-null value. /// public static Action ExceptionHandler { get; set; } internal bool CallbackHooksEnabled = true; // tests might need to disable the callback hooks to manually pump updating private MonoBehaviourCallbackHooks m_CallbackHooks; ListWithEvents m_ResourceProviders = new ListWithEvents(); IAllocationStrategy m_allocator; // list of all the providers in s_ResourceProviders that implement IUpdateReceiver ListWithEvents m_UpdateReceivers = new ListWithEvents(); List m_UpdateReceiversToRemove = null; bool m_UpdatingReceivers = 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 Dictionary m_providerMap = new Dictionary(); Dictionary m_AssetOperationCache = new Dictionary(); HashSet m_TrackedInstanceOperations = new HashSet(); DelegateList m_UpdateCallbacks = DelegateList.CreateWithGlobalCache(); List m_DeferredCompleteCallbacks = new List(); 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(); } 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); } private void RegisterForCallbacks() { if (CallbackHooksEnabled && m_CallbackHooks == null) { m_CallbackHooks = new GameObject("ResourceManagerCallbacks", typeof(MonoBehaviourCallbackHooks)).GetComponent(); m_CallbackHooks.OnUpdateDelegate += Update; } } /// /// Clears out the diagnostics callback handler. /// public void ClearDiagnosticsCallback() { m_diagnosticsHandler = null; } /// /// Register a handler for diagnostic events. /// /// The event handler function. public void RegisterDiagnosticCallback(Action func) { m_diagnosticsHandler = func; } internal void PostDiagnosticEvent(AsyncOperationHandle op, ResourceManager.DiagnosticEventType type, int eventValue = 0, object context = null) { if (m_diagnosticsHandler == null) return; m_diagnosticsHandler(op, type, eventValue, context); } /// /// Gets the appropriate for the given and . /// /// The resource provider. Or null if an appropriate provider cannot be found /// The resource location. /// The desired object type to be loaded from the provider. 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. /// Object type to load. private AsyncOperationHandle ProvideResource(IResourceLocation location, Type desiredType = null) { if (location == null) throw new ArgumentNullException("location"); IResourceProvider provider = null; if (desiredType == null) { provider = GetResourceProvider(desiredType, location); if (provider == null) return CreateCompletedOperation(null, new UnknownResourceProviderException(location).Message); desiredType = provider.GetDefaultType(location); } IAsyncOperation op; int hash = location.Hash(desiredType); if (m_AssetOperationCache.TryGetValue(hash, out op)) { op.IncrementReferenceCount(); return new AsyncOperationHandle(op); } 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(), hash, m_ReleaseOpCached); // Calculate the hash of the dependencies int depHash = location.DependencyHashCode; var depOp = location.HasDependencies ? ProvideResourceGroupCached(location.Dependencies, depHash, null, null) : default(AsyncOperationHandle>); if(provider == null) provider = GetResourceProvider(desiredType, location); ((IGenericProviderOperation)op).Init(this, provider, location, depOp); var handle = StartOperation(op, depOp); if (depOp.IsValid()) depOp.Release(); return handle; } 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 { TObject m_Result; bool m_Success; string m_ErrorMsg; public CompletedOperation() { } public void Init(TObject result, bool success, string errorMsg) { m_Result = result; m_Success = success; m_ErrorMsg = errorMsg; } protected override void Execute() { Complete(m_Result, m_Success, m_ErrorMsg); } } 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 != null) RemoveOperationFromCache(cachable.Hash); } internal T CreateOperation(Type actualType, int typeHash, int operationHash, Action onDestroyAction) where T : IAsyncOperation { if (operationHash == 0) { var op = (T)Allocator.New(actualType, typeHash); op.OnDestroy = onDestroyAction; return op; } else { var op = (T)Allocator.New(actualType, typeHash); op.OnDestroy = onDestroyAction; var cachable = op as ICachable; if (cachable != null) cachable.Hash = operationHash; AddOperationToCache(operationHash, op); return op; } } internal void AddOperationToCache(int hash, IAsyncOperation operation) { if(!IsOperationCached(hash)) m_AssetOperationCache.Add(hash, operation); } internal bool RemoveOperationFromCache(int hash) { if (!IsOperationCached(hash)) return true; return m_AssetOperationCache.Remove(hash); } internal bool IsOperationCached(int hash) { return m_AssetOperationCache.ContainsKey(hash); } 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. public AsyncOperationHandle CreateCompletedOperation(TObject result, string errorMsg) { var cop = CreateOperation>(typeof(CompletedOperation), typeof(CompletedOperation).GetHashCode(), 0, null); cop.Init(result, string.IsNullOrEmpty(errorMsg), errorMsg); 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(int hash) { IAsyncOperation opGeneric; if (m_AssetOperationCache.TryGetValue(hash, out opGeneric)) { opGeneric.IncrementReferenceCount(); return (GroupOperation)opGeneric; } return null; } internal AsyncOperationHandle> ProvideResourceGroupCached(IList locations, int groupHash, Type desiredType, Action callback) { GroupOperation op = AcquireGroupOpFromCache(groupHash); AsyncOperationHandle> handle; if (op == null) { op = CreateOperation(typeof(GroupOperation), s_GroupOperationTypeHash, groupHash, m_ReleaseOpCached); var ops = new List(locations.Count); foreach (var loc in locations) ops.Add(ProvideResource(loc, desiredType)); op.Init(ops); 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 . /// /// 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) { 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); var chainOp = CreateChainOperation(typelessHandle, (x) => { if(x.Status != AsyncOperationStatus.Succeeded) return CreateCompletedOperation>(null, x.OperationException != null ? x.OperationException.Message : "ProvidResources failed"); var list = new List(); foreach(var r in x.Result) list.Add(r.Convert().Result); return CreateCompletedOperation>(list, string.Empty); }); // 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(), 0, null); op.Init(dependentOp, callback); 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); return StartOperation(cOp, dependentOp); } class InstanceOperation : AsyncOperationBase { AsyncOperationHandle m_dependency; InstantiationParameters m_instantiationParams; IInstanceProvider m_instanceProvider; GameObject m_instance; ResourceManager m_RM; 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; } protected 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 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 loadMode, bool activateOnLoad, int priority) { if (sceneProvider == null) throw new NullReferenceException("sceneProvider is null"); return sceneProvider.ProvideScene(this, location, loadMode, 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); } /// /// Asynchronouslly 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, 0, m_ReleaseInstanceOp); baseOp.Init(this, provider, instantiateParameters, depOp); m_TrackedInstanceOperations.Add(baseOp); return StartOperation(baseOp, depOp); } /// /// Cleans up ref counting on any instances that were in a newly closed scene. /// /// The scene that was closed 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() { for (int i = 0; i < m_DeferredCompleteCallbacks.Count; i++) { m_DeferredCompleteCallbacks[i].InvokeCompletionEvent(); m_DeferredCompleteCallbacks[i].DecrementReferenceCount(); } m_DeferredCompleteCallbacks.Clear(); } internal void RegisterForDeferredCallback(IAsyncOperation op, bool incrementRefCount = true) { if(incrementRefCount) op.IncrementReferenceCount(); m_DeferredCompleteCallbacks.Add(op); RegisterForCallbacks(); } internal void Update(float unscaledDeltaTime) { 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; } ExecuteDeferredCallbacks(); } /// /// Disposes internal resources used by the resource manager /// public void Dispose() { if (m_CallbackHooks != null) GameObject.Destroy(m_CallbackHooks.gameObject); m_CallbackHooks = null; } } }