Skip to content

Latest commit

 

History

History
 
 

Folders and files

NameName
Last commit message
Last commit date

parent directory

..
 
 

Readme.md

Chapter - 5 : Deferred Events

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

5.1 Using sc::deferral

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

5.2 Deferring Multiple events

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.

5.3 : Life of Deferred Events

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

5.3.1 : Adding a thirdState

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

5.4 : Conclusion

In this chapter, we learnt how to defer the events to the next state and how the sequence of events changes its behaviour.