SO 5.7 InDepth User Type Msg - Stiffstream/sobjectizer GitHub Wiki

Introduction

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 )
   {...}
}

How It Works

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 )... )
      {}
};

How to Receive Messages of Arbitrary Types

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

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 )
   {
      ...
   }
};

Arbitrary Message Types and limit_then_transform

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 );
            } ) )
   {...}
   ...
};
⚠️ **GitHub.com Fallback** ⚠️