Replies: 4 comments 2 replies
-
|
Hi @eao197 ! In my opinion, the proposed approach is overly complicated. If users need to introduce their own It also introduces some readability risks, since the logic becomes split into two parts: the wrapper and the event handler. And what if the next developer sees the subscription but forgets to check wrap_event_handlers? BTW there is maybe a typo here: ctx + so_5::wrap_event_handlers(std::make_shared<my_agent>(...)) }I think you mean: ctx + so_5::wrap_event_handlers(std::make_shared<my_wrapper>(...)) } |
Beta Was this translation helpful? Give feedback.
-
What I meant is a bit different: the logic behind all event handlers would literally be split across different parts of the source code. You might say that message limits or delivery filters already do something similar, but I see those as different since they can prevent a message from being handled at all. Here, instead, we're spreading the handling logic across two functions, and the programmer has to mentally piece together the flow by jumping between them. This is not necessarily a problem, I am just thinking loudly. Don't get me wrong. I see the value of such a feature and your example is a good rationale. Since you asked, I am trying to share ideas. Let me also ask one thing. You said:
Just to confirm: since you handle this through context options, you can pass them from outside without changing the agent's code, right? For example: env.introduce_coop([&](so_5::coop_t& coop) {
coop.make_agent<Agent1>(); // will benefit from the feature
coop.make_agent<Agent2>(); // will benefit from the feature
coop.make_agent<Agent3>(); // will benefit from the feature
coop.make_agent<Agent4>();
coop.make_agent<Agent5>();
}This changes into: agent_context_t context{ env };
context = context + wrap_event_handlers(std::make_shared<my_wrapper>(...)) ;
// special agents
auto agent1 = make_unique<Agent1>(context);
auto agent2 = make_unique<Agent2>(context);
auto agent3 = make_unique<Agent3>(context);
auto coop = env.make_coop();
coop->add_agent(move(agent1));
coop->add_agent(move(agent2));
coop->add_agent(move(agent3));
// ordinary agents
coop->make_agent<Agent4>();
coop->make_agent<Agent5>();
env.register_coop(move(coop));Is my interpretation correct? Since we discussed something similar recently, I'm just checking that I've understood the pattern the right way :) |
Beta Was this translation helpful? Give feedback.
-
I agree.
I see here a decorator pattern coming to light. A sort of |
Beta Was this translation helpful? Give feedback.
-
|
The first draft of how nested wrappers can be supported. This is how description of class event_handler_wrapper_t;
using event_handler_wrapper_shptr_t =
std::shared_ptr<event_handler_wrapper_t>;
class event_handler_invoker_t
{
friend class event_handler_wrapper_t;
protected:
~event_handler_invoker_t() = default;
// NOTE: this method is protected and is available
// only for the friends of the invoker class.
virtual void
invoke() = 0;
};
class event_handler_wrapper_t
{
private:
event_handler_wrapper_shptr_t m_nested;
protected:
void
do_invoke(
const execution_demand_t & src_demand,
event_handler_invoker_t & invoker) /* const ??? */
{
if(m_nested)
m_nested->wrap_then_invoke(src_demand, invoker);
else
// NOTE: only event_handler_wrapper can do this call.
// Because of that all users wrappers have to use
// event_handler_wrapper_t::do_invoke() method instead
// of calling event_handler_invoker directly.
ivoker.invoke();
}
public:
event_handler_wrapper_t() = default;
virtual ~event_handler_wrapper_t() = default;
void
set_nested(event_handler_wrapper_shptr_t nested)
{
if(m_nested)
throw exception_t{ ... };
m_nested = std::move(nested);
}
virtual void
wrap_then_invoke(
const execution_demand_t & src_demand,
event_handler_invoker_t & invoker) = 0;
};The idea is that when a wrapper is added to the agent context via: ctx + wrap_event_handler(wrapper_B)then the agent context checks the presence of the already specified wrapper. If there is another wrapper_A that was specified earlier then the agent context:
With such an approach several wrappers can be added to the agent context, but they will be chained together via The implementation of class my_wrapper final : public so_5::event_handler_wrapper_t
{
public:
void
wrap_then_invoke(
const so_5::execution_demand_t & src_demand,
so_5::event_handler_invoker_t & invoker) override
{
special_context_initializer special_context{...};
this->do_invoke(src_demand, invoker);
special_context.finalize();
}
}; |
Beta Was this translation helpful? Give feedback.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
-
I've encountered a case where every agent's event handlers have to be wrapped with special prologue and epilogue. I mean that every event handler should look like that:
There are, of course, several ways to do it.
The obvious one is to use a helper method with a lambda:
I don't like that way because it's boring and intrusive to agents that implement business logic.
Another way is to make own dispatcher that will do something like that:
This approach allows to write "clean" agents, but it also has own drawbacks:
special_context.finialize()throws then we have to catch and handle an exception and this handling won't be related to SObjectizer's exception_reaction. There is a big difference: iffinalizethrows from the event handler then the exception will be considered as an agent's fault and handled in an ordinary way (invocation ofso_exception_reactionand so on). But if an exception happens outside of the event handler, then it won't be related to the agent and it's not good from my point of view.This leads to an idea about a special wrapper that could be installed by a user and will be used for invocation of every agent's event handler (for evt_start/evt_finish too).
It may look like that (please note that this is just a very, very first draft and may change dramatically over time):
When SObjectizer sees that a wrapper is set for the agent then SObjectizer changes a way of calling event handlers. For example, the current implementation of agent_t::process_message could be changed this way:
As for me this feature looks interesting, but I have some doubts. And I wondered if someone had the necessity of having something similar. It will be very useful to know such examples (if they are present).
PS. It seems that this feature could be used for implementation of coroutines support, but maybe it's not the right way to go for the case of coroutines.
Beta Was this translation helpful? Give feedback.
All reactions