Sloan Phillippi
Sloan Phillippi

Reputation: 55

Creating a C++ Event System

I've decided to begin making a game engine lately. I know most people don't finish theirs, and if I'm being honest I may not either. I'm doing this because I'm sick of googling "Cool C++ projects" and doing the 3 answers every single user gives (that'd be an address book or something similar, tic tac toe, and a report card generator or something like that). I like programming, but unfortunately I have no real use for it. Everything I would use it for I can do faster and easier in another way, or a solution already exists. However, in an effort to learn more than the basic level of C++ and do something that would teach me something that's truly in depth, I've revoked this policy and decided to begin a game engine, as it's something I've always been interested in. I've decided to model it loosely after Amazon's Lumberyard engine, as it's almost entirely C++ and gives me a good basis to learn from, as I can always just go there and do something with it to see how it behaves.

Onto the actual problem now:

I've got a working Entity Component system (yay), that although is in its early stages and not super great functionality wise, I'm very proud of. Honestly I never thought I'd get this far. I'm currently working with the Event Bus system. Now, I really love LY's EBus system. It's extremely easy to use and very straight forward, but from a programming newbie-ish's eyes it's black magic and witchcraft. I have no clue how they did certain things, so hopefully you do!

Making an EBus goes something like this:

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

And that's it... As if by magic, when you go to use it, all you have to do is this:

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

I just don't get how you can do this much stuff just by adding a single virtual function to NewEbusDealio. Any help on this is much appreciated. Sorry for so many text walls but I'd really like to get something out of this, and I've hit a massive brick wall on this bit. This may be way overkill for what I'm making, and it also may wind up being so much work that it's just not within the realm of possibility for one person to make in a reasonable amount of time, but if a simple version of this is possible I'd like to give it a go.

I'm putting this down here so people know what the setup work is. All you do is define a static const EBusHandlerPolicy and EBusAddressPolicy, which defines how many handlers can connect to each address on the bus, and whether the bus works on a single address (no address needed in event call), or whether you can use addresses to send events to handlers listening on a certain address. For now, I'd like to have a simple bus where if you send an event, all handlers receive it.

Upvotes: 0

Views: 2669

Answers (1)

Galaxy
Galaxy

Reputation: 1279

Not familiar with EBus you given, but event buses should be similar: one side creates an event and puts it into a list, the other side picks up events one by one and reacts.

As modern C++ gives us closure feature, it ismuch easier to implement a event bus now.

Following, I'm going to give a simple example, where looper is a event bus.

Be aware mutexs and conditional variables are necessary for this looper in production.

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

An example to use this 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;
  }
};

Upvotes: 1

Related Questions