// 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
}
}