Events with parameters example

This project demonstrates how to use the events with parameters in two different ways.

For this example, we use the following simple state machine:

images/download/attachments/5963778/events_with_parameters.png

We also consider:

  • The transition from the initial pseudostate to 'simple_state_1' is (of course) a completion transition.

  • event_1 is an event with 1 parameter.

  • event_2 is an event with 2 parameters

  • Both states have entry, do and exit behaviors.

  • All transitions have a do behavior.

Creating and firing events

Creation and firing of events is the same for all examples in this project.

The functionality to create event classes is made available via the events.hpp header. For creating new event classes, we will use two different macros: Y_EVENT_1PARAM_WITH_ID for a event with just one parameter and Y_EVENT_2PARAM_WITH_ID for the event with two parameters.

A list of available macros for creating event classed can be found on Creating event classes page.

The event classes will automatically provide a getter for each parameter according to its parameter type.

constexpr sxy::event_id EVENT_1 = 1;
constexpr sxy::event_id EVENT_2 = 2;
 
namespace examples
{
Y_EVENT_CREATE( event_1, EVENT_1, std::string, get_param )
Y_EVENT_CREATE( event_2, EVENT_2, int, get_param_1, double, get_param_2 )
}

After the state machine is created and started, two events will be fired. The first event will have a string type parameter and the second one will transport an int and a double as parameters.

state_machine->fire_event( event_1::create( "parameter 1 test" ) );
state_machine->fire_event( event_2::create( 2, 3.4 ) );

Strongly typed events (C++11 only)

For the strongly typed events scenario, there are two variations of the example: one using free functions and one using class methods.

Free functions

First, we build the state machine. The special part is the creation of the simple states and transitions. The code looks like this:

sxy::simple_state& simple_state_1 = main_region.add_simple_state( "1",
Y_BEHAVIOR_FUNCTION2( &do_something_event_0_parameters, &do_something_event_2_parameters ),
Y_BEHAVIOR_FUNCTION2( &do_something_event_0_parameters, &do_something_event_2_parameters ),
Y_BEHAVIOR_FUNCTION2( &do_something_event_1_parameter ) );
sxy::simple_state& simple_state_2 = main_region.add_simple_state( "2",
Y_BEHAVIOR_FUNCTION2( &do_something_event_1_parameter ),
Y_BEHAVIOR_FUNCTION2( &do_something_event_1_parameter ),
Y_BEHAVIOR_FUNCTION2( &do_something_event_2_parameters ) );
state_machine->add_transition( sxy::Y_COMPLETION_EVENT_ID, initial_pseudostate, simple_state_1,
Y_BEHAVIOR_FUNCTION2( &do_something_event_0_parameters ) );
state_machine->add_transition( EVENT_1, simple_state_1, simple_state_2,
Y_BEHAVIOR_FUNCTION2( &do_something_event_1_parameter ) );
state_machine->add_transition( EVENT_2, simple_state_2, simple_state_1,
Y_BEHAVIOR_FUNCTION2( &do_something_event_2_parameters ) );

Each simple state will have all types of behaviors used by passing them handlers using the macro Y_BEHAVIOR_FUNCTION2. This macro is variadic and takes up to 10 function pointers as parameters. The functions that are passed via pointer have to take all different types of events that can reach the state or transition.
For example, do_something_event_2_parameters, do_something_event_1_parameter and do_something_event_0_parameters will look like this:

void do_something_event_2_parameters( const examples::event_2& _event, sxy::event_collector& _event_collector )
{
SX_UNUSED_PARAMETER( _event_collector );
std::cout << "Parameters of event " << _event.get_name() << " are:\n" << _event.get_param_1() << "\n" <<
_event.get_param_2() << std::endl;
}
 
void do_something_event_1_parameter( const examples::event_1& _event )
{
std::cout << "Parameter of event " << _event.get_name() << " is: " << _event.get_param() << std::endl;
}
 
void do_something_event_0_parameters( const sxy::completion_event& _event, sxy::event_collector& _event_collector )
{
SX_UNUSED_PARAMETER( _event );
SX_UNUSED_PARAMETER( _event_collector );
std::cout << "Completion event has no parameters." << std::endl;
}

Each of them will take a different type of event. On occurrence of an event the appropriate method is going to be called.

Class methods

Using class methods as behaviors for simple states and transitions (in this example - in behaviors in general) works pretty much the same like using free functions:

void class_method_downcast::do_something_event_2_parameters( const event_2& _event, sxy::event_collector& _event_collector )
{
SX_UNUSED_PARAMETER( _event_collector );
std::cout << "Parameters of event " << _event.get_name() << " are:\n" << _event.get_param_1() << "\n" <<
_event.get_param_2() << std::endl;
}
 
