using System; using System.Collections.Generic; using System.Threading.Tasks; using UnityEngine.ResourceManagement.ResourceLocations; using UnityEngine.ResourceManagement.Util; // ReSharper disable DelegateSubtraction namespace UnityEngine.ResourceManagement.AsyncOperations { internal interface ICachable { int Hash { 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; } AsyncOperationStatus Status { get; } Exception OperationException { get; } bool IsDone { get; } Action OnDestroy { set; } void GetDependencies(List deps); 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; } } /// /// base class for implemented AsyncOperations, implements the needed interfaces and consolidates redundant code /// 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. protected virtual void GetDependencies(List dependencies) { } internal TObject Result { get { return m_Result; } } int m_referenceCount = 1; AsyncOperationStatus m_Status; Exception m_Error; ResourceManager m_RM; TObject m_Result; private int m_Version; internal int Version { get { return m_Version; } } DelegateList m_CompletedAction; DelegateList m_DestroyedAction; DelegateList> m_CompletedActionT; Action m_OnDestroyAction; internal Action OnDestroy { set { m_OnDestroyAction = value; } } internal int ReferenceCount { get { return m_referenceCount; } } Action m_dependencyCompleteAction; 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; } 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++; m_RM.PostDiagnosticEvent(new AsyncOperationHandle(this), ResourceManager.DiagnosticEventType.AsyncOperationReferenceCount, m_referenceCount); } 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--; m_RM.PostDiagnosticEvent(new AsyncOperationHandle(this), ResourceManager.DiagnosticEventType.AsyncOperationReferenceCount, m_referenceCount); if (m_referenceCount == 0) { m_RM.PostDiagnosticEvent(new AsyncOperationHandle(this), ResourceManager.DiagnosticEventType.AsyncOperationDestroy); Destroy(); if (m_DestroyedAction != null) { m_DestroyedAction.Invoke(new AsyncOperationHandle(this)); m_DestroyedAction.Clear(); } if (m_OnDestroyAction != null) { m_OnDestroyAction(this); m_OnDestroyAction = null; } m_Result = default(TObject); m_referenceCount = 1; m_Status = AsyncOperationStatus.None; m_Error = null; m_Version++; m_RM = null; } } System.Threading.EventWaitHandle m_waitHandle; internal System.Threading.WaitHandle WaitHandle { get { if (m_waitHandle == null) m_waitHandle = new System.Threading.EventWaitHandle(false, System.Threading.EventResetMode.ManualReset); m_waitHandle.Reset(); return m_waitHandle; } } internal System.Threading.Tasks.Task Task { get { if (Status == AsyncOperationStatus.Failed) { return System.Threading.Tasks.Task.FromResult(default(TObject)); } if (Status == AsyncOperationStatus.Succeeded) { return System.Threading.Tasks.Task.FromResult(Result); } var handle = WaitHandle; return System.Threading.Tasks.Task.Factory.StartNew(o => { var asyncOperation = o as AsyncOperationBase; if (asyncOperation == null) return default(TObject); handle.WaitOne(); return asyncOperation.Result; }, this); } } System.Threading.Tasks.Task IAsyncOperation.Task { get { return Task as System.Threading.Tasks.Task; } } /// public override string ToString() { var instId = ""; var or = m_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 { if (m_CompletedAction == null) m_CompletedAction = DelegateList.CreateWithGlobalCache(); m_CompletedAction.Add(value); RegisterForDeferredCallbackEvent(); } remove { m_CompletedAction.Remove(value); } } /// 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_CompletedAction != null) { m_CompletedAction.Invoke(new AsyncOperationHandle(this)); m_CompletedAction.Clear(); } if (m_CompletedActionT != null) { m_CompletedActionT.Invoke(new AsyncOperationHandle(this)); m_CompletedActionT.Clear(); } if (m_waitHandle != null) m_waitHandle.Set(); m_InDeferredCallbackQueue = false; } internal AsyncOperationHandle Handle { get { return new AsyncOperationHandle(this); } } DelegateList m_UpdateCallbacks; Action m_UpdateCallback; //bool m_IsExecuting; private void UpdateCallback(float unscaledDeltaTime) { IUpdateReceiver updateOp = this as IUpdateReceiver; updateOp.Update(unscaledDeltaTime); } /// /// Complete the operation and invoke events. /// /// The result object for the operation. /// True if successful. /// The error message if the operation has failed. public void Complete(TObject result, bool success, string errorMsg) { IUpdateReceiver upOp = this as IUpdateReceiver; if (m_UpdateCallbacks != null && upOp != null) m_UpdateCallbacks.Remove(m_UpdateCallback); m_Result = result; m_Status = success ? AsyncOperationStatus.Succeeded : AsyncOperationStatus.Failed; m_RM.PostDiagnosticEvent(new AsyncOperationHandle(this), ResourceManager.DiagnosticEventType.AsyncOperationPercentComplete, 1); m_RM.PostDiagnosticEvent(new AsyncOperationHandle(this), ResourceManager.DiagnosticEventType.AsyncOperationComplete); if (m_Status == AsyncOperationStatus.Failed) { if (string.IsNullOrEmpty(errorMsg)) errorMsg = "Unknown error in AsyncOperation"; m_RM.PostDiagnosticEvent(new AsyncOperationHandle(this), ResourceManager.DiagnosticEventType.AsyncOperationFail, 0, errorMsg); OperationException = new Exception(errorMsg); ICachable cachedOperation = this as ICachable; if (cachedOperation != null) m_RM.RemoveOperationFromCache(cachedOperation.Hash); RegisterForDeferredCallbackEvent(false); } else { InvokeCompletionEvent(); DecrementReferenceCount(); } } internal void Start(ResourceManager rm, AsyncOperationHandle dependency, DelegateList updateCallbacks) { m_RM = rm; m_RM.PostDiagnosticEvent(new AsyncOperationHandle(this), ResourceManager.DiagnosticEventType.AsyncOperationCreate); m_RM.PostDiagnosticEvent(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(); } private void InvokeExecute() { // m_IsExecuting = true; Execute(); IUpdateReceiver upOp = this as IUpdateReceiver; if (upOp != null) m_UpdateCallbacks.Add(m_UpdateCallback); // m_IsExecuting = false; } event Action IAsyncOperation.CompletedTypeless { add { CompletedTypeless += value; } remove { CompletedTypeless -= value; } } event Action IAsyncOperation.Destroyed { add { Destroyed += value; } remove { Destroyed -= value; } } int IAsyncOperation.Version { get { return Version; } } int IAsyncOperation.ReferenceCount { get { return ReferenceCount; } } float IAsyncOperation.PercentComplete { get { return PercentComplete; } } AsyncOperationStatus IAsyncOperation.Status { get { return Status; } } Exception IAsyncOperation.OperationException { get { return OperationException; } } bool IAsyncOperation.IsDone { get { return IsDone; } } AsyncOperationHandle IAsyncOperation.Handle { get { return Handle; } } Action IAsyncOperation.OnDestroy { set { OnDestroy = value; } } string IAsyncOperation.DebugName { get { return DebugName; } } object IAsyncOperation.GetResultAsObject() { return m_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); } } }