UNITASK_WEBREQUEST_SUPPORT

master
neuecc 2021-02-08 19:09:02 +09:00
parent 9a3f10d4bf
commit a35e5f929d
9 changed files with 524 additions and 518 deletions

View File

@ -20,8 +20,9 @@ public static class EditorRunnerChecker
{ {
Debug.Log("Start"); Debug.Log("Start");
var r = await UnityWebRequest.Get("https://bing.com/").SendWebRequest().ToUniTask(); //var r = await UnityWebRequest.Get("https://bing.com/").SendWebRequest().ToUniTask();
Debug.Log(r.downloadHandler.text.Substring(0, 100)); //Debug.Log(r.downloadHandler.text.Substring(0, 100));
await UniTask.Yield();
Debug.Log("End"); Debug.Log("End");
} }

View File

@ -7,7 +7,7 @@ using UnityEngine.Networking;
namespace Cysharp.Threading.Tasks.Internal namespace Cysharp.Threading.Tasks.Internal
{ {
#if ENABLE_UNITYWEBREQUEST && UNITASK_WEBREQUEST_SUPPORT #if ENABLE_UNITYWEBREQUEST && (!UNITY_2019_1_OR_NEWER || UNITASK_WEBREQUEST_SUPPORT)
internal static class UnityWebRequestResultExtensions internal static class UnityWebRequestResultExtensions
{ {

View File

@ -1,7 +1,7 @@
{ {
"name": "UniTask", "name": "UniTask",
"references": [ "rootNamespace": "",
], "references": [],
"includePlatforms": [], "includePlatforms": [],
"excludePlatforms": [], "excludePlatforms": [],
"allowUnsafeCode": false, "allowUnsafeCode": false,
@ -13,7 +13,7 @@
{ {
"name": "com.unity.modules.assetbundle", "name": "com.unity.modules.assetbundle",
"expression": "", "expression": "",
"define": "NITASK_ASSETBUNDLE_SUPPORT" "define": "UNITASK_ASSETBUNDLE_SUPPORT"
}, },
{ {
"name": "com.unity.modules.physics", "name": "com.unity.modules.physics",
@ -34,6 +34,11 @@
"name": "com.unity.ugui", "name": "com.unity.ugui",
"expression": "", "expression": "",
"define": "UNITASK_UGUI_SUPPORT" "define": "UNITASK_UGUI_SUPPORT"
},
{
"name": "com.unity.modules.unitywebrequestwww",
"expression": "",
"define": "UNITASK_WEBREQUEST_SUPPORT"
} }
], ],
"noEngineReferences": false "noEngineReferences": false

View File

@ -5,7 +5,7 @@ using System.Runtime.CompilerServices;
using System.Threading; using System.Threading;
using UnityEngine; using UnityEngine;
using Cysharp.Threading.Tasks.Internal; using Cysharp.Threading.Tasks.Internal;
#if !UNITY_2019_1_OR_NEWER && (ENABLE_UNITYWEBREQUEST && UNITASK_WEBREQUEST_SUPPORT) #if ENABLE_UNITYWEBREQUEST && (!UNITY_2019_1_OR_NEWER || UNITASK_WEBREQUEST_SUPPORT)
using UnityEngine.Networking; using UnityEngine.Networking;
#endif #endif
@ -1221,7 +1221,7 @@ namespace Cysharp.Threading.Tasks
#endregion #endregion
#endif #endif
#if ENABLE_UNITYWEBREQUEST && UNITASK_WEBREQUEST_SUPPORT #if ENABLE_UNITYWEBREQUEST && (!UNITY_2019_1_OR_NEWER || UNITASK_WEBREQUEST_SUPPORT)
#region UnityWebRequestAsyncOperation #region UnityWebRequestAsyncOperation
public static UnityWebRequestAsyncOperationAwaiter GetAwaiter(this UnityWebRequestAsyncOperation asyncOperation) public static UnityWebRequestAsyncOperationAwaiter GetAwaiter(this UnityWebRequestAsyncOperation asyncOperation)

View File

@ -1,4 +1,4 @@
#if ENABLE_UNITYWEBREQUEST #if ENABLE_UNITYWEBREQUEST && (!UNITY_2019_1_OR_NEWER || UNITASK_WEBREQUEST_SUPPORT)
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;

View File

@ -1,471 +1,471 @@
using Cysharp.Threading.Tasks; //using Cysharp.Threading.Tasks;
using System; //using System;
using System.Collections.Generic; //using System.Collections.Generic;
using System.Diagnostics; //using System.Diagnostics;
using System.Linq; //using System.Linq;
using System.Text; //using System.Text;
using System.Threading; //using System.Threading;
using System.Threading.Tasks; //using System.Threading.Tasks;
using UnityEngine; //using UnityEngine;
using UnityEngine.Networking; //using UnityEngine.Networking;
using UnityEngine.SceneManagement; //using UnityEngine.SceneManagement;
using UnityEngine.UI; //using UnityEngine.UI;
namespace Cysharp.Threading.Tasks.Sample //namespace Cysharp.Threading.Tasks.Sample
{
//public class Sample2
//{ //{
// public Sample2() // //public class Sample2
// { // //{
// // デコレーターの詰まったClientを生成これは一度作ったらフィールドに保存可 // // public Sample2()
// var client = new NetworkClient("http://localhost", TimeSpan.FromSeconds(10), // // {
// new QueueRequestDecorator(), // // // デコレーターの詰まったClientを生成これは一度作ったらフィールドに保存可
// new LoggingDecorator(), // // var client = new NetworkClient("http://localhost", TimeSpan.FromSeconds(10),
// new AppendTokenDecorator(), // // new QueueRequestDecorator(),
// new SetupHeaderDecorator()); // // new LoggingDecorator(),
// // new AppendTokenDecorator(),
// // new SetupHeaderDecorator());
// await client.PostAsync("/User/Register", new { Id = 100 }); // // await client.PostAsync("/User/Register", new { Id = 100 });
// } // // }
//} // //}
public class ReturnToTitleDecorator : IAsyncDecorator // public class ReturnToTitleDecorator : IAsyncDecorator
{
public async UniTask<ResponseContext> SendAsync(RequestContext context, CancellationToken cancellationToken, Func<RequestContext, CancellationToken, UniTask<ResponseContext>> next)
{
try
{
return await next(context, cancellationToken);
}
catch (Exception ex)
{
if (ex is OperationCanceledException)
{
// キャンセルはきっと想定されている処理なのでそのまんまスルー呼び出し側でOperationCanceledExceptionとして飛んでいく)
throw;
}
if (ex is UnityWebRequestException uwe)
{
// ステータスコードを使って、タイトルに戻す例外です、とかリトライさせる例外です、とかハンドリングさせると便利
// if (uwe.ResponseCode) { }...
}
// サーバー例外のMessageを直接出すなんて乱暴なことはデバッグ時だけですよ勿論。
var result = await MessageDialog.ShowAsync(ex.Message);
// OK か Cancelかで分岐するなら。今回はボタン一個、OKのみの想定なので無視
// if (result == DialogResult.Ok) { }...
// シーン呼び出しはawaitしないことawaitして正常終了しちゃうと、この通信の呼び出し元に処理が戻って続行してしまいます
// のでForget。
SceneManager.LoadSceneAsync("TitleScene").ToUniTask().Forget();
// そしてOperationCanceledExceptionを投げて、この通信の呼び出し元の処理はキャンセル扱いにして終了させる
throw new OperationCanceledException();
}
}
}
public enum DialogResult
{
Ok,
Cancel
}
public static class MessageDialog
{
public static async UniTask<DialogResult> ShowAsync(string message)
{
// (例えば)Prefabで作っておいたダイアログを生成する
var view = await Resources.LoadAsync("Prefabs/Dialog");
// Ok, Cancelボタンのどちらかが押されるのを待機
return await (view as GameObject).GetComponent<MessageDialogView>().ClickResult;
}
}
public class MessageDialogView : MonoBehaviour
{
[SerializeField] Button okButton = default;
[SerializeField] Button closeButton = default;
UniTaskCompletionSource<DialogResult> taskCompletion;
// これでどちらかが押されるまで無限に待つを表現
public UniTask<DialogResult> ClickResult => taskCompletion.Task;
private void Start()
{
taskCompletion = new UniTaskCompletionSource<DialogResult>();
okButton.onClick.AddListener(() =>
{
taskCompletion.TrySetResult(DialogResult.Ok);
});
closeButton.onClick.AddListener(() =>
{
taskCompletion.TrySetResult(DialogResult.Cancel);
});
}
// もしボタンが押されずに消滅した場合にネンノタメ。
private void OnDestroy()
{
taskCompletion.TrySetResult(DialogResult.Cancel);
}
}
public class MockDecorator : IAsyncDecorator
{
Dictionary<string, object> mock;
// Pathと型を1:1にして事前定義したオブジェクトを返す辞書を渡す
public MockDecorator(Dictionary<string, object> mock)
{
this.mock = mock;
}
public UniTask<ResponseContext> SendAsync(RequestContext context, CancellationToken cancellationToken, Func<RequestContext, CancellationToken, UniTask<ResponseContext>> next)
{
if (mock.TryGetValue(context.Path, out var value))
{
// 一致したものがあればそれを返す(実際の通信は行わない)
return new UniTask<ResponseContext>(new ResponseContext(value));
}
else
{
return next(context, cancellationToken);
}
}
}
//public class LoggingDecorator : IAsyncDecorator
// { // {
// public async UniTask<ResponseContext> SendAsync(RequestContext context, CancellationToken cancellationToken, Func<RequestContext, CancellationToken, UniTask<ResponseContext>> next) // public async UniTask<ResponseContext> SendAsync(RequestContext context, CancellationToken cancellationToken, Func<RequestContext, CancellationToken, UniTask<ResponseContext>> next)
// { // {
// var sw = Stopwatch.StartNew();
// try // try
// { // {
// UnityEngine.Debug.Log("Start Network Request:" + context.Path); // return await next(context, cancellationToken);
// var response = await next(context, cancellationToken);
// UnityEngine.Debug.Log($"Complete Network Request: {context.Path} , Elapsed: {sw.Elapsed}, Size: {response.GetRawData().Length}");
// return response;
// } // }
// catch (Exception ex) // catch (Exception ex)
// { // {
// if (ex is OperationCanceledException) // if (ex is OperationCanceledException)
// { // {
// UnityEngine.Debug.Log("Request Canceled:" + context.Path); // // キャンセルはきっと想定されている処理なのでそのまんまスルー呼び出し側でOperationCanceledExceptionとして飛んでいく)
// }
// else if (ex is TimeoutException)
// {
// UnityEngine.Debug.Log("Request Timeout:" + context.Path);
// }
// else if (ex is UnityWebRequestException webex)
// {
// if (webex.IsHttpError)
// {
// UnityEngine.Debug.Log($"Request HttpError: {context.Path} Code:{webex.ResponseCode} Message:{webex.Message}");
// }
// else if (webex.IsNetworkError)
// {
// UnityEngine.Debug.Log($"Request NetworkError: {context.Path} Code:{webex.ResponseCode} Message:{webex.Message}");
// }
// }
// throw; // throw;
// } // }
// if (ex is UnityWebRequestException uwe)
// {
// // ステータスコードを使って、タイトルに戻す例外です、とかリトライさせる例外です、とかハンドリングさせると便利
// // if (uwe.ResponseCode) { }...
// }
// // サーバー例外のMessageを直接出すなんて乱暴なことはデバッグ時だけですよ勿論。
// var result = await MessageDialog.ShowAsync(ex.Message);
// // OK か Cancelかで分岐するなら。今回はボタン一個、OKのみの想定なので無視
// // if (result == DialogResult.Ok) { }...
// // シーン呼び出しはawaitしないことawaitして正常終了しちゃうと、この通信の呼び出し元に処理が戻って続行してしまいます
// // のでForget。
// SceneManager.LoadSceneAsync("TitleScene").ToUniTask().Forget();
// // そしてOperationCanceledExceptionを投げて、この通信の呼び出し元の処理はキャンセル扱いにして終了させる
// throw new OperationCanceledException();
// }
// }
// }
// public enum DialogResult
// {
// Ok,
// Cancel
// }
// public static class MessageDialog
// {
// public static async UniTask<DialogResult> ShowAsync(string message)
// {
// // (例えば)Prefabで作っておいたダイアログを生成する
// var view = await Resources.LoadAsync("Prefabs/Dialog");
// // Ok, Cancelボタンのどちらかが押されるのを待機
// return await (view as GameObject).GetComponent<MessageDialogView>().ClickResult;
// }
// }
// public class MessageDialogView : MonoBehaviour
// {
// [SerializeField] Button okButton = default;
// [SerializeField] Button closeButton = default;
// UniTaskCompletionSource<DialogResult> taskCompletion;
// // これでどちらかが押されるまで無限に待つを表現
// public UniTask<DialogResult> ClickResult => taskCompletion.Task;
// private void Start()
// {
// taskCompletion = new UniTaskCompletionSource<DialogResult>();
// okButton.onClick.AddListener(() =>
// {
// taskCompletion.TrySetResult(DialogResult.Ok);
// });
// closeButton.onClick.AddListener(() =>
// {
// taskCompletion.TrySetResult(DialogResult.Cancel);
// });
// }
// // もしボタンが押されずに消滅した場合にネンノタメ。
// private void OnDestroy()
// {
// taskCompletion.TrySetResult(DialogResult.Cancel);
// }
// }
// public class MockDecorator : IAsyncDecorator
// {
// Dictionary<string, object> mock;
// // Pathと型を1:1にして事前定義したオブジェクトを返す辞書を渡す
// public MockDecorator(Dictionary<string, object> mock)
// {
// this.mock = mock;
// }
// public UniTask<ResponseContext> SendAsync(RequestContext context, CancellationToken cancellationToken, Func<RequestContext, CancellationToken, UniTask<ResponseContext>> next)
// {
// if (mock.TryGetValue(context.Path, out var value))
// {
// // 一致したものがあればそれを返す(実際の通信は行わない)
// return new UniTask<ResponseContext>(new ResponseContext(value));
// }
// else
// {
// return next(context, cancellationToken);
// }
// }
// }
// //public class LoggingDecorator : IAsyncDecorator
// //{
// // public async UniTask<ResponseContext> SendAsync(RequestContext context, CancellationToken cancellationToken, Func<RequestContext, CancellationToken, UniTask<ResponseContext>> next)
// // {
// // var sw = Stopwatch.StartNew();
// // try
// // {
// // UnityEngine.Debug.Log("Start Network Request:" + context.Path);
// // var response = await next(context, cancellationToken);
// // UnityEngine.Debug.Log($"Complete Network Request: {context.Path} , Elapsed: {sw.Elapsed}, Size: {response.GetRawData().Length}");
// // return response;
// // }
// // catch (Exception ex)
// // {
// // if (ex is OperationCanceledException)
// // {
// // UnityEngine.Debug.Log("Request Canceled:" + context.Path);
// // }
// // else if (ex is TimeoutException)
// // {
// // UnityEngine.Debug.Log("Request Timeout:" + context.Path);
// // }
// // else if (ex is UnityWebRequestException webex)
// // {
// // if (webex.IsHttpError)
// // {
// // UnityEngine.Debug.Log($"Request HttpError: {context.Path} Code:{webex.ResponseCode} Message:{webex.Message}");
// // }
// // else if (webex.IsNetworkError)
// // {
// // UnityEngine.Debug.Log($"Request NetworkError: {context.Path} Code:{webex.ResponseCode} Message:{webex.Message}");
// // }
// // }
// // throw;
// // }
// // finally
// // {
// // /* log other */
// // }
// // }
// //}
// public class SetupHeaderDecorator : IAsyncDecorator
// {
// public async UniTask<ResponseContext> SendAsync(RequestContext context, CancellationToken cancellationToken, Func<RequestContext, CancellationToken, UniTask<ResponseContext>> next)
// {
// context.RequestHeaders["x-app-timestamp"] = context.Timestamp.ToString();
// context.RequestHeaders["x-user-id"] = "132141411"; // どこかから持ってくる
// context.RequestHeaders["x-access-token"] = "fafafawfafewaea"; // どこかから持ってくる2
// var respsonse = await next(context, cancellationToken);
// var nextToken = respsonse.ResponseHeaders["token"];
// // UserProfile.Token = nextToken; // どこかにセットするということにする
// return respsonse;
// }
// }
// public class AppendTokenDecorator : IAsyncDecorator
// {
// public async UniTask<ResponseContext> SendAsync(RequestContext context, CancellationToken cancellationToken, Func<RequestContext, CancellationToken, UniTask<ResponseContext>> next)
// {
// string token = "token"; // どっかから取ってくるということにする
// RETRY:
// try
// {
// context.RequestHeaders["x-accesss-token"] = token;
// return await next(context, cancellationToken);
// }
// catch (UnityWebRequestException ex)
// {
// // 例えば700はTokenを再取得してください的な意味だったとする
// if (ex.ResponseCode == 700)
// {
// // 別口でTokenを取得します的な処理
// var newToken = await new NetworkClient(context.BasePath, context.Timeout).PostAsync<string>("/Auth/GetToken", "access_token", cancellationToken);
// context.Reset(this);
// goto RETRY;
// }
// goto RETRY;
// }
// }
// }
// public class QueueRequestDecorator : IAsyncDecorator
// {
// readonly Queue<(UniTaskCompletionSource<ResponseContext>, RequestContext, CancellationToken, Func<RequestContext, CancellationToken, UniTask<ResponseContext>>)> q = new Queue<(UniTaskCompletionSource<ResponseContext>, RequestContext, CancellationToken, Func<RequestContext, CancellationToken, UniTask<ResponseContext>>)>();
// bool running;
// public async UniTask<ResponseContext> SendAsync(RequestContext context, CancellationToken cancellationToken, Func<RequestContext, CancellationToken, UniTask<ResponseContext>> next)
// {
// if (q.Count == 0)
// {
// return await next(context, cancellationToken);
// }
// else
// {
// var completionSource = new UniTaskCompletionSource<ResponseContext>();
// q.Enqueue((completionSource, context, cancellationToken, next));
// if (!running)
// {
// Run().Forget();
// }
// return await completionSource.Task;
// }
// }
// async UniTaskVoid Run()
// {
// running = true;
// try
// {
// while (q.Count != 0)
// {
// var (tcs, context, cancellationToken, next) = q.Dequeue();
// try
// {
// var response = await next(context, cancellationToken);
// tcs.TrySetResult(response);
// }
// catch (Exception ex)
// {
// tcs.TrySetException(ex);
// }
// }
// }
// finally // finally
// { // {
// /* log other */ // running = false;
// } // }
// } // }
// } // }
public class SetupHeaderDecorator : IAsyncDecorator
{
public async UniTask<ResponseContext> SendAsync(RequestContext context, CancellationToken cancellationToken, Func<RequestContext, CancellationToken, UniTask<ResponseContext>> next)
{
context.RequestHeaders["x-app-timestamp"] = context.Timestamp.ToString();
context.RequestHeaders["x-user-id"] = "132141411"; // どこかから持ってくる
context.RequestHeaders["x-access-token"] = "fafafawfafewaea"; // どこかから持ってくる2
var respsonse = await next(context, cancellationToken); // public class RequestContext
// {
// int decoratorIndex;
// readonly IAsyncDecorator[] decorators;
// Dictionary<string, string> headers;
var nextToken = respsonse.ResponseHeaders["token"]; // public string BasePath { get; }
// UserProfile.Token = nextToken; // どこかにセットするということにする // public string Path { get; }
// public object Value { get; }
// public TimeSpan Timeout { get; }
// public DateTimeOffset Timestamp { get; private set; }
return respsonse; // public IDictionary<string, string> RequestHeaders
} // {
} // get
// {
// if (headers == null)
// {
// headers = new Dictionary<string, string>();
// }
// return headers;
// }
// }
// public RequestContext(string basePath, string path, object value, TimeSpan timeout, IAsyncDecorator[] filters)
// {
// this.decoratorIndex = -1;
// this.decorators = filters;
// this.BasePath = basePath;
// this.Path = path;
// this.Value = value;
// this.Timeout = timeout;
// this.Timestamp = DateTimeOffset.UtcNow;
// }
// internal Dictionary<string, string> GetRawHeaders() => headers;
// internal IAsyncDecorator GetNextDecorator() => decorators[++decoratorIndex];
// public void Reset(IAsyncDecorator currentFilter)
// {
// decoratorIndex = Array.IndexOf(decorators, currentFilter);
// if (headers != null)
// {
// headers.Clear();
// }
// Timestamp = DateTimeOffset.UtcNow;
// }
// }
// public class ResponseContext
// {
// bool hasValue;
// object value;
// readonly byte[] bytes;
// public long StatusCode { get; }
// public Dictionary<string, string> ResponseHeaders { get; }
// public ResponseContext(object value, Dictionary<string, string> header = null)
// {
// this.hasValue = true;
// this.value = value;
// this.StatusCode = 200;
// this.ResponseHeaders = (header ?? new Dictionary<string, string>());
// }
// public ResponseContext(byte[] bytes, long statusCode, Dictionary<string, string> responseHeaders)
// {
// this.hasValue = false;
// this.bytes = bytes;
// this.StatusCode = statusCode;
// this.ResponseHeaders = responseHeaders;
// }
// public byte[] GetRawData() => bytes;
// public T GetResponseAs<T>()
// {
// if (hasValue)
// {
// return (T)value;
// }
// value = JsonUtility.FromJson<T>(Encoding.UTF8.GetString(bytes));
// hasValue = true;
// return (T)value;
// }
// }
// public interface IAsyncDecorator
// {
// UniTask<ResponseContext> SendAsync(RequestContext context, CancellationToken cancellationToken, Func<RequestContext, CancellationToken, UniTask<ResponseContext>> next);
// }
public class AppendTokenDecorator : IAsyncDecorator // public class NetworkClient : IAsyncDecorator
{ // {
public async UniTask<ResponseContext> SendAsync(RequestContext context, CancellationToken cancellationToken, Func<RequestContext, CancellationToken, UniTask<ResponseContext>> next) // readonly Func<RequestContext, CancellationToken, UniTask<ResponseContext>> next;
{ // readonly IAsyncDecorator[] decorators;
string token = "token"; // どっかから取ってくるということにする // readonly TimeSpan timeout;
RETRY: // readonly IProgress<float> progress;
try // readonly string basePath;
{
context.RequestHeaders["x-accesss-token"] = token;
return await next(context, cancellationToken);
}
catch (UnityWebRequestException ex)
{
// 例えば700はTokenを再取得してください的な意味だったとする
if (ex.ResponseCode == 700)
{
// 別口でTokenを取得します的な処理
var newToken = await new NetworkClient(context.BasePath, context.Timeout).PostAsync<string>("/Auth/GetToken", "access_token", cancellationToken);
context.Reset(this);
goto RETRY;
}
goto RETRY; // public NetworkClient(string basePath, TimeSpan timeout, params IAsyncDecorator[] decorators)
} // : this(basePath, timeout, null, decorators)
} // {
} // }
public class QueueRequestDecorator : IAsyncDecorator // public NetworkClient(string basePath, TimeSpan timeout, IProgress<float> progress, params IAsyncDecorator[] decorators)
{ // {
readonly Queue<(UniTaskCompletionSource<ResponseContext>, RequestContext, CancellationToken, Func<RequestContext, CancellationToken, UniTask<ResponseContext>>)> q = new Queue<(UniTaskCompletionSource<ResponseContext>, RequestContext, CancellationToken, Func<RequestContext, CancellationToken, UniTask<ResponseContext>>)>(); // this.next = InvokeRecursive; // setup delegate
bool running;
public async UniTask<ResponseContext> SendAsync(RequestContext context, CancellationToken cancellationToken, Func<RequestContext, CancellationToken, UniTask<ResponseContext>> next) // this.basePath = basePath;
{ // this.timeout = timeout;
if (q.Count == 0) // this.progress = progress;
{ // this.decorators = new IAsyncDecorator[decorators.Length + 1];
return await next(context, cancellationToken); // Array.Copy(decorators, this.decorators, decorators.Length);
} // this.decorators[this.decorators.Length - 1] = this;
else // }
{
var completionSource = new UniTaskCompletionSource<ResponseContext>();
q.Enqueue((completionSource, context, cancellationToken, next));
if (!running)
{
Run().Forget();
}
return await completionSource.Task;
}
}
async UniTaskVoid Run() // public async UniTask<T> PostAsync<T>(string path, T value, CancellationToken cancellationToken = default)
{ // {
running = true; // var request = new RequestContext(basePath, path, value, timeout, decorators);
try // var response = await InvokeRecursive(request, cancellationToken);
{ // return response.GetResponseAs<T>();
while (q.Count != 0) // }
{
var (tcs, context, cancellationToken, next) = q.Dequeue();
try
{
var response = await next(context, cancellationToken);
tcs.TrySetResult(response);
}
catch (Exception ex)
{
tcs.TrySetException(ex);
}
}
}
finally
{
running = false;
}
}
}
public class RequestContext // UniTask<ResponseContext> InvokeRecursive(RequestContext context, CancellationToken cancellationToken)
{ // {
int decoratorIndex; // return context.GetNextDecorator().SendAsync(context, cancellationToken, next); // マジカル再帰処理
readonly IAsyncDecorator[] decorators; // }
Dictionary<string, string> headers;
public string BasePath { get; } // async UniTask<ResponseContext> IAsyncDecorator.SendAsync(RequestContext context, CancellationToken cancellationToken, Func<RequestContext, CancellationToken, UniTask<ResponseContext>> _)
public string Path { get; } // {
public object Value { get; } // // Postしか興味ないからPostにしかしないよ
public TimeSpan Timeout { get; } // // パフォーマンスを最大限にしたい場合はuploadHandler, downloadHandlerをカスタマイズすること
public DateTimeOffset Timestamp { get; private set; }
public IDictionary<string, string> RequestHeaders // // JSONでbodyに送るというパラメータで送るという雑設定。
{ // var data = JsonUtility.ToJson(context.Value);
get // var formData = new Dictionary<string, string> { { "body", data } };
{
if (headers == null)
{
headers = new Dictionary<string, string>();
}
return headers;
}
}
public RequestContext(string basePath, string path, object value, TimeSpan timeout, IAsyncDecorator[] filters) // using (var req = UnityWebRequest.Post(basePath + context.Path, formData))
{ // {
this.decoratorIndex = -1; // var header = context.GetRawHeaders();
this.decorators = filters; // if (header != null)
this.BasePath = basePath; // {
this.Path = path; // foreach (var item in header)
this.Value = value; // {
this.Timeout = timeout; // req.SetRequestHeader(item.Key, item.Value);
this.Timestamp = DateTimeOffset.UtcNow; // }
} // }
internal Dictionary<string, string> GetRawHeaders() => headers; // // Timeout処理はCancellationTokenSourceのCancelAfterSlim(UniTask拡張)を使ってサクッと処理
internal IAsyncDecorator GetNextDecorator() => decorators[++decoratorIndex]; // var linkToken = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
// linkToken.CancelAfterSlim(timeout);
// try
// {
// // 完了待ちや終了処理はUniTaskの拡張自体に丸投げ
// await req.SendWebRequest().ToUniTask(progress: progress, cancellationToken: linkToken.Token);
// }
// catch (OperationCanceledException)
// {
// // 元キャンセレーションソースがキャンセルしてなければTimeoutによるものと判定
// if (!cancellationToken.IsCancellationRequested)
// {
// throw new TimeoutException();
// }
// }
// finally
// {
// // Timeoutに引っかからなかった場合にてるのでCancelAfterSlimの裏で回ってるループをこれで終わらせとく
// if (!linkToken.IsCancellationRequested)
// {
// linkToken.Cancel();
// }
// }
public void Reset(IAsyncDecorator currentFilter) // // UnityWebRequestを先にDisposeしちゃうので先に必要なものを取得しておく性能的には無駄なのでパフォーマンスを最大限にしたい場合は更に一工夫を
{ // return new ResponseContext(req.downloadHandler.data, req.responseCode, req.GetResponseHeaders());
decoratorIndex = Array.IndexOf(decorators, currentFilter); // }
if (headers != null) // }
{ // }
headers.Clear(); //}
}
Timestamp = DateTimeOffset.UtcNow;
}
}
public class ResponseContext
{
bool hasValue;
object value;
readonly byte[] bytes;
public long StatusCode { get; }
public Dictionary<string, string> ResponseHeaders { get; }
public ResponseContext(object value, Dictionary<string, string> header = null)
{
this.hasValue = true;
this.value = value;
this.StatusCode = 200;
this.ResponseHeaders = (header ?? new Dictionary<string, string>());
}
public ResponseContext(byte[] bytes, long statusCode, Dictionary<string, string> responseHeaders)
{
this.hasValue = false;
this.bytes = bytes;
this.StatusCode = statusCode;
this.ResponseHeaders = responseHeaders;
}
public byte[] GetRawData() => bytes;
public T GetResponseAs<T>()
{
if (hasValue)
{
return (T)value;
}
value = JsonUtility.FromJson<T>(Encoding.UTF8.GetString(bytes));
hasValue = true;
return (T)value;
}
}
public interface IAsyncDecorator
{
UniTask<ResponseContext> SendAsync(RequestContext context, CancellationToken cancellationToken, Func<RequestContext, CancellationToken, UniTask<ResponseContext>> next);
}
public class NetworkClient : IAsyncDecorator
{
readonly Func<RequestContext, CancellationToken, UniTask<ResponseContext>> next;
readonly IAsyncDecorator[] decorators;
readonly TimeSpan timeout;
readonly IProgress<float> progress;
readonly string basePath;
public NetworkClient(string basePath, TimeSpan timeout, params IAsyncDecorator[] decorators)
: this(basePath, timeout, null, decorators)
{
}
public NetworkClient(string basePath, TimeSpan timeout, IProgress<float> progress, params IAsyncDecorator[] decorators)
{
this.next = InvokeRecursive; // setup delegate
this.basePath = basePath;
this.timeout = timeout;
this.progress = progress;
this.decorators = new IAsyncDecorator[decorators.Length + 1];
Array.Copy(decorators, this.decorators, decorators.Length);
this.decorators[this.decorators.Length - 1] = this;
}
public async UniTask<T> PostAsync<T>(string path, T value, CancellationToken cancellationToken = default)
{
var request = new RequestContext(basePath, path, value, timeout, decorators);
var response = await InvokeRecursive(request, cancellationToken);
return response.GetResponseAs<T>();
}
UniTask<ResponseContext> InvokeRecursive(RequestContext context, CancellationToken cancellationToken)
{
return context.GetNextDecorator().SendAsync(context, cancellationToken, next); // マジカル再帰処理
}
async UniTask<ResponseContext> IAsyncDecorator.SendAsync(RequestContext context, CancellationToken cancellationToken, Func<RequestContext, CancellationToken, UniTask<ResponseContext>> _)
{
// Postしか興味ないからPostにしかしないよ
// パフォーマンスを最大限にしたい場合はuploadHandler, downloadHandlerをカスタマイズすること
// JSONでbodyに送るというパラメータで送るという雑設定。
var data = JsonUtility.ToJson(context.Value);
var formData = new Dictionary<string, string> { { "body", data } };
using (var req = UnityWebRequest.Post(basePath + context.Path, formData))
{
var header = context.GetRawHeaders();
if (header != null)
{
foreach (var item in header)
{
req.SetRequestHeader(item.Key, item.Value);
}
}
// Timeout処理はCancellationTokenSourceのCancelAfterSlim(UniTask拡張)を使ってサクッと処理
var linkToken = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
linkToken.CancelAfterSlim(timeout);
try
{
// 完了待ちや終了処理はUniTaskの拡張自体に丸投げ
await req.SendWebRequest().ToUniTask(progress: progress, cancellationToken: linkToken.Token);
}
catch (OperationCanceledException)
{
// 元キャンセレーションソースがキャンセルしてなければTimeoutによるものと判定
if (!cancellationToken.IsCancellationRequested)
{
throw new TimeoutException();
}
}
finally
{
// Timeoutに引っかからなかった場合にてるのでCancelAfterSlimの裏で回ってるループをこれで終わらせとく
if (!linkToken.IsCancellationRequested)
{
linkToken.Cancel();
}
}
// UnityWebRequestを先にDisposeしちゃうので先に必要なものを取得しておく性能的には無駄なのでパフォーマンスを最大限にしたい場合は更に一工夫を
return new ResponseContext(req.downloadHandler.data, req.responseCode, req.GetResponseHeaders());
}
}
}
}

View File

@ -18,7 +18,6 @@ using UnityEngine.SceneManagement;
using UnityEngine.Rendering; using UnityEngine.Rendering;
using System.IO; using System.IO;
using System.Linq.Expressions; using System.Linq.Expressions;
using Cysharp.Threading.Tasks.Sample;
// using DG.Tweening; // using DG.Tweening;
@ -269,32 +268,33 @@ public class SandboxMain : MonoBehaviour
async Task Test1() async Task Test1()
{ {
var r = await TcsAsync("https://bing.com/"); // var r = await TcsAsync("https://bing.com/");
await Task.Yield();
Debug.Log("TASKASYNC"); Debug.Log("TASKASYNC");
} }
async UniTaskVoid Test2() //async UniTaskVoid Test2()
{ //{
try // try
{ // {
//var cts = new CancellationTokenSource(); // //var cts = new CancellationTokenSource();
//var r = UniAsync("https://bing.com/", cts.Token); // //var r = UniAsync("https://bing.com/", cts.Token);
//cts.Cancel(); // //cts.Cancel();
//await r; // //await r;
Debug.Log("SendWebRequestDone:" + PlayerLoopInfo.CurrentLoopType); // Debug.Log("SendWebRequestDone:" + PlayerLoopInfo.CurrentLoopType);
// var foo = await UnityWebRequest.Get("https://bing.com/").SendWebRequest(); // // var foo = await UnityWebRequest.Get("https://bing.com/").SendWebRequest();
// foo.downloadHandler.text; // // foo.downloadHandler.text;
// // //
_ = await UnityWebRequest.Get("https://bing.com/").SendWebRequest().WithCancellation(CancellationToken.None); // _ = await UnityWebRequest.Get("https://bing.com/").SendWebRequest().WithCancellation(CancellationToken.None);
Debug.Log("SendWebRequestWithCancellationDone:" + PlayerLoopInfo.CurrentLoopType); // Debug.Log("SendWebRequestWithCancellationDone:" + PlayerLoopInfo.CurrentLoopType);
} // }
catch // catch
{ // {
Debug.Log("Canceled"); // Debug.Log("Canceled");
} // }
} //}
IEnumerator Test3(string url) IEnumerator Test3(string url)
{ {
@ -303,17 +303,17 @@ public class SandboxMain : MonoBehaviour
Debug.Log("COROUTINE"); Debug.Log("COROUTINE");
} }
static async Task<UnityWebRequest> TcsAsync(string url) //static async Task<UnityWebRequest> TcsAsync(string url)
{ //{
var req = await UnityWebRequest.Get(url).SendWebRequest(); // var req = await UnityWebRequest.Get(url).SendWebRequest();
return req; // return req;
} //}
static async UniTask<UnityWebRequest> UniAsync(string url, CancellationToken cancellationToken) //static async UniTask<UnityWebRequest> UniAsync(string url, CancellationToken cancellationToken)
{ //{
var req = await UnityWebRequest.Get(url).SendWebRequest().WithCancellation(cancellationToken); // var req = await UnityWebRequest.Get(url).SendWebRequest().WithCancellation(cancellationToken);
return req; // return req;
} //}
async Task<int> Test() async Task<int> Test()
{ {

View File

@ -14,14 +14,14 @@ public class FooMonoBehaviour : MonoBehaviour
private async UniTask Download(UnityWebRequest req, string filePath) private async UniTask Download(UnityWebRequest req, string filePath)
{ {
var foo = req.SendWebRequest(); _ = req.SendWebRequest();
var aaa = await foo; // var aaa = await foo;
Debug.Log(aaa); // Debug.Log(aaa);
await UniTask.Yield();
//File.WriteAllText(filePath, req.downloadHandler.text ?? string.Empty); //File.WriteAllText(filePath, req.downloadHandler.text ?? string.Empty);
} }
} }

View File

@ -1,2 +1,2 @@
m_EditorVersion: 2020.2.1f1 m_EditorVersion: 2020.2.0f1
m_EditorVersionWithRevision: 2020.2.1f1 (270dd8c3da1c) m_EditorVersionWithRevision: 2020.2.0f1 (3721df5a8b28)