Browse Source

Added some more helpers to Async.Coroutines for easier interop between Tasks and Coroutines

pull/46/head
Anairkoen Schno 4 years ago
parent
commit
a45d423447
2 changed files with 121 additions and 14 deletions
  1. +14
    -13
      IPA.Loader/Loader/PluginComponent.cs
  2. +107
    -1
      IPA.Loader/Utilities/Async/Coroutines.cs

+ 14
- 13
IPA.Loader/Loader/PluginComponent.cs View File

@ -15,14 +15,15 @@ namespace IPA.Loader
private CompositeBSPlugin bsPlugins;
private CompositeIPAPlugin ipaPlugins;
private bool quitting;
public static PluginComponent Instance;
private static bool initialized = false;
internal static PluginComponent Create()
{
return new GameObject("IPA_PluginManager").AddComponent<PluginComponent>();
return Instance = new GameObject("IPA_PluginManager").AddComponent<PluginComponent>();
}
void Awake()
internal void Awake()
{
DontDestroyOnLoad(gameObject);
@ -38,7 +39,7 @@ namespace IPA.Loader
ipaPlugins = new CompositeIPAPlugin(PluginManager.Plugins);
#pragma warning restore 618
#if BeatSaber
#if BeatSaber // TODO: remove this
gameObject.AddComponent<Updating.BeatMods.Updater>();
#endif
@ -61,7 +62,7 @@ namespace IPA.Loader
}
}
void Update()
internal void Update()
{
bsPlugins.OnUpdate();
ipaPlugins.OnUpdate();
@ -71,27 +72,27 @@ namespace IPA.Loader
StartCoroutine(unitySched.Coroutine());
}
void LateUpdate()
internal void LateUpdate()
{
bsPlugins.OnLateUpdate();
ipaPlugins.OnLateUpdate();
}
void FixedUpdate()
internal void FixedUpdate()
{
bsPlugins.OnFixedUpdate();
ipaPlugins.OnFixedUpdate();
}
void OnDestroy()
internal void OnDestroy()
{
if (!quitting)
{
Create();
}
}
void OnApplicationQuit()
}
internal void OnApplicationQuit()
{
SceneManager.activeSceneChanged -= OnActiveSceneChanged;
SceneManager.sceneLoaded -= OnSceneLoaded;
@ -105,17 +106,17 @@ namespace IPA.Loader
quitting = true;
}
void OnLevelWasLoaded(int level)
internal void OnLevelWasLoaded(int level)
{
ipaPlugins.OnLevelWasLoaded(level);
}
void OnLevelWasInitialized(int level)
internal void OnLevelWasInitialized(int level)
{
ipaPlugins.OnLevelWasInitialized(level);
}
void OnSceneLoaded(Scene scene, LoadSceneMode sceneMode)
private void OnSceneLoaded(Scene scene, LoadSceneMode sceneMode)
{
bsPlugins.OnSceneLoaded(scene, sceneMode);
}


+ 107
- 1
IPA.Loader/Utilities/Async/Coroutines.cs View File

@ -1,4 +1,5 @@
using System;
using IPA.Loader;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
@ -18,9 +19,114 @@ namespace IPA.Utilities.Async
/// <param name="task">the <see cref="Task"/> to wait for</param>
/// <returns>a coroutine waiting for the given task</returns>
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)
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();
}
}
}

Loading…
Cancel
Save