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
switchon 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()inUserWidgetwant to be calledhandle(); 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.