Skip to content

v.5.6.0

Yauheni Akhotnikau edited this page Aug 30, 2019 · 2 revisions

This page describes changes and new features of v.5.6.0.

Switch to C++17

Since v.5.6.0 SObjectizer requires C++17.

If you want to have support for C++14 or even C++11 in 5.6-branch please contact stiffstream to discuss such work.

New stuff

Method agent_t::so_make_new_direct_mbox()

A new method so_make_new_direct_mbox has been added to so_5::agent_t class. This method allows the creation of a new MPSC mbox bound to the agent. A new MPSC will work independently from the default direct agent's mbox that is available via so_direct_mbox() method. This allows to create different subscriptions for the agent. For example:

struct request_data {
  const so_5::mbox_t reply_to_;
  ...
};

class data_customer final : public so_5::agent_t {
  void on_some_event(mhood_t<some_event> cmd) {
    // A new data should be requested.
    // Create a new mbox for it.
    const auto unique_mbox = so_make_new_direct_mbox();
    // Subscribe for a message from it.
    so_subscribe(unique_mbox).event([this](mhood_t<data> cmd) {...});
    // Limit waiting time for it.
    so_subscribe(unique_mbox).event([this, unique_mbox](mhood_t<timeout>) {
        so_drop_subscription<data>(unique_mbox);
      });
    so_5::send_delayed<timeout>(unique_mbox, 15s);
    // Use this mbox in request as reply-to address.
    so_5::send<request_data>(data_supplier_, unique_mbox, ...);
    ...
  }
  ...
};

New class message_holder_t for working with preallocated or stored messages.

Interface abstract_message_box_t has been changed in v.5.6.0. Because of that a new way of sending preallocated messages should be used. This way uses a new message_holder_t class:

class some_agent final : public so_5::agent_t
{
   // A message to be send by this agent.
   class do_something { ... };

   // A single preallocated instance of that message.
   so_5::message_holder_t<do_something> msg_;
   ...
   void on_some_cmd(mhood_t<some_command> cmd) {
      ...
      // The preallocated message should be sent to someone.
      so_5::send(cmd->target_, msg_);
      ...
   }
   ...
};

This class can also be used for storing a received message:

class demo final : public so_5::agent_t {
   // Stored message.
   so_5::message_holder_t<some_message> stored_msg_;
   ...
   void on_message(mhood_t<some_message> cmd) {
      // Store the message to redirect it later.
      stored_msg_ = cmd.make_holder();
      ...
   }
   void on_some_external_event(mhood_t<some_event>) {
      // It's time to redirect the stored message.
      so_5::send(some_target, stored_msg_);
   }
};

The new class message_holder_t has several unique features:

First of all, it correctly handles the distinctions between messages inherited from so_5::message_t and messages of arbitrary user types. If a message has type T and that type is inherited from so_5::message_t an instance of message_holder_t holds a pointer to T. If T is not derived from so_5::message_t then message_holder_t will hold a pointer to the special internal wrapper so_5::user_type_message_t<T>. But getter's of message_holder_t will always return a pointer/reference to T:

struct so5_message final : public so_5::message_t { ... };
struct user_message final { ... };

so_5::message_holder_t<so5_message> one{...}; // Holds pointer to so5_message.
so_5::message_holder_t<user_message> two{...}; // Holds pointer to so_5::user_type_message_t<user_message>.

so5_message * p1 = one.get(); // Pointer to so5_message is returned.
user_message * p2 = two.get(); // Pointer to user_message is returned.

The second feature is the correct handling of message mutability. It means that message_holder_t allows to write:

so_5::message_holder_t<my_message> msg{...}; // An instance of immutable message.
so_5::message_holder_t<so_5::immutable_msg<my_message>> msg2{...}; // Another immutable message.
so_5::message_holder_t<so_5::mutable_msg<my_message>> msg3{...}; // A mutable message.

The getters from message_holder_t are depended on message mutability: for immutable messages they return const pointers/references, but for mutable messages they return non-const pointers:

so_5::message_holder_t<my_message> immutable_msg{...};
auto v = immutable_msg->some_field; // Ok.
immutable_message->some_field = v; // WON'T COMPILE!

