In the state machine code till now, we saw that the events have to be handled by the state which is receiving the event. In cases where the state is not listening to the event, the event(s) will simply gets lost.
Unfortunately, in many scenarios its extremely unlikely to have a synchronous event capture mechanism. In those situations, if we aren't able to handle the event in the current state, but may probably do it in another state, then the event should be preserved not discared.
This is achieved by using sc::deferral which deferred the handling of event from the current state, but the event remain alive and can be handled in the next state.
However, it's extremely tricky to use deferring events. The behavior changes based on the way the events are raised or handled
To use sc::deferral, we need to import one special header file of boost::statechart libary called deferral.hpp
#include <boost/statechart/deferral.hpp>
sc::derferral can be used as a part of mpl::list along with sc::trasnsition and sc::custom_reactions.
Basically with sc::deferral, the State Machine keeps the NOT HANDLED events in a separate queue. Events in that queue are triggered every time a state is transitioned from the current state to any other state
sc::deferral takes the name of the event which needs to be deferred or be kept in a separate queue. However, we need to watch the queue carefully to understand the behaviour
In the code below, we'll raise an event_ event_OutOfBlueEvent which is deferred but will be automatically handled in 2nd state if a handler is written for that in 2nd state. Let's watch the event queue carefully
// States
struct firstState;
struct secondState;
// Events
struct event_OutOfBlueEvent : sc::event<event_OutOfBlueEvent> {};
struct event_MoveToSecond : sc::event<event_MoveToSecond> {};
struct statemachine : sc::state_machine<statemachine, firstState>{};
struct firstState : sc::simple_state<firstState, statemachine> {
firstState() { cout << "In State => firstState" << endl; }
typedef mpl::list<
sc::deferral<event_OutOfBlueEvent>,
sc::transition<event_MoveToSecond, secondState>
>reactions;
};
struct secondState : sc::simple_state<secondState, statemachine> {
secondState() { cout << "In State => secondState" << endl; }
typedef sc::custom_reaction<event_OutOfBlueEvent> reactions;
sc::result react(const event_OutOfBlueEvent & event) {
cout << "event_OutOfBlueEvent Tiggered in => secondState" << endl;
return discard_event();
}
};
int main() {
statemachine sm;
sm.initiate();
// will do nothing
sm.process_event(event_OutOfBlueEvent());
// will change state and trigger event_OutOfBlueEvent in second state
sm.process_event(event_MoveToSecond());
return 0;
}
Let's visualize the event queue of the above code DISCLAIMER NOTE : The depiction below is not the actual implementation. Its only for illustration purpose
sm.process_event(event_MoveToSecond());
_______________________________________________
| Event Queue |
-----------------------------------------------
| event_OutOfBlueEvent |
-----------------------------------------------
Since in firstState, the event event_OutOfBlueEvent is deferred, it will remain in the event queue when firstState exits and the state machine moves to secondState.
The secondState picks up the event from the event queue and execute the event
It's possible to defer multiple events using multiple sc::deferral with appropriate event names. Let's create a new event called event_OutOfGreenEvent along with existing 2 events
struct event_OutOfBlueEvent : sc::event<event_OutOfBlueEvent> {};
struct event_OutOfGreenEvent : sc::event<event_OutOfGreenEvent> {};
struct event_MoveToSecond : sc::event<event_MoveToSecond> {};
Now update the firstState for deferring outofBlue as well as outofGreen events
struct firstState : sc::simple_state<firstState, statemachine> {
firstState() { cout << "In State => firstState" << endl; }
typedef mpl::list<
sc::deferral<event_OutOfBlueEvent>,
sc::deferral<event_OutOfGreenEvent>,
sc::transition<event_MoveToSecond, secondState>
>reactions;
};
Now lets capture both the events in secondState
struct secondState : sc::simple_state<secondState, statemachine> {
secondState() { cout << "In State => secondState" << endl; }
typedef mpl::list<
sc::custom_reaction<event_OutOfGreenEvent>,
sc::custom_reaction<event_OutOfBlueEvent>
> reactions;
sc::result react(const event_OutOfBlueEvent & event) {
cout << "event_OutOfBlueEvent Tiggered in => secondState" << endl;
return discard_event();
}
sc::result react(const event_OutOfGreenEvent & event) {
cout << "event_OutOfGreenEvent Tiggered in => secondState" << endl;
return discard_event();
}
};
Let's trigger those events from the main function
int main() {
statemachine sm;
sm.initiate();
// will do nothing
sm.process_event(event_OutOfBlueEvent());
sm.process_event(event_OutOfGreenEvent());
// will change state and trigger event_OutOfBlueEvent and event_OutOfGreenEvent in second state
sm.process_event(event_MoveToSecond());
return 0;
}
The event queue at the end of state one will have two events as they were deferred
_______________________________________________
| Event Queue |
-----------------------------------------------
| event_OutOfBlueEvent |
-----------------------------------------------
| event_OutOfGreenEvent |
-----------------------------------------------
Both the events will be handled in secondState.
It will be interesting to note that the sequence of the events is maintained, i.e they will be triggered in sequence. For example the output of the main function above has event_OutOfBlueEvent triggered before event_OutOfGreenEvent. If we change the sequence, then the events will be handled appropriately where event_OutOfGreenEvent will be triggered before event_OutOfBlueEvent.
Let consider a statement The deferred events remain the external queue of the state machine till its handled by one of the state event handler or overwritten by new events.
The statement sounds complicated, but will make more sense with upcoming examples
In the example above, if we handle only event event_OutOfBlueEvent in secondState then what will happen to the event event_OutOfGreenEvent which was deferred in firstState. Furthermore, if we move to a thirdState as a result of event event_OutOfBlueEvent, then the deferred event will persist for thirdState
Lets add a thirdState and a handler for event event_OutOfGreenEvent
The event queue at the end of firstState will consist of two deferred events
_______________________________________________
| Event Queue |
-----------------------------------------------
| event_OutOfBlueEvent |
-----------------------------------------------
| event_OutOfGreenEvent |
-----------------------------------------------
The secondState will handle event event_OutOfBlueEvent, since we're transiting to thirdState, the event queue will contain unhandled event` which is deferred from first state
_______________________________________________
| Event Queue |
-----------------------------------------------
| event_OutOfGreenEvent |
-----------------------------------------------
At thirdState, the event event_OutOfGreenEvent is handled as it was maintained in the deferred queue.
Unfortunately, things get changed if we change the sequence of posting events. If we post event_OutOfGreenEvent before event_OutOfBlueEvent then event_OutOfGreenEvent remains unhandled
Let's see how event queues plays a role in it. First, we'll amend the sequence of posting events
sm.process_event(event_OutOfGreenEvent());
sm.process_event(event_OutOfBlueEvent());
Since both events are deferred, the event queue at the end of firstState will look like
_______________________________________________
| Event Queue |
-----------------------------------------------
| event_OutOfGreenEvent |
-----------------------------------------------
| event_OutOfBlueEvent |
-----------------------------------------------
At secondState, the event event_OutOfGreenEvent is popped out from the Event Queue, Since there is no handler of this event in secondState and the event is not marked as deferred event, it will be lost. This will be followed by popping off event event_OutOfBlueEvent which will be handled in secondState
When we get into this state, the event queue is empty, so it will not execute the event handler for event_OutOfGreenEvent.
To make sure that the event event_OutOfGreenEvent is available in thirdState we again need to defer the event in secondState as coded below
struct secondState : sc::simple_state<secondState, statemachine> {
secondState() { cout << "In State => secondState" << endl; }
typedef mpl::list <
sc::custom_reaction<event_OutOfBlueEvent>,
sc::deferral<event_OutOfGreenEvent>
> reactions;
sc::result react(const event_OutOfBlueEvent & event) {
cout << "event_OutOfBlueEvent Tiggered in => secondState" << endl;
return transit<thirdState>();
}
};
This code will allow the event queue to again have event_OutOfGreenEvent at the end of second state and can be handled in third state
In this chapter, we learnt how to defer the events to the next state and how the sequence of events changes its behaviour.