initial commit

This commit is contained in:
Jo 2025-01-07 02:06:59 +01:00
parent 6715289efe
commit 788c3389af
37645 changed files with 2526849 additions and 80 deletions

View file

@ -0,0 +1,616 @@
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};
}
}
}

View file

@ -0,0 +1,13 @@
fileFormatVersion: 2
guid: f9fa130dc1a674f38a215c4f52e2b420
timeCreated: 1503083765
licenseType: Pro
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,609 @@
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&lt;T&gt;()
/// </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;
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: c3b0971a8e5dab44a959bfe453348846
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,28 @@
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine.ResourceManagement.ResourceLocations;
namespace UnityEngine.ResourceManagement.AsyncOperations
{
/// <summary>
/// Options for IAsyncOperations status values
/// </summary>
public enum AsyncOperationStatus
{
/// <summary>
/// Use to indicate that the operation is still in progress.
/// </summary>
None,
/// <summary>
/// Use to indicate that the operation succeeded.
/// </summary>
Succeeded,
/// <summary>
/// Use to indicate that the operation failed.
/// </summary>
Failed
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: f2b227c56ee439f46b08efa638b27a84
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,258 @@
using System;
using System.Collections.Generic;
using UnityEngine.ResourceManagement.AsyncOperations;
using UnityEngine.ResourceManagement.Exceptions;
using UnityEngine.ResourceManagement.Util;
namespace UnityEngine.ResourceManagement
{
class ChainOperation<TObject, TObjectDependency> : AsyncOperationBase<TObject>
{
AsyncOperationHandle<TObjectDependency> m_DepOp;
AsyncOperationHandle<TObject> m_WrappedOp;
DownloadStatus m_depStatus = default;
DownloadStatus m_wrapStatus = default;
Func<AsyncOperationHandle<TObjectDependency>, AsyncOperationHandle<TObject>> m_Callback;
Action<AsyncOperationHandle<TObject>> m_CachedOnWrappedCompleted;
bool m_ReleaseDependenciesOnFailure = true;
public ChainOperation()
{
m_CachedOnWrappedCompleted = OnWrappedCompleted;
}
protected override string DebugName
{
get { return $"ChainOperation<{typeof(TObject).Name},{typeof(TObjectDependency).Name}> - {m_DepOp.DebugName}"; }
}
/// <inheritdoc />
public override void GetDependencies(List<AsyncOperationHandle> deps)
{
if (m_DepOp.IsValid())
deps.Add(m_DepOp);
}
public void Init(AsyncOperationHandle<TObjectDependency> dependentOp, Func<AsyncOperationHandle<TObjectDependency>, AsyncOperationHandle<TObject>> callback, bool releaseDependenciesOnFailure)
{
m_DepOp = dependentOp;
m_DepOp.Acquire();
m_Callback = callback;
m_ReleaseDependenciesOnFailure = releaseDependenciesOnFailure;
RefreshDownloadStatus();
}
///<inheritdoc />
protected override bool InvokeWaitForCompletion()
{
if (IsDone)
return true;
if (!m_DepOp.IsDone)
m_DepOp.WaitForCompletion();
m_RM?.Update(Time.unscaledDeltaTime);
if (!HasExecuted)
InvokeExecute();
if (!m_WrappedOp.IsValid())
return m_WrappedOp.IsDone;
m_WrappedOp.WaitForCompletion();
return m_WrappedOp.IsDone;
}
protected override void Execute()
{
m_WrappedOp = m_Callback(m_DepOp);
m_WrappedOp.Completed += m_CachedOnWrappedCompleted;
m_Callback = null;
}
private void OnWrappedCompleted(AsyncOperationHandle<TObject> x)
{
OperationException ex = null;
if (x.Status == AsyncOperationStatus.Failed)
ex = new OperationException($"ChainOperation failed because dependent operation failed", x.OperationException);
Complete(m_WrappedOp.Result, x.Status == AsyncOperationStatus.Succeeded, ex, m_ReleaseDependenciesOnFailure);
}
protected override void Destroy()
{
if (m_WrappedOp.IsValid())
m_WrappedOp.Release();
if (m_DepOp.IsValid())
m_DepOp.Release();
}
internal override void ReleaseDependencies()
{
if (m_DepOp.IsValid())
m_DepOp.Release();
}
internal override DownloadStatus GetDownloadStatus(HashSet<object> visited)
{
RefreshDownloadStatus(visited);
return new DownloadStatus() {DownloadedBytes = m_depStatus.DownloadedBytes + m_wrapStatus.DownloadedBytes, TotalBytes = m_depStatus.TotalBytes + m_wrapStatus.TotalBytes, IsDone = IsDone};
}
void RefreshDownloadStatus(HashSet<object> visited = default)
{
m_depStatus = m_DepOp.IsValid() ? m_DepOp.InternalGetDownloadStatus(visited) : m_depStatus;
m_wrapStatus = m_WrappedOp.IsValid() ? m_WrappedOp.InternalGetDownloadStatus(visited) : m_wrapStatus;
}
protected override float Progress
{
get
{
DownloadStatus downloadStatus = GetDownloadStatus(new HashSet<object>());
if (!downloadStatus.IsDone && downloadStatus.DownloadedBytes == 0)
return 0.0f;
float total = 0f;
int numberOfOps = 2;
if (m_DepOp.IsValid())
total += m_DepOp.PercentComplete;
else
total++;
if (m_WrappedOp.IsValid())
total += m_WrappedOp.PercentComplete;
else
total++;
return total / numberOfOps;
}
}
}
class ChainOperationTypelessDepedency<TObject> : AsyncOperationBase<TObject>
{
AsyncOperationHandle m_DepOp;
AsyncOperationHandle<TObject> m_WrappedOp;
DownloadStatus m_depStatus = default;
DownloadStatus m_wrapStatus = default;
Func<AsyncOperationHandle, AsyncOperationHandle<TObject>> m_Callback;
Action<AsyncOperationHandle<TObject>> m_CachedOnWrappedCompleted;
bool m_ReleaseDependenciesOnFailure = true;
internal AsyncOperationHandle<TObject> WrappedOp => m_WrappedOp;
public ChainOperationTypelessDepedency()
{
m_CachedOnWrappedCompleted = OnWrappedCompleted;
}
protected override string DebugName
{
get { return $"ChainOperation<{typeof(TObject).Name}> - {m_DepOp.DebugName}"; }
}
/// <inheritdoc />
public override void GetDependencies(List<AsyncOperationHandle> deps)
{
if (m_DepOp.IsValid())
deps.Add(m_DepOp);
}
public void Init(AsyncOperationHandle dependentOp, Func<AsyncOperationHandle, AsyncOperationHandle<TObject>> callback, bool releaseDependenciesOnFailure)
{
m_DepOp = dependentOp;
m_DepOp.Acquire();
m_Callback = callback;
m_ReleaseDependenciesOnFailure = releaseDependenciesOnFailure;
RefreshDownloadStatus();
}
///<inheritdoc />
protected override bool InvokeWaitForCompletion()
{
if (IsDone)
return true;
if (!m_DepOp.IsDone)
m_DepOp.WaitForCompletion();
m_RM?.Update(Time.unscaledDeltaTime);
if (!HasExecuted)
InvokeExecute();
if (!m_WrappedOp.IsValid())
return m_WrappedOp.IsDone;
Result = m_WrappedOp.WaitForCompletion();
return true;
}
protected override void Execute()
{
m_WrappedOp = m_Callback(m_DepOp);
m_WrappedOp.Completed += m_CachedOnWrappedCompleted;
m_Callback = null;
}
private void OnWrappedCompleted(AsyncOperationHandle<TObject> x)
{
OperationException ex = null;
if (x.Status == AsyncOperationStatus.Failed)
ex = new OperationException($"ChainOperation failed because dependent operation failed", x.OperationException);
Complete(m_WrappedOp.Result, x.Status == AsyncOperationStatus.Succeeded, ex, m_ReleaseDependenciesOnFailure);
}
protected override void Destroy()
{
if (m_WrappedOp.IsValid())
m_WrappedOp.Release();
if (m_DepOp.IsValid())
m_DepOp.Release();
}
internal override void ReleaseDependencies()
{
if (m_DepOp.IsValid())
m_DepOp.Release();
}
internal override DownloadStatus GetDownloadStatus(HashSet<object> visited)
{
RefreshDownloadStatus(visited);
return new DownloadStatus() {DownloadedBytes = m_depStatus.DownloadedBytes + m_wrapStatus.DownloadedBytes, TotalBytes = m_depStatus.TotalBytes + m_wrapStatus.TotalBytes, IsDone = IsDone};
}
void RefreshDownloadStatus(HashSet<object> visited = default)
{
m_depStatus = m_DepOp.IsValid() ? m_DepOp.InternalGetDownloadStatus(visited) : m_depStatus;
m_wrapStatus = m_WrappedOp.IsValid() ? m_WrappedOp.InternalGetDownloadStatus(visited) : m_wrapStatus;
}
protected override float Progress
{
get
{
DownloadStatus downloadStatus = GetDownloadStatus(new HashSet<object>());
if (!downloadStatus.IsDone && downloadStatus.DownloadedBytes == 0)
return 0.0f;
float total = 0f;
int numberOfOps = 2;
if (m_DepOp.IsValid())
total += m_DepOp.PercentComplete;
else
total++;
if (m_WrappedOp.IsValid())
total += m_WrappedOp.PercentComplete;
else
total++;
return total / numberOfOps;
}
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 4d148220e8d54054e851e5c220feea5f
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,33 @@
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine.ResourceManagement.ResourceLocations;
namespace UnityEngine.ResourceManagement.AsyncOperations
{
/// <summary>
/// Contains download information for async operations.
/// </summary>
public struct DownloadStatus
{
/// <summary>
/// The number of bytes downloaded by the operation and all of its dependencies.
/// </summary>
public long TotalBytes;
/// <summary>
/// The total number of bytes needed to download by the operation and dependencies.
/// </summary>
public long DownloadedBytes;
/// <summary>
/// Is the operation completed. This is used to determine if the computed Percent should be 0 or 1 when TotalBytes is 0.
/// </summary>
public bool IsDone;
/// <summary>
/// Returns the computed percent complete as a float value between 0 &amp; 1. If TotalBytes == 0, 1 is returned.
/// </summary>
public float Percent => (TotalBytes > 0) ? ((float)DownloadedBytes / (float)TotalBytes) : (IsDone ? 1.0f : 0f);
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: faf32c22a012a554c8e6e71e64844c23
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,231 @@
using System;
using System.Collections.Generic;
using System.Security;
using UnityEngine.ResourceManagement.Exceptions;
using UnityEngine.ResourceManagement.ResourceLocations;
using UnityEngine.ResourceManagement.ResourceProviders;
using UnityEngine.ResourceManagement.Util;
namespace UnityEngine.ResourceManagement.AsyncOperations
{
class GroupOperation : AsyncOperationBase<IList<AsyncOperationHandle>>, ICachable
{
[Flags]
public enum GroupOperationSettings
{
None = 0,
ReleaseDependenciesOnFailure = 1,
AllowFailedDependencies = 2
}
Action<AsyncOperationHandle> m_InternalOnComplete;
int m_LoadedCount;
GroupOperationSettings m_Settings;
string debugName = null;
private const int k_MaxDisplayedLocationLength = 45;
public GroupOperation()
{
m_InternalOnComplete = OnOperationCompleted;
Result = new List<AsyncOperationHandle>();
}
///<inheritdoc />
protected override bool InvokeWaitForCompletion()
{
//If Result is null then we've auto released and need to return
if (IsDone || Result == null)
return true;
foreach (var r in Result)
{
r.WaitForCompletion();
if (Result == null)
return true;
}
m_RM?.Update(Time.unscaledDeltaTime);
if (!IsDone && Result != null)
Execute();
m_RM?.Update(Time.unscaledDeltaTime);
return IsDone;
}
IOperationCacheKey ICachable.Key { get; set; }
internal IList<AsyncOperationHandle> GetDependentOps()
{
return Result;
}
/// <inheritdoc />
public override void GetDependencies(List<AsyncOperationHandle> deps)
{
deps.AddRange(Result);
}
internal override void ReleaseDependencies()
{
for (int i = 0; i < Result.Count; i++)
if (Result[i].IsValid())
Result[i].Release();
Result.Clear();
}
internal override DownloadStatus GetDownloadStatus(HashSet<object> visited)
{
var status = new DownloadStatus() {IsDone = IsDone};
for (int i = 0; i < Result.Count; i++)
{
if (Result[i].IsValid())
{
var depStatus = Result[i].InternalGetDownloadStatus(visited);
status.DownloadedBytes += depStatus.DownloadedBytes;
status.TotalBytes += depStatus.TotalBytes;
}
}
return status;
}
HashSet<string> m_CachedDependencyLocations = new HashSet<string>();
private bool DependenciesAreUnchanged(List<AsyncOperationHandle> deps)
{
if (m_CachedDependencyLocations.Count != deps.Count) return false;
foreach (var d in deps)
if (!m_CachedDependencyLocations.Contains(d.LocationName))
return false;
return true;
}
protected override string DebugName
{
get
{
List<AsyncOperationHandle> deps = new List<AsyncOperationHandle>();
GetDependencies(deps);
if (deps.Count == 0)
return "Dependencies";
//Only recalculate DebugName if a name hasn't been generated for currently held dependencies
if (debugName != null && DependenciesAreUnchanged(deps))
return debugName;
m_CachedDependencyLocations.Clear();
string toBeDisplayed = "Dependencies [";
for (var i = 0; i < deps.Count; i++)
{
var d = deps[i];
var locationString = d.LocationName;
m_CachedDependencyLocations.Add(locationString);
if (locationString == null)
continue;
//Prevent location display from being excessively long
if (locationString.Length > k_MaxDisplayedLocationLength)
{
locationString = AsyncOperationBase<object>.ShortenPath(locationString, true);
locationString = locationString.Substring(0, Math.Min(k_MaxDisplayedLocationLength, locationString.Length)) + "...";
}
if (i == deps.Count - 1)
toBeDisplayed += locationString;
else
toBeDisplayed += locationString + ", ";
}
toBeDisplayed += "]";
debugName = toBeDisplayed;
return debugName;
}
}
protected override void Execute()
{
m_LoadedCount = 0;
for (int i = 0; i < Result.Count; i++)
{
if (Result[i].IsDone)
m_LoadedCount++;
else
Result[i].Completed += m_InternalOnComplete;
}
CompleteIfDependenciesComplete();
}
private void CompleteIfDependenciesComplete()
{
if (m_LoadedCount == Result.Count)
{
bool success = true;
OperationException ex = null;
if (!m_Settings.HasFlag(GroupOperationSettings.AllowFailedDependencies))
{
for (int i = 0; i < Result.Count; i++)
{
if (Result[i].Status != AsyncOperationStatus.Succeeded)
{
success = false;
ex = new OperationException("GroupOperation failed because one of its dependencies failed", Result[i].OperationException);
break;
}
}
}
Complete(Result, success, ex, m_Settings.HasFlag(GroupOperationSettings.ReleaseDependenciesOnFailure));
}
}
protected override void Destroy()
{
ReleaseDependencies();
}
protected override float Progress
{
get
{
float total = 0f;
for (int i = 0; i < Result.Count; i++)
{
var handle = Result[i];
if (!handle.IsDone)
total += handle.PercentComplete;
else
total++;
}
return total / Result.Count;
}
}
public void Init(List<AsyncOperationHandle> operations, bool releaseDependenciesOnFailure = true, bool allowFailedDependencies = false)
{
Result = new List<AsyncOperationHandle>(operations);
m_Settings = releaseDependenciesOnFailure ? GroupOperationSettings.ReleaseDependenciesOnFailure : GroupOperationSettings.None;
if (allowFailedDependencies)
m_Settings |= GroupOperationSettings.AllowFailedDependencies;
}
public void Init(List<AsyncOperationHandle> operations, GroupOperationSettings settings)
{
Result = new List<AsyncOperationHandle>(operations);
m_Settings = settings;
}
void OnOperationCompleted(AsyncOperationHandle op)
{
m_LoadedCount++;
CompleteIfDependenciesComplete();
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: f5fa1c5d453431041ab1e49b72e569e2
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,303 @@
using System;
using System.Collections.Generic;
using UnityEngine.ResourceManagement.ResourceLocations;
using UnityEngine.ResourceManagement.ResourceProviders;
using UnityEngine.ResourceManagement.Util;
namespace UnityEngine.ResourceManagement.AsyncOperations
{
internal interface IGenericProviderOperation
{
void Init(ResourceManager rm, IResourceProvider provider, IResourceLocation location, AsyncOperationHandle<IList<AsyncOperationHandle>> depOp);
void Init(ResourceManager rm, IResourceProvider provider, IResourceLocation location, AsyncOperationHandle<IList<AsyncOperationHandle>> depOp, bool releaseDependenciesOnFailure);
int ProvideHandleVersion { get; }
IResourceLocation Location { get; }
int DependencyCount { get; }
void GetDependencies(IList<object> dstList);
TDepObject GetDependency<TDepObject>(int index);
void SetProgressCallback(Func<float> callback);
void ProviderCompleted<T>(T result, bool status, Exception e);
Type RequestedType { get; }
void SetDownloadProgressCallback(Func<DownloadStatus> callback);
void SetWaitForCompletionCallback(Func<bool> callback);
}
[UnityEngine.Scripting.Preserve]
internal class ProviderOperation<TObject> : AsyncOperationBase<TObject>, IGenericProviderOperation, ICachable
{
private bool m_ReleaseDependenciesOnFailure = true;
private Action<int, object, bool, Exception> m_CompletionCallback;
private Action<int, IList<object>> m_GetDepCallback;
private Func<float> m_GetProgressCallback;
private Func<DownloadStatus> m_GetDownloadProgressCallback;
private Func<bool> m_WaitForCompletionCallback;
private bool m_ProviderCompletedCalled = false; //This is used to fix a race condition with WaitForCompletion, like in AssetBundleProvider
private DownloadStatus m_DownloadStatus;
private IResourceProvider m_Provider;
internal AsyncOperationHandle<IList<AsyncOperationHandle>> m_DepOp;
private IResourceLocation m_Location;
private int m_ProvideHandleVersion;
private bool m_NeedsRelease;
IOperationCacheKey ICachable.Key { get; set; }
private ResourceManager m_ResourceManager;
private const float k_OperationWaitingToCompletePercentComplete = 0.99f;
public int ProvideHandleVersion
{
get { return m_ProvideHandleVersion; }
}
public IResourceLocation Location
{
get { return m_Location; }
}
public void SetDownloadProgressCallback(Func<DownloadStatus> callback)
{
m_GetDownloadProgressCallback = callback;
if (m_GetDownloadProgressCallback != null)
m_DownloadStatus = m_GetDownloadProgressCallback();
}
public void SetWaitForCompletionCallback(Func<bool> callback)
{
m_WaitForCompletionCallback = callback;
}
///<inheritdoc />
protected override bool InvokeWaitForCompletion()
{
if (IsDone || m_ProviderCompletedCalled)
return true;
if (m_DepOp.IsValid() && !m_DepOp.IsDone)
m_DepOp.WaitForCompletion();
m_RM?.Update(Time.unscaledDeltaTime);
if (!HasExecuted)
InvokeExecute();
if (m_WaitForCompletionCallback == null)
return false;
return m_WaitForCompletionCallback.Invoke();
}
internal override DownloadStatus GetDownloadStatus(HashSet<object> visited)
{
var depDLS = m_DepOp.IsValid() ? m_DepOp.InternalGetDownloadStatus(visited) : default;
if (m_GetDownloadProgressCallback != null)
m_DownloadStatus = m_GetDownloadProgressCallback();
if (Status == AsyncOperationStatus.Succeeded)
m_DownloadStatus.DownloadedBytes = m_DownloadStatus.TotalBytes;
return new DownloadStatus() {DownloadedBytes = m_DownloadStatus.DownloadedBytes + depDLS.DownloadedBytes, TotalBytes = m_DownloadStatus.TotalBytes + depDLS.TotalBytes, IsDone = IsDone};
}
public ProviderOperation()
{
}
/// <inheritdoc />
public override void GetDependencies(List<AsyncOperationHandle> deps)
{
if (m_DepOp.IsValid())
deps.Add(m_DepOp);
}
internal override void ReleaseDependencies()
{
if (m_DepOp.IsValid())
m_DepOp.Release();
}
protected override string DebugName
{
get { return string.Format("Resource<{0}>({1})", typeof(TObject).Name, m_Location == null ? "Invalid" : ShortenPath(m_Location.InternalId, true)); }
}
internal const string kInvalidHandleMsg = "The ProvideHandle is invalid. After the handle has been completed, it can no longer be used";
public void GetDependencies(IList<object> dstList)
{
dstList.Clear();
if (!m_DepOp.IsValid())
return;
if (m_DepOp.Result == null)
return;
for (int i = 0; i < m_DepOp.Result.Count; i++)
dstList.Add(m_DepOp.Result[i].Result);
}
public Type RequestedType
{
get { return typeof(TObject); }
}
public int DependencyCount
{
get { return (!m_DepOp.IsValid() || m_DepOp.Result == null) ? 0 : m_DepOp.Result.Count; }
}
public TDepObject GetDependency<TDepObject>(int index)
{
if (!m_DepOp.IsValid() || m_DepOp.Result == null)
throw new Exception("Cannot get dependency because no dependencies were available");
return (TDepObject)(m_DepOp.Result[index].Result);
}
public void SetProgressCallback(Func<float> callback)
{
m_GetProgressCallback = callback;
}
public void ProviderCompleted<T>(T result, bool status, Exception e)
{
m_ProvideHandleVersion++;
m_GetProgressCallback = null;
m_GetDownloadProgressCallback = null;
m_WaitForCompletionCallback = null;
m_NeedsRelease = status;
m_ProviderCompletedCalled = true;
ProviderOperation<T> top = this as ProviderOperation<T>;
if (top != null)
{
top.Result = result;
}
else if (result == null && !typeof(TObject).IsValueType)
{
Result = (TObject)(object)null;
}
else if (result != null && typeof(TObject).IsAssignableFrom(result.GetType()))
{
Result = (TObject)(object)result;
}
else
{
string errorMsg = string.Format(
"Provider of type {0} with id {1} has provided a result of type {2} which cannot be converted to requested type {3}. The operation will be marked as failed.",
m_Provider.GetType().ToString(), m_Provider.ProviderId, typeof(T), typeof(TObject));
Complete(Result, false, errorMsg);
throw new Exception(errorMsg);
}
Complete(Result, status, e, m_ReleaseDependenciesOnFailure);
}
protected override float Progress
{
get
{
try
{
float numberOfOps = 1f;
float total = 0f;
if (m_GetProgressCallback != null)
total += m_GetProgressCallback();
if (!m_DepOp.IsValid() || m_DepOp.Result == null || m_DepOp.Result.Count == 0)
{
total++;
numberOfOps++;
}
else
{
foreach (var handle in m_DepOp.Result)
{
total += handle.PercentComplete;
numberOfOps++;
}
}
float result = total / numberOfOps;
//This is done because all AssetBundle operations (m_DepOp.Result) can complete as well as the
//BundledAssetRequest operation (m_GetProgressCallBack) but this overall operation hasn't completed yet.
//Once the operation has a chance to complete we short circut calling into Progress here and just return 1.0f
return Mathf.Min(result, k_OperationWaitingToCompletePercentComplete);
}
catch
{
return 0.0f;
}
}
}
protected override void Execute()
{
Debug.Assert(m_DepOp.IsDone);
if (m_DepOp.IsValid() && m_DepOp.Status == AsyncOperationStatus.Failed && (m_Provider.BehaviourFlags & ProviderBehaviourFlags.CanProvideWithFailedDependencies) == 0)
{
ProviderCompleted(default(TObject), false, new Exception("Dependency Exception", m_DepOp.OperationException));
}
else
{
try
{
m_Provider.Provide(new ProvideHandle(m_ResourceManager, this));
}
catch (Exception e)
{
ProviderCompleted(default(TObject), false, e);
}
}
}
public void Init(ResourceManager rm, IResourceProvider provider, IResourceLocation location, AsyncOperationHandle<IList<AsyncOperationHandle>> depOp)
{
m_DownloadStatus = default;
m_ResourceManager = rm;
m_DepOp = depOp;
if (m_DepOp.IsValid())
m_DepOp.Acquire();
m_Provider = provider;
m_Location = location;
m_ReleaseDependenciesOnFailure = true;
m_ProviderCompletedCalled = false;
SetWaitForCompletionCallback(WaitForCompletionHandler);
}
public void Init(ResourceManager rm, IResourceProvider provider, IResourceLocation location, AsyncOperationHandle<IList<AsyncOperationHandle>> depOp, bool releaseDependenciesOnFailure)
{
m_DownloadStatus = default;
m_ResourceManager = rm;
m_DepOp = depOp;
if (m_DepOp.IsValid())
m_DepOp.Acquire();
m_Provider = provider;
m_Location = location;
m_ReleaseDependenciesOnFailure = releaseDependenciesOnFailure;
m_ProviderCompletedCalled = false;
SetWaitForCompletionCallback(WaitForCompletionHandler);
}
bool WaitForCompletionHandler()
{
if (IsDone)
return true;
if (!m_DepOp.IsDone)
m_DepOp.WaitForCompletion();
if (!HasExecuted)
InvokeExecute();
return IsDone;
}
protected override void Destroy()
{
if (m_NeedsRelease)
m_Provider.Release(m_Location, Result);
if (m_DepOp.IsValid())
m_DepOp.Release();
Result = default(TObject);
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: b28e4f74756c9e3418600f517d56e9f2
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,22 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Networking;
namespace UnityEngine.ResourceManagement.AsyncOperations
{
internal class UnityWebRequestOperation : AsyncOperationBase<UnityWebRequest>
{
UnityWebRequest m_UWR;
public UnityWebRequestOperation(UnityWebRequest webRequest)
{
m_UWR = webRequest;
}
protected override void Execute()
{
m_UWR.SendWebRequest().completed += (request) => { Complete(m_UWR, string.IsNullOrEmpty(m_UWR.error), m_UWR.error); };
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 5d9a6f10d6aba7e40b982e7ed18ecfff
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant: