Comments (12)
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?
from sobjectizer.
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:
- every "simple" block just works as-is, without any prior conversion
- when a new image type is added (possibly by the user), such blocks do not need to be updated, but only the "common image type" converter will be, rightly, revised
Let me know your thoughts.
Many thanks!
from sobjectizer.
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 image_base
type:
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 image_base
:
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 dynamic_cast
or via the usage of visitor-pattern.
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);
} );
}
from sobjectizer.
Thanks for the code. It confirms one of my ideas to handle this scenario!
The cons is that I have to wrap every image type by hand. Alternatively, what about using a variant?
from sobjectizer.
Alternatively, what about using a variant?
Yes, you can use a variant as a message type, I don't expect problems here.
from sobjectizer.
Great, thanks for your ideas. I think I can close this one.
from sobjectizer.
The cons is that I have to wrap every image type by hand.
Maybe this issue can be solved by a helper function template?
from sobjectizer.
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 sobjectizer.
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:
- on the first pass an event-handler for the message type (e.g.
image_vendor_A
) will be searched; - if the first pass failed, SObjectizer will fallback to the underlying type (
image_base
in that case) and will try to search an event-handler for a message of typeimage_base
.
This mechanism looks pretty implementable, but it just an idea now.
from sobjectizer.
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.
from sobjectizer.
It would be nice to have this!
IIRC, this is just the second real-world use-case reported where such functionality is applicable.
By the way, just as a side note, I am mildly biased by CAF because it provides a default handler concept.
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).
from sobjectizer.
Many thanks for explaining this, I didn't know.
from sobjectizer.
Related Issues (20)
- Asynchrony of register_coop HOT 4
- Documentation for message_holder_t has to be extended HOT 1
- A usage example for agent_t::limit_then_redirect method in API Reference HOT 1
- [Design] Your opinion on expressing agent intent HOT 2
- Deprecation of coop_t::deregister and coop_t::deregister_normally methods HOT 1
- [idea] An emergency stop of SOEnv on an exception in noexcept context HOT 1
- `so_evt_finish` not called until `so_evt_start` is running? HOT 2
- Should agent_t::so_drop_subscription* methods be marked as noexcept? HOT 1
- Should delivery filters be checked for noexcept-ness?
- bind_and_transform HOT 10
- so_5::details::make_message_instance_impl metafunction doesn't set message mutability flag properly HOT 1
- limit_then_transform for mutable messages HOT 1
- Allow `const auto &` as an argument for delivery filter in single/multi_sink_binding HOT 1
- Should there be agent_t::so_disp_binder() and agent_t::so_coop_default_disp_binder() methods? HOT 2
- [idea] Make so5extra's revocable timers the default implementation for timers in SObjectizer
- Another constructor for wrapped_env_t that waits completion of init-function HOT 1
- Use of message limits and state_t::time_limit
- Optional name for an agent? HOT 2
- New method `as_string_view` for so_5::stats::prefix_t and so_5::stats::suffix_t HOT 1
- SO_5_TYPE shouldn't be used for so_5::stats::messages::quantity
Recommend Projects
-
React
A declarative, efficient, and flexible JavaScript library for building user interfaces.
-
Vue.js
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
-
Typescript
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
-
TensorFlow
An Open Source Machine Learning Framework for Everyone
-
Django
The Web framework for perfectionists with deadlines.
-
Laravel
A PHP framework for web artisans
-
D3
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
-
Recommend Topics
-
javascript
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
-
web
Some thing interesting about web. New door for the world.
-
server
A server is a program made to process requests and deliver data to clients.
-
Machine learning
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
-
Visualization
Some thing interesting about visualization, use data art
-
Game
Some thing interesting about game, make everyone happy.
Recommend Org
-
Facebook
We are working to build community through open source technology. NB: members must have two-factor auth.
-
Microsoft
Open source projects and samples from Microsoft.
-
Google
Google ❤️ Open Source for everyone.
-
Alibaba
Alibaba Open Source for everyone
-
D3
Data-Driven Documents codes.
-
Tencent
China tencent open source team.
from sobjectizer.