c++ - 创建 C++ 事件系统
问题描述
我最近决定开始制作游戏引擎。我知道大多数人没有完成他们的,如果我说实话,我可能也不会。我这样做是因为我厌倦了在谷歌上搜索“酷 C++ 项目”并做每个用户给出的 3 个答案(那是地址簿或类似的东西、井字游戏和成绩单生成器或类似的东西那)。我喜欢编程,但不幸的是我没有真正的用处。我将使用它的所有东西都可以通过另一种方式更快更容易地完成,或者已经存在解决方案。然而,为了学习比 C++ 基本水平更多的东西,并做一些能教给我真正深入的东西,我撤销了这个政策并决定开始一个游戏引擎,因为这是我一直感兴趣的东西在。我
现在进入实际问题:
我有一个可以工作的实体组件系统(耶),虽然它还处于早期阶段,而且功能还不是超级棒,但我对此感到非常自豪。老实说,我从来没有想过我会走到这一步。我目前正在使用事件总线系统。现在,我真的很喜欢 LY 的 EBus 系统。它非常易于使用且非常直接,但在编程新手的眼中,它是黑魔法和巫术。我不知道他们是如何做某些事情的,所以希望你能做到!
制作 EBus 是这样的:
#include <EBusThingy.h>
class NewEbusDealio
: public EbusThingy
{
public:
//Normally there's some setup work involved here, but I'm excluding it as I don't really feel that it's necessary for now. I can always add it later (see the footnote for details on what these actually are).
//As if by magic, this is all it takes to do it (I'd like to clarify that I'm aware that this is a pure virtual function, I just don't get how they generate so much usage out of this one line):
virtual void OnStuffHappening(arguments can go here if you so choose) = 0;
};
就是这样......就像变魔术一样,当你去使用它时,你所要做的就是:
#include "NewEbusDealio.h"
class ComponentThatUsesTheBus
: public NewEbusDealio::Handler
{
public:
void Activate() override
{
NewEbusDealio::Handler::BusConnect();
}
protected:
void OnStuffHappening(arguments so chosen)
{
//Do whatever you want to happen when the event fires
}
};
class ComponentThatSendsEvents
{
public:
void UpdateOrWhatever()
{
NewEbusDealio::Broadcast(NewEbusDealio::Events::OnStuffHappening, arguments go here)
}
};
我只是不明白如何通过向 NewEbusDealio 添加一个虚拟函数来完成这么多事情。非常感谢您对此的任何帮助。很抱歉有这么多文字墙,但我真的很想从中得到一些东西,而且我在这一点上碰到了一堵巨大的砖墙。对于我正在制作的东西来说,这可能有点过头了,而且它也可能最终导致工作量太大,以至于一个人不可能在合理的时间内完成,但如果这是一个简单的版本有可能我想试一试。
我把它放在这里,以便人们知道设置工作是什么。你要做的就是定义一个静态的 const EBusHandlerPolicy 和 EBusAddressPolicy,它定义了总线上每个地址可以连接多少个处理程序,以及总线是否工作在单个地址上(事件调用中不需要地址),或者你是否可以使用地址将事件发送到侦听某个地址的处理程序。现在,我想要一个简单的总线,如果你发送一个事件,所有的处理程序都会收到它。
解决方案
不熟悉EBus
你给出的,但事件总线应该是相似的:一方创建一个事件并将其放入列表中,另一方一个接一个地拿起事件并做出反应。
由于现代 C++ 为我们提供了闭包功能,因此现在实现事件总线更加容易。
下面,我将举一个简单的例子,其中 looper 是一个事件总线。
请注意,在生产中此循环器需要互斥锁和条件变量。
#include <queue>
#include <list>
#include <thread>
#include <functional>
class ThreadWrapper {
public:
ThreadWrapper() = default;
~ThreadWrapper() { Detach(); }
inline void Attach(std::thread &&th) noexcept {
Detach();
routine = std::forward<std::thread &&>(th);
}
inline void Detach() noexcept {
if (routine.joinable()) {
routine.join();
}
}
private:
std::thread routine{};
};
class Looper {
public:
// return ture to quit the loop, false to continue
typedef std::function<void()> Task;
typedef std::list<Task> MsgQueue;
Looper() = default;
~Looper() {
Deactivate();
}
// Post a method
void Post(const Task &tsk) noexcept {
Post(tsk, false);
}
// Post a method
void Post(const Task &tsk, bool flush) noexcept {
if(!running) {
return;
}
if (flush) msg_queue.clear();
msg_queue.push_back(tsk);
}
// Start looping
void Activate() noexcept {
if (running) {
return;
}
msg_queue.clear();
looping = true;
worker.Attach(std::thread{&Looper::Entry, this});
running = true;
}
// stop looping
void Deactivate() noexcept {
{
if(!running) {
return;
}
looping = false;
Post([] { ; }, true);
worker.Detach();
running = false;
}
}
bool IsActive() const noexcept { return running; }
private:
void Entry() noexcept {
Task tsk;
while (looping) {
//if(msg_queue.empty()) continue;
tsk = msg_queue.front();
msg_queue.pop_front();
tsk();
}
}
MsgQueue msg_queue{};
ThreadWrapper worker{};
volatile bool running{false};
volatile bool looping{false};
};
使用此 Looper 的示例:
class MySpeaker: public Looper{
public:
// Call SayHi without blocking current thread
void SayHiAsync(const std::string &msg){
Post([this, msg] {
SayHi(msg);
});
}
private:
// SayHi will be called in the working thread
void SayHi() {
std::cout << msg << std::endl;
}
};