Skip to content

SO 5.8 ByExample Change State

Yauheni Akhotnikau edited this page Jul 7, 2023 · 2 revisions

Note. It is better to read SO-5.8 Basics and SO-5.8 By Example Periodic Hello before this text.

Introduction

This sample demonstrates agent's states and state-dependent event handling.

There is just one agent in the sample. This agent has several state. The agent handles the same message in every state but uses different event handling methods. The agent initiates periodic signal msg_periodic and switches from state to state in every event handler. When agent receives msg_periodic in st_shutdown state it finishes SObjectizer Environment.

There is also demonstration of another SObjectizer facility: listener of agent's states. State listener is a object to be connected with an agent. Method of this object is called by SObjectizer when agent changes its state. For listener demonstration state_monitor_t class defined and two instances of that class are bound to the sample agent.

Sample Code

#include <iostream>
#include <time.h>

// Main SObjectizer header file.
#include <so_5/all.hpp>

// Periodic message.
class msg_periodic final : public so_5::signal_t {};

// State listener for fixing state changes.
class state_monitor_t final : public so_5::agent_state_listener_t
{
	const std::string m_type_hint;

	public:
		state_monitor_t( const std::string & type_hint )
			:	m_type_hint( type_hint )
		{}

		void changed(
			so_5::agent_t &,
			const so_5::state_t & state ) noexcept override
		{
			std::cout << m_type_hint << " agent changed state to "
				<< state.query_name()
				<< std::endl;
		}
};

// A sample agent class.
class a_state_swither_t final : public so_5::agent_t
{
		// Agent states.
		const state_t st_1{ this, "state_1" };
		const state_t st_2{ this, "state_2" };
		const state_t st_3{ this, "state_3" };
		const state_t st_shutdown{ this, "shutdown" };

	public:
		a_state_swither_t( context_t ctx )
			:	so_5::agent_t{ ctx }
		{}

		// Definition of the agent for SObjectizer.
		void so_define_agent() override;

		// Reaction to start into SObjectizer.
		void so_evt_start() override;

		// Message handler for the default state.
		void evt_handler_default( mhood_t<msg_periodic> );

		// Message handler for the state_1.
		void evt_handler_1( mhood_t<msg_periodic> );

		// Message handler for the state_2.
		void evt_handler_2( mhood_t<msg_periodic> );

		// Message handler for the state_3.
		void evt_handler_3( mhood_t<msg_periodic> );

		// Message handler for the shutdown_state.
		void evt_handler_shutdown( mhood_t<msg_periodic> );

	private:
		// Timer event id.
		// If we do not store it the periodic message will
		// be canceled automatically.
		so_5::timer_id_t m_timer_id;

		// Helper method for showing that event handler is called.
		void show_event_invocation( const char * event_name );
};

void a_state_swither_t::so_define_agent()
{
	// Message subsription.
	so_subscribe_self()
		.event( &a_state_swither_t::evt_handler_default );

	so_subscribe_self().in( st_1 )
		.event( &a_state_swither_t::evt_handler_1 );

	so_subscribe_self().in( st_2 )
		.event( &a_state_swither_t::evt_handler_2 );

	so_subscribe_self().in( st_3 )
		.event( &a_state_swither_t::evt_handler_3 );

	so_subscribe_self().in( st_shutdown )
		.event( &a_state_swither_t::evt_handler_shutdown );
}

void a_state_swither_t::so_evt_start()
{
	show_event_invocation( "so_evt_start()" );

	// Periodic message should be initiated.
	m_timer_id = so_5::send_periodic< msg_periodic >(
			*this,
			std::chrono::seconds( 1 ),
			std::chrono::seconds( 1 ) );
}

void a_state_swither_t::evt_handler_default( mhood_t<msg_periodic> )
{
	show_event_invocation( "evt_handler_default" );

	// Switching to the next state.
	so_change_state( st_1 );
}

void a_state_swither_t::evt_handler_1( mhood_t<msg_periodic> )
{
	show_event_invocation( "evt_handler_1" );

	// Switching to the next state.
	so_change_state( st_2 );
}

void a_state_swither_t::evt_handler_2( mhood_t<msg_periodic> )
{
	show_event_invocation( "evt_handler_2" );

	// Switching to the next state.
	so_change_state( st_3 );
}

void a_state_swither_t::evt_handler_3( mhood_t<msg_periodic> )
{
	show_event_invocation( "evt_handler_3" );

	// Switching to the next state.
	so_change_state( st_shutdown );
}

void a_state_swither_t::evt_handler_shutdown( mhood_t<msg_periodic> )
{
	show_event_invocation( "evt_handler_3" );

	// Switching to the default state.
	so_change_state( so_default_state() );

	// Finishing SObjectizer's work.
	std::cout << "Stop sobjectizer..." << std::endl;
	so_environment().stop();
}

void a_state_swither_t::show_event_invocation( const char * event_name )
{
	time_t t = time( nullptr );
	std::cout << asctime( localtime( &t ) )
		<< event_name << ", state: " << so_current_state().query_name()
		<< std::endl;
}

// A state listener.
state_monitor_t g_state_monitor( "nondestroyable_listener" );

// The SObjectizer Environment initialization.
void init( so_5::environment_t & env )
{
	auto ag = env.make_agent< a_state_swither_t >();

	// Adding the state listener. Its lifetime is not controlled by the agent.
	ag->so_add_nondestroyable_listener( g_state_monitor );

	// Adding another state listener.
	// Its lifetime is controlled by the agent.
	ag->so_add_destroyable_listener(
		std::make_unique< state_monitor_t >( "destroyable_listener" ) );

	// Creating and registering a cooperation.
	env.register_agent_as_coop( std::move(ag) );
}

