Boost logo

Boost Users :

From: Andreas Huber (ahd6974-spamgroupstrap_at_[hidden])
Date: 2005-10-17 16:48:52


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.

Boost-users list run by williamkempf at hotmail.com, kalb at libertysoft.com, bjorn.karlsson at readsoft.com, gregod at cs.rpi.edu, wekempf at cox.net