C++事件系统设置



我很好奇如何使以下代码真正工作。我目前有一个事件系统,但它使用的是观察者模式。我希望我的窗口事件按如下方式工作:

window win("Engine");
win.on_event(KEY_PRESSED, []{
// key pressed
});
win.on_event(KEY_RELEASED, []{
// key released
});
win.on_event(MOUSE_CLICKED, []{
// mouse clicked
});

问题是我不知道从哪里开始。我还希望能够获得有关事件的特定信息,例如在窗口中单击鼠标的(x,y(坐标。任何意见都将不胜感激,即使这只是我需要做什么的一个总体想法。谢谢。

到目前为止,我认为您的做法是正确的,但构建事件系统并不是一件简单的事情。在一个事件系统中,有很多事情需要考虑。我假设你这样做是为了学习,所以不要使用任何库。然而,我建议通过查看一些现有的事件系统来获得一些灵感,你可能会发现你甚至不知道自己想要的功能,直到你看到它们。这些是我建议你采取的步骤/思考练习。

  1. 定义您的事件:听起来你已经这样做了,但请考虑一下,如果你希望事件是不相关的类型或继承层次结构的一部分,每种方法都有利弊。

  2. 定义您的发布机制:你说你有观察者模式,这是一个很好的第一选择,但要考虑这是否是你想要的一切。也许还要考虑这些事情;一个对象应该只直接传递到它的独立目的地,或者可能涉及某种间接或委派,如消息总线或中心事件队列。好消息是,设计成一个类似观察者模式所建议的界面,以后很容易更改。

  3. 在时间空间中定义事件模型:您是否需要或希望立即、延迟或异步处理事件?你需要储存它们吗?穿线怎么样?这是一个单线程系统吗?如果是这样的话,那就增加了一层复杂性。

  4. 定义消息的接收:有点链接到2,考虑对象A想要发布一个事件,但它如何知道将其传递给谁,它可能会在它知道的某种注册表中查找,它可能是函数调用的参数,它也可能是你可以想象的其他东西。传统上,我看到这是通过两种方式完成的:一种是向某个对象注册事件处理程序,该对象将接收事件,然后调用您的处理程序。此机制要求您考虑是否也可以注销处理程序,以及如果可以,如何注销。另一个是";函数";参数,其中函数可能是类成员函数、策略对象或类似对象。

我想在那个阶段你会问的大部分难题

这里有一个基于层次结构的事件的单线程事件系统的快速示例:(注意,这不是遵循最佳实践,它不是生产质量,它是演示概念的绝对最低限度,[使用c++17标准来方便我](

#include <iostream>
#include <functional>
#include <string>
#include <string_view>
#include <vector>
// Lets define a base event type
struct event_base {
explicit event_base(std::string const& event_type) : type{event_type} {}
std::string type;
};
// Now a more specific event type
struct name_changed : public event_base  {
constexpr static auto event_type = "name_changed_event";
name_changed(std::string old_name, std::string new_name) :
event_base(event_type),
old_name{std::move(old_name)},
new_name{std::move(new_name)}
{}
std::string old_name;
std::string new_name;
};
// Next our observer interface
using event_handler = std::function<void(event_base const&)>;
/* could do these the traditional way with a class interface but i
* prefer a std::function when there only one method to implement
* and it means i dont have to bring out unique_ptrs or anything
* for this example
*/
// And a structure to associate an observer with an event type
struct registered_handler {
std::string event_type;
event_handler handler;
};
// A simple example observable person 
class person_info {
public:
person_info(int age, std::string name) :
m_age{age},
m_name{std::move(name)}
{}
void add_handler(std::string const& event_type, event_handler const& handler) {
m_handlers.push_back({event_type, handler});
}
void set_name(std::string new_name) {
// check for change
if(new_name == m_name) {
return;
}
// build event
name_changed const event {m_name, new_name};
// make change
std::swap(m_name, new_name);
// publish events
if(auto *const handler = get_handler_for_event_type(event.type); handler != nullptr) {
handler->handler(event);
}
}
void set_age(int new_age) {
// same thing here but using age and age change event
}
private:
registered_handler* get_handler_for_event_type(std::string const& event_type) {
auto const& existing_handler = std::find_if(std::begin(m_handlers), std::end(m_handlers), [&](auto const& h){
return h.event_type == event_type;
});
if(existing_handler != std::end(m_handlers)) {
return &(*existing_handler);
} else {
return nullptr;
}
}
int m_age;
std::string m_name;
std::vector<registered_handler> m_handlers;
};
// And a main to exercise it 
int main() {
person_info my_guy{25, "my guy"};
my_guy.add_handler(name_changed::event_type, [](event_base const& event) {
auto const& name_change_event = reinterpret_cast<name_changed const&>(event);
std::cout << "My guy changed his name from: " << name_change_event.old_name << " to: " << name_change_event.new_name << "n";
});
my_guy.set_name("someone else");
}

不幸的是,对于一个简单的事件系统来说,这是一个相当多的锅炉板,但一旦你有了它,并将其调整为你想要的,那么你就可以继续使用它。

如果你运行这个例子,输出应该很简单:

My guy changed his name from: my guy to: someone else

不久前,我遇到了与您类似的问题。对于我的窗口,我决定继承entt::emitter

class Win32Window : public entt::emitter<Win32Window>

然后进入WndProc

LRESULT Win32Window::_wndProc(HWND, UINT, WPARAM wParam, LPARAM lParam) {
switch (uMsg) {
case WM_MOUSEMOVE:
if (!empty<MouseMoveEvent>()) {
publish<MouseMoveEvent>(GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam));
return 0;
}
break;
// other messages ...
}
}

最后的结果是这样的:

window.on<ResizeWindowEvent>([](const auto &evt, auto &) {
ImGui::GetIO().DisplaySize = glm::vec2{evt.width, evt.height};
});
window.on<MouseMoveEvent>([](const auto &evt, auto &) {
ImGui::GetIO().MousePos = glm::vec2{evt.x, evt.y};
});

当然,你需要一些事件类型:

struct MouseMoveEvent {
int32_t x, y;
};
struct CloseWindowEvent {};
// and so on ...

这不是一个完整的例子,它是一个简单的片段,可以让你对你要做的事情有一个通用的想法。

#include <iostream>
#include <functional>
#include <thread>
#include <Windows.h>
#include <map>
enum class MOUSE_EVENT_TYPE {
MOUSE_CLICKED,
MOUSE_MOVE
};
struct window
{
std::thread *th;
bool loop{ true };
std::map < MOUSE_EVENT_TYPE, std::function<void()>> func;
window()
{
th = new std::thread([this]() {
while (loop)
{
if (func[MOUSE_EVENT_TYPE::MOUSE_MOVE] && GetCursorPos())
{
func[MOUSE_EVENT_TYPE::MOUSE_MOVE](p);
}
if (func[MOUSE_EVENT_TYPE::MOUSE_CLICKED] && GetAsyncKeyState(VK_LBUTTON))
{
func[MOUSE_EVENT_TYPE::MOUSE_CLICKED]();
}
}
});
}
void on_event(MOUSE_EVENT_TYPE event, std::function<void(POINT)> fn)
{
if (!func[event])
{
func[event] = fn;
}
}
};
int main()
{
window win;
win.on_event(MOUSE_EVENT_TYPE::MOUSE_MOVE, []() {
std::cout << "mouse moved" << std::endl;
});
win.on_event(MOUSE_EVENT_TYPE::MOUSE_CLICKED, []() {
std::cout << "mouse clicked" << std::endl;
});
win.th->join();
}

最新更新