|
@ -1,4 +1,5 @@ |
|
|
using System; |
|
|
|
|
|
|
|
|
using IPA.Loader; |
|
|
|
|
|
using System; |
|
|
using System.Collections; |
|
|
using System.Collections; |
|
|
using System.Collections.Generic; |
|
|
using System.Collections.Generic; |
|
|
using System.Linq; |
|
|
using System.Linq; |
|
@ -18,9 +19,114 @@ namespace IPA.Utilities.Async |
|
|
/// <param name="task">the <see cref="Task"/> to wait for</param>
|
|
|
/// <param name="task">the <see cref="Task"/> to wait for</param>
|
|
|
/// <returns>a coroutine waiting for the given task</returns>
|
|
|
/// <returns>a coroutine waiting for the given task</returns>
|
|
|
public static IEnumerator WaitForTask(Task task) |
|
|
public static IEnumerator WaitForTask(Task task) |
|
|
|
|
|
=> WaitForTask(task, false); |
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// Stalls the coroutine until <paramref name="task"/> completes, faults, or is canceled.
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <param name="task">the <see cref="Task"/> to wait for</param>
|
|
|
|
|
|
/// <param name="throwOnFault">whether or not to throw if the task faulted</param>
|
|
|
|
|
|
/// <returns>a coroutine waiting for the given task</returns>
|
|
|
|
|
|
public static IEnumerator WaitForTask(Task task, bool throwOnFault = false) |
|
|
{ |
|
|
{ |
|
|
while (!task.IsCompleted && !task.IsCanceled && !task.IsFaulted) |
|
|
while (!task.IsCompleted && !task.IsCanceled && !task.IsFaulted) |
|
|
yield return null; |
|
|
yield return null; |
|
|
|
|
|
if (throwOnFault && task.IsFaulted) |
|
|
|
|
|
throw task.Exception; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// Binds a <see cref="Task"/> to a Unity coroutine, capturing exceptions as well as the coroutine call stack.
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <remarks>
|
|
|
|
|
|
/// <para>
|
|
|
|
|
|
/// This may be called off of the Unity main thread. If it is, the coroutine start will be scheduled using the default
|
|
|
|
|
|
/// <see cref="UnityMainThreadTaskScheduler"/> and will be run on the main thread as required by Unity.
|
|
|
|
|
|
/// </para>
|
|
|
|
|
|
/// <para>
|
|
|
|
|
|
/// Unity provides a handful of coroutine helpers that are not <see cref="IEnumerable"/>s. Most of these are not terribly
|
|
|
|
|
|
/// helpful on their own, however <see cref="UnityEngine.WaitForSeconds"/> may be. Instead, prefer to use the typical .NET
|
|
|
|
|
|
/// <see cref="Task.Wait(TimeSpan)"/> or similar overloads, or use <see cref="UnityEngine.WaitForSecondsRealtime"/>.
|
|
|
|
|
|
/// </para>
|
|
|
|
|
|
/// </remarks>
|
|
|
|
|
|
/// <param name="coroutine">the coroutine to bind to a task</param>
|
|
|
|
|
|
/// <returns>a <see cref="Task"/> that completes when <paramref name="coroutine"/> completes, and fails when it throws</returns>
|
|
|
|
|
|
public static Task AsTask(IEnumerator coroutine) |
|
|
|
|
|
{ |
|
|
|
|
|
if (!UnityGame.OnMainThread) |
|
|
|
|
|
return UnityMainThreadTaskScheduler.Factory.StartNew(() => AsTask(coroutine)).Unwrap(); |
|
|
|
|
|
|
|
|
|
|
|
var tcs = new TaskCompletionSource<VoidStruct>(coroutine, TaskCreationOptions.RunContinuationsAsynchronously); |
|
|
|
|
|
PluginComponent.Instance.StartCoroutine(new AsTaskCoroutineExecutor(coroutine, tcs)); |
|
|
|
|
|
return tcs.Task; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
private struct VoidStruct { } |
|
|
|
|
|
private class ExceptionLocation : Exception |
|
|
|
|
|
{ |
|
|
|
|
|
public ExceptionLocation(IEnumerable<string> locations) |
|
|
|
|
|
: base(string.Join("\n", locations.Select(s => "in " + s))) |
|
|
|
|
|
{ |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
private class AsTaskCoroutineExecutor : IEnumerator |
|
|
|
|
|
{ |
|
|
|
|
|
private readonly TaskCompletionSource<VoidStruct> completionSource; |
|
|
|
|
|
|
|
|
|
|
|
public AsTaskCoroutineExecutor(IEnumerator coroutine, TaskCompletionSource<VoidStruct> completion) |
|
|
|
|
|
{ |
|
|
|
|
|
completionSource = completion; |
|
|
|
|
|
enumerators.Push(coroutine); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
private readonly Stack<IEnumerator> enumerators = new Stack<IEnumerator>(2); |
|
|
|
|
|
|
|
|
|
|
|
public object Current => enumerators.FirstOrDefault()?.Current; // effectively a TryPeek
|
|
|
|
|
|
|
|
|
|
|
|
public bool MoveNext() |
|
|
|
|
|
{ |
|
|
|
|
|
do |
|
|
|
|
|
{ |
|
|
|
|
|
if (enumerators.Count == 0) |
|
|
|
|
|
{ |
|
|
|
|
|
completionSource.SetResult(new VoidStruct()); |
|
|
|
|
|
return false; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
try |
|
|
|
|
|
{ |
|
|
|
|
|
var top = enumerators.Peek(); |
|
|
|
|
|
if (top.MoveNext()) |
|
|
|
|
|
{ |
|
|
|
|
|
if (top.Current is IEnumerator enumerator) |
|
|
|
|
|
{ |
|
|
|
|
|
enumerators.Push(enumerator); |
|
|
|
|
|
continue; |
|
|
|
|
|
} |
|
|
|
|
|
else |
|
|
|
|
|
{ |
|
|
|
|
|
return true; |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
else |
|
|
|
|
|
{ // this enumerator completed, so pop it and continue
|
|
|
|
|
|
enumerators.Pop(); |
|
|
|
|
|
continue; |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
catch (Exception e) |
|
|
|
|
|
{ // execution errored
|
|
|
|
|
|
completionSource.SetException(new AggregateException( |
|
|
|
|
|
e, new ExceptionLocation(enumerators.Select(e => e.GetType().ToString())) |
|
|
|
|
|
)); |
|
|
|
|
|
return false; |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
while (true); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
public void Reset() => throw new InvalidOperationException(); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
} |
|
|
} |
|
|
} |
|
|
} |