[Qt-interest] Qml & State Machines

Volodimir.Burlik at nokia.com Volodimir.Burlik at nokia.com
Wed Dec 1 18:46:50 CET 2010


Hi Frank,

As you can imagine many things are open for interpretation :). And as you can imaging what I was describing already exists in some shape or form(not in qt/qml). So, yes - internal, external and system events (heartbeats, onentry) are treated differently in the dispatcher of the state machine.

Only 3 terms are used to absolutly minimize the entities in this scheme to make it simplest as possible: State, Event, Action. Guarded condition in essence is an event. All Ifs(predicates) are events. Origin of events, however, is different: external, internal(guarding condition/helpers/etc) and system - the ones associated with the life cycle of the state machine, states and parent-child relationships. System events generated by sm dispatcher. So, it has minimal notitions and clean abstructions. 

We are on the same page in most of the details. My question to qt guys is if they see this approatch fit for qml. Do they consider it overkill for UI handling? In addition - would Qml be a good place to have application/native code's state machine boby "declared". So you don't have to do things like this:

      QState *s1 = new QState();
      QEventTransition *enterTransition = new QEventTransition(button, QEvent::Enter);
	machine->addState(s1);

All you need to do is to declare state machine and launch it: myDailyRoutine.start( myDailyRoutineStateMachineBody );

Thanks
Vlad


-----Original Message-----
From: qt-interest-bounces at trolltech.com [mailto:qt-interest-bounces at trolltech.com] On Behalf Of ext K. Frank
Sent: Tuesday, November 30, 2010 9:23 PM
To: Qt-interest
Subject: Re: [Qt-interest] Qml & State Machines

Hello Vlad -

On Tue, Nov 30, 2010 at 7:35 PM,  <Volodimir.Burlik at nokia.com> wrote:
> Hi Frank,
>
> Two exremes:
> 1) You have only one state - basically it is event handler for all 
> events in the system - the logic ends up in the action 
> functions/handlers
>
> 2) You go crazy and create a state for each condition in your system. Bad, but still better to have logic co-located in one place, even big one.

In my experience, 2 can sometimes be worse that 1.

People often do write event-driven systems with nested if-then code.  It gets complicated and hard to maintain, but in practice, people sometimes (and with pain) get it to work.  But trying to transform what I've called "extended state variables" into pure enumerated state values can become fully unmanageable for realistic (and not overly complicated) problems.  (Among other things, the number of states can explode exponentially.)

Of course I'm not saying that 1 is good.  I think we both agree that neither extreme is the right design choice.

> It is designer decision which logic belongs to application/component logic, which one - implementation detail of one sequence of action. That's why I used "linear-ish" term for action function.

Absolutely.  And it's not always obvious what the right design decision is.  (That's why good programming is hard sometimes.)

>>>      As a somewhat contrived example, consider a state machine that 
>>> beeps every time you press a button one hundred times in a row.  You 
>>> could have one >>   hundred states, and the button event causes the 
>>> transition from state i to state i+1, with state 100 transitioning 
>>> to the beep state.  But it's a
>>>      fundamentally better design to have a single processing-button 
>>> state and an extended state variable, number-of-button-presses, with 
>>> a guard
>>>      condition that takes you to the beep state only when number-of-button-presses equals 100.
>
> Here it is:
>
> // more of a pseudo code though. Emmited properties mean absence of value or null.
>
>  property int counter:0
>  states: [
> ...
> function checkNumber(){ if(++counter == 
> 100)sm.pushEvent("INT_TimeToBeep");   // or Id } ...
>
> This example shows that counting is hidden and it is implementation detail. However resulting event (reached 100) is there to understand logic by just reading the state machine.

Yes, this is a perfectly viable design approach.  You can always get rid of formal guard conditions by moving the logical content of the guard conditions into actions, and have the actions emit helper events that trigger transitions to the desired states.

However, there are a couple of disadvantages to this approach.

The first is technical:  You either need to treat the helper event specially, and process it synchronously (or, effectively equivalently, put it on the front of the event queue), or you need additional logic in your state machine to take care cases where external events come in before you have processed your helper event.  (For example, how do you handle it if the button is clicked a 101st time before your "INT_TimeToBeep" helper event has triggered the desired transition?)

Having two kinds of events -- internally generated helper events that are processed synchronously, and external events that are processed asynchronously -- is a fully workable approach, and some state-machine frameworks do it this way (generally frameworks that discourage extended state variables, by the way), but in my mind, it's a complication.

The second disadvantage is more philosophical:  When building an event-driven system without using state machines the flow of control (i.e., what code gets executed in response to events) is usually encoded in nested if-then programming.  To me, the power of state machines is that the state (including extended state variables, if you use them) cleanly isolates everything you need to know in order choose which transition to make (and therefore what actions to execute) in response to an event.
The states and the allowed transitions are sometimes called the topology of the state machine.

In our example, the counter does control part of the state machine's topology -- in your implementation, through the helper event.  From this perspective, it really is part of the state.  Rather than view it as a mere implementation detail, I think it is better to use extended state variables and formally elevate the counter to part of the state, and let it directly control the topology through formal guard conditions.

It seems to me that this design more directly encodes what is actually going on -- the actual topology of the state machine, and ultimately the flow of control in your program.

> ...
>
> Thanks
> Vlad

Best.


K. Frank

_______________________________________________
Qt-interest mailing list
Qt-interest at trolltech.com
http://lists.trolltech.com/mailman/listinfo/qt-interest




More information about the Qt-interest-old mailing list