using IPA.Loader;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace IPA.Utilities.Async
{
///
/// A class providing coroutine helpers.
///
public static class Coroutines
{
///
/// Stalls the coroutine until completes, faults, or is canceled.
///
/// the to wait for
/// a coroutine waiting for the given task
public static IEnumerator WaitForTask(Task task)
=> WaitForTask(task, false);
///
/// Stalls the coroutine until completes, faults, or is canceled.
///
/// the to wait for
/// whether or not to throw if the task faulted
/// a coroutine waiting for the given task
public static IEnumerator WaitForTask(Task task, bool throwOnFault = false)
{
while (!task.IsCompleted && !task.IsCanceled && !task.IsFaulted)
yield return null;
if (throwOnFault && task.IsFaulted)
throw task.Exception;
}
///
/// Binds a to a Unity coroutine, capturing exceptions as well as the coroutine call stack.
///
///
///
/// This may be called off of the Unity main thread. If it is, the coroutine start will be scheduled using the default
/// and will be run on the main thread as required by Unity.
///
///
/// Unity provides a handful of coroutine helpers that are not s. Most of these are not terribly
/// helpful on their own, however may be. Instead, prefer to use the typical .NET
/// or similar overloads, or use .
///
///
/// the coroutine to bind to a task
/// a that completes when completes, and fails when it throws
public static Task AsTask(IEnumerator coroutine)
{
if (!UnityGame.OnMainThread)
return UnityMainThreadTaskScheduler.Factory.StartNew(() => AsTask(coroutine)).Unwrap();
var tcs = new TaskCompletionSource(coroutine, TaskCreationOptions.RunContinuationsAsynchronously);
PluginComponent.Instance.StartCoroutine(new AsTaskCoroutineExecutor(coroutine, tcs));
return tcs.Task;
}
private struct VoidStruct { }
private class ExceptionLocation : Exception
{
public ExceptionLocation(IEnumerable locations)
: base(string.Join("\n", locations.Select(s => "in " + s)))
{
}
}
private class AsTaskCoroutineExecutor : IEnumerator
{
private readonly TaskCompletionSource completionSource;
public AsTaskCoroutineExecutor(IEnumerator coroutine, TaskCompletionSource completion)
{
completionSource = completion;
enumerators.Push(coroutine);
}
private readonly Stack enumerators = new Stack(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();
}
}
}