Skip to content

Commit be6b8a7

Browse files
authored
Merge pull request #10 from coryleach/dev
Fix for Unity's YieldInstructions
2 parents 2fb8f43 + ebdd8d3 commit be6b8a7

File tree

6 files changed

+114
-51
lines changed

6 files changed

+114
-51
lines changed

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,15 +19,15 @@
1919
#### Using UnityPackageManager (for Unity 2019.3 or later)
2020
Open the package manager window (menu: Window > Package Manager)<br/>
2121
Select "Add package from git URL...", fill in the pop-up with the following link:<br/>
22-
https://github.com/coryleach/UnityAsync.git#1.0.6<br/>
22+
https://github.com/coryleach/UnityAsync.git#1.0.7<br/>
2323

2424
#### Using UnityPackageManager (for Unity 2019.1 or later)
2525

2626
Find the manifest.json file in the Packages folder of your project and edit it to look like this:
2727
```js
2828
{
2929
"dependencies": {
30-
"com.gameframe.async": "https://github.com/coryleach/UnityAsync.git#1.0.6",
30+
"com.gameframe.async": "https://github.com/coryleach/UnityAsync.git#1.0.7",
3131
...
3232
},
3333
}

Runtime/Coroutines/CoroutineHost.cs

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
using System;
2+
using System.Collections;
3+
using UnityEngine;
4+
5+
namespace Gameframe.Async.Coroutines
6+
{
7+
public class CoroutineHost : MonoBehaviour
8+
{
9+
private static CoroutineHost _instance;
10+
11+
public static void Run(YieldInstruction yieldInstruction, Action onComplete = null)
12+
{
13+
GetHost().StartCoroutine(RunYieldInstruction(yieldInstruction, onComplete));;
14+
}
15+
16+
public static Coroutine RunCoroutine(YieldInstruction yieldInstruction, Action onComplete = null)
17+
{
18+
return GetHost().StartCoroutine(RunYieldInstruction(yieldInstruction, onComplete));;
19+
}
20+
21+
public static Coroutine RunCoroutine(IEnumerator routine, Action onComplete = null)
22+
{
23+
return GetHost().StartCoroutine(RunRoutine(routine, onComplete));;
24+
}
25+
26+
public static void KillCoroutine(Coroutine coroutine)
27+
{
28+
GetHost().StopCoroutine(coroutine);
29+
}
30+
31+
private static CoroutineHost GetHost()
32+
{
33+
if (_instance != null)
34+
{
35+
return _instance;
36+
}
37+
38+
_instance = new GameObject("_CoroutineHost").AddComponent<CoroutineHost>();
39+
_instance.gameObject.hideFlags = HideFlags.DontSave | HideFlags.HideInHierarchy;
40+
return _instance;
41+
}
42+
43+
private static IEnumerator RunYieldInstruction(YieldInstruction instruction, Action onComplete)
44+
{
45+
yield return instruction;
46+
onComplete?.Invoke();
47+
}
48+
49+
private static IEnumerator RunRoutine(IEnumerator routine, Action onComplete)
50+
{
51+
yield return routine;
52+
onComplete?.Invoke();
53+
}
54+
55+
private void Awake()
56+
{
57+
DontDestroyOnLoad(gameObject);
58+
}
59+
}
60+
}

Runtime/Coroutines/CoroutineHost.cs.meta

Lines changed: 11 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Runtime/Coroutines/CoroutineRunner.cs

Lines changed: 13 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,4 @@
1-
using System;
2-
using System.Collections;
3-
using System.Collections.Generic;
1+
using System.Collections;
42
using System.Threading;
53
using System.Threading.Tasks;
64
using UnityEngine;
@@ -78,50 +76,24 @@ private static void OnApplicationQuitting()
7876

7977
private static async Task RunAsync(IEnumerator routine, CancellationToken token)
8078
{
81-
var coroutine = RunCoroutine(routine);
82-
while (!token.IsCancellationRequested && coroutine.MoveNext())
79+
var running = true;
80+
// I tried creating my own runner for coroutines that doesn't require MonoBehaviour
81+
// However, Unity's YieldInstruction objects such as WaitForSeconds are unable to be handled gracefully this way
82+
// To implement Unity's YieldInstruction objects correctly would probably require using reflection and isn't very practical
83+
var coroutine = CoroutineHost.RunCoroutine(routine, () =>
84+
{
85+
running = false;
86+
});
87+
88+
while (!token.IsCancellationRequested && running)
8389
{
8490
//Task.Yield() on the Unity sync context appears to yield for one frame
8591
await Task.Yield();
8692
}
87-
}
88-
89-
private static IEnumerator RunCoroutine(object state)
90-
{
91-
var processStack = new Stack<IEnumerator>();
92-
processStack.Push((IEnumerator)state);
9393

94-
while (processStack.Count > 0)
94+
if (running)
9595
{
96-
var currentCoroutine = processStack.Peek();
97-
var done = false;
98-
99-
try
100-
{
101-
done = !currentCoroutine.MoveNext();
102-
}
103-
catch (Exception e)
104-
{
105-
Debug.LogException(e);
106-
yield break;
107-
}
108-
109-
if (done)
110-
{
111-
processStack.Pop();
112-
}
113-
else
114-
{
115-
if (currentCoroutine.Current is IEnumerator innerCoroutine)
116-
{
117-
processStack.Push(innerCoroutine);
118-
}
119-
else
120-
{
121-
yield return currentCoroutine.Current;
122-
}
123-
}
124-
96+
CoroutineHost.KillCoroutine(coroutine);
12597
}
12698
}
12799

Tests/Runtime/Coroutines/CoroutineTests.cs

Lines changed: 27 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -38,12 +38,32 @@ public IEnumerator CanStartCoroutineFromBackgroundThread()
3838
Assert.IsFalse(UnityTaskUtil.CurrentThreadIsUnityThread);
3939
UnityTaskUtil.StartCoroutineAsync(LongRunningCoroutine(() => { result = true; }), hostBehaviour).GetAwaiter().GetResult();
4040
});
41-
41+
4242
yield return new WaitUntil(()=>task.IsCompleted);
43-
43+
4444
Assert.IsTrue(result);
4545
}
46-
46+
47+
[UnityTest]
48+
public IEnumerator CanWaitForSeconds()
49+
{
50+
float duration = 1f;
51+
float t = Time.time;
52+
bool done = false;
53+
CoroutineRunner.RunAsync(LongRunningCoroutine(() =>
54+
{
55+
done = true;
56+
}, duration));
57+
58+
while (!done)
59+
{
60+
yield return null;
61+
}
62+
63+
float delta = Time.time - t;
64+
Assert.IsTrue(delta >= duration, $"Failed to wait duration: Delta: {delta} should be >= actual duration: {duration}");
65+
}
66+
4767
private static async Task<bool> TestAwaitCoroutineAsync()
4868
{
4969
var hostBehaviour = new GameObject("Test").AddComponent<TestHostBehaviour>();
@@ -53,15 +73,15 @@ private static async Task<bool> TestAwaitCoroutineAsync()
5373
return result;
5474
}
5575

56-
private static IEnumerator LongRunningCoroutine(Action onComplete)
76+
private static IEnumerator LongRunningCoroutine(Action onComplete, float duration = 0.05f)
5777
{
58-
yield return new WaitForSeconds(0.05f);
78+
yield return new WaitForSeconds(duration);
5979
onComplete?.Invoke();
6080
}
6181
}
62-
82+
6383
public class TestHostBehaviour : MonoBehaviour
6484
{
6585
}
66-
86+
6787
}

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "com.gameframe.async",
33
"displayName": "Gameframe.Async",
4-
"version": "1.0.6",
4+
"version": "1.0.7",
55
"description": "> Async task utility package for Unity \n> Helper methods for starting tasks on the Unity thread. \n> Start and await coroutines from any thread.",
66
"keywords": [],
77
"author": {

0 commit comments

Comments
 (0)