so_5::message_holder_t<so_5::mutable_msg<my_message>> mutable_msg{...};
auto v = mutable_msg->some_field; // Ok.
mutable_message->some_field = v; // Ok.

The third feature is the ability to work as different smart pointers. The message_holder_t class is a kind of smart pointer; it holds the only pointer to the message instance, not the copy of the message.

By default, the behavior of message_holder_t depends on the message's mutability. For immutable messages, it works as std::shared_ptr. For mutable messages it works as std::unique_ptr:

so_5::message_holder_t<my_message> msg{...};
so_5::message_holder_t<my_message> msg2 = msg; // msg2 and msg point to the same message instance.

so_5::message_holder_t<so_5::mutable_msg<my_message>> msg3{...};
so_5::message_holder_t<so_5::mutable_msg<my_message>> msg4 = msg3; // WON'T COMPILE!
so_5::message_holder_t<so_5::mutable_msg<my_message>> msg5 = std::move(msg3); // Ownership moved to msg5.

However, the behavior can be specified manually as a template parameter. It allows to have std::unique_ptr-like behavior for immutable messages or std::shared_ptr-like behavior for mutable messages:

using unique_my_message = so_5::message_holder_t<
   my_message,
   so_5::message_ownership_t::unique>;

unique_my_message msg{...};
unique_my_message msg2 = msg; // WON'T COMPILE!
unique_my_message msg3 = std::move(msg); // Ownership moved to msg3.

using shared_another_messsage = so_5::message_holder_t<
   so_5::mutable_msg<another_message>,
   so_5::message_ownership_t::shared>;

shared_another_message msg4{...};
shared_another_message msg5 = msg4; // msg5 and msg4 point to the same message instance.

