SO 5.7 InDepth Custom Direct Mbox - Stiffstream/sobjectizer GitHub Wiki

Introduction

Every agent in SObjectizer has a direct mbox. It's a MPSC mbox that can only be used by a single agent -- the agent that owns this mbox.

The direct mbox is created by SObjectizer Environment somewhere in the constructor of the so_5::agent_t class. Until v.5.7.4 a standard implementation of single-owner MPSC mbox was used for all agents. That implementation was (and is) hidden from a user. It's only guaranteed that this implementation implements so_5::abstract_message_box_t interface.

Since v.5.7.4 SObjectizer allows setting a custom MPSC mbox as the direct mbox for an agent.

That feature is intended to be used in very rare cases.

For example, you have already written and tested code where some agents send messages to direct mboxes each of another. This code works well and it's too expensive and risky to refactor it. But you have to introduce some changes in the logic: some messages sent to an agent have to be intercepted and redirected to another mbox.

A task of intercepting/redirection of a message can be done via a custom implementation of a mbox. So it's not a complex part of problem. The complex part is to replace the use of so_direct_mbox in already written code (at the places where messages are sent and in the places where subscriptions are made).

In such a case it's possible to set a custom intercepting mbox as the direct mbox for required agents. It means that so_direct_mbox will return a reference to that custom mbox, not to the default MPSC mbox created for the agent by SObjectizer Environment.

Setting a custom direct mbox

A custom direct mbox can be set via custom_direct_mbox_factory passed to agent's tuning options:

// Implementation of custom direct mbox.
class my_custom_mbox_type : public so_5::abstract_message_box_t
{
  ...
};

// Factory for new custom direct mbox.
[nodiscard](/Stiffstream/sobjectizer/wiki/nodiscard) so_5::mbox_t
my_custom_direct_mbox_factory(
  // This is the pointer to the agent being created.
  so_5::partially_constructed_agent_ptr_t /*agent_ptr*/,
  // This is the default direct mbox for that agent created by SOEnv.
  so_5::mbox_t source_mbox )
{
  return { std::make_unique<my_custom_mbox_type>( source_mbox ) };
}

// An agent that uses a custom direct mbox.
class my_agent : public so_5::agent_t
{
  ...
public:
  my_agent( context_t ctx )
    : so_5::agent_t{ ctx +
        // Pass a factory to be used for a custom direct mbox.
        custom_direct_mbox_factory( my_custom_direct_mbox_factory )
      }
  {...}
};

Please note that ctx with additional tuning options is being passed to the constructor of base class. This is an essential point, because the direct mbox is set for agent in the body of so_5::agent_t constructor.

The example above shows how to pass a custom_direct_mbox_factory in the constructor of our class. But sometimes it could be necessary to change the direct mbox for an agent of the class we can't change. For example we have a 3rd-party library with some_useful_agent class and can't modify its source code. In that case we can write something like that:

env.introduce_coop( [](so_5::coop_t & coop) {
    // We can't use coop.make_agent method here because we have to make
    // context for a new agent manually.
    auto useful_agent = std::make_unique<some_useful_agent>(
      // Define a context for a new agent with a custom mbox factory.
      so_5::agent_context_t{ coop.environment() } +
        // Pass our custom mbox factory to that context.
        so_5::agent_t::custom_direct_mbox_factory( my_custom_direct_mbox_factory ),
      ... // All other arguments for some_useful_agent's constructor
      );

    // Add a new agent to the cooperation.
    coop.add_agent( std::move(useful_agent) );
  } );

The role of partially_constructed_agent_ptr_t

A factory for custom direct mboxes receives two parameters. The first one is a pointer to the agent for that a new mbox has to be created. The pointer can be necessary to some implementations of mboxes.

But there is a nuance. The agent itself is only partially constructed when the factory for custom mboxes is called. Strictly speaking the factory is called somewhere in the body of the so_5::agent_t constructor. The constructor is not completed yet, so there is no fully constructed agent. And the pointer to the object being constructed can't be seen as a normal pointer: it's dangerous to call methods of that object inside the factory.

That is why a pointer to the agent being constructed is passed to the factory in the partially_constructed_agent_ptr_t wrapper. That wrapper tells that additional care should be taken in manipulations with this pointer.