6
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

UnityWebRequestをUniTaskで待つときにdownloadedBytesを受け取れるようにする。

Last updated at Posted at 2020-06-20

環境

UniTask 2.0.19

デフォルトではUnityWebRequestをUniTaskで待つときにdownloadedBytesを受け取れない

UnityAsyncExtensions.cs
public static UniTask<UnityWebRequest> ToUniTask(this UnityWebRequestAsyncOperation asyncOperation, IProgress<float> progress = null, PlayerLoopTiming timing = PlayerLoopTiming.Update, CancellationToken cancellationToken = default(CancellationToken))

UnityWebRequestをToUniTaskでUniTaskに変換するときにIProgressを使って割合は取ってこれるが、downloadedBytesは取れない。
(Content-Lengthでダウンロードサイズ取得してProgress使って算出する等は可能だと思うが、UnityWebRequestのdownloadedBytesが取れないって意味で)

そこで、引数にAction<long> reportDownloadedBytesを追加してみる。

新しく拡張メソッドを定義

UnityWebRequestAsyncOperationExt.cs
using System;
using System.Threading;
using Cysharp.Threading.Tasks;
using UnityEngine.Networking;

namespace DL
{
    public static partial class UnityWebRequestAsyncOperationExt
    {
        public static UniTask<UnityWebRequest> ToUniTaskWithDownloadedBytes(this UnityWebRequestAsyncOperation asyncOperation, IProgress<float> progress = null, Action<ulong> reportDownloadedBytes = null, PlayerLoopTiming timing = PlayerLoopTiming.Update, CancellationToken cancellationToken = default(CancellationToken))
        {
            if (asyncOperation == null)
            {
                throw new ArgumentNullException(nameof(asyncOperation));
            }
            if (asyncOperation.isDone) return UniTask.FromResult(asyncOperation.webRequest);
            return new UniTask<UnityWebRequest>(UnityWebRequestAsyncOperationConfiguredSource.Create(asyncOperation, timing, progress, reportDownloadedBytes, cancellationToken, out var token), token);
        }

        sealed class UnityWebRequestAsyncOperationConfiguredSource : IUniTaskSource<UnityWebRequest>, IPlayerLoopItem, ITaskPoolNode<UnityWebRequestAsyncOperationConfiguredSource>
        {
            static TaskPool<UnityWebRequestAsyncOperationConfiguredSource> pool;
            public UnityWebRequestAsyncOperationConfiguredSource NextNode { get; set; }

            static UnityWebRequestAsyncOperationConfiguredSource()
            {
                TaskPool.RegisterSizeGetter(typeof(UnityWebRequestAsyncOperationConfiguredSource), () => pool.Size);
            }

            UnityWebRequestAsyncOperation asyncOperation;
            IProgress<float> progress;
            Action<ulong> reportDownloadedBytes;
            CancellationToken cancellationToken;

            UniTaskCompletionSourceCore<UnityWebRequest> core;

            UnityWebRequestAsyncOperationConfiguredSource()
            {

            }

            public static IUniTaskSource<UnityWebRequest> Create(UnityWebRequestAsyncOperation asyncOperation, PlayerLoopTiming timing, IProgress<float> progress, Action<ulong> reportDownloadedBytes, CancellationToken cancellationToken, out short token)
            {
                if (cancellationToken.IsCancellationRequested)
                {
                    return AutoResetUniTaskCompletionSource<UnityWebRequest>.CreateFromCanceled(cancellationToken, out token);
                }

                if (!pool.TryPop(out var result))
                {
                    result = new UnityWebRequestAsyncOperationConfiguredSource();
                }

                result.asyncOperation = asyncOperation;
                result.progress = progress;
                result.reportDownloadedBytes = reportDownloadedBytes;
                result.cancellationToken = cancellationToken;

                TaskTracker.TrackActiveTask(result, 3);

                PlayerLoopHelper.AddAction(timing, result);

                token = result.core.Version;
                return result;
            }

            public UnityWebRequest GetResult(short token)
            {
                try
                {
                    return core.GetResult(token);
                }
                finally
                {
                    TryReturn();
                }
            }

