[statechart] history transitions make state-local-storage "un-cool"
Apologies if this has been discussed before, but I couldn't find any mention of it: I am currently using the history facility of boost::fsm. I've discovered that when I re-enter a state, X, via a history transition I don't actually re-enter the same instance of X that I was in before, but instead enter a newly constructed instance of X. I just checked the UML spec regarding history transitions and it says that "Any necessary entry actions are performed", so given that boost::fsm uses constructors to represent entry actions I guess a new instance of X has to be constructed to ensure UML compliance. My actual concern is the effect this has on the "coolness" of state-local-storage (see http://boost-sandbox.sourceforge.net/libs/statechart/doc/faq.html#Whats_so_c...) It seems to me that state-local-storage, which I agree is generally cool, is particularly un-cool (and bug-inducing) in the presence of history transitions. I'm not sure what a good alternative is, but just thought I'd raise the discussion. cheers, mick PS: does the history facility display the same semantics in boost::statechart? I plan to move to statechart real soon now ...
Hi Mick Mick Hollins wrote:
I am currently using the history facility of boost::fsm. I've discovered that when I re-enter a state, X, via a history transition I don't actually re-enter the same instance of X that I was in before, but instead enter a newly constructed instance of X.
I just checked the UML spec regarding history transitions and it says that "Any necessary entry actions are performed", so given that boost::fsm uses constructors to represent entry actions I guess a new instance of X has to be constructed to ensure UML compliance.
Correct.
My actual concern is the effect this has on the "coolness" of state-local-storage (see http://boost-sandbox.sourceforge.net/libs/statechart/doc/faq.html#Whats_so_c...) It seems to me that state-local-storage, which I agree is generally cool, is particularly un-cool (and bug-inducing) in the presence of history transitions.
I'm not sure what a good alternative is, but just thought I'd raise the discussion.
When I implemented history, I had to make a choice between: a) The current behavior b) Abandon the entry-action --> ctor / exit-action --> dtor mappings and introduce enter() and exit() functions instead (exit() already exists for unrelated reasons). In non-history transitions the state objects would be constructed and destructed as they are now (and enter() / exit() would be called after construction / before destruction). However, when a "historized" state is exited, exit() is called but the state is not destructed. Instead, it is stored in the state_machine object. When a transition to history is made later the state is retrieved and only enter() is called. In my experience none of the above behaviors is entirely satisfying in practice because for some states you want a) and for others you'd want b). When a state contains two members you could even want a) for the first one and b) for the second one. Moreover, you can still get the desired behavior with a), by pushing the state-variables that you want preserved into outer states or the state machine object. As you observed, this essentially wrecks the benefits of state-local storage for those variables. I guess the main reasons that I settled with a) are: 1. It is simpler to explain and implement. 2. There were no votes in favor of b). In fact, you are the first user to bring this up. 3. History is used relatively rarely anyway. Then again, almost all my experience with state machines stems from the machine control field. Other areas might well have different requirements / expectations, so I'm interested in your use-case and how much of a difference b) would make. Also, there might be other, less intrusive approaches that I haven't thought about.
PS: does the history facility display the same semantics in boost::statechart?
Yes. Before switching to the current version I'd recommend reviewing the change history here: http://boost-sandbox.sourceforge.net/libs/statechart/doc/index.html Regards, -- Andreas Huber When replying by private email, please remove the words spam and trap from the address shown in the header.
Thanks for your quick reply Andreas, Andreas Huber wrote:
Hi Mick
Mick Hollins wrote:
I am currently using the history facility of boost::fsm. I've discovered that when I re-enter a state, X, via a history transition I don't actually re-enter the same instance of X that I was in before, but instead enter a newly constructed instance of X.
I just checked the UML spec regarding history transitions and it says that "Any necessary entry actions are performed", so given that boost::fsm uses constructors to represent entry actions I guess a new instance of X has to be constructed to ensure UML compliance.
Correct.
My actual concern is the effect this has on the "coolness" of state-local-storage (see http://boost-sandbox.sourceforge.net/libs/statechart/doc/faq.html#Whats_so_c...) It seems to me that state-local-storage, which I agree is generally cool, is particularly un-cool (and bug-inducing) in the presence of history transitions.
I'm not sure what a good alternative is, but just thought I'd raise the discussion.
When I implemented history, I had to make a choice between:
a) The current behavior b) Abandon the entry-action --> ctor / exit-action --> dtor mappings and introduce enter() and exit() functions instead (exit() already exists for unrelated reasons). In non-history transitions the state objects would be constructed and destructed as they are now (and enter() / exit() would be called after construction / before destruction). However, when a "historized" state is exited, exit() is called but the state is not destructed. Instead, it is stored in the state_machine object. When a transition to history is made later the state is retrieved and only enter() is called.
Given my recent experience, (b) sounds good to me :-) Would it be feasible to support both models somehow?
In my experience none of the above behaviors is entirely satisfying in practice because for some states you want a) and for others you'd want b).
Well, for states not involved in history transitions, (a) and (b) are more or less equivalent in terms of power of expression. I guess (a) is a little nicer as it doesn't involve 2-phase construction. For states that are involved in history transitions, it seems to me that (b) is the winner since it distinguishes between the two ways in which a state can be entered and is thereby more flexible. Om the subject of two phase construction, I often find that you need it for states anyway, as what you often want is the ability to provide parameters to the constructor of a state to which you are transitioning. The approach I've settled on for this is to post an "initialisation event" just before transitioning state, so that the initialisation event will be the first event handled by the state to which I am transitioning. Is there a better way to achieve this?
When a state contains two members you could even want a) for the first one and b) for the second one.
(b) allows you to have two members with different frequency of initialisation, but (a) makes that harder as it doesn't distinguish between time of state construction and time of (re)entry to a state.
Moreover, you can still get the desired behavior with a), by pushing the state-variables that you want preserved into outer states or the state machine object. As you observed, this essentially wrecks the benefits of state-local storage for those variables. I guess the main reasons that I settled with a) are: 1. It is simpler to explain and implement. 2. There were no votes in favor of b). In fact, you are the first user to bring this up.
Are you saying that I am the first person to attempt to use state local storage and history together?
3. History is used relatively rarely anyway.
I love the history facility (of both UML statecharts and boost::statechart). In fact, history ranks in my top 3 things I love about UML statecharts. The top 3 being: 1) heirarchical states 2) history states 3) deferred event delivery For the record, what I love about boost::statechart is: 1) It makes it trivial to map from a UML statechart to C++ code. When programming using boost::statechart I spend a great deal of my time working at the diagram level, rather than at the code level. That is great for productivity. I've never really experienced such a degree of productivity improvement from other bits of UML. 2) State local storage lets me constrain the scope of variables to exactly where I need them I find state local storage so nice that I like to use it for all my states. The problem is that if I realise down the track that a state needs to support history transitions then I need to rewrite the state to avoid using state local storage, possibly only after having tracked down a nasty bug caused by the history transitions.
Then again, almost all my experience with state machines stems from the machine control field. Other areas might well have different requirements / expectations, so I'm interested in your use-case and how much of a difference b) would make. Also, there might be other, less intrusive approaches that I haven't thought about.
I've started to think about other ways to represent state local storage that work well with history. If you have any suggestions, I'd love to hear them. cheers, mick
Mick Hollins wrote:
When I implemented history, I had to make a choice between:
a) The current behavior b) Abandon the entry-action --> ctor / exit-action --> dtor mappings and introduce enter() and exit() functions instead (exit() already exists for unrelated reasons). In non-history transitions the state objects would be constructed and destructed as they are now (and enter() / exit() would be called after construction / before destruction). However, when a "historized" state is exited, exit() is called but the state is not destructed. Instead, it is stored in the state_machine object. When a transition to history is made later the state is retrieved and only enter() is called.
Given my recent experience, (b) sounds good to me :-)
Ok, noted.
Would it be feasible to support both models somehow?
It might be, by letting users that require b) derive from a special state.
In my experience none of the above behaviors is entirely satisfying in practice because for some states you want a) and for others you'd want b).
Well, for states not involved in history transitions, (a) and (b) are more or less equivalent in terms of power of expression. I guess (a) is a little nicer as it doesn't involve 2-phase construction.
Exactly.
For states that are involved in history transitions, it seems to me that (b) is the winner since it distinguishes between the two ways in which a state can be entered and is thereby more flexible.
It would definitely be more flexible, no question about that. See below.
Om the subject of two phase construction, I often find that you need it for states anyway, as what you often want is the ability to provide parameters to the constructor of a state to which you are transitioning.
Yes, that is a common problem. AFAICT, UML only allows for event access in a transition action but not in entry and exit actions.
The approach I've settled on for this is to post an "initialisation event" just before transitioning state, so that the initialisation event will be the first event handled by the state to which I am transitioning. Is there a better way to achieve this?
Not currently. triggering_event(), which is on position 2 of the to-do list, would allow ctors, dtors & exit() functions to access the event that triggered the state entry or exit. Since these can be triggered by events of different types, the function only returns the base class event, which needs to be down-cast by the user. I'm not very happy with this interface though, so I will also consider the possibility to type-safely pass the triggering event to state constructors that accept a corresponding parameter. AFAICS, this would require a SFINAE compiler (i.e. a very compliant compiler). Unfortunately, due to inevitable type-erasure when the state is stored in state_machine, the same doesn't seem to be possible with exit() functions, at least not without major performance hits.
When a state contains two members you could even want a) for the first one and b) for the second one.
(b) allows you to have two members with different frequency of initialisation, but (a) makes that harder as it doesn't distinguish between time of state construction and time of (re)entry to a state.
With b) you'd need to do extra work to initialize non-POD variables you want reset for normal and history entry.
Moreover, you can still get the desired behavior with a), by pushing the state-variables that you want preserved into outer states or the state machine object. As you observed, this essentially wrecks the benefits of state-local storage for those variables. I guess the main reasons that I settled with a) are: 1. It is simpler to explain and implement. 2. There were no votes in favor of b). In fact, you are the first user to bring this up.
Are you saying that I am the first person to attempt to use state local storage and history together?
No. I know of at least 2 users that use both history and state-local storage. They were/are apparently happy with the current behavior.
3. History is used relatively rarely anyway.
I love the history facility (of both UML statecharts and boost::statechart). In fact, history ranks in my top 3 things I love about UML statecharts. The top 3 being:
1) heirarchical states 2) history states 3) deferred event delivery
Ok, noted. My guess is that only about 1/3 of the users really need history. No hard numbers, just a feeling.
For the record, what I love about boost::statechart is:
1) It makes it trivial to map from a UML statechart to C++ code. When programming using boost::statechart I spend a great deal of my time working at the diagram level, rather than at the code level. That is great for productivity. I've never really experienced such a degree of productivity improvement from other bits of UML. 2) State local storage lets me constrain the scope of variables to exactly where I need them
I find state local storage so nice that I like to use it for all my states. The problem is that if I realise down the track that a state needs to support history transitions then I need to rewrite the state to avoid using state local storage, possibly only after having tracked down a nasty bug caused by the history transitions.
I was aware of the theoretical problem but I didn't realize that it could be so relevant in practice. When realizing that a state needs to support history transitions, how many of the state-local member do you usually need to push outward? All of them or just a fraction? Also, I would appreciate your sharing of some numbers of your use case: - Number of machines - Number of states - Number of states with history (shallow/deep) - Number of history transitions + number of history initial states
Then again, almost all my experience with state machines stems from the machine control field. Other areas might well have different requirements / expectations, so I'm interested in your use-case and how much of a difference b) would make. Also, there might be other, less intrusive approaches that I haven't thought about.
I've started to think about other ways to represent state local storage that work well with history. If you have any suggestions, I'd love to hear them.
Library support similar to b) would require a full rewrite of the history facility and would almost certainly break existing code at runtime (code that relies on member variables being initialized in the state ctor, no matter whether the state is entered through history or normally). Moreover, the interface would become more complex (users not yet using history would rightly ask themselves where to put their entry action and I wouldn't be able to give a clear guideline). I think it's obvious that I'd need more people requesting b) before I could justify the effort and the negative consequences. Plus, even when there are enough people the chance of having a working implementation in the next 6 months are very slim (I'm currently a bit short in spare time :-(). In the mean time, I think one could alleviate the problem a little bit: // pseudo-code template< /* same parameters as state_machine */ > struct micks_state_machine : sc::state_machine< /* forward parameters */
{ template< typename T > void store_persistent_member( /* .. */ ); template< typename T > T retrieve_persistent_member( /* ... */ ); bool has_persistent_member( /* ... */ ); private: // hash_map that maps an ID to boost::any }; // Use this class instead of normal members in states that are // entered through histroy transitions. template< class Machine, class T > class persistent_member { public: persistent_member( Machine & m, T initial ) { if ( m.has_persistent_member( /* ... */ ) { member = m.retrieve_persistent_member( /* ... */ ); } else { member = initial; } } ~persistent_member() { m.store_persistent_member( /* ... */ ); } T & get() { return member; } private: Machine & m; T member; } I realize this only works with states that are entered through history only, because you currently have no way of knowing whether you have normal or history entry... HTH & Regards, -- Andreas Huber When replying by private email, please remove the words spam and trap from the address shown in the header.
On Mon, Oct 17, 2005 at 6:48 PM, Andreas Huber
[snip]
Om the subject of two phase construction, I often find that you need it for states anyway, as what you often want is the ability to provide parameters to the constructor of a state to which you are transitioning.
Yes, that is a common problem. AFAICT, UML only allows for event access in a transition action but not in entry and exit actions.
The approach I've settled on for this is to post an "initialisation event" just before transitioning state, so that the initialisation event will be the first event handled by the state to which I am transitioning. Is there a better way to achieve this?
Not currently. triggering_event(), which is on position 2 of the to-do list, would allow ctors, dtors & exit() functions to access the event that triggered the state entry or exit. Since these can be triggered by events of different types, the function only returns the base class event, which needs to be down-cast by the user. I'm not very happy with this interface though, so I will also consider the possibility to type-safely pass the triggering event to state constructors that accept a corresponding parameter. AFAICS, this would require a SFINAE compiler (i.e. a very compliant compiler). Unfortunately, due to inevitable type-erasure when the state is stored in state_machine, the same doesn't seem to be possible with exit() functions, at least not without major performance hits.
Hi, has anything improved in this matter? I find two-phase construction not something desirable. I would really want to pass a state instance to transit<>(). Why isn't this available? [snip] -- Felipe Magno de Almeida
Hi Felipe
Not currently. triggering_event(), which is on position 2 of the to-do list, would allow ctors, dtors & exit() functions to access the event that triggered the state entry or exit. Since these can be triggered by events of different types, the function only returns the base class event, which needs to be down-cast by the user. I'm not very happy with this interface though, so I will also consider the possibility to type-safely pass the triggering event to state constructors that accept a corresponding parameter. AFAICS, this would require a SFINAE compiler (i.e. a very compliant compiler). Unfortunately, due to inevitable type-erasure when the state is stored in state_machine, the same doesn't seem to be possible with exit() functions, at least not without major performance hits.
Hi, has anything improved in this matter?
No, unfortunately.
I find two-phase construction not something desirable.
Neither do I, but I have come to the conclusion that it's probably the best of all approaches. As mentioned, triggering_event() wrecks type-safety and type-safely calling the right constructor is only a half-backed solution (doesn't work with exit()). triggering_event() might still be necessary for rare corner-cases, which is why it is still on the to-do list. I certainly haven't received many requests in this direction.
I would really want to pass a state instance to transit<>(). Why isn't this available?
Um, why do you want pass a *state* object to transit<>()? State construction & destruction must be done by the state machine itself. Regards, -- Andreas Huber When replying by private email, please remove the words spam and trap from the address shown in the header.
On Wed, Jun 18, 2008 at 6:54 PM, Andreas Huber
Hi Felipe
[snip]
I would really want to pass a state instance to transit<>(). Why isn't this available?
Um, why do you want pass a *state* object to transit<>()? State construction & destruction must be done by the state machine itself.
I understand it conceptually, but practically I can't see much difference from I constructing it or the machine doing it. All I wanted is to pass constructor arguments. Explicitly or not. The machine would have to copy the state, but I don't mind if at least I can give arguments to the next state somehow. Can't we pass the arguments to transit<>() ? I'm not an expert in statechart implementation, but it shouldn't be that hard, transit<> could pass a boost::factory to the statechart internals responsible for instantiating a state.
Regards,
-- Andreas Huber
Thanks, -- Felipe Magno de Almeida
"Felipe Magno de Almeida"
I would really want to pass a state instance to transit<>(). Why isn't this available?
Um, why do you want pass a *state* object to transit<>()? State construction & destruction must be done by the state machine itself.
I understand it conceptually, but practically I can't see much difference from I constructing it or the machine doing it.
A state constructor is the entry action of a state and the destructor is the exit action of a state (there's also exit() but let's ignore that for the moment). Now, during a transition these actions must be called in a well-defined order (as mandated by UML, Harel, etc.). If you construct the destination state before the origin state is destructed then said order is violated.
All I wanted is to pass constructor arguments. Explicitly or not.
I do agree that this is sometimes desirable. So far, I have worked around this by posting an event carrying the data (often, the posted event is a copy of the event triggering the transition). The destination state simply defines an in_state_reaction to pick up the previously posted event. Sure, this is kind of ugly.
The machine would have to copy the state, but I don't mind if at least I can give arguments to the next state somehow. Can't we pass the arguments to transit<>() ?
I guess we could, but as mentioned this requires some pretty involved compiler magic.
I'm not an expert in statechart implementation, but it shouldn't be that hard, transit<> could pass a boost::factory to the statechart internals responsible for instantiating a state.
Hm, something along these lines might actually work. Remember though, that a transition could potentially create more than one state object, so an example factory object could look like this: class MyStateFactory { public: MyStateFactory(/* arguments that should be passed to state ctors */) { /* store arguments in data members */ } template<class State> boost::intrusive_ptr<State> construct() { return new State(/* ctor arguments here */); } }; construct() would be called once for each state that is created as a result of a transition. If you want to treat different state types differently you'd need to specialize. Thoughts? Regards, -- Andreas Huber When replying by private email, please remove the words spam and trap from the address shown in the header.
On Thu, Jun 19, 2008 at 5:22 PM, Andreas Huber
"Felipe Magno de Almeida"
wrote in message
[snip]
Hm, something along these lines might actually work. Remember though, that a transition could potentially create more than one state object, so an example factory object could look like this:
class MyStateFactory { public: MyStateFactory(/* arguments that should be passed to state ctors */) { /* store arguments in data members */ }
template<class State> boost::intrusive_ptr<State> construct() { return new State(/* ctor arguments here */); } };
construct() would be called once for each state that is created as a result of a transition. If you want to treat different state types differently you'd need to specialize.
I was thinking more that boost.statechart would always use a functional factory,
like boost::function
Thoughts?
Is this possible?
Regards,
-- Andreas Huber
-- Felipe Magno de Almeida
On Fri, Jun 20, 2008 at 4:09 AM, Felipe Magno de Almeida
On Thu, Jun 19, 2008 at 5:22 PM, Andreas Huber
wrote: "Felipe Magno de Almeida"
wrote in message
Sorry replying to myself, but I must correct somethings. [snip]
I was thinking more that boost.statechart would always use a functional factory, like boost::function
() to instantiate states.
s/boost::function
And that when using transit<>(args), transit could create a different factory for this and pass along.
For example using boost::factory
when no argument is given or *can* be given, a simple boost::factory<State>() would be used as factory.
s/boost::factory<State>()/boost::factory
"Felipe Magno de Almeida"
On Fri, Jun 20, 2008 at 4:09 AM, Felipe Magno de Almeida
wrote: On Thu, Jun 19, 2008 at 5:22 PM, Andreas Huber
wrote: "Felipe Magno de Almeida"
wrote in message Sorry replying to myself, but I must correct somethings.
[snip]
I was thinking more that boost.statechart would always use a functional factory, like boost::function
() to instantiate states.
Which works for the construction of the destination state but not for the construction of possibly also entered outer and inner states of the destination state. For example, please have a look at the following chart: http://www.boost.org/doc/libs/1_35_0/libs/statechart/doc/LCA.gif During the transition triggered by the event Ev, the states X, Y and Z are entered and thus constructed. IIUC, with your approach it is only possible to pass arguments to the constructor of Y, but not to the ones of X and Z, correct? The solution I have in mind (see code in previous post) would allow you to selectively provide whatever arguments you might want to pass to X, Y and/or Z. Admittedly, passing arguments to outer and inner states of the destination might be a less frequent use case but I'd still rather not rule it out. Thoughts? Regards, -- Andreas Huber When replying by private email, please remove the words spam and trap from the address shown in the header.
On Fri, Jun 20, 2008 at 4:47 PM, Andreas Huber
"Felipe Magno de Almeida"
wrote in message
[snip]
I was thinking more that boost.statechart would always use a functional factory, like boost::function
() to instantiate states. Which works for the construction of the destination state but not for the construction of possibly also entered outer and inner states of the destination state. For example, please have a look at the following chart:
http://www.boost.org/doc/libs/1_35_0/libs/statechart/doc/LCA.gif
Ok, if I understand the diagram correctly, this is what happens:
struct Z : sc::simple_state
During the transition triggered by the event Ev, the states X, Y and Z are entered and thus constructed. IIUC, with your approach it is only possible to pass arguments to the constructor of Y, but not to the ones of X and Z, correct? The solution I have in mind (see code in previous post) would allow you to selectively provide whatever arguments you might want to pass to X, Y and/or Z. Admittedly, passing arguments to outer and inner states of the destination might be a less frequent use case but I'd still rather not rule it out.
Thoughts?
IIUC the diagram, the functional factory solution allows both Y and Z
to have arguments to its
constructors.
The Y state could pass the arguments to its base with the arguments
for Z constructor, which
would then create a factory to instantiate Z.
If someone wants to pass arguments to X, then they should transit to
X, and X transit to Y IMHO.
I see your triggering_event idea, and I find it not very useful
because of its necessary downcast
and dynamic typing, it also seems to me that the reponsability to
receive the arguments continue
to be in the just-entered state. Which I find dissatisfying. If I have
three or four state constructors,
each with different parameters, how would that be implemented with
triggering_event?
I hope not like this:
if(e1* p = dynamic_cast
Regards,
-- Andreas Huber
Regards, -- Felipe Magno de Almeida
"Felipe Magno de Almeida"
http://www.boost.org/doc/libs/1_35_0/libs/statechart/doc/LCA.gif
Ok, if I understand the diagram correctly, this is what happens:
struct Z : sc::simple_state
{ };
struct Y : sc::simple_state
{ }; struct B : sc::simple_state { typedef sc::custom_reaction< Ev > reactions;
boost::statechart::result react( Ev const& e) { // do something return transit<Y>() } };
Yep (Z should of course also be defined somewhere).
During the transition triggered by the event Ev, the states X, Y and Z are entered and thus constructed. IIUC, with your approach it is only possible to pass arguments to the constructor of Y, but not to the ones of X and Z, correct? The solution I have in mind (see code in previous post) would allow you to selectively provide whatever arguments you might want to pass to X, Y and/or Z. Admittedly, passing arguments to outer and inner states of the destination might be a less frequent use case but I'd still rather not rule it out.
Thoughts?
IIUC the diagram, the functional factory solution allows both Y and Z to have arguments to its constructors. The Y state could pass the arguments to its base with the arguments for Z constructor, which would then create a factory to instantiate Z.
So you're suggesting that simple_state::simple_state() should be overloaded say 10 times, one templated overload for each distinct number of parameters?
If someone wants to pass arguments to X, then they should transit to X, and X transit to Y IMHO.
Transiting to X could be semantically different from transiting to Y. In the diagram discussed so far it's not but looking at the transitions triggered by e3 and e4 in ... http://i.cmpnet.com/embedded/gifs/9901/9901feat1fig4.gif ... you'll see an example where it is. Transiting to S0 leads to the entry of S0 and S0_1 while transiting to S0_2 leads to the entry of S0 and S0_2.
I see your triggering_event idea, and I find it not very useful because of its necessary downcast and dynamic typing, it also seems to me that the reponsability to receive the arguments continue to be in the just-entered state. Which I find dissatisfying.
I wholeheartedly agree, but apart from your factory suggestion I've not yet seen any good ideas how this could be improved. Even if we were to go the factory route (my version), you'd be forced to implement quite a few factory classes (I guess approximately one for each state ctor with parameters). Sure this is better than anything we have now but it's not particularly elegant.
If I have three or four state constructors, each with different parameters, how would that be implemented with triggering_event? I hope not like this:
if(e1* p = dynamic_cast
... else if(e2* p = dynamic_cast ...
You could make your events visitable, in which case you could do with a finite number of casts/virtual function calls for any number of state ctors. Of course, this would require the implementation of a visitor class, which isn't exactly elegant either. Regards, -- Andreas Huber When replying by private email, please remove the words spam and trap from the address shown in the header.
On Mon, Jun 23, 2008 at 6:18 PM, Andreas Huber
"Felipe Magno de Almeida"
wrote in message news:a2b17b60806211502i447c22e1t9d552dd76b8972a1@mail.gmail.com...
[snip]
IIUC the diagram, the functional factory solution allows both Y and Z to have arguments to its constructors. The Y state could pass the arguments to its base with the arguments for Z constructor, which would then create a factory to instantiate Z.
So you're suggesting that simple_state::simple_state() should be overloaded say 10 times, one templated overload for each distinct number of parameters?
Yes. This can be done very easily with boost.preprocessor.
#ifndef BOOST_STATECHART_PARAMS
#define BOOST_STATECHART_PARAMS 10
#endif
#define BOOST_STATECHART_DETAIL_generate_overload(z, n, data) \
template
If someone wants to pass arguments to X, then they should transit to X, and X transit to Y IMHO.
Transiting to X could be semantically different from transiting to Y. In the diagram discussed so far it's not but looking at the transitions triggered by e3 and e4 in ...
http://i.cmpnet.com/embedded/gifs/9901/9901feat1fig4.gif
... you'll see an example where it is. Transiting to S0 leads to the entry of S0 and S0_1 while transiting to S0_2 leads to the entry of S0 and S0_2.
I understand. But if someone is trying to transit directly to an inner state, does he care about the local-state of the new outer state? This is not rhetorical actually, I'm a little inexperienced with developing state machines. So far I've writed a SMTP client with asio, and am creating a SMTP server. I have no other experience with state machines.
I see your triggering_event idea, and I find it not very useful because of its necessary downcast and dynamic typing, it also seems to me that the reponsability to receive the arguments continue to be in the just-entered state. Which I find dissatisfying.
I wholeheartedly agree, but apart from your factory suggestion I've not yet seen any good ideas how this could be improved.
I hope I convince you of my idea then. :)
Even if we were to go the factory route (my version), you'd be forced to implement quite a few factory classes (I guess approximately one for each state ctor with parameters).
I don't think creating classes is a good idea. The constructors would still be fixed. I believe constructor overloading is of much importance in this. A simple construction indirection is all that's needed for this to work. [snip]
You could make your events visitable, in which case you could do with a finite number of casts/virtual function calls for any number of state ctors. Of course, this would require the implementation of a visitor class, which isn't exactly elegant either.
I believe I'd rather continue with the workaround I use today.
sc::result react( event1 const& )
{
state_machine& m = context
Regards,
-- Andreas Huber
Regards, -- Felipe Magno de Almeida
On Tue, Jun 24, 2008 at 1:37 AM, Felipe Magno de Almeida
On Mon, Jun 23, 2008 at 6:18 PM, Andreas Huber
wrote: "Felipe Magno de Almeida"
wrote in message news:a2b17b60806211502i447c22e1t9d552dd76b8972a1@mail.gmail.com...
[snip]
If someone wants to pass arguments to X, then they should transit to X, and X transit to Y IMHO.
Transiting to X could be semantically different from transiting to Y. In the diagram discussed so far it's not but looking at the transitions triggered by e3 and e4 in ...
http://i.cmpnet.com/embedded/gifs/9901/9901feat1fig4.gif
... you'll see an example where it is. Transiting to S0 leads to the entry of S0 and S0_1 while transiting to S0_2 leads to the entry of S0 and S0_2.
I understand. But if someone is trying to transit directly to an inner state, does he care about the local-state of the new outer state? This is not rhetorical actually, I'm a little inexperienced with developing state machines. So far I've writed a SMTP client with asio, and am creating a SMTP server. I have no other experience with state machines.
Actually, I have myself an use case for passing arguments to outer states.
I'm just throwing here a suggestion:
How about having another way of transiting in this case?
cascate_transit<state1>(inner_arg1, inner_arg2)
.outer<state2>(middle_arg1, middle_arg2)
.outer<state3>(outer_arg1, middle_arg2);
template
Actually, I have myself an use case for passing arguments to outer states.
I'm just throwing here a suggestion: How about having another way of transiting in this case?
cascate_transit<state1>(inner_arg1, inner_arg2) .outer<state2>(middle_arg1, middle_arg2) .outer<state3>(outer_arg1, middle_arg2);
Something in this direction could work. With .outer you mean to imply that state2 is a direct outer state of state1, right? What if state2 doesn't require any ctor parameters but state3 does? Also, here's a somewhat esoteric but IMO still not unreasonable case: http://www.boost.org/doc/libs/1_35_0/libs/statechart/doc/CameraWithHistory2.... In the transition triggered by EvShutterReleased, it is clear that we're entering NotShooting but it's not clear whether we will enter Idle or Configuring (this depends on which of the two was active when we last left NotShooting). IMO, you should be able to supply ctor parameters for none, one or even both of the inner states of NotShooting. At this point I think it is kind of obvious that there's no point in enforcing an order of supplying ctor arguments. We need a map that associates a type with a construction function, with no particular order of map entries. Exactly how said map should look like and how it is constructed is not yet clear to me. I hope I'll find the time to toy around a little bit.
And completely off-topic: Also, a state that is instantiated through tss (when multithreaded), or through a global variable (when single-threaded) could be good.
I don't think I understand. Could you elaborate a little bit on this one? Regards, -- Andreas Huber When replying by private email, please remove the words spam and trap from the address shown in the header.
"Felipe Magno de Almeida"
On Mon, Jun 23, 2008 at 6:18 PM, Andreas Huber
wrote: "Felipe Magno de Almeida"
wrote in message news:a2b17b60806211502i447c22e1t9d552dd76b8972a1@mail.gmail.com... [snip]
IIUC the diagram, the functional factory solution allows both Y and Z to have arguments to its constructors. The Y state could pass the arguments to its base with the arguments for Z constructor, which would then create a factory to instantiate Z.
So you're suggesting that simple_state::simple_state() should be overloaded say 10 times, one templated overload for each distinct number of parameters?
Yes. This can be done very easily with boost.preprocessor. [snip]
I know, I just wanted to ensure that we're on the same page.
If you find it too inconvenient, a simple fusion sequence works too.
I'll have to have a look at fusion sequences then.
In my cppgui library I use a fusion sequence to allow passing argument constructors to windows:
wnd
w = create ( _arguments = fusion::make_vector(a1, a2, a3) ); But since this lib uses boost.named_parameters, I can't pass multiple arguments to it, so a sequence is the only thing that fits. Maybe as syntactic sugar this could be done, instead of overloading simple_state constructor.
template <typename T0> fusion::vector<T0> inner_state_arguments(T0);
template
fusion::vector inner_state_arguments(T0 a0, T1 a1); We could write:
struct Z : sc::simple_state
{ Z(int, int); }; struct Y : sc::simple_state
{ Y() : sc::simple_state(inner_state_arguments(0,1)) {} };
IIUC, then Y as defined above would require that all its inner states have a constructor with two integer parameters. I think this is too limiting.
If someone wants to pass arguments to X, then they should transit to X, and X transit to Y IMHO.
Transiting to X could be semantically different from transiting to Y. In the diagram discussed so far it's not but looking at the transitions triggered by e3 and e4 in ...
http://i.cmpnet.com/embedded/gifs/9901/9901feat1fig4.gif
... you'll see an example where it is. Transiting to S0 leads to the entry of S0 and S0_1 while transiting to S0_2 leads to the entry of S0 and S0_2.
I understand. But if someone is trying to transit directly to an inner state, does he care about the local-state of the new outer state?
In my experience, I would expect that he *usually* doesn't, but only usually.
I see your triggering_event idea, and I find it not very useful because of its necessary downcast and dynamic typing, it also seems to me that the reponsability to receive the arguments continue to be in the just-entered state. Which I find dissatisfying.
I wholeheartedly agree, but apart from your factory suggestion I've not yet seen any good ideas how this could be improved.
I hope I convince you of my idea then. :)
I'm sure we'll be able to agree on some form of ctor support.
Even if we were to go the factory route (my version), you'd be forced to implement quite a few factory classes (I guess approximately one for each state ctor with parameters).
I don't think creating classes is a good idea. The constructors would still be fixed.
I'm not sure I understand. How are the constructors "fixed" with the factory approach I suggested? How are they not fixed with other approaches? Regards, -- Andreas Huber When replying by private email, please remove the words spam and trap from the address shown in the header.
On Tue, Jun 24, 2008 at 4:57 PM, Andreas Huber
"Felipe Magno de Almeida"
wrote in message
[snip]
struct Z : sc::simple_state
{ Z(int, int); }; struct Y : sc::simple_state
{ Y() : sc::simple_state(inner_state_arguments(0,1)) {} }; IIUC, then Y as defined above would require that all its inner states have a constructor with two integer parameters. I think this is too limiting.
The naming is confusing, but I meant only the inner initial state Z. [snip]
I'm not sure I understand. How are the constructors "fixed" with the factory approach I suggested? How are they not fixed with other approaches?
My idea is that different transit calls could call different constructors with
different arguments.
struct A : sc::simple_state
{
A(int);
A(int, int);
};
struct B : sc::simple_state
{
typedef sc::custom_reaction<event1> reactiotns;
sc::result react(event1 const&) { return transit<A>(5); }
};
struct C : sc::simple_state
Actually, I have myself an use case for passing arguments to outer states.
I'm just throwing here a suggestion: How about having another way of transiting in this case?
cascate_transit<state1>(inner_arg1, inner_arg2) .outer<state2>(middle_arg1, middle_arg2) .outer<state3>(outer_arg1, middle_arg2);
Something in this direction could work. With .outer you mean to imply that state2 is a direct outer state of state1, right? What if state2 doesn't require any ctor parameters but state3 does? Also, here's a somewhat esoteric but IMO still not unreasonable case:
I wanted that empty constructors could be implicit, so if it wanted to call state2 with an empty constructor, then it wouldn't be needed to call .outer<state2>. And an empty call to outer<statex>() should be defined as calling the default constructor. Should this be too hard?
http://www.boost.org/doc/libs/1_35_0/libs/statechart/doc/CameraWithHistory2....
In the transition triggered by EvShutterReleased, it is clear that we're entering NotShooting but it's not clear whether we will enter Idle or Configuring (this depends on which of the two was active when we last left NotShooting). IMO, you should be able to supply ctor parameters for none, one or even both of the inner states of NotShooting.
At this point I think it is kind of obvious that there's no point in enforcing an order of supplying ctor arguments. We need a map that associates a type with a construction function, with no particular order of map entries. Exactly how said map should look like and how it is constructed is not yet clear to me. I hope I'll find the time to toy around a little bit.
I'm not sure if a map is helpful. What we really need, IMHO, is a way that reactions can construct dictate how state transition is going to be. Not only from a static typing POV, but as dynamic data as well. That's why I have focused on transit<> until now. I see that your example is related to history. Isn't it more related to how the state was, rather than how the state will be? I understand that *if needed* (as much as in my case for construction of outer states) people should be able to pass arguments for all states in the chain. But where someone doesn't matter, the states should be restored. So I think that my cascate_transit would do fine here too. IMHO, a transition is always specific, and anything that wouldn't allow different transitions to pass different arguments to different constructors of the same state in whichever reaction is not enough. Even the same reaction function should be able to transit to different state values for the same state type. I want this for example: struct A : sc::simple_state { int i; A(int i) : i(i) {} }; struct B : sc::simple_state { typedef sc::custom_reaction<event1> reactions; sc::result reac(event1 const& e) { return transit<A>(e.value); } };
And completely off-topic: Also, a state that is instantiated through tss (when multithreaded), or through a global variable (when single-threaded) could be good.
I don't think I understand. Could you elaborate a little bit on this one?
It might not be worth because states are completely templated, and
that could generate too much global points, though that can be
alleviated with downcasting and upcasting the context.
but let me show:
struct A : sc::auto_state
{
A()
{
context<machine>().process_event(event1());
}
};
the code that does the initialization for auto_state would be
something like this:
boost::thread_specific_ptr<context> global_auto_state_context;
template
Regards,
-- Andreas Huber
Regards, -- Felipe Magno de Almeida
"Felipe Magno de Almeida"
On Tue, Jun 24, 2008 at 4:57 PM, Andreas Huber
wrote: "Felipe Magno de Almeida"
wrote in message [snip]
struct Z : sc::simple_state
{ Z(int, int); }; struct Y : sc::simple_state
{ Y() : sc::simple_state(inner_state_arguments(0,1)) {} }; IIUC, then Y as defined above would require that all its inner states have a constructor with two integer parameters. I think this is too limiting.
The naming is confusing, but I meant only the inner initial state Z.
As we have seen in a different example, after Y is entered a different inner state than Z might be entered next. That's why the approach to pass the inner state arguments to simple_state<> is IMO not to be pursued further.
I'm not sure I understand. How are the constructors "fixed" with the factory approach I suggested? How are they not fixed with other approaches?
My idea is that different transit calls could call different constructors with different arguments. [snip]
I agree that it is a good idea to allow that. So with "fixed" you meant the fact that a user would have to implement two factories if two different constructors should be called in two different transitions. [snip]
quoting the other email:
Actually, I have myself an use case for passing arguments to outer states.
I'm just throwing here a suggestion: How about having another way of transiting in this case?
cascate_transit<state1>(inner_arg1, inner_arg2) .outer<state2>(middle_arg1, middle_arg2) .outer<state3>(outer_arg1, middle_arg2);
Something in this direction could work. With .outer you mean to imply that state2 is a direct outer state of state1, right? What if state2 doesn't require any ctor parameters but state3 does? Also, here's a somewhat esoteric but IMO still not unreasonable case:
I wanted that empty constructors could be implicit, so if it wanted to call state2 with an empty constructor, then it wouldn't be needed to call .outer<state2>. And an empty call to outer<statex>() should be defined as calling the default constructor. Should this be too hard?
If we were to define that only the destination state and its outer states can be called with parameters, then no this wouldn't be too hard. See below.
http://www.boost.org/doc/libs/1_35_0/libs/statechart/doc/CameraWithHistory2....
In the transition triggered by EvShutterReleased, it is clear that we're entering NotShooting but it's not clear whether we will enter Idle or Configuring (this depends on which of the two was active when we last left NotShooting). IMO, you should be able to supply ctor parameters for none, one or even both of the inner states of NotShooting.
At this point I think it is kind of obvious that there's no point in enforcing an order of supplying ctor arguments. We need a map that associates a type with a construction function, with no particular order of map entries. Exactly how said map should look like and how it is constructed is not yet clear to me. I hope I'll find the time to toy around a little bit.
I'm not sure if a map is helpful. What we really need, IMHO, is a way that reactions can construct dictate how state transition is going to be. Not only from a static typing POV, but as dynamic data as well. That's why I have focused on transit<> until now.
Ok, forget about the map (that might very well be an implementation detail only). I just wanted to emphasize that the transit<> interface should not force the user to mention the different state types in any particular order. Yes, there is a natural order for the destination state and its outer states, but there's none for the inner state(s) of the destination state. That's why I would prefer an interface where the user simply specifies a state type with its associated ctor arguments, then another type with its ctor arguments, etc. Whether he prefers to list the outer or the inner states first is best left to his taste.
I see that your example is related to history. Isn't it more related to how the state was, rather than how the state will be?
I don't think I understand. The only purpose of the history example was to show that you can't always predict at compile time exactly what inner states will be entered when you transition to a certain outer state. In the CameraWithHistory2 case, as a result of the transition triggered by EvShutterReleased, you will enter either Idle *or* Configuring, so you might want to specify ctor arguments for *both* states.
I understand that *if needed* (as much as in my case for construction of outer states) people should be able to pass arguments for all states in the chain. But where someone doesn't matter, the states should be restored.
Agreed.
So I think that my cascate_transit would do fine here too.
Not IMO, because "cascade" + "outer" suggest that there's a natural order that does not exist for inner states. Sure this is "just" naming and is easily resolved but I'm also not very fond of the the chaining interface (no technical reason just personal preference). I would much rather have something like this: transit<MyState>(ctor<SomeOtherState>(1, 42), ctor<YetAnotherState>(24), ...) You wouldn't mention states with default ctors. Also, you can mention the states in any order you see fit. Would this work for you? Disclaimer: I'm not yet sure whether this interface is implementable with reasonable effort. It might turn out *very* tricky with history.
IMHO, a transition is always specific, and anything that wouldn't allow different transitions to pass different arguments to different constructors of the same state in whichever reaction is not enough. Even the same reaction function should be able to transit to different state values for the same state type. I want this for example:
struct A : sc::simple_state { int i; A(int i) : i(i) {} };
struct B : sc::simple_state { typedef sc::custom_reaction<event1> reactions; sc::result reac(event1 const& e) { return transit<A>(e.value); } };
Agreed. Regards, -- Andreas Huber When replying by private email, please remove the words spam and trap from the address shown in the header.
On Thu, Jun 26, 2008 at 6:16 PM, Andreas Huber
"Felipe Magno de Almeida"
wrote in message news:a2b17b60806241815r4f0c6e5fpbfde9454701837d5@mail.gmail.com...
[snip]
I see that your example is related to history. Isn't it more related to how the state was, rather than how the state will be?
I don't think I understand. The only purpose of the history example was to show that you can't always predict at compile time exactly what inner states will be entered when you transition to a certain outer state. In the CameraWithHistory2 case, as a result of the transition triggered by EvShutterReleased, you will enter either Idle *or* Configuring, so you might want to specify ctor arguments for *both* states.
I'll need more time to think about history. [snip]
Not IMO, because "cascade" + "outer" suggest that there's a natural order that does not exist for inner states. Sure this is "just" naming and is easily resolved but I'm also not very fond of the the chaining interface (no technical reason just personal preference). I would much rather have something like this:
transit<MyState>(ctor<SomeOtherState>(1, 42), ctor<YetAnotherState>(24), ...)
This syntax can be achieved the same way the other one I think.
template
You wouldn't mention states with default ctors. Also, you can mention the states in any order you see fit. Would this work for you?
Yes, surely. But I'm a little unsure how this plays with history.
Disclaimer: I'm not yet sure whether this interface is implementable with reasonable effort. It might turn out *very* tricky with history.
History seems the main problem here. [snip]
Regards,
-- Andreas Huber
I'll give more thought to history and then hopefully I can give better suggestions. Thanks, -- Felipe Magno de Almeida
participants (4)
-
Andreas Huber
-
Andreas Huber
-
Felipe Magno de Almeida
-
Mick Hollins