首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >GLFW/Glad事件调度员

GLFW/Glad事件调度员
EN

Code Review用户
提问于 2022-01-19 22:03:47
回答 2查看 199关注 0票数 2

我试着为GLFW/Glad库编写一个事件分派程序。然而,我不太习惯于编写高效的代码(我一生的大部分时间都是用C#编写代码,而性能从来都不是我在大学里学到的)。

代码可以工作,但我不确定它是太复杂了还是太愚蠢了。我将感谢任何和所有的反馈。

我意识到的一个问题是,要从事件中获取任何有用的信息,就必须对事件进行dynamic_cast,这是不可取的。如果很少调用所有事件,那么dynamic_cast不会成为一个问题,但是这个调度程序也会处理关键的新闻事件,我认为每个帧转换几次将是一场灾难。此外,由于回调的注册方式,目前无法注销/分离回调。

我也没有真正想过内联,这让我有点困惑,因为你最终永远不会在内联的事情上有发言权。

MythDispatcher.h

代码语言:javascript
复制
#ifndef MYTH_FRAMEWORK_DISPATCHER
#define MYTH_FRAMEWORK_DISPATCHER

#include "mpch.h"
#include "MythEvent.h"

namespace MythFramework
{
    using MythCallback = std::function<void(MythEvent&)>;

    class MythDispatcher
    {
    public:
        static void RegisterCallback(EventType eventType, MythCallback callback);
    private:
        MythDispatcher();

        static MythDispatcher& GetInstance();

        MythDispatcher(const MythDispatcher&) = delete;
        MythDispatcher& operator=(const MythDispatcher&) = delete;

        static void Trigger(EventType eventType, MythEvent& eventToDispatch);

        std::map<EventType, std::vector<MythCallback> > callbacks;

        friend class MythWindow;
        friend class MythWinWindow;
    };
}

#endif

MythDispatcher.cpp

代码语言:javascript
复制
#include "MythDispatcher.h"

namespace MythFramework
{
    MythDispatcher::MythDispatcher()
    {
        callbacks = std::map<EventType, std::vector<MythCallback> >();
    }

    void MythDispatcher::RegisterCallback(EventType eventType, MythCallback callback)
    {
        if (GetInstance().callbacks.find(eventType) == GetInstance().callbacks.end())
        {
            GetInstance().callbacks[eventType] = std::vector<MythCallback>();
        }

        GetInstance().callbacks[eventType].push_back(callback);
    }

    MythDispatcher& MythDispatcher::GetInstance()
    {
        static std::unique_ptr<MythDispatcher> instance;
        if (!instance)
        {
            instance.reset(new MythDispatcher());
        }

        return *instance;
    }

    void MythDispatcher::Trigger(EventType eventType, MythEvent& eventToDispatch)
    {
        if (GetInstance().callbacks.find(eventType) == GetInstance().callbacks.end())
            return;

        //Could use auto but i hate auto
        for (std::vector<MythCallback>::iterator it = GetInstance().callbacks[eventType].begin(); it != GetInstance().callbacks[eventType].end(); it++)
        {
            (*it)(eventToDispatch);
        }
    }
}

从window类中提取以显示如何调用事件:

/\* ###################### GLFW CALLBACKS ###################### \*/ glfwSetWindowFocusCallback(window, [](GLFWwindow\* window, int focus) { if (focus == GL\_TRUE) { MythDispatcher::Trigger(EventType::AppFocus, MythFocusEvent()); } else { MythDispatcher::Trigger(EventType::AppLostFocus, MythLostFocusEvent()); } }); glfwSetWindowPosCallback(window, [](GLFWwindow\* window, int x, int y) { MythDispatcher::Trigger(EventType::AppMove, MythMoveEvent(x, y)); }); glfwSetWindowSizeCallback(window, [](GLFWwindow\* window, int width, int height) { MythDispatcher::Trigger(EventType::AppResize, MythResizeEvent(width, height)); });

MythApp.h中提取,显示如何注册回调:

MythDispatcher::RegisterCallback(EventType::AppExit, [this](MythEvent& e) { running = false; });

编辑:Github回购

EN

回答 2

Code Review用户

发布于 2022-01-20 07:52:34

我们在头文件中缺少了一些内容:

代码语言:javascript
复制
#include <functional>
#include <map>
#include <vector>

在执行过程中:

代码语言:javascript
复制
#include <memory>

我不是单例的粉丝,这很难独立进行单元测试。

然而,如果这是一个已完成的交易,我不认为有任何调用,例如方法和数据-它可以只是一个静态的类。

成员应该在构造函数的初始化列表中初始化,而不是在主体中初始化:

代码语言:javascript
复制
MythDispatcher::MythDispatcher()
    : callbacks{}
{}

更好的是,我们可以在类定义中进行内联初始化,从而允许我们默认构造函数:

代码语言:javascript
复制
private:
    std::map<EventType, std::vector<MythCallback> > callbacks = {};
    MythDispatcher() = default;

公开Trigger()可能更好,所以我们不必列出所有可能使用它作为朋友的类。这是一个潜在的开放式集合,这使得我们很难预见每一个用例。

RegisterCallback()中,我们默认地构造一个新的映射条目,如果它还不存在的话。但是这正是std::map::operator[]已经做的,所以我们可以这样写它:

代码语言:javascript
复制
void MythDispatcher::RegisterCallback(EventType eventType, MythCallback callback)
{
    GetInstance().callbacks[eventType].push_back(std::move(callback));
}

GetInstance()中,我们从instance == nullptr开始。但这不是必要的,因为我们可以用实际的对象初始化--它将在第一次调用函数时填充。我们甚至不需要智能指针,因为它在程序结束之前不会被删除:

Trigger()中,我们真的应该克服你对auto的厌恶。这种突出的类型(和重复查找)使得行长,充满了杂乱:

//Could use auto but i hate auto for (std::vector<MythCallback>::iterator it = GetInstance().callbacks[eventType].begin(); it != GetInstance().callbacks[eventType].end(); it++) { (\*it)(eventToDispatch); }

听着,要清楚得多:

代码语言:javascript
复制
    for (auto& callback: GetInstance().callbacks[eventType]) {
        callback(eventToDispatch);
    }

我也避免使用多个GetInstance()callbacks[eventType]查询:

代码语言:javascript
复制
void MythDispatcher::Trigger(EventType eventType, MythEvent& eventToDispatch)
{
    auto const& callbacks = GetInstance().callbacks;
    auto const it = callbacks.find(eventType);
    if (it == callbacks.end()) {
        return;
    }

    for (auto& callback: it->second) {
        callback(eventToDispatch);
    }
}

修改代码

我已经更改了接口,这样它就不再是单例了--因此,如果需要的话,应用程序可以使用多个分派器(单元测试当然需要)。

报头

代码语言:javascript
复制
#include <functional>
#include <map>
#include <vector>

namespace MythFramework
{
    using MythCallback = std::function<void(MythEvent&)>;

    class MythDispatcher
    {
        std::map<EventType, std::vector<MythCallback>> callbacks = {};

    public:
        MythDispatcher(const MythDispatcher&) = delete;
        MythDispatcher& operator=(const MythDispatcher&) = delete;

        void RegisterCallback(EventType eventType, MythCallback callback);
        void Trigger(EventType eventType, MythEvent& eventToDispatch);
    };
}

Implementation

代码语言:javascript
复制
#include <utility>

namespace MythFramework
{
    void MythDispatcher::RegisterCallback(EventType eventType, MythCallback callback)
    {
        callbacks[eventType].push_back(std::move(callback));
    }

    void MythDispatcher::Trigger(EventType eventType, MythEvent& eventToDispatch)
    {
        auto const it = callbacks.find(eventType);
        if (it == callbacks.end()) {
            return;
        }

        for (auto& callback: it->second) {
            callback(eventToDispatch);
        }
    }
}

进一步启发

Qt库的核心是一个事件调度器,经过了几十年的战斗测试。值得一看它是如何运行的,因此您可以看到在您自己的实现中应该解决哪些问题。

票数 3
EN

Code Review用户

发布于 2022-01-20 11:55:25

除了托比·斯皮特的出色回答:

避免不必要地重复名称

添加的每一种类型都以Myth开头。但是,一旦将所有内容都放在名称空间中,就没有理由重复名称空间名称的部分。我还建议将MythFramework重命名为Myth。然后,它变成:

代码语言:javascript
复制
namespace Myth
{
    using Callback = std::function<void(Event&)>;

    class Dispatcher {
        ...
    };
};

另外,什么是MythWinWindow

爱胜过恨

你写了这段代码:

代码语言:javascript
复制
//Could use auto but i hate auto
for (std::vector<MythCallback>::iterator it = GetInstance().callbacks[eventType].begin(); it != GetInstance().callbacks[eventType].end(); it++)
{
    (*it)(eventToDispatch);
}

您不需要auto将其转换为基于范围的循环

代码语言:javascript
复制
for (MythCallback &callback: callbacks[eventType])
{
    callback(eventToDispatch);
}

但请不要讨厌这些东西。您可能不喜欢auto的某些方面,但这并不意味着auto也有可能超过某些场景中的缺点的优点。“讨厌”只会使您更难看到这些好处,如果您不在适当的地方使用auto,您的代码质量可能会受到影响。也许它甚至使你看不见没有auto还可以使用范围的事实?

使用std::unordered_map

您不需要callbacks按任何特定的顺序排序,所以可以考虑使用std::unordered_map,因为它比std::map具有更快的查找速度。

请注意,您甚至可以避免使用(无序的)映射,方法是让MythDispatcher成为模板,而事件类型是模板参数。基本上,每个事件类型都有一个分配器。

票数 2
EN
页面原文内容由Code Review提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://codereview.stackexchange.com/questions/273162

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档