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\$
4
  • \$\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\$
    – user1430
    Commented Oct 15, 2019 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
    Commented Oct 15, 2019 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
    Commented Oct 16, 2019 at 21:22
  • \$\begingroup\$ @PSquall It is in an engine yes. Fairly basic but I would like to try and create a fairly generic engine that I can build simple games with. Mainly for experience and learning new techniques. \$\endgroup\$
    – jjmcc
    Commented Oct 16, 2019 at 21:24

1 Answer 1

1
\$\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\$
2
  • \$\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
    Commented Oct 19, 2019 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
    Commented Oct 20, 2019 at 23:52

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .