Создание системы событий C ++ - PullRequest
0 голосов
/ 20 сентября 2018

Я решил начать создавать игровой движок в последнее время.Я знаю, что большинство людей не заканчивают свои, и, если я честен, я тоже не могу.Я делаю это потому, что мне надоело гуглить «крутые проекты на C ++» и делать 3 ответа, которые дает каждый пользователь (это будет адресная книга или что-то подобное, Tic Tac Toe, генератор отчетов или что-то вродетот).Мне нравится программирование, но, к сожалению, я не пользуюсь им.Все, что я хотел бы использовать, я могу делать быстрее и проще другим способом, или решение уже существует.Однако, чтобы выучить больше, чем базовый уровень C ++ и сделать что-то, что могло бы научить меня чему-то действительно глубокому, я отменил эту политику и решил запустить игровой движок, поскольку это то, что меня всегда интересовалов. Я решил смоделировать его свободно после движка Amazon Lumberyard, поскольку он почти полностью состоит из C ++ и дает мне хорошую основу для изучения, поскольку я всегда могу просто пойти туда и что-то с ним сделать, чтобы посмотреть, как он себя ведет.

Об актуальной проблеме сейчас:

У меня есть работающая система Entity Component (yay), которая хотя и находится на ранних стадиях и не очень хороша в плане функциональности, я очень горжусь.Честно говоря, я никогда не думал, что получу это далеко.В настоящее время я работаю с системой Event Bus.Теперь я очень люблю систему EBus от LY.Он чрезвычайно прост в использовании и очень прост, но с точки зрения новичка в программировании это черная магия и колдовство.Я понятия не имею, как они делали определенные вещи, так что, надеюсь, вы делаете!

Создание 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.Любая помощь в этом очень ценится.Извините за столько текстовых стен, но я бы очень хотел получить что-то из этого, и я натолкнулся на массивную кирпичную стену на этом кусочке.Это может быть излишним из-за того, что я делаю, и может оказаться настолько большим трудом, что один человек не сможет выполнить за разумное время, но если простая версия этогоВозможно, я бы хотел попробовать.

Я опишу это здесь, чтобы люди знали, как работает установка.Все, что вам нужно сделать, это определить статические константы EBusHandlerPolicy и EBusAddressPolicy, которые определяют, сколько обработчиков может подключаться к каждому адресу на шине, и работает ли шина по одному адресу (адрес не требуется при вызове события), или можно ли использовать адресаотправлять события обработчикам, прослушивающим определенный адрес.Сейчас я хотел бы иметь простую шину, где, если вы отправляете событие, все обработчики получают его.

Ответы [ 2 ]

0 голосов
/ 20 сентября 2018

Не знаком с 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};
};

Пример использования этого петлителя:

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;
  }
};
0 голосов
/ 20 сентября 2018

Я не уверен, какая часть здесь удивляет вас.Без проверки этой библиотеки EBus, казалось бы очевидным, что NewEbusDealio, скорее всего, наследует Broadcast() от EbusThingy, и эта функция может выполнять практически всю необходимую работу.Казалось бы, Broadcast() запрашивает указатель на функцию-член для вызова каждой сущности (я думаю, в вашем коде выше отсутствует &), а также параметры.Затем он просто вызывает данный метод с заданным набором аргументов для каждой сущности…

...