Closure is not a Good Thing

Before we move on we need to take a quick break to have a little talk about closures.

Take a look at the following function:

void foo()
{
     Vector3 Offset = new Vector3(0f, 1f, 2f);
     
     Effect<Controller, Vector3> moveEffect = new Effect<Controller, Vector3>();
     moveEffect.Duration = 3f;
     moveEffect.RetrieveEnd = me => me.TargetLocation;
     moveEffect.OnUpdate = 
        (me, value) => me.MovingObject.transform.position = value + Offset;

     Movement.Run(this, moveEffect);
}

 

There are actually two problems with this block. The first is that we aren’t setting the RetrieveSequenceStart or RetrieveStart for the effect, so this effect will default to starting at (0, 0, 0) regardless of where the object happens to be at the moment, but that can be easily fixed by adding one of those two actions. The second problem is far more tricky.

The second problem is that we are using the variable Offset inside the action that we are storing in OnUpdate. Offset is a local variable that is scoped to the function foo, and by the time the OnUpdate method is called foo has already finished executing and quit. See the problem? How do we reference a variable every frame for three seconds when that variable has a different scope than our function?

However, this function will compile and run. So what is actually happening here?

What happens is the compiler recognizes that you are referencing a variable here that exists in a different context, so it takes a snapshot of that variable and bundles it along side the lambda expression in memory. Making a single copy of the variable wouldn’t be so bad, but the compiler can’t actually be sure you didn’t modify the variable while accessing it, so it ends up making another copy of Offset for every single time OnUpdate is run. This will eat up your memory fast.

Closures can be created for other reasons as well. A common mistake would be to use the line MovingObject.transform.position = value rather than me.MovingObject.transform.position = value. If you don’t put the me. in front of the line you’re breaking that bubble and accessing outside variables.

This is an important point. So important, in fact, that the Movement over Time framework checks for closures in the OnUpdate function before running every sequence. If you accidentally create a closure you’ll start getting a bunch of warnings in your console. Don’t ignore them, they’re important!

Closures are ok in functions that are executed once per effect, like RetrieveEnd. They only really hurt you in Effect.OnUpdate, Effect.RunEffectUntilTime, Effect.RunEffectUntilValue, and Effect.CalculatePercent.

Conceivably, you could run into a situation where you just have to create a closure. If you understand the performance implications but want to do it anyway then you can set Sequence.SupressClosureWarnings to true so that the warnings won’t pop up in your inspector. Use that field at your own risk.