Created
April 10, 2016 00:31
-
-
Save mstevenson/2a3acd75e7f7f6d2a0a52a72734202b2 to your computer and use it in GitHub Desktop.
Execute a Unity Coroutine in one frame without yielding
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using UnityEngine; | |
using System.Collections; | |
using System.Collections.Generic; | |
public static class IEnumeratorExtensions | |
{ | |
/// <summary> | |
/// Execute an entire Unity Coroutine in one frame. | |
/// This is useful for testing coroutines with NUnit. | |
/// | |
/// The process will bail out after a fixed number of yields to avoid | |
/// looping infinitely. | |
/// | |
/// Calling StartCoroutine from inside an IEnumerator is not supported | |
/// because there is no way to access the IEnumerator object created | |
/// by StartCoroutine. | |
/// | |
/// Returns true if the coroutine execution finished, otherwise false if it | |
/// bailed early after reaching the maximum number of yields. | |
/// </summary> | |
public static bool RunCoroutineWithoutYields (this IEnumerator enumerator, int maxYields = 1000) | |
{ | |
Stack<IEnumerator> enumStack = new Stack<IEnumerator> (); | |
enumStack.Push (enumerator); | |
int step = 0; | |
while (enumStack.Count > 0) { | |
IEnumerator activeEnum = enumStack.Pop (); | |
while (activeEnum.MoveNext ()) { | |
if (activeEnum.Current is IEnumerator) { | |
enumStack.Push (activeEnum); | |
activeEnum = (IEnumerator)activeEnum.Current; | |
} else if (activeEnum.Current is Coroutine) { | |
throw new System.NotSupportedException ("RunCoroutineWithoutYields can not be used with an IEnumerator that calls StartCoroutine inside itself."); | |
} | |
step += 1; | |
if (step >= maxYields) { | |
return false; | |
} | |
} | |
} | |
return true; | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using UnityEngine; | |
using System.Collections; | |
using NUnit.Framework; | |
namespace Tests.Extensions | |
{ | |
public class CoroutineBehaviour : MonoBehaviour | |
{ | |
public int current; | |
public IEnumerator OneYield () | |
{ | |
current++; | |
yield return null; | |
current++; | |
} | |
public IEnumerator WaitForSecondsCoroutine () | |
{ | |
current++; | |
yield return new WaitForSeconds (1); | |
current++; | |
} | |
public IEnumerator WaitForFixedUpdateCoroutine () | |
{ | |
current++; | |
yield return new WaitForFixedUpdate (); | |
current++; | |
} | |
public IEnumerator WaitForEndOfFrameCoroutine () | |
{ | |
current++; | |
yield return new WaitForEndOfFrame (); | |
current++; | |
} | |
public IEnumerator WaitWhileCoroutine () | |
{ | |
current++; | |
int waitCounter = 0; | |
yield return new WaitWhile (() => { | |
waitCounter++; | |
return waitCounter <= 2; | |
}); | |
current++; | |
} | |
public IEnumerator WaitUntilCoroutine () | |
{ | |
current++; | |
int waitCounter = 0; | |
yield return new WaitUntil (() => { | |
waitCounter++; | |
return waitCounter == 2; | |
}); | |
current++; | |
} | |
public IEnumerator NestedIEnumerator () | |
{ | |
current++; | |
yield return null; | |
current++; | |
yield return OneYield (); | |
current++; | |
} | |
public IEnumerator NestedCoroutine () | |
{ | |
current++; | |
yield return null; | |
current++; | |
yield return StartCoroutine (OneYield ()); | |
current++; | |
} | |
public IEnumerator InfiniteLoopCoroutine () | |
{ | |
while (true) { | |
current++; | |
yield return null; | |
} | |
} | |
} | |
[TestFixture] | |
public class IEnumeratorExtensionTest | |
{ | |
GameObject obj; | |
CoroutineBehaviour behaviour; | |
[SetUp] | |
public void Setup () | |
{ | |
obj = new GameObject (); | |
behaviour = obj.AddComponent<CoroutineBehaviour> (); | |
} | |
[TearDown] | |
public void Teardown () | |
{ | |
GameObject.DestroyImmediate (obj); | |
} | |
[Test] | |
public void RunCoroutineWithoutYields_OneYieldReturnNull_Completes () | |
{ | |
IEnumerator enumerator = behaviour.OneYield (); | |
bool completed = enumerator.RunCoroutineWithoutYields (); | |
Assert.AreEqual (2, behaviour.current); | |
Assert.IsTrue (completed); | |
} | |
[Test] | |
public void RunCoroutineWithoutYields_WaitForSeconds_Completes () | |
{ | |
IEnumerator enumerator = behaviour.WaitForSecondsCoroutine (); | |
bool completed = enumerator.RunCoroutineWithoutYields (); | |
Assert.AreEqual (2, behaviour.current); | |
Assert.IsTrue (completed); | |
} | |
[Test] | |
public void RunCoroutineWithoutYields_WaitForFixedUpdate_Completes () | |
{ | |
IEnumerator enumerator = behaviour.WaitForFixedUpdateCoroutine (); | |
bool completed = enumerator.RunCoroutineWithoutYields (); | |
Assert.AreEqual (2, behaviour.current); | |
Assert.IsTrue (completed); | |
} | |
[Test] | |
public void RunCoroutineWithoutYields_WaitForEndOfFrame_Completes () | |
{ | |
IEnumerator enumerator = behaviour.WaitForEndOfFrameCoroutine (); | |
bool completed = enumerator.RunCoroutineWithoutYields (); | |
Assert.AreEqual (2, behaviour.current); | |
Assert.IsTrue (completed); | |
} | |
[Test] | |
public void RunCoroutineWithoutYields_WaitWhile_Completes () | |
{ | |
IEnumerator enumerator = behaviour.WaitWhileCoroutine (); | |
bool completed = enumerator.RunCoroutineWithoutYields (); | |
Assert.AreEqual (2, behaviour.current); | |
Assert.IsTrue (completed); | |
} | |
[Test] | |
public void RunCoroutineWithoutYields_WaitUntil_Completes () | |
{ | |
IEnumerator enumerator = behaviour.WaitUntilCoroutine (); | |
bool completed = enumerator.RunCoroutineWithoutYields (); | |
Assert.AreEqual (2, behaviour.current); | |
Assert.IsTrue (completed); | |
} | |
[Test] | |
public void RunCoroutineWithoutYields_NestedIEnumerator_Completes () | |
{ | |
IEnumerator enumerator = behaviour.NestedIEnumerator (); | |
bool completed = enumerator.RunCoroutineWithoutYields (); | |
Assert.AreEqual (5, behaviour.current); | |
} | |
[Test] | |
[ExpectedException (typeof(System.NotSupportedException))] | |
public void RunCoroutineWithoutYields_NestedStartCoroutine_ThrowsException () | |
{ | |
IEnumerator enumerator = behaviour.NestedCoroutine (); | |
enumerator.RunCoroutineWithoutYields (); | |
} | |
[Test] | |
public void RunCoroutineWithoutYields_InfiniteLoop_BailsOut () | |
{ | |
int maxYields = 10; | |
IEnumerator enumerator = behaviour.InfiniteLoopCoroutine (); | |
bool completed = enumerator.RunCoroutineWithoutYields (maxYields); | |
Assert.AreEqual (maxYields, behaviour.current); | |
Assert.IsFalse (completed); | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment