Skip to content

Building An ECS

Entity

An Entity will represented as an unsigned int.

#pragma once

using Entity = unsigned int;

const Entity MAX_ENTITIES = 20000;
const Entity NULL_ENTITY = 0;

Component

Components will add functionality for an entity.

Component Class

#pragma once

#include <bitset>

const std::uint32_t MAX_COMPONENT_TYPES = 32;

using ComponentType = std::uint32_t;
using ComponentSignature = std::bitset<MAX_COMPONENT_TYPES>;

Component Array

#pragma once

#include <unordered_map>
#include <cassert>

#include "../entity/entity.h"
#include "component.h"

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

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

        size_t newIndex = size;
        entityToIndexMap[entity] = newIndex;
        indexToEntityMap[newIndex] = entity;
        components[newIndex] = component;

        size++;
    }

    void UpdateData(Entity entity, T component) {
        assert(entityToIndexMap.find(entity) != entityToIndexMap.end() && "Component hasn't been added!");

        components[entityToIndexMap[entity]] = component;
    }

    void RemoveData(Entity entity) {
        assert(entityToIndexMap.find(entity) != entityToIndexMap.end() && "Removing non-existent component!");

        // Copy element at end into deleted element's place to maintain array density
        size_t indexOfRemovedEntity = entityToIndexMap[entity];
        size_t indexOfLastElement = size - 1;
        components[indexOfRemovedEntity] = components[indexOfLastElement];

        // Update map to point to moved spot
        Entity entityOfLastElement = indexToEntityMap[indexOfLastElement];
        entityToIndexMap[entityOfLastElement] = indexOfRemovedEntity;
        indexToEntityMap[indexOfRemovedEntity] = entityOfLastElement;

        entityToIndexMap.erase(entity);
        indexToEntityMap.erase(indexOfLastElement);

        size--;
    }

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

        return components[entityToIndexMap[entity]];
    }

    bool HasData(Entity entity) {
        return entityToIndexMap.find(entity) != entityToIndexMap.end();
    }

    void EntityDestroyed(Entity entity) override {
        if (entityToIndexMap.find(entity) != entityToIndexMap.end()) {
            RemoveData(entity);
        }
    }

  private:
    std::array<T, MAX_ENTITIES> components;
    std::unordered_map<Entity, size_t> entityToIndexMap;
    std::unordered_map<size_t, Entity> indexToEntityMap;
    size_t size = 0;
};

System

Systems will process components for an entity based on a signature.

#pragma once

#include <set>

#include "../entity/entity.h"
#include "../../scene/scene.h"

const unsigned int MAX_SYSTEMS = 32;

class ECSystem {
  protected:
    bool enabled = false;
    std::set<Entity> entities;

  public:
    virtual void Initialize()  {
        enabled = true;
    }

    virtual void RegisterEntity(Entity entity) {
        entities.insert(entity);
    }

    virtual void UnregisterEntity(Entity entity) {
        entities.erase(entity);
    }

    virtual void Enable() {
        enabled = true;
    }

    virtual void Disable() {
        enabled = false;
    }

    bool IsEnabled() {
        return enabled;
    }

    // Event hooks
    virtual void Update(float deltaTime) {}
    virtual void PhysicsUpdate(float deltaTime) {}
    virtual void Render() {}
    virtual void OnSceneStart(Scene* scene) {}
    virtual void OnSceneEnd(Scene* scene) {}
};

Scene

Entities will be placed in a scene.

#pragma once

#include <vector>
#include <unordered_map>

#include "../ecs/entity/entity.h"
#include "../utils/helper.h"

struct SceneNode {
    Entity entity = NULL_ENTITY;
    Entity parent = NULL_ENTITY;
    std::vector<SceneNode> children = {};
};

struct Scene {
    SceneNode rootNode = {};
    std::unordered_map<Entity, SceneNode> sceneNodes = {};
};