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.

+1


source to share


1 answer


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.

+1


source







All Articles