[statechart] Non-Const Machine Pass-throughs and state_cast
I am using Boost::Statechart for the first time, throwing in the bin a history of hand-coded and other class-based implementations of my own design, to realize a rather simple machine consisting of four events (start/stop/suspend/resume) and four states (stopped/running/stopped-suspended/running-suspended). The machine is effectively a timer and wraps another internal timer class that can be stopped/started and suspended/resumed. This is all the machine does. The internal timer class can get/set its interval and can also get/set a delegate, that is called (if set) when the timer fires. These internal timer management interfaces are "passed through" to the state machine interface, such that application/test code is as follows: ...(TimerDelegate & inDelegate, float inInterval) { Timer theTimer; theTimer.initiate(); BOOST_ASSERT(!theTimer.IsSuspended()); BOOST_ASSERT(!theTimer.IsRunning()); DisplayState(theTimer); theTimer.SetDelegate(inDelegate); theTimer.SetInterval(inInterval); cout << "The delegate is " << theTimer.GetDelegate() << endl; cout << "The interval is " << theTimer.GetInterval() << endl; BOOST_ASSERT(!theTimer.IsSuspended()); BOOST_ASSERT(!theTimer.IsRunning()); DisplayState(theTimer); theTimer.process_event(Events::Start()); BOOST_ASSERT(!theTimer.IsSuspended()); BOOST_ASSERT(theTimer.IsRunning()); DisplayState(theTimer); The delegate does the real work of the program and receives events that drive the timer machine. Where I have run into a problem is that my machine "pass through" setter methods allowing the delegate to be set or the timer interval to be changed (neither of which materially alter the state of the machine) cannot be called as they are non-const qualified whereas their corresponding getters work fine since they are const qualified. I've worked around this in the short term by qualifying the setters as const and then qualifying the data members as mutable; however, this strikes me as flat-out ugly and wrong. Is there a better way to accomplish this? Am I misusing the template/design pattern? I suppose I could applying a const_cast(); however, that seems just as bad as my existing workaround. class InternalTimer; struct TimerDelegate { virtual void TimerDidFire(const InternalTimer &inTimer) const = 0; }; class InternalTimer { public: InternalTimer(void); : ~InternalTimer(void); TimerDelegate *GetDelegate(void) const; float GetInterval(void) const; void SetDelegate(TimerDelegate *inDelegate); void SetInterval(float inInterval); void Start(void); void Stop(void); private: TimerDelegate * mDelegate; float mInterval; }; struct ITimerManagement { public: virtual TimerDelegate *GetDelegate(void) const = 0; virtual float GetInterval(void) const = 0; virtual void SetDelegate(TimerDelegate * inDelegate) = 0; virtual void SetInterval(float inInterval) = 0; }; struct IRunning { public: virtual bool IsRunning(void) const; protected: IRunning(bool inRunning); private: bool mRunning; }; struct ISuspended { public: virtual bool IsSuspended(void) const; protected: ISuspended(bool inSuspended); private: bool mSuspended; }; struct Active; struct Timer : state_machine< Timer, Active > { public: TimerDelegate * GetDelegate(void) const { return (state_cast< const ITimerManagement & >().GetDelegate()); } float GetInterval(void) const { return (state_cast< const ITimerManagement & >().GetInterval()); } void SetDelegate(TimerDelegate *inDelegate) { state_cast< ITimerManagement & >().SetDelegate(inDelegate); // XXX } void SetInterval(float inInterval) { state_cast< ITimerManagement & >().SetInterval(inInterval); // XXX } bool IsRunning(void) const { return (state_cast< const IRunning & >().IsRunning()); } bool IsSuspended(void) const { return (state_cast< const ISuspended & >().IsSuspended()); } }; struct Timer; struct Stopped; struct Running; struct StoppedSuspended; struct RunningSuspended; struct Active : ITimerManagement, simple_state< Active, Timer, Stopped > { virtual TimerDelegate * GetDelegate(void) const { return (mInternalTimer.GetDelegate()); } virtual float GetInterval(void) const { return (mInternalTimer.GetInterval()); } virtual void SetDelegate(TimerDelegate *inDelegate) { mInternalTimer.SetDelegate(inDelegate); } virtual void SetInterval(float inInterval) { mInternalTimer.SetInterval(inInterval); } void Start(void) { mInternalTimer.Start(); } void Stop(void) { mInternalTimer.Stop(); } private: InternalTimer mInternalTimer; }; struct Running : IRunning, ISuspended, state< Running, Active > { public: typedef mpl::list< transition< Events::Stop, Stopped >, transition< Events::Suspend, RunningSuspended > > reactions; Running(my_context inContext) : my_base(inContext), IRunning(true), ISuspended(false) { context< Active >().Start(); } ~Running(void) { context< Active >().Stop(); } }; Regards, Grant
Hi Grant
The machine is effectively a timer and wraps another internal timer class that can be stopped/started and suspended/resumed. This is all the machine does. The internal timer class can get/set its interval and can also get/set a delegate, that is called (if set) when the timer fires. These internal timer management interfaces are "passed through" to the state machine interface, such that application/test code is as follows: [snip code]
Where I have run into a problem is that my machine "pass through" setter methods allowing the delegate to be set or the timer interval to be changed (neither of which materially alter the state of the machine) cannot be called as they are non-const qualified whereas their corresponding getters work fine since they are const qualified.
So both the values of the delegate and the interval are orthogonal to the state the machine has? If so, I'm wondering why they're not members of struct Timer? This would make ITimerManagement unnecessary and also save quite a bit of code? If they absolutely must be members of Active then you best implement the Set functions with events (e.g. EvSetInterval which would carry the new interval as a data member) and leave the Get functions as they are. What is the purpose of the IRunning & ISuspended structs? HTH, -- Andreas Huber When replying by private email, please remove the words spam and trap from the address shown in the header.
On 8/10/09 12:48 AM, Andreas Huber wrote:
The machine is effectively a timer and wraps another internal timer class that can be stopped/started and suspended/resumed. This is all the machine does. The internal timer class can get/set its interval and can also get/set a delegate, that is called (if set) when the timer fires. These internal timer management interfaces are "passed through" to the state machine interface, such that application/test code is as follows:
[snip code]
Where I have run into a problem is that my machine "pass through" setter methods allowing the delegate to be set or the timer interval to be changed (neither of which materially alter the state of the machine) cannot be called as they are non-const qualified whereas their corresponding getters work fine since they are const qualified.
So both the values of the delegate and the interval are orthogonal to the state the machine has? If so, I'm wondering why they're not members of struct Timer? This would make ITimerManagement unnecessary and also save quite a bit of code?
Andreas:
Making the internal timer a member of the state_machine was precisely my
initial implementation; however, initially, I'd found a destruction ordering
problem with that approach. Basically, on destruction of the running state,
I ensure the timer is stopped:
struct Running :
Interfaces::IRunning,
Interfaces::ISuspended,
state
What is the purpose of the IRunning & ISuspended structs?
They are application-level pseudo-state queries. Thanks for the tips! Regards, Grant
Initially, the machine and, in turn, the internal timer object appeared to be destructed before the state, causing an assertion when I tried to stop the destructed object.
You can avoid that by calling terminate() from Timer::~Timer(). You do not need to wrap the machine into another object for this purpose.
Through iterating on the const state_cast issue, I had also revised the base of the Running state to 'state' rather than 'simple_state' which eliminated the construction assertion for that state.
Ok, in this case you probably called a state machine function from the state constructor, most likely context<>. This is not allowed, please see: http://www.boost.org/doc/libs/1_39_0/libs/statechart/doc/reference.html#Clas... HTH, -- Andreas Huber When replying by private email, please remove the words spam and trap from the address shown in the header.
participants (2)
-
Andreas Huber
-
Grant Erickson