Skip to content

SO 5.8 ByExample Periodic Hello

Yauheni Akhotnikau edited this page Jul 6, 2023 · 1 revision

Introduction

This sample shows how to work with named mboxes and timers. It is strongly recommended to read SO-5.8 Basics and SO-5.8 By Example Minimal Ping-Pong before viewing this sample in order to have a better understanding of what is happening here in the code.

There are two agents in this sample. The first and the simple one is a shutdowner agent. It receives msg_stop_signal and finishes SObjectizer work.

The second agent, a_hello, uses periodic and delayed messages/signals. A periodic message msg_hello_periodic is used to show ability of SObjectizer to repeat messages with the specified period of time. A delayed signal msg_stop_signal is used to demonstrate ability of SObjectizer to cancel delayed messages/signals before they are sent.

In the so_evt_start() method a_hello agent initiates two messages:

  • a periodic msg_hello_periodic with greeting text inside;
  • a delayed signal msg_stop_signal.

The trick is that the delay interval for msg_hello_periodic appearance is less than the delay interval for msg_stop_signal. It allows a_hello to cancel previous msg_stop_signal instance and schedule new msg_stop_signal instance. But yet again the next appearance of msg_hello_periodic happened before the appearance of msg_stop_signal. It leads to a canceling and rescheduling loop of msg_stop_signal. After several iterations of that loop a_hello cancels msg_hello_periodic, but not msg_stop_signal. It causes msg_stop_signal to appear and finish the sample.

Sample Code

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

#include <time.h>

// Hello message.
struct msg_hello_periodic
{
	// Greeting.
	std::string m_message;
};

// Stop message.
class msg_stop_signal final : public so_5::signal_t {};

// Agent for stopping an example on stop_signal.
class a_shutdowner_t final : public so_5::agent_t
{
	public:
		using so_5::agent_t::agent_t;

		void so_define_agent() override
		{
			so_subscribe( so_environment().create_mbox( "shutdown" ) )
				.event( &a_shutdowner_t::evt_stop_signal );
		}

	private:
		void evt_stop_signal( mhood_t<msg_stop_signal> )
		{
			time_t t = time( nullptr );
			std::cout << asctime( localtime( &t ) )
				<< "Stop SObjectizer..." << std::endl;

			// Shutting down SObjectizer.
			so_environment().stop();
		}
};

// An agent class definition.
class a_hello_t final : public so_5::agent_t
{
	public:
		a_hello_t( context_t ctx )
			:	so_5::agent_t{ ctx }
			,	m_shutdowner_mbox( so_environment().create_mbox( "shutdown" ) )
			,	m_evt_count( 0 )
		{}

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

		// A reaction to start of work in SObjectizer.
		void so_evt_start() override;

	private:
		// Shutdowner's mbox.
		const so_5::mbox_t m_shutdowner_mbox;

		// Timer events' identifiers.
		so_5::timer_id_t m_hello_timer_id;
		so_5::timer_id_t m_stop_timer_id;

		// How much timer event has been processed.
		unsigned int m_evt_count;

		// Hello message handler.
		void evt_hello_periodic( const msg_hello_periodic & msg );
};

void a_hello_t::so_define_agent()
{
	// Message subscription.
	so_subscribe_self().event( &a_hello_t::evt_hello_periodic );
}

void a_hello_t::so_evt_start()
{
	time_t t = time( nullptr );
	std::cout << asctime( localtime( &t ) )
		<< "a_hello_t::so_evt_start()" << std::endl;

	// Sending a greeting.
	m_hello_timer_id = so_5::send_periodic< msg_hello_periodic >(
		*this,
		// Delay for a second.
		std::chrono::seconds(1),
		// Repeat every 1.25 of seconds.
		std::chrono::milliseconds(1250),
		"Hello, periodic!" );

	// Sending a stop signal.
	m_stop_timer_id = so_5::send_periodic< msg_stop_signal >(
		m_shutdowner_mbox,
		// Delay for two seconds.
		std::chrono::seconds(2),
		// Not a periodic.
		std::chrono::seconds::zero() );
}

void a_hello_t::evt_hello_periodic( const msg_hello_periodic & msg )
{
	time_t t = time( nullptr );
	std::cout << asctime( localtime( &t ) ) << msg.m_message << std::endl;

	if( 5 == ++m_evt_count )
	{
		// Stops hello message.
		m_hello_timer_id.release();
	}
	else
	{
		// Reschedule a stop signal.
		// Previous a stop signal should be canceled.
		m_stop_timer_id = so_5::send_periodic< msg_stop_signal >(
			m_shutdowner_mbox,
			// 1300ms but specified in microsecs just for demonstration.
			std::chrono::microseconds(1300000),
			std::chrono::seconds::zero() );
	}
}

// Creation of 'hello' cooperation.
void create_hello_coop( so_5::environment_t & env )
{
	// Single agent can be registered as whole cooperation.
	env.register_agent_as_coop( env.make_agent< a_hello_t >() );
}

// Creation of 'shutdowner' cooperation.
void create_shutdowner_coop( so_5::environment_t & env )
{
	env.register_agent_as_coop( env.make_agent< a_shutdowner_t >() );
}

