MEC / Quick Start (Free) Pro features → / Asset Store

More Effective Coroutines

Quick Start Guide
Free Edition

01 What is MEC

More Effective Coroutines (MEC) is a free asset on the Unity Asset Store. It is an improved implementation of coroutines that runs about twice as fast as Unity's coroutines do and has zero per-frame memory allocations. It has been tested and refined extensively to maximize performance and create a rock solid platform for coroutines in your app.

MEC keeps Unity's familiar yield return structure, so switching over is mostly a matter of find and replace. The sections below walk through the changes, then cover the extra capabilities you get along the way.

02 Feature Comparison

FeatureUnityMEC FreeMEC Pro
Uses the yield return structure
Time to run 100,000 empty coroutines~110.53 ms~9.62 ms~9.64 ms
Singleton instances of coroutines
Switch the timing of coroutines mid-process
CallDelayed, CallPeriodically, CallContinuously
Choose whether to link a coroutine to a GameObject

Timing segments available in each version:

SegmentUnityMEC FreeMEC Pro
Update
FixedUpdate
LateUpdate
SlowUpdate
RealtimeUpdate
EndOfFrame
LateFixedUpdate
ManualTimeframe

03 Adding the Namespaces

MEC coroutines are defined in the MEC namespace, and they rely on System.Collections.Generic rather than the System.Collections functionality that Unity coroutines use. System.Collections is hardly ever used for anything except Unity's coroutines, so an easy way to switch is a find and replace, then fix the lines that show errors. Make sure these two using statements are at the top of every script that uses MEC:

using System.Collections.Generic;
using MEC;

04 Replacing StartCoroutine

Replace every StartCoroutine call with Timing.RunCoroutine. You pick the execution loop when you define the process; it defaults to Segment.Update.

// Unity
StartCoroutine(_CheckForWin());

// MEC, in the Update segment
Timing.RunCoroutine(_CheckForWin());

// In a specific segment
Timing.RunCoroutine(_CheckForWin(), Segment.FixedUpdate);
Timing.RunCoroutine(_CheckForWin(), Segment.LateUpdate);
Timing.RunCoroutine(_CheckForWin(), Segment.SlowUpdate);

RunCoroutine returns a CoroutineHandle. That handle is your reference to the running coroutine: pass it to KillCoroutines, PauseCoroutines, ResumeCoroutines, WaitUntilDone, IsRunning, and SetSegment to control the coroutine after it starts. You can also supply an optional string tag to group coroutines for batch operations.

Note: use CancelWith (see section 07) on any coroutine that moves or changes GameObjects, or you will start to see null reference exceptions when you do things like switch screens in the middle of a transition.

05 Function Headers

The process header changes from IEnumerator to IEnumerator<float>:

// from
IEnumerator _CheckForWin() { ... }

// to
IEnumerator<float> _CheckForWin() { ... }

It is a good habit to put an underscore before every coroutine function. Coroutines have a tendency to look like they run correctly but actually do nothing if you call them without RunCoroutine. The leading underscore reminds you to always write Timing.RunCoroutine(_CheckForWin()) rather than calling _CheckForWin() like a normal function.

06 Yield Statements

To wait one frame, use yield return Timing.WaitForOneFrame; (or yield return 0;, which is equivalent). WaitForOneFrame reads more clearly to anyone unfamiliar with coroutines.

IEnumerator<float> _CheckForWin()
{
    while (_cubesHit < TotalCubes)
    {
        WinText.text = "Have not won yet.";
        yield return Timing.WaitForOneFrame;
    }
    WinText.text = "You win!";
}

To pause for a number of seconds, use Timing.WaitForSeconds in place of Unity's new WaitForSeconds:

yield return Timing.WaitForSeconds(0.1f);

Unity lets you wait for another coroutine to finish with a shorthand yield return. In MEC Free, use the longhand form with WaitUntilDone:

yield return Timing.WaitUntilDone(Timing.RunCoroutine(_CheckForWin()));

(MEC Pro adds a shorthand that calls RunCoroutine for you.)

07 CancelWith

MEC coroutines do not automatically stop when the GameObject they were created on is destroyed or disabled, the way Unity's do. That is intentional, since it does not always make sense, and for coroutines that never touch the scene the check would be wasted work. When a coroutine does affect UI or scene objects, turn the check on with the CancelWith extension so you do not get errors when changing screens:

Timing.RunCoroutine(_moveMyButton().CancelWith(gameObject));
TLDR: use .CancelWith on all coroutines that affect UI elements.

CancelWith adds roughly 20 bytes to the unavoidable GC allocation that all coroutines generate. If you want to avoid even that, you can do the same thing by hand: after every yield return inside the coroutine, check

if (gameObject != null && gameObject.activeInHierarchy)

08 WaitUntilDone

Unity lets you yield on various objects. MEC does the same through WaitUntilDone:

yield return Timing.WaitUntilDone(wwwObject);
yield return Timing.WaitUntilDone(asyncOperation);

// MEC Pro additions
yield return Timing.WaitUntilDone(newCoroutine);
yield return Timing.WaitUntilTrue(functionDelegateThatReturnsBool);
yield return Timing.WaitUntilFalse(functionDelegateThatReturnsBool);

09 SlowUpdate

Unity's coroutines have no concept of a slow update loop. MEC's SlowUpdate runs (by default) seven times a second and uses absolute timescale, so slowing Unity's timescale does not slow it down. It is ideal for tasks like displaying text, where updating faster would not be visible anyway.

