Cześć,
Problem:
Projekt wykorzystuje wzorzec entity component system. Mam klasę abstrakcyjną Component
, którą dziedziczy każdy rodzaj komponentu oraz klasę Entity
, która posiada kontener dla różnego rodzaju komponentów. Często potrzebuję pobierać dany typ komponentu lub sprawdzać, czy dany komponent obiekt Entity
posiada. Zdecydowałem, że kontenerem tym będzie std::unordered_map
. Potrzebowałem unikalnych identyfikatorów dla każdego typu komponentów. Stworzyłem więc osobny plik nagłówkowy, w którym zawarłem typ wyliczeniowy enum class ComponentIdentifier
i jest użyty, jako klucz w kontenerze. Jeśli mam komponent klasy TransformComponent
to dodałem dla tego typu identyfikator Transform
, itd..
enum class ComponentIdentifier
{
Transform
}
dodawanie, sprawdzanie, pobieranie wygląda następująco:
entity.addComponent<TransformComponent>(ComponentIdentifier::Transform);
entity.hasComponent(ComponentIdentifier::Transform);
entity.getComponent<TransformComponent>(ComponentIdentifier::Transform);
drugi sposób, jaki przyszedł mi do głowy, a o niego w głównej mierze chodzi, to wykorzystanie mechanizmu RTTI. Z jego wykorzystaniem sprawa się upraszcza. Znika potrzeba definiowania własnych unikalnych identyfikatorów tylko wykorzystuję operator typeid
. Wtedy kluczem w kontenerze byłby typ std::type_index
.
dodawanie, sprawdzanie, pobieranie wygląda następująco i wszystko mogę robić z poziomu samego typu:
entity.addComponent<TransformComponent>();
entity.hasComponent<TransformComponent>();
entity.getComponent<TransformComponent>();
definicje, z wykorzystaniem typeid
, wyglądają mniej więcej tak (przykłady pisane z palca):
template<typename T>
void addComponent() noexcept
{
static_assert(std::is_base_of<Component, T>(), "");
assert(!hasComponent<T>());
...
mComponents[typeid(T)] = Component::Ptr{new T}; // unique_ptr
}
template<typename T>
T& getComponent() noexcept
{
static_assert(std::is_base_of<Component, T>(), "");
assert(hasComponent<T>());
...
return static_cast<T&>(*mComponents[typeid(T)]);
}
gdzieś indziej stosuję pobieranie z kontenera entity o określonych komponentach. Kod wygląda mniej więcej tak:
pool.getEntities(Matcher::allOf({typeid(TransformComponent), typeid(OtherComponentType)}));
lub wersja z typem wyliczeniowym:
pool.getEntities(Matcher::allOf({ComponentIdentifier::Transform, ComponentIdentifier::OtherComponent}));
Pytanie:
Czy w tym przypadku wykorzystanie RTTI ma sens? Czy znacznie wpłynie na wydajność, bezpieczeństwo? Zapis wydaje się prostszy, ale nie wiem, czy jest to 'ładne' rozwiązanie. Oczywiście mogę zostać przy definiowaniu własnych identyfikatorów. Natomiast jeżeli dojdzie kolejny typ komponentu będę musiał dodać nowy dla niego lecz operator typeid
sam załatwi sprawę. Przy jakim wariancie Ty byś został? Proszę o opinię.