Skip to content

Commit

Permalink
Merge pull request #10 from coryleach/dev
Browse files Browse the repository at this point in the history
Fix for Unity's YieldInstructions
  • Loading branch information
coryleach authored Aug 30, 2023
2 parents 2fb8f43 + ebdd8d3 commit be6b8a7
Show file tree
Hide file tree
Showing 6 changed files with 114 additions and 51 deletions.
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,15 +19,15 @@
#### Using UnityPackageManager (for Unity 2019.3 or later)
Open the package manager window (menu: Window > Package Manager)<br/>
Select "Add package from git URL...", fill in the pop-up with the following link:<br/>
https://github.com/coryleach/UnityAsync.git#1.0.6<br/>
https://github.com/coryleach/UnityAsync.git#1.0.7<br/>

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

Find the manifest.json file in the Packages folder of your project and edit it to look like this:
```js
{
"dependencies": {
"com.gameframe.async": "https://github.com/coryleach/UnityAsync.git#1.0.6",
"com.gameframe.async": "https://github.com/coryleach/UnityAsync.git#1.0.7",
...
},
}
Expand Down
60 changes: 60 additions & 0 deletions Runtime/Coroutines/CoroutineHost.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
using System;
using System.Collections;
using UnityEngine;

namespace Gameframe.Async.Coroutines
{
public class CoroutineHost : MonoBehaviour
{
private static CoroutineHost _instance;

public static void Run(YieldInstruction yieldInstruction, Action onComplete = null)
{
GetHost().StartCoroutine(RunYieldInstruction(yieldInstruction, onComplete));;
}

public static Coroutine RunCoroutine(YieldInstruction yieldInstruction, Action onComplete = null)
{
return GetHost().StartCoroutine(RunYieldInstruction(yieldInstruction, onComplete));;
}

public static Coroutine RunCoroutine(IEnumerator routine, Action onComplete = null)
{
return GetHost().StartCoroutine(RunRoutine(routine, onComplete));;
}

public static void KillCoroutine(Coroutine coroutine)
{
GetHost().StopCoroutine(coroutine);
}

private static CoroutineHost GetHost()
{
if (_instance != null)
{
return _instance;
}

_instance = new GameObject("_CoroutineHost").AddComponent<CoroutineHost>();
_instance.gameObject.hideFlags = HideFlags.DontSave | HideFlags.HideInHierarchy;
return _instance;
}

private static IEnumerator RunYieldInstruction(YieldInstruction instruction, Action onComplete)
{
yield return instruction;
onComplete?.Invoke();
}

private static IEnumerator RunRoutine(IEnumerator routine, Action onComplete)
{
yield return routine;
onComplete?.Invoke();
}

private void Awake()
{
DontDestroyOnLoad(gameObject);
}
}
}
11 changes: 11 additions & 0 deletions Runtime/Coroutines/CoroutineHost.cs.meta

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

54 changes: 13 additions & 41 deletions Runtime/Coroutines/CoroutineRunner.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections;
using System.Threading;
using System.Threading.Tasks;
using UnityEngine;
Expand Down Expand Up @@ -78,50 +76,24 @@ private static void OnApplicationQuitting()

private static async Task RunAsync(IEnumerator routine, CancellationToken token)
{
var coroutine = RunCoroutine(routine);
while (!token.IsCancellationRequested && coroutine.MoveNext())
var running = true;
// I tried creating my own runner for coroutines that doesn't require MonoBehaviour
// However, Unity's YieldInstruction objects such as WaitForSeconds are unable to be handled gracefully this way
// To implement Unity's YieldInstruction objects correctly would probably require using reflection and isn't very practical
var coroutine = CoroutineHost.RunCoroutine(routine, () =>
{
running = false;
});

while (!token.IsCancellationRequested && running)
{
//Task.Yield() on the Unity sync context appears to yield for one frame
await Task.Yield();
}
}

private static IEnumerator RunCoroutine(object state)
{
var processStack = new Stack<IEnumerator>();
processStack.Push((IEnumerator)state);

while (processStack.Count > 0)
if (running)
{
var currentCoroutine = processStack.Peek();
var done = false;

try
{
done = !currentCoroutine.MoveNext();
}
catch (Exception e)
{
Debug.LogException(e);
yield break;
}

if (done)
{
processStack.Pop();
}
else
{
if (currentCoroutine.Current is IEnumerator innerCoroutine)
{
processStack.Push(innerCoroutine);
}
else
{
yield return currentCoroutine.Current;
}
}

CoroutineHost.KillCoroutine(coroutine);
}
}

Expand Down
34 changes: 27 additions & 7 deletions Tests/Runtime/Coroutines/CoroutineTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -38,12 +38,32 @@ public IEnumerator CanStartCoroutineFromBackgroundThread()
Assert.IsFalse(UnityTaskUtil.CurrentThreadIsUnityThread);
UnityTaskUtil.StartCoroutineAsync(LongRunningCoroutine(() => { result = true; }), hostBehaviour).GetAwaiter().GetResult();
});

yield return new WaitUntil(()=>task.IsCompleted);

Assert.IsTrue(result);
}


[UnityTest]
public IEnumerator CanWaitForSeconds()
{
float duration = 1f;
float t = Time.time;
bool done = false;
CoroutineRunner.RunAsync(LongRunningCoroutine(() =>
{
done = true;
}, duration));

while (!done)
{
yield return null;
}

float delta = Time.time - t;
Assert.IsTrue(delta >= duration, $"Failed to wait duration: Delta: {delta} should be >= actual duration: {duration}");
}

private static async Task<bool> TestAwaitCoroutineAsync()
{
var hostBehaviour = new GameObject("Test").AddComponent<TestHostBehaviour>();
Expand All @@ -53,15 +73,15 @@ private static async Task<bool> TestAwaitCoroutineAsync()
return result;
}

private static IEnumerator LongRunningCoroutine(Action onComplete)
private static IEnumerator LongRunningCoroutine(Action onComplete, float duration = 0.05f)
{
yield return new WaitForSeconds(0.05f);
yield return new WaitForSeconds(duration);
onComplete?.Invoke();
}
}

public class TestHostBehaviour : MonoBehaviour
{
}

}
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "com.gameframe.async",
"displayName": "Gameframe.Async",
"version": "1.0.6",
"version": "1.0.7",
"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.",
"keywords": [],
"author": {
Expand Down

0 comments on commit be6b8a7

Please sign in to comment.