            void IUniTaskSource.GetResult(short token)
            {
                GetResult(token);
            }

            public UniTaskStatus GetStatus(short token)
            {
                return core.GetStatus(token);
            }

            public UniTaskStatus UnsafeGetStatus()
            {
                return core.UnsafeGetStatus();
            }

            public void OnCompleted(Action<object> continuation, object state, short token)
            {
                core.OnCompleted(continuation, state, token);
            }

            public bool MoveNext()
            {
                if (cancellationToken.IsCancellationRequested)
                {
                    asyncOperation.webRequest.Abort();
                    core.TrySetCanceled(cancellationToken);
                    return false;
                }

                if (progress != null)
                {
                    progress.Report(asyncOperation.progress);
                }

                if (reportDownloadedBytes != null)
                {
                    reportDownloadedBytes.Invoke(asyncOperation.webRequest.downloadedBytes);
                }

                if (asyncOperation.isDone)
                {
                    if (asyncOperation.webRequest.isHttpError || asyncOperation.webRequest.isNetworkError)
                    {
                        core.TrySetException(new UnityWebRequestException(asyncOperation.webRequest));
                    }
                    else
                    {
                        core.TrySetResult(asyncOperation.webRequest);
                    }
                    return false;
                }

                return true;
            }

            bool TryReturn()
            {
                TaskTracker.RemoveTracking(this);
                core.Reset();
                asyncOperation = default;
                progress = default;
                cancellationToken = default;
                return pool.TryPush(this);
            }
        }
    }
}


内容はほぼUniTaskのをコピペ。
UnityWebRequestAsyncOperationConfiguredSourceのMoveNextでprogress.Reportを呼んでいたため、
その下にreportDownloadedBytesを追加。

テスト

UnityWebRequestTest.cs
using System.Collections;
using UnityEngine.Networking;
using UnityEngine.TestTools;
using Cysharp.Threading.Tasks;
using System;
using NUnit.Framework;
using System.IO;
using DL;

namespace DLTests
{
    public class UnityWebRequestTest
    {
        [UnityTest]
        public IEnumerator DownloadFileTest() => UniTask.ToCoroutine(async () =>
        {
            string testDir = "Test/DL/DownloadFileTest";
            string url = "https://teach310.github.io/AssetBundlesForDownloadTest/SBP/Prefabs001";
            string destPath = testDir + "/Prefabs001";
            float timeout = 3f;

            // リセット
            if (Directory.Exists(testDir))
            {
                Directory.Delete(testDir, true);
            }
            Directory.CreateDirectory(testDir);

            ulong downloadedBytes = 0L;
            Action<ulong> reportDownloadedBytes = x => downloadedBytes = x;
            using (var request = UnityWebRequest.Get(url))
            {
                var downloadHandler = new DownloadHandlerFile(destPath);
                downloadHandler.removeFileOnAbort = true;
                request.downloadHandler = downloadHandler;
                try
                {
                    await request.SendWebRequest()
                        .ToUniTaskWithDownloadedBytes(reportDownloadedBytes: reportDownloadedBytes)
                        .Timeout(TimeSpan.FromSeconds(timeout));
                }
                catch (UnityWebRequestException ex)
                {
                    if (ex.IsHttpError)
                    {
                        Assert.Fail($"HTTPError status {request.responseCode} message {ex.Message}");
                    }
                    else if (ex.IsNetworkError)
                    {
                        Assert.Fail($"NetworkError status {request.responseCode} message {ex.Message}");
                    }
                    Assert.Fail(ex.Message);
                }
                catch (TimeoutException ex)
                {
                    Assert.Fail(ex.Message);
                }
                catch (Exception ex)
                {
                    // urlが間違ってるときとかに呼ばれる。
                    Assert.Fail(ex.Message);
                }
            }
            Assert.AreEqual(true, downloadedBytes > 0L);
            Directory.Delete(testDir, true);
        });
    }
}

リクエストがIsHttpErrorまたはIsNetworkErrorのときにUnityWebRequestExceptionをthrowするようになっていたことに気づけてよかった。

6
3
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
6
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?