Order of Operations in Components

I have a trigger component that can cause things to happen in my games as a response to other events. Each trigger has various properties (animation, sound, movement) that the trigger component uses to update other components.

The trigger component works through an array of these triggers in order, waiting for the relevant components to finish before moving on to the next one (a lot like SKAction, which I don’t use because, uh, reasons. [I should probably just use SKAction.]).

For example, if a trigger has an animation, the trigger component will ask the animation component to start this animation and will then query the animation component every frame until it’s done. Once the animation component is done, the trigger component will dispose of the trigger.

Planting the Seed of my Own Destruction

The trigger component has a triggers property that is an array of triggers that it gradually consumes. It has a currentTrigger property to keep track of the current one it’s waiting to complete. Finally, it has a delegate that it calls when all of the triggers in the array have completed:

The completionDelegate is designed to be disposable. The idea is that it is set on the trigger component at the same time the array of triggers is set, then called once at some later date when the triggers are completed before being removed.

However, because this delegate method is called in the trigger component’s update method, I could easily end up in the situation where the delegate is being sent the completion notice multiple times a second.

Leaving it up to an outside object to remove this delegate was just asking for bugs. As there was no reason that the trigger component itself couldn’t remove it, I changed it to this:

Problem solved!

The Absolute State of This

I also have a use component that, when activated, runs through the following states:

  1. Start
  2. Success/Failure
  3. End
  4. None

Each state can optionally have triggers that it can set on the trigger component:

If a state does set some triggers, I need to wait for them to finish before moving on to the next state. This is where the delegate method comes in:

If you can immediately see the problem with this, you’re doing better than I did.

Bugs, Bugs, Bugs

In testing, there was an entity that never reached its end state and it took me a while to figure out why. It’s only when I looked carefully at the stack trace did I realise what was happening:

  1. The delegate method was being called by the Trigger Component
  2. Which called the nextState() method of the Use Component
  3. Which called the runTriggers(_:) method of the Use Component
  4. Which set the delegate of the Trigger Component again

Once I followed all of that back up the stack, I realised that it was all being kicked off by this one line of code:

Then this next line would be called:

Which would nil out the completion delegate that the use component had just set up.

Of course, it’s an easy fix once you see it:

Switching the operations ensures that the trigger component is still in charge of removing the delegate once it had been called once but also allows the delegate to re-add itself if it has another round of triggers to work through.

In Conclusion

Order matters.

Also, watch those stack traces. They’re very helpful.

  • Share:

Get all of my latest posts direct to your inbox! Pop in your email address here: