Boost logo

Boost :

Subject: Re: [boost] [msm] guard behavior if guard guards the transition MSM threats event as handled is that correct ?
From: Christophe Henry (christophe.j.henry_at_[hidden])
Date: 2011-11-24 16:21:59


>Here is the execution output of the example attached :
>
>MS1 Entering State: Idle by: event1
>MS1 Leaving State: Idle by: event1
>MS1 Entering State: SubRunning by: event1
>FwdGuard: returns:1, event: event2
>Processing BaseEvent Instance(event2)
>Setting guard false
>FwdGuard: returns:0, event: event3
>MS1 DEFERED: event3
>FwdGuard: returns:0, event: event4
>FwdGuard: returns:0, event: event3
>MS1 DEFERED: event3
>FwdGuard: returns:0, event: event5
>MS1 Leaving State: SubRunning by: event5
>MS1 Entering State: AfterSub by: event5
>MS1 no_transition event (event3)
>
>It seems that the bug not fixed ...

Sorry, I should have been more explicit (and avid sheating to save a guard).
You need to invert your 2 transitions to get the desired effect (processing
is done from the bottom of the table):

        Row < SubRunning , baseEvent , none , ProcessBaseEvent ,
FwdGuard >,
        Row < SubRunning , event3 , none , LogDefer , none
>

Now, deferring has a higher priority, which gives the following desired
output:

MS1 Entering State: Idle by: event1
MS1 Leaving State: Idle by: event1
MS1 Entering State: SubRunning by: event1
FwdGuard: returns:1, event: event2
Processing BaseEvent Instance(event2)
Setting guard false
MS1 DEFERED: event3 // ok, defer event3
FwdGuard: returns:0, event: event4 //event4 is not deferred
MS1 DEFERED: event3 // event4 processed, can we process event3? Nope, defer
again
FwdGuard: returns:0, event: event5
MS1 Leaving State: SubRunning by: event5
MS1 Entering State: AfterSub by: event5 // we changed state, can we process
event3?
MS1 no_transition event (event3) // yes but we have no transition, call
handler

>but this is my least worries (
>partly my fault You have asked me to test it for you .... but I did
>not sorry about that).

>My bigger problem is the interpretation of no_transition you saying
>that it is an error situation... Well I have in my code a lot of
>situation when there are signals which are not interested to my state
>machine but they are dispatched to me anyhow our modules are used by
>different places for different purposes and there are signals which
>are useful in one case but not needed for my purpose I just simply
>ignore them these are ending up in no_transition so there I just write
>a log entry that signal is not handled so for me it is not an error at
>all it is the intended behavior.

State machines have a long history in the hardware world. And there it is
tradition that if no transition processes an event, it is an error. Purists
spend a lot of time handling every event in every state and even have state
tables to check this.
This means that to be completely correct, we should write an internal
transition in every state for every event to document we choose to ignore
these events (I try to do it regularly and it does help me from time to
time).
The UML Standard kept all this. When a transition could handle the event but
rejects it, the event is processed. For once the Standard is about clear...
;-)
But there are solutions to your problem in MSM's toolbox.

>Now back to the My blocking problem .....
>
>We use at the moment about 36 MSM state machines

:)

>out of this 26 are
>performing specific well defined configuration task these 26
>state-machine are nested to each in to each other to perform
>complicated behaviors sometimes there are nesting goes done to 5 level
>deep. We have, I call them main state machines which are connect
>these small task to each other to build complex behavior. We have more
>than 500 different row entries spitted in to these state machines.
>At the beginning we try to use MSM way of state machine nesting and
>soon we figured out that there is no compiler in the word which can
>compile/link this code.

Admittedly. We'll have to be patient.

>Than I have decided to make smaller compile unit and make each state
>machine as a separate compile unit and let communicate them over a
>simple interface.
>Yes this slows done the dispatching because we have to re-dispatch
>each event in run-time as many times as many nesting level we have.
>But this tread of is acceptable. And yes this way state machines are
>holding references to outer state machines if it is running as a
>nested state machine and states which are nested state machines are
>holding references to inner state machine instances. Every state
>machine uses the same interface to communicate and we hide the
>construction of the state machines with factories. So this way the
>outer does not exposed to the inner the inner does not exposed to the
>outer leads us to a compile-able code :).

I personally use callbacks with boost::function or signals to avoid cycles
but yes, it is still a logical cycle.

>In case the state machine is in a stare where the sate is actually a
>holder of an inner state machine there is a Row with a baseEvent which
>this case re-dispatches the event to the inner one. Now if the inner
>one does not handles the event it shall send it back to the outer one.
>My assumption was that I can use the no_transition for this purpose
>but the implementation turns out different :(. For me name
>no_transition suggests that if no transition triggered. This function
>will be called and if the guard rejects the transition than this means
>to me that the transition is not triggered.
>Anyhow we could argue on it but in the end it does not help with my problem
>....

Sure. I see other ways out of your problem. I think they are elegant and
conform to the spirit of state machines:
- exit points, as previously said. Just write another region as last, which
will process your event and send it out to the outer. This has the
disadvantage to force you to check is the event has been processed or not.
- internal transitions in the submachine itself. IIUC, SubRunning is meant
to be a submachine, right? Then we only need to add this to its definition:

struct internal_transition_table : mpl::vector<
             Internal < event3 , Action , Guard >
> {};

(supposing you need a guard, this is optional).
What does this mean? We added to your submachine (or substate) an internal
transition table. By convention, MSM decides this is less "inner" than a
standard transition table, so the transition table is tried first. If no
transition from the transition table processes the event (or if the event is
rejected by guards), then the internal table is tried.
This can be easily used to replace your use of no_transition, it is more
elegant, Standard-conforming and you have extra capabilities (like having
different handlers, conflicting ones solved by guards, etc). I find this
quite fun :)

The whole internal transitions have been rewritten in 1.48 to allow this to
work for all machines so you will need it or trunk (sadly this also means
that while I tested as well as I could, I cannot exclude the possibility of
a bug).

HTH,
Christophe


Boost list run by bdawes at acm.org, gregod at cs.rpi.edu, cpdaniel at pacbell.net, john at johnmaddock.co.uk