mercredi 12 février 2020

C++ ECS GetComponent

So I've been looking around at ECS patterns in C++ for a while. A lot of solutions (some posted on StackOverflow) for GetComponent using modern C++ look something like this:

// Could be in the entity for example
std::map<std::type_index, Component*> componentMap;

template <T>
T* GetComponent() {
    return static_cast<T*>(componentMap[typeid(T)]);
}

Sure, that works ok. But the issue is that this does not respect inheritance:

class Component {};
class Base : public Component {};
class Derived : public Base {};

entity.AddComponent<Derived>();
entity.GetComponent<Base>(); // Would throw

I have seen other solutions that somewhat respect inheritance, and they look something like this:

template <F>
class Component {
public:
    typedef F componentFamily;
}

class Base : public Component<Base> {};
class Derived : public Base {};

template <T>
void AddComponent() {
    componentMap[typeid(T::familyType)] = new T();
}

template <T>
T* GetComponent() {
    return dynamic_cast<T*>(componentMap[typeid(T::familyType)]);
}

Or something like that (I'm not compiling these examples so they may not be entirely accurate but you get the idea!)

The issue with this approach is that clearly we can only have one unique component that derives from this Base type attached to the entity at any given time.

Finally, you could have something like this. For each base type, a separate map exists (held for example by the system that is responsible for updating components of that base type).

std::map<std::type_index, Base*> baseMap;

// The Entity's GetComponent
template <T>
T* GetComponent() {
    return GameSingleton::GetSystem<T::familyType>()->GetComponent<T>();
}

// The System's GetComponent
template <T>
T* GetComponent() {
    return baseMap[typeid(T)];
}

Now you could have multiple different derived types on the entity. But you still cannot have the multiple components of the same derived type. Additionally, this doesn't respect intermediate inheritance. If we have some Derived type, and we add a SuperDerived type, asking for Derived type won't return anything.

So how do we do this in C++? This is the behavior we should expect:

class Foo : public Component {};

class Bar : public Component {};
class Baz : public Bar {};
class Bud : public Baz {};

class FazBaz: public Foo, public Bar {};

// Case 1
entity.AddComponent<Bud>();

// All work
entity.GetComponent<Bar>();
entity.GetComponent<Baz>();
entity.GetComponent<Bud>();


// Case 2
entity.AddComponent<FazBaz>();

// All work
entity.GetComponent<Foo>();
entity.GetComponent<Bar>();
entity.GetComponent<FazBaz>();


// Case 3
entity.AddComponent<Baz>();

// All work
entity.GetComponent<Bar>();
entity.GetComponent<Baz>();


// Case 4
// In this case, multiple components are added:
entity.AddComponent<Bud>(); // component "A"
entity.AddComponent<Baz>(); // component "B"

entity.GetComponent<Bar>(); // would return component "A" because it's first on the list
entity.GetComponents<Bar>(); // would return a std::vector<Bar*> containing "A" and "B"

Thanks,

Thor

Aucun commentaire:

Enregistrer un commentaire