4
\$\begingroup\$

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>;
        }
    }
\$\endgroup\$
  • \$\begingroup\$ I can't provide an answer now, but you could take a look at how the entt framework does this. \$\endgroup\$ – Vaillancourt Oct 12 at 17:41
  • \$\begingroup\$ @AlexandreVaillancourt I will take a look thanks. It looks a quite complex and I'm only after the idea behind how it's done and how I would achieve a really basic version of what the entt is doing. That is exactly what I want to achieve though (github.com/skypjack/entt#code-example) so I guess I'll have to dig a little further. \$\endgroup\$ – jjmcc Oct 12 at 18:21
  • \$\begingroup\$ Is it more important to you to understand and emulate exactly what entt is doing, or are you still mainly interested in a solution to your original problem even if it is nothing like entt's solution? \$\endgroup\$ – Josh Oct 15 at 3:13
  • \$\begingroup\$ Are you working on a game or an engine? Because for just a game you dont really need this. \$\endgroup\$ – PSquall Oct 15 at 11:38
  • \$\begingroup\$ @Josh Ultimately I would like a similar solution, but obviously not as complex. I would like to follow that pattern of being able to register a number of components against an entity (using a templated solution) without actually storing the components on the entity. I would like to keep the components in their own separate storage. Understanding exactly what they're doing would be a bonus, I've looked through it and tried to make sense as much as I can but there's some things that I get stuck on. Probably due to lack of experience with these kind of templates. \$\endgroup\$ – jjmcc Oct 16 at 21:22
0
\$\begingroup\$

First of all, i don't know how the entt-Framework works. I recognize most of its functions, but some i dont really understand what they do. I try to explain what i did the first time around and also try explain, what problems i had. Maybe that helps what my way of thinking was.

Second, i just make some easy assumptions, that can be changed by your liking, but might require big changes in the structure themself.

  • For simplicity sake, entities are just an ID and carry no information or functions other than that
  • Components are only carrier for information, not functions.
  • Components are all derived by a BaseComponent. That makes things a lot easier.
  • Every component class has a static variable COMPONENT_IDfor unique class identification. (I simply used Integer, but you can use what you want)
  • You only need one system to hold all components.
  • Entities can only have one of every component.

Ok, now to the real meat. First off all your ComponentSystemneeds a way to register new Components. For that your system needs a way of saving what type of Components are registered and a way to save them. I also will mostly reside to comments or pseudocode, as its completly depends on your style of coding and using of variables.

For that, your system needs some sort of Container<ComponentBase>, that gets created for every type of component class you register. Then create a Map<COMPONENT_ID, Container<ComponentBase*> That was a problem for me, as i created my own Container for that. Luckily Stack Overflow helped me there.
With that map your ComponentSystem can keep track what components already have been regiestered. But just for convenience you should add a counter, what number the next registered COMPONENT_IDwill be. Then you can implement your register-function:

    //register Components and return reference to them (Pre-Init Phase)
    template<class TComponent>
    void registerComponent() {
        //set COMPONENT_ID of the TComponent to a new and unused 

    ... //Create Buffer for your TComponent and map it to your Map<COMPONENT_ID, Container<ComponentBase*>
    };

As you can see, i changed it from my original implementation, as you dont really need to get the COMPONENT_ID. The Systems working with the distinct components can just check the static COMPONENT_ID on the components themselfs.

The components your engine start with should be registered by some sort of Pre-Loop Init-System, so all your starting systems dont try to register those aswell.

Now you could start adding Components to Entities:

//add a Component to an entity
template <class TComponent>
bool addComponent(EntityId entityId, TComponent component){
   //check if component is registered
   if(!mapOfComponentContainer.contains(component.getCOMPONENT_ID()){
      return false;
   } else {
      ComponentContainer* container = mapOfComponentContainers.get(component.getCOMPONENT_ID);
      //check if entitiy already has component of that type
      if(container->getPos(entityId) != null){
         return false;
      }
      //add component at entitiy Id as position
      container.add(component, entityId);
   }
}

I just wrote that on the fly, so i dont really now if there is any universe or compiler, that just can interpret that as i wrote it.

But that are basically the basics. Your system should add a way to return the component lists, or certain component constellation (as your RenderSystem will likely need a PositionComponentand a GraphicComponentat least).
Also you should add a way to get a single componente of an Entity, for editing, and a way to delete them. Also note, that this way is not in any way the optimal way of doing things. It may be slow, slughish or stupid. Just take it as it is as help to understand. There are so many ways to optimize this, so you should easily find a way to make things better (Pools, Contagious Memory etc.)

\$\endgroup\$
  • \$\begingroup\$ Thank you for the explanation and I'll get round to reading this at some point today but after skimming over it's not quite what I'm looking for. I really wanted to try and avoid the whole base class stuff so I can literally just make isolated structs with no dependency on anything else and try to understand templates a little better so I could do this stuff at compile-time. \$\endgroup\$ – jjmcc Oct 19 at 10:48
  • \$\begingroup\$ @jjmcc Well you can do that easily. You only have two things to change/consider: 1. You don't need a base class, but you need some sort of Component_Id. In that case your ComponentSystemneeds to keep track of those. In c++ structs are basically classes, and if you want want inheritance (because of time wasted for vTables), then you dont have to change that much. 2. If you want to use your engine to later add systems when building your game, most of the systems might implement existing components. so you SHOULD keep them in seperate files from your systems to get implemented later on. \$\endgroup\$ – PSquall Oct 20 at 23:52

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.