Skip to content

Instantly share code, notes, and snippets.

@mstevenson
Created April 10, 2016 00:31
Show Gist options
  • Save mstevenson/2a3acd75e7f7f6d2a0a52a72734202b2 to your computer and use it in GitHub Desktop.
Save mstevenson/2a3acd75e7f7f6d2a0a52a72734202b2 to your computer and use it in GitHub Desktop.
Execute a Unity Coroutine in one frame without yielding
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;
}
}
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