WuhuIslandTesting/Library/PackageCache/com.unity.addressables@1.21.12/Runtime/ResourceManager/AsyncOperations/AsyncOperationBase.cs

617 lines
23 KiB
C#
Raw Normal View History

2025-01-07 02:06:59 +01:00
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<object> visited);
AsyncOperationStatus Status { get; }
Exception OperationException { get; }
bool IsDone { get; }
Action<IAsyncOperation> OnDestroy { set; }
void GetDependencies(List<AsyncOperationHandle> deps);
bool IsRunning { get; }
event Action<AsyncOperationHandle> CompletedTypeless;
event Action<AsyncOperationHandle> Destroyed;
void InvokeCompletionEvent();
System.Threading.Tasks.Task<object> Task { get; }
void Start(ResourceManager rm, AsyncOperationHandle dependency, DelegateList<float> updateCallbacks);
AsyncOperationHandle Handle { get; }
void WaitForCompletion();
}
/// <summary>
/// base class for implemented AsyncOperations, implements the needed interfaces and consolidates redundant code
/// </summary>
/// <typeparam name="TObject">The type of the operation.</typeparam>
public abstract class AsyncOperationBase<TObject> : IAsyncOperation
{
/// <summary>
/// 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.
/// </summary>
protected abstract void Execute();
/// <summary>
/// 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
/// </summary>
protected virtual void Destroy()
{
}
/// <summary>
/// A custom operation should override this method to return the progress of the operation.
/// </summary>
/// <returns>Progress of the operation. Value should be between 0.0f and 1.0f</returns>
protected virtual float Progress
{
get { return 0; }
}
/// <summary>
/// A custom operation should override this method to provide a debug friendly name for the operation.
/// </summary>
protected virtual string DebugName
{
get { return this.ToString(); }
}
/// <summary>
/// A custom operation should override this method to provide a list of AsyncOperationHandles that it depends on.
/// </summary>
/// <param name="dependencies">The list that should be populated with dependent AsyncOperationHandles.</param>
public virtual void GetDependencies(List<AsyncOperationHandle> dependencies)
{
}
/// <summary>
/// Accessor to Result of the operation.
/// </summary>
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<AsyncOperationHandle> m_DestroyedAction;
DelegateList<AsyncOperationHandle<TObject>> m_CompletedActionT;
internal bool CompletedEventHasListeners => m_CompletedActionT != null && m_CompletedActionT.Count > 0;
internal bool DestroyedEventHasListeners => m_DestroyedAction != null && m_DestroyedAction.Count > 0;
Action<IAsyncOperation> m_OnDestroyAction;
internal Action<IAsyncOperation> OnDestroy
{
set { m_OnDestroyAction = value; }
}
Action<AsyncOperationHandle> m_dependencyCompleteAction;
/// <summary>
/// True, If the operation has been executed, else false
/// </summary>
protected internal bool HasExecuted = false;
internal event Action Executed;
/// <summary>
/// The number of references that are using this operation.
/// When the ReferenceCount reaches 0, this operation is Destroyed.
/// </summary>
protected internal int ReferenceCount
{
get { return m_referenceCount; }
}
/// <summary>
/// True if the current op has begun but hasn't yet reached completion. False otherwise.
/// </summary>
public bool IsRunning { get; internal set; }
/// <summary>
/// Basic constructor for AsyncOperationBase.
/// </summary>
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;
}
/// <summary>
/// Synchronously complete the async operation.
/// </summary>
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.");
}
/// <summary>
/// Used for the implementation of WaitForCompletion in an IAsyncOperation.
/// </summary>
/// <returns>True if the operation has completed, otherwise false.</returns>
protected virtual bool InvokeWaitForCompletion()
{
return true;
}
/// <summary>
/// Increments the reference count for this operation.
/// </summary>
/// <exception cref="Exception">Thrown if the operation has already been destroyed after reaching 0 reference count.</exception>
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));
}
/// <summary>
/// Reduces the reference count for this operation by 1. If the reference count is reduced to 0, the operation is destroyed.
/// </summary>
/// <exception cref="Exception">Thrown if the operation has already been destroyed after reaching 0 reference count.</exception>
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<TObject>(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<TObject> m_taskCompletionSource;
internal Task<TObject> Task
{
get
{
if (m_taskCompletionSource == null)
{
m_taskCompletionSource = new TaskCompletionSource<TObject>(TaskCreationOptions.RunContinuationsAsynchronously);
if (IsDone && !CompletedEventHasListeners)
m_taskCompletionSource.SetResult(Result);
}
return m_taskCompletionSource.Task;
}
}
TaskCompletionSource<object> m_taskCompletionSourceTypeless;
Task<object> IAsyncOperation.Task
{
get
{
if (m_taskCompletionSourceTypeless == null)
{
m_taskCompletionSourceTypeless = new TaskCompletionSource<object>(TaskCreationOptions.RunContinuationsAsynchronously);
if (IsDone && !CompletedEventHasListeners)
m_taskCompletionSourceTypeless.SetResult(Result);
}
return m_taskCompletionSourceTypeless.Task;
}
}
/// <summary>
/// Converts the information about the operation to a formatted string.
/// </summary>
/// <returns>Returns the information about the operation.</returns>
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<AsyncOperationHandle<TObject>> Completed
{
add
{
if (m_CompletedActionT == null)
m_CompletedActionT = DelegateList<AsyncOperationHandle<TObject>>.CreateWithGlobalCache();
m_CompletedActionT.Add(value);
RegisterForDeferredCallbackEvent();
}
remove { m_CompletedActionT?.Remove(value); }
}
internal event Action<AsyncOperationHandle> Destroyed
{
add
{
if (m_DestroyedAction == null)
m_DestroyedAction = DelegateList<AsyncOperationHandle>.CreateWithGlobalCache();
m_DestroyedAction.Add(value);
}
remove { m_DestroyedAction?.Remove(value); }
}
internal event Action<AsyncOperationHandle> CompletedTypeless
{
add { Completed += s => value(s); }
remove { Completed -= s => value(s); }
}
/// <inheritdoc />
internal AsyncOperationStatus Status
{
get { return m_Status; }
}
/// <inheritdoc />
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<TObject>(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<TObject> Handle
{
get { return new AsyncOperationHandle<TObject>(this); }
}
DelegateList<float> m_UpdateCallbacks;
Action<float> m_UpdateCallback;
private void UpdateCallback(float unscaledDeltaTime)
{
IUpdateReceiver updateOp = this as IUpdateReceiver;
updateOp.Update(unscaledDeltaTime);
}
/// <summary>
/// Complete the operation and invoke events.
/// </summary>
/// <remarks>
/// 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.
/// </remarks>
/// <param name="result">The result object for the operation.</param>
/// <param name="success">True if successful or if the operation failed silently.</param>
/// <param name="errorMsg">The error message if the operation has failed.</param>
public void Complete(TObject result, bool success, string errorMsg)
{
Complete(result, success, errorMsg, true);
}
/// <summary>
/// Complete the operation and invoke events.
/// </summary>
/// <remarks>
/// 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.
/// </remarks>
/// <param name="result">The result object for the operation.</param>
/// <param name="success">True if successful or if the operation failed silently.</param>
/// <param name="errorMsg">The error message if the operation has failed.</param>
/// <param name="releaseDependenciesOnFailure">When true, failed operations will release any dependencies that succeeded.</param>
public void Complete(TObject result, bool success, string errorMsg, bool releaseDependenciesOnFailure)
{
Complete(result, success, !string.IsNullOrEmpty(errorMsg) ? new OperationException(errorMsg) : null, releaseDependenciesOnFailure);
}
/// <summary>
/// Complete the operation and invoke events.
/// </summary>
/// <remarks>
/// 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.
/// </remarks>
/// <param name="result">The result object for the operation.</param>
/// <param name="success">True if successful or if the operation failed silently.</param>
/// <param name="exception">The exception if the operation has failed.</param>
/// <param name="releaseDependenciesOnFailure">When true, failed operations will release any dependencies that succeeded.</param>
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<float> 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<AsyncOperationHandle> IAsyncOperation.CompletedTypeless
{
add { CompletedTypeless += value; }
remove { CompletedTypeless -= value; }
}
event Action<AsyncOperationHandle> 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> IAsyncOperation.OnDestroy
{
set { OnDestroy = value; }
}
string IAsyncOperation.DebugName => DebugName;
/// <inheritdoc/>
object IAsyncOperation.GetResultAsObject() => Result;
Type IAsyncOperation.ResultType
{
get { return typeof(TObject); }
}
/// <inheritdoc/>
void IAsyncOperation.GetDependencies(List<AsyncOperationHandle> deps) => GetDependencies(deps);
/// <inheritdoc/>
void IAsyncOperation.DecrementReferenceCount() => DecrementReferenceCount();
/// <inheritdoc/>
void IAsyncOperation.IncrementReferenceCount() => IncrementReferenceCount();
/// <inheritdoc/>
void IAsyncOperation.InvokeCompletionEvent() => InvokeCompletionEvent();
/// <inheritdoc/>
void IAsyncOperation.Start(ResourceManager rm, AsyncOperationHandle dependency, DelegateList<float> updateCallbacks) => Start(rm, dependency, updateCallbacks);
internal virtual void ReleaseDependencies()
{
}
/// <inheritdoc/>
DownloadStatus IAsyncOperation.GetDownloadStatus(HashSet<object> visited) => GetDownloadStatus(visited);
internal virtual DownloadStatus GetDownloadStatus(HashSet<object> visited)
{
visited.Add(this);
return new DownloadStatus() {IsDone = IsDone};
}
}
}