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?
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;
}
}
}
}
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;
}
}
}
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.