Say you were designing a C++ windowing library. It may or may not provide a callback API, but needs to provide a polling API to facilitate a functional style of programming.
What would the polling API look like?
Some options
SDL style
struct Event {
enum { MousePress, KeyPress } type;
union {
struct { Point pos; MouseButton b; } mousePress;
struct { Modifiers mods; char key; } keyPress;
};
};
void userCode() {
for(;;) {
Event e; if(pollEvent(&e)) {
switch(e.type) {
case MousePress: cout<<event.mousePress.pos.x; break; // not typesafe
case KeyPress: cout<<event.keyPress.key; break;
}
}
}
}
State style
struct Input {
enum { Mouse, Keyboard, Nothing } whatChanged;
MouseButtonsBitfield pressedButtons;
bool keysPressed[keyCount];
};
void userCode() {
for(;;) {
Input in = pollInput();
switch(in.whatChanged) {
// typesafe yay
case Mouse: cout << "is LMB pressed? " << bool(in.pressedButtons&LeftButton); break;
case Keyboard: cout << "is A pressed? " << in.keysPressed['A']; break;
}
}
}
Fun functional pseudo-C++ style
struct Event {
// transforms listener by notifying it of event,
// returns transormed listener. nondestructive.
template<class Listener> // sadly invalid, templates can't be virtual.
// a solution is to make Listener the base
// of a hierarchy and make Listener::handle virtual
// but then we're forced to use imperative style
virtual Listener transform(Listener const&) =0;
};
struct MousePress : Event { // yay we're extensible via inheritance
template<class Listener>
virtual Listener transform(Listener const& listener) {
return listener.handle(*this); // calls the MousePress overload
}
Point pos; MouseButton b;
};
struct KeyPress : Event {
template<class Listener>
virtual Listener transform(Listener const& listener) {
return listener.handle(*this); // calls the KeyPress overload
}
Modifiers mods; char key;
};
struct NoEvent : Event {
template<class Listener>
virtual Listener transform(Listener const& listener) {
return listener.handle(*this);
}
};
struct UserWidget {
UserWidget handle(NoEvent) {
return UserWidget();
}
UserWidget handle(MousePress p) {
return (UserWidget) { string("pressed at")+lex_cast<string>(p.pos)) };
}
UserWidget handle(KeyPress k) {
return (UserWidget) { string("pressed key=")+lex_cast<string>(k.key)) };
}
string pendingOutput;
};
void userTick(UserWidget const& w) {
cout<<w.pendingOutput;
userTick(pollEvent().transform(w));
}
void userCode() {
userTick(UserWidget());
}
Answers for other languages than C++ are OK, if they provide interesting insight.
No comments on encapsulation please - yes public fields should really be accessors, i left that out for clarity.
To answer your question quickly, I prefer the simplicity of the "SDL-style code". Mainly because your slightly more complicated "State Style" wastes memory and buys you absolutely nothing (see below), and the recursion in your tortured "Functional pseudo-C++" style will overflow the stack within a few milliseconds.
"State Style": Your "typesafe yay" in the "State Style" code is a bit unwarranted. You are still deciding which member to access based on a switch on another member, so the code has all the same weaknesses that the "SDL Style" code has -- for any mistake that you could make with the SDL-style code that leads to interpreting memory as the wrong type, you would make the equally bad mistake of accessing an uninitialised member with the State-style code.
"Functional pseudo-C++ style": Now you're getting somewhere, inheriting different event types from a base event type. Obviously the silly recursion needs to become a loop, and there are a few little things to tidy up (I think your 3 methods named transform() in UserWidget want to be called handle(); I'm guessing that you can resolve the problem of no template virtual methods using Boost.Function or similar). I think this approach has potential, though I prefer the simplicity of SDL style.
But more fundamentally: I question the need for a polling interface. Is there a reason why pollEvent() cannot block? As it stands, all 3 code segments are burning CPU time doing nothing 99.99% of the time.
Related
I have problems finding the right place for an actor and a timer used in a state machine.
I found some inspiration from this site about the state pattern:
State Design Pattern in Modern C++ and created a small example:
Simple door state machine
There might be more transitions possible but I kept it short and simple.
class Door
{
void open() {}
void close() {}
};
Events:
class EventOpenDoor
{
public:
OpenDoor(Door* door) : m_pDoor(door) {}
Door* m_pDoor;
};
class EventOpenDoorTemporary
{
public:
EventOpenDoorTemporary(Door* door) : m_pDoor(door) {}
Door* m_pDoor;
};
class EventOpenDoorTimeout
{
public:
EventOpenDoorTimeout(Door* door) : m_pDoor(door) {}
Door* m_pDoor;
};
class EventCloseDoor
{
public:
EventCloseDoor(Door* door) : m_pDoor(door) {}
Door* m_pDoor;
};
using Event = std::variant<EventOpenDoor,
EventOpenDoorTemporary,
EventOpenDoorTimeout,
EventCloseDoor>;
States:
class StateClosed {};
class StateOpen {};
class StateTemporaryOpen {};
using State = std::variant<StateClosed,
StateOpen,
StateTemporaryOpen>;
Transitions (not complete):
struct Transitions {
std::optional<State> operator()(StateClosed &s, const EventOpenDoor &e) {
if (e.m_pDoor)
{
e.m_pDoor->open();
}
auto newState = StateOpen{};
return newState;
}
std::optional<State> operator()(StateClosed &s, const EventOpenDoorTemporary &e) {
if (e.m_pDoor)
{
e.m_pDoor->open();
**// start timer here?**
}
auto newState = StateOpen{};
return newState;
}
std::optional<State> operator()(StateTemporaryOpen &s, const EventOpenDoorTimeout &e) {
if (e.m_pDoor)
{
e.m_pDoor->close();
}
auto newState = StateOpen{};
return newState;
}
std::optional<State> operator()(StateTemporaryOpen &s, const EventOpenDoor &e) {
if (e.m_pDoor)
{
e.m_pDoor->open();
**// stop active timer here?**
}
auto newState = StateOpen{};
return newState;
}
/* --- default ---------------- */
template <typename State_t, typename Event_t>
std::optional<State> operator()(State_t &s, const Event_t &e) const {
// "Unknown transition!";
return std::nullopt;
}
};
Door controller:
template <typename StateVariant, typename EventVariant, typename Transitions>
class DoorController {
StateVariant m_curr_state;
void dispatch(const EventVariant &Event)
{
std::optional<StateVariant> new_state = visit(Transitions{this}, m_curr_state, Event);
if (new_state)
{
m_curr_state = *move(new_state);
}
}
public:
template <typename... Events>
void handle(Events... e)
{ (dispatch(e), ...); }
void setState(StateVariant s)
{
m_curr_state = s;
}
};
The events can be triggered by a client which holds an instance to the "DoorController"
door_controller->handle(EventOpenDoor{door*});
In the events I pass a pointer to the door itself so it's available in the transitions. The door is operated within the transitons only.
I have problems now with modelling the 20s timeout/timer. Where to have such a timer, which triggers the transition to close the door?
Having a timer within the door instance means, I have a circular dependency, because in case of a timeout it needs to call "handle()" of the "door_controller".
I can break the circular dependency with a forward declarations.
But is there a better solution?
Maybe I have modelled it not well. I'm open to improving suggetions.
Thanks a lot!
This isn't going to be the best answer, but I have more questions than answers.
Some of your choices seem odd. I presume there's a complicated reason why you're storing state based on a variant rather than using an enum class State{}, for instance.
I also get nervous when I see raw pointers in modern C++. I'd feel a whole lot better with smart pointers.
When I've done state machines, the events I can handle always subclass from a common Event class -- or I might even just use a single class and give it as many distinct data fields are required for the things that I need to handle. It's a little odd that you use unrelated classes and depend on a dispatch method. Does that even work? Aren't you pushing objects onto an event queue? How do you end up calling that dispatch method with random objects?
You don't show your event loop, but maybe you have a state machine without an event loop. Is it a state machine then? Or maybe you didn't show it. Maybe you can have a state machine without an event loop, but I thought the two concepts were tied together.
I am curious as to how I can make the following code actually work. I currently have an event system in place but it is using the observer pattern. I want my window events to work as such :
window win("Engine");
win.on_event(KEY_PRESSED, []{
// key pressed
});
win.on_event(KEY_RELEASED, []{
// key released
});
win.on_event(MOUSE_CLICKED, []{
// mouse clicked
});
The problem is I don't know where to start. I also want to be able to get specific information on an event, for example the (x, y) coords of where the mouse was clicked in the window. Any input would be greatly appreciated, even if it's just a general idea of what I need to do. Thank you.
I think you are going along the right lines so far, but an event system is not a simple thing to build. There is a lot to think about in an events system. I Am assuming you are doing this as a learning exercise so lets not use any libraries. However I would reccommend looking at a few existing event systems to get some inspiration, you might find features you didn't even know you wanted until you see them. These are the steps / thought exercises i suggest for you to take.
Define your events:
It sounds like you have done this, but do consider if you want events to be unrelated types or part of an inheritance hierarchy, there are pros and cons to each approach.
Define your publishing mechanism:
You said you have observer pattern and thats a good first choice, but consider if it's everything you want. Maybe also consider these things; Should one object only deliver directly to it's indended destination or maybe there is some kind of indirection or delegation involved like a message bus or central event queue. Good news is that designing to an interface like observer pattern suggests makes this easy to change later.
Define your event model in time space:
do you need or want immediate, deferred or asynchronous processing of the events? do you need to store them? What about threadding? is this a single threaded system? If it is then thats an additional layer of complexity to consdier.
Define receipt of messages:
Kind of linked to 2, Consider object A wants to publish an event but how does it know whom to deliver it to, it might be looked up in some kind of registry it knows about, it might be the argument to a function call, it could be something else you can imagine. Traditionally i've seen this done in two ways one where you can register an event handler with some object who will receive the event then invoke your handler. This mechanism requires you to consider if handlers can be unregistered as well, and if so how. The other is the "function" argument, where a function might be a class member function, or a strategy object or similar.
I think at that stage you will have asked most of the hard questions
heres a quick example of a single threaded event system with events based in a hierarchy: (NOTE, this is NOT following best practices and its not production quality, its the absolute bare minimum to demonstrate the concept, [using c++17 standard for my convienance])
#include <iostream>
#include <functional>
#include <string>
#include <string_view>
#include <vector>
// Lets define a base event type
struct event_base {
explicit event_base(std::string const& event_type) : type{event_type} {}
std::string type;
};
// Now a more specific event type
struct name_changed : public event_base {
constexpr static auto event_type = "name_changed_event";
name_changed(std::string old_name, std::string new_name) :
event_base(event_type),
old_name{std::move(old_name)},
new_name{std::move(new_name)}
{}
std::string old_name;
std::string new_name;
};
// Next our observer interface
using event_handler = std::function<void(event_base const&)>;
/* could do these the traditional way with a class interface but i
* prefer a std::function when there only one method to implement
* and it means i dont have to bring out unique_ptrs or anything
* for this example
*/
// And a structure to associate an observer with an event type
struct registered_handler {
std::string event_type;
event_handler handler;
};
// A simple example observable person
class person_info {
public:
person_info(int age, std::string name) :
m_age{age},
m_name{std::move(name)}
{}
void add_handler(std::string const& event_type, event_handler const& handler) {
m_handlers.push_back({event_type, handler});
}
void set_name(std::string new_name) {
// check for change
if(new_name == m_name) {
return;
}
// build event
name_changed const event {m_name, new_name};
// make change
std::swap(m_name, new_name);
// publish events
if(auto *const handler = get_handler_for_event_type(event.type); handler != nullptr) {
handler->handler(event);
}
}
void set_age(int new_age) {
// same thing here but using age and age change event
}
private:
registered_handler* get_handler_for_event_type(std::string const& event_type) {
auto const& existing_handler = std::find_if(std::begin(m_handlers), std::end(m_handlers), [&](auto const& h){
return h.event_type == event_type;
});
if(existing_handler != std::end(m_handlers)) {
return &(*existing_handler);
} else {
return nullptr;
}
}
int m_age;
std::string m_name;
std::vector<registered_handler> m_handlers;
};
// And a main to exercise it
int main() {
person_info my_guy{25, "my guy"};
my_guy.add_handler(name_changed::event_type, [](event_base const& event) {
auto const& name_change_event = reinterpret_cast<name_changed const&>(event);
std::cout << "My guy changed his name from: " << name_change_event.old_name << " to: " << name_change_event.new_name << "\n";
});
my_guy.set_name("someone else");
}
Unfortunately thats a fair amount of boiler plate for a simple event system, but once you have it and have tweaked it to be what you want then you can just keep re-using it.
If you run this example output should be quite simply:
My guy changed his name from: my guy to: someone else
Some time ago I've had similar problem to yours.
For my window I decided to inherit from entt::emitter
class Win32Window : public entt::emitter<Win32Window>
then inside WndProc
LRESULT Win32Window::_wndProc(HWND, UINT, WPARAM wParam, LPARAM lParam) {
switch (uMsg) {
case WM_MOUSEMOVE:
if (!empty<MouseMoveEvent>()) {
publish<MouseMoveEvent>(GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam));
return 0;
}
break;
// other messages ...
}
}
And the final result looks like this:
window.on<ResizeWindowEvent>([](const auto &evt, auto &) {
ImGui::GetIO().DisplaySize = glm::vec2{evt.width, evt.height};
});
window.on<MouseMoveEvent>([](const auto &evt, auto &) {
ImGui::GetIO().MousePos = glm::vec2{evt.x, evt.y};
});
Ofcourse you gonna need some event types:
struct MouseMoveEvent {
int32_t x, y;
};
struct CloseWindowEvent {};
// and so on ...
This is not a complete example, it is a simple snippet to give you a generic idea on what you're trying to do.
#include <iostream>
#include <functional>
#include <thread>
#include <Windows.h>
#include <map>
enum class MOUSE_EVENT_TYPE {
MOUSE_CLICKED,
MOUSE_MOVE
};
struct window
{
std::thread *th;
bool loop{ true };
std::map < MOUSE_EVENT_TYPE, std::function<void()>> func;
window()
{
th = new std::thread([this]() {
while (loop)
{
if (func[MOUSE_EVENT_TYPE::MOUSE_MOVE] && GetCursorPos())
{
func[MOUSE_EVENT_TYPE::MOUSE_MOVE](p);
}
if (func[MOUSE_EVENT_TYPE::MOUSE_CLICKED] && GetAsyncKeyState(VK_LBUTTON))
{
func[MOUSE_EVENT_TYPE::MOUSE_CLICKED]();
}
}
});
}
void on_event(MOUSE_EVENT_TYPE event, std::function<void(POINT)> fn)
{
if (!func[event])
{
func[event] = fn;
}
}
};
int main()
{
window win;
win.on_event(MOUSE_EVENT_TYPE::MOUSE_MOVE, []() {
std::cout << "mouse moved" << std::endl;
});
win.on_event(MOUSE_EVENT_TYPE::MOUSE_CLICKED, []() {
std::cout << "mouse clicked" << std::endl;
});
win.th->join();
}
I've written the most basic Event System I can think of. I come from a javascript background so I followed the On, Off, Emit syntax. The intention is to be able to create an EventSystem that can Emit any type of derived Event object and have the appropriate handlers called.
Please note, I am stuck in C++98 for reasons
So far my best idea is to have a simple Event object and a typedef for each Event type to handle it.
class Event {};
class AlarmEvent : Event {};
class ErrorEvent : Event {};
typedef void (*EventHandler)(Event event);
typedef void (*AlarmEventHandler)(AlarmEvent event);
typedef void (*ErrorEventHandler)(ErrorEvent event);
My issue is I want my modules to be able to attach as easy as possible.
int main()
{
Module module;
EventSystem es;
Event shutdown_event("shutdown");
AlarmEvent alarm_event("alarm", "Oh crap");
es.On("shutdown", module.OnEvent);
es.On("shutdown", module.OnEvent);
es.On("alarm", module.OnAlarmEvent);
es.Emit(shutdown_event);
es.Emit(alarm_event);
}
But looking at the EventSystem
class EventSystem {
public:
void On(std::string id, EventHandler handler);
void Emit(Event event);
void GetEventHandlers(std::string id, std::vector<EventHandler> *&handlers);
std::map<std::string, std::vector<EventHandler> > events;
};
I'd need an On, GetEventHandlers, and events property for every event type. This would quickly become terrible. Is there a better path where I can use a template to allow EventSystem to stay as simple as possible?
C++98 is old, older than variadic templates. The following emulates variadic templates with linked lists, which is very much suboptimal, but it should work.
// linked lists for "variadic" templates
struct Nil { };
template<typename X, typename XS>
struct Cons { };
// utility type
struct BlackHole {
template<typename T>
BlackHole(const T&) { }
};
// anything can be converted to a BlackHole implicitly, but it's a "worse"
// conversion than being converted to a base class
// I would template your event system over every event type
// this implementation only works properly if more derived events appear before their bases
template<typename Events> // e.g. Events = Cons<AlarmEvent, Cons<ErrorEvent, Cons<Event, Nil>>>
class EventSystem;
template<>
class EventSystem<Nil> {
protected:
// see below for Emit/EmitEmitted thing
// usage of BlackHole means that e.g. if calling with AlarmEvent
// and only overloads for Event and BlackHole are visible
// then the former will be chosen, since the latter conversion is worse
// can't just say template<typename T> EmitEmitted(T const&) { }
void EmitEmitted(BlackHole) { }
public:
// these overloads exist so the using declarations ahead don't fail
// for maximum type-safety, create a private type and
// make it an argument of each, so they can never be called
// using Emit/EmitEmitted creates type safety; again, see below
void Emit() { }
// On has easy type safety: you just can't call it for an unknown type
void On() { }
// GetEventHandlers doesn't really make sense anyway
// I don't think you need it, you can't have a vector of mixed handlers
// so why bother?
};
template<typename X, typename XS>
class EventSystem<Cons<X, XS> > : public EventSystem<XS> {
std::vector<void (*)(X)> handlers;
protected:
// "forward" all the EmitEmitted overloads made for XS
using EventSystem<XS>::EmitEmitted;
// overload for the specific case of an X
void EmitEmitted(X x) {
// fire all of the X-specific handlers
for(typename std::vector<void (*)(X)>::iterator i = handlers.begin(); i != handlers.end(); ++i) {
(*i)(x);
}
// call the rest of the handlers
EventSystem<XS>::EmitEmitted(x);
}
public:
// more "forwarding"
using EventSystem<XS>::Emit;
void Emit(X x) {
return EmitEmitted(x);
}
// suppose you have an EventSystem<Cons<std::string, Nil> >
// if you Emit an int, say, then you want this to fail
// thus the overload of Emit in EventSystem<Nil> should not be
// a catch-all or anything
// however, if you emit a std::string, then you need to recursively
// emit from EventSystem<Nil>, to handle any handlers for superclasses
// now you don't want it to explode
// solution? two functions
// Emit is the public entry point, and fails on unknown types
// EmitEmitted is named so because, once it's called, the type
// is known to be known, and will/has been emitted by at least one layer
// it no-ops once the base case is reached
// it is protected, and it is where the actual logic is
// easy now, right?
using EventSystem<XS>::On;
void On(void (*handler)(X)) {
handlers.push_back(handler);
}
};
Example usage:
struct Event {
std::string message;
Event(std::string message) : message(message) { }
};
void HandleEvent(Event e) {
std::cerr << e.message << "\n";
}
class AlarmEvent : public Event {
int hour;
int minute;
static std::string BuildMessage(int hour, int minute) {
std::stringstream builder;
builder << "Alarm: " << std::setfill('0');
builder << std::setw(2) << hour << ":";
builder << std::setw(2) << minute;
return builder.str();
}
friend void HandleAlarm(AlarmEvent);
public:
AlarmEvent(int hour, int minute) : Event(BuildMessage(hour, minute)), hour(hour), minute(minute) { }
};
void HandleAlarm(AlarmEvent a) {
// please ignore the fact that this is very stupid
if((a.hour + (a.minute / 60)) % 24 < 12) std::cerr << "AM Alarm\n";
else std::cerr << "PM Alarm\n";
}
struct ErrorEvent : Event {
ErrorEvent(std::string message) : Event(message) { }
};
void HandleError(ErrorEvent) {
static int count = 1;
std::cerr << "Error " << count++ << "\n";
}
int main() {
EventSystem<Cons<AlarmEvent, Cons<ErrorEvent, Cons<Event, Nil> > > > system;
// all handled by overload resolution
// no need to say what type you're dealing with
system.On(HandleEvent);
system.On(HandleAlarm);
system.On(HandleError);
// doesn't work
// system.On(std::exit)
system.Emit(ErrorEvent("Bad things"));
system.Emit(AlarmEvent(2, 30));
system.Emit(Event("Something happened"));
system.Emit(ErrorEvent("More bad things"));
system.Emit(AlarmEvent(11, 67));
// doesn't work
// system.Emit(5);
}
Not sure that all the example code is C++98, but that doesn't matter. It appears to work nicely. Also, there's a lot of copying going on here. It may be advisable to change handlers from void (*)(T) (which necessitates a copy) to void (*)(T&) or void (*)(T const&).
If your functions took references or pointers, then you would be able to pass polymorphic child types into them. So then you only need one type of function ptr.
typedef void (*EventHandler)(Event& event);
or
typedef void (*EventHandler)(Event* event);
I have a bit of a design problem:
I have a class describing a Robot; It can move to different directions, move a camera to different views etc. It looks something like this:
class Robot {
private:
...
public:
void move_right();
void move_left();
void switch_camera()
void raise_camera()
}
I want to add another method which performs a series of events. Thing is, I need able to abort the events midway.
I do want to clarify that the robot is running on a micro controller and not on a standard OS - so I can't really send a signal to the process or anything.
My first idea was to store the event functions in an array and iterate over it:
#typedef void(robo_event *)(void)
robo_event next_event;
robo_event *event_sequence;
Robot() {
this->next_event = nullptr;
}
void perform_event_series() {
for(this->next_event = *event_sequence; this->next_event != nullptr; this->next_event+=sizeof(robo_event)) {
this->next_event();
}
}
void abort_event_series() {
this->next_event = nullptr;
}
Thing is, the c++ standard forbids storing addresses of member functions, so this is starting to get awkward. I can make the functions static, but I do need to use them quite frequently and that would still be awkward. I want to be able to change to event sequence without too much work if changes are yet to come, so I thought that saving those on some sort of array / vector would be the best.
Any help with c++ member function syntax / better ideas on how to approach this problem would be much appreciated.
Thing is, the c++ standard forbids storing addresses of member functions
C++ most certainly allows you to store pointers to member functions (and variables), but the syntax is a bit different to accommodate the this pointer type, virtual functions, inheritance, etc.
class Example
{
public:
double foo(int x) { return x * 1.5; }
};
int main() {
double (Example::* member_function_ptr)(int);
member_function_ptr = &Example::foo;
Example example;
std::cout << (example.*member_function_ptr)(2) << std::endl;
}
If all your functions are for the same class, same return type, same arguments, etc. then you can make a table of them easy enough.
Storing pointers to member functions is perfectly allowable in c++:
#include <vector>
class Robot {
private:
public:
void move_right();
void move_left();
void switch_camera();
void raise_camera();
};
struct Action
{
Action(void (Robot::*what)(void))
: what(what)
{}
void perform(Robot& who) const
{
(who.*what)();
}
void (Robot::*what)(void);
};
bool should_abort();
void perform_actions(Robot& who, std::vector<Action> const& actions)
{
for (auto&& action : actions)
{
if (should_abort()) break;
action.perform(who);
}
}
int main()
{
std::vector<Action> actions {
&Robot::move_right,
&Robot::raise_camera,
&Robot::switch_camera,
&Robot::move_left
};
Robot r;
perform_actions(r, actions);
}
Pointers to functions are of different types to pointers to members.
You need void(Robot::*)(void) not void(*)(void).
class Robot {
private:
typedef void(Robot::*robot_event)(void)
robo_event next_event;
robo_event *event_sequence;
Robot() {
next_event = nullptr;
}
void perform_event_series() {
for(next_event = *event_sequence; next_event != nullptr; ++next_event) {
(this->*next_event)();
}
}
void abort_event_series() {
next_event = nullptr;
}
public:
void move_right();
void move_left();
void switch_camera()
void raise_camera()
}
I am trying to apply the Composite pattern, so I need to create a Leaf class and a Composite class, both inheriting from the same Component class. In order for any of my Components to perform their duty they need to ask help from a single Helper object. We have the following
struct Helper {
void provide_help();
};
struct Component {
Component(Helper* helper)
: m_helper(helper) {
}
virtual void operation() = 0;
// the call_for_help function will be used by subclasses of Component to implement Component::operation()
void call_for_help() {
m_helper->provide_help();
}
private:
Helper* m_helper;
};
And here are two different Leaf subclasses:
struct Leaf1
: Component {
Leaf1(Helper* helper)
: Component(helper) {
}
void operation() override {
call_for_help();
operation1();
}
void operation1();
};
struct Leaf2
: Component {
Leaf2(Helper* helper)
: Component(helper) {
}
void operation() override {
call_for_help();
operation2();
}
void operation2();
};
So far, so good. Now the Composite class is giving me grief. The typical implementation is as follows
struct Composite
: Component {
Composite(Helper* helper)
: Component(helper) {
}
void operation() override {
for (auto el : m_children) el->operation();
}
private:
std::vector<Component*> m_children;
};
which by going through the m_children one by one and calling operation on each essentially calls the helper function multiple times, even though one call is enough for all children. Ideally, if the m_children consisted, say, of a Leaf1 and a Leaf2, I would like somehow the Composite operation to call the helper function only once and then call in succession Leaf1::operation1() and then Leaf2::operation2(). Is there any way to achieve what I need? Alternative designs are welcome. I hope my question makes sense. Thanks in advance!
You want a polymorphic operation but you are adding more responability to the method (calling the helper). It's better to separate these two things.
struct Component {
void call_operation(){
call_for_help();
operation();
}
virtual void operation() = 0;
void call_for_help();
};
Remove the call_for_help() from leaf::operation() (making operation1, operation2 redundant, polymorphism) and the rest should work fine.
You can even hide operation() from your public interface, you'll need friendship with your Composite in that case.
As it could happen at any level, one approach could be to handle this at the level of the helper.
A sketch of the approach would be:
class Helper {
bool composite_help = false;
bool help_provided;
public:
void provide_help() {
if ((composite_help && !help_provided) || !composite_help) {
//TO DO: provide help
help_provided = true;
}
}
void start_composite_help() {
composite_help = true;
help_provided = false;
}
void end_composite_help() {
composite_help = false;
}
};
The principle is that the call for help performed by individual components works as before. But when the composite calls for help, you take preacutions to make sure that the call is performed only once:
void operation() override {
m_helper->start_composite_help();
for (auto el : m_children) el->operation();
m_helper->start_composite_help();
}
As said, this is only a sketch: the code provided as such will not work as soon as you have several levels of composites. So this needs to be improved:
instead of a bool composite_help you'd need a counter, which gets incremented when entering a composite operation and decremented when you exit it. In this case, the counter would go back to 0 (re-enabling help) only when the last level of composte has finished its job.
may be the helper performs different operations to provide help. So you could also imagine to have a "transaction id" that uniquely identifies a group of related operations, and you manage the counter not for the helper overall, in a map of active transactions.
finally, the start/end is not so nice. A RAII helper to the helper could make the whole setup more robust (for example when an exception breaks the normal execution flow.)
I think this problem would be better solved with a combination of Composite and Mediator.
Heads up! I'll show you a different version of the mediator pattern, which is not the same as the canonical version.
It's not of the business of your composite structure to know if a helper was called or not. You'd better do this using some kind of event handler.
Since you have only one helper, you could try like this:
class Helper {
public:
void callHelper() { std::cout << "Helper called" << std::endl; }
};
class Mediator {
private:
std::map<std::string, std::vector<Helper>> subscribers;
int updateLimit = -1;
int currentUpdateCount = 0;
void resetUpdateCount() {
currentUpdateCount = 0;
}
public:
Mediator(){}
void subscribe(std::string evt, Helper helper) {
subscribers[evt].push_back(helper);
}
void update(std::string evt) {
for (auto& h: subscribers[evt]) {
h.callHelper();
}
}
void setUpdateLimit(int i) {
updateLimit = i;
resetUpdateCount();
}
void removeUpdateLimit() {
updateLimit = -1;
resetUpdateCount();
}
int getUpdateLimit() {
return updateLimit;
}
void updateLimited(std::string evt) {
if (updateLimit < 0 || currentUpdateCount < updateLimit) {
update(evt);
currentUpdateCount++;
}
}
};
int main(int argc, const char *argv[])
{
Mediator m;
Helper h1, h2;
m.subscribe("bar", h1);
m.setUpdateLimit(1);
// Will be called only once
m.updateLimited("bar");
m.updateLimited("bar");
m.updateLimited("bar");
m.removeUpdateLimit();
return 0;
}
Using it:
Mediator m;
Helper h1, h2;
m.subscribe("bar", h1);
m.setUpdateLimit(1);
// Will be called only once
m.updateLimited("bar");
m.updateLimited("bar");
m.updateLimited("bar");
m.removeUpdateLimit();
So, here is what you do to integrate this to you composite structure. Remove the helper from you nodes, add the Mediator to the base class:
struct Component {
Component(Mediator& mediator)
: m_helper(mediator) {
}
virtual void operation() = 0;
// the call_for_help function will be used by subclasses of Component to implement Component::operation()
void notify() {
m_mediator->updateFiltered(Component::updateEventName);
}
static std::string updateEventName;
private:
Mediator& m_mediator;
};
std::string Component::updateEventName = "update.composite";
struct Leaf1
: Component {
Leaf1(Helper* helper)
: Component(helper) {
}
void operation() override {
notify();
operation1();
}
void operation1();
};
Using it:
Mediator m;
Helper h;
Composite c(m);
Leaf1 l1(m), l2(m);
c.add(l1);
c.add(l2);
m.subscribe(Component::updateEventName, h);
m.setUpdateLimit(1);
// Will be called only once, even if it has childrens
c.update();
m.removeUpdateLimit();
IMPORTANT: This solution is suboptimal, it has some issues, like you having to pass a mediator instance to every node constructor, but it's just a raw idea for you to work on.
Hope it helps!