diff --git a/UnityProject/Assets/Plugins/AsyncAwaitUtil/Source/IEnumeratorAwaitExtensions.cs b/UnityProject/Assets/Plugins/AsyncAwaitUtil/Source/IEnumeratorAwaitExtensions.cs index 1c5273f..a19fc26 100644 --- a/UnityProject/Assets/Plugins/AsyncAwaitUtil/Source/IEnumeratorAwaitExtensions.cs +++ b/UnityProject/Assets/Plugins/AsyncAwaitUtil/Source/IEnumeratorAwaitExtensions.cs @@ -289,11 +289,20 @@ public IEnumerator Run() } } - // 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) + if (!Application.isPlaying + && topWorker.Current is WaitForSecondsRealtime) { + // Return `WaitForSecondsRealtime` to + // `EditModeCoroutineRunner` since its + // `IEnumerator` behaviour doesn't work + // correctly in Edit Mode + yield return topWorker.Current; + } + else if (topWorker.Current is IEnumerator) + { + // 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 _processStack.Push((IEnumerator)topWorker.Current); } else diff --git a/UnityProject/Assets/Plugins/AsyncAwaitUtil/Source/Internal/AsyncCoroutineRunner.cs b/UnityProject/Assets/Plugins/AsyncAwaitUtil/Source/Internal/AsyncCoroutineRunner.cs index a3f5d6a..31defa4 100644 --- a/UnityProject/Assets/Plugins/AsyncAwaitUtil/Source/Internal/AsyncCoroutineRunner.cs +++ b/UnityProject/Assets/Plugins/AsyncAwaitUtil/Source/Internal/AsyncCoroutineRunner.cs @@ -6,30 +6,26 @@ namespace UnityAsyncAwaitUtil { - public class AsyncCoroutineRunner : MonoBehaviour + public interface ICoroutineRunner { - static AsyncCoroutineRunner _instance; + void StartCoroutine(IEnumerator coroutine); + } - public static AsyncCoroutineRunner Instance + public class AsyncCoroutineRunner : MonoBehaviour + { + public static ICoroutineRunner Instance { get { - if (_instance == null) - { - _instance = new GameObject("AsyncCoroutineRunner") - .AddComponent(); - } - - return _instance; +#if UNITY_EDITOR + if (Application.isPlaying) + return PlayModeCoroutineRunner.Instance; + else + return EditModeCoroutineRunner.Instance; +#else + return PlayModeCoroutineRunner.Instance; +#endif } } - - void Awake() - { - // Don't show in scene hierarchy - gameObject.hideFlags = HideFlags.HideAndDontSave; - - DontDestroyOnLoad(gameObject); - } } } diff --git a/UnityProject/Assets/Plugins/AsyncAwaitUtil/Source/Internal/EditModeCoroutineRunner.cs b/UnityProject/Assets/Plugins/AsyncAwaitUtil/Source/Internal/EditModeCoroutineRunner.cs new file mode 100644 index 0000000..0ff348f --- /dev/null +++ b/UnityProject/Assets/Plugins/AsyncAwaitUtil/Source/Internal/EditModeCoroutineRunner.cs @@ -0,0 +1,241 @@ +#if UNITY_EDITOR +using System; +using Debug = UnityEngine.Debug; +using System.Collections; +using System.Collections.Generic; +using System.Diagnostics; +using System.Reflection; +using UnityEditor; +using UnityEngine; + +namespace UnityAsyncAwaitUtil +{ + /// + /// This class runs coroutines in Edit Mode. + /// + /// In Edit Mode, Unity does *not* automatically automatically advance + /// coroutines in each frame by calling `MoveNext`, like it does in Play + /// Mode. Instead, we keep track of the active coroutines ourselves and + /// call `MoveNext` on them in response to the `EditorApplication.update` + /// event. + /// + public class EditModeCoroutineRunner : ICoroutineRunner + { + private static EditModeCoroutineRunner _instance; + + /// + /// Return the singleton instance of EditModeCoroutineRunner. + /// + public static EditModeCoroutineRunner Instance + { + get + { + if (_instance == null) + _instance = new EditModeCoroutineRunner(); + return _instance; + } + } + + /// + /// Timer class used to emulate `WaitForSeconds` and + /// `WaitForSecondsRealtime` in Edit Mode. + /// + private class WaitTimer + { + private readonly Stopwatch _stopwatch; + private readonly float _secondsToWait; + + public WaitTimer(float secondsToWait) + { + _secondsToWait = secondsToWait; + _stopwatch = new Stopwatch(); + _stopwatch.Start(); + } + + public bool IsDone + { + get + { + _stopwatch.Stop(); + if (_stopwatch.Elapsed.Seconds >= _secondsToWait) + return true; + _stopwatch.Start(); + return false; + } + } + } + + /// + /// The list of currently running coroutines. + /// + private List _coroutines; + + /// + /// A list of WaitTimer objects with a one-to-one + /// correspondence to the elements of `_coroutines`. + /// A coroutine's WaitTimer entry will be null + /// unless that coroutine is currently waiting on a + /// `WaitForSeconds` or `WaitForSecondsRealtime` + /// instruction. + /// + private List _waitTimers; + + private EditModeCoroutineRunner() + { + _coroutines = new List(); + _waitTimers = new List(); + + EditorApplication.update += Update; + } + + public void StartCoroutine(IEnumerator coroutine) + { + // Make first call to `coroutine.MoveNext`, so that + // we can safely use `coroutine.Current` in `Update`. + if (coroutine.MoveNext()) + { + _coroutines.Add(coroutine); + _waitTimers.Add(null); + } + } + + /// + /// Return the time in seconds of a `WaitForSecondsRealtime` object. + /// + private float GetSeconds(WaitForSecondsRealtime waitObj) + { + // In Unity 2018.3, the `waitTime` field was changed from protected + // to public and the value was changed from + // `Time.realtimeSinceStartup + seconds` to `seconds`. The best way + // to understand the code below is to clone the `UnityCsReference` + // repo and view the source file history with + // `git log --follow --patch -- Runtimetime/Export/Scripting/WaitForSecondsRealtime.cs` + +#if UNITY_2018_3_OR_NEWER + return waitObj.waitTime; +#else + FieldInfo field = waitObj.GetType() + .GetField("waitTime", BindingFlags.Instance + | BindingFlags.NonPublic); + + return (float)field.GetValue(waitObj) + - Time.realtimeSinceStartup; +#endif + } + + /// + /// Return the time in seconds of a `WaitForSeconds` object. + /// + private float GetSeconds(WaitForSeconds waitObj) + { + FieldInfo field = waitObj + .GetType().GetField("m_Seconds", + BindingFlags.Instance + | BindingFlags.NonPublic); + + return (float)field.GetValue(waitObj); + } + + /// + /// Return the time in seconds for a `WaitForSeconds` + /// or `WaitForSecondsRealtime` object. + /// + private float GetSecondsFromWaitObject(object waitObj) + { + WaitForSeconds waitForSeconds = waitObj as WaitForSeconds; + if (waitForSeconds != null) + return GetSeconds(waitForSeconds); + + WaitForSecondsRealtime waitForSecondsRealtime + = waitObj as WaitForSecondsRealtime; + if (waitForSecondsRealtime != null) + return GetSeconds(waitForSecondsRealtime); + + Debug.LogError("GetSecondsFromWaitObject returning 0f: " + + "unrecognized argument type"); + return 0.0f; + } + + private void Update() + { + // Loop in reverse order so that we can remove + // completed coroutines/timers while iterating. + + for (int i = _coroutines.Count - 1; i >= 0; i--) + { + // object returned by `yield return` + + var yieldInstruction = _coroutines[i].Current; + + // Emulate `WaitForSeconds` and `WaitForSecondsRealtime` + // behaviour in Edit Mode + + if (yieldInstruction is WaitForSeconds + || yieldInstruction is WaitForSecondsRealtime) + { + if (_waitTimers[i] == null) + { + float secondsToWait + = GetSecondsFromWaitObject(yieldInstruction); + _waitTimers[i] = new WaitTimer(secondsToWait); + } + + if (_waitTimers[i].IsDone + && !_coroutines[i].MoveNext()) + { + _coroutines.RemoveAt(i); + _waitTimers.RemoveAt(i); + } + + continue; + } + + // `CustomYieldInstruction`s provide an + // `IEnumerator` interface. + // + // Examples of `CustomYieldInstruction`: + // `WaitUntil`, `WaitWhile`, `WWW`. + + IEnumerator enumerator = yieldInstruction as IEnumerator; + if (enumerator != null) + { + if (!enumerator.MoveNext() && !_coroutines[i].MoveNext()) + { + _coroutines.RemoveAt(i); + _waitTimers.RemoveAt(i); + } + + continue; + } + + // Examples of `AsyncOperation`: + // `ResourceRequest`, `AssetBundleRequest` + // and `AssetBundleCreateRequest`. + + AsyncOperation asyncOperation + = yieldInstruction as AsyncOperation; + if (asyncOperation != null) + { + if (asyncOperation.isDone && !_coroutines[i].MoveNext()) + { + _coroutines.RemoveAt(i); + _waitTimers.RemoveAt(i); + } + + continue; + } + + // Default case: `yield return` is `null` or is a type with no + // special Unity-defined behaviour. + + if (!_coroutines[i].MoveNext()) + { + _coroutines.RemoveAt(i); + _waitTimers.RemoveAt(i); + } + } + } + } +} + +#endif diff --git a/UnityProject/Assets/Plugins/AsyncAwaitUtil/Source/Internal/EditModeCoroutineRunner.cs.meta b/UnityProject/Assets/Plugins/AsyncAwaitUtil/Source/Internal/EditModeCoroutineRunner.cs.meta new file mode 100644 index 0000000..ded4006 --- /dev/null +++ b/UnityProject/Assets/Plugins/AsyncAwaitUtil/Source/Internal/EditModeCoroutineRunner.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: 11805ab7682a12f46b6609cac28ff88f +timeCreated: 1572634596 +licenseType: Free +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Assets/Plugins/AsyncAwaitUtil/Source/Internal/PlayModeCoroutineRunner.cs b/UnityProject/Assets/Plugins/AsyncAwaitUtil/Source/Internal/PlayModeCoroutineRunner.cs new file mode 100644 index 0000000..04359ad --- /dev/null +++ b/UnityProject/Assets/Plugins/AsyncAwaitUtil/Source/Internal/PlayModeCoroutineRunner.cs @@ -0,0 +1,40 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using UnityEngine; + +namespace UnityAsyncAwaitUtil +{ + public class PlayModeCoroutineRunner : MonoBehaviour, ICoroutineRunner + { + static PlayModeCoroutineRunner _instance; + + public static PlayModeCoroutineRunner Instance + { + get + { + if (_instance == null) + { + _instance = new GameObject("PlayModeCoroutineRunner") + .AddComponent(); + } + + return _instance; + } + } + + public new void StartCoroutine(IEnumerator coroutine) + { + base.StartCoroutine(coroutine); + } + + void Awake() + { + // Don't show in scene hierarchy + gameObject.hideFlags = HideFlags.HideAndDontSave; + + DontDestroyOnLoad(gameObject); + } + } +} diff --git a/UnityProject/Assets/Plugins/AsyncAwaitUtil/Source/Internal/PlayModeCoroutineRunner.cs.meta b/UnityProject/Assets/Plugins/AsyncAwaitUtil/Source/Internal/PlayModeCoroutineRunner.cs.meta new file mode 100644 index 0000000..4faa0bb --- /dev/null +++ b/UnityProject/Assets/Plugins/AsyncAwaitUtil/Source/Internal/PlayModeCoroutineRunner.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: 5ac4365e029940a468d5662527e41de7 +timeCreated: 1506153419 +licenseType: Free +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Assets/Plugins/AsyncAwaitUtil/Source/Internal/SyncContextUtil.cs b/UnityProject/Assets/Plugins/AsyncAwaitUtil/Source/Internal/SyncContextUtil.cs index 3d505a4..22bf8cd 100644 --- a/UnityProject/Assets/Plugins/AsyncAwaitUtil/Source/Internal/SyncContextUtil.cs +++ b/UnityProject/Assets/Plugins/AsyncAwaitUtil/Source/Internal/SyncContextUtil.cs @@ -6,10 +6,17 @@ using System.Threading.Tasks; using UnityEngine; +#if UNITY_EDITOR +using UnityEditor; +#endif + namespace UnityAsyncAwaitUtil { public static class SyncContextUtil { +#if UNITY_EDITOR + [InitializeOnLoadMethod()] +#endif [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSceneLoad)] static void Install() { diff --git a/UnityProject/Assets/Plugins/AsyncAwaitUtil/Tests/AsyncTests.unity b/UnityProject/Assets/Plugins/AsyncAwaitUtil/Tests/AsyncTests.unity index 4eff9b1..a2bb38f 100644 --- a/UnityProject/Assets/Plugins/AsyncAwaitUtil/Tests/AsyncTests.unity +++ b/UnityProject/Assets/Plugins/AsyncAwaitUtil/Tests/AsyncTests.unity @@ -38,7 +38,7 @@ RenderSettings: m_ReflectionIntensity: 1 m_CustomReflection: {fileID: 0} m_Sun: {fileID: 0} - m_IndirectSpecularColor: {r: 0.37311992, g: 0.38074034, b: 0.35872713, a: 1} + m_IndirectSpecularColor: {r: 0.37311953, g: 0.38074014, b: 0.3587274, a: 1} --- !u!157 &3 LightmapSettings: m_ObjectHideFlags: 0 @@ -221,7 +221,7 @@ MonoBehaviour: m_GameObject: {fileID: 1790199439} m_Enabled: 1 m_EditorHideFlags: 0 - m_Script: {fileID: 11500000, guid: cb4f149e5fdd59e45958e5b591ca42e6, type: 3} + m_Script: {fileID: 11500000, guid: ed2c06f606cdd4c4493e83b7fce7f5fd, type: 3} m_Name: m_EditorClassIdentifier: _buttonSettings: diff --git a/UnityProject/Assets/Plugins/AsyncAwaitUtil/Tests/AsyncUtilTests.cs b/UnityProject/Assets/Plugins/AsyncAwaitUtil/Tests/AsyncUtilTests.cs index c7ec4b9..89762bb 100644 --- a/UnityProject/Assets/Plugins/AsyncAwaitUtil/Tests/AsyncUtilTests.cs +++ b/UnityProject/Assets/Plugins/AsyncAwaitUtil/Tests/AsyncUtilTests.cs @@ -10,98 +10,12 @@ namespace UnityAsyncAwaitUtil { - public class AsyncUtilTests : MonoBehaviour + public static class AsyncUtilTests { const string AssetBundleSampleUrl = "http://www.stevevermeulen.com/wp-content/uploads/2017/09/teapot.unity3d"; const string AssetBundleSampleAssetName = "Teapot"; - [SerializeField] - TestButtonHandler.Settings _buttonSettings = null; - - TestButtonHandler _buttonHandler; - - public void Awake() - { - _buttonHandler = new TestButtonHandler(_buttonSettings); - } - - public void OnGUI() - { - _buttonHandler.Restart(); - - if (_buttonHandler.Display("Test await seconds")) - { - RunAwaitSecondsTestAsync().WrapErrors(); - } - - if (_buttonHandler.Display("Test return value")) - { - RunReturnValueTestAsync().WrapErrors(); - } - - if (_buttonHandler.Display("Test try-catch exception")) - { - RunTryCatchExceptionTestAsync().WrapErrors(); - } - - if (_buttonHandler.Display("Test unhandled exception")) - { - // Note: Without WrapErrors here this wouldn't log anything - RunUnhandledExceptionTestAsync().WrapErrors(); - } - - if (_buttonHandler.Display("Test IEnumerator")) - { - RunIEnumeratorTestAsync().WrapErrors(); - } - - if (_buttonHandler.Display("Test IEnumerator with return value (untyped)")) - { - RunIEnumeratorUntypedStringTestAsync().WrapErrors(); - } - - if (_buttonHandler.Display("Test IEnumerator with return value (typed)")) - { - RunIEnumeratorStringTestAsync().WrapErrors(); - } - - if (_buttonHandler.Display("Test IEnumerator unhandled exception")) - { - RunIEnumeratorUnhandledExceptionAsync().WrapErrors(); - } - - if (_buttonHandler.Display("Test IEnumerator try-catch exception")) - { - RunIEnumeratorTryCatchExceptionAsync().WrapErrors(); - } - - if (_buttonHandler.Display("Load assetbundle")) - { - RunAsyncOperationAsync().WrapErrors(); - } - - if (_buttonHandler.Display("Test opening notepad")) - { - RunOpenNotepadTestAsync().WrapErrors(); - } - - if (_buttonHandler.Display("Test www download")) - { - RunWwwAsync().WrapErrors(); - } - - if (_buttonHandler.Display("Test Call Async from coroutine")) - { - StartCoroutine(RunAsyncFromCoroutineTest()); - } - - if (_buttonHandler.Display("Test multiple threads")) - { - RunMultipleThreadsTestAsync().WrapErrors(); - } - } - - IEnumerator RunAsyncFromCoroutineTest() + public static IEnumerator RunAsyncFromCoroutineTest() { Debug.Log("Waiting 1 second..."); yield return new WaitForSeconds(1.0f); @@ -110,7 +24,7 @@ IEnumerator RunAsyncFromCoroutineTest() Debug.Log("Done"); } - async Task RunMultipleThreadsTestAsync() + public static async Task RunMultipleThreadsTestAsync() { PrintCurrentThreadContext("Start"); await Task.Delay(TimeSpan.FromSeconds(1.0f)); @@ -126,45 +40,45 @@ async Task RunMultipleThreadsTestAsync() PrintCurrentThreadContext("After WaitForUpdate"); } - async Task RunMultipleThreadsTestAsyncWait() + private static async Task RunMultipleThreadsTestAsyncWait() { PrintCurrentThreadContext("RunMultipleThreadsTestAsyncWait1"); await new WaitForSeconds(1.0f); PrintCurrentThreadContext("RunMultipleThreadsTestAsyncWait2"); } - void PrintCurrentThreadContext(string prefix = null) + private static void PrintCurrentThreadContext(string prefix = null) { Debug.Log(string.Format("{0}Current Thread: {1}, Scheduler: {2}", prefix == null ? "" : prefix + ": ", Thread.CurrentThread.ManagedThreadId, SynchronizationContext.Current == null ? "null" : SynchronizationContext.Current.GetType().Name)); } - async Task RunAsyncFromCoroutineTest2() + private static async Task RunAsyncFromCoroutineTest2() { await new WaitForSeconds(1.0f); } - async Task RunWwwAsync() + public static async Task RunWwwAsync() { Debug.Log("Downloading asset bundle using WWW"); var bytes = (await new WWW(AssetBundleSampleUrl)).bytes; Debug.Log("Downloaded " + (bytes.Length / 1024) + " kb"); } - async Task RunOpenNotepadTestAsync() + public static async Task RunOpenNotepadTestAsync() { Debug.Log("Waiting for user to close notepad..."); await Process.Start("notepad.exe"); Debug.Log("Closed notepad"); } - async Task RunUnhandledExceptionTestAsync() + public static async Task RunUnhandledExceptionTestAsync() { // This should be logged when using WrapErrors await WaitThenThrowException(); } - async Task RunTryCatchExceptionTestAsync() + public static async Task RunTryCatchExceptionTestAsync() { try { @@ -176,24 +90,24 @@ async Task RunTryCatchExceptionTestAsync() } } - async Task NestedRunAsync() + private static async Task NestedRunAsync() { await new WaitForSeconds(1); throw new Exception("foo"); } - async Task WaitThenThrowException() + private static async Task WaitThenThrowException() { await new WaitForSeconds(1.5f); throw new Exception("asdf"); } - async Task RunAsyncOperationAsync() + public static async Task RunAsyncOperationAsync() { await InstantiateAssetBundleAsync(AssetBundleSampleUrl, AssetBundleSampleAssetName); } - async Task InstantiateAssetBundleAsync(string abUrl, string assetName) + private static async Task InstantiateAssetBundleAsync(string abUrl, string assetName) { // We could use WWW here too which might be easier Debug.Log("Downloading asset bundle data..."); @@ -207,14 +121,14 @@ async Task InstantiateAssetBundleAsync(string abUrl, string assetName) Debug.Log("Asset bundle instantiated"); } - async Task DownloadRawDataAsync(string url) + private static async Task DownloadRawDataAsync(string url) { var request = UnityWebRequest.Get(url); await request.Send(); return request.downloadHandler.data; } - async Task RunIEnumeratorTryCatchExceptionAsync() + public static async Task RunIEnumeratorTryCatchExceptionAsync() { try { @@ -226,44 +140,44 @@ async Task RunIEnumeratorTryCatchExceptionAsync() } } - async Task RunIEnumeratorUnhandledExceptionAsync() + public static async Task RunIEnumeratorUnhandledExceptionAsync() { await WaitThenThrow(); } - IEnumerator WaitThenThrow() + private static IEnumerator WaitThenThrow() { yield return WaitThenThrowNested(); } - IEnumerator WaitThenThrowNested() + private static IEnumerator WaitThenThrowNested() { Debug.Log("Waiting 1 second..."); yield return new WaitForSeconds(1.0f); throw new Exception("zxcv"); } - async Task RunIEnumeratorStringTestAsync() + public static async Task RunIEnumeratorStringTestAsync() { Debug.Log("Waiting for ienumerator..."); Debug.Log("Done! Result: " + await WaitForString()); } - async Task RunIEnumeratorUntypedStringTestAsync() + public static async Task RunIEnumeratorUntypedStringTestAsync() { Debug.Log("Waiting for ienumerator..."); string result = (string)(await WaitForStringUntyped()); Debug.Log("Done! Result: " + result); } - async Task RunIEnumeratorTestAsync() + public static async Task RunIEnumeratorTestAsync() { Debug.Log("Waiting for ienumerator..."); await WaitABit(); Debug.Log("Done!"); } - IEnumerator WaitForString() + private static IEnumerator WaitForString() { var startTime = Time.realtimeSinceStartup; while (Time.realtimeSinceStartup - startTime < 2) @@ -273,31 +187,31 @@ IEnumerator WaitForString() yield return "bsdfgas"; } - IEnumerator WaitForStringUntyped() + private static IEnumerator WaitForStringUntyped() { yield return WaitABit(); yield return "qwer"; } - IEnumerator WaitABit() + private static IEnumerator WaitABit() { yield return new WaitForSeconds(1.5f); } - async Task RunReturnValueTestAsync() + public static async Task RunReturnValueTestAsync() { Debug.Log("Waiting to get value..."); var result = await GetValueExampleAsync(); Debug.Log("Got value: " + result); } - async Task GetValueExampleAsync() + private static async Task GetValueExampleAsync() { await new WaitForSeconds(1.0f); return "asdf"; } - async Task RunAwaitSecondsTestAsync() + public static async Task RunAwaitSecondsTestAsync() { Debug.Log("Waiting 1 second..."); await new WaitForSeconds(1.0f); diff --git a/UnityProject/Assets/Plugins/AsyncAwaitUtil/Tests/Editor.meta b/UnityProject/Assets/Plugins/AsyncAwaitUtil/Tests/Editor.meta new file mode 100644 index 0000000..edb4ba8 --- /dev/null +++ b/UnityProject/Assets/Plugins/AsyncAwaitUtil/Tests/Editor.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: c3e6fe943b8d0fe43abad24ecfe279c2 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Assets/Plugins/AsyncAwaitUtil/Tests/Editor/EditModeTests.cs b/UnityProject/Assets/Plugins/AsyncAwaitUtil/Tests/Editor/EditModeTests.cs new file mode 100644 index 0000000..ac1ac53 --- /dev/null +++ b/UnityProject/Assets/Plugins/AsyncAwaitUtil/Tests/Editor/EditModeTests.cs @@ -0,0 +1,141 @@ +#if UNITY_EDITOR +using NUnit.Framework; +using System; +using System.Collections; +using System.Collections.Generic; +using System.Diagnostics; +using System.Threading; +using System.Threading.Tasks; +using UnityEngine; +using UnityEngine.Networking; +using Debug = UnityEngine.Debug; +using Task = System.Threading.Tasks.Task; + +using UnityEditor; +using UnityEditor.VersionControl; + +namespace UnityAsyncAwaitUtil +{ + /// + /// Tests async/await/IEnumerator integration in Edit Mode. + /// + /// In Edit Mode, Unity does not call `MoveNext` on + /// coroutines at regular intervals. Instead, we have implemented + /// `EditModeCoroutineRunner` to track running coroutines and to call + /// `MoveNext` on them during each Editor update + /// (`EditorApplication.update` event). + /// + /// It would be much nicer if these tests were implemented as standard unit + /// tests that could be run with the Unity Test Runner. Unfortunately, as + /// of Unity 2017.1.1f1, the version of NUnit shipped with Unity does not + /// support using `async`/`await` in tests. See the following link for a + /// discussion of this issue: + /// https://forum.unity.com/threads/async-await-in-unittests.513857/ + /// + public class EditModeTestsWindow : EditorWindow + { + private TestButtonHandler _buttonHandler; + + [MenuItem("Window/AsyncAwaitUtil/Edit Mode Tests")] + public static void ShowWindow() + { + GetWindow(); + } + + public void Awake() + { + TestButtonHandler.Settings settings + = new TestButtonHandler.Settings(); + + settings.NumPerColumn = 4; + settings.VerticalMargin = 10; + settings.VerticalSpacing = 10; + settings.HorizontalSpacing = 10; + settings.HorizontalMargin = 10; + settings.ButtonWidth = 300; + settings.ButtonHeight = 30; + + _buttonHandler = new TestButtonHandler(settings); + } + + public void OnGUI() + { + _buttonHandler.Restart(); + + if (_buttonHandler.Display("Test await seconds")) + { + AsyncUtilTests.RunAwaitSecondsTestAsync().WrapErrors(); + } + + if (_buttonHandler.Display("Test return value")) + { + AsyncUtilTests.RunReturnValueTestAsync().WrapErrors(); + } + + if (_buttonHandler.Display("Test try-catch exception")) + { + AsyncUtilTests.RunTryCatchExceptionTestAsync().WrapErrors(); + } + + if (_buttonHandler.Display("Test unhandled exception")) + { + // Note: Without WrapErrors here this wouldn't log anything + AsyncUtilTests.RunUnhandledExceptionTestAsync().WrapErrors(); + } + + if (_buttonHandler.Display("Test IEnumerator")) + { + AsyncUtilTests.RunIEnumeratorTestAsync().WrapErrors(); + } + + if (_buttonHandler.Display("Test IEnumerator with return value (untyped)")) + { + AsyncUtilTests.RunIEnumeratorUntypedStringTestAsync().WrapErrors(); + } + + if (_buttonHandler.Display("Test IEnumerator with return value (typed)")) + { + AsyncUtilTests.RunIEnumeratorStringTestAsync().WrapErrors(); + } + + if (_buttonHandler.Display("Test IEnumerator unhandled exception")) + { + AsyncUtilTests.RunIEnumeratorUnhandledExceptionAsync().WrapErrors(); + } + + if (_buttonHandler.Display("Test IEnumerator try-catch exception")) + { + AsyncUtilTests.RunIEnumeratorTryCatchExceptionAsync().WrapErrors(); + } + + if (_buttonHandler.Display("Load assetbundle")) + { + AsyncUtilTests.RunAsyncOperationAsync().WrapErrors(); + } + + if (_buttonHandler.Display("Test opening notepad")) + { + AsyncUtilTests.RunOpenNotepadTestAsync().WrapErrors(); + } + + if (_buttonHandler.Display("Test www download")) + { + AsyncUtilTests.RunWwwAsync().WrapErrors(); + } + + if (_buttonHandler.Display("Test Call Async from coroutine")) + { + EditModeCoroutineRunner.Instance.StartCoroutine( + AsyncUtilTests.RunAsyncFromCoroutineTest()); + } + + if (_buttonHandler.Display("Test multiple threads")) + { + AsyncUtilTests.RunMultipleThreadsTestAsync().WrapErrors(); + } + } + + } + +} +#endif diff --git a/UnityProject/Assets/Plugins/AsyncAwaitUtil/Tests/Editor/EditModeTests.cs.meta b/UnityProject/Assets/Plugins/AsyncAwaitUtil/Tests/Editor/EditModeTests.cs.meta new file mode 100644 index 0000000..ef34f70 --- /dev/null +++ b/UnityProject/Assets/Plugins/AsyncAwaitUtil/Tests/Editor/EditModeTests.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: fe875a8d710e1404f97d35efd221af86 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Assets/Plugins/AsyncAwaitUtil/Tests/PlayModeTests.cs b/UnityProject/Assets/Plugins/AsyncAwaitUtil/Tests/PlayModeTests.cs new file mode 100644 index 0000000..5599af3 --- /dev/null +++ b/UnityProject/Assets/Plugins/AsyncAwaitUtil/Tests/PlayModeTests.cs @@ -0,0 +1,94 @@ +using System.Collections; +using System.Collections.Generic; +using UnityEngine; + +namespace UnityAsyncAwaitUtil +{ + public class PlayModeTests : MonoBehaviour + { + [SerializeField] TestButtonHandler.Settings _buttonSettings = null; + + TestButtonHandler _buttonHandler; + + public void Awake() + { + _buttonHandler = new TestButtonHandler(_buttonSettings); + } + + public void OnGUI() + { + _buttonHandler.Restart(); + + if (_buttonHandler.Display("Test await seconds")) + { + AsyncUtilTests.RunAwaitSecondsTestAsync().WrapErrors(); + } + + if (_buttonHandler.Display("Test return value")) + { + AsyncUtilTests.RunReturnValueTestAsync().WrapErrors(); + } + + if (_buttonHandler.Display("Test try-catch exception")) + { + AsyncUtilTests.RunTryCatchExceptionTestAsync().WrapErrors(); + } + + if (_buttonHandler.Display("Test unhandled exception")) + { + // Note: Without WrapErrors here this wouldn't log anything + AsyncUtilTests.RunUnhandledExceptionTestAsync().WrapErrors(); + } + + if (_buttonHandler.Display("Test IEnumerator")) + { + AsyncUtilTests.RunIEnumeratorTestAsync().WrapErrors(); + } + + if (_buttonHandler.Display("Test IEnumerator with return value (untyped)")) + { + AsyncUtilTests.RunIEnumeratorUntypedStringTestAsync().WrapErrors(); + } + + if (_buttonHandler.Display("Test IEnumerator with return value (typed)")) + { + AsyncUtilTests.RunIEnumeratorStringTestAsync().WrapErrors(); + } + + if (_buttonHandler.Display("Test IEnumerator unhandled exception")) + { + AsyncUtilTests.RunIEnumeratorUnhandledExceptionAsync().WrapErrors(); + } + + if (_buttonHandler.Display("Test IEnumerator try-catch exception")) + { + AsyncUtilTests.RunIEnumeratorTryCatchExceptionAsync().WrapErrors(); + } + + if (_buttonHandler.Display("Load assetbundle")) + { + AsyncUtilTests.RunAsyncOperationAsync().WrapErrors(); + } + + if (_buttonHandler.Display("Test opening notepad")) + { + AsyncUtilTests.RunOpenNotepadTestAsync().WrapErrors(); + } + + if (_buttonHandler.Display("Test www download")) + { + AsyncUtilTests.RunWwwAsync().WrapErrors(); + } + + if (_buttonHandler.Display("Test Call Async from coroutine")) + { + StartCoroutine(AsyncUtilTests.RunAsyncFromCoroutineTest()); + } + + if (_buttonHandler.Display("Test multiple threads")) + { + AsyncUtilTests.RunMultipleThreadsTestAsync().WrapErrors(); + } + } + } +} diff --git a/UnityProject/Assets/Plugins/AsyncAwaitUtil/Tests/PlayModeTests.cs.meta b/UnityProject/Assets/Plugins/AsyncAwaitUtil/Tests/PlayModeTests.cs.meta new file mode 100644 index 0000000..cfdd199 --- /dev/null +++ b/UnityProject/Assets/Plugins/AsyncAwaitUtil/Tests/PlayModeTests.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: ed2c06f606cdd4c4493e83b7fce7f5fd +timeCreated: 1572986410 +licenseType: Free +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: