I plan to create a 3D space simulator (realistic (orbital) physics, procedural planets) based on this ECS library.
I am very new to game development so feedback is very welcome. I am especially concerned about performance/bottlenecks.
#pragma once
#include <iostream>
#include <vector>
#include <chrono>
namespace ecs
{
typedef size_t ID;
const ID NULL_ID = (0-1);//(size_t)(0-1) == SIZE_MAX;
typedef std::chrono::nanoseconds Duration;
typedef std::chrono::time_point<std::chrono::high_resolution_clock> TimePoint;
typedef std::chrono::high_resolution_clock Clock;
/********************ENTITIES and COMPONENTS********************/
class Entity
{
template<typename T, typename... Targs>
friend class Iterator;
public:
static Entity createEntity();
void removeEntity();
template<typename T, typename... Args>
void createComponent(const Args&... args);
template<typename T>
void removeComponent();
template<typename T>
T & getComponent();
template<typename T>
bool hasComponents();
template<typename T1, typename T2, typename... Targs>
bool hasComponents();
ID getID()
{
return entityID;
}
private:
Entity(ID entityID) : entityID(entityID) {}
ID entityID;
struct EntityEntry
{
std::vector<void (*)(ID)> removeComponentFunctions = std::vector<void (*)(ID)>();
bool exists = true;
};
static std::vector<EntityEntry> entityEntryArray;
static std::vector<ID> freeEntityIDs;
template<typename T>
static std::vector<T> componentArray;
template<typename T>
static std::vector<ID> entityToComponentIDs;
static void removeEntity(ID entityID);
template<typename T, typename... Args>
static void createComponent(ID entityID, const Args&... args);
template<typename T>
static void removeComponent(ID entityID);
template<typename T>
static T & getComponent(ID entityID);
template<typename T>
static bool hasComponents(ID entityID);
template<typename T1, typename T2, typename... Targs>
static bool hasComponents(ID entityID);
};
template<typename T>
std::vector<T> Entity::componentArray = std::vector<T>();
template<typename T>
std::vector<ID> Entity::entityToComponentIDs = std::vector<ID>();
std::vector<ID> Entity::freeEntityIDs = std::vector<ID>();
std::vector<Entity::EntityEntry> Entity::entityEntryArray = std::vector<EntityEntry>();
Entity Entity::createEntity()
{
ID tempID;
if(freeEntityIDs.empty())
{
tempID = entityEntryArray.size();
entityEntryArray.emplace_back();
}
else
{
tempID = freeEntityIDs.back();
freeEntityIDs.pop_back();
entityEntryArray[tempID].exists = true;
}
return Entity(tempID);
}
void Entity::removeEntity()
{
removeEntity(this->entityID);
}
template<typename T, typename... Args>
void Entity::createComponent(const Args&... args)
{
createComponent<T>(this->entityID, args...);
}
template<typename T>
void Entity::removeComponent()
{
removeComponent<T>(this->entityID);
}
template<typename T>
T & Entity::getComponent()
{
return getComponent<T>(this->entityID);
}
template<typename T>
bool Entity::hasComponents()
{
return hasComponents<T>(this->entityID);
}
template<typename T1, typename T2, typename... Targs>
bool Entity::hasComponents()
{
return hasComponents<T1, T2, Targs...>(this->entityID);
}
void Entity::removeEntity(ID entityID)
{
freeEntityIDs.push_back(entityID);
for(void (*f)(ID) : entityEntryArray[entityID].removeComponentFunctions)
{
f(entityID);
}
entityEntryArray[entityID].removeComponentFunctions = std::vector<void (*)(ID)>();
entityEntryArray[entityID].exists = false;
}
template<typename T, typename... Args>
void Entity::createComponent(ID entityID, const Args&... args)
{
if(hasComponents<T>(entityID))
{
return;
}
if(entityID >= entityToComponentIDs<T>.size())
{
entityToComponentIDs<T>.resize(entityID+1, NULL_ID);
entityToComponentIDs<T>[entityID] = componentArray<T>.size();
componentArray<T>.emplace_back(args...);
}
else if(entityID < entityToComponentIDs<T>.size())
{
bool foundBiggerID = false;
for(ID i = entityID + 1; i < entityToComponentIDs<T>.size(); i++)
{
if(entityToComponentIDs<T>[i] != NULL_ID)
{
if(foundBiggerID == false)
{
componentArray<T>.emplace(componentArray<T>.begin() + entityToComponentIDs<T>[i], args...);
entityToComponentIDs<T>[entityID] = entityToComponentIDs<T>[i];
foundBiggerID = true;
}
entityToComponentIDs<T>[i] += 1;
}
}
}
entityEntryArray[entityID].removeComponentFunctions.push_back(&removeComponent<T>);
}
template<typename T>
void Entity::removeComponent(ID entityID)
{
if(!hasComponents<T>(entityID))
{
return;
}
componentArray<T>.erase(componentArray<T>.begin() + entityToComponentIDs<T>[entityID]);
for(ID i = entityID; i<entityToComponentIDs<T>.size(); i++)
{
if(entityToComponentIDs<T>[i] != NULL_ID)
{
entityToComponentIDs<T>[i] -= 1;
}
}
entityToComponentIDs<T>[entityID] = NULL_ID;
}
template<typename T>
T & Entity::getComponent(ID entityID)
{
return componentArray<T>[entityToComponentIDs<T>[entityID]];
}
template<typename T>
bool Entity::hasComponents(ID entityID)
{
return entityToComponentIDs<T>.size() > entityID && entityToComponentIDs<T>[entityID] != NULL_ID;
}
template<typename T1, typename T2, typename... Targs>
bool Entity::hasComponents(ID entityID)
{
return hasComponents<T1>(entityID) && hasComponents<T2, Targs...>(entityID);
}
template<>
bool Entity::hasComponents<void>(ID entityID)
{
return entityEntryArray.size() > entityID && entityEntryArray[entityID].exists;
}
template<typename T, typename... Targs>
class Iterator
{
private:
ID entityID;
public:
Iterator() : entityID(NULL_ID) {}
explicit Iterator(ID entityID) : entityID(entityID) {}
explicit Iterator(Entity entity) : entityID(entity.entityID) {}
Iterator<T, Targs...> begin();
Iterator<T, Targs...> end();
void operator= (const Iterator<T, Targs...>& a);
bool operator== (const Iterator<T, Targs...>& a);
bool operator!= (const Iterator<T, Targs...>& a);
Entity operator*();
Entity operator->();
Iterator<T, Targs...> operator++();
};
template<typename T, typename... Targs>
Iterator<T, Targs...> Iterator<T, Targs...>::begin()
{
ID i = 0;
while(end().entityID > i)
{
if(!Entity::hasComponents<T, Targs...>(i))
{
i+=1;
continue;
}
return Iterator<T, Targs...>(i);
}
return end();
}
template<typename T, typename... Targs>
Iterator<T, Targs...> Iterator<T, Targs...>::end()
{
return Iterator<T, Targs...>(Entity::entityEntryArray.size());
}
template<typename T, typename... Targs>
void Iterator<T, Targs...>::operator= (const Iterator<T, Targs...>& a)
{
entityID = a.entityID;
}
template<typename T, typename... Targs>
bool Iterator<T, Targs...>::operator== (const Iterator<T, Targs...>& a)
{
return entityID == a.entityID;
}
template<typename T, typename... Targs>
bool Iterator<T, Targs...>::operator!= (const Iterator<T, Targs...>& a)
{
return entityID != a.entityID;
}
template<typename T, typename... Targs>
Entity Iterator<T, Targs...>::operator*()
{
return Entity(entityID);
}
template<typename T, typename... Targs>
Entity Iterator<T, Targs...>::operator->()
{
return Entity(entityID);
}
template<typename T, typename... Targs>
Iterator<T, Targs...> Iterator<T, Targs...>::operator++() // Prefix Increment
{
entityID+=1;
while(end().entityID > entityID)
{
if(Entity::hasComponents<T, Targs...>(entityID))
{
return Iterator<T, Targs...>(entityID);
}
entityID+=1;
}
entityID = end().entityID;
return end();
}
/********************SYSTEMS********************/
class SystemManager
{
public:
static void addSystem(void (*update)(), const Duration& deltaTime);
template<typename T>
static void addSystem(void (*update)(const T&));
static void removeSystem(void (*update)());
template<typename T>
static void removeSystem(void (*update)(const T&));
static void runSystems();
template<typename T>
static void throwEvent(const T& event);
private:
struct TimeBasedSystem
{
TimeBasedSystem(void (*update)(), Duration deltaTime) :
update(update),
deltaTime(deltaTime),
lastUpdateCallTime(TimePoint())
{
}
void (*update)();
Duration deltaTime;
TimePoint lastUpdateCallTime;
};
struct EventRegisterHelper
{
explicit EventRegisterHelper(void (*f)())
{
runEventBasedSystemsList.push_back(f);
}
};
template<typename T>
static void runEventBasedSystems();
static std::vector<TimeBasedSystem> timeBasedSystems;
static std::vector<void(*)()> runEventBasedSystemsList;
template<typename T>
static std::vector<void(*)(const T&)> eventBasedSystem;
template<typename T>
static std::vector<T> eventQueue;
};
std::vector<SystemManager::TimeBasedSystem> SystemManager::timeBasedSystems = std::vector<SystemManager::TimeBasedSystem>();
std::vector<void(*)()> SystemManager::runEventBasedSystemsList = std::vector<void(*)()>();
template<typename T>
std::vector<void(*)(const T&)> SystemManager::eventBasedSystem = std::vector<void(*)(const T&)>();
template<typename T>
std::vector<T> SystemManager::eventQueue = std::vector<T>();
void SystemManager::addSystem(void (*update)(), const Duration& deltaTime)
{
timeBasedSystems.emplace_back(update, deltaTime);
}
template<typename T>
void SystemManager::addSystem(void (*update)(const T&))
{
eventBasedSystem<T>.push_back(update);
}
void SystemManager::removeSystem(void (*update)())
{
for(size_t i = 0; i<timeBasedSystems.size(); ++i)
{
if(timeBasedSystems[i].update == update)
{
timeBasedSystems.erase(timeBasedSystems.begin() + i);
--i;
}
}
}
template<typename T>
void SystemManager::removeSystem(void (*update)(const T&))
{
for(size_t i = 0; i<eventBasedSystem<T>.size(); ++i)
{
if(eventBasedSystem<T>[i] == update)
{
eventBasedSystem<T>.erase(eventBasedSystem<T>.begin() + i);
--i;
}
}
}
void SystemManager::runSystems()
{
for(TimeBasedSystem & system : timeBasedSystems)
{
TimePoint now = Clock::now();
Duration currentDeltaTime = now - system.lastUpdateCallTime;
if(system.deltaTime <= currentDeltaTime)
{
system.update();
system.lastUpdateCallTime = now;
}
}
for(auto f : runEventBasedSystemsList)
{
f();
}
}
template<typename T>
void SystemManager::throwEvent(const T& event)
{
//register event type
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wunused-variable"
const static EventRegisterHelper _(&runEventBasedSystems<T>);
#pragma GCC diagnostic pop
eventQueue<T>.push_back(event);
}
template<typename T>
void SystemManager::runEventBasedSystems()
{
for(T & event : eventQueue<T>)
{
for(auto f : eventBasedSystem<T>)
{
f(event);
}
}
eventQueue<T>.clear();
}
}
Create an entity:
Entity a = Entity::createEntity();
Destroy an entity:
a.removeEntity();
This will also automatically destroy all components that have been assigned to this entity.
Assign a component to an entity:
struct Position
{
double x;
double y;
};
a.createComponent<Position>(Position{ 0.2, 0.3 });
You can replace the Position
struct with any data structure that you wish to use.
Remove a component from an entity:
a.removeComponent<Position>();
Access a component that is assigned to an entity:
a.getComponent<Position>().x = 1337.42; // is undefined when a has no Position component assigned
This function returns a reference so you do not need a setComponent<T>(...
function, but this reference is not stable when you assign or remove components so it might get invalid over time. If you want to store the return value of this function in a variable be sure that it stays valid as long as you need it.
Check if an entity has a component:
bool a_has_Position = a.hasComponents<Position>();
bool a_has_Position_and_Mass_and_Velocity = a.hasComponents<Position, Mass, Velocity>();
bool a_is_a_valid_entity = a.hasComponents<void>();
Looping over entities:
for(auto a : Iterator<Mass, Position>()) // loops over all entities that have the components Mass and Position
{
for(
auto b_iter = ++Iterator<Mass, Position>(a); // this sets b_iter to an Iterator, that is one entity further than a is
b_iter != Iterator<Mass, Position>().end();
++b_iter
)
{
Entity b = *b_iter;
// do gravity calculations or whatever
}
}
for(auto a : Iterator<void>()) // loops over all valid entities
{ /*...*/ }
Run all added systems and resolve all thrown events:
SystemManager::runSystems();
Add a system:
void gravitySystem()
{
// loop over entities or whatever
}
SystemManager::addSystem(&gravitySystem, std::chrono::milliseconds(10));
This system will only be called by SystemManager::runSystems()
if the last call of this system was 10 milliseconds or more ago.
Throw an event:
struct SomeEvent
{
double value;
}
SystemManager::throwEvent(SomeEvent{0.001});
Again, you can replace SomeEvent
with any structure you want to use.
Catch an event:
void catchEvent(const SomeEvent& event)
{
std::cout << "Catched event: " << event.value << std::endl;
}
SystemManager::addSystem(&catchEvent);
You can replace catchEvent()
with any function you want. If you want to catch all events T
you need to add a function that looks like this:
void customEventCatcher(const T& event)
{
// ...
}
Remove a system:
SystemManager::removeSystem(&catchEvent);
SystemManager::removeSystem(&gravitySystem);
If you have added the same system several times, all of them will be removed, even if you have assigned different durations to them.