TransWikia.com

Game Event Messenger based on the Observer pattern

Code Review Asked on October 27, 2021

I am making a game event messenger based on the observer pattern and I’d really appreciate some feedback on this please. The intent behind it is that I am sending in a function pointer so that it can work for the broadcasting. Also using the subscribers memory address as a way of detecting it for unsubscribe.

Thank you in advance.

enum class eGameEventType
{
    SpawnPickup = 0,
    Max = SpawnPickup
};

namespace GameEvents
{
    template <eGameEventType T>
    struct BaseEvent
    {
        static eGameEventType getType() { return T; };
    };

    struct SpawnPickUp : public BaseEvent<eGameEventType::SpawnPickup>
    {
        SpawnPickUp(int type, const std::string& name)
            : type(type),
            name(name)
        {}

        int type;
        std::string name;
    };
}


struct Listener
{
    Listener(const std::function<void(const void*)>& fp, const void* ownerAddress)
        : m_listener(fp),
        m_ownerAddress(ownerAddress)
    {}

    Listener(Listener&& orig) noexcept
        : m_listener(orig.m_listener),
        m_ownerAddress(orig.m_ownerAddress)
    {
        orig.m_listener = nullptr;
        orig.m_ownerAddress = nullptr;
    }

    Listener& operator=(Listener&& orig) noexcept
    {
        m_listener = orig.m_listener;
        m_ownerAddress = orig.m_ownerAddress;

        orig.m_listener = nullptr;
        orig.m_ownerAddress = nullptr;

        return *this;
    }

    std::function<void(const void*)> m_listener;
    const void* m_ownerAddress;
};

class GameEventMessenger
{
public:
    static GameEventMessenger& getInstance()
    {
        static GameEventMessenger instance;
        return instance;
    }

    template <typename GameEvent>
    void subscribe(const std::function<void(const GameEvent&)>& gameEvent, const void* ownerAddress)
    {
        auto& listeners = m_listeners[static_cast<int>(GameEvent::getType())];
        assert(!isOwnerAlreadyRegistered(listeners, GameEvent::getType(), ownerAddress));

        listeners.emplace_back(reinterpret_cast<std::function<void(const void*)> const&>(gameEvent), ownerAddress);
    }

    template <typename GameEvent>
    void unsubscribe(const void* ownerAddress)
    {
        auto& listeners = m_listeners[static_cast<int>(GameEvent::getType())];
        assert(isOwnerAlreadyRegistered(listeners, GameEvent::getType(), ownerAddress));

        auto iter = std::find_if(listeners.begin(), listeners.end(), [ownerAddress](const auto& listener)
        {
            return listener.m_ownerAddress == ownerAddress;
        });

        assert(iter != listeners.end());
        listeners.erase(iter);
    }

    template <typename GameEvent>
    void broadcast(GameEvent gameEvent)
    {
        const auto& listeners = m_listeners[static_cast<int>(GameEvent::getType())];
        for (const auto& listener : listeners)
        {
            reinterpret_cast<std::function<void(const GameEvent&)> const&>(listener.m_listener)(gameEvent);
        }
    }

private:
    GameEventMessenger() {}
    std::array<std::vector<Listener>, static_cast<size_t>(eGameEventType::Max) + 1> m_listeners;

    bool isOwnerAlreadyRegistered(const std::vector<Listener>& listeners, eGameEventType gameEventType, const void* ownerAddress) const
    {
        assert(ownerAddress != nullptr);

        if (!listeners.empty())
        {
            auto result = std::find_if(listeners.cbegin(), listeners.cend(), [ownerAddress](const auto& listener)
            {
                return listener.m_ownerAddress == ownerAddress;
            });

            return result != listeners.cend();
        }
        else
        {
            return false;
        }
    }
};

Working Example:

GameEventMessenger::getInstance().subscribe<GameEvents::AddToInventory>(std::bind(&Player::onAddToInventory, this, std::placeholders::_1), this);
void Player::onAddToInventory(const GameEvents::AddToInventory & gameEvent)
{
    m_inventory.add(gameEvent.type);
}
GameEventMessenger::getInstance().unsubscribe<GameEvents::AddToInventory>(this);

Add your own answers!

Ask a Question

Get help from others!

© 2024 TransWikia.com. All rights reserved. Sites we Love: PCI Database, UKBizDB, Menu Kuliner, Sharing RPP