top of page
Writer's pictureDan Hatch

IS IT HARD TO WRITE A GAME ENGINE? (PT 4)

Updated: Jan 27, 2021

(Part 4: Get the message, eventually)


Event-ually? Get it? Huh??

In the last post, I’d managed to get together a bare bones render subsystem, and even rendered some stuff on screen. I’d noticed that there was no way to exit the program cleanly though, and in order to do so, we needed to get the subsystems talking to each other. My solution to this problem is to implement a simple Event subsystem. Hence the terrible pun on the subtitle of this blog. I was powerless to resist the pun, all I can do is apologise.


What will it do?

So I started with my simple requirement of when we close the main window, we want to be able to post an event to say it’s happened and whoever has registered to listen for this event will get notified. In this case, the listener will be something that controls the main loop and breaks out of it when the event occurs.

Once again, keeping it simple and “good enough” I figured that a really rough idea of the functionality will be:

  • Ability to register as a listener for an event.

  • Post an event.

I QUIT!

As it’s another subsystem, I created it as an ISubSystem. A class will be needed to represent the event itself, it’s type, for example “QUIT” or “WINDOW_CLOSED” or whatever is appropriate, and we’ll be using an interface to be implemented by the listeners so that we can easily post events to them.

Let’s start with the event type. The really obvious way to do this would be to use constants or an enumeration for the different event types, it would certainly work, but at the time that I started putting together my event system, I was feeling a little more adventurous. I decided that what I REALLY needed was an EventType class that you could build any event type with, in an ad-hoc manner. I decided that what I really wanted to do was to just spin up a new event by giving it an arbitrary event name.

I’m not convinced that I made the right call here, but I went with generating a hash based on an event type string provided. This way I figured, I can use any string and easily compare two separately created event types for equality. I’ll show you what I did anyway, it might prove to be a good call, it at least avoids having a massive list of events hard coded!

My header file for EventType looks something like this…

#ifndef BFDE_EVENTTYPE_HPP
#define BFDE_EVENTTYPE_HPP

namespace bfde {
    class EventType {
    public:
        EventType(const std::string& eventTypeName);
        ~EventType();
        unsigned int GetHash() const;
        bool operator==(const EventType& o) const;
    private:
        void GenerateHash(const std::string& eventTypeName);
        unsigned int hash_;
    };
}

#endif

…and the implementation like this…

#include "EventType.hpp"

namespace bfde {
    EventType::EventType(const std::string& eventTypeName)
        : hash_(0){
        GenerateHash(eventTypeName);
    }

    EventType::~EventType() { }

    unsigned int EventType::GetHash() const {
        return hash_;
    }

    bool EventType::operator==(const EventType& o) const {
        return (hash_ == o.GetHash());
    }

    void EventType::GenerateHash(const std::string&
            eventTypeName) {
        hash_ = HashAlgorithmOfYourChoice();
    }
}

Pretty simple. You’ll need to implement an actual hash algorithm behind the HashAlgorithmOfyourChoice() method. I’ve left this out as the hash algorithm that I went for is actually woefully inadequate, and I’m probably teaching you enough bad habits as it is :) I’ll come back to this later. Needless to say though, it will just be a matter of dropping it in place when I do come back to it.

I now had a class to deal with the type of event, now for the event itself…

#ifndef BFDE_EVENT_HPP
#define BFDE_EVENT_HPP

#include "EventType.hpp"

namespace bfde {
    class Event {
    public:
        Event(std::string eventTypeName);
        virtual ~Event();
        const EventType& GetEventType() const;
    private:
        EventType eventType_;
    };
}

#endif

Implementation:

#include "bfde.hpp"
#include "Event.hpp"

namespace bfde {
    Event::Event(std::string eventTypeName)
        : eventType_(eventTypeName) {
        log_(debug) << "Event::Event()";
}

    Event::~Event() { 
        log_(debug) << "Event::~Event()";
    }

    const EventType& Event::GetEventType() const {
        return eventType_;
    }
}

No real surprises there. The only thing that might be a bit odd is that by this point I wanted to get some debugging output. For this I just used the boost offering, BOOST_LOG_TRIVIAL. I’ve put a macro into bfde.hpp to save a bit of typing:

#define log_ BOOST_LOG_TRIVIAL

You’ve probably noticed that I haven’t even allowed any data with the event. That won’t be hard to add in later, but for now all we’re really doing is trying to send a “QUIT” event so we don’t need additional data, so I moved on to getting the listeners registering.

On that note, here is the interface that makes up an event listener, again, absolute bare bones:

#ifndef BFDE_IEVENTLISTENER_HPP
#define BFDE_IEVENTLISTENER_HPP

#include "Events/Event.hpp"

namespace bfde {
    class IEventListener {
    public:
        virtual ~IEventListener() {};
        virtual void HandleEvent(Event* event) = 0;
    };
}
#endif

Finally, here is my EventManager class. It’s actually a SubSystem, so I don’t know why I’ve called it EventManager. Perhaps EventSubSystem would have been better. It must have been getting late by this point.

#ifndef BFDE_EVENTMANAGER_HPP
#define BFDE_EVENTMANAGER_HPP

#include <queue>
#include <map>
#include "Event.hpp"
#include "IEventListener.hpp"
#include "ISubSystem.hpp"

namespace bfde {
    class EventManager : public ISubSystem {
    public:
        EventManager();
        virtual ~EventManager();
        virtual void Initialise();
        virtual void Terminate();
        virtual void Update();

        void Post(Event* event) const;
        void AddListener(const EventType& eventType,
            IEventListener* listener);

    private:
        typedef std::vector<IEventListener*> IEventListenerList;
        typedef std::map<unsigned int, IEventListenerList*>
            ListenerMap;

        void Flip();

