// 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.Runtime.CompilerServices; using System.Threading; using System.Threading.Tasks; using UnityEngine; using UnityEngine.Networking; namespace UnityFx.Tasks { /// /// Extensions of . /// public static class UnityWebRequestExtensions { #region interface /// /// Creates an wrapper for the specified . The method calls (if not called). /// /// The source web request. /// Returns a instance that can be used to track the operation state. /// public static Task ToTask(this UnityWebRequest request) { return ToTask(request, CancellationToken.None); } /// /// Creates a cancellable wrapper for the specified . The method calls (if not called). /// /// The source web request. /// 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 UnityWebRequest request, CancellationToken cancellationToken) { if (cancellationToken.IsCancellationRequested) { request.Abort(); return Task.FromCanceled(cancellationToken); } if (request.isDone) { // NOTE: Short path for completed requests. if (request.isHttpError || request.isNetworkError) { return Task.FromException(new UnityWebRequestException(request.error, request.responseCode)); } else { return Task.CompletedTask; } } else { var result = new TaskCompletionSource(request); if (cancellationToken.CanBeCanceled) { cancellationToken.Register(() => { if (result.TrySetCanceled(cancellationToken)) { request.Abort(); } }, true); } if (request.isModifiable) { request.SendWebRequest().completed += op => OnTaskCompleted(result, request); } else { TaskUtility.AddCompletionCallback(request, () => OnTaskCompleted(result, request)); } return result.Task; } } /// /// Creates an wrapper for the specified . The method calls (if not called). /// /// Type of the request result value. /// The source web request. /// Returns a instance that can be used to track the operation state. /// public static Task ToTask(this UnityWebRequest request) where T : class { return ToTask(request, CancellationToken.None); } /// /// Creates a cancellable wrapper for the specified . The method calls (if not called). /// /// Type of the request result value. /// The source web request. /// 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 UnityWebRequest request, CancellationToken cancellationToken) where T : class { if (cancellationToken.IsCancellationRequested) { request.Abort(); return Task.FromCanceled(cancellationToken); } if (request.isDone) { // NOTE: Short path for completed requests. if (request.isHttpError || request.isNetworkError) { return Task.FromException(new UnityWebRequestException(request.error, request.responseCode)); } else { return Task.FromResult(GetResultInternal(request)); } } else { var result = new TaskCompletionSource(request); if (cancellationToken.CanBeCanceled) { cancellationToken.Register(() => { if (result.TrySetCanceled(cancellationToken)) { request.Abort(); } }, true); } if (request.isModifiable) { request.SendWebRequest().completed += op => OnTaskCompleted(result, request); } else { TaskUtility.AddCompletionCallback(request, () => OnTaskCompleted(result, request)); } return result.Task; } } internal static UnityWebRequest ValidateResultType(this UnityWebRequest request) where T : class { if (request.downloadHandler == null) { throw new InvalidOperationException("Result type is only available for download requests."); } if (request.downloadHandler is DownloadHandlerAssetBundle && typeof(T) != typeof(AssetBundle)) { throw new InvalidOperationException($"{nameof(AssetBundle)} result expected, while {typeof(T).Name} supplied."); } if (request.downloadHandler is DownloadHandlerTexture && typeof(T) != typeof(Texture) && typeof(T) != typeof(Texture2D)) { throw new InvalidOperationException($"{nameof(Texture2D)} result expected, while {typeof(T).Name} supplied."); } if (request.downloadHandler is DownloadHandlerAudioClip && typeof(T) != typeof(AudioClip)) { throw new InvalidOperationException($"{nameof(AudioClip)} result expected, while {typeof(T).Name} supplied."); } if (request.downloadHandler is DownloadHandlerBuffer && typeof(T) != typeof(string) && typeof(T) != typeof(byte[])) { throw new InvalidOperationException($"{nameof(String)} or byte array result expected, while {typeof(T).Name} supplied."); } return request; } internal static T GetResult(this UnityWebRequest request) where T : class { ThrowIfNotCompleted(request); ThrowIfFailed(request); return GetResultInternal(request); } [MethodImpl(MethodImplOptions.AggressiveInlining)] internal static void ThrowIfNotCompleted(this UnityWebRequest request) { if (!request.isDone) { throw new InvalidOperationException("The request is expected to be completed."); } } [MethodImpl(MethodImplOptions.AggressiveInlining)] internal static void ThrowIfFailed(this UnityWebRequest request) { if (request.isHttpError || request.isNetworkError) { throw new UnityWebRequestException(request.error, request.responseCode); } } #endregion #region implementation private static T GetResultInternal(UnityWebRequest request) where T : class { if (request.downloadHandler != null) { if (request.downloadHandler is DownloadHandlerAssetBundle) { return ((DownloadHandlerAssetBundle)request.downloadHandler).assetBundle as T; } else if (request.downloadHandler is DownloadHandlerTexture) { return ((DownloadHandlerTexture)request.downloadHandler).texture as T; } else if (request.downloadHandler is DownloadHandlerAudioClip) { return ((DownloadHandlerAudioClip)request.downloadHandler).audioClip as T; } else if (typeof(T) == typeof(byte[])) { return request.downloadHandler.data as T; } else if (typeof(T) == typeof(string)) { return request.downloadHandler.text as T; } } return null; } private static void OnTaskCompleted(TaskCompletionSource tcs, UnityWebRequest request) where T : class { try { var requestResult = GetResult(request); tcs.TrySetResult(requestResult); } catch (Exception e) { tcs.TrySetException(e); } } #endregion } }