Boost logo

Boost Users :

From: John McCabe (john_at_[hidden])
Date: 2022-01-10 18:27:57


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:

> Output using doProcess():
>
>------
>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
>------
>

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_at_[hidden]>
To: boost-users_at_[hidden]
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;
>}


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