// Copyright (c) Alexander Bogarsukov. // Licensed under the MIT license. See the LICENSE.md file in the project root for more information. using System; using System.Threading; using System.Threading.Tasks; using UnityEngine; namespace UnityFx.Tasks { /// /// Extension methods for . /// public static class AsyncOperationExtensions { /// /// Creates an wrapper for the Unity . /// /// The source operation. /// Returns a instance that can be used to track the operation state. /// public static Task ToTask(this AsyncOperation op) { return ToTask(op, CancellationToken.None); } /// /// Creates an wrapper for the Unity . /// /// The source operation. /// A token that can be used to cancel the request. /// Returns a instance that can be used to track the operation state. /// public static Task ToTask(this AsyncOperation op, CancellationToken cancellationToken) { if (op.isDone) { return Task.CompletedTask; } else { if (cancellationToken.IsCancellationRequested) { return Task.FromCanceled(cancellationToken); } var result = new TaskCompletionSource(op); if (cancellationToken.CanBeCanceled) { cancellationToken.Register(() => result.TrySetCanceled(cancellationToken)); } op.completed += o => { result.TrySetResult(null); }; return result.Task; } } /// /// Creates an wrapper for the Unity . /// /// The source operation. /// Returns a instance that can be used to track the operation state. /// public static Task ToTask(this ResourceRequest op) { return ToTask(op, CancellationToken.None); } /// /// Creates an wrapper for the Unity . /// /// The source operation. /// A token that can be used to cancel the request. /// Returns a instance that can be used to track the operation state. /// public static Task ToTask(this ResourceRequest op, CancellationToken cancellationToken) { return ToTask(op, cancellationToken); } /// /// Creates an wrapper for the Unity . /// /// Type of the operation result value. /// The source operation. /// Returns a instance that can be used to track the operation state. /// public static Task ToTask(this ResourceRequest op) where T : UnityEngine.Object { return ToTask(op, CancellationToken.None); } /// /// Creates an wrapper for the Unity . /// /// Type of the operation result value. /// The source operation. /// A token that can be used to cancel the request. /// Returns a instance that can be used to track the operation state. /// public static Task ToTask(this ResourceRequest op, CancellationToken cancellationToken) where T : UnityEngine.Object { if (op.isDone) { if (op.asset is T) { return Task.FromResult(op.asset as T); } else { return Task.FromException(new UnityAssetLoadException(typeof(T))); } } else { if (cancellationToken.IsCancellationRequested) { return Task.FromCanceled(cancellationToken); } var result = new TaskCompletionSource(op); if (cancellationToken.CanBeCanceled) { cancellationToken.Register(() => result.TrySetCanceled(cancellationToken)); } op.completed += o => { var asset = op.asset as T; if (asset) { // NOTE: TrySetResult() failure means that the operation was cancelled, thus the resource should be unloaded. if (!result.TrySetResult(asset)) { Resources.UnloadAsset(asset); } } else { result.TrySetException(new UnityAssetLoadException(typeof(T))); } }; return result.Task; } } /// /// Creates an wrapper for the Unity . /// /// Type of the operation result value. /// The source operation. /// Returns a instance that can be used to track the operation state. /// public static Task ToTask(this AssetBundleRequest op) where T : UnityEngine.Object { return ToTask(op, CancellationToken.None); } /// /// Creates an wrapper for the Unity . /// /// Type of the operation result value. /// The source operation. /// A token that can be used to cancel the request. /// Returns a instance that can be used to track the operation state. /// public static Task ToTask(this AssetBundleRequest op, CancellationToken cancellationToken) where T : class { if (op.isDone) { var result = GetResult(op); if (result != null) { return Task.FromResult(result); } else { return Task.FromException(new UnityAssetLoadException(typeof(T))); } } else { if (cancellationToken.IsCancellationRequested) { return Task.FromCanceled(cancellationToken); } var tcs = new TaskCompletionSource(op); if (cancellationToken.CanBeCanceled) { cancellationToken.Register(() => tcs.TrySetCanceled(cancellationToken)); } op.completed += o => { var result = GetResult(op); if (result != null) { // NOTE: TrySetResult() failure means that the operation was cancelled, thus all assets should be unloaded. if (!tcs.TrySetResult(result)) { if (op.allAssets != null) { foreach (var obj in op.allAssets) { UnityEngine.Object.Destroy(obj); } } if (op.asset != null) { UnityEngine.Object.Destroy(op.asset); } } } else { tcs.TrySetException(new UnityAssetLoadException(typeof(T))); } }; return tcs.Task; } } /// /// Creates an wrapper for the Unity . /// /// The source operation. /// Returns a instance that can be used to track the operation state. /// public static Task ToTask(this AssetBundleCreateRequest op) { return ToTask(op, CancellationToken.None); } /// /// Creates an wrapper for the Unity . /// /// The source operation. /// A token that can be used to cancel the request. /// Returns a instance that can be used to track the operation state. /// public static Task ToTask(this AssetBundleCreateRequest op, CancellationToken cancellationToken) { if (op.isDone) { if (op.assetBundle) { return Task.FromResult(op.assetBundle); } else { return Task.FromException(new UnityAssetLoadException(typeof(AssetBundle))); } } else { if (cancellationToken.IsCancellationRequested) { return Task.FromCanceled(cancellationToken); } var result = new TaskCompletionSource(op); if (cancellationToken.CanBeCanceled) { cancellationToken.Register(() => result.TrySetCanceled(cancellationToken)); } op.completed += o => { if (!op.assetBundle) { result.TrySetException(new UnityAssetLoadException(typeof(AssetBundle))); } else if (!result.TrySetResult(op.assetBundle)) { // NOTE: TrySetResult() failure means that the operation was cancelled, thus the asset bundle should be unloaded. op.assetBundle.Unload(true); } }; return result.Task; } } internal static T GetResult(AssetBundleRequest op) where T : class { if (typeof(T).IsArray) { var assets = op.allAssets; // NOTE: Cannot just cast op.allAssets to T (this would return null), have to create new array. if (assets != null && assets.Length >= 0) { var elementType = typeof(T).GetElementType(); var result = Array.CreateInstance(elementType, assets.Length); assets.CopyTo(result, 0); return result as T; } } else if (op.asset != null) { return op.asset as T; } else if (op.allAssets != null && op.allAssets.Length > 0) { return op.allAssets[0] as T; } return default(T); } } }