// The SObjectizer Environment initialization.
void init( so_5::environment_t & env )
{
	create_hello_coop( env );
	create_shutdowner_coop( env );
}

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

Message msg_hello_periodic Declaration

In first versions of SObjectizer 5.5 every message which carries actual data must be derived from so_5::message_t class. But since v.5.5.9 it is not necessary. A simple struct is used as message in that example:

// Hello message.
struct msg_hello_periodic
{
	// Greeting.
	std::string m_message;
};

Creation of Mbox in a_hello_t Constructor

a_hello_t( context_t ctx )
	:	so_5::agent_t{ ctx }
	,	m_shutdowner_mbox( so_environment().create_mbox( "shutdown" ) )
	,	m_evt_count( 0 )
{}

The agent a_hello uses two mboxes. The first one is used for sending and handling the msg_hello_periodic message. A MPSC mbox (which is automatically created for an agent) is enough for those purposes. There is no need to create this mbox, it is accessible via so_direct_mbox() method.

The second mbox is used for sending msg_stop_signal. This mbox is also used by shutdowner agent. But a_hello and shudowner agents are created separately. Thus, it is impossible to share the reference to an anonymous mbox between them. As a solution a named mbox is used here.

The named mbox is created only once and any subsequent call to create_mbox() with the same name will return the reference to already created object. So one is able to receive a reference to the same mbox from anywhere in the code.

Initiation of Periodic and Delayed Messages

The lines where periodic and delayed messages are initiated might seem too complicated at first glance. But in reality they are pretty straightforward:

// Sending a greeting.
m_hello_timer_id = so_5::send_periodic< msg_hello_periodic >(
	*this,
	// Delay for a second.
	std::chrono::seconds(1),
	// Repeat every 1.25 of seconds.
	std::chrono::milliseconds(1250),
	"Hello, periodic!" );

// Sending a stop signal.
m_stop_timer_id = so_5::send_periodic< msg_stop_signal >(
	so_environment(),
	m_shutdowner_mbox,
	// Delay for two seconds.
	std::chrono::seconds(2),
	// Not a periodic.
	std::chrono::seconds::zero() );

A periodic message is initiated by calling so_5::send_periodic template function. It automatically allocated a message instance which will be sent every 1250 milliseconds after the first appearance which would be delayed for one second. The message will be sent to the direct mbox of a_hello_t agent.

Next, a delayed signal is initiated. It will be sent to the m_shutdowner_mbox after two seconds. The forth argument to send_periodic is 0 here. It specifies that message will be delayed, but not periodically. We use send_periodic here but not send_delayed because we need a timer ID for delayed message. Function send_periodic returns that ID.

Note. The timer ID storing which is returned from send_periodic is absolutely necessary. The timer is alive until there is at least one timer ID reference. If all of timer ID references are destroyed then the timer is canceled automatically.

Canceling and Rescheduling Messages

The code in a_hello_t::evt_hello_periodic shows how timer-related messages can be canceled and rescheduled:

void a_hello_t::evt_hello_periodic( const msg_hello_periodic & msg )
{
	time_t t = time( nullptr );
	std::cout << asctime( localtime( &t ) ) << msg.m_message << std::endl;

	if( 5 == ++m_evt_count )
	{
		// Stops hello message.
		m_hello_timer_id.release();
	}
	else
	{
		// Reschedule a stop signal.
		// Previous a stop signal should be canceled.
		m_stop_timer_id = so_5::send_periodic< msg_stop_signal >(
			m_shutdowner_mbox,
			// 1300ms but specified in microsecs just for demonstration.
			std::chrono::microseconds(1300000),
			std::chrono::seconds::zero() );
	}
}

The construct m_hello_timer_id.release() cancels the delivery of periodic message msg_hello_periodic.

The new value assignment to m_stop_timer_id has the same effect. It is equivalent of:

// Initiate a new delayed message and receive ID of a new timer.
so_5::timer_id_t new_id = so_environment().schedule_timer...;
// Cancel the previous timer.
m_stop_timer_id.release();
// Store a new timer.
m_stop_timer_id = new_id;

Creation of Cooperation with a Single Agent Inside

It is not necessary to create a so_5::coop_t object and fill it up when there is only one agent inside. A special method of environment_t can be used in that situation:

// Creation of 'hello' cooperation.
void create_hello_coop( so_5::environment_t & env )
{
	// Single agent can be registered as whole cooperation.
	env.register_agent_as_coop( env.make_agent< a_hello_t >() );
}

// Creation of 'shutdowner' cooperation.
void create_shutdowner_coop( so_5::environment_t & env )
{
	env.register_agent_as_coop( env.make_agent< a_shutdowner_t >() );
}

Creation of Several Cooperations in Initialization Function

Please note that SObjectizer Environment's initialization function, init, creates two cooperations, not one as in the previous sample (see SO-5.8 By Example Minimal Ping-Pong). It is allowed to create plenty of cooperations inside initialization functions and this could be used in complex applications.

Clone this wiki locally