views:

351

answers:

1

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.

+1  A: 

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.

j_random_hacker
**StateStyle** _is_ typesafe. pollInput returns complete objects. eg even for a mouseclick, the returned event has a correct keyboard-array. And yeah I was being silly with the recursion. Fooling around with Haskell right now - it optimizes tail recursion into a loop so no stack overflow there :)
I do like the FP idea of minimizing mutable state tho - hence "transform". Using a notifying system like you suggest ("handle") does solve the template problem as I no longer need the template, but forces me into imperative programming. Oh well. Guess I can't do haskell in C++ :)
and if i let go of that, then no need for a polling interface, no. IAC the thing is intended for game-like apps where I burn 100% CPU anyway.
Regarding type safety: my point is that type safety is not the end goal, correctness is, and while your "state style" approach is technically typesafe, it has the exact same weaknesses as the non-typesafe SDL style in this case.
j_random_hacker
My suggestion to s/transform/handle/g in UserWidget was simply because transform() calls the method "handle()", not the method "transform()". (Your original code would not compile for that reason.) It doesn't remove the need for templates -- as you said, you would need a base class for that.
j_random_hacker
Actually, you could define separate listener interfaces for each event type, and have UserWidget implement only the ones it needs. Avoids the "virtual template" issue and is cleaner than a single base class requiring implementation of all listeners.
j_random_hacker
I have now realized why the typesafety of state style is nothing to get excited about - i.e. why it goes contrary to correctness. Thanks for the sound advice, then.