I had implemented a basic ECS system and was then informed that it should be changed as each system should not directly store their required components.
I have created a ComponentManager
which will own all of the registered components, and I would like to keep all of the different components in their own containers
class ComponentManager
{
template <typename T> T& AddComponent(Entity entity)
{
//...
}
std::vector<2DRenderComponent> m_renderComponents;
std::vector<TransformComponent> m_transformComponents;
};
I could create a different function for each component type but I would like to get familiar with using templates for tasks like this. It will also make it a little easier further down the line when I try to do more complex tasks.
Could anyone point me in the right direction as to how I would achieve something like this, whilst keeping the components isolated from each other?
My usage would be something like the code below
Entity entity = entityManager->CreateEntity();
TransformComponent& transformComponent = componentManager->AddComponent<TransformComponent>(entity);
When I call AddComponent
with a TransformComponent
, how do I add it to the correct container (m_transformComponents
). If I have to change the structure slightly to get this to work, i.e a map of types to vectors that is fine. Any guidance on this is appreciated.
So after looking into the entt framework, the functionality there is what I want to achieve, on a much smaller scale. Code example of how it functions (https://github.com/skypjack/entt#code-example)
I've done some digging into how they register components through what they describe as a registry
(https://github.com/skypjack/entt/blob/master/src/entt/entity/registry.hpp).
From what I can gather after looking they identify component containers by ids but I'm confused as to what it is doing so if someone could help me to understand this it would be appreciated.
First entry point when we want to add a component to a given entity
template<typename Component, typename... Args>
decltype(auto) assign(const entity_type entity, [[maybe_unused]] Args &&... args) {
ENTT_ASSERT(valid(entity));
return assure<Component>()->assign(*this, entity, std::forward<Args>(args)...);
}
assure() is called to obtain the correct pool for the component being added
template<typename Component>
const pool_type<Component> * assure() const {
const auto ctype = to_integer(type<Component>());
//....
}
type function - this returns what I can see to be an enum? But how is the function constructing and returning an enum like it does. When I try this in a separate application it fails to compile
ENTT_OPAQUE_TYPE(component, ENTT_ID_TYPE) //enum is created from this macro, along with the to_integer function
//macro
#define ENTT_OPAQUE_TYPE(clazz, type)\
enum class clazz: type {};\
constexpr auto to_integer(const clazz id) ENTT_NOEXCEPT {\
return std::underlying_type_t<clazz>(id);\
}
template<typename Component>
static component type() ENTT_NOEXCEPT {
return component{runtime_type<Component, component_family>()}; //how is this compiling?
}
runtime_type function - ENTT_ID_TYPE
is defined as std::uint32_t
#define ENTT_ID_TYPE std::uint32_t
template<typename Type, typename Family>
static ENTT_ID_TYPE runtime_type() ENTT_NOEXCEPT {
if constexpr(is_named_type_v<Type>) {
return named_type_traits<Type>::value;
//this looks like it returns a hashed string of the type name as std::uint32_t
} else {
return Family::template type<Type>;
}
}