using System; using System.Collections.Generic; using System.Threading.Tasks; using UnityEngine.ResourceManagement.Exceptions; using UnityEngine.ResourceManagement.Util; // ReSharper disable DelegateSubtraction namespace UnityEngine.ResourceManagement.AsyncOperations { internal interface ICachable { IOperationCacheKey Key { get; set; } } internal interface IAsyncOperation { object GetResultAsObject(); Type ResultType { get; } int Version { get; } string DebugName { get; } void DecrementReferenceCount(); void IncrementReferenceCount(); int ReferenceCount { get; } float PercentComplete { get; } DownloadStatus GetDownloadStatus(HashSet visited); AsyncOperationStatus Status { get; } Exception OperationException { get; } bool IsDone { get; } Action OnDestroy { set; } void GetDependencies(List deps); bool IsRunning { get; } event Action CompletedTypeless; event Action Destroyed; void InvokeCompletionEvent(); System.Threading.Tasks.Task Task { get; } void Start(ResourceManager rm, AsyncOperationHandle dependency, DelegateList updateCallbacks); AsyncOperationHandle Handle { get; } void WaitForCompletion(); } /// /// base class for implemented AsyncOperations, implements the needed interfaces and consolidates redundant code /// /// The type of the operation. public abstract class AsyncOperationBase : IAsyncOperation { /// /// This will be called by the resource manager after all dependent operation complete. This method should not be called manually. /// A custom operation should override this method and begin work when it is called. /// protected abstract void Execute(); /// /// This will be called by the resource manager when the reference count of the operation reaches zero. This method should not be called manually. /// A custom operation should override this method and release any held resources /// protected virtual void Destroy() { } /// /// A custom operation should override this method to return the progress of the operation. /// /// Progress of the operation. Value should be between 0.0f and 1.0f protected virtual float Progress { get { return 0; } } /// /// A custom operation should override this method to provide a debug friendly name for the operation. /// protected virtual string DebugName { get { return this.ToString(); } } /// /// A custom operation should override this method to provide a list of AsyncOperationHandles that it depends on. /// /// The list that should be populated with dependent AsyncOperationHandles. public virtual void GetDependencies(List dependencies) { } /// /// Accessor to Result of the operation. /// public TObject Result { get; set; } int m_referenceCount = 1; internal AsyncOperationStatus m_Status; internal Exception m_Error; internal ResourceManager m_RM; internal int m_Version; internal int Version { get { return m_Version; } } DelegateList m_DestroyedAction; DelegateList> m_CompletedActionT; internal bool CompletedEventHasListeners => m_CompletedActionT != null && m_CompletedActionT.Count > 0; internal bool DestroyedEventHasListeners => m_DestroyedAction != null && m_DestroyedAction.Count > 0; Action m_OnDestroyAction; internal Action OnDestroy { set { m_OnDestroyAction = value; } } Action m_dependencyCompleteAction; /// /// True, If the operation has been executed, else false /// protected internal bool HasExecuted = false; internal event Action Executed; /// /// The number of references that are using this operation. /// When the ReferenceCount reaches 0, this operation is Destroyed. /// protected internal int ReferenceCount { get { return m_referenceCount; } } /// /// True if the current op has begun but hasn't yet reached completion. False otherwise. /// public bool IsRunning { get; internal set; } /// /// Basic constructor for AsyncOperationBase. /// protected AsyncOperationBase() { m_UpdateCallback = UpdateCallback; m_dependencyCompleteAction = o => InvokeExecute(); } internal static string ShortenPath(string p, bool keepExtension) { var slashIndex = p.LastIndexOf('/'); if (slashIndex > 0) p = p.Substring(slashIndex + 1); if (!keepExtension) { slashIndex = p.LastIndexOf('.'); if (slashIndex > 0) p = p.Substring(0, slashIndex); } return p; } /// /// Synchronously complete the async operation. /// public void WaitForCompletion() { if (PlatformUtilities.PlatformUsesMultiThreading(Application.platform)) while (!InvokeWaitForCompletion()) { } else throw new Exception($"{Application.platform} does not support synchronous Addressable loading. Please do not use WaitForCompletion on the {Application.platform} platform."); } /// /// Used for the implementation of WaitForCompletion in an IAsyncOperation. /// /// True if the operation has completed, otherwise false. protected virtual bool InvokeWaitForCompletion() { return true; } /// /// Increments the reference count for this operation. /// /// Thrown if the operation has already been destroyed after reaching 0 reference count. protected internal void IncrementReferenceCount() { if (m_referenceCount == 0) throw new Exception(string.Format("Cannot increment reference count on operation {0} because it has already been destroyed", this)); m_referenceCount++; if (m_RM != null && m_RM.postProfilerEvents) m_RM.PostDiagnosticEvent(new ResourceManager.DiagnosticEventContext(new AsyncOperationHandle(this), ResourceManager.DiagnosticEventType.AsyncOperationReferenceCount, m_referenceCount)); } /// /// Reduces the reference count for this operation by 1. If the reference count is reduced to 0, the operation is destroyed. /// /// Thrown if the operation has already been destroyed after reaching 0 reference count. protected internal void DecrementReferenceCount() { if (m_referenceCount <= 0) throw new Exception(string.Format("Cannot decrement reference count for operation {0} because it is already 0", this)); m_referenceCount--; if (m_RM != null && m_RM.postProfilerEvents) m_RM.PostDiagnosticEvent(new ResourceManager.DiagnosticEventContext(new AsyncOperationHandle(this), ResourceManager.DiagnosticEventType.AsyncOperationReferenceCount, m_referenceCount)); if (m_referenceCount == 0) { if (m_RM != null && m_RM.postProfilerEvents) m_RM.PostDiagnosticEvent(new ResourceManager.DiagnosticEventContext(new AsyncOperationHandle(this), ResourceManager.DiagnosticEventType.AsyncOperationDestroy)); if (m_DestroyedAction != null) { m_DestroyedAction.Invoke(new AsyncOperationHandle(this)); m_DestroyedAction.Clear(); } Destroy(); Result = default(TObject); m_referenceCount = 1; m_Status = AsyncOperationStatus.None; m_taskCompletionSource = null; m_taskCompletionSourceTypeless = null; m_Error = null; m_Version++; m_RM = null; if (m_OnDestroyAction != null) { m_OnDestroyAction(this); m_OnDestroyAction = null; } } } TaskCompletionSource m_taskCompletionSource; internal Task Task { get { if (m_taskCompletionSource == null) { m_taskCompletionSource = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); if (IsDone && !CompletedEventHasListeners) m_taskCompletionSource.SetResult(Result); } return m_taskCompletionSource.Task; } } TaskCompletionSource m_taskCompletionSourceTypeless; Task IAsyncOperation.Task { get { if (m_taskCompletionSourceTypeless == null) { m_taskCompletionSourceTypeless = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); if (IsDone && !CompletedEventHasListeners) m_taskCompletionSourceTypeless.SetResult(Result); } return m_taskCompletionSourceTypeless.Task; } } /// /// Converts the information about the operation to a formatted string. /// /// Returns the information about the operation. public override string ToString() { var instId = ""; var or = Result as Object; if (or != null) instId = "(" + or.GetInstanceID() + ")"; return string.Format("{0}, result='{1}', status='{2}'", base.ToString(), (or + instId), m_Status); } bool m_InDeferredCallbackQueue; void RegisterForDeferredCallbackEvent(bool incrementReferenceCount = true) { if (IsDone && !m_InDeferredCallbackQueue) { m_InDeferredCallbackQueue = true; m_RM.RegisterForDeferredCallback(this, incrementReferenceCount); } } internal event Action> Completed { add { if (m_CompletedActionT == null) m_CompletedActionT = DelegateList>.CreateWithGlobalCache(); m_CompletedActionT.Add(value); RegisterForDeferredCallbackEvent(); } remove { m_CompletedActionT?.Remove(value); } } internal event Action Destroyed { add { if (m_DestroyedAction == null) m_DestroyedAction = DelegateList.CreateWithGlobalCache(); m_DestroyedAction.Add(value); } remove { m_DestroyedAction?.Remove(value); } } internal event Action CompletedTypeless { add { Completed += s => value(s); } remove { Completed -= s => value(s); } } /// internal AsyncOperationStatus Status { get { return m_Status; } } /// internal Exception OperationException { get { return m_Error; } private set { m_Error = value; if (m_Error != null && ResourceManager.ExceptionHandler != null) ResourceManager.ExceptionHandler(new AsyncOperationHandle(this), value); } } internal bool MoveNext() { return !IsDone; } internal void Reset() { } internal object Current { get { return null; } } // should throw exception? internal bool IsDone { get { return Status == AsyncOperationStatus.Failed || Status == AsyncOperationStatus.Succeeded; } } internal float PercentComplete { get { if (m_Status == AsyncOperationStatus.None) { try { return Progress; } catch { return 0.0f; } } return 1.0f; } } internal void InvokeCompletionEvent() { if (m_CompletedActionT != null) { m_CompletedActionT.Invoke(new AsyncOperationHandle(this)); m_CompletedActionT.Clear(); } if (m_taskCompletionSource != null) m_taskCompletionSource.TrySetResult(Result); if (m_taskCompletionSourceTypeless != null) m_taskCompletionSourceTypeless.TrySetResult(Result); m_InDeferredCallbackQueue = false; } internal AsyncOperationHandle Handle { get { return new AsyncOperationHandle(this); } } DelegateList m_UpdateCallbacks; Action m_UpdateCallback; private void UpdateCallback(float unscaledDeltaTime) { IUpdateReceiver updateOp = this as IUpdateReceiver; updateOp.Update(unscaledDeltaTime); } /// /// Complete the operation and invoke events. /// /// /// An operation is considered to have failed silently if success is true and if errorMsg isn't null or empty. /// The exception handler will be called in cases of silent failures. /// Any failed operations will call Release on any dependencies that succeeded. /// /// The result object for the operation. /// True if successful or if the operation failed silently. /// The error message if the operation has failed. public void Complete(TObject result, bool success, string errorMsg) { Complete(result, success, errorMsg, true); } /// /// Complete the operation and invoke events. /// /// /// An operation is considered to have failed silently if success is true and if errorMsg isn't null or empty. /// The exception handler will be called in cases of silent failures. /// /// The result object for the operation. /// True if successful or if the operation failed silently. /// The error message if the operation has failed. /// When true, failed operations will release any dependencies that succeeded. public void Complete(TObject result, bool success, string errorMsg, bool releaseDependenciesOnFailure) { Complete(result, success, !string.IsNullOrEmpty(errorMsg) ? new OperationException(errorMsg) : null, releaseDependenciesOnFailure); } /// /// Complete the operation and invoke events. /// /// /// An operation is considered to have failed silently if success is true and if exception isn't null. /// The exception handler will be called in cases of silent failures. /// /// The result object for the operation. /// True if successful or if the operation failed silently. /// The exception if the operation has failed. /// When true, failed operations will release any dependencies that succeeded. public void Complete(TObject result, bool success, Exception exception, bool releaseDependenciesOnFailure = true) { if (IsDone) return; IUpdateReceiver upOp = this as IUpdateReceiver; if (m_UpdateCallbacks != null && upOp != null) m_UpdateCallbacks.Remove(m_UpdateCallback); Result = result; m_Status = success ? AsyncOperationStatus.Succeeded : AsyncOperationStatus.Failed; if (m_RM != null && m_RM.postProfilerEvents) { m_RM.PostDiagnosticEvent(new ResourceManager.DiagnosticEventContext(new AsyncOperationHandle(this), ResourceManager.DiagnosticEventType.AsyncOperationPercentComplete, 1)); m_RM.PostDiagnosticEvent(new ResourceManager.DiagnosticEventContext(new AsyncOperationHandle(this), ResourceManager.DiagnosticEventType.AsyncOperationComplete)); } if (m_Status == AsyncOperationStatus.Failed || exception != null) { if (exception == null || string.IsNullOrEmpty(exception.Message)) OperationException = new OperationException($"Unknown error in AsyncOperation : {DebugName}"); else OperationException = exception; } if (m_Status == AsyncOperationStatus.Failed) { if (releaseDependenciesOnFailure) ReleaseDependencies(); if (m_RM != null && m_RM.postProfilerEvents) m_RM.PostDiagnosticEvent(new ResourceManager.DiagnosticEventContext(new AsyncOperationHandle(this), ResourceManager.DiagnosticEventType.AsyncOperationFail, 0, exception?.ToString())); ICachable cachedOperation = this as ICachable; if (cachedOperation?.Key != null) m_RM?.RemoveOperationFromCache(cachedOperation.Key); RegisterForDeferredCallbackEvent(false); } else { InvokeCompletionEvent(); DecrementReferenceCount(); } IsRunning = false; } internal void Start(ResourceManager rm, AsyncOperationHandle dependency, DelegateList updateCallbacks) { m_RM = rm; IsRunning = true; HasExecuted = false; if (m_RM != null && m_RM.postProfilerEvents) { m_RM.PostDiagnosticEvent(new ResourceManager.DiagnosticEventContext(new AsyncOperationHandle(this), ResourceManager.DiagnosticEventType.AsyncOperationCreate)); m_RM.PostDiagnosticEvent(new ResourceManager.DiagnosticEventContext(new AsyncOperationHandle(this), ResourceManager.DiagnosticEventType.AsyncOperationPercentComplete, 0)); } IncrementReferenceCount(); // keep a reference until the operation completes m_UpdateCallbacks = updateCallbacks; if (dependency.IsValid() && !dependency.IsDone) dependency.Completed += m_dependencyCompleteAction; else InvokeExecute(); } internal void InvokeExecute() { Execute(); HasExecuted = true; IUpdateReceiver upOp = this as IUpdateReceiver; if (upOp != null && !IsDone) m_UpdateCallbacks.Add(m_UpdateCallback); Executed?.Invoke(); } event Action IAsyncOperation.CompletedTypeless { add { CompletedTypeless += value; } remove { CompletedTypeless -= value; } } event Action IAsyncOperation.Destroyed { add { Destroyed += value; } remove { Destroyed -= value; } } int IAsyncOperation.Version => Version; int IAsyncOperation.ReferenceCount => ReferenceCount; float IAsyncOperation.PercentComplete => PercentComplete; AsyncOperationStatus IAsyncOperation.Status => Status; Exception IAsyncOperation.OperationException => OperationException; bool IAsyncOperation.IsDone => IsDone; AsyncOperationHandle IAsyncOperation.Handle => Handle; Action IAsyncOperation.OnDestroy { set { OnDestroy = value; } } string IAsyncOperation.DebugName => DebugName; /// object IAsyncOperation.GetResultAsObject() => Result; Type IAsyncOperation.ResultType { get { return typeof(TObject); } } /// void IAsyncOperation.GetDependencies(List deps) => GetDependencies(deps); /// void IAsyncOperation.DecrementReferenceCount() => DecrementReferenceCount(); /// void IAsyncOperation.IncrementReferenceCount() => IncrementReferenceCount(); /// void IAsyncOperation.InvokeCompletionEvent() => InvokeCompletionEvent(); /// void IAsyncOperation.Start(ResourceManager rm, AsyncOperationHandle dependency, DelegateList updateCallbacks) => Start(rm, dependency, updateCallbacks); internal virtual void ReleaseDependencies() { } /// DownloadStatus IAsyncOperation.GetDownloadStatus(HashSet visited) => GetDownloadStatus(visited); internal virtual DownloadStatus GetDownloadStatus(HashSet visited) { visited.Add(this); return new DownloadStatus() {IsDone = IsDone}; } } }