Clarification on statechart post_event vs process_event within react()

Hi, Apologies if this has already been answered/clarified, but my search of the internet to try to get a definitive answer, at least one I recognised as being definitive, was unsuccessful. Below I have posted some source code, based on the stopwatch example from the tutorial, but using an 'external' object to delegate the actual handling of event stuff to and using the state, rather than simple_state base class, and some other tweaks. This is a cut down version of something that someone I work with has devised, but I just wanted to get a clear answer on whether process_event() should be getting called in cases like this. I think it probably should not since, in the code below, you change state (with a state object's destructor being executed) whilst you're executing that state's react(EvStartStop) function. If someone can confirm this is the correct interpretation that would be very much appreciated. Any questions, feel free to ask (BTW - I'm compiling for C++17) John PS - In case it's of any use, the output from running this code is: ------ Entered Active Entered Stopped Stopped - payload2 m_charValue: a Exited ~Stopped <<<--- This is the bit I don't like the look of because... Entered Running Active - payload2 m_charValue: a <<<--- ... this happens from the forward_event() call payload1 value1: 20 payload1 value2: 200.2 Exited ~Running Entered Stopped Exiting Program Exited ~Stopped Exited ~Active ------ If I swap the //*** comments to the doProcess() lines instead of doPost(), the output seems more sensible ------ Entered Active Entered Stopped Stopped - payload2 m_charValue: a Exited ~Stopped Entered Running Active - payload2 m_charValue: a payload1 value1: 20 payload1 value2: 200.2 Exited ~Running Entered Stopped Exiting Program Exited ~Stopped Exited ~Active ------ ========== code =========== #include <boost/statechart/event.hpp> #include <boost/statechart/state_machine.hpp> #include <boost/statechart/state.hpp> #include <boost/statechart/transition.hpp> #include <boost/statechart/custom_reaction.hpp> #include <boost/mpl/list.hpp> #include <iostream> #include <string> namespace sc = boost::statechart; namespace mpl = boost::mpl; // Forward declare a handler 'class' that's going to have activity delegated to it struct Handler; // Normal example start/stop and reset events struct EvStartStop : sc::event<EvStartStop> {}; struct EvReset : sc::event<EvReset> {}; // A couple of events that carry data with them struct EvPayload1 : sc::event<EvPayload1> { EvPayload1(int value1, double value2) : m_value1 { value1 }, m_value2 { value2 } {}; int m_value1 { 0 }; double m_value2 { 0.0 }; }; struct EvPayload2 : sc::event<EvPayload2> { EvPayload2(const char& charValue) : m_charValue { charValue } {}; char m_charValue { false }; }; // Back to the example bits struct Active; struct StopWatch : sc::state_machine<StopWatch, Active> { typedef sc::transition<EvReset, Active> reactions; // The extra 'handler' stuff void setHandler(Handler* handler) { m_handler = handler; }; Handler* getHandler() const { return m_handler; }; Handler* m_handler { nullptr }; // Used to make post_event() accessible from an external class...seems unpleasant, but... using sc::state_machine<StopWatch, Active>::post_event; }; // The implementation of the Handler class; created with a reference to a StopWatch object struct Handler { Handler(StopWatch& stopWatch) : m_stopWatch { stopWatch } {}; void doPost() { m_stopWatch.post_event(EvStartStop()); } void doProcess() { m_stopWatch.process_event(EvStartStop()); } StopWatch& m_stopWatch; }; struct Stopped; struct Active : sc::state<Active, StopWatch, Stopped> { Active(my_context ctx) : my_base { ctx } { std::cout << "Entered Active" << std::endl; }; ~Active() { std::cout << "Exited ~Active" << std::endl; }; typedef mpl::list<sc::custom_reaction<EvPayload1>, sc::custom_reaction<EvPayload2>, sc::transition<EvReset, Active>> reactions; sc::result react(const EvPayload1& payload1) { std::cout << "payload1 Event NOT HANDLED" << std::endl; return discard_event(); } sc::result react(const EvPayload2& payload2) { std::cout << "Active - payload2 m_charValue: " << payload2.m_charValue << std::endl; return discard_event(); } }; struct Running : sc::state<Running, Active> { Running(my_context ctx) : my_base { ctx } { std::cout << "Entered Running" << std::endl; }; ~Running() { std::cout << "Exited ~Running" << std::endl; }; typedef mpl::list<sc::custom_reaction<EvPayload1>, sc::transition<EvStartStop, Stopped>> reactions; sc::result react(const EvPayload1& payload1) { std::cout << "payload1 value1: " << payload1.m_value1 << std::endl; std::cout << "payload1 value2: " << payload1.m_value2 << std::endl; //***context<StopWatch>().getHandler()->doPost(); context<StopWatch>().getHandler()->doProcess(); return discard_event(); } }; struct Stopped : sc::state<Stopped, Active> { Stopped(my_context ctx) : my_base { ctx } { std::cout << "Entered Stopped" << std::endl; }; ~Stopped() { std::cout << "Exited ~Stopped" << std::endl; }; typedef mpl::list<sc::custom_reaction<EvPayload2>, sc::transition<EvStartStop, Running>> reactions; sc::result react(const EvPayload2& payload2) { std::cout << "Stopped - payload2 m_charValue: " << payload2.m_charValue << std::endl; //***context<StopWatch>().getHandler()->doPost(); context<StopWatch>().getHandler()->doProcess(); return forward_event(); } }; int main() { StopWatch myWatch; Handler myHandler(myWatch); myWatch.setHandler(&myHandler); myWatch.initiate(); myWatch.process_event(EvPayload2 { 'a' }); myWatch.process_event(EvPayload1 { 20, 200.2 }); std::cout << "Exiting Program" << std::endl; return 0; }

