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

Loading…
Cancel
Save