-
Notifications
You must be signed in to change notification settings - Fork 46
v.5.6.0
This page describes changes and new features of v.5.6.0.
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.
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, ...);
...
}
...
};
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);
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:
- Deregistration of the coop. Method
environment_t::deregister_coop()
accepts the name of coop to be deregistered. - 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. Newenvironment_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 oldcoop_t::set_parent_coop_name()
from the previous versions; - methods like
register_coop()
,register_agent_as_coop()
andcreate_child_coop()
now return the ID of a new coop; - there is a new method
agent_t::so_coop()
instead ofagent_t::so_coop_name()
; - there is no more
so_5::autoname
and related stuff.
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.
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
.
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();
...
});
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, ...);
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_(...));
Interface abstract_message_box_t
has been significantly changed in v.5.6.0:
- there are no more public
deliver_message
anddeliver_signal
methods. Free functions from send-family should be used now; - there is only one
do_deliver_message()
method instead of tripletdo_deliver_message()
,do_deliver_service_request()
anddo_deliver_enveloped_msg()
. Thisdo_deliver_message()
is notconst
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.
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.
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 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.
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.
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
.
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.
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
, 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.
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
.
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
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.
There were some other things marked as deprecated in 5.5-branch. All of them removed in v.5.6.0.