Sub state machine with variables example

This project demonstrates how the sub machine concept can be implemented and how variables that are visible to more than one state can be used in a state machine.

We build a state machine that has two identically structured sub machines:

images/download/attachments/5964072/substatemachine_with_variables_diagram.png

A sub machine will look like this:

images/download/attachments/5964072/submachine_diagram.png

The fired events are shown in the table below (in the order they are fired in):

Event

Parameter

EVENT_1

 

EVENT_5

"Hello!"

EVENT_5

"yasmine"

EVENT_2

 

EVENT_3

 

EVENT_5

"abc"

EVENT_5

"xyz"

EVENT_5

"123"

EVENT_4

 

Sub machine

We separated the sub machine (or sub state machine) into its own class. The class is named submachine. It has two members: an integer (i_) and a string (s_). Three private member functions will serve as the behaviors of the states.

The constructor takes a reference to the parent state machine for adding its transitions. It also takes a reference to a region, which is supposed to be the parent of the sub state machine. In the constructor the sub state machine is created and added to the parent region (represented by a composite state).

submachine( sxy::sync_state_machine& _parent_state_machine, sxy::region& _parent_region )
: submachine_( _parent_region.add_composite_state( "submachine", Y_BEHAVIOR_METHOD2( this, &reset_members ) ) ),
i_(),
s_()
{
region& submachine_region = submachine_.add_region( "submachine region" );
initial_pseudostate& submachine_initial_pseudostate = submachine_region.add_initial_pseudostate(
"submachine_initial_pseudostate" );
simple_state& submachine_simple_state_1 = submachine_region.add_simple_state( "submachine simple_state_1",
Y_BEHAVIOR_METHOD2( this, &print_members ) );
simple_state& submachine_simple_state_2 = submachine_region.add_simple_state( "submachine simple_state_2",
Y_BEHAVIOR_METHOD2( this, &submachine::change_members ) );
_parent_state_machine.add_transition( sxy::Y_COMPLETION_EVENT_ID, submachine_initial_pseudostate, submachine_simple_state_1 );
_parent_state_machine.add_transition( EVENT_5, submachine_simple_state_1, submachine_simple_state_2 );
_parent_state_machine.add_transition( sxy::Y_COMPLETION_EVENT_ID, submachine_simple_state_2, submachine_simple_state_1 );
}

State machine

The state machine is encapsulated in a class that owns the state machine itself as well as the two sub state machines.

Inside the create method, the state machine is created using submachine twice.

void state_machine_with_submachines::create()
{
sxy::composite_state& root_state = state_machine_->get_root_state();
sxy::region& main_region = root_state.add_region( "main region" );
sxy::initial_pseudostate& initial_pseudostate = main_region.add_initial_pseudostate( "initial pseudostate" );
sxy::simple_state& simple_state_1 = main_region.add_simple_state( "simple_state_1" );
 
submachine1_ = Y_MAKE_UNIQUE<submachine>( *state_machine_, main_region );
submachine2_ = Y_MAKE_UNIQUE<submachine>( *state_machine_, main_region );
 
sxy::final_state& final_state = main_region.add_final_state( "final state" );
 
 
state_machine_->add_transition( sxy::Y_COMPLETION_EVENT_ID, initial_pseudostate, simple_state_1 );
state_machine_->add_transition( EVENT_1, simple_state_1, submachine1_->get_submachine_root_state() );
state_machine_->add_transition( EVENT_2, submachine1_->get_submachine_root_state(), simple_state_1 );
state_machine_->add_transition( EVENT_3, simple_state_1, submachine2_->get_submachine_root_state() );
state_machine_->add_transition( EVENT_4, submachine2_->get_submachine_root_state(), final_state );
}

In order to access the composite state that represents the sub state machines, we use the public method get_submachine_root_state. Through the reference we can add transitions that target or emanate from the sub machine.

To make it easier to connect transitions to inner states of the sub state machine, you can use entry or exit points. Just connect these entry or exit points to the states that you want to connect to in your sub state machine. Then you can just offer access to these entry and/or exit points through the public interface of your sub state machine. Alternatively you can also directly expose the sub states of course. This might harm encapsulation though.

Events

We create the event classes in events.hpp using the macro Y_EVENT_CREATE.

Y_EVENT_CREATE( event_4, EVENT_4 )
Y_EVENT_CREATE( event_5, EVENT_5, std::string, get_param )

The start of the state machine and the firing of events is implemented in the run method. We fire the events as usual:

state_machine_->run();
state_machine_->fire_event( event_1::create() );
state_machine_->fire_event( event_5::create( "Hello!" ) );
state_machine_->fire_event( event_5::create( "yasmine" ) );
state_machine_->fire_event( event_2::create() );
state_machine_->fire_event( event_3::create() );
state_machine_->fire_event( event_5::create( "abc" ) );
state_machine_->fire_event( event_5::create( "xyz" ) );
state_machine_->fire_event( event_5::create( "123" ) );
state_machine_->fire_event( event_4::create() );
state_machine_->halt();

The output looks like this:

0
1 Hello!
2 yasmine
0
1 abc
2 xyz
3 123

Source code

The source code can be found on GitHub.