int main()
{
	try
	{
		so_5::launch( &init );
	}
	catch( const std::exception & ex )
	{
		std::cerr << "Error: " << ex.what() << std::endl;
		return 1;
	}

	return 0;
}

Sample in Details

Agent's States

Declaring Agent's States

Each state must be represented by a separate object of so_5::state_t type. Usually those objects are attributes of agent's class. The simplest way of declaring and initializing agent's state is shown in the sample:

class a_state_swither_t : public so_5::agent_t
{
		// Agent states.
		const state_t st_1{ this, "state_1" };
		const state_t st_2{ this, "state_2" };
		const state_t st_3{ this, "state_3" };
		const state_t st_shutdown{ this, "shutdown" };

Note: a name agent_t::state_t is just an alias for so_5::state_t.

But there is another, more verbose way: declaring attribute and then initializing it in the agent's constructor. Like this:

class a_state_swither_t : public so_5::agent_t
{
        // Agent states.
        const state_t st_1;
        const state_t st_2;
        const state_t st_3;
        const state_t st_shutdown;

    public :
        a_state_switcher_t( context_t ctx )
            :    so_5::agent_t( env )
            ,    st_1( self_ptr(), "state_1" )
            ,    st_2( self_ptr(), "state_2" )
            ,    st_3( self_ptr(), "state_3" )
            ,    st_shutdown( self_ptr(), "shutdown" )
        {}

Subscribing Event in the Specified State

Short form of so_subscribe() chain like so_subscribe(mbox).event(evt) subscribes event handler evt for the default agent state. To subscribe event in the specified state the long so_subscribe() chain must be used. This is the form so_subscribe(mbox).in(st).event(evt):

// Subscription for the default state.
so_subscribe_self()
	.event< msg_periodic >( &a_state_swither_t::evt_handler_default );

// Subscription for the state 'st_1'
so_subscribe_self().in( st_1 )
	.event< msg_periodic >( &a_state_swither_t::evt_handler_1 );

// Subscription for the state 'st_2'
so_subscribe_self().in( st_2 )
	.event< msg_periodic >( &a_state_swither_t::evt_handler_2 );

// Subscription for the state 'st_3'
so_subscribe_self().in( st_3 )
	.event< msg_periodic >( &a_state_swither_t::evt_handler_3 );

// Subscription for the state 'st_shutdown'
so_subscribe_self().in( st_shutdown )
	.event< msg_periodic >( &a_state_swither_t::evt_handler_shutdown );

An event handler can be subscribed in several states by one so_subscribe() chain by using several in() methods like this:

so_subscribe(mbox).in( st_listening ).in( st_reading ).in( st_writing )
    .event( signal< msg_shutdown >, &my_agent::evt_shutdown );

And the opposite: several event handlers can be subscribed in one state by one so_subscribe() chain by using several event() methods like this:

so_subscribe(mbox).in( st_listening )
    .event< msg_shutdown >( &my_agent::evt_shutdown )
    .event< msg_activity_detected >( &my_agent::evt_handle_activity )
    .event< msg_check_status >( &my_agent::evt_check_status );

It is strongly not recommended to use several in() and event() method in the single so_subscribe() chain.

Yet more info about this topic can be found in SO-5.8 By Example Subscriptions.

There is no any functional advantages between those ways. The selection for one of them depends from the taste of a developer.

Switching to State

The so_5::agent_t::so_change_state() method can used to change agent's state:

// Switching to the next state.
so_change_state( st_3 );

The current agent's state can be detected by so_5::agent_t::so_current_state() method. If you use nested states then the status of a state can be checked via so_5::agent_t::so_is_active_state() method.

Default Agent's State

Every agent already has several states which are created by SObjectizer. One of them is the default state. This state is initial state for an agent (but user can change it at any moment by so_change_state() method like shown above). The default state can be accessed by so_default_state() method. The following code shows how to return an agent to the default state:

// Switching to the default state.
so_change_state( so_default_state() );

State Listener

State listeners were introduced into SObjectizer to solve the following problem: sometimes it is necessary to monitor state of an agent and do some actions when the agent changes its states. For example, send notification to external monitoring software (like Zabbix).

State listener is an object that implements so_5::agent_state_listener_t interface.

State listener can be bound to any agent by one of the methods:

  • agent_t::so_add_nondestroyable_listener. This method adds a state listener that lifetime must not be controlled by the agent. This method is useful when someone controls lifetime of the listener or when the same listener must be bound to several agents;
  • agent_t::so_add_destroyable_listener. This method adds a state listener which will be destroyed with the agent. This method is useful when listener is intended to be used with just one agent.

This sample shows both of those methods. so_add_nondestroyable_listener is used for statically allocated instance of state_monitor_t. And so_add_destroyable_listener is used for dynamically allocated instance:

auto ag = env.make_agent< a_state_swither_t >();

// Adding the state listener. Its lifetime is not controlled by the agent.
ag->so_add_nondestroyable_listener( g_state_monitor );

// Adding another state listener.
// Its lifetime is controlled by the agent.
ag->so_add_destroyable_listener(
    std::make_unique< state_monitor_t >( "destroyable_listener" ) );
Clone this wiki locally