Lately I've been working on a small C++ Entity-Component-System framework.
Like most other ECS frameworks the internal data is presented as a table where an entity is a simple row index and each Component type maps to a column index.
In contrast to many other C++ ECS frameworks that usually map the Components to the indexes using RTTI, I thought that since Components are known beforehand, it's simply a case of implementing the get/set/has functions at compile time. Much of this functionality is implemented in the std::tuple
template.
In short, here is how the ComponentManager
class vaguely looks like:
#include <vector>
#include <tuple>
#include <memory>
typedef std::size_t Entity;
namespace vf
{
namespace details
{
template <class... Types>
struct TypeIndex;
template <class Type, class... Types>
struct TypeIndex<Type, Type, Types...>
{
static std::size_t constexpr value = 0;
};
template <class Type, class AnotherType, class... Types>
struct TypeIndex<Type, AnotherType, Types...>
{
static std::size_t constexpr value = TypeIndex<Type, Types...>::value + 1;
};
}
template <class... Components>
class ComponentManager
{
public:
template <class Component>
Component& addComponent(Entity entity, Component* component)
{
std::get<details::TypeIndex<Component, Components...>::value>(m_components[entity]).reset(component);
return *component;
}
template <class Component>
Component& getComponent(Entity entity)
{
return *(std::get<details::TypeIndex<Component, Components...>::value>(m_components[entity]));
}
template <class Component>
const Component& getComponent(Entity entity) const
{
return *(std::get<details::TypeIndex<Component, Components...>::value>(m_components[entity]));
}
template <class Component>
bool hasComponent(Entity entity) const
{
return std::get<details::TypeIndex<Component, Components...>::value>(m_components[entity]) != nullptr;
}
void addComponentEntry()
{
m_components.push_back(std::tuple<std::unique_ptr<Components>...>());
}
void resetComponentEntry(Entity entity)
{
m_components[entity] = std::tuple<std::unique_ptr<Components>...>();
}
private:
std::vector<std::tuple<std::unique_ptr<Components>...>> m_components;
};
}
The actual framework wiring is implemented in 3 classes (ComponentManager
, EntityManager
, SystemManager
) that are semi-coupled and communicate together. All of the functionality is hidden in a wrapper class. Systems keep a list of Entities according to the components on which they function.
Now one thing that I want to address is caching:
In my current implementation, the table actually keeps pointers to the component objects since:
- It makes checking for existence much easier.
- It doesn't require that the components are default initialized.
This makes the retrieval of components slower in some cases.
I know that I shouldn't try to optimize before bench-marking but assuming that the results show a noticeable difference (I won't be doing tests any time soon since the framework is by no means ready yet), do you think that it's worth it to introduce a few requirements in the components in order to increase performance for a few admittedly rare situations?
I am also open to any criticism on the ComponentManager
template. It doesn't make sense to talk about efficiency if the current implementation needs correction.