Designing the Poll Event API
Let's say you were developing a C ++ windows library. It may or may not provide a callback API, but should provide a polling API to facilitate functional programming style.
What does the poll API look like?
Some parameters
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 in languages ββother than C ++ are ok if they provide interesting information.
No comments on encapsulation, please, yes, public fields should actually be accessible, I left that for clarity.
To quickly answer your question, I prefer the simplicity of "SDL style code". Mainly because your slightly more complex "state style" is destroying memory and buying you absolutely nothing (see below) and recursion in your tortured "Functional pseudo-C ++" style will overflow the stack in a matter of milliseconds.
"State style" . Your "typical yay" in the "State Style" code is a little unreasonable. You are still deciding which member to access based on switch
another member, so the code has all the same drawbacks as the SDL Style code - for any mistake you could make with SDL style code that leads to interpreting memory as the wrong type, you would make an equally bad mistake when accessing an uninitialized member with state-style code.
"Functional pseudo-C ++ style" . Now you get somewhere by inheriting different event types from the base event type. Obviously the silly recursion should become a loop, and there are a few small things to tidy up (I think your 3 methods named transform()
in UserWidget
want to be called handle()
; I assume you can solve the problem without boilerplate virtual methods using the Boost function .Function or similar). I think this approach has potential, although I prefer the simplicity of the SDL style.
But more fundamentally, I doubt the need for a poll interface. Is there a reason why pollEvent()
it cannot block? Be that as it may, all 3 code segments burn up CPU time, doing nothing 99.99% of the time.
source to share