SO 5.8 InDepth User Type Msg - Stiffstream/sobjectizer GitHub Wiki
- Introduction
- How It Works
- How to Receive Messages of Arbitrary Types
- Delivery Filters
- Arbitrary Message Types and limit_then_transform
Created by gh-md-toc
Prior to v.5.5.9 every message had to be represented by an instance of a class derived from message_t. It means that if a single integer value need to be sent to an agent the whole dedicated class must have been defined. For example:
enum class engine_action { turn_on, speed_up, slow_down, turn_off };
struct msg_engine_action : public so_5::message_t
{
engine_action m_action;
msg_engine_action( engine_action action ) : m_action{ action } {}
};
...
// Message sending...
so_5::send< msg_engine_action >( engine_agent, engine_action::turn_on );
...
// Message processing…
void engine_agent::evt_engine_action( const msg_engine_action & msg )
{
switch( msg.m_action )
{...}
}
Since v.5.5.9 there is a possibility to send an object of arbitrary type as a message. For example:
enum class engine_action { turn_on, speed_up, slow_down, turn_off };
...
// Message sending...
so_5::send< engine_action >( engine_agent, engine_action::turn_on );
...
// Message processing…
void engine_agent::evt_engine_action( engine_action msg )
{
switch( msg )
{...}
}
There is a very simple trick: when a user calls send<T>(mbox,args)
and T
is not derived from message_t
then an instance of a special template type user_type_message_t<T>
is constructed and delivered as a message.
When an instance of user_type_message_t<T>
is extracted from an event queue and the event handler for it has the signature void(const T&)
or void(T)
then this event handler is called and a reference to the content of user_type_message_t<T>
is passed to that event handler.
So it is possible to say that call to send<T>(mbox, args)
where T
is not derived from message_t
is replaced by send<user_message_type_t<T>>(mbox, args)
.
And the event handler in the form void(const T&)
is replaced by:
// void some_class::evt_handler(const T &):
void some_class::real_evt_handler(const user_type_message_t<T> & evt) {
evt_handler(evt.m_payload);
}
The user_type_message_t<T>
itself is a very simple template:
template< typename T >
struct user_type_message_t : public message_t
{
const T m_payload;
template< typename... Args >
user_type_message_t( Args &&... args )
: m_payload( std::forward< Args >( args )... )
{}
};
Messages of arbitrary type T must be handled by non-static methods with signature like:
void event_handler(const T & ); // (1)
void event_handler(T); // (2)
void event_handler(const so_5::mhood_t<T> &); // (3)
void event_handler(so_5::mhood_t<T>); // (3)
Or by lambda-function with signatures like:
void(const T&); // (1)
void(T); // (2)
void(const so_5::mhood_t<T> &); // (3)
void(so_5::mhood_t<T>); // (3)
But note that in the cases (1) and (3) event-handler will receive a reference to the source message instance. In the case (2) a copy of message content will be passed to the event handler. It can be costly if T is a heavy object.
For example:
struct new_config { ... };
enum class overload_status { normal_load, high_load };
...
class overload_controller : public so_5::agent_t
{
public :
...
virtual void so_define_agent() override
{
so_subscribe_self()
.event( &overload_controller::evt_new_config )
.event( [this]( overload_status status ) {
if( overload_status::high_load == status )
handle_overload();
} );
...
}
void evt_new_config( const new_config & cfg ) {...}
...
};
If an event handler receives a message via so_5::mhood_t<T>
then T
can be a message or signal type.
Delivery filters can be used for messages of arbitrary types. Delivery filter must be a callable object (lambda-function or function) with signatures:
bool(const T&); // (1)
bool(T); // (2)
But note that in the case (1) filter will receive a reference to the source message instance. In the case (2) a copy of message content will be passed to filter. It can be costly if T is a heavy object.
class sensor_listener : public so_5::agent_t
{
public :
...
void so_define_agent() override
{
auto mbox = so_environment().create_mbox("sensor");
so_set_delivery_filter( mbox, []( int v ) {
return v > -10 && v < 65;
} );
so_subscribe(mbox).event( &sensor_listener::evt_value );
...
}
...
void evt_value( int v )
{
...
}
};
Messages of arbitrary type T can be handled by limit_then_transform
helper method. But it is strongly recommended to use make_transformed
for new message instance. For example:
enum class overload_status { normal_load, high_load };
...
class request_processor : public so_5::agent_t
{
public :
request_processor( context_t ctx )
: so_5::agent_t( ctx +
limit_then_transform< std::string >( 1000,
[this]( const std::string & ) {
return make_transformed< overload_status >(
overload_controller_mbox,
overload_status::high_load );
} ) )
{...}
...
};