-
Notifications
You must be signed in to change notification settings - Fork 46
SO 5.6 InDepth Send Functions
There are two kinds of send
functions in SObjectizer. The first kind uses dynamic allocation of a message object. The second kind works with preallocated message instances.
This send
-function is a variadic template function with the following prototype:
template<typename Msg, typename Target, typename... Msg_Constructor_Args>
void send(Target && target, Msg_Constructor_Args && ...args);
A reference to an agent, instance of so_5::mbox_t
or so_5::mchain_t
can be passed as target
argument.
All args
arguments are forwarded to the call of a constructor of Msg
type.
For example, if we have a message of that type:
class my_message final : public so_5::message_t {
public:
int a_;
char b_;
my_message(int a, char b) : a_{a}, b_{b} {}
};
and call:
so_5::send<my_message>(some_target, 1, 'c');
Then this call will be transformed to something like that:
// Step 1: construction of a message instance.
auto msg__ = std::make_unique<my_message>(1, 'c');
// Step 2: delivery of the constructed message.
so_5::some::low::level::stuff::deliver(some_target, std::move(msg__));
In the case, if my_message
is not derived from so_5::message_t
:
struct my_message {
int a_;
char b_;
};
The actual code behind the send
function will be somewhat more complex:
// Step 1: construction of a message instance.
// Note the creation of so_5::user_type_message_t<T>.
auto msg__ = std::make_unique< so_5::user_type_message_t<my_message> >(1, 'c');
// Step 2: delivery of the constructed message.
so_5::some::low::level::stuff::deliver(some_target, std::move(msg__));
It is important to note that send
is a variadic template function that uses perfect-forwarding of its arguments to the constructor of message object. This perfect-forwarding should be taken into account if message contains plain-C array as a field. For example,
struct msg_with_c_array {
int data_[8];
int len_;
};
We can initialize an instance of that type by using the initializer list in the following form:
msg_with_c_array msg{
{1, 2, 3, 4, 5, 6, 7, 8},
8
};
But we'll get a compilation error on attempt to send message of that type:
so_5::send<msg_with_c_array>(some_target, {1, 2, 3, 4, 5, 6, 7, 8}, 8);
It is because compiler knows what {1, 2, 3, 4, 5, 6, 7, 8}
means in that context:
msg_with_c_array msg{
{1, 2, 3, 4, 5, 6, 7, 8},
8
};
but doesn't know what {1, 2, 3, 4, 5, 6, 7, 8}
means in arguments of send
.
Even if we write send
as that:
so_5::send<msg_with_c_array>(some_target, std::initializer_list{1, 2, 3, 4, 5, 6, 7, 8}, 8);
it doesn't help because the compiler will report an error in the constructor of so_5::user_type_message_t
. To avoid that error it is necessary to explicitly define a constructor for msg_with_c_array
that accepts std::initializer_list
and int
:
struct msg_with_c_array {
int data_[8];
int len_;
msg_with_c_array(std::initializer_list<int> data, int len)
: len_{len}
{
std::copy(std::begin(data), std::end(data), std::begin(data_));
}
};
In that case the call:
so_5::send<msg_with_c_array>(some_target, std::initializer_list{1, 2, 3, 4, 5, 6, 7, 8}, 8);
Well be compiled successfully.
But it is much easier to use std::array
instead of plain-C arrays:
struct msg_with_array {
std::array<int, 8> data_;
int len_;
};
In that case we can write like that:
so_5::send<msg_with_array>(mbox, std::array{1, 2, 3, 4, 5, 6, 7, 8}, 8);
There are two forms of send
-function which allow to send already allocated object. The first form accepts mhood_t<Msg>
and intended to be used for redirection of received messages. For example:
class load_balancer final : public so_5::agent_t {
// Handler for an incoming message.
void on_new_message(mhood_t<some_message> cmd) {
auto mbox = detect_target_for_next_message(*cmd);
// Redirection of an existing message.
so_5::send(mbox, std::move(cmd));
}
...
};
The second form accepts message_holder_t
instance and intended to be used when an object is preallocated earlier. For example, when a message is stored for some time and will be resent later:
class peak_deleter final : public so_5::agent_t {
// Buffer for delayed messages.
std::queue< so_5::message_holder_t<some_message> > delayed_;
...
void on_new_message(mhood_t<some_message> cmd) {
if(too_many_messages_in_progress()) {
// New message should be delayed.
delayed_.push(cmd.make_holder());
}
else
// New message can be processed right now.
so_5::send(actual_processor_, std::move(cmd));
}
...
void on_resend_time(mhood_t<resend_time> cmd) {
// Now we can resend the first delayed message.
auto m = std::move(delayed_.front());
delayed_.pop_front();
so_5::send(actual_processor_, std::move(m));
}
};