New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Handling any message type #24
Comments
Hi! It's impossible in SObjectizer-5. It goes against the SObjectizer's ideology: every message has its type, and a handler for a message is being found by the message type in the current agent's state. We didn't encounter real-world use cases where a handler for any message type would be needed. Can you provide an example of where you need such functionality? |
I am happy to share with you a use case! Basically I have an application that grabs images and other measures from industrial cameras. Imagine that this application has an abstraction that enables the users to configure some "blocks" to process such data in a sort of "flow graph" fashion. The users can also code their own blocks and also add new devices. You can see a block like an agent. Handling several devices usually means managing several SDKs of several vendors, each having a specific image type. Some blocks leverage the underlying type information to exploit some SDK capabilities. For instance, the SDK of a certain thermal camera provides tons of useful metrics on the thermal image, however those are strongly coupled with the internal image type. This particular block won't work with an incompatible image source. In other cases, a "common image type" would be enough for performing the operation. For example, for printing a label on the image and save it. In this case the image dimensions and raw data are enough. For this reason, the application provides a special block that is just a converter from any supported image type to a common image type. However, for some other tasks (like sending a heartbeat when a message comes, or logging the number of messages arrived), such a conversion is useless since it does not take any information of the image at all. Since the conversion adds some copy cost (that makes some difference in very specific cases), the current solution is to have another conversion block that just maps any supported image type to a fake type (a sort of signal). Handling "any message type" was just a possible way to make things a bit simpler:
Let me know your thoughts. Many thanks! |
An interesting use-case, thanks for sharing. At the first glance, it's needed to look to an OO-hierarchy of message types. Something like: class image_base : public so_5::message_t
{
public:
... // some common image properties.
};
class vendor_one_image : public image_base
{
... // some vendor specific stuff.
}
class vendor_two_image : public image_base
{
... // some vendor specific stuff.
};
... A message-subscription should be done for class some_block : public so_5::agent_t
{
void on_image(mhood_t<image_base> cmd) {...}
...
void so_define_agent() override {
so_subscribe(board_).event(&some_block::on_image);
...
}
}; Messages have to be sent as so_5::message_holder_t<image_base> msg{std::make_unique<vendor_one_image>(...)};
so_5::send(board, std::move(msg)); Every message handler can access common image properties easily. If some vendor-specific part has to be accessed it can be done via There is a working example showing this technique: #include <so_5/all.hpp>
class msg_base_t : public so_5::message_t
{
public:
using so_5::message_t::message_t;
virtual std::string say_hello() const = 0;
};
class msg_receiver_t : public so_5::agent_t
{
public:
using so_5::agent_t::agent_t;
struct stop_t final : public so_5::signal_t {};
void so_define_agent() override
{
so_subscribe_self()
.event([](mhood_t<msg_base_t> cmd) {
std::cout << "incoming message is: "
<< cmd->say_hello() << std::endl;
} )
.event( [this](mhood_t<stop_t>) {
so_deregister_agent_coop_normally();
});
}
};
class image_type_A_t : public msg_base_t
{
public:
using msg_base_t::msg_base_t;
std::string say_hello() const override
{
return "img-typ-a";
}
};
class image_type_B_t : public msg_base_t
{
std::string m_additional_info;
public:
image_type_B_t(std::string additional_info)
: m_additional_info{ std::move(additional_info) }
{}
std::string say_hello() const override
{
return "img-typ-b::" + m_additional_info;
}
};
int main() {
so_5::launch( [](so_5::environment_t & env) {
auto dest_mbox = env.introduce_coop( [](so_5::coop_t & coop) {
return coop.make_agent<msg_receiver_t>()->so_direct_mbox();
} );
{
so_5::message_holder_t<msg_base_t> msg{
std::make_unique<image_type_A_t>()
};
so_5::send(dest_mbox, std::move(msg));
}
{
so_5::message_holder_t<msg_base_t> msg{
std::make_unique<image_type_B_t>("First")
};
so_5::send(dest_mbox, std::move(msg));
}
{
so_5::message_holder_t<msg_base_t> msg{
std::make_unique<image_type_B_t>("Second")
};
so_5::send(dest_mbox, std::move(msg));
}
so_5::send<msg_receiver_t::stop_t>(dest_mbox);
} );
} |
Thanks for the code. It confirms one of my ideas to handle this scenario! |
Yes, you can use a variant as a message type, I don't expect problems here. |
Great, thanks for your ideas. I think I can close this one. |
Maybe this issue can be solved by a helper function template? |
That's definitely helpful but maintaining both the wrapper classes and the code calling the helper function is still required. It's not a big deal but needs to be taken into account. Speaking in general, both the variant and the inheritance-based techniques work nicely as you described, however they do not leverage the pattern matching capabilities of SObjectizer. You cannot just do: void so_define_agent() override
{
so_subscribe_self()
.event([](mhood<image_vendor_A> vendorA) {
// converts from vendorA to CommonImage
} )
.event([](mhood<image_vendor_B> vendorB) {
// converts from vendorB to CommonImage
} )
.event( [](SOME_ANY_TYPE) {
throw runtime_exception("this conversion has not been implemented");
});
}
//... another agent:
void so_define_agent() override
{
so_subscribe_self()
.event( [this](SOME_ANY_TYPE) {
m_counter++; // just counts incoming messages
});
} On the other hand, both the variant and the inheritance requires you to manage an extra level of indirection (e.g. visitor, virtual functions) that gets the message and translates it into your domain logic. SObjectizer, instead, enables us to express the domain logic directly in the subscription handlers, without any indirections. That's just awesome. |
From my point of view the cases like: // one agent
void so_define_agent() override
{
so_subscribe_self()
...
.event( [](SOME_ANY_TYPE) {
throw runtime_exception("this conversion has not been implemented");
});
}
//... another agent:
void so_define_agent() override
{
so_subscribe_self()
...
.event( [this](SOME_ANY_TYPE) {
m_counter++; // just counts incoming messages
});
} are rare and do not allow to do something really useful because you don't know what message you are handling. But if OO-hierarchy is used for message types then it opens a possibility to do something like that: class image_base : so_5::message_with_fallback_t {...};
class image_vendor_A : public image_base {...};
class image_vendor_B : public image_base {...};
...
void first_agent::so_define_agent() {
so_subscribe_self()
.event([this](mhood_t<image_vendor_A> cmd) {...})
.event([this](mhood_t<image_vendor_B> cmd) {...})
.event([this](mhood_t<image_base> cmd) {...})
...
}
void second_agent::so_define_agent() {
so_subscribe_self()
.event([this](mhood_t<image_base>) { ++m_counter; });
}
... An event-handler searching procedure has to be changed:
This mechanism looks pretty implementable, but it just an idea now. |
It would be nice to have this! By the way, just as a side note, I am mildly biased by CAF because it provides a default handler concept. I am not a CAF user but I admit I tried it before getting to SObjectizer. Thanks @eao197 , you are always very clear and kind. Looking forward to host your community in March. |
IIRC, this is just the second real-world use-case reported where such functionality is applicable.
SObjectizer and CAF have very different roots. SObjectizer's principles grow from SCADA software where discarding unhandled messages was a normal and usual thing. That's why SObjectizer just ignores a message if there is no event-handler for it (in the current state and in all parent states). |
Many thanks for explaining this, I didn't know. |
Hi,
I have a very simple question: is it possible to make a subscription handler that will be called regardless of the message type?
Something like this:
What's the recommended practice here?
Thanks.
The text was updated successfully, but these errors were encountered: