1
\$\begingroup\$

I'm implementing an Entity-Component-System. Basically, an entity is a wrapper around an ID, a component is just a POD struct, and a System is a class that does work on components corresponding to the Entities subscribed to that system.

I'd like to take advantage of smart pointers, so the cleanup will be much smoother. However, I've ended up with a rather ugly looking function for getting the component attached to an Entity.

For this implementation of an ECS, all components inherit from a base struct just simply called Component. Entities have a hashmap of the components they own, with that components ID as the key.

Entity.h

//Forward declaration
struct Component;

struct Entity
{
    /*A unique ID*/
    const unsigned long long int kID;

    /*A map containing all components held by this entity*/
    std::map<int, std::shared_ptr<Component>> component_map_;
};

Component.h

typedef int COMPONENT_ID;

/*Base component class*/
struct Component
{
    static const COMPONENT_ID kTYPE = -1;
};

/*Components*/
struct CPosition : public Component
{
    static const COMPONENT_ID kTYPE = 0;
    float x, y;
};

EntityManager.h templated function for getting a component from an entity

/*Gets a component from an entity*/
template<typename T> 
std::shared_ptr<T> GetComponent(std::shared_ptr<Entity> entity) {
    return std::shared_ptr<T>((T*)entity->component_map_.at(T::kTYPE).get());
}

An example of how it would be used:

int main(int argc, char** argv)
{
    /*Create the entitymanager*/
    EntityManager em;
    /*Create an entity*/
    auto a = em.CreateEntity();
    /*Create a new Position Component*/
    /*struct CPosition : public Component */
    auto component = new CPosition();
    component->x = 55;
    component->y = 200;
    /*Add the CPosition component to the entity a*/
    em.AddComponent(a, component);
    /*Get the component back from the entity a*/
    auto mcomponent = em.GetComponent<CPosition>(a);
    return 0;
}

Is there a cleaner way to get the component back from the entity, without grabbing the raw pointer and casting it and sticking it back into a shared_ptr? What about having the static ID of components and using that to map the components in the entity? Any better solutions?

\$\endgroup\$
4
\$\begingroup\$
template<typename T> 
std::shared_ptr<T> GetComponent(std::shared_ptr<Entity> entity) {
    return std::shared_ptr<T>((T*)entity->component_map_.at(T::kTYPE).get());
}

Noooo. This is a misuse of shared_ptr's constructor (trying to create a brand-new shared_ptr to manage an object that is already managed), and will lead to double-free bugs or worse. What you meant was to cast the existing smart pointer to your desired smart pointer type:

template<typename T> 
std::shared_ptr<T> GetComponent(std::shared_ptr<Entity> entity) {
    std::shared_ptr<Component> ptr = component_map_.at(T::kTYPE);
    return std::static_pointer_cast<T>(ptr);
}

This is a moderately obscure corner of smart-pointer-land, and you shouldn't be expected to know it; but now that you see it, I bet you're smacking your forehead in disgust. :)

By the way, I see no reason to provide static const kTYPE = -1 in the base class; doesn't that just counterproductively move the diagnosing of several kinds of typo-bugs from compile-time to run-time?

Also, note that std::map is not a hashmap; it's a tree-map. If you really want a hashmap (or if you don't care), and you're using C++11 or later, you should switch to std::unordered_map.

What about having the static ID of components and using that to map the components in the entity? Any better solutions?

C++11 has a feature designed specifically for this use-case, although I personally have zero experience with it — it's spelled std::type_index. What you're looking for is a std::unordered_map<std::type_index, std::shared_ptr<Component>>.

\$\endgroup\$

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.