🎉 Celebrating 25 Years of GameDev.net! 🎉

Not many can claim 25 years on the Internet! Join us in celebrating this milestone. Learn more about our history, and thank you for being a part of our community!

Straight Forward c++ RTTI

Started by
12 comments, last by LorenzoGatti 4 years, 1 month ago

Hi, i have been trying to implement RTTI for a while but haven't found a good solution. Ideally the interface should revolve to asking via a static call:

T::GetTypeID();

I have tried using CRTP but as you know this doesn't give the Base class access too the TypeID so you cant do:

T::GetTypeID() == BaseClass::GetTypeID();

I also tried the macro approach where you pass a class name in and generate some functions for it, at runtime i compute the hash when the TypeID is requested and store it, yeah it costs an extra 4 Bytes per instance.

But again if you wanted to support the static based request you end up with a derived trying to create a parents static that already exists which runs into problems.

I could just go down the manual/basic enum route but my Engine exists as a lib that you include so you cannot extend the components/other class available if you use this method.

The main problem i am trying to solve is to check if a Entity already owns a specific component type, i don't want to create the component type just to grab the type and a lot suggest avoiding c++ inbuilt RTTI as its overly complicated behind the scenes (read in noel noel llopis book).

Any suggestions would be appreciated, Thanks.

Advertisement

The way I do it, is having a static method GetStaticType for querying the type-id of a known derived type, and a virtual dynamic GetDynamicType-method for getting the type of an unknown base-type pointer.

template<typename ComponentT>
[[nodiscard]] bool isOfType(const BaseComponent&amp;amp; component) noexcept
{
    return component.GetDynamicType() == ComponentT::GetStaticType();
}

CRTP is used to automate this code, which would look somewhat like that:

class BaseComponent
{
public:
     [[nodiscard]] virtual uint8_t GetDynamicType(void) const noexcept = 0;
}

template<typename DerivedT>
class Component : public BaseComponent
{
public:
    [[nodiscard]] static uint8_t GetStaticType(void) noexcept
    {
         static uint8_t componentType = GenerateStaticType();
         return componentType;
    }
    
private:
    uint8_t GetDynamicType(void) const override final
    {
        return GetStaticType();
    }
}

Alternatively, if your types do not have any other virtual methods you could just store the dynamic type directly as a field in the base class.

karlmar said:
read in noel noel llopis book

Published in 2003, it seems? So that makes that information about at least 17 years old, right?

A lot happened in the last 17 years in c++ history, so my suggestion would be to check that claim using a recent compiler (probably even a few compilers, as I doubt they all do the exact same thing to decide RTTI). Also, we ran in the wall of memory access being the main problem to solve in the mean time, so perhaps “complicated” is not that relevant any more?

Put in another way, a lot of smart people looked at the problem and decided the best solution. It may be complicated, but apparently it's needed or it wouldn't be there. People don't add complexity to make it look better.

Another path is to figure out why you need RTTI in the first place. Ie why don't you KNOW what an entity owns? Failing that, if you must ask the instance, how often would you have to do that? I would hope once is often enough, so is it very very bad if it takes a little longer?

Wouldn't std::type_info's typeid operator help ? It's dynamic …

Custom type-ids can still have advantages over RTTI, even aside of whether comparing std::type_info is faster or not.

First, if you don't use RTTI at all outside of the components, can turn if off entirely for the app, saving space and probably compilation-time.

Generating your own type-IDs also allows you to generate them as 0-based, sequential numbers, meaning you can make lookups much faster eigther by directly indexing into an array, using binary search etc… (I'm personally using a bitset for very quickly checking if an component exists on an entity)

Its also much easier to share your own type-ids between otherwise disjunct types. Like, if I have my components ID, I could possibly use that same ID to lookup the components associated system w/o having to query another type-information).

Whether thats all really relevant is another topic, and I'm ususally the last person to be concerned with maximum performance in every single piece of code, but I think something as substantially as component-access might be place for a little more though/custom work.

@Alberth RTTI is required as Enttiies are constructed from components, weather its Component Entity Architecture or ECS somewhere the type would be required. Int he most basic “unity” way you would have to do:

