MEC / Pro Features Free quick start → / Asset Store

More Effective Coroutines Pro

Pro-only Features
Pro Edition

01 What is MEC Pro

More Effective Coroutines Pro runs on the exact same fast, optimized core as the free version, and adds a host of additional methods, most of which cannot be found in Unity's coroutines or anywhere else. This page covers the Pro-only features. For setup, migration from Unity coroutines, and core functionality, see the Quick Start Guide.

02 Additional Segments

MEC Pro adds several timing segments beyond Update, FixedUpdate, LateUpdate, and SlowUpdate.

RealtimeUpdate

Just like Update, but Timing.LocalTime and Timing.DeltaTime ignore Unity's timescale. Useful for menus that run while the game is paused (if you pause by setting timescale to 0).

Timing.RunCoroutine(_AnimateMenu(), Segment.RealtimeUpdate);

EndOfFrame

Executes as the very last action before the frame is done, after all rendering has finished.

Timing.RunCoroutine(_CaptureScreenshot(), Segment.EndOfFrame);

LateFixedUpdate

Executes when FixedUpdate happens, but after it has finished its work. Useful for operations that need to read the results of physics calculations performed in FixedUpdate.

Timing.RunCoroutine(_ReadPhysicsResults(), Segment.LateFixedUpdate);

ManualTimeframe

Can be configured to execute and define its notion of time in custom ways, giving you full control over when coroutines in this segment tick.

03 Layers and Tags

The free version has tags only. MEC Pro adds layers. Together, tags and layers are called graffiti, because both are just ways to identify a particular coroutine instance. The only real difference is that a layer is an integer and a tag is a string; in Pro you can supply one, the other, or both. Once an instance carries graffiti, you can pause, resume, kill, or use it for RunCoroutineSingleton.

Every GameObject has a unique GetInstanceID(), so even a swarm of identical enemies can be addressed individually. Kill all coroutines attached to one enemy with:

Timing.KillCoroutines(enemy.gameObject.GetInstanceID());

Graffiti can combine a layer and a tag. An "amnesia gun" could wipe just one enemy's AI:

Timing.RunCoroutine(_EnemyAI(), gameObject.GetInstanceID(), "AI");

void HitEnemyWithAmnesiaGun(EnemyController enemy)
{
    Timing.KillCoroutines(enemy.gameObject.GetInstanceID(), "AI");
}

KillCoroutines only kills instances that match all the graffiti you pass in, so the enemy would stand still while its other coroutines keep running. The same pattern makes pause-on-disable easy:

void Start()  { Timing.RunCoroutine(_MoveUpAndDown(), gameObject.GetInstanceID()); }
void OnEnable() { Timing.ResumeCoroutines(gameObject.GetInstanceID()); }
void OnDisable(){ Timing.PauseCoroutines(gameObject.GetInstanceID()); }

04 RunCoroutineSingleton

Often you want to run a coroutine but never run multiple copies of it at once. A classic case is a button that pops on and off screen as it is enabled and disabled: click back and forth quickly and several movement coroutines can stack up unpleasantly. RunCoroutineSingleton fixes that. The usual approach applies a unique tag to every coroutine in the set:

Timing.RunCoroutineSingleton(
    _ButtonPopIn(button.transform),
    "HealthButtonMovementSet",
    SingletonBehavior.Overwrite);

Tags allocate memory, so to be maximally GC-friendly you can define the singleton by handle instead:

CoroutineHandle handle;
handle = Timing.RunCoroutineSingleton(_MoveThatThing(), handle, SingletonBehavior.Overwrite);

The third parameter decides how conflicts are handled. Abort fails to run the new coroutine if a match already exists. Overwrite kills any matches, then runs yours. Wait keeps yours paused until all matches finish, then runs it. Wait can chain a sequence of coroutines that share a tag, each waiting on the matches that existed when it was created.

05 Controlling Coroutines by Handle

In Free, a CoroutineHandle is mainly used with KillCoroutines, PauseCoroutines, WaitUntilDone, and IsRunning. In Pro the handle itself exposes properties and methods for richer control.

Tag and Layer

CoroutineHandle handle = Timing.RunCoroutine(_Foo());
string oldTag = handle.Tag;
handle.Tag = "newTag";
if (handle.Layer == null)        // no layer assigned
    handle.Layer = gameObject.GetInstanceID();
Note: Layer is a nullable int (int?), so it may need casting to a regular int. Check for null when you query a tag or layer; null means none is assigned.

Segment

handle.Segment = Segment.SlowUpdate;

IsRunning, IsAliveAndPaused, IsValid

IsRunning returns true until the coroutine terminates (paused or held coroutines count as running); setting it to false kills the coroutine. IsAliveAndPaused returns true if the coroutine is paused, and setting it pauses or resumes. IsValid returns true if the handle has ever pointed to a valid coroutine.

