You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
287 lines
9.8 KiB
287 lines
9.8 KiB
#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 Cysharp.Threading.Tasks.Internal; |
|
using UnityEngine; |
|
|
|
namespace Cysharp.Threading.Tasks |
|
{ |
|
public static class EnumeratorAsyncExtensions |
|
{ |
|
public static UniTask.Awaiter GetAwaiter<T>(this T enumerator) |
|
where T : IEnumerator |
|
{ |
|
var e = (IEnumerator)enumerator; |
|
Error.ThrowArgumentNullException(e, nameof(enumerator)); |
|
return new UniTask(EnumeratorPromise.Create(e, PlayerLoopTiming.Update, CancellationToken.None, out var token), token).GetAwaiter(); |
|
} |
|
|
|
public static UniTask WithCancellation(this IEnumerator enumerator, CancellationToken cancellationToken) |
|
{ |
|
Error.ThrowArgumentNullException(enumerator, nameof(enumerator)); |
|
return new UniTask(EnumeratorPromise.Create(enumerator, PlayerLoopTiming.Update, cancellationToken, out var token), token); |
|
} |
|
|
|
public static UniTask ToUniTask(this IEnumerator enumerator, PlayerLoopTiming timing = PlayerLoopTiming.Update, CancellationToken cancellationToken = default(CancellationToken)) |
|
{ |
|
Error.ThrowArgumentNullException(enumerator, nameof(enumerator)); |
|
return new UniTask(EnumeratorPromise.Create(enumerator, timing, cancellationToken, out var token), token); |
|
} |
|
|
|
public static UniTask ToUniTask(this IEnumerator enumerator, MonoBehaviour coroutineRunner) |
|
{ |
|
var source = AutoResetUniTaskCompletionSource.Create(); |
|
coroutineRunner.StartCoroutine(Core(enumerator, coroutineRunner, source)); |
|
return source.Task; |
|
} |
|
|
|
static IEnumerator Core(IEnumerator inner, MonoBehaviour coroutineRunner, AutoResetUniTaskCompletionSource source) |
|
{ |
|
yield return coroutineRunner.StartCoroutine(inner); |
|
source.TrySetResult(); |
|
} |
|
|
|
sealed class EnumeratorPromise : IUniTaskSource, IPlayerLoopItem, ITaskPoolNode<EnumeratorPromise> |
|
{ |
|
static TaskPool<EnumeratorPromise> pool; |
|
EnumeratorPromise nextNode; |
|
public ref EnumeratorPromise NextNode => ref nextNode; |
|
|
|
static EnumeratorPromise() |
|
{ |
|
TaskPool.RegisterSizeGetter(typeof(EnumeratorPromise), () => pool.Size); |
|
} |
|
|
|
IEnumerator innerEnumerator; |
|
CancellationToken cancellationToken; |
|
int initialFrame; |
|
bool loopRunning; |
|
bool calledGetResult; |
|
|
|
UniTaskCompletionSourceCore<object> core; |
|
|
|
EnumeratorPromise() |
|
{ |
|
} |
|
|
|
public static IUniTaskSource Create(IEnumerator innerEnumerator, PlayerLoopTiming timing, CancellationToken cancellationToken, out short token) |
|
{ |
|
if (cancellationToken.IsCancellationRequested) |
|
{ |
|
return AutoResetUniTaskCompletionSource.CreateFromCanceled(cancellationToken, out token); |
|
} |
|
|
|
if (!pool.TryPop(out var result)) |
|
{ |
|
result = new EnumeratorPromise(); |
|
} |
|
TaskTracker.TrackActiveTask(result, 3); |
|
|
|
result.innerEnumerator = ConsumeEnumerator(innerEnumerator); |
|
result.cancellationToken = cancellationToken; |
|
result.loopRunning = true; |
|
result.calledGetResult = false; |
|
result.initialFrame = -1; |
|
|
|
token = result.core.Version; |
|
|
|
// run immediately. |
|
if (result.MoveNext()) |
|
{ |
|
PlayerLoopHelper.AddAction(timing, result); |
|
} |
|
|
|
return result; |
|
} |
|
|
|
public void GetResult(short token) |
|
{ |
|
try |
|
{ |
|
calledGetResult = true; |
|
core.GetResult(token); |
|
} |
|
finally |
|
{ |
|
if (!loopRunning) |
|
{ |
|
TryReturn(); |
|
} |
|
} |
|
} |
|
|
|
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 (calledGetResult) |
|
{ |
|
loopRunning = false; |
|
TryReturn(); |
|
return false; |
|
} |
|
|
|
if (innerEnumerator == null) // invalid status, returned but loop running? |
|
{ |
|
return false; |
|
} |
|
|
|
if (cancellationToken.IsCancellationRequested) |
|
{ |
|
loopRunning = false; |
|
core.TrySetCanceled(cancellationToken); |
|
return false; |
|
} |
|
|
|
if (initialFrame == -1) |
|
{ |
|
// Time can not touch in threadpool. |
|
if (PlayerLoopHelper.IsMainThread) |
|
{ |
|
initialFrame = Time.frameCount; |
|
} |
|
} |
|
else if (initialFrame == Time.frameCount) |
|
{ |
|
return true; // already executed in first frame, skip. |
|
} |
|
|
|
try |
|
{ |
|
if (innerEnumerator.MoveNext()) |
|
{ |
|
return true; |
|
} |
|
} |
|
catch (Exception ex) |
|
{ |
|
loopRunning = false; |
|
core.TrySetException(ex); |
|
return false; |
|
} |
|
|
|
loopRunning = false; |
|
core.TrySetResult(null); |
|
return false; |
|
} |
|
|
|
bool TryReturn() |
|
{ |
|
TaskTracker.RemoveTracking(this); |
|
core.Reset(); |
|
innerEnumerator = default; |
|
cancellationToken = default; |
|
|
|
return pool.TryPush(this); |
|
} |
|
|
|
// 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 cyi) |
|
{ |
|
// WWW, WaitForSecondsRealtime |
|
while (cyi.keepWaiting) |
|
{ |
|
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 |
|
{ |
|
goto WARN; |
|
} |
|
} |
|
else if (current is IEnumerator e3) |
|
{ |
|
var e4 = ConsumeEnumerator(e3); |
|
while (e4.MoveNext()) |
|
{ |
|
yield return null; |
|
} |
|
} |
|
else |
|
{ |
|
goto WARN; |
|
} |
|
|
|
continue; |
|
|
|
WARN: |
|
// WaitForEndOfFrame, WaitForFixedUpdate, others. |
|
UnityEngine.Debug.LogWarning($"yield {current.GetType().Name} is not supported on await IEnumerator or IEnumerator.ToUniTask(), please use ToUniTask(MonoBehaviour coroutineRunner) instead."); |
|
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 elapsed = 0.0f; |
|
while (true) |
|
{ |
|
yield return null; |
|
|
|
elapsed += Time.deltaTime; |
|
if (elapsed >= second) |
|
{ |
|
break; |
|
} |
|
}; |
|
} |
|
|
|
static IEnumerator UnwrapWaitAsyncOperation(AsyncOperation asyncOperation) |
|
{ |
|
while (!asyncOperation.isDone) |
|
{ |
|
yield return null; |
|
} |
|
} |
|
} |
|
} |
|
} |