c++ - 轮询事件API的设计

标签 c++ api events polling

假设您正在设计一个 C++ 窗口库。它可能提供也可能不提供回调 API,但需要提供轮询 API 以促进函数式编程风格。

轮询 API 会是什么样子?

一些选项

SDL风格

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

有趣的函数式伪 C++ 风格

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());
}

C++ 以外的其他语言的答案也可以,只要它们提供了有趣的见解。

请不要对封装发表评论 - 是的,公共(public)字段确实应该是访问器,为了清楚起见,我将其省略。

最佳答案

为了快速回答您的问题,我更喜欢“SDL 样式代码”的简单性。主要是因为你稍微复杂一点的“State Style”会浪费内存并且绝对不会给你带来任何好处(见下文),而你折磨的“Functional pseudo-C++”风格中的递归将在几毫秒内溢出堆栈。

“State Style”:“State Style”代码中的“typesafe yay”有点不合理。您仍在根据另一个成员的 switch 来决定访问哪个成员,因此该代码具有与“SDL 样式”代码相同的弱点——对于您可能会犯的任何错误导致将内存解释为错误类型的 SDL 样式代码,您会犯同样严重的错误,即使用 State 样式代码访问未初始化的成员。

“函数式伪 C++ 风格”:现在您有所收获,从基本事件类型继承不同的事件类型。显然愚蠢的递归需要变成一个循环,并且有一些小东西需要整理(我认为你在 UserWidget 中名为 transform() 的 3 个方法想要被调用handle(); 我猜你可以使用 Boost.Function 或类似的方法解决没有模板虚拟方法的问题。我认为这种方法有潜力,尽管我更喜欢 SDL 风格的简单性。

但更根本的是:我质疑是否需要一个轮询接口(interface)。 pollEvent() 不能阻塞有什么原因吗?目前,所有 3 个代码段都在消耗 CPU 时间,99.99% 的时间什么都不做。

关于c++ - 轮询事件API的设计,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/427678/

相关文章:

java - 等待物体完成移动,然后执行操作

c++ - 根据入参值调用不同的基类构造函数

java - 是否有访问 bugzilla 的 java api?

javascript - Dojo.Connect 事件未被调用 - 为什么?

c# - 这个 API 到底是什么,它在做什么?

node.js - 纯 NodeJS 和 GraphQL,无中间件

javascript - webkit TransitionEnd 事件监听器触发两次

c++ - 将变量和文本添加到字符串?

c++ - shared_ptr 现实生活中的用例

c++ - 如何重新绑定(bind) Boost.TypeErasure any<...> 对象