void class_method_downcast::do_something_event_1_parameter( const event_1& _event, sxy::event_collector& _event_collector )
{
SX_UNUSED_PARAMETER( _event_collector );
std::cout << "Parameter of event " << _event.get_name() << " is: " << _event.get_param() << std::endl;
}
 
void class_method_downcast::do_something_event_0_parameters( const sxy::completion_event& _event, sxy::event_collector& _event_collector )
{
SX_UNUSED_PARAMETER( _event );
SX_UNUSED_PARAMETER( _event_collector );
std::cout << "Completion event has no parameters." << std::endl;
}

Also, there is no big difference when building the state machine:

sxy::simple_state& simple_state_1 = main_region.add_simple_state( "1",
Y_BEHAVIOR_METHOD2( this, &class_method_downcast::do_something_event_2_parameters,
&class_method_downcast::do_something_event_0_parameters ),
Y_BEHAVIOR_METHOD2( this, &class_method_downcast::do_something_event_2_parameters,
&class_method_downcast::do_something_event_0_parameters),
Y_BEHAVIOR_METHOD2( this, &class_method_downcast::do_something_event_1_parameter ) );
sxy::simple_state& simple_state_2 = main_region.add_simple_state( "2",
Y_BEHAVIOR_METHOD2( this, &class_method_downcast::do_something_event_1_parameter ),
Y_BEHAVIOR_METHOD2( this, &class_method_downcast::do_something_event_1_parameter ),
Y_BEHAVIOR_METHOD2( this, &class_method_downcast::do_something_event_2_parameters ) );
state_machine->add_transition( sxy::Y_COMPLETION_EVENT_ID, initial_pseudostate, simple_state_1,
Y_BEHAVIOR_METHOD2( this, &class_method_downcast::do_something_event_0_parameters ) );
state_machine->add_transition( EVENT_1, simple_state_1, simple_state_2,
Y_BEHAVIOR_METHOD2( this, &class_method_downcast::do_something_event_1_parameter ) );
state_machine->add_transition( EVENT_2, simple_state_2, simple_state_1,
Y_BEHAVIOR_METHOD2( this, &class_method_downcast::do_something_event_2_parameters ) );

The most important difference is the use of the macro Y_BEHAVIOR_METHOD2 instead of Y_BEHAVIOR_FUNCTION2.

Manual cast

For this scenario the state machine is set up like this:

sxy::simple_state& simple_state_1 = main_region.add_simple_state( "1",
Y_BEHAVIOR_FUNCTION2( do_something_event_2_parameters ),
Y_BEHAVIOR_FUNCTION2( do_something_event_2_parameters ),
Y_BEHAVIOR_FUNCTION2( do_something_event_1_parameter ) );
sxy::simple_state& simple_state_2 = main_region.add_simple_state( "2",
Y_BEHAVIOR_FUNCTION2( do_something_event_1_parameter ),
Y_BEHAVIOR_FUNCTION2( do_something_event_1_parameter ),
Y_BEHAVIOR_FUNCTION2( do_something_event_2_parameters ) );
state_machine->add_transition( sxy::Y_COMPLETION_EVENT_ID, initial_pseudostate, simple_state_1,
Y_BEHAVIOR_FUNCTION2( do_something_event_0_parameters ) );
state_machine->add_transition( EVENT_1, simple_state_1, simple_state_2,
Y_BEHAVIOR_FUNCTION2( do_something_event_1_parameter ) );
state_machine->add_transition( EVENT_2, simple_state_2, simple_state_1,
Y_BEHAVIOR_FUNCTION2( do_something_event_2_parameters ) );

By using the macro Y_BEHAVIOR_FUNCTION2 we add the specified function as a behavior of the simple states and of the transitions. The macro can be used for any behavior (entry, do or exit).

The event is processed and the behaviors will be executed as well. Inside a function that serves as the implementation of a behavior we cast the event to the actual event type. The parameters are extracted using the getters that were set on creation of event object.

void do_something_event_2_parameters( const sxy::event& _event )
{
const sxy::event_2* event_with_param = dynamic_cast< const examples::event_2* >( &_event );
if( event_with_param )
{
std::cout << "Parameters of event " << event_with_param->get_name() << " are:\n" <<
event_with_param->get_param_1() << "\n" << event_with_param->get_param_2() << std::endl;
}
else
{
std::cout << "No event parameter.\n";
}
}

Source code

The source code can be found on GitHub.