SO 5.8 ByExample Subscriptions - Stiffstream/sobjectizer GitHub Wiki
Note. It is better to read SO-5.8 Basics and SO-5.8 By Example Change State before this text.
This example demonstrates event subscriptions and handling of messages in different agent's states. This example uses some features introduced in v.5.5.1 for reducing amount of SObjectizer-related code which user has to write.
There is one agent in the sample. It uses three states: the default agent's state (provided by SObjectizer for every agent) and two user-defined states -- st_first
, st_second
. The agent uses message change_state_message
for switching between those states.
When agent switches to st_first
and st_second
states it sends two messages to itself: my_message
and my_another_message
. In the st_first
state the agent handles both of them. But in the st_second
state the agent handles only one of them, my_message
. The my_another_message
is ignored in the state st_second
.
Sample output shows that agents starts, makes its subscriptions, switches to st_first
state and sends a couple of messages to itself, handles both of them in st_first
state (and shows its contents), switches to st_second
state and again sends a couple of message to itself, but handles only one of them.
#include <iostream>
#include <string>
// Main SObjectizer header file.
#include <so_5/all.hpp>
// State sequence for sample agent.
enum sample_state_t
{
DEFAULT_STATE,
FIRST_STATE,
SECOND_STATE,
};
// Sample message.
struct my_message
{
// Some data.
int m_x;
};
// Another sample message.
struct my_another_message
{
// Some data.
std::string m_s;
};
// Sample message for the subscription demonstrtion.
class my_agent_t final : public so_5::agent_t
{
public:
my_agent_t( context_t ctx );
// Definition of an agent for SObjectizer.
void so_define_agent() override;
// A reaction to start of work in SObjectizer.
void so_evt_start() override;
// Handle change state.
void change_state_event_handler( sample_state_t next_state );
// Handle my_message.
void my_event_handler( const my_message & message );
// Handle my_another_message.
void my_another_event_handler( const my_another_message & message );
private:
// Agent states.
const state_t st_first{ this, "first" };
const state_t st_second{ this, "second" };
};
my_agent_t::my_agent_t( context_t ctx )
: so_5::agent_t{ std::move(ctx) }
{}
void my_agent_t::so_define_agent()
{
std::cout << "so_define_agent()" << std::endl;
st_first.event( &my_agent_t::change_state_event_handler );
st_second.event( &my_agent_t::change_state_event_handler );
so_default_state().event( &my_agent_t::change_state_event_handler );
std::cout << "\tsubscribe my_event_handler in "
<< st_first.query_name() << std::endl;
st_first.event( &my_agent_t::my_event_handler );
std::cout << "\tsubscribe my_another_event_handler in "
<< st_first.query_name() << std::endl;
st_first.event( &my_agent_t::my_another_event_handler );
std::cout << "\tsubscribe my_event_handler in "
<< st_second.query_name() << std::endl;
st_second.event( &my_agent_t::my_event_handler );
}
void my_agent_t::so_evt_start()
{
std::cout << "so_evt_start()" << std::endl;
std::cout << "\tsend the first sample_state_t for state changes" << std::endl;
// Switch to first state and handle messages.
so_5::send< sample_state_t >( *this, FIRST_STATE );
}
void my_agent_t::change_state_event_handler( sample_state_t next_state )
{
std::cout << "change_state_event_handler()" << std::endl;
if( DEFAULT_STATE == next_state )
{
this >>= so_default_state();
std::cout << "\tswitched to default state and shutdown..." << std::endl;
so_environment().stop();
}
else
{
if( FIRST_STATE == next_state )
{
this >>= st_first;
std::cout << "\tswitched to " << so_current_state().query_name()
<< std::endl;
// Send serie of messages...
so_5::send< my_message >( *this, 42 );
so_5::send< my_another_message >( *this, "SObjectizer" );
std::cout << "\tmessages sent" << std::endl;
// Switch to second.
so_5::send< sample_state_t >( *this, SECOND_STATE );
}
else if( SECOND_STATE == next_state )
{
this >>= st_second;
std::cout << "\tswitched to " << so_current_state().query_name()
<< std::endl;
// Send serie of messages...
so_5::send< my_message >( *this, -42 );
// Message should not be received.
so_5::send< my_another_message >( *this, "rezitcejbOS" );
std::cout << "\tmessages sent" << std::endl;
// Switch to default.
so_5::send< sample_state_t >( *this, DEFAULT_STATE );
}
}
}
void my_agent_t::my_event_handler( const my_message & message )
{
std::cout << "my_event_handler()" << std::endl;
std::cout
<< "\tcurrent state is " << so_current_state().query_name() << std::endl
<< "\tmessage.x = " << message.m_x << std::endl;
}
void my_agent_t::my_another_event_handler( const my_another_message & message )
{
std::cout << "my_another_event_handler()" << std::endl;
std::cout
<< "\tcurrent state is " << so_current_state().query_name() << std::endl
<< "\tmessage.s = " << message.m_s << std::endl;
}
int main()
{
try
{
so_5::launch(
[]( so_5::environment_t & env )
{
env.register_agent_as_coop( env.make_agent< my_agent_t >() );
} );
}
catch( const std::exception & ex )
{
std::cerr << "Error: " << ex.what() << std::endl;
return 1;
}
return 0;
}
so_define_agent()
subscribe my_event_handler in first
subscribe my_another_event_handler in first
subscribe my_event_handler in second
so_evt_start()
send the first sample_state_t for state changes
change_state_event_handler()
switched to first
messages sent
my_event_handler()
current state is first
message.x = 42
my_another_event_handler()
current state is first
message.s = SObjectizer
change_state_event_handler()
switched to second
messages sent
my_event_handler()
current state is second
message.x = -42
change_state_event_handler()
switched to default state and shutdown...
The main amount of the sample's code is a simple routine SObjectizer-related stuff. If you don't understand it please read SO-5.8 Basics, SO-5.8 By Example Change State and SO-5.8 By Example Periodic Hello.
The new approach for event subscriptions in shown in my_agent_t::so_define_agent()
method. It was introduced in v.5.5.1. It allows to subscribe agent's events via agent's states.
The traditional approach requires so_subscribe().in().event()
method chain. But in the simple cases it is requires to write a little bit more code than desired. So instead of writing:
so_subscribe( so_direct_mbox() ).in( st_first ).event( handler );
// Or
so_subscribe_self().in( st_first ).event( handler );
It is possible to write just:
st_first.event( handler );
The new approach is used in this sample. This sample also shows how to use new approach with default agent state:
so_default_state().event( &my_agent_t::change_state_event_handler );
There are some forms of state_t::event
methods. The first one shown about subscribes agent to the messages sent to agent's direct mbox. It means that:
st_first.event( handler )
does exactly the same as:
so_subscribe( so_direct_mbox ).in( st_first ).event( handler );
The second form of state_t::event
method allows to specify a mbox for subscription:
st_first.event( source_mbox, handler );
This line does exactly the same thing as:
so_subscribe( source_mbox ).in( st_first ).event( handler );
The traditional way of changing agent's state is by using agent_t::so_change_state()
method. This method is still actual but since v.5.5.1 there are two more ways for changing agent's state.
The first one is state_t::activate()
method. The second one is operator>>=()
.
So there are three ways of changing agent's state and all of them do the same thing. Because of that the following examples are equivalent:
so_change_state( st_first ); // Good old one.
st_first.activate(); // Another way. For OOP fanboys :)
this >>= st_first; // And yet another way. For the most concise and cryptic code :)