        std::queue<Event*> queue1_;
        std::queue<Event*> queue2_;
        std::queue<Event*>* currentQueue_;
        ListenerMap listeners_;
    };
}
#endif

As you can see from this declaration, I went with a couple of std::queue for the event queue(s) and a simple std::vector of std::map for the listener lists. I went with two queues so that I can switch out one of them for processing whilst the other can continue to accept events. This means that posting new events while processing an existing event shouldn’t ever be able to cause an infinite event processing loop. Here’s the implementation of EventManager:

#include "bfde.hpp"
#include "EventManager.hpp"

namespace bfde {
    EventManager::EventManager()
        : currentQueue_(&queue1_) {
    }

    EventManager::~EventManager() {
        foreach_(ListenerMap::value_type& i, listeners_)
            delete i.second;
    }

    void EventManager::Initialise() {
        log_(debug) << "EventManager::Initialise()";
    }

    void EventManager::Terminate() {
        log_(debug) << "EventManager::Terminate()";
    }

    void EventManager::Update() {
        std::queue<Event*>* processQueue = currentQueue_;

        Flip();

        while (!processQueue->empty()) {
            Event* event = processQueue->front();

            processQueue->pop();

            unsigned int eventTypeHash = 
                event->GetEventType().GetHash();

            IEventListenerList* listenerList =
                listeners_[eventTypeHash];

            foreach_(IEventListener* l, *listenerList) {
                l->HandleEvent(event);
                delete event;
            }
        }
    }

    void EventManager::Post(Event* event) const {
        currentQueue_->push(event);
    }

    void EventManager::AddListener(const EventType& eventType,
            IEventListener* listener) {
        unsigned int eventTypeHash = eventType.GetHash();

        IEventListenerList* listenerList =
            listeners_[eventTypeHash];

        if (!listenerList){
            listenerList = new IEventListenerList();
            listeners_[eventTypeHash] = listenerList;
        }

        listenerList->push_back(listener);
    }

    void EventManager::Flip() {
        if (currentQueue_ == &queue1_)
            currentQueue_ = &queue2_;
        else
            currentQueue_ = &queue1_;
    }
}

To add a listener, it’s a simple case of trying to look up the listener list for the event type, if not there add a new listener list, then add the listener to the found (or newly created) list.

To post an event, it’s just push it onto the current queue.

Even the processing is pretty noddy (in the Update() method). I pick up a pointer to the current list as the processQueue, flip the currentQueue_ pointer to point to the other queue, then iterate through the events in the processingQueue, looking up the listenerList and calling the HandleEvent for each of the listeners. Simple.

The next step was to plumb the EventHandler into the main loop, just a matter of creating an instance, initialising (by calling Initialise()), calling the Update() method within the main loop, and then cleaning up after (call Terminate() method and delete).

I had to tweak the OgreRenderSystem so that I could pass it the EventManager to use to post the event, and actually post the event. I added a member variable to store the reference to the event manager (eventManager_) and then updated windowClosed method to read:

void OgreRenderSubSystem::windowClosed(Ogre::RenderWindow* rw) {
    // Only close for window that created OIS
    if (rw == window_)
        eventManager_.Post(new Event("QUIT"));
}

If all went well, we should now have a “QUIT” event being posted when the window is closed.


Listen! Do you smell something?

Hurrah, I have a (thin) excuse for quoting Ghostbusters :)

Now an event was being posted, I needed to code up something to do something with the event. I needed a listener implementation.

At present, the main loop will run forever, so I needed to tweak this so that it can handle our event and cleanly exit the main loop. To achieve this, I went with a finite state machine to represent our game state rather than a simple “is running” boolean. I went this route as I figured that there may well be other states in the future. I even made some up, then I cleverly called this class GameState. Declaration looks like…

#ifndef BFDE_GAMESTATE_HPP
#define BFDE_GAMESTATE_HPP

#include "Events/IEventListener.hpp"
#include "Events/EventManager.hpp"

namespace bfde {
    class GameState : public IEventListener {
    public:
        enum State {
            STARTING,
            RUNNING,
            STOPPING,
            STOPPED
        };

        GameState(EventManager& eventManager);
        ~GameState();

        State GetState() const;
        void SetState(State state);
        virtual void HandleEvent(Event* event);
    private:
        EventType quitEventType_;
        EventManager& eventManager_;
        State currentState_;
    };
}
#endif

… and the implementation like…

#include "bfde.hpp"
#include "GameState.hpp"

namespace bfde {
    GameState::GameState(EventManager& eventManager)
        : eventManager_(eventManager),
        currentState_(GameState::STARTING),
        quitEventType_("QUIT") {
        log_(debug) << "GameState::GameState()";
        eventManager_.AddListener(quitEventType_, this);
    }

    GameState::~GameState() {
        log_(debug) << "GameState::~GameState()";
    }

    GameState::State GameState::GetState() const {
        return currentState_;
    }

    void GameState::SetState(GameState::State state) {
        currentState_ = state;
    }

    void GameState::HandleEvent(Event* event) {
        if (event->GetEventType() == quitEventType_) {
            SetState(GameState::STOPPING);
        }
    }
}

The things of note in this snippet are that in the constructor I register with the EventManager that I wish to listen for events with the event type of “QUIT”, and that there is a HandleEvent method that checks that the event type is indeed a “QUIT” event, and if so sets the game state to stopping.

Then it’s just a matter of creating a GameState object in the main method, and using the state in the main loop like this:

while (gameState.GetState() == GameState::RUNNING) {
}

That seems to do it, now when I run it, I can close the window, and our engine stops nice and cleanly. Result!


Next time…

I write some code to allow interaction with the engine.

1 view0 comments

Recent Posts

See All

Comentarios


bottom of page