implement ECS library
This commit is contained in:
parent
4591ad623e
commit
5273b8017e
11 changed files with 444 additions and 0 deletions
13
libs/component.hpp
Normal file
13
libs/component.hpp
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
#ifndef COMPONENT_HPP
|
||||||
|
#define COMPONENT_HPP
|
||||||
|
|
||||||
|
#include <bitset>
|
||||||
|
#include <cstdint>
|
||||||
|
|
||||||
|
using ComponentType = std::uint8_t;
|
||||||
|
|
||||||
|
const ComponentType MAX_COMPONENTS = 32;
|
||||||
|
|
||||||
|
using Signature = std::bitset<MAX_COMPONENTS>;
|
||||||
|
|
||||||
|
#endif // COMPONENT_HPP
|
||||||
47
libs/componentarray.cpp
Normal file
47
libs/componentarray.cpp
Normal file
|
|
@ -0,0 +1,47 @@
|
||||||
|
#include <cassert>
|
||||||
|
#include "componentarray.hpp"
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
void ComponentArray<T>::InsertData(Entity entity, T component) {
|
||||||
|
assert(mEntityToIndexMap.find(entity) == mEntityToIndexMap.end() &&
|
||||||
|
"Component added to same entity more than once.");
|
||||||
|
|
||||||
|
// Put new entry at end and update the maps
|
||||||
|
size_t newIndex = mSize;
|
||||||
|
mEntityToIndexMap[entity] = newIndex;
|
||||||
|
mIndexToEntityMap[newIndex] = entity;
|
||||||
|
mComponentArray[newIndex] = component;
|
||||||
|
++mSize;
|
||||||
|
};
|
||||||
|
template <typename T> void ComponentArray<T>::RemoveData(Entity entity) {
|
||||||
|
assert(mEntityToIndexMap.find(entity) != mEntityToIndexMap.end() &&
|
||||||
|
"Removing non-existent component.");
|
||||||
|
|
||||||
|
// Copy element at end into deleted element's place to maintain density
|
||||||
|
size_t indexOfRemovedEntity = mEntityToIndexMap[entity];
|
||||||
|
size_t indexOfLastElement = mSize - 1;
|
||||||
|
mComponentArray[indexOfRemovedEntity] = mComponentArray[indexOfLastElement];
|
||||||
|
|
||||||
|
// Update map to point to moved spot
|
||||||
|
Entity entityOfLastElement = mIndexToEntityMap[indexOfLastElement];
|
||||||
|
mEntityToIndexMap[entityOfLastElement] = indexOfRemovedEntity;
|
||||||
|
mIndexToEntityMap[indexOfRemovedEntity] = entityOfLastElement;
|
||||||
|
|
||||||
|
mEntityToIndexMap.erase(entity);
|
||||||
|
mIndexToEntityMap.erase(indexOfLastElement);
|
||||||
|
|
||||||
|
--mSize;
|
||||||
|
};
|
||||||
|
template <typename T> T &ComponentArray<T>::GetData(Entity entity) {
|
||||||
|
assert(mEntityToIndexMap.find(entity) != mEntityToIndexMap.end() &&
|
||||||
|
"Retrieving non-existent component.");
|
||||||
|
|
||||||
|
// Return a reference to the entity's component
|
||||||
|
return mComponentArray[mEntityToIndexMap[entity]];
|
||||||
|
};
|
||||||
|
template <typename T> void ComponentArray<T>::EntityDestroyed(Entity entity) {
|
||||||
|
if (mEntityToIndexMap.find(entity) != mEntityToIndexMap.end()) {
|
||||||
|
// Remove the entity's component if it existed
|
||||||
|
RemoveData(entity);
|
||||||
|
}
|
||||||
|
};
|
||||||
37
libs/componentarray.hpp
Normal file
37
libs/componentarray.hpp
Normal file
|
|
@ -0,0 +1,37 @@
|
||||||
|
#ifndef COMPONENTARRAY_HPP
|
||||||
|
#define COMPONENTARRAY_HPP
|
||||||
|
|
||||||
|
#include <unordered_map>
|
||||||
|
#include <array>
|
||||||
|
#include "entity.hpp"
|
||||||
|
|
||||||
|
class IComponentArray {
|
||||||
|
public:
|
||||||
|
virtual ~IComponentArray() = default;
|
||||||
|
virtual void EntityDestroyed(Entity entity) = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename T> class ComponentArray : public IComponentArray {
|
||||||
|
// The packed array of components (of generic type T),
|
||||||
|
// set to a specified maximum amount, matching the maximum number
|
||||||
|
// of entities allowed to exist simultaneously, so that each entity
|
||||||
|
// has a unique spot.
|
||||||
|
std::array<T, MAX_ENTITIES> mComponentArray;
|
||||||
|
|
||||||
|
// Map from an entity ID to an array index.
|
||||||
|
std::unordered_map<Entity, size_t> mEntityToIndexMap;
|
||||||
|
|
||||||
|
// Map from an array index to an entity ID.
|
||||||
|
std::unordered_map<size_t, Entity> mIndexToEntityMap;
|
||||||
|
|
||||||
|
// Total size of valid entries in the array.
|
||||||
|
size_t mSize;
|
||||||
|
|
||||||
|
public:
|
||||||
|
void InsertData(Entity entity, T component);
|
||||||
|
void RemoveData(Entity entity);
|
||||||
|
T &GetData(Entity entity);
|
||||||
|
void EntityDestroyed(Entity entity) override;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // COMPONENTARRAY_HPP
|
||||||
63
libs/componentmanager.cpp
Normal file
63
libs/componentmanager.cpp
Normal file
|
|
@ -0,0 +1,63 @@
|
||||||
|
#include "componentmanager.hpp"
|
||||||
|
#include "componentarray.hpp"
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
// Private functions
|
||||||
|
template <typename T>
|
||||||
|
std::shared_ptr<ComponentArray<T>> ComponentManager::GetComponentArray() {
|
||||||
|
const char *typeName = typeid(T).name();
|
||||||
|
|
||||||
|
assert(mComponentTypes.find(typeName) != mComponentTypes.end() &&
|
||||||
|
"Component not registered before use.");
|
||||||
|
|
||||||
|
return std::static_pointer_cast<ComponentArray<T>>(
|
||||||
|
mComponentArrays[typeName]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Public functions
|
||||||
|
template <typename T> void ComponentManager::RegisterComponent() {
|
||||||
|
const char *typeName = typeid(T).name();
|
||||||
|
|
||||||
|
assert(mComponentTypes.find(typeName) == mComponentTypes.end() &&
|
||||||
|
"Registering component type more than once.");
|
||||||
|
|
||||||
|
// Add this component type to the component type map
|
||||||
|
mComponentTypes.insert({typeName, mNextComponentType});
|
||||||
|
|
||||||
|
// Create a ComponentArray pointer and add it to the component arrays map
|
||||||
|
mComponentArrays.insert({typeName, std::make_shared<ComponentArray<T>>()});
|
||||||
|
|
||||||
|
// Increment the value so that the next component registered will be different
|
||||||
|
++mNextComponentType;
|
||||||
|
};
|
||||||
|
template <typename T> ComponentType ComponentManager::GetComponentType() {
|
||||||
|
const char *typeName = typeid(T).name();
|
||||||
|
|
||||||
|
assert(mComponentTypes.find(typeName) != mComponentTypes.end() &&
|
||||||
|
"Component not registered before use.");
|
||||||
|
|
||||||
|
// Return this component's type - used for creating signatures
|
||||||
|
return mComponentTypes[typeName];
|
||||||
|
}
|
||||||
|
template <typename T>
|
||||||
|
void ComponentManager::AddComponent(Entity entity, T component) {
|
||||||
|
// Add a component to the array for an entity
|
||||||
|
GetComponentArray<T>()->InsertData(entity, component);
|
||||||
|
}
|
||||||
|
template <typename T> void ComponentManager::RemoveComponent(Entity entity) {
|
||||||
|
// Remove a component from the array for an entity
|
||||||
|
GetComponentArray<T>()->RemoveData(entity);
|
||||||
|
}
|
||||||
|
template <typename T> T &ComponentManager::GetComponent(Entity entity) {
|
||||||
|
// Get a reference to a component from the array for an entity
|
||||||
|
return GetComponentArray<T>()->GetData(entity);
|
||||||
|
}
|
||||||
|
void ComponentManager::EntityDestroyed(Entity entity) {
|
||||||
|
// Notify each component array that an entity has been destroyed
|
||||||
|
// If it has a component for that entity, it will remove it
|
||||||
|
for (auto const &pair : mComponentArrays) {
|
||||||
|
auto const &component = pair.second;
|
||||||
|
|
||||||
|
component->EntityDestroyed(entity);
|
||||||
|
}
|
||||||
|
};
|
||||||
35
libs/componentmanager.hpp
Normal file
35
libs/componentmanager.hpp
Normal file
|
|
@ -0,0 +1,35 @@
|
||||||
|
#ifndef COMPONENTMANAGER_HPP
|
||||||
|
#define COMPONENTMANAGER_HPP
|
||||||
|
|
||||||
|
#include <unordered_map>
|
||||||
|
#include <memory>
|
||||||
|
#include <cassert>
|
||||||
|
#include "component.hpp"
|
||||||
|
#include "componentarray.hpp"
|
||||||
|
|
||||||
|
class ComponentManager {
|
||||||
|
// Map from type string pointer to a component type
|
||||||
|
std::unordered_map<const char *, ComponentType> mComponentTypes{};
|
||||||
|
|
||||||
|
// Map from type string pointer to a component array
|
||||||
|
std::unordered_map<const char *, std::shared_ptr<IComponentArray>>
|
||||||
|
mComponentArrays{};
|
||||||
|
|
||||||
|
// The component type to be assigned to the next registered component -
|
||||||
|
// starting at 0
|
||||||
|
ComponentType mNextComponentType{};
|
||||||
|
|
||||||
|
// Convenience function to get the statically casted pointer to the
|
||||||
|
// ComponentArray of type T.
|
||||||
|
template <typename T> std::shared_ptr<ComponentArray<T>> GetComponentArray();
|
||||||
|
|
||||||
|
public:
|
||||||
|
template <typename T> void RegisterComponent();
|
||||||
|
template <typename T> ComponentType GetComponentType();
|
||||||
|
template <typename T> void AddComponent(Entity entity, T component);
|
||||||
|
template <typename T> void RemoveComponent(Entity entity);
|
||||||
|
template <typename T> T &GetComponent(Entity entity);
|
||||||
|
void EntityDestroyed(Entity entity);
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // COMPONENTMANAGER_HPP
|
||||||
63
libs/coordinator.cpp
Normal file
63
libs/coordinator.cpp
Normal file
|
|
@ -0,0 +1,63 @@
|
||||||
|
#include "coordinator.hpp"
|
||||||
|
|
||||||
|
void Coordinator::Init() {
|
||||||
|
// Create pointers to each manager
|
||||||
|
mComponentManager = std::make_unique<ComponentManager>();
|
||||||
|
mEntityManager = std::make_unique<EntityManager>();
|
||||||
|
mSystemManager = std::make_unique<SystemManager>();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Entity methods
|
||||||
|
Entity Coordinator::CreateEntity() { return mEntityManager->CreateEntity(); }
|
||||||
|
|
||||||
|
void Coordinator::DestroyEntity(Entity entity) {
|
||||||
|
mEntityManager->DestroyEntity(entity);
|
||||||
|
|
||||||
|
mComponentManager->EntityDestroyed(entity);
|
||||||
|
|
||||||
|
mSystemManager->EntityDestroyed(entity);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Component methods
|
||||||
|
template <typename T> void Coordinator::RegisterComponent() {
|
||||||
|
mComponentManager->RegisterComponent<T>();
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
void Coordinator::AddComponent(Entity entity, T component) {
|
||||||
|
mComponentManager->AddComponent<T>(entity, component);
|
||||||
|
|
||||||
|
auto signature = mEntityManager->GetSignature(entity);
|
||||||
|
signature.set(mComponentManager->GetComponentType<T>(), true);
|
||||||
|
mEntityManager->SetSignature(entity, signature);
|
||||||
|
|
||||||
|
mSystemManager->EntitySignatureChanged(entity, signature);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T> void Coordinator::RemoveComponent(Entity entity) {
|
||||||
|
mComponentManager->RemoveComponent<T>(entity);
|
||||||
|
|
||||||
|
auto signature = mEntityManager->GetSignature(entity);
|
||||||
|
signature.set(mComponentManager->GetComponentType<T>(), false);
|
||||||
|
mEntityManager->SetSignature(entity, signature);
|
||||||
|
|
||||||
|
mSystemManager->EntitySignatureChanged(entity, signature);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T> T &Coordinator::GetComponent(Entity entity) {
|
||||||
|
return mComponentManager->GetComponent<T>(entity);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T> ComponentType Coordinator::GetComponentType() {
|
||||||
|
return mComponentManager->GetComponentType<T>();
|
||||||
|
}
|
||||||
|
|
||||||
|
// System methods
|
||||||
|
template <typename T> std::shared_ptr<T> Coordinator::RegisterSystem() {
|
||||||
|
return mSystemManager->RegisterSystem<T>();
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
void Coordinator::SetSystemSignature(Signature signature) {
|
||||||
|
mSystemManager->SetSignature<T>(signature);
|
||||||
|
}
|
||||||
30
libs/coordinator.hpp
Normal file
30
libs/coordinator.hpp
Normal file
|
|
@ -0,0 +1,30 @@
|
||||||
|
#ifndef COORDINATOR_HPP
|
||||||
|
#define COORDINATOR_HPP
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
#include "componentmanager.hpp"
|
||||||
|
#include "entity.hpp"
|
||||||
|
#include "system.hpp"
|
||||||
|
|
||||||
|
class Coordinator {
|
||||||
|
std::unique_ptr<ComponentManager> mComponentManager;
|
||||||
|
std::unique_ptr<EntityManager> mEntityManager;
|
||||||
|
std::unique_ptr<SystemManager> mSystemManager;
|
||||||
|
|
||||||
|
public:
|
||||||
|
void Init();
|
||||||
|
// Entity methods
|
||||||
|
Entity CreateEntity();
|
||||||
|
void DestroyEntity(Entity entity);
|
||||||
|
// Component methods
|
||||||
|
template <typename T> void RegisterComponent();
|
||||||
|
template <typename T> void AddComponent(Entity entity, T component);
|
||||||
|
template <typename T> void RemoveComponent(Entity entity);
|
||||||
|
template <typename T> T &GetComponent(Entity entity);
|
||||||
|
template <typename T> ComponentType GetComponentType();
|
||||||
|
// System methods
|
||||||
|
template <typename T> std::shared_ptr<T> RegisterSystem();
|
||||||
|
template <typename T> void SetSystemSignature(Signature signature);
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // COORDINATOR_HPP
|
||||||
42
libs/entity.cpp
Normal file
42
libs/entity.cpp
Normal file
|
|
@ -0,0 +1,42 @@
|
||||||
|
#include "entity.hpp"
|
||||||
|
#include <cassert>
|
||||||
|
|
||||||
|
EntityManager::EntityManager() {
|
||||||
|
// Initialize the queue with all possible entity IDs
|
||||||
|
for (Entity entity = 0; entity < MAX_ENTITIES; ++entity) {
|
||||||
|
mAvailableEntities.push(entity);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Entity EntityManager::CreateEntity() {
|
||||||
|
assert(mLivingEntityCount < MAX_ENTITIES &&
|
||||||
|
"Too many entities in existence.");
|
||||||
|
|
||||||
|
// Take an ID from the front of the queue
|
||||||
|
Entity id = mAvailableEntities.front();
|
||||||
|
mAvailableEntities.pop();
|
||||||
|
++mLivingEntityCount;
|
||||||
|
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
void EntityManager::DestroyEntity(Entity entity) {
|
||||||
|
assert(entity < MAX_ENTITIES && "Entity out of range.");
|
||||||
|
|
||||||
|
// Invalidate the destroyed entity's signature
|
||||||
|
mSignatures[entity].reset();
|
||||||
|
|
||||||
|
// Put the destroyed ID at the back of the queue
|
||||||
|
mAvailableEntities.push(entity);
|
||||||
|
--mLivingEntityCount;
|
||||||
|
}
|
||||||
|
void EntityManager::SetSignature(Entity entity, Signature signature) {
|
||||||
|
assert(entity < MAX_ENTITIES && "Entity out of range.");
|
||||||
|
|
||||||
|
// Put this entity's signature into the array
|
||||||
|
mSignatures[entity] = signature;
|
||||||
|
}
|
||||||
|
Signature EntityManager::GetSignature(Entity entity) {
|
||||||
|
assert(entity < MAX_ENTITIES && "Entity out of range.");
|
||||||
|
|
||||||
|
// Get this entity's signature from the array
|
||||||
|
return mSignatures[entity];
|
||||||
|
}
|
||||||
31
libs/entity.hpp
Normal file
31
libs/entity.hpp
Normal file
|
|
@ -0,0 +1,31 @@
|
||||||
|
#ifndef ENTITY_HPP
|
||||||
|
#define ENTITY_HPP
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
#include <queue>
|
||||||
|
#include <array>
|
||||||
|
#include "component.hpp"
|
||||||
|
|
||||||
|
using Entity = std::uint32_t;
|
||||||
|
|
||||||
|
const Entity MAX_ENTITIES = 5000;
|
||||||
|
|
||||||
|
class EntityManager {
|
||||||
|
// Queue of unused entity IDs
|
||||||
|
std::queue<Entity> mAvailableEntities{};
|
||||||
|
|
||||||
|
// Array of signatures where the index corresponds to the entity ID
|
||||||
|
std::array<Signature, MAX_ENTITIES> mSignatures{};
|
||||||
|
|
||||||
|
// Total living entities - used to keep limits on how many exist
|
||||||
|
uint32_t mLivingEntityCount{};
|
||||||
|
|
||||||
|
public:
|
||||||
|
EntityManager();
|
||||||
|
Entity CreateEntity();
|
||||||
|
void DestroyEntity(Entity entity);
|
||||||
|
void SetSignature(Entity entity, Signature signature);
|
||||||
|
Signature GetSignature(Entity entity);
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // ENTITY_HPP
|
||||||
54
libs/system.cpp
Normal file
54
libs/system.cpp
Normal file
|
|
@ -0,0 +1,54 @@
|
||||||
|
#include "system.hpp"
|
||||||
|
|
||||||
|
#include <cassert>
|
||||||
|
|
||||||
|
template <typename T> std::shared_ptr<T> SystemManager::RegisterSystem() {
|
||||||
|
const char *typeName = typeid(T).name();
|
||||||
|
|
||||||
|
assert(mSystems.find(typeName) == mSystems.end() &&
|
||||||
|
"Registering system more than once.");
|
||||||
|
|
||||||
|
// Create a pointer to the system and return it so it can be used externally
|
||||||
|
auto system = std::make_shared<T>();
|
||||||
|
mSystems.insert({typeName, system});
|
||||||
|
return system;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T> void SystemManager::SetSignature(Signature signature) {
|
||||||
|
const char *typeName = typeid(T).name();
|
||||||
|
|
||||||
|
assert(mSystems.find(typeName) != mSystems.end() &&
|
||||||
|
"System used before registered.");
|
||||||
|
|
||||||
|
// Set the signature for this system
|
||||||
|
mSignatures.insert({typeName, signature});
|
||||||
|
}
|
||||||
|
|
||||||
|
void SystemManager::EntityDestroyed(Entity entity) {
|
||||||
|
// Erase a destroyed entity from all system lists
|
||||||
|
// mEntities is a set so no check needed
|
||||||
|
for (auto const &pair : mSystems) {
|
||||||
|
auto const &system = pair.second;
|
||||||
|
|
||||||
|
system->mEntities.erase(entity);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void SystemManager::EntitySignatureChanged(Entity entity,
|
||||||
|
Signature entitySignature) {
|
||||||
|
// Notify each system that an entity's signature changed
|
||||||
|
for (auto const &pair : mSystems) {
|
||||||
|
auto const &type = pair.first;
|
||||||
|
auto const &system = pair.second;
|
||||||
|
auto const &systemSignature = mSignatures[type];
|
||||||
|
|
||||||
|
// Entity signature matches system signature - insert into set
|
||||||
|
if ((entitySignature & systemSignature) == systemSignature) {
|
||||||
|
system->mEntities.insert(entity);
|
||||||
|
}
|
||||||
|
// Entity signature does not match system signature - erase from set
|
||||||
|
else {
|
||||||
|
system->mEntities.erase(entity);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
29
libs/system.hpp
Normal file
29
libs/system.hpp
Normal file
|
|
@ -0,0 +1,29 @@
|
||||||
|
#ifndef SYSTEM_HPP
|
||||||
|
#define SYSTEM_HPP
|
||||||
|
|
||||||
|
#include <set>
|
||||||
|
#include <memory>
|
||||||
|
#include <unordered_map>
|
||||||
|
#include "component.hpp"
|
||||||
|
#include "entity.hpp"
|
||||||
|
|
||||||
|
class System {
|
||||||
|
public:
|
||||||
|
std::set<Entity> mEntities;
|
||||||
|
};
|
||||||
|
|
||||||
|
class SystemManager {
|
||||||
|
// Map from system type string pointer to a signature
|
||||||
|
std::unordered_map<const char *, Signature> mSignatures{};
|
||||||
|
|
||||||
|
// Map from system type string pointer to a system pointer
|
||||||
|
std::unordered_map<const char *, std::shared_ptr<System>> mSystems{};
|
||||||
|
|
||||||
|
public:
|
||||||
|
template <typename T> std::shared_ptr<T> RegisterSystem();
|
||||||
|
template <typename T> void SetSignature(Signature signature);
|
||||||
|
void EntityDestroyed(Entity entity);
|
||||||
|
void EntitySignatureChanged(Entity entity, Signature entitySignature);
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // !SYSTEM_HPP
|
||||||
Loading…
Add table
Reference in a new issue