I admit it: I have neglected to directly talk about something that people who have been using Ki end up running into, and it’s about “pivot state” errors. This is something that I have been asked about a few times, and while I do describe what the error means, I, for some darn reason, just keep forgetting to write it down for others to learn about. Well know more. This sad lack of information shall end now.

So here’s the scenario: You’re having fun (yes, that’s right, fun!) building your statechart using Ki to make your SproutCore application’s logic awesome. (No, not just awesome, rather — super awesome!) Things are going great, until, at some inevitable point, you notice that your statechart stops working. You look down at your browser’s JavaScript console to see a funny looking error that reads something like this:

ERROR Ki.Statechart<sc487>: Can not go to state Ki.State<hamsterState, sc333>. Pivot state Ki.State<guacamoleState, sc254> has concurrent substates.

In your initial reaction you ask, what the heck does this error mean? You then follow it up with, what the heck is a pivot state? Finally, you ask, what does this have to do with concurrent substates? In a state of shock you then realize you’ve managed call two of your states “hamsterState” and “guacamoleState”. All hope is lost. Trust me, that is exactly what happened to you. Don’t deny it. This is a circle of trust.

With you at your wits end and no guacamole in sight (or hamsters), you give up cursing the day you ever thought statecharts could make your life a better world in which to live. Well, there is hope, and those better days are coming back. So let’s dig in to what this ridiculous error really means and know how to avoid it.

Making Sense of the Pivot State Error

Let’s say we have the following statechart:

MyApp.statechart = Ki.Statechart.create({

  stateAreConcurrent: YES,

  stateA: Ki.State.design({

    initialSubstate: 'stateJ',

    stateJ: Ki.State.design(),

    stateK: Ki.State.design()

  }),

  stateB: Ki.State.design({

    initialSubstate: 'stateX',

    stateX: Ki.State.design(),

    stateY: Ki.State.design()

  })

});

Above, the statechart has states A and B that are directly concurrent to each other and each contains their own set of substates. When dealing with concurrent states, they need to be seen as completely independent of each other, which also means that the substates belonging to A and B only care about other states that descend from the same concurrent parent state. Therefore, states J and K should only ever make reference to each other under state A, and states X and Y should only ever make reference to each other under state B.

A problem appears when either states A, J or K try to make a state transition to states X, Y or B, and when the respective opposite tries to be performed. Why? Because it does not make sense to transition out of state A and into state B or one of its substates when they are already concurrent to each other. What does it mean to leave concurrent state A that has been entered when you still have to be in state A? Nothing.

Now, regarding what the pivot state is, it is used to determine when to go (or pivot) from exiting states to entering states during a state transition process, and it is calculated each time the statechart’s gotoState method is called. What the pivot state turns out to be is the most direct common ancestor of the state you want to go from and the state you want to go to. So, if state J was the current state and you wanted to transition to state K, then the pivot state would be state A since it is the most direct common ancestor of the two states. Not the root state since state A is a closer common ancestor even though the root state is also a common ancestor of both state’s J and K. The pivot state itself is never entered or exited.

When you try to make a state transition from state K to, say, state X, the pivot state is calculated to be the root state, for this particular case. After the pivot state has been calculated but just before the actual transitioning takes place, the statechart checks if the pivot state’s immediate substates are concurrent. If so, that’s when you end up getting that funny looking pivot state error in the brower’s JavaScript console, and looks like this:

ERROR Ki.Statechart<sc321>: Can not go to state Ki.State<stateB.stateX, sc871>. Pivot state Ki.State<__ROOT_STATE__, sc243> has concurrent substates.

Let’s pretend that this check did not take place and instead the statechart just went blissfully ahead with the actual transition. What would the transition look like? It would be the following (We will assume that state K and Y are both current states, and we want to transition from state K to state X):

  1. exit state K
  2. exit state A
  3. enter state B
  4. enter state X

Looking at the transition above, by exiting state A, A simply goes away. By that I mean state A will no longer be part of a state transition process until it gets entered again. But why would A go away when it is concurrent to B? They both have to have current states, so A needs to remain entered. As well, state B now has two current states X and Y, but according to our statechart, X and Y are not concurrent to each other, therefore states X and Y can not both be current states — only one of them can be current. Finally, B already has a current state, which means that B has already been entered. Then what does it mean to enter B again? As you can now tell, the logic breaks down pretty quickly and we are then left with a statechart that is invalid.

How to Address the Pivot State Error

Although the pivot state error has (hopefully) been clarified for you, that still leaves a lingering question as to what you need to do in order to avoid the error. The pivot state error is essentially telling you that you have created your statechart in an invalid way and it needs to be modified. Because this only happens with concurrent states, your focus then needs to be on how you’ve structured those states.

Basically, when you have a state that is concurrent to its immediate sibling states, you have to look at all of its substates as being a self contained module and only referring to each other when you want to perform a state transition. The exception to this rule is if one of those substates, or the concurrent state itself, wants to make a state transition to some other state that is not an immediate concurrent state. As an example:

MyApp.statechart = Ki.Statechart.create({

  initialState: 'stateFoo',

  stateFoo: Ki.State.design({

    stateAreConcurrent: YES,

    stateA: Ki.State.design({

      initialSubstate: 'stateJ',

      stateJ: Ki.State.design(),

      stateK: Ki.State.design()

    }),

    stateB: Ki.State.design({

      initialSubstate: 'stateX',

      stateX: Ki.State.design(),

      stateY: Ki.State.design()

    }),

  }),

  stateBar: Ki.State.design()

});

States A, B, J, K, X and Y are all now substates of state foo. In addition, we see that the state bar is a sibling to foo under the root state, but they are not concurrent to each other, so you can only be in either foo or bar, not both.

Because state foo is the initial state, then that means there are two current states: states J and X. States A, B, J, K, X and Y are all free to make a state transition to state bar. Why? Because bar is not a substate of states A and B, nor is bar a concurrent sibling to A and B.

Now let’s say, that we want to make a state transition from state J to state bar. What would the state transition process look like? Let’s find out:

  1. exit state J
  2. exit state A
  3. exit state X
  4. exit state B
  5. enter state bar

Ah, interesting. Notice how all the concurrent states must be exited first before the bar state can be entered. We couldn’t first leave states J and A and then immediately enter state bar because that, technically, leaves the statechart invalid due to it still having state B as being entered. Can you transition from state bar to any of foo’s states? Absolutely. In fact, let’s see what happens when we transition from bar to, say, state Y:

  1. exit state bar
  2. enter state B
  3. enter state Y
  4. enter state A
  5. enter state J

All works well. However, now notice that during the state transition process, the statechart must descend fully into state B before then descending into state A. This is an order of operations since state Y belongs to state B.

Wrapping Up

That was a lot of information being tossed at you for such a small and funny looking error, but it required a detailed explanation to assure you really know what’s going on under the hood. With the information now in hand, you are finally on your way back to conquering all and letting the best days be ahead of you. And, please, mind the hamsters.

Happy statechart coding!

-FC

Update (Feb 22, 2011): I just made a change to the error message so that it provides more information about what is going on. So instead of getting this:

ERROR Ki.Statechart<sc321>: Can not go to state Ki.State<stateB.stateX, sc871>. Pivot state Ki.State<__ROOT_STATE__, sc243> has concurrent substates.

You now get this:

ERROR Ki.Statechart<sc321>: Can not go to state Ki.State<stateB.stateX, sc871> from Ki.State<stateA.stateK, sc999>. Pivot state Ki.State<__ROOT_STATE__, sc243> has concurrent substates.
Advertisements