创建C ++事件系统

时间:2018-09-20 09:22:13

标签: c++ events game-engine

我最近决定开始制作游戏引擎。我知道大多数人不会完成他们的任务,如果我说实话,我也可能不会。我这样做是因为我厌倦了搜索“ Cool C ++项目”,并做了每个用户给出的3个答案(这将是通讯录或类似的东西,井字游戏,以及报告卡生成器或类似的东西)那)。我喜欢编程,但是不幸的是我没有真正的用处。我会用它做的所有事情都可以通过另一种方式更快,更轻松地完成,或者已经存在解决方案。但是,为了努力学习更多的C ++基本知识并做一些可以教会我真正深入的事情,我撤销了这项政策,决定开始使用游戏引擎,因为我一直对此感兴趣。我决定采用Amazon的Lumberyard引擎对它进行宽松的建模,因为它几乎完全是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,它们定义了多少个处理程序可以连接到总线上的每个地址,以及总线是否在单个地址上工作(事件调用中不需要地址),或者是否可以使用地址将事件发送给侦听某个地址的处理程序。现在,我希望有一条简单的总线,如果您发送事件,所有处理程序都会接收它。

2 个答案:

答案 0 :(得分:1)

您对给定的EBus不熟悉,但是事件总线应该类似:一侧创建一个事件并将其放入列表,另一侧一个接一个地拾取事件并做出反应。

由于现代C ++为我们提供了闭包功能,因此现在更容易实现事件总线。

下面,我将举一个简单的示例,其中循环程序是事件总线。

请注意,互斥量和条件变量是生产此循环器所必需的。

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

答案 1 :(得分:0)

我不确定这到底是哪一部分令您惊讶。在没有签出此EBus库的情况下,显然NewEbusDealio很可能从Broadcast()继承了EbusThingy,并且该函数基本上可以完成所有必要的工作。似乎Broadcast()要求一个指向成员函数的指针以在每个实体上调用(我想您上面的代码在那里缺少&)以及参数。然后,它只需在每个实体上使用给定的参数集调用给定的方法即可。