m_GameObject->AddComponent<MeshRenderer>();

in ECS/DOD you would have to do:

m_World->AddComponent<MeshRenderer>(EntityID);

Both would need to know the type in order to resolve for appending the component, in unity's case it would added it to an unordered map using TypeID as the key. In an ECS case it would use the TypeID to look up the correct ComponentAllocator/Pool in order to insert it into a packed array using the entityID as a look up into the packed array etc.

Now you could say well why not OOD? and OOD is perfectly valid in the sense that you remove the concept of runtime entity construction and meta language in favour of fixed pre-defined types. But somewhere you will still need to query what type an object is that or do the unreal cast method:

APlayer* player = static_cast<APlayer>(otherActor);
if(player)
{
	player->HealthComponent.ApplyDamage(50);
}

karlmar said:
APlayer* player = static_cast(otherActor);

if(player) { player->HealthComponent.ApplyDamage(50); }

This kind of code means that, earlier, you decided to forget what type otherActor is. Not that in a normal ECS there should be actors of different C++ types to begin with; different entity types should differ only by what components they have and what data they contain.

And of course, determining the formal C++ type of an actor in order to determine how much damage to apply is the wrong question: there should always be less crude and more general ways to make calculations and represent game rules. For example:

otherActor->HealthComponent. ApplyDamage( myWeapon.successfulAttackDamage() - otherActor->ArmourComponent. damageReductionFor( myWeapon.damageType()));

which requires otherActor to have some components, not to be a specific class: easier and without constrained inheritance hierarchies. Getting rid of a hardcoded value (50) in the middle of a function is another important advantage.

Omae Wa Mou Shindeiru

RTTI is never straight forward without knowing why you want to use it. It seems to be bad style to utilize it just for accessing a component because the work done in behind the scenes is more intensive than the result can be useful. Think of a AAA game of millions of components that live in the game, if you need to query the component-ID every for even thousands of them every frame, you'll get what Unity makes slow these days, massive framedrops caused by lookups.

AAA games and even Unity itself don't query a component's inheritance chain, instead they add generated ID tables to every component to speed lookups up or limit the amount of components that much that you can have a single 16/32 or 64 bit value to represent an entity's components via an on/off switch. Why? Just because memory efficiency on the CPU.

You don't want to query each component on it's own and process it, you want a system o operate an a set of components who's entities match a given pattern. For example a movement system operates on entites that have the transform and animation component attached, in a single for loop.

karlmar said:
Ideally the interface should revolve to asking via a static call

Our engine's straight forward solution is a compiler macro trick and a static/ global template function. Using the __PRETTY_FUNCTION__ and similar macros in other compilers result in a string representation of the function currently called. If you define a template function, the compiler will create an instance of that function for every template type used in your code and so the output of __PRETTY_FUNCTION__ changes to reflect the exact type name of ANY given type in the entire C++ environment.
Something like

template<typename T> inline const char* gfn()
{
    return __PRETTY_FUNCTION__;
}

using namespace std;

gfn<string>();

results in

"char* const gfn<std::basic_string<char>>()"

We are running a short hash function like FNV32b over the resulting string to obtain our own unique type IDs for whatever is requested and base everything else on that ID.

On the other hand we also have generated type information like in C#.NET Reflection. This way we can iterate members, properties and functions of our types, obtain pointers to them and also feed a type ID into a generator class to create instances on demand.

A very usefull thing our Type System is capable for is the anonymous interface wrapper. This is a quite useful trick stolen from Typescrypt: as long as certain type exists that implements certain functions defined by an interface, that type's instances can be converted into and used by APIs that require exactly that interface without every inheriting from the interface type.

Our Type System is meant to be used in editor code for example but also in release code if instances are serialized into savegames for example

LorenzoGatti said:

karlmar said:
APlayer* player = static_cast(otherActor);

if(player) { player->HealthComponent.ApplyDamage(50); }

This kind of code means that, earlier, you decided to forget what type otherActor is. Not that in a normal ECS there should be actors of different C++ types to begin with; different entity types should differ only by what components they have and what data they contain.