SlowUpdate differs from simply yielding Timing.WaitForSeconds(1f/7f) in two ways: it uses absolute timescale, and all SlowUpdate ticks happen on the same frame, which matters when several text boxes should change together.

Timing.RunCoroutine(_UpdateTime(), Segment.SlowUpdate);

private IEnumerator<float> _UpdateTime()
{
    while (true)
    {
        clock = Timing.LocalTime;
        yield return 0f;
    }
}
Note: Unity's Time.deltaTime will not return the correct value during SlowUpdate, because Unity's Time class knows nothing about this segment. Use Timing.DeltaTime instead.

You can change how often SlowUpdate runs:

Timing.Instance.TimeBetweenSlowUpdateCalls = 3f; // once every 3 seconds

10 Tags

When you start a coroutine you can supply a tag: a string that identifies it. Tag a coroutine or a group of them and you can later kill them all with KillCoroutines(tag).

Timing.RunCoroutine(_shout(1, "Hello"), "shout");
Timing.RunCoroutine(_shout(2, "World!"), "shout");
Timing.RunCoroutine(_shout(3, "I"), "shout2");
Timing.RunCoroutine(_shout(4, "Like"), "shout2");
Timing.RunCoroutine(_shout(5, "Cake!"), "shout2");

Debug.Log("Killed " + Timing.KillCoroutines("shout2"));

11 LocalTime and DeltaTime

MEC keeps track of the local time inside each segment and keeps Timing.LocalTime and Timing.DeltaTime updated. Unity's default Time class works fine most of the time, but not in the SlowUpdate segment, where you should use the MEC values instead.

12 Helper Functions

Three helpers on the Timing object cover patterns you reach for constantly:

// Start _RunFor5Seconds two seconds from now
Timing.CallDelayed(2f, delegate {
    Timing.RunCoroutine(_RunFor5Seconds(handle)); });

// Push this object forward one unit per second for 4 seconds
Timing.CallContinuously(4f, delegate {
    PushOnGameObject(Vector3.forward); }, Segment.FixedUpdate);
Tip: CallContinuously also has a non-closure, generic version. Avoid closures here, since a closure on CallContinuously results in a GC allocation every frame.

13 Multiple Instances

If you do nothing special, the Timing object adds itself to a new object named "Timing Controller" and hands out all coroutine processes from there. If you want more control, attach the Timing object to a GameObject yourself, or create more than one. The Timing.RunCoroutine functions are static, but with a handle to a specific instance you can call yourInstance.RunCoroutineOnInstance() to run on that instance. Creating multiple instances effectively groups processes that can be paused or destroyed together. All static methods have instance variants (RunCoroutineOnInstance, KillCoroutinesOnInstance, and so on).

14 Quick Reference

Running and stopping

MethodDescription
Timing.RunCoroutine(coroutine)Runs a coroutine in the Update segment. Returns a CoroutineHandle.
Timing.RunCoroutine(coroutine, segment)Runs a coroutine in the specified segment.
Timing.RunCoroutine(coroutine, tag)Runs a coroutine with a tag for batch operations.
Timing.KillCoroutines()Kills all coroutines on the main instance. Returns the count killed.
Timing.KillCoroutines(handle)Kills a single coroutine by handle.
Timing.KillCoroutines(tag)Kills all coroutines with the given tag.
Timing.IsRunning(handle)True if the handle points to a coroutine that is still running (paused counts as running).

Pausing and resuming

MethodDescription
Timing.PauseCoroutines() / (handle) / (tag)Pauses all, one, or a tagged group of coroutines.
Timing.ResumeCoroutines() / (handle) / (tag)Resumes all, one, or a tagged group of coroutines.

Waiting and timing

MethodDescription
Timing.WaitForOneFrameConstant. Use with yield return to wait one frame.
Timing.WaitForSeconds(seconds)Pauses for the given duration.
Timing.WaitUntilDone(handle)Pauses the current coroutine until the target finishes.
Timing.LocalTimeTime in seconds that the current segment has been running.
Timing.DeltaTimeTime since the last tick in the current segment. Use this in SlowUpdate.
Timing.SetSegment(handle, segment)Moves a running coroutine to a different segment.

Helpers and extensions

MethodDescription
Timing.CallDelayed(delay, action)Calls the action once after a delay.
Timing.CallContinuously(time, action)Calls the action every frame for the given duration.
Timing.CallPeriodically(time, period, action)Calls the action every period seconds for the given duration.
coroutine.CancelWith(gameObject)Stops the coroutine when the GameObject is destroyed or disabled.

15 FAQ

Does MEC have a function for StopCoroutine?

Yes, it is Timing.KillCoroutines(). It takes a handle or a tag. To end a coroutine from inside its own function, use yield break; instead, which is the equivalent of return; in a normal function.

Does MEC completely remove GC allocations?

No. MEC removes all per-frame GC allocations. When a coroutine is first created, the function pointer and any variables you pass in are placed on the heap and eventually collected. This first allocation happens in both Unity and MEC. MEC coroutines allocate less on average, and produce less GC allocation than Unity's in all cases, except if you allocate large strings and use them as a coroutine tag.

Any advantages besides speed and lower GC?

Yes. Unity's coroutines are attached to the object they were started on; MEC uses a central object to run all processes. So: Unity coroutines will not start if the object is disabled, while MEC does not care. Disabling a GameObject stops its Unity coroutines (and they do not resume on re-enable), while MEC coroutines keep going unless you tell them otherwise. Destroying the object kills its Unity coroutines, but not MEC's. MEC also lets you create coroutine groups to pause, resume, or destroy together, and lets you run in LateUpdate or SlowUpdate. MEC Pro adds even more segments.