406 lines
15 KiB
C#
406 lines
15 KiB
C#
// MIT License
|
|
|
|
// Copyright(c) 2016 Modest Tree Media Inc
|
|
|
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
// of this software and associated documentation files (the "Software"), to deal
|
|
// in the Software without restriction, including without limitation the rights
|
|
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
// copies of the Software, and to permit persons to whom the Software is
|
|
// furnished to do so, subject to the following conditions:
|
|
|
|
// The above copyright notice and this permission notice shall be included in all
|
|
// copies or substantial portions of the Software.
|
|
|
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
// SOFTWARE.
|
|
|
|
using System;
|
|
using System.Collections;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using System.Reflection;
|
|
using System.Runtime.CompilerServices;
|
|
using System.Runtime.ExceptionServices;
|
|
using System.Text;
|
|
using System.Threading;
|
|
using UnityEngine;
|
|
using Object = UnityEngine.Object;
|
|
|
|
namespace Microsoft.MixedReality.Toolkit.Utilities
|
|
{
|
|
/// <summary>
|
|
/// We could just add a generic GetAwaiter to YieldInstruction and CustomYieldInstruction
|
|
/// but instead we add specific methods to each derived class to allow for return values
|
|
/// that make the most sense for the specific instruction type.
|
|
/// </summary>
|
|
public static class AwaiterExtensions
|
|
{
|
|
public static SimpleCoroutineAwaiter GetAwaiter(this WaitForSeconds instruction)
|
|
{
|
|
return GetAwaiterReturnVoid(instruction);
|
|
}
|
|
|
|
public static SimpleCoroutineAwaiter GetAwaiter(this WaitForUpdate instruction)
|
|
{
|
|
return GetAwaiterReturnVoid(instruction);
|
|
}
|
|
|
|
public static SimpleCoroutineAwaiter GetAwaiter(this WaitForEndOfFrame instruction)
|
|
{
|
|
return GetAwaiterReturnVoid(instruction);
|
|
}
|
|
|
|
public static SimpleCoroutineAwaiter GetAwaiter(this WaitForFixedUpdate instruction)
|
|
{
|
|
return GetAwaiterReturnVoid(instruction);
|
|
}
|
|
|
|
public static SimpleCoroutineAwaiter GetAwaiter(this WaitForSecondsRealtime instruction)
|
|
{
|
|
return GetAwaiterReturnVoid(instruction);
|
|
}
|
|
|
|
public static SimpleCoroutineAwaiter GetAwaiter(this WaitUntil instruction)
|
|
{
|
|
return GetAwaiterReturnVoid(instruction);
|
|
}
|
|
|
|
public static SimpleCoroutineAwaiter GetAwaiter(this WaitWhile instruction)
|
|
{
|
|
return GetAwaiterReturnVoid(instruction);
|
|
}
|
|
|
|
#if !UNITY_2023_1_OR_NEWER
|
|
public static SimpleCoroutineAwaiter<AsyncOperation> GetAwaiter(this AsyncOperation instruction)
|
|
{
|
|
return GetAwaiterReturnSelf(instruction);
|
|
}
|
|
|
|
public static SimpleCoroutineAwaiter<Object> GetAwaiter(this ResourceRequest instruction)
|
|
{
|
|
var awaiter = new SimpleCoroutineAwaiter<Object>();
|
|
RunOnUnityScheduler(() => AsyncCoroutineRunner.Instance.StartCoroutine(
|
|
InstructionWrappers.ResourceRequest(awaiter, instruction)));
|
|
return awaiter;
|
|
}
|
|
|
|
public static SimpleCoroutineAwaiter<AssetBundle> GetAwaiter(this AssetBundleCreateRequest instruction)
|
|
{
|
|
var awaiter = new SimpleCoroutineAwaiter<AssetBundle>();
|
|
RunOnUnityScheduler(() => AsyncCoroutineRunner.Instance.StartCoroutine(
|
|
InstructionWrappers.AssetBundleCreateRequest(awaiter, instruction)));
|
|
return awaiter;
|
|
}
|
|
|
|
public static SimpleCoroutineAwaiter<Object> GetAwaiter(this AssetBundleRequest instruction)
|
|
{
|
|
var awaiter = new SimpleCoroutineAwaiter<Object>();
|
|
RunOnUnityScheduler(() => AsyncCoroutineRunner.Instance.StartCoroutine(
|
|
InstructionWrappers.AssetBundleRequest(awaiter, instruction)));
|
|
return awaiter;
|
|
}
|
|
#endif
|
|
|
|
public static SimpleCoroutineAwaiter<T> GetAwaiter<T>(this IEnumerator<T> coroutine)
|
|
{
|
|
var awaiter = new SimpleCoroutineAwaiter<T>();
|
|
RunOnUnityScheduler(() => AsyncCoroutineRunner.Instance.StartCoroutine(
|
|
new CoroutineWrapper<T>(coroutine, awaiter).Run()));
|
|
return awaiter;
|
|
}
|
|
|
|
public static SimpleCoroutineAwaiter<object> GetAwaiter(this IEnumerator coroutine)
|
|
{
|
|
var awaiter = new SimpleCoroutineAwaiter<object>();
|
|
RunOnUnityScheduler(() => AsyncCoroutineRunner.Instance.StartCoroutine(
|
|
new CoroutineWrapper<object>(coroutine, awaiter).Run()));
|
|
return awaiter;
|
|
}
|
|
|
|
private static SimpleCoroutineAwaiter GetAwaiterReturnVoid(object instruction)
|
|
{
|
|
var awaiter = new SimpleCoroutineAwaiter();
|
|
RunOnUnityScheduler(() => AsyncCoroutineRunner.Instance.StartCoroutine(
|
|
InstructionWrappers.ReturnVoid(awaiter, instruction)));
|
|
return awaiter;
|
|
}
|
|
|
|
private static SimpleCoroutineAwaiter<T> GetAwaiterReturnSelf<T>(T instruction)
|
|
{
|
|
var awaiter = new SimpleCoroutineAwaiter<T>();
|
|
RunOnUnityScheduler(() => AsyncCoroutineRunner.Instance.StartCoroutine(
|
|
InstructionWrappers.ReturnSelf(awaiter, instruction)));
|
|
return awaiter;
|
|
}
|
|
|
|
private static void RunOnUnityScheduler(Action action)
|
|
{
|
|
if (SynchronizationContext.Current == SyncContextUtility.UnitySynchronizationContext)
|
|
{
|
|
action();
|
|
}
|
|
else
|
|
{
|
|
// Make sure there is a running instance of AsyncCoroutineRunner before calling AsyncCoroutineRunner.Post
|
|
// If not warn the user. Note we cannot call AsyncCoroutineRunner.Instance here as that getter contains
|
|
// calls to Unity functions that can only be run on the Unity thread
|
|
if (!AsyncCoroutineRunner.IsInstanceRunning)
|
|
{
|
|
Debug.LogWarning("There is no active AsyncCoroutineRunner when an action is posted. Place a GameObject " +
|
|
"at the root of the scene and attach the AsyncCoroutineRunner script to make it function properly.");
|
|
}
|
|
AsyncCoroutineRunner.Post(action);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Processes Coroutine and notifies completion with result.
|
|
/// </summary>
|
|
/// <typeparam name="T">The result type.</typeparam>
|
|
public class SimpleCoroutineAwaiter<T> : INotifyCompletion
|
|
{
|
|
private Exception exception;
|
|
private Action continuation;
|
|
private T result;
|
|
|
|
public bool IsCompleted { get; private set; }
|
|
|
|
public T GetResult()
|
|
{
|
|
Debug.Assert(IsCompleted);
|
|
|
|
if (exception != null)
|
|
{
|
|
ExceptionDispatchInfo.Capture(exception).Throw();
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
public void Complete(T taskResult, Exception e)
|
|
{
|
|
Debug.Assert(!IsCompleted);
|
|
|
|
IsCompleted = true;
|
|
exception = e;
|
|
result = taskResult;
|
|
|
|
// Always trigger the continuation on the unity thread
|
|
// when awaiting on unity yield instructions.
|
|
if (continuation != null)
|
|
{
|
|
RunOnUnityScheduler(continuation);
|
|
}
|
|
}
|
|
|
|
void INotifyCompletion.OnCompleted(Action notifyContinuation)
|
|
{
|
|
Debug.Assert(continuation == null);
|
|
Debug.Assert(!IsCompleted);
|
|
|
|
continuation = notifyContinuation;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Processes Coroutine and notifies completion.
|
|
/// </summary>
|
|
public class SimpleCoroutineAwaiter : INotifyCompletion
|
|
{
|
|
private Exception exception;
|
|
private Action continuation;
|
|
|
|
public bool IsCompleted { get; private set; }
|
|
|
|
public void GetResult()
|
|
{
|
|
Debug.Assert(IsCompleted);
|
|
|
|
if (exception != null)
|
|
{
|
|
ExceptionDispatchInfo.Capture(exception).Throw();
|
|
}
|
|
}
|
|
|
|
public void Complete(Exception e)
|
|
{
|
|
Debug.Assert(!IsCompleted);
|
|
|
|
IsCompleted = true;
|
|
exception = e;
|
|
|
|
// Always trigger the continuation on the unity thread
|
|
// when awaiting on unity yield instructions.
|
|
if (continuation != null)
|
|
{
|
|
RunOnUnityScheduler(continuation);
|
|
}
|
|
}
|
|
|
|
void INotifyCompletion.OnCompleted(Action notifyContinuation)
|
|
{
|
|
Debug.Assert(continuation == null);
|
|
Debug.Assert(!IsCompleted);
|
|
|
|
continuation = notifyContinuation;
|
|
}
|
|
}
|
|
|
|
private class CoroutineWrapper<T>
|
|
{
|
|
private readonly SimpleCoroutineAwaiter<T> awaiter;
|
|
private readonly Stack<IEnumerator> processStack;
|
|
|
|
public CoroutineWrapper(IEnumerator coroutine, SimpleCoroutineAwaiter<T> awaiter)
|
|
{
|
|
processStack = new Stack<IEnumerator>();
|
|
processStack.Push(coroutine);
|
|
this.awaiter = awaiter;
|
|
}
|
|
|
|
public IEnumerator Run()
|
|
{
|
|
while (true)
|
|
{
|
|
var topWorker = processStack.Peek();
|
|
|
|
bool isDone;
|
|
|
|
try
|
|
{
|
|
isDone = !topWorker.MoveNext();
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
// The IEnumerators we have in the process stack do not tell us the
|
|
// actual names of the coroutine methods but it does tell us the objects
|
|
// that the IEnumerators are associated with, so we can at least try
|
|
// adding that to the exception output
|
|
var objectTrace = GenerateObjectTrace(processStack);
|
|
awaiter.Complete(default(T), objectTrace.Any() ? new Exception(GenerateObjectTraceMessage(objectTrace), e) : e);
|
|
|
|
yield break;
|
|
}
|
|
|
|
if (isDone)
|
|
{
|
|
processStack.Pop();
|
|
|
|
if (processStack.Count == 0)
|
|
{
|
|
awaiter.Complete((T)topWorker.Current, null);
|
|
yield break;
|
|
}
|
|
}
|
|
|
|
// We could just yield return nested IEnumerator's here but we choose to do
|
|
// our own handling here so that we can catch exceptions in nested coroutines
|
|
// instead of just top level coroutine
|
|
if (topWorker.Current is IEnumerator item)
|
|
{
|
|
processStack.Push(item);
|
|
}
|
|
else
|
|
{
|
|
// Return the current value to the unity engine so it can handle things like
|
|
// WaitForSeconds, WaitToEndOfFrame, etc.
|
|
yield return topWorker.Current;
|
|
}
|
|
}
|
|
}
|
|
|
|
private static string GenerateObjectTraceMessage(List<Type> objTrace)
|
|
{
|
|
var result = new StringBuilder();
|
|
|
|
foreach (var objType in objTrace)
|
|
{
|
|
if (result.Length != 0)
|
|
{
|
|
result.Append(" -> ");
|
|
}
|
|
|
|
result.Append(objType);
|
|
}
|
|
|
|
result.AppendLine();
|
|
return $"Unity Coroutine Object Trace: {result}";
|
|
}
|
|
|
|
private static List<Type> GenerateObjectTrace(IEnumerable<IEnumerator> enumerators)
|
|
{
|
|
var objTrace = new List<Type>();
|
|
|
|
foreach (var enumerator in enumerators)
|
|
{
|
|
// NOTE: This only works with scripting engine 4.6
|
|
// And could easily stop working with unity updates
|
|
var field = enumerator.GetType().GetField("$this", BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance);
|
|
|
|
if (field == null)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
var obj = field.GetValue(enumerator);
|
|
|
|
if (obj == null)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
var objType = obj.GetType();
|
|
|
|
if (!objTrace.Any() || objType != objTrace.Last())
|
|
{
|
|
objTrace.Add(objType);
|
|
}
|
|
}
|
|
|
|
objTrace.Reverse();
|
|
return objTrace;
|
|
}
|
|
}
|
|
|
|
private static class InstructionWrappers
|
|
{
|
|
public static IEnumerator ReturnVoid(SimpleCoroutineAwaiter awaiter, object instruction)
|
|
{
|
|
// For simple instructions we assume that they don't throw exceptions
|
|
yield return instruction;
|
|
awaiter.Complete(null);
|
|
}
|
|
|
|
public static IEnumerator AssetBundleCreateRequest(SimpleCoroutineAwaiter<AssetBundle> awaiter, AssetBundleCreateRequest instruction)
|
|
{
|
|
yield return instruction;
|
|
awaiter.Complete(instruction.assetBundle, null);
|
|
}
|
|
|
|
public static IEnumerator ReturnSelf<T>(SimpleCoroutineAwaiter<T> awaiter, T instruction)
|
|
{
|
|
yield return instruction;
|
|
awaiter.Complete(instruction, null);
|
|
}
|
|
|
|
public static IEnumerator AssetBundleRequest(SimpleCoroutineAwaiter<Object> awaiter, AssetBundleRequest instruction)
|
|
{
|
|
yield return instruction;
|
|
awaiter.Complete(instruction.asset, null);
|
|
}
|
|
|
|
public static IEnumerator ResourceRequest(SimpleCoroutineAwaiter<Object> awaiter, ResourceRequest instruction)
|
|
{
|
|
yield return instruction;
|
|
awaiter.Complete(instruction.asset, null);
|
|
}
|
|
}
|
|
}
|
|
}
|