The last but not least feature is the ability to construct message object inplace (with respect to inheritance from so_5::message_t and message's mutability):

class so5_message final : public so_5::message_t {
   int a_;
   const std::string b_;

   so5_message(int a, std::string b) : a_{a}, b_{std::move(b)} {}
};

so_5::message_holder_t<so5_message> msg{std::piecewise_construct, 0, "Hello");
// Or, another variant:
auto msg2 = so_5::message_holder_t<so5_message>::make(0, "Hello");

class user_message final {
  std::string str_;
  std::chrono::milliseconds time_;
};

so_5::message_holder_t<so_5::mutable_msg<user_message>> msg3{std::piecewise_construct, "Hello", 125ms);
// Or, another variant:
auto msg4 = so_5::message_holder_t<so_5::mutable_msg<user_message>>::make("Hello", 125ms);

Changed stuff

Cooperations of agents

Cooperation (or just coop) is one of SObjectizer's cornerstones. A coop is a container for agents. Agents from a coop are added and removed from SObjectizer's Environment in a transactional manner: all agents from the coop are registered and started or no one of them.

In the previous versions of SObjectizer, every coop should have a unique name. A user can choose that name by him/herself or can ask SObjectizer to generate that name automatically. But every coop has its unique name.

That name was used for two very important operations:

  1. Deregistration of the coop. Method environment_t::deregister_coop() accepts the name of coop to be deregistered.
  2. The parent-child relationship between coop. If a coop has the parent the name of the parent must be set for the child coop.

The presence of coop's name significantly limits the scalability of registration/deregistration procedures. Because of that, there is no coop's name since v.5.6.0.

Since v.5.6.0 every coop is identified by a special type so_5::coop_id_t. An instance of that type is returned by environment_t::register_coop():

auto coop = env.make_coop();
... // Fill the coop.
auto coop_id = env.register_coop(std::move(coop));

This coop_id is then used for coop's deregistration and for parent-child relation. For example:

// Create a parent coop.
auto parent = env.make_coop();
... // Fill the parent coop.
auto parent_id = env.register_coop(std::move(parent));

// Now we can create a child coop.
auto child = env.make_coop(parent_id); // We tell that this coop has the parent.
... // Fille the child.
env.register_coop(std::move(child));
...
// Deregister the parent (child will be deregistered automatically).
env.deregister_coop(parent_id, so_5::dereg_reason::normal);

Because of the rewriting of coop's handling mechanism, there are some user-visible changes:

  • there are no more environment_t::create_coop() methods. New environment_t::make_coop() methods should be used instead;
  • the ID of the parent coop is now specified in make_coop() method. There is no analog of old coop_t::set_parent_coop_name() from the previous versions;
  • methods like register_coop(), register_agent_as_coop() and create_child_coop() now return the ID of a new coop;
  • there is a new method agent_t::so_coop() instead of agent_t::so_coop_name();
  • there is no more so_5::autoname and related stuff.

Only one kind of dispatchers

Until v.5.6.0 there were two kinds of dispatchers: public and private. There were two different APIs for the creation of those dispatchers. There was a significant difference in public/private dispatchers' lifetimes.

Since v.5.6.0 only one kind of dispatchers is supported in SObjectizer. New dispatchers are similar to private dispatchers from the previous versions of SObjectizer.

Dispatchers are created by the corresponding make_dispatcher() functions. A dispatcher is automatically started when it created and will be automatically stopped when no one uses it.

make_dispatcher() functions return a dispatcher-specific type dispatcher_handle. This type can be seen as a smart reference or smart pointer to the dispatcher object. Every dispatcher_handle type has binder() method. This method should be called to get a binder for the dispatcher. The binder returned by binder() method can be used the usual way (almost like in the previous versions):

// Create an instance of thread_pool dispatcher.
auto tp_disp = so_5::disp::thread_pool::make_dispatcher(env, 4);
// Create a new coop and use tp_disp as the default binder for it.
auto coop = env.make_coop(tp_disp.binder());
coop->make_agent<...>(...);
...
// Add an agent that will be bound to different dispatcher.
coop->make_agent_with_binder<...>(
  // Create a new one_thread dispatcher...
  so_5::disp::one_thread::make_dispatcher(env)
    // ...and get a binder for it.
    .binder(),
  ...);

Note. Instances of dispatcher_handle and disp_binder hold references to the dispatcher. While there is at least one live reference (e.g. one dispatcher_handle or disp_binder) the dispatcher will live and work. The dispatcher will be destroyed only when all such references are gone.

Format of event-handlers

Since v.5.6.0 SObjectizer supports event-handlers in the following formats:

void event_handler(mhood_t<T>);
void event_handler(const mhood_t<T> &);
void event_handler(T);
void event_handler(const T &);

Note that event-handlers should return void.

Helpers introduce_coop()/introduce_child_coop() now return values

Since v.5.6.0 helpers introduce_coop and introduce_child_coop return the value that is returned by lambda-function passed to introduce_coop/introduced_child_coop. It allows to write:

const auto mbox = env.introduce_coop([](so_5::coop_t & coop) {
  auto my_agent = coop.make_agent<some_my_agent_type>(...);
  ...
  return my_agent->so_direct_mbox();
});

instead of:

auto mbox;
env.introduce_coop([&](so_5::coop_t & coop) {
  auto my_agent = coop.make_agent<some_my_agent_type>(...);
  mbox = my_agent->so_direct_mbox();
  ...
});

Now every mbox has a reference to SOEnv. New formats of send_delayed()/send_periodic()

Since v.5.6.0 every mbox has a reference to SObjectizer Environment that created this mbox. It allows to change the format of send_delayed/send_periodic functions. Now all those functions have the following format:

void send_delayed(dest, pause, ...);
so_5::timer_id_t send_periodic(dest, pause, period, ...);

where dest can be a mbox, a mchain or a reference to an agent:

so_5::send_delayed<some_message>(*this, 15s, ...);
// Or:
so_5::send_delayed<some_message>(this->so_direct_mbox(), 15s, ...);

receive() and select() functions now require handle_all()/handle_n()/extract_n() restriction

Since v.5.6.0 only advanced version of receive() and select() functions are supported.

It is necessary to define a restriction in the form of handle_all()/handle_n()/extract_n() for receive()/select(). For example, such code won't compile:

so_5::receive(from(ch), [](so_5::mhood_t<event>) {...});

If a user want to handle all messages from the chain he/she should specify handle_all():

so_5::receive(from(ch).handle_all(), [](so_5::mhood_t<event>) {...});

The same applies to select(). This simple code won't compile:

so_5::select(so_5::from_all(),
  case_(...),
  case_(...));

It should be rewritten such way:

so_5::select(so_5::from_all().handle_all(),
  case_(...),
  case_(...));

abstract_message_box_t interface changed

Interface abstract_message_box_t has been significantly changed in v.5.6.0:

  • there are no more public deliver_message and deliver_signal methods. Free functions from send-family should be used now;
  • there is only one do_deliver_message() method instead of triplet do_deliver_message(), do_deliver_service_request() and do_deliver_enveloped_msg(). This do_deliver_message() is not const now.

These changes have the following consequences:

  • all sends of messages/signals should be rewritten by using send functions;
  • all implementations of custom mboxes should be rewritten.

Removed stuff

Synchronous interaction between agents

Synchronous interaction between agents was introduced in v.5.3.0. Then it was expanded and helpers request_value and request_feature were introduced in v.5.5.9. This form of interaction required special support in SObjectizer's message delivery mechanism.

Support for synchronous interaction between agents was removed in v.5.6.0. SObjectizer's core doesn't support this type of interaction anymore. Support for synchronous interaction is moved to so5extra project.

One of event-handler formats

In previous versions of SObjectizer event-handler for signals can have the following format:

ret_type event_handler();

This format is no more supported. An event-handler for a signal should have one of the following formats:

void event_handler(mhood_t<Signal>);
void event_handler(const mhood_t<Signal> &);

It also means that this form of event-subscription:

state.event<Signal>(event_handler);

is also removed.

Ad-hoc agents

Ad-hoc agents were introduced in v.5.3.0. Then they were enhanced in 5.5-branch.

Ad-hoc agents and related stuff removed in v.5.6.0. Ordinary agents should be used instead.

Some methods which accept raw pointers

Until v.5.6.0 there were some methods which accept raw pointers: register_agent_as_coop in environment_t, add_agent and take_under_control in coop_t, and so on. Such methods are removed. Methods which accepts std::unique_ptr should be used instead.

Typedef event_data_t

Name event_data_t was redefined as a typedef in v.5.5.14. This name removed in v.5.6.0. Type mhood_t should be used instead of event_data_t.

Simple forms or receive() and select() functions

There were simple forms of receive() and select() functions in 5.5-branch. For example:

so_5::receive(ch, so_5::infinite_wait(), [](so_5::mhood_t<some_msg>) {...});

This simple form of receive() had an equivalent in advanced form of receive:

so_5::receive(from(ch).extract_n(1), [](so_5::mhood_t<some_msg>) {...});

But often a user think that simple form of receive() is the equivalent to:

so_5::receive(from(ch).handle_n(1), [](so_5::mhood_t<some_msg>) {...});

There is a big difference in behavior of extract_n(1) and handle_n(1) and misundestanding of it leads to mistakes. Because of that simple forms of receive() and select() functions were removed in v.5.6.0. Advanced versions of receive() and select() should be used instead.

Methods schedule_timer() and single_timer() from environment_t

There were public methods environment_t::schedule_timer() and environment_t::single_timer() which allow to send periodic or/and delayed messages. These methods are removed in v.5.6.0. Free functions send_periodic() and send_delayed() should be used now for dealing with periodic/delayed messages.

Old send-family functions like send_to_agent()

Old send-family functions like send_to_agent, send_delayed_to_agent, send_periodic_to_agent were removed in v.5.6.0. Usual send, send_delayed and send_periodic functions should be used instead.

No more so_make_state() method

Helper method agent_t::so_make_state was introduced in v.5.4.0. But there was no need in that method after switching to rather new C++ compilers. Now this method is removed from so_5::agent_t.

No more tuple_as_message

tuple_as_message was introduced in v.5.5.5. It had the sense at that time because old versions of SObjectizer-5 didn't support messages of user types. But after the introduction of support of arbitrary user types as messages in v.5.5.9 the direct support for tuple_as_message became redundant. It was present in 5.5-branch for compatibility.

Support for tuple_as_message has been deleted in v.5.6.0.

Namespace so_5::rt

Namespace so_5::rt was deprecated in v.5.5.13. It was present in 5.5-branch for compatibility. There is no such namespace in v.5.6.0.

Various deprecated stuff removed

There were some other things marked as deprecated in 5.5-branch. All of them removed in v.5.6.0.

Clone this wiki locally