From https://austinmorlan.com/posts/entity_component_system/

types.h

#pragma once

#include <bitset>
#include <cstdint>

namespace Piccolo
{

    // Source: https://gist.github.com/Lee-R/3839813
    constexpr std::uint32_t fnv1a_32(char const* s, std::size_t count)
    {
        return ((count ? fnv1a_32(s, count - 1) : 2166136261u) ^ s[count]) * 16777619u; // NOLINT (hicpp-signed-bitwise)
    }

    constexpr std::uint32_t operator"" _hash(char const* s, std::size_t count) { return fnv1a_32(s, count); }

    // ECS defines
    using Entity                       = std::uint32_t;
    const Entity MAX_ENTITIES          = 5000;
    using ComponentType                = std::uint8_t;
    const ComponentType MAX_COMPONENTS = 32;
    using Signature                    = std::bitset<MAX_COMPONENTS>;

    // Events
    using EventId = std::uint32_t;
    using ParamId = std::uint32_t;

#define METHOD_LISTENER(EventType, Listener) EventType, std::bind(&Listener, this, std::placeholders::_1)
#define FUNCTION_LISTENER(EventType, Listener) EventType, std::bind(&Listener, std::placeholders::_1)

} // namespace Piccolo

system.h

#pragma once
#include "runtime/function/framework/ecs/types.h"

#include <set>

namespace Piccolo
{
    class System
    {
    public:

        // Example:
        // for (auto const& entity : mEntities)
        // {
        //     auto& rigidBody = gCoordinator.GetComponent<RigidBody>(entity);
        //     auto& transform = gCoordinator.GetComponent<Transform>(entity);
        //     // Forces
        //     auto const& gravity = gCoordinator.GetComponent<Gravity>(entity);
        //     transform.position += rigidBody.velocity * dt;
        //     rigidBody.velocity += gravity.force * dt;
        // }
        virtual void tick(float dt) = 0;

        std::set<Entity> m_entities;
    };
} // namespace Piccolo

component_array.h

#pragma once

#include "runtime/function/framework/ecs/types.h"

#include <array>
#include <cassert>
#include <unordered_map>

namespace Piccolo
{
    class IComponentArray
    {
    public:
        virtual ~IComponentArray()                  = default;
        virtual void entityDestroyed(Entity entity) = 0;
    };

    template<typename T>
    class ComponentArray : public IComponentArray
    {
    public:
        void insertData(Entity entity, T component)
        {
            assert(m_entity_to_index.find(entity) == m_entity_to_index.end() && "Component added to same entity more than once.");

            // Put new entry at end
            size_t newIndex             = m_size;
            m_entity_to_index[entity]   = newIndex;
            m_index_to_entity[newIndex] = entity;
            m_components[newIndex]      = component;
            ++m_size;
        }

        T& getData(Entity entity)
        {
            assert(m_entity_to_index.find(entity) != m_entity_to_index.end() && "Retrieving non-existent component.");

            return m_components[m_entity_to_index[entity]];
        }

        void removeData(Entity entity);
        void entityDestroyed(Entity entity) override;

    private:
        std::array<T, MAX_ENTITIES>        m_components {};
        std::unordered_map<Entity, size_t> m_entity_to_index {};
        std::unordered_map<size_t, Entity> m_index_to_entity {};
        size_t                             m_size {};
    };
} // namespace Piccolo

component_array.cpp

#include "runtime/function/framework/ecs/component_array.h"

namespace Piccolo
{
    template<typename T>
    void ComponentArray<T>::removeData(Entity entity)
    {
        assert(m_entity_to_index.find(entity) != m_entity_to_index.end() && "Removing non-existent component.");

        // Copy element at end into deleted element's place to maintain density
        size_t indexOfRemovedEntity        = m_entity_to_index[entity];
        size_t indexOfLastElement          = m_size - 1;
        m_components[indexOfRemovedEntity] = m_components[indexOfLastElement];

        // Update map to point to moved spot
        Entity entityOfLastElement              = m_index_to_entity[indexOfLastElement];
        m_entity_to_index[entityOfLastElement]  = indexOfRemovedEntity;
        m_index_to_entity[indexOfRemovedEntity] = entityOfLastElement;

        m_entity_to_index.erase(entity);
        m_index_to_entity.erase(indexOfLastElement);
        --m_size;
    }

    template<typename T>
    void ComponentArray<T>::entityDestroyed(Entity entity)
    {
        if (m_entity_to_index.find(entity) != m_entity_to_index.end())
        {
            removeData(entity);
        }
    }
} // namespace Piccolo

entity_manager.h

#pragma once

#include "runtime/function/framework/ecs/types.h"

#include <array>
#include <cassert>
#include <queue>

namespace Piccolo
{
    class EntityManager
    {
    public:
        EntityManager();

        Entity createEntity();
        void   destroyEntity(Entity entity);

        void      setSignature(Entity entity, Signature signature);
        Signature getSignature(Entity entity);

    private:
        std::queue<Entity>                  m_available_entities {};
        std::array<Signature, MAX_ENTITIES> m_signatures {};
        uint32_t                            m_living_entity_count {};
    };
} // namespace Piccolo

