FrozenCanuck / Ki

A Statechart Framework for SproutCore
http://frozencanuck.wordpress.com
Other
105 stars 7 forks source link

What are the differences between initialSubstate and substatesAreConcurrent? #7

Closed ghost closed 13 years ago

ghost commented 13 years ago

I wonder, from the view of the GUI/Statechart, what are the differences between initialSubstate and substatesAreConcurrent?

/Johnny

FrozenCanuck commented 13 years ago

This is easier to address with a proper blog post and diagrams, but I can still give you the quick summary here.

A statechart consists of a set of states that are organized in various ways. The most basic form is a root state that has no substates, meaning the statechart itself only has one state:

MyApp.statechart = Ki.Statechart.create({
  rootState: Ki.State.design({ ... }); // no assigned substates
});

All statecharts using Ki must have a root state. Of course, for most, only having a root state is not entirely useful. Rather, you'll start by adding substates to the root state, like so:

MyApp.statechart = Ki.Statechart.create({
  rootState: Ki.State.design({

    fooState: Ki.State.design({ ... });

    barState: Ki.State.design({ ... });

  });
});

Above, the root state now has two substates: fooState and barState. There's a problem here. When you initialize the statechart (i.e. MyApp.statechart.initStatechart()), the statechart will go through its procedures to instantiate all the states, wire them together, and then, finally, begin to enter the root state. Entering and exiting states is a recursive process, so when the root state is entered, the statechart will then enter one of the root state's substates. The problem with the code above is that we haven't informed the statechart about which substate to enter. Does the statechart enter the fooState or the barState? In order to remove this ambiguity, we inform the statechart about what substate to enter using the initialSubstate property, like so:

MyApp.statechart = Ki.Statechart.create({
  rootState: Ki.State.design({

    initialSubstate: 'fooState',

    fooState: Ki.State.design({ ... });

    barState: Ki.State.design({ ... });

  });
});

Now we have informed the statechart that when it enters the root state, it should then enter the substate fooState. fooState becomes the statechart's only current state. If you where to the do statechart.gotoState('barState') then a state transition process would occur and barState would then become the statechart's only current state. Let's move on to concurrent states.

Concurrent states are a bit more advanced, so let's start out with the idea in general. Let's say your application is made up of two modules that are independent (concurrent) of each other but operate at the same time. In addition, when an asynchronous event is picked up by the application is then forwards the event to each module in order for them to handle the event, if so required. Using statecharts, you can represent the states for both modules within the same statechart, like so:

MyApp.statechart = Ki.Statechart.create({

  rootState: Ki.State.design({

    moduleA: Ki.State.design({ ... }); // The first module

    moduleB: Ki.State.design({ ... }); // The second module

  });

}); 

Looking at the code above, this basically looks the same as the code with the fooState and barState, so what gives? With respect to the fooState and barState example, you can only be in one of those two states at any given time, not both; therefore, they are not independent (concurrent) to each other. However, for this example, we do want states moduleA and moduleB to be indepedent of each other so you can be in both states at the same time. In order to indicate this to the statechart, you use the substatesAreConcurrent property, like so:

MyApp.statechart = Ki.Statechart.create({

  rootState: Ki.State.design({

    substatesAreConcurrent: YES,

    moduleA: Ki.State.design({ ... }); // The first module

    moduleB: Ki.State.design({ ... }); // The second module

  });

}); 

With the substatesAreConcurrent set to YES on the root state, this then informs the statechart to enter both states, not just one of them. This also means that both moduleA and moduleB are current states on the statechart. You can always acquire the statechart's current states using the currentStates property. Now when you sent an event to the statechart using the sendEvent method, both moduleA and moduleB will pick up the event. So as an example:

MyApp.statechart = Ki.Statechart.create({

  rootState: Ki.State.design({

    substatesAreConcurrent: YES,

    moduleA: Ki.State.design({
      eventA: function() { ... };
      eventB: function() { ... };
    });

    moduleB: Ki.State.design({ 
      eventA: function() { ... };
      eventC: function() { ... };
    });

  }); 

Both modules can respond to event eventA, but moduleA can only respond to eventB and moduleB can only respond to eventC. So when you do statechart.sendEvent('eventA') both moduleA and moduleB will respond. When you do statechart.sendEvent('eventB') only moduleA will respond, and so on.

A note about the term "concurrent". I used the term to reflect what was in the original Harel statechart paper. He of course also uses the terms "independent" and "orthogonal". A few people have mentioned that "concurrent" is a bit confusing since states that are concurrent do not technically run at the same time as code does with true concurrency. True, browsers do not let JavaScript code to run concurrently, unless, of course, you happen to use the new HTML5 web workers, but I'll pretend most aren't right now. So for what it is worth, "concurrent" is what is used, and I still think the term applies correctly despite some technical details.

Hmm. I think I'll actually take this and make a more beefed up version as a blog post. In the mean time, I hope this helps you out.

MIke

ghost commented 13 years ago

This helped a lot. You made it so simple to get what this is all about!

Please do so and make this and other features (history states) to something others can read about. Maybe not a blog post but an actual documentation page?

Thanks a lot!