Output using doProcess():
------ Entered Active Entered Stopped Stopped - payload2 m_charValue: a Exited ~Stopped <<<--- This is the bit I don't like
Sorry to top-post, but I was looking for some more information on this and came across this message I sent around 18 months ago. As there were no responses, I've retained the complete text here, but I realised that there's something wrong with some of the information I gave. While it would be great if someone could respond this time with some clarification (the documentation refers to state_machine functions not being re-entrant, but I've found no obvious statement of "never call process_event from within the context of a custom reaction"), the error is in the output when I use doPost() instead of doProcess(). I mentioned: the look of because...
Entered Running Active - payload2 m_charValue: a <<<--- ... this happens from the forward_event() call payload1 value1: 20 payload1 value2: 200.2 Exited ~Running Entered Stopped Exiting Program Exited ~Stopped Exited ~Active ------
If I swap the //*** comments to the doProcess() lines instead of doPost(), the output seems more sensible
------ Entered Active Entered Stopped Stopped - payload2 m_charValue: a Exited ~Stopped Entered Running Active - payload2 m_charValue: a payload1 value1: 20 payload1 value2: 200.2 Exited ~Running Entered Stopped Exiting Program Exited ~Stopped Exited ~Active ------
As you may have noticed, the second block of text, other than my annotations, is the same as the first! It should, actually, be:
Entered Active Entered Stopped Stopped - payload2 m_charValue: a Active - payload2 m_charValue: a Exited ~Stopped Entered Running payload1 value1: 20 payload1 value2: 200.2 Exited ~Running Entered Stopped Exiting Program Exited ~Stopped Exited ~Active
i.e the forwarded event is handled BEFORE the state we're currently in is destroyed. Anyway, thank you to anyone who can either confirm my concerns are valid and/or point me to somewhere this is explicitly defined in words of few syllables! John ------ Original Message ------ From: "John McCabe" <john@mccabe.org.uk> To: boost-users@lists.boost.org Sent: 06/07/2020 18:31:10 Subject: Clarification on statechart post_event vs process_event within react()
Hi,
Apologies if this has already been answered/clarified, but my search of the internet to try to get a definitive answer, at least one I recognised as being definitive, was unsuccessful.
Below I have posted some source code, based on the stopwatch example from the tutorial, but using an 'external' object to delegate the actual handling of event stuff to and using the state, rather than simple_state base class, and some other tweaks.
This is a cut down version of something that someone I work with has devised, but I just wanted to get a clear answer on whether process_event() should be getting called in cases like this. I think it probably should not since, in the code below, you change state (with a state object's destructor being executed) whilst you're executing that state's react(EvStartStop) function.
If someone can confirm this is the correct interpretation that would be very much appreciated.
Any questions, feel free to ask (BTW - I'm compiling for C++17)
John
PS - In case it's of any use, the output from running this code is:
------ Entered Active Entered Stopped Stopped - payload2 m_charValue: a Exited ~Stopped <<<--- This is the bit I don't like the look of because... Entered Running Active - payload2 m_charValue: a <<<--- ... this happens from the forward_event() call payload1 value1: 20 payload1 value2: 200.2 Exited ~Running Entered Stopped Exiting Program Exited ~Stopped Exited ~Active ------
If I swap the //*** comments to the doProcess() lines instead of doPost(), the output seems more sensible
------ Entered Active Entered Stopped Stopped - payload2 m_charValue: a Exited ~Stopped Entered Running Active - payload2 m_charValue: a payload1 value1: 20 payload1 value2: 200.2 Exited ~Running Entered Stopped Exiting Program Exited ~Stopped Exited ~Active ------
========== code =========== #include <boost/statechart/event.hpp> #include <boost/statechart/state_machine.hpp> #include <boost/statechart/state.hpp> #include <boost/statechart/transition.hpp> #include <boost/statechart/custom_reaction.hpp> #include <boost/mpl/list.hpp>
#include <iostream> #include <string>
namespace sc = boost::statechart; namespace mpl = boost::mpl;
// Forward declare a handler 'class' that's going to have activity delegated to it struct Handler;
// Normal example start/stop and reset events struct EvStartStop : sc::event<EvStartStop> {}; struct EvReset : sc::event<EvReset> {};
// A couple of events that carry data with them struct EvPayload1 : sc::event<EvPayload1> { EvPayload1(int value1, double value2) : m_value1 { value1 }, m_value2 { value2 } {}; int m_value1 { 0 }; double m_value2 { 0.0 }; }; struct EvPayload2 : sc::event<EvPayload2> { EvPayload2(const char& charValue) : m_charValue { charValue } {}; char m_charValue { false }; };
// Back to the example bits struct Active; struct StopWatch : sc::state_machine<StopWatch, Active> { typedef sc::transition<EvReset, Active> reactions;
// The extra 'handler' stuff void setHandler(Handler* handler) { m_handler = handler; }; Handler* getHandler() const { return m_handler; }; Handler* m_handler { nullptr };
// Used to make post_event() accessible from an external class...seems unpleasant, but... using sc::state_machine<StopWatch, Active>::post_event; };
// The implementation of the Handler class; created with a reference to a StopWatch object struct Handler { Handler(StopWatch& stopWatch) : m_stopWatch { stopWatch } {};
void doPost() { m_stopWatch.post_event(EvStartStop()); }
void doProcess() { m_stopWatch.process_event(EvStartStop()); }
StopWatch& m_stopWatch; };
struct Stopped; struct Active : sc::state<Active, StopWatch, Stopped> { Active(my_context ctx) : my_base { ctx } { std::cout << "Entered Active" << std::endl; }; ~Active() { std::cout << "Exited ~Active" << std::endl; };
typedef mpl::list<sc::custom_reaction<EvPayload1>, sc::custom_reaction<EvPayload2>, sc::transition<EvReset, Active>> reactions;
sc::result react(const EvPayload1& payload1) { std::cout << "payload1 Event NOT HANDLED" << std::endl; return discard_event(); }
sc::result react(const EvPayload2& payload2) { std::cout << "Active - payload2 m_charValue: " << payload2.m_charValue << std::endl; return discard_event(); } };
struct Running : sc::state<Running, Active> { Running(my_context ctx) : my_base { ctx } { std::cout << "Entered Running" << std::endl; }; ~Running() { std::cout << "Exited ~Running" << std::endl; };
typedef mpl::list<sc::custom_reaction<EvPayload1>, sc::transition<EvStartStop, Stopped>> reactions;
sc::result react(const EvPayload1& payload1) { std::cout << "payload1 value1: " << payload1.m_value1 << std::endl; std::cout << "payload1 value2: " << payload1.m_value2 << std::endl;
//***context<StopWatch>().getHandler()->doPost(); context<StopWatch>().getHandler()->doProcess();
return discard_event(); } };
struct Stopped : sc::state<Stopped, Active> { Stopped(my_context ctx) : my_base { ctx } { std::cout << "Entered Stopped" << std::endl; }; ~Stopped() { std::cout << "Exited ~Stopped" << std::endl; };
typedef mpl::list<sc::custom_reaction<EvPayload2>, sc::transition<EvStartStop, Running>> reactions;
sc::result react(const EvPayload2& payload2) { std::cout << "Stopped - payload2 m_charValue: " << payload2.m_charValue << std::endl;
//***context<StopWatch>().getHandler()->doPost(); context<StopWatch>().getHandler()->doProcess();
return forward_event(); } };
int main() { StopWatch myWatch; Handler myHandler(myWatch); myWatch.setHandler(&myHandler);
myWatch.initiate(); myWatch.process_event(EvPayload2 { 'a' }); myWatch.process_event(EvPayload1 { 20, 200.2 }); std::cout << "Exiting Program" << std::endl; return 0; }
participants (1)
-
John McCabe