entity_manager.cpp

#include "runtime/function/framework/ecs/entity_manager.h"

namespace Piccolo
{
    EntityManager::EntityManager()
    {
        for (Entity entity = 0; entity < MAX_ENTITIES; ++entity)
        {
            m_available_entities.push(entity);
        }
    }

    Entity EntityManager::createEntity()
    {
        assert(m_living_entity_count < MAX_ENTITIES && "Too many entities in existence.");

        Entity id = m_available_entities.front();
        m_available_entities.pop();
        ++m_living_entity_count;

        return id;
    }

    void EntityManager::destroyEntity(Entity entity)
    {
        assert(entity < MAX_ENTITIES && "Entity out of range.");

        m_signatures[entity].reset();
        m_available_entities.push(entity);
        --m_living_entity_count;
    }

    void EntityManager::setSignature(Entity entity, Signature signature)
    {
        assert(entity < MAX_ENTITIES && "Entity out of range.");

        m_signatures[entity] = signature;
    }

    Signature EntityManager::getSignature(Entity entity)
    {
        assert(entity < MAX_ENTITIES && "Entity out of range.");

        return m_signatures[entity];
    }
} // namespace Piccolo

component_manager.h

#pragma once

#include "runtime/function/framework/ecs/component_array.h"

#include <any>
#include <memory>
#include <unordered_map>

namespace Piccolo
{
    class ComponentManager
    {
    public:
        template<typename T>
        void registerComponent()
        {
            const char* type_name = typeid(T).name();

            assert(m_component_types.find(type_name) == m_component_types.end() && "Registering component type more than once.");

            m_component_types.insert({type_name, m_next_component_type});
            m_component.insert({type_name, std::make_shared<ComponentArray<T>>()});
            ++m_next_component_type;
        }

        template<typename T>
        ComponentType getComponentType()
        {
            const char* type_name = typeid(T).name();

            assert(m_component_types.find(type_name) != m_component_types.end() && "Component not registered before use.");

            return m_component_types[type_name];
        }

        template<typename T>
        void addComponent(Entity entity, T component)
        {
            getComponentArray<T>()->insertData(entity, component);
        }

        template<typename T>
        void removeComponent(Entity entity)
        {
            getComponentArray<T>()->removeData(entity);
        }

        template<typename T>
        T& getComponent(Entity entity)
        {
            return getComponentArray<T>()->getData(entity);
        }

        void entityDestroyed(Entity entity);

    private:
        template<typename T>
        std::shared_ptr<ComponentArray<T>> getComponentArray()
        {
            const char* type_name = typeid(T).name();

            assert(m_component_types.find(type_name) != m_component_types.end() && "Component not registered before use.");

            return std::static_pointer_cast<ComponentArray<T>>(m_component[type_name]);
        }

    private:
        std::unordered_map<const char*, ComponentType>                    m_component_types {};
        std::unordered_map<const char*, std::shared_ptr<IComponentArray>> m_component {};
        ComponentType                                                     m_next_component_type {};
    };
} // namespace Piccolo

component_manager.cpp

#include "runtime/function/framework/ecs/component_manager.h"

namespace Piccolo
{
    void ComponentManager::entityDestroyed(Entity entity)
    {
        for (auto const& pair : m_component)
        {
            auto const& component = pair.second;
            component->entityDestroyed(entity);
        }
    }
} // namespace Piccolo

system_manager.h

#pragma once

#include "runtime/function/framework/ecs/system.h"

#include <cassert>
#include <memory>
#include <unordered_map>

namespace Piccolo
{
    class SystemManager
    {
    public:
        template<typename T>
        std::shared_ptr<T> registerSystem()
        {
            const char* type_name = typeid(T).name();

            assert(m_systems.find(type_name) == m_systems.end() && "Registering system more than once.");

            auto system = std::make_shared<T>();
            m_systems.insert({type_name, system});
            return system;
        }

        template<typename T>
        void setSignature(Signature signature)
        {
            const char* type_name = typeid(T).name();

            assert(m_systems.find(type_name) != m_systems.end() && "System used before registered.");

            m_signatures.insert({type_name, signature});
        }

        void entityDestroyed(Entity entity);
        void entitySignatureChanged(Entity entity, Signature entitySignature);

    private:
        std::unordered_map<const char*, Signature>               m_signatures {};
        std::unordered_map<const char*, std::shared_ptr<System>> m_systems {};
    };
} // namespace Piccolo

system_manager.cpp

#include "runtime/function/framework/ecs/system_manager.h"

namespace Piccolo
{
    void SystemManager::entityDestroyed(Entity entity)
    {
        for (auto const& pair : m_systems)
        {
            auto const& system = pair.second;
            system->m_entities.erase(entity);
        }
    }

    void SystemManager::entitySignatureChanged(Entity entity, Signature entitySignature)
    {
        for (auto const& pair : m_systems)
        {
            auto const& type            = pair.first;
            auto const& system          = pair.second;
            auto const& systemSignature = m_signatures[type];

            if ((entitySignature & systemSignature) == systemSignature)
                system->m_entities.insert(entity);
            else
                system->m_entities.erase(entity);
        }
    }
} // namespace Piccolo