Giter Site home page Giter Site logo

Comments (12)

eao197 avatar eao197 commented on May 17, 2024

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.

ilpropheta avatar ilpropheta commented on May 17, 2024

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.

eao197 avatar eao197 commented on May 17, 2024

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.

ilpropheta avatar ilpropheta commented on May 17, 2024

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.

eao197 avatar eao197 commented on May 17, 2024

Alternatively, what about using a variant?

Yes, you can use a variant as a message type, I don't expect problems here.

from sobjectizer.

ilpropheta avatar ilpropheta commented on May 17, 2024

Great, thanks for your ideas. I think I can close this one.

from sobjectizer.

eao197 avatar eao197 commented on May 17, 2024

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.

ilpropheta avatar ilpropheta commented on May 17, 2024

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.

eao197 avatar eao197 commented on May 17, 2024

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 type image_base.

This mechanism looks pretty implementable, but it just an idea now.

from sobjectizer.

ilpropheta avatar ilpropheta commented on May 17, 2024

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.

eao197 avatar eao197 commented on May 17, 2024

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.

ilpropheta avatar ilpropheta commented on May 17, 2024

Many thanks for explaining this, I didn't know.

from sobjectizer.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo 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.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.