Hi Albert,
we are talking about error handling and which entity should be the best to do it.
Well, in fact in I wasn't thinking about error handling...
Sorry, then I got it wrong. I interpreted your first post as a missing no_transition (which really was missing). Looks like you wanted something else. Let's see if I can help.
I was using an orthogonal region for my Timer state/class, not as an error handler, but as a "no activity handler"... ;-)
Let me see if I got it right. You want a timer to count from x to 0, and when timer == 0, generate a timeout event, correct? Then you want the possibility to reset the timer. IIUC, you implemented your timer as a second region with a single Timer state. This looks so far ok to me because the region can process events to the state machine (for other regions). Of course, the more complicated your timer gets, the harder it becomes.
My problem arise when the "activity" is generated/processed in a submachine.
Probably I should rethink my design: - A Timer inside all my (sub)state machines?
Sounds like a lot of work ;-)
- An independent state machine only for the Timer and (not so ugly) pointers from/to it in all my (sub) state machines?
This looks like a perfect use case for a submachine. Define it once as an independent state machine and reuse it in any state machine needing a timer. So far so good, but you are stuck with how to inform client state machines of your timer that there is a timeout, right?
- More work on the pseudo exit?
I was going to suggest you this. I see 3 possibilities defining a timer (sub) state machine: - templatize it on an ugly pointer type of the client outer fsm and call process_event (timeout) on it. Not going to win a beauty contest ;-) - make it less ugly by using a boost::function to inform caller. Not great either. - use the UML way. The timer is a state machine, with a pseudo exit when a timeout is detected. This is an encapsulated submodule used by any fsm. Like a well-behaved submodule, it advertises that timeout events are going to be fired through the pseudo exit. How it is done inside is not the outer fsm's business. What is the outer fsm's business, however, is to decide what to do next. Usually some error handling and the decision of whether to restart the timer, stop all, whatever. If restart the timer, no problem, the outer defines a transition from Timer::PseudoExit -> Timer, so that the timer can restart. This has several advantages: - no ugly pointer - UML-conform - perfectly reusable and encapsulated timer for different uses. Often, fsm's need a timer but differ in how they handle timeouts. Leave it to them. The documentation states the timeout is sent through a pseudo exit. Or several pseudo exit states. - the timer is a state machine in its own right, you can write a wonderful unit test for it alone.
From a user point of view, it seems to me logical that when I process an event on the top-level fsm, I get a no_transition handler called on this top-level fsm. I don't want a submachine to generate some error for an event the top fsm is going to handle right after (the UML Standard sees the priority of event handling be given to the "deeper" entity). It would only be a disturbance, right? Now, if a submachine sends an event to itself (as you are doing in your added transition of the submachine), I also find it logical that the error handler be located there, this is why no_transition of the submachine is called. That's why for this case, I needed to revise my previous position.
Sure you are right, but probably because I'm completely newbie on UML, I see the "no_transition" handler as a kind of "exception cached", which is more familiar to me... ;-)
For this reason I could think that a (sub) state machine could handle some "no_transitions" for some events (some cached cases), but it can also auto-upper-fwd them to the parent state machine...
But this is totally out of the scope of my Timer goal, and I've to remember: there is no upper-fwd!! ;-)
I understand but it's not the way the Standard is thought (well, at least I think). You're supposed to process an event on the outer, which then uses priority rules to know what's done (deeper first, completion events, deferred events, conflicts, etc.). Not that I have a problem "fixing" the Standard, but I'd find it hard to implement and document some basic mechanisms. For example, say I'd upper-fwd from a submachine in region 2 from 4. Which regions get to try handling the event? 3, then 4? 1 - 3 - 4? 1 -2 -3 -4? 3 - 4 - 1? What do I do if while I do this, another submachine wants to upper-fwd an event? And what do I do if this sub-fsm has a sub-sub-fsm? Do I process up or down?
As already said, several people asked for actions to get as parameter not only the current fsm but also the top-level one. I find it a good idea, I am just lacking time at the moment :(
I know, it will be great, thanks a lot for your time!! :-)
No problem. It's going to solve many problems anyway.
(again, and only if you are curious, take a look to the attached implementation...)
I did and I think you'll be satisfied with a submachine with pseudo-exit(s). At least in your timer case. There are others where there simply is no simple UML solution. Cheers, Christophe