OnDestroy

Register a callback that fires when a coroutine ends, whether it finishes, throws, or is killed:

CoroutineHandle handle = Timing.RunCoroutine(_LoadAssets());
handle.OnDestroy(() => Debug.Log("Loading complete."));
Note: avoid using OnDestroy on a coroutine you would also use CancelWith on, since that typically leads to exceptions when the GameObject is destroyed.

06 Linking Coroutine Handles

Handles can be linked with LinkCoroutines. A "master" coroutine sends a kill command to the "slave" when it ends, and copies any pause or resume commands (but not calls to WaitForSeconds). You link coroutines so you can start two or more independently but treat the whole group as one with the master's scope.

For example, a loading bar coroutine plus a second "sorry for the delay" coroutine that runs when loading stalls, with cancel and pause buttons driving the first. Turning one reference into a list gets messy fast; linking keeps it simple. The slave is always terminated or paused along with the master.

CoroutineHandle master = Timing.RunCoroutine(_LoadAssets());
CoroutineHandle slave  = Timing.RunCoroutine(_ShowDelayWarning());
Timing.LinkCoroutines(master, slave);

07 Getting the Current Handle

GetMyHandle lets a coroutine retrieve its own handle from the inside, so it can set up links on the fly, change its own tags, or coordinate static lists of instances.

private IEnumerator<float> _shout(float time, string text)
{
    yield return Timing.WaitForSeconds(time);
    CoroutineHandle myHandle = new CoroutineHandle();
    yield return Timing.GetMyHandle(x => myHandle = x);
    Debug.Log(myHandle.Tag + ": " + text);
}
Note: GetMyHandle is special: it does not cause a one-frame delay even though it sits in a yield return statement.

08 WaitUntilDone Overloads

MEC Pro adds shorthand overloads of WaitUntilDone that accept an IEnumerator<float> directly, starting a new coroutine and holding the current one until it finishes. It also accepts a CustomYieldInstruction.

// Free requires the longhand form:
yield return Timing.WaitUntilDone(Timing.RunCoroutine(_OtherCoroutine()));

// Pro shorthand, same effect:
yield return Timing.WaitUntilDone(_OtherCoroutine());

// Pro also supports CustomYieldInstruction:
yield return Timing.WaitUntilDone(customYieldInstruction);

09 Extension Methods

Extension methods modify how a coroutine behaves while running it. They are very lightweight, so use them liberally and chain them together.

Delay and DelayFrames

Delay runs the coroutine after a delay, which is cleaner than passing a delay parameter and waiting at the top of the function. It also has overloads that take a condition function and hold the coroutine until it returns false.

Timing.RunCoroutine(_MoveBackAndForth(button1).Delay(1f));

// Delay until a condition becomes false
Timing.RunCoroutine(_DoWork().Delay(() => !isReady));

// Generic version to avoid closure allocations
Timing.RunCoroutine(_DoWork().Delay(someData, (data) => !data.IsReady));

// Wait a number of frames instead of seconds
Timing.RunCoroutine(_DoWork().DelayFrames(5));

CancelWith (Pro overloads)

Beyond the GameObject form in the Quick Start, Pro adds two more. Pass a MonoBehaviour and the coroutine cancels when that script is destroyed or disabled; or pass a function returning a bool, and the coroutine ends as soon as it returns false. You can also chain CancelWith calls to guard against more than three objects going out of scope.

Timing.RunCoroutine(_AnimateUI().CancelWith(this));
Timing.RunCoroutine(_Coroutine().CancelWith(CancelFunction));
Timing.RunCoroutine(_Coroutine().CancelWith(obj1, obj2, obj3).CancelWith(obj4));

Append and Prepend

Chain two or more coroutines together, or append/prepend a delegate. Useful for running a coroutine and then cleaning up.

Timing.RunCoroutine(_TurnRight().Append(_TurnLeft()));

Timing.RunCoroutine(_MoveToFinalPosition(obj1).Append(delegate { Destroy(obj1); }));

Superimpose

Combines two coroutines into a single handle. The combined coroutine does not finish until both contributors are done, which pairs well with WaitUntilDone.

CoroutineHandle handle = Timing.RunCoroutine(_NetworkStream1().Superimpose(_NetworkStream2()));
Timing.RunCoroutine(_RunWhenDone(handle));

Hijack

Alters the return value of a coroutine, which is handy for cutscenes or replays where the same code should run in slow motion.

float slowdown = 0.1f;
Timing.RunCoroutine(_MoveButton().Hijack(input =>
{
    if (input <= Timing.LocalTime)
        input = (float)Timing.LocalTime;
    return input + slowdown;
}));