代码之家  ›  专栏  ›  技术社区  ›  Iraimbilanja

一种轮询事件API的设计

  •  1
  • Iraimbilanja  · 技术社区  · 17 年前

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

    轮询API是什么样子的?

    一些选择

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

    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++好,如果他们提供有趣的洞察力。

    请不要对封装发表评论-是的,公共字段应该是访问器,为了清楚起见,我省略了这一点。

    1 回复  |  直到 17 年前
        1
  •  1
  •   j_random_hacker    17 年前

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

    :现在您正在取得进展,从基本事件类型继承不同的事件类型。显然,愚蠢的递归需要变成一个循环,还有一些小事情需要整理(我认为你的3个方法命名为 transform() 在里面 UserWidget handle() ; 我猜您可以使用Boost.Function或类似工具解决无模板虚拟方法的问题。我认为这种方法有潜力,尽管我更喜欢SDL风格的简单性。

    但更根本的是:我质疑是否需要一个轮询接口。有什么原因吗 pollEvent() 无法阻止?目前,所有3个代码段99.99%的时间都在浪费CPU时间。

    推荐文章