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