From 5938bc4f6216818d96603fb2a89ecc2c3216402e Mon Sep 17 00:00:00 2001 From: Paul Du Bois Date: Wed, 4 Dec 2019 10:55:11 -0800 Subject: [PATCH 1/2] Add edit-time support to AsyncCoroutineRunner If the package com.unity.editorcoroutine is installed, AsyncCoroutineRunner gains the ability to work at edit time. In Unity 2019.1 and above, no additional setup is needed. Otherwise, the symbol HAVE_EDITOR_COROUTINES must also be manually defined. Includes unit test. Addresses part of modesttree#9 --- .../Source/AsyncAwaitUtil.asmdef | 20 ++++++++ .../Source/AsyncAwaitUtil.asmdef.meta | 7 +++ .../Source/Internal/AsyncCoroutineRunner.cs | 47 ++++++++++++++++--- .../Plugins/AsyncAwaitUtil/Tests/Editor.meta | 8 ++++ .../AsyncAwaitUtil/Tests/Editor/TestMisc.cs | 31 ++++++++++++ .../Tests/Editor/TestMisc.cs.meta | 11 +++++ 6 files changed, 118 insertions(+), 6 deletions(-) create mode 100644 UnityProject/Assets/Plugins/AsyncAwaitUtil/Source/AsyncAwaitUtil.asmdef create mode 100644 UnityProject/Assets/Plugins/AsyncAwaitUtil/Source/AsyncAwaitUtil.asmdef.meta create mode 100644 UnityProject/Assets/Plugins/AsyncAwaitUtil/Tests/Editor.meta create mode 100644 UnityProject/Assets/Plugins/AsyncAwaitUtil/Tests/Editor/TestMisc.cs create mode 100644 UnityProject/Assets/Plugins/AsyncAwaitUtil/Tests/Editor/TestMisc.cs.meta diff --git a/UnityProject/Assets/Plugins/AsyncAwaitUtil/Source/AsyncAwaitUtil.asmdef b/UnityProject/Assets/Plugins/AsyncAwaitUtil/Source/AsyncAwaitUtil.asmdef new file mode 100644 index 0000000..9f2f05c --- /dev/null +++ b/UnityProject/Assets/Plugins/AsyncAwaitUtil/Source/AsyncAwaitUtil.asmdef @@ -0,0 +1,20 @@ +{ + "name": "AsyncAwaitUtil", + "references": [ + "GUID:478a2357cc57436488a56e564b08d223" + ], + "includePlatforms": [], + "excludePlatforms": [], + "allowUnsafeCode": false, + "overrideReferences": false, + "precompiledReferences": [], + "autoReferenced": true, + "defineConstraints": [], + "versionDefines": [ + { + "name": "com.unity.editorcoroutines", + "expression": "0.0.2-preview", + "define": "HAVE_EDITOR_COROUTINES" + } + ] +} \ No newline at end of file diff --git a/UnityProject/Assets/Plugins/AsyncAwaitUtil/Source/AsyncAwaitUtil.asmdef.meta b/UnityProject/Assets/Plugins/AsyncAwaitUtil/Source/AsyncAwaitUtil.asmdef.meta new file mode 100644 index 0000000..04d7208 --- /dev/null +++ b/UnityProject/Assets/Plugins/AsyncAwaitUtil/Source/AsyncAwaitUtil.asmdef.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 77496ac4da31e304f9e7872e863b54e8 +AssemblyDefinitionImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Assets/Plugins/AsyncAwaitUtil/Source/Internal/AsyncCoroutineRunner.cs b/UnityProject/Assets/Plugins/AsyncAwaitUtil/Source/Internal/AsyncCoroutineRunner.cs index a3f5d6a..51dc734 100644 --- a/UnityProject/Assets/Plugins/AsyncAwaitUtil/Source/Internal/AsyncCoroutineRunner.cs +++ b/UnityProject/Assets/Plugins/AsyncAwaitUtil/Source/Internal/AsyncCoroutineRunner.cs @@ -1,19 +1,49 @@ using System; using System.Collections; -using System.Collections.Generic; -using System.Linq; + using UnityEngine; +#if UNITY_EDITOR && HAVE_EDITOR_COROUTINES +using Unity.EditorCoroutines.Editor; +#endif namespace UnityAsyncAwaitUtil { - public class AsyncCoroutineRunner : MonoBehaviour + public class AsyncCoroutineRunner : MonoBehaviour, AsyncCoroutineRunner.ICoroutineRunner { - static AsyncCoroutineRunner _instance; + public interface ICoroutineRunner + { + object StartCoroutine(IEnumerator routine); + } + +#if UNITY_EDITOR + class EditorAsyncCoroutineRunner : ICoroutineRunner + { + object ICoroutineRunner.StartCoroutine(IEnumerator routine) + { +#if HAVE_EDITOR_COROUTINES + return EditorCoroutineUtility.StartCoroutine(routine, this); +#elif UNITY_2019_1_OR_NEWER + throw new NotImplementedException("Install package com.unity.editorcoroutines"); +#else + // asmdef "Version Defines" support doesn't exist yet + throw new NotImplementedException("Install package com.unity.editorcoroutines and define HAVE_EDITOR_COROUTINES"); +#endif + } + } +#endif + + static ICoroutineRunner _instance; - public static AsyncCoroutineRunner Instance + public static ICoroutineRunner Instance { get { +#if UNITY_EDITOR + if (_instance == null && !Application.isPlaying) + { + _instance = new EditorAsyncCoroutineRunner(); + } +#endif if (_instance == null) { _instance = new GameObject("AsyncCoroutineRunner") @@ -31,5 +61,10 @@ void Awake() DontDestroyOnLoad(gameObject); } + + object ICoroutineRunner.StartCoroutine(IEnumerator routine) + { + return StartCoroutine(routine); + } } -} +} \ No newline at end of file diff --git a/UnityProject/Assets/Plugins/AsyncAwaitUtil/Tests/Editor.meta b/UnityProject/Assets/Plugins/AsyncAwaitUtil/Tests/Editor.meta new file mode 100644 index 0000000..d5ba2ab --- /dev/null +++ b/UnityProject/Assets/Plugins/AsyncAwaitUtil/Tests/Editor.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 94b93817f8792fd45835bebba406b814 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Assets/Plugins/AsyncAwaitUtil/Tests/Editor/TestMisc.cs b/UnityProject/Assets/Plugins/AsyncAwaitUtil/Tests/Editor/TestMisc.cs new file mode 100644 index 0000000..ed163b1 --- /dev/null +++ b/UnityProject/Assets/Plugins/AsyncAwaitUtil/Tests/Editor/TestMisc.cs @@ -0,0 +1,31 @@ +using System.Collections; +using System.Collections.Generic; + +using NUnit.Framework; +using UnityEngine; +using UnityEngine.TestTools; + +namespace UnityAsyncAwaitUtil +{ + class TestMisc + { + [UnityTest] + public IEnumerator TestAsyncCoroutineRunnerInEditor() + { + List result = new List(); + AsyncCoroutineRunner.Instance.StartCoroutine(AppendCoroutine(result)); + for (int i = 0; i < 10; ++i) + { + if (result.Count > 0) { yield break; } + yield return null; + } + Assert.Fail("Coroutine did not complete"); + } + + static IEnumerator AppendCoroutine(List result) + { + yield return null; + result.Add(true); + } + } +} diff --git a/UnityProject/Assets/Plugins/AsyncAwaitUtil/Tests/Editor/TestMisc.cs.meta b/UnityProject/Assets/Plugins/AsyncAwaitUtil/Tests/Editor/TestMisc.cs.meta new file mode 100644 index 0000000..466b0c0 --- /dev/null +++ b/UnityProject/Assets/Plugins/AsyncAwaitUtil/Tests/Editor/TestMisc.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 33b332b97e3f8004ab233b5da2bfa109 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: From 104ff55289104ed25c96e6d663c644116331ef63 Mon Sep 17 00:00:00 2001 From: Paul Du Bois Date: Wed, 4 Dec 2019 13:16:43 -0800 Subject: [PATCH 2/2] Add edit-time support to SyncContextUtil Includes sample unit test showing that "await" works at edit time. Fixes modesttree#9 --- .../Source/Internal/SyncContextUtil.cs | 3 ++ .../AsyncAwaitUtil/Tests/Editor/TestMisc.cs | 38 ++++++++++++++++--- 2 files changed, 35 insertions(+), 6 deletions(-) diff --git a/UnityProject/Assets/Plugins/AsyncAwaitUtil/Source/Internal/SyncContextUtil.cs b/UnityProject/Assets/Plugins/AsyncAwaitUtil/Source/Internal/SyncContextUtil.cs index 3d505a4..2b21b79 100644 --- a/UnityProject/Assets/Plugins/AsyncAwaitUtil/Source/Internal/SyncContextUtil.cs +++ b/UnityProject/Assets/Plugins/AsyncAwaitUtil/Source/Internal/SyncContextUtil.cs @@ -10,6 +10,9 @@ namespace UnityAsyncAwaitUtil { public static class SyncContextUtil { +#if UNITY_EDITOR + [UnityEditor.InitializeOnLoadMethod] +#endif [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSceneLoad)] static void Install() { diff --git a/UnityProject/Assets/Plugins/AsyncAwaitUtil/Tests/Editor/TestMisc.cs b/UnityProject/Assets/Plugins/AsyncAwaitUtil/Tests/Editor/TestMisc.cs index ed163b1..a774905 100644 --- a/UnityProject/Assets/Plugins/AsyncAwaitUtil/Tests/Editor/TestMisc.cs +++ b/UnityProject/Assets/Plugins/AsyncAwaitUtil/Tests/Editor/TestMisc.cs @@ -1,5 +1,8 @@ +using System; using System.Collections; using System.Collections.Generic; +using System.Runtime.ExceptionServices; +using System.Threading.Tasks; using NUnit.Framework; using UnityEngine; @@ -9,23 +12,46 @@ namespace UnityAsyncAwaitUtil { class TestMisc { + // Helper for writing async editor tests. + static IEnumerator AsUnityTest(Func body) + { + var task = Task.Run(body); + while (!task.IsCompleted) + { + yield return null; + } + if (task.IsFaulted) + { + ExceptionDispatchInfo.Capture(task.Exception.InnerExceptions[0]).Throw(); + } + } + [UnityTest] public IEnumerator TestAsyncCoroutineRunnerInEditor() { List result = new List(); - AsyncCoroutineRunner.Instance.StartCoroutine(AppendCoroutine(result)); + IEnumerator AppendResult() + { + yield return null; + result.Add(true); + } + AsyncCoroutineRunner.Instance.StartCoroutine(AppendResult()); for (int i = 0; i < 10; ++i) { if (result.Count > 0) { yield break; } yield return null; } - Assert.Fail("Coroutine did not complete"); + Assert.Fail("Coroutine did not complete in the allotted time"); } - static IEnumerator AppendCoroutine(List result) + + [UnityTest] + public IEnumerator TestAwaitSecondsRealtime() => AsUnityTest(async () => { - yield return null; - result.Add(true); - } + const float delay = .25f; + var start = DateTime.Now; + await new WaitForSecondsRealtime(delay); + Assert.Greater(DateTime.Now - start, TimeSpan.FromSeconds(delay - .01)); + }); } }