using System; using System.Collections; using System.Collections.Generic; using UnityEngine; namespace UnityEngine.ResourceManagement.AsyncOperations { /// <summary> /// Handle for internal operations. This allows for reference counting and checking for valid references. /// </summary> /// <typeparam name="TObject">The object type of the underlying operation.</typeparam> public struct AsyncOperationHandle<TObject> : IEnumerator, IEquatable<AsyncOperationHandle<TObject>> { internal AsyncOperationBase<TObject> m_InternalOp; int m_Version; string m_LocationName; internal string LocationName { get { return m_LocationName; } set { m_LocationName = value; } } bool m_UnloadSceneOpExcludeReleaseCallback; internal bool UnloadSceneOpExcludeReleaseCallback { get { return m_UnloadSceneOpExcludeReleaseCallback; } set { m_UnloadSceneOpExcludeReleaseCallback = value; } } /// <summary> /// Conversion from typed to non typed handles. This does not increment the reference count. /// To convert from non-typed back, use AsyncOperationHandle.Convert<T>() /// </summary> /// <param name="obj">The typed handle to convert.</param> /// <returns>Returns the converted operation handle.</returns> static public implicit operator AsyncOperationHandle(AsyncOperationHandle<TObject> obj) { return new AsyncOperationHandle(obj.m_InternalOp, obj.m_Version, obj.m_LocationName); } internal AsyncOperationHandle(AsyncOperationBase<TObject> op) { m_InternalOp = op; m_Version = op?.Version ?? 0; m_LocationName = null; m_UnloadSceneOpExcludeReleaseCallback = false; } /// <summary> /// Return the current download status for this operation and its dependencies. /// </summary> /// <returns>The download status.</returns> public DownloadStatus GetDownloadStatus() { return InternalGetDownloadStatus(new HashSet<object>()); } internal DownloadStatus InternalGetDownloadStatus(HashSet<object> visited) { if (visited == null) visited = new HashSet<object>(); return visited.Add(InternalOp) ? InternalOp.GetDownloadStatus(visited) : new DownloadStatus() {IsDone = IsDone}; } internal AsyncOperationHandle(IAsyncOperation op) { m_InternalOp = (AsyncOperationBase<TObject>)op; m_Version = op?.Version ?? 0; m_LocationName = null; m_UnloadSceneOpExcludeReleaseCallback = false; } internal AsyncOperationHandle(IAsyncOperation op, int version) { m_InternalOp = (AsyncOperationBase<TObject>)op; m_Version = version; m_LocationName = null; m_UnloadSceneOpExcludeReleaseCallback = false; } internal AsyncOperationHandle(IAsyncOperation op, string locationName) { m_InternalOp = (AsyncOperationBase<TObject>)op; m_Version = op?.Version ?? 0; m_LocationName = locationName; m_UnloadSceneOpExcludeReleaseCallback = false; } internal AsyncOperationHandle(IAsyncOperation op, int version, string locationName) { m_InternalOp = (AsyncOperationBase<TObject>)op; m_Version = version; m_LocationName = locationName; m_UnloadSceneOpExcludeReleaseCallback = false; } /// <summary> /// Acquire a new handle to the internal operation. This will increment the reference count, therefore the returned handle must also be released. /// </summary> /// <returns>A new handle to the operation. This handle must also be released.</returns> internal AsyncOperationHandle<TObject> Acquire() { InternalOp.IncrementReferenceCount(); return this; } /// <summary> /// Completion event for the internal operation. If this is assigned on a completed operation, the callback is deferred until the LateUpdate of the current frame. /// </summary> public event Action<AsyncOperationHandle<TObject>> Completed { add { InternalOp.Completed += value; } remove { InternalOp.Completed -= value; } } /// <summary> /// Completion event for non-typed callback handlers. If this is assigned on a completed operation, the callback is deferred until the LateUpdate of the current frame. /// </summary> public event Action<AsyncOperationHandle> CompletedTypeless { add { InternalOp.CompletedTypeless += value; } remove { InternalOp.CompletedTypeless -= value; } } /// <summary> /// Debug name of the operation. /// </summary> public string DebugName { get { if (!IsValid()) return "InvalidHandle"; return ((IAsyncOperation)InternalOp).DebugName; } } /// <summary> /// Get dependency operations. /// </summary> /// <param name="deps">The list of AsyncOperationHandles that are dependencies of a given AsyncOperationHandle</param> public void GetDependencies(List<AsyncOperationHandle> deps) { InternalOp.GetDependencies(deps); } /// <summary> /// Event for handling the destruction of the operation. /// </summary> public event Action<AsyncOperationHandle> Destroyed { add { InternalOp.Destroyed += value; } remove { InternalOp.Destroyed -= value; } } /// <summary> /// Provide equality for this struct. /// </summary> /// <param name="other">The operation to compare to.</param> /// <returns>True if the the operation handles reference the same AsyncOperation and the version is the same.</returns> public bool Equals(AsyncOperationHandle<TObject> other) { return m_Version == other.m_Version && m_InternalOp == other.m_InternalOp; } /// <summary> /// Get hash code of this struct. /// </summary> /// <returns></returns> public override int GetHashCode() { return m_InternalOp == null ? 0 : m_InternalOp.GetHashCode() * 17 + m_Version; } /// <summary> /// Synchronously complete the async operation. /// </summary> /// <returns>The result of the operation or null.</returns> public TObject WaitForCompletion() { #if !UNITY_2021_1_OR_NEWER AsyncOperationHandle.IsWaitingForCompletion = true; try { if (IsValid() && !InternalOp.IsDone) InternalOp.WaitForCompletion(); if (IsValid()) return Result; } finally { AsyncOperationHandle.IsWaitingForCompletion = false; m_InternalOp?.m_RM?.Update(Time.unscaledDeltaTime); } #else if (IsValid() && !InternalOp.IsDone) InternalOp.WaitForCompletion(); m_InternalOp?.m_RM?.Update(Time.unscaledDeltaTime); if (IsValid()) return Result; #endif return default(TObject); } internal AsyncOperationBase<TObject> InternalOp { get { if (m_InternalOp == null || m_InternalOp.Version != m_Version) throw new Exception("Attempting to use an invalid operation handle"); return m_InternalOp; } } /// <summary> /// True if the operation is complete. /// </summary> public bool IsDone { get { return !IsValid() || InternalOp.IsDone; } } /// <summary> /// Check if the handle references an internal operation. /// </summary> /// <returns>True if valid.</returns> public bool IsValid() { return m_InternalOp != null && m_InternalOp.Version == m_Version; } /// <summary> /// The exception for a failed operation. This will be null unless Status is failed. /// </summary> public Exception OperationException { get { return InternalOp.OperationException; } } /// <summary> /// The progress of the internal operation. /// This is evenly weighted between all sub-operations. For example, a LoadAssetAsync call could potentially /// be chained with InitializeAsync and have multiple dependent operations that download and load content. /// In that scenario, PercentComplete would reflect how far the overal operation was, and would not accurately /// represent just percent downloaded or percent loaded into memory. /// For accurate download percentages, use GetDownloadStatus(). /// </summary> public float PercentComplete { get { return InternalOp.PercentComplete; } } /// <summary> /// The current reference count of the internal operation. /// </summary> internal int ReferenceCount { get { return InternalOp.ReferenceCount; } } /// <summary> /// Release the handle. If the internal operation reference count reaches 0, the resource will be released. /// </summary> internal void Release() { InternalOp.DecrementReferenceCount(); m_InternalOp = null; } /// <summary> /// The result object of the operations. /// </summary> public TObject Result { get { return InternalOp.Result; } } /// <summary> /// The status of the internal operation. /// </summary> public AsyncOperationStatus Status { get { return InternalOp.Status; } } /// <summary> /// Return a Task object to wait on when using async await. /// </summary> public System.Threading.Tasks.Task<TObject> Task { get { return InternalOp.Task; } } object IEnumerator.Current { get { return Result; } } /// <summary> /// Overload for <see cref="IEnumerator.MoveNext"/>. /// </summary> /// <returns>Returns true if the enumerator can advance to the next element in the collectin. Returns false otherwise.</returns> bool IEnumerator.MoveNext() { return !IsDone; } /// <summary> /// Overload for <see cref="IEnumerator.Reset"/>. /// </summary> void IEnumerator.Reset() { } } /// <summary> /// Non typed operation handle. This allows for reference counting and checking for valid references. /// </summary> public struct AsyncOperationHandle : IEnumerator { #if !UNITY_2021_1_OR_NEWER private static bool m_IsWaitingForCompletion = false; /// <summary> /// Indicates that the async operation is in the process of being completed synchronously. /// </summary> public static bool IsWaitingForCompletion { get { return m_IsWaitingForCompletion; } internal set { m_IsWaitingForCompletion = value; } } #endif internal IAsyncOperation m_InternalOp; int m_Version; string m_LocationName; internal string LocationName { get { return m_LocationName; } set { m_LocationName = value; } } internal AsyncOperationHandle(IAsyncOperation op) { m_InternalOp = op; m_Version = op?.Version ?? 0; m_LocationName = null; } internal AsyncOperationHandle(IAsyncOperation op, int version) { m_InternalOp = op; m_Version = version; m_LocationName = null; } internal AsyncOperationHandle(IAsyncOperation op, string locationName) { m_InternalOp = op; m_Version = op?.Version ?? 0; m_LocationName = locationName; } internal AsyncOperationHandle(IAsyncOperation op, int version, string locationName) { m_InternalOp = op; m_Version = version; m_LocationName = locationName; } /// <summary> /// Acquire a new handle to the internal operation. This will increment the reference count, therefore the returned handle must also be released. /// </summary> /// <returns>A new handle to the operation. This handle must also be released.</returns> internal AsyncOperationHandle Acquire() { InternalOp.IncrementReferenceCount(); return this; } /// <summary> /// Completion event for the internal operation. If this is assigned on a completed operation, the callback is deferred until the LateUpdate of the current frame. /// </summary> public event Action<AsyncOperationHandle> Completed { add { InternalOp.CompletedTypeless += value; } remove { InternalOp.CompletedTypeless -= value; } } /// <summary> /// Converts handle to be typed. This does not increment the reference count. /// To convert back to non-typed, implicit conversion is available. /// </summary> /// <typeparam name="T">The type of the handle.</typeparam> /// <returns>A new handle that is typed.</returns> public AsyncOperationHandle<T> Convert<T>() { return new AsyncOperationHandle<T>(InternalOp, m_Version, m_LocationName); } /// <summary> /// Provide equality for this struct. /// </summary> /// <param name="other">The operation to compare to.</param> /// <returns>True if the the operation handles reference the same AsyncOperation and the version is the same.</returns> public bool Equals(AsyncOperationHandle other) { return m_Version == other.m_Version && m_InternalOp == other.m_InternalOp; } /// <summary> /// Debug name of the operation. /// </summary> public string DebugName { get { if (!IsValid()) return "InvalidHandle"; return InternalOp.DebugName; } } /// <summary> /// Event for handling the destruction of the operation. /// </summary> public event Action<AsyncOperationHandle> Destroyed { add { InternalOp.Destroyed += value; } remove { InternalOp.Destroyed -= value; } } /// <summary> /// Get dependency operations. /// </summary> /// <param name="deps"></param> public void GetDependencies(List<AsyncOperationHandle> deps) { InternalOp.GetDependencies(deps); } /// <summary> /// Get hash code of this struct. /// </summary> /// <returns></returns> public override int GetHashCode() { return m_InternalOp == null ? 0 : m_InternalOp.GetHashCode() * 17 + m_Version; } IAsyncOperation InternalOp { get { if (m_InternalOp == null || m_InternalOp.Version != m_Version) throw new Exception("Attempting to use an invalid operation handle"); return m_InternalOp; } } /// <summary> /// True if the operation is complete. /// </summary> public bool IsDone { get { return !IsValid() || InternalOp.IsDone; } } /// <summary> /// Check if the internal operation is not null and has the same version of this handle. /// </summary> /// <returns>True if valid.</returns> public bool IsValid() { return m_InternalOp != null && m_InternalOp.Version == m_Version; } /// <summary> /// The exception for a failed operation. This will be null unless Status is failed. /// </summary> public Exception OperationException { get { return InternalOp.OperationException; } } /// <summary> /// The progress of the internal operation. /// This is evenly weighted between all sub-operations. For example, a LoadAssetAsync call could potentially /// be chained with InitializeAsync and have multiple dependent operations that download and load content. /// In that scenario, PercentComplete would reflect how far the overal operation was, and would not accurately /// represent just percent downloaded or percent loaded into memory. /// For accurate download percentages, use GetDownloadStatus(). /// </summary> public float PercentComplete { get { return InternalOp.PercentComplete; } } /// <summary> /// Return the current download status for this operation and its dependencies. In some instances, the information will not be available. This can happen if the operation /// is dependent on the initialization operation for addressables. Once the initialization operation completes, the information returned will be accurate. /// </summary> /// <returns>The download status.</returns> public DownloadStatus GetDownloadStatus() { return InternalGetDownloadStatus(new HashSet<object>()); } internal DownloadStatus InternalGetDownloadStatus(HashSet<object> visited) { if (visited == null) visited = new HashSet<object>(); return visited.Add(InternalOp) ? InternalOp.GetDownloadStatus(visited) : new DownloadStatus() {IsDone = IsDone}; } /// <summary> /// The current reference count of the internal operation. /// </summary> internal int ReferenceCount { get { return InternalOp.ReferenceCount; } } /// <summary> /// Release the handle. If the internal operation reference count reaches 0, the resource will be released. /// </summary> internal void Release() { InternalOp.DecrementReferenceCount(); m_InternalOp = null; } /// <summary> /// The result object of the operations. /// </summary> public object Result { get { return InternalOp.GetResultAsObject(); } } /// <summary> /// The status of the internal operation. /// </summary> public AsyncOperationStatus Status { get { return InternalOp.Status; } } /// <summary> /// Return a Task object to wait on when using async await. /// </summary> public System.Threading.Tasks.Task<object> Task { get { return InternalOp.Task; } } object IEnumerator.Current { get { return Result; } } /// <summary> /// Overload for <see cref="IEnumerator.MoveNext"/>. /// </summary> /// <returns>Returns true if the enumerator can advance to the next element in the collectin. Returns false otherwise.</returns> bool IEnumerator.MoveNext() { return !IsDone; } /// <summary> /// Overload for <see cref="IEnumerator.Reset"/>. /// </summary> void IEnumerator.Reset() { } /// <summary> /// Synchronously complete the async operation. /// </summary> /// <returns>The result of the operation or null.</returns> public object WaitForCompletion() { #if !UNITY_2021_1_OR_NEWER IsWaitingForCompletion = true; try { if (IsValid() && !InternalOp.IsDone) InternalOp.WaitForCompletion(); if (IsValid()) return Result; } finally { IsWaitingForCompletion = false; } #else if (IsValid() && !InternalOp.IsDone) InternalOp.WaitForCompletion(); if (IsValid()) return Result; #endif return null; } } }