UniTask/Assets/UniRx.Async/EnumeratorAsyncExtensions.cs

246 lines
8.4 KiB
C#

#if CSHARP_7_OR_LATER || (UNITY_2018_3_OR_NEWER && (NET_STANDARD_2_0 || NET_4_6))
#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member
using System;
using System.Collections;
using System.Reflection;
using System.Runtime.ExceptionServices;
using System.Threading;
using UniRx.Async.Internal;
using UnityEngine;
namespace UniRx.Async
{
public static class EnumeratorAsyncExtensions
{
public static IAwaiter GetAwaiter(this IEnumerator enumerator)
{
var awaiter = new EnumeratorAwaiter(enumerator, CancellationToken.None);
if (!awaiter.IsCompleted)
{
PlayerLoopHelper.AddAction(PlayerLoopTiming.Update, awaiter);
}
return awaiter;
}
public static UniTask ToUniTask(this IEnumerator enumerator)
{
var awaiter = new EnumeratorAwaiter(enumerator, CancellationToken.None);
if (!awaiter.IsCompleted)
{
PlayerLoopHelper.AddAction(PlayerLoopTiming.Update, awaiter);
}
return new UniTask(awaiter);
}
public static UniTask ConfigureAwait(this IEnumerator enumerator, PlayerLoopTiming timing = PlayerLoopTiming.Update, CancellationToken cancellationToken = default(CancellationToken))
{
var awaiter = new EnumeratorAwaiter(enumerator, cancellationToken);
if (!awaiter.IsCompleted)
{
PlayerLoopHelper.AddAction(timing, awaiter);
}
return new UniTask(awaiter);
}
class EnumeratorAwaiter : IAwaiter, IPlayerLoopItem
{
IEnumerator innerEnumerator;
CancellationToken cancellationToken;
Action continuation;
AwaiterStatus status;
ExceptionDispatchInfo exception;
public EnumeratorAwaiter(IEnumerator innerEnumerator, CancellationToken cancellationToken)
{
if (cancellationToken.IsCancellationRequested)
{
status = AwaiterStatus.Canceled;
return;
}
this.innerEnumerator = ConsumeEnumerator(innerEnumerator);
this.status = AwaiterStatus.Pending;
this.cancellationToken = cancellationToken;
this.continuation = null;
TaskTracker.TrackActiveTask(this, 2);
}
public bool IsCompleted => status.IsCompleted();
public AwaiterStatus Status => status;
public void GetResult()
{
switch (status)
{
case AwaiterStatus.Succeeded:
break;
case AwaiterStatus.Pending:
Error.ThrowNotYetCompleted();
break;
case AwaiterStatus.Faulted:
exception.Throw();
break;
case AwaiterStatus.Canceled:
Error.ThrowOperationCanceledException();
break;
default:
break;
}
}
public bool MoveNext()
{
if (cancellationToken.IsCancellationRequested)
{
InvokeContinuation(AwaiterStatus.Canceled);
return false;
}
var success = false;
try
{
if (innerEnumerator.MoveNext())
{
return true;
}
else
{
success = true;
}
}
catch (Exception ex)
{
exception = ExceptionDispatchInfo.Capture(ex);
}
InvokeContinuation(success ? AwaiterStatus.Succeeded : AwaiterStatus.Faulted);
return false;
}
void InvokeContinuation(AwaiterStatus status)
{
this.status = status;
var cont = this.continuation;
// cleanup
TaskTracker.RemoveTracking(this);
this.continuation = null;
this.cancellationToken = CancellationToken.None;
this.innerEnumerator = null;
if (cont != null) cont.Invoke();
}
public void OnCompleted(Action continuation)
{
UnsafeOnCompleted(continuation);
}
public void UnsafeOnCompleted(Action continuation)
{
Error.ThrowWhenContinuationIsAlreadyRegistered(this.continuation);
this.continuation = continuation;
}
// Unwrap YieldInstructions
static IEnumerator ConsumeEnumerator(IEnumerator enumerator)
{
while (enumerator.MoveNext())
{
var current = enumerator.Current;
if (current == null)
{
yield return null;
}
else if (current is CustomYieldInstruction)
{
// WWW, WaitForSecondsRealtime
var e2 = UnwrapWaitCustomYieldInstruction((CustomYieldInstruction)current);
while (e2.MoveNext())
{
yield return null;
}
}
else if (current is YieldInstruction)
{
IEnumerator innerCoroutine = null;
switch (current)
{
case AsyncOperation ao:
innerCoroutine = UnwrapWaitAsyncOperation(ao);
break;
case WaitForSeconds wfs:
innerCoroutine = UnwrapWaitForSeconds(wfs);
break;
}
if (innerCoroutine != null)
{
while (innerCoroutine.MoveNext())
{
yield return null;
}
}
else
{
yield return null;
}
}
else if (current is IEnumerator e3)
{
var e4 = ConsumeEnumerator(e3);
while (e4.MoveNext())
{
yield return null;
}
}
else
{
// WaitForEndOfFrame, WaitForFixedUpdate, others.
yield return null;
}
}
}
// WWW and others as CustomYieldInstruction.
static IEnumerator UnwrapWaitCustomYieldInstruction(CustomYieldInstruction yieldInstruction)
{
while (yieldInstruction.keepWaiting)
{
yield return null;
}
}
static readonly FieldInfo waitForSeconds_Seconds = typeof(WaitForSeconds).GetField("m_Seconds", BindingFlags.Instance | BindingFlags.GetField | BindingFlags.NonPublic);
static IEnumerator UnwrapWaitForSeconds(WaitForSeconds waitForSeconds)
{
var second = (float)waitForSeconds_Seconds.GetValue(waitForSeconds);
var startTime = DateTimeOffset.UtcNow;
while (true)
{
yield return null;
var elapsed = (DateTimeOffset.UtcNow - startTime).TotalSeconds;
if (elapsed >= second)
{
break;
}
};
}
static IEnumerator UnwrapWaitAsyncOperation(AsyncOperation asyncOperation)
{
while (!asyncOperation.isDone)
{
yield return null;
}
}
}
}
}
#endif