And of course, determining the formal C++ type of an actor in order to determine how much damage to apply is the wrong question: there should always be less crude and more general ways to make calculations and represent game rules. For example:

otherActor->HealthComponent. ApplyDamage( myWeapon.successfulAttackDamage() - otherActor->ArmourComponent. damageReductionFor( myWeapon.damageType()));

which requires otherActor to have some components, not to be a specific class: easier and without constrained inheritance hierarchies. Getting rid of a hardcoded value (50) in the middle of a function is another important advantage.

You overanalysed a fictitious example of OOD from my interpretation of how unreal works, in real code obviously you wouldn't have a hardcoded value but it was just a simple example. Also note i mentioned it as OOD and not a form of ECS so the “that is not normal in ECS” is invalid as i wasn't talking about that at all.

Shaarigan said:

RTTI is never straight forward without knowing why you want to use it. It seems to be bad style to utilize it just for accessing a component because the work done in behind the scenes is more intensive than the result can be useful. Think of a AAA game of millions of components that live in the game, if you need to query the component-ID every for even thousands of them every frame, you'll get what Unity makes slow these days, massive framedrops caused by lookups.

AAA games and even Unity itself don't query a component's inheritance chain, instead they add generated ID tables to every component to speed lookups up or limit the amount of components that much that you can have a single 16/32 or 64 bit value to represent an entity's components via an on/off switch. Why? Just because memory efficiency on the CPU.

You don't want to query each component on it's own and process it, you want a system o operate an a set of components who's entities match a given pattern. For example a movement system operates on entites that have the transform and animation component attached, in a single for loop.

karlmar said:
Ideally the interface should revolve to asking via a static call

Our engine's straight forward solution is a compiler macro trick and a static/ global template function. Using the __PRETTY_FUNCTION__ and similar macros in other compilers result in a string representation of the function currently called. If you define a template function, the compiler will create an instance of that function for every template type used in your code and so the output of __PRETTY_FUNCTION__ changes to reflect the exact type name of ANY given type in the entire C++ environment.
Something like

template<typename T> inline const char* gfn()
{
    return __PRETTY_FUNCTION__;
}

using namespace std;

gfn<string>();

results in

"char* const gfn<std::basic_string<char>>()"

We are running a short hash function like FNV32b over the resulting string to obtain our own unique type IDs for whatever is requested and base everything else on that ID.

On the other hand we also have generated type information like in C#.NET Reflection. This way we can iterate members, properties and functions of our types, obtain pointers to them and also feed a type ID into a generator class to create instances on demand.

A very usefull thing our Type System is capable for is the anonymous interface wrapper. This is a quite useful trick stolen from Typescrypt: as long as certain type exists that implements certain functions defined by an interface, that type's instances can be converted into and used by APIs that require exactly that interface without every inheriting from the interface type.

Our Type System is meant to be used in editor code for example but also in release code if instances are serialized into savegames for example

Im using CRTP and the reason for querying is too be able to fetch pools of components when i implement ECS such that:

ComponentPool<MeshRenderer>&amp;amp; m_Renderers = m_World->GetComponentPool<MeshRenderer>();

Why do you need a RTTI query (on what object?) to know you need a pool of MeshRenderer? Is there some kind of factory that needs to translate names to classes?

You need to provide more context in your examples to receive advice, because in this area the common and preferred techniques involve expensive RTTI only as a last resort.
The type name trick Shaarigan describes represents the extreme limits of simple, cheap and general purpose RTTI techniques that can be recommended lightly because they don't hurt; you probably need something more specific and more problematic.

karlmar said:
But somewhere you will still need to query what type an object is that or do the unreal cast method:

No. You only “need” a query if you forget types, usually due to inappropriately polymorphic collections or to excessively fragmented types for slightly different objects that should actually have the same class.
If you think forgetting types is “unavoidable”, finding a way to avoid forgetting types is likely (and expected) to be easier, and more importantly cleaner, than setting up and using a RTTI system; if you are convinced of the contrary, you need to explain your unusual problems in detail to receive good advice.

Omae Wa Mou Shindeiru

This topic is closed to new replies.

Advertisement