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.
Related
I try again to explain better again what I would achieve.
I would like make a thing like this (inspired to Unity's UnityEvent):
Public "variables" declared in some classes:
GameEvent<> OnEnemySpawn = GameEvent<>();
GameEvent<string> OnPlayerSpawn = GameEvent<string>();
GameEvent<string, float> OnEnemyDie = GameEvent<string, float>();
Referral where some other classes subscribe their methods:
...
enemySpawner.OnEnemySpawn.Subscribe(IncreaseEnemyAliveCountByOne);
...
playerSpawner.OnPlayerSpawn.Subscribe(NewPlayerSpawned);
...
enemy.OnEnemyDie.Subscribe(IncreasePlayerScore);
...
// Subscribed methods declaration
void IncreaseEnemyAliceCountByOne() { ... }
void NewPlayerSpawned(string playerName) { ... }
void IncreasePlayerScore(string playerName, float scoreToAdd) { ... }
And then GameEvent class would be able to notify the event happens:
...
OnEnemySpawn.Notify();
...
OnPlayerSpawn.Notify(newPlayer.PlayerName);
...
OnEnemyDie.Notify(playerKiller.PlayerName, scoreOnKill);
...
Actually, I achieved the declaration and subscription part creating this class:
templace<class ... T>
class GameEvent
{
private:
std::vector<std::function<void(T...)>> _subscribers;
public:
void Subscribe(std::function<void(T...)> newSubscriber)
{
_subscribers.push_back(newSubscriber);
}
}
The thing that makes me crazy is how implement the Notify method. How should I know how many parameters I received and which types they have
void Notify(T...)
{
for (std::function<void(T...)> subscriber : _subscribers)
{
}
}
I hope now this is a valid question cause I'm losing my mind behind this
What is wrong with the obvious way?
void Notify(T... args)
{
// note: no need to write the type if it's quite long
// note: & means the std::function isn't copied
for (auto const& subscriber : _subscribers)
{
subscriber(args...);
}
}
class close_queue
{};
class dispatcher
{
queue* q;
bool chained;
dispatcher(dispatcher const&)=delete;
dispatcher& operator=(dispatcher const&)=delete;
template<
typename Dispatcher,
typename Msg,
typename Func>
friend class TemplateDispatcher;
void wait_and_dispatch()
{
for(;;)
{
auto msg=q->wait_and_pop();
dispatch(msg);
}
}
bool dispatch(
std::shared_ptr<message_base> const& msg)
{
if(dynamic_cast<wrapped_message<close_queue>*>(msg.get()))
{
throw close_queue();
}
return false;
}
public:
dispatcher(dispatcher&& other):
q(other.q),chained(other.chained)
{
other.chained=true;
}
explicit dispatcher(queue* q_):
q(q_),chained(false)
{}
template<typename Message,typename Func>
TemplateDispatcher<dispatcher,Message,Func>
handle(Func&& f)
{
return TemplateDispatcher<dispatcher,Message,Func>(
q,this,std::forward<Func>(f));
}
~dispatcher() noexcept(false)
{
if(!chained)
{
wait_and_dispatch();
}
}
};
class receiver
{
queue q;
public:
sender operator()()
{
return sender(&q);
}
dispatcher wait()
{
return dispatcher(&q);
}
};
template<typename PreviousDispatcher,typename Msg,typename Func>
class TemplateDispatcher
{
queue* q;
PreviousDispatcher* prev;
Func f;
bool chained;
TemplateDispatcher(TemplateDispatcher const&)=delete;
TemplateDispatcher& operator=(TemplateDispatcher const&)=delete;
template<typename Dispatcher,typename OtherMsg,typename OtherFunc>
friend class TemplateDispatcher;
void wait_and_dispatch()
{
for(;;)
{
auto msg=q->wait_and_pop();
if(dispatch(msg))
break;
}
}
bool dispatch(std::shared_ptr<message_base> const& msg)
{
if(wrapped_message<Msg>* wrapper=
dynamic_cast<wrapped_message<Msg>*>(msg.get()))
{
f(wrapper->contents);
return true;
}
else
{
return prev->dispatch(msg);
}
}
public:
TemplateDispatcher(TemplateDispatcher&& other):
q(other.q),prev(other.prev),f(std::move(other.f)),
chained(other.chained)
{
other.chained=true;
}
TemplateDispatcher(queue* q_,PreviousDispatcher* prev_,Func&& f_):
q(q_),prev(prev_),f(std::forward<Func>(f_)),chained(false)
{
prev_->chained=true;
}
template<typename OtherMsg,typename OtherFunc>
TemplateDispatcher<TemplateDispatcher,OtherMsg,OtherFunc>
handle(OtherFunc&& of)
{
return TemplateDispatcher<
TemplateDispatcher,OtherMsg,OtherFunc>(
q,this,std::forward<OtherFunc>(of));
}
~TemplateDispatcher() noexcept(false)
{
if(!chained)
{
wait_and_dispatch();
}
}
};
class bank_machine
{
messaging::receiver incoming;
public:
bank_machine():
void run()
{
try
{
for(;;)
{
incoming.wait()
.handle<verify_pin>(
[&](verify_pin const& msg)
{
}
)
.handle<withdraw>(
[&](withdraw const& msg)
{
}
)
.handle<get_balance>(
[&](get_balance const& msg)
{
msg.atm_queue.send(::balance(balance));
}
)
.handle<withdrawal_processed>(
[&](withdrawal_processed const& msg)
{
}
)
.handle<cancel_withdrawal>(
[&](cancel_withdrawal const& msg)
{
}
);
}
}
}
};
The code above is a snippet from
C++ Concurrency in Action.
and I was wondering if someone can explain, what looks like, chained template instantiation inside bank_machine::run()? Why is it that we can we have a long chain of handle<some_type>( ...).handle<some_type>( ...).handle<some_type>( ...) . If you could point me to some resources and also correct any missuses of nomenclature I would appreciate it.
Cheers!
Why is it that we can we have a long chain of handle<some_type>( ...).handle<some_type>( ...).handle<some_type>( ...)?
For the same reason that you can chain any operator, e.g.
a + b + c + ...
works, so long as a + b returns an object that can be used as the left-hand side of operator+ with c as the right hand side.
In your example
handle<some_type>(...)
must return an object that has a member access operator . that can be invoked on it, where the member itself can be a handle<some_other_type> that can then be invoked, and so on.
I studied this example code snippet as well and used it in some of my own projects so I wanted to understand it in depth. This my best bet:
Everytime you call
handle
on the temporary dispatcher-object constructed by
incoming.wait()
a temporary object of type TemplateDispatcher is created. The magic happens in the destructor:
the last object to be created will be destroyed first and this will trigger the call to
wait_and_dispatch()
This makes the current thread of execution which is executing this line of code wait for a message to arrive at the message queue (it will sleep, besides spurious wakeups, as the code for dequeueing messages involves condition_variables and associated mutexes). If a message arrives, the thread of execution will check if it is able to deal with the message type and else, if this is not the case, delegate the call to the previously chained TemplateDispatcher or dispatcher-object. When this call is resolved, the destruction of the temporary objects will conclude and due to the
for(;;)
the thread will continue to wait for incoming messages in the same manner, again, until a close_queue-message arrives at the queue and will trigger an exception thrown in the temporary dispatcher-object's code, exactly in:
dispatcher::dispatch()
The TemplateDispatcher-objects created by calls to
handle
will not deal with close_queue-message objects and will therefore delegate any objects of this type to their predecessor in the call chain (dispatcher-class should be the only class that can deal with close_queue-objects) and it will finally be delegated to the dispatcher-object which will then trigger the exception.
The delegation of message objects that are not handled by a certain TemplateDispatcher-objects (identified by the template parameters on instantation in the handle-calls) are delegated to the previous TemplateDispatcher-object in the method
dispatch
of class TemplateDispatcher. A dynamic cast is used to determine if the current TemplateDispatcher-object has to deal with the arrived message.
This question has been asked multiple times but mine is a slightly different case. Say I have a std::vector of observers which I notify when a certain event happens:
void SomeClass::doThing() {
// do things ...
// notify observers
for (auto* o : mObservers) {
o->thingHappened();
}
}
What if in the implementation of thingHappened the observer calls a method in SomeClass to remove itself from the observers? What are some of the best ways to handle this?
One possibility is to make a copy of mObservers before the for loop and use it instead, but the extra copy can be wasteful.
Another possibility is to delegate changes to the array to be run after the loop is finished, perhaps setting a lock (just a boolean) before the loop starts and while this lock is set, the methods that mutate the vector delegate themselves to be called after the loop is done when lock is set to false (could be done with a vector of lambdas... quite cumbersome).
If you have control over the signature of thingHappened(), you can change it to return a bool indicating whether it should be removed. Then, you can remove all the values which return true (or false; depends on the semantics you want).
Luckily for us, std::remove_if and std::partition are guaranteed to call the predicate exactly once per object in the range.
void SomeClass::doThing() {
// do things ...
// notify observers
auto newEnd = std::remove_if(mObservers.begin(), mObservers.end(), [](auto *o) {
return o->thingHappened();
});
// assuming mObservers is a vector
mObservers.erase(newEnd, mObservers.end());
}
One way to work around this is to change the data structure. With a std::list the removal of a element only invalidates iterators/references/pointers to that element. Since the rest of the list remains intact all we need to do is get an iterator to the next element before we process the current one. That would look like
for (auto it = the_list.begin(); it != the_list.end();)
{
auto next = std::next(it);
it->call_the_possibly_removing_function();
it = next;
}
What if in the implementation of thingHappened the observer calls a method in SomeClass to remove itself from the observers? What are some of the best ways to handle this?
The following method has worked for me in the past.
Note that your are going to iterate over the observers.
When a client requests to remove an observer to be removed, check whether you are in the middle of iterating over the observers. If you are, set it aside in another vector. If not, remove it from the observers.
After you are done iterating over the observers, remove all the observers that need to be removed.
Note that you are done iterating over the observers.
void SomeClass::removeObserver(Observer* o) {
if ( this->isIterating )
{
observersToRemove.push_back(o);
}
else
{
// Code for real removal of the observer
}
}
void SomeClass::doThing() {
this->isIterating = true;
for (auto* o : mObservers) {
o->thingHappened();
}
for ( auto* o : observersToRemove )
{
// Code for real removal of the observer
}
observersToRemove.clear();
this->isIterating = false;
}
R Sahu's answer provides a flexible technique for solving this problem. The one thing that concerns me about it is the introduction of several variables that you have to manage. However, it's totally possible to wrap the functionality in a utility class.
Here's a sketch of what you could do:
#include <functional>
#include <utility>
#include <vector>
// Note that this is not threadsafe
template <typename Type>
class MutableLock {
bool locked = false;
Type value;
// std::function gives us a more general action,
// but it does come at a cost; you might want to consider using
// other techniques.
std::vector<std::function<void(Type&)>> actions;
public:
class AutoLocker {
MutableLock& lock;
friend class MutableLock<Type>;
explicit AutoLocker(MutableLock& lock)
: lock{ lock }
{
}
public:
~AutoLocker()
{
lock.unlock();
}
};
MutableLock() = default;
// The [[nodiscard]] is a C++17 attribute that
// would help enforce using this function appropriately
[[nodiscard]] AutoLocker lock()
{
locked = true;
return AutoLocker{ *this };
}
void unlock()
{
for (auto const& action : actions) {
action(value);
}
actions.clear();
locked = false;
}
template <typename F>
void action(F&& f)
{
if (!locked) {
f(value);
} else {
actions.emplace_back(std::forward<F>(f));
}
}
// There needs to be some way to expose the value
// not under the lock (so that we can use it when
// we call `lock()`).
//
// Even if your `Type` is not a range, this would
// be fine, as member functions of a template class
// aren't instantiated unless you call them.
//
// However, you may want to expose other ways to
// access the value
auto begin() { return std::begin(value); }
auto end() { return std::end(value); }
auto begin() const { return std::begin(value); }
auto end() const { return std::end(value); }
};
Using it would look something like this:
#include <algorithm>
#include <iostream>
class Observer {
public:
virtual void thingHappened() = 0;
protected:
~Observer() = default;
};
class SomeClass {
MutableLock<std::vector<Observer*>> observers;
public:
void addObserver(Observer* observer)
{
observers.action([observer](auto& observers) {
observers.push_back(observer);
});
}
void remove(Observer const* observer)
{
observers.action([observer](auto& observers) {
observers.erase(std::remove(observers.begin(), observers.end(), observer), observers.end());
});
}
void doSomething()
{
auto lock = observers.lock();
for (auto* observer : observers) {
observer->thingHappened();
}
// when `lock` goes out of scope, we automatically unlock `observers` and
// apply any actions that were built up
}
};
class Observer1 : public Observer {
public:
SomeClass* thing;
void thingHappened() override
{
std::cout << "thing 1\n";
thing->remove(this);
}
};
int main()
{
SomeClass thing;
Observer1 obs;
obs.thing = &thing;
thing.addObserver(&obs);
thing.doSomething();
thing.doSomething();
}
On Coliru
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!
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.