Component Entity Model Without RTTI

Started by
66 comments, last by l0k0 12 years, 3 months ago
I'm writing my own component entity model in C++. At the moment, it is somewhat similar to the framework used for Unity Scripting (particularly in C# with templates). Since I'm still in the early phases of the project, I just used type info to handle the type based retrieval of components like so:


template<class T> T * getComponent() {
for (int i = 0; i < componentList.size(); ++i) {
if (typeid(*componentList) == typeid(T)) {
return componentList;
}
}
return NULL;
}


However, RTTI (along with exceptions) is typically best left disabled in frameworks and engines if possible. What would you recommend for a minimalist way to get this functionality for components only? I'm willing (and hoping) to use the preprocessor to declare classes and assign them a "type id" kept inside the components themselves. The tricky issue is that this must also support subclasses. So if I call getComponent on a base class it must also return a subclass if it is found.

Is a 32 bit type id determined at compile time my best option here? Any tips from people who have implemented the thing themselves?
<shameless blog plug>
A Floating Point
</shameless blog plug>
Advertisement
What I have typically seen is that there is a unique numeric value assigned to each family of components. You can assign this value either by doing it at compile-time in your code or by using some factory/class registration mechanism that handles the assignment at run-time.

#define ADD_RTTI(BASE_TYPE, ID) public: enum { kTypeId = ID; }; bool isDerivedFrom(uint32_t nodeType) const { return nodeType == kTypeId ? true : BASE_TYPE :: isDerivedFrom(nodeType); }

class Base
{
public:

enum { kTypeId = 0; }

virtual bool isDerivedFrom(uint32_t nodeType) const { return nodeType == kTypeId; }

template<typename T>
T* asType()
{
return isDerivedFrom( T::kTypeId ) ? (T*)this: 0;
}
template<typename T>
const T* asType() const
{
return isDerivedFrom( T::kTypeId ) ? (const T*)this: 0;
}
};

class Foo : public Base
{
ADD_RTTI(Base, 1);
};

class Bar : public Foo
{
ADD_RTTI(Foo, 2);
};
void rttiTestBar(Base* obj)
{
Bar* bar = obj1->asType<Bar>();
if(bar )
{
std::cout << "obj is Bar\n";
}
else
{
std::cout << "obj is not Bar\n";
}
}
void rttiTestFoo(Base* obj)
{
Foo* foo = obj1->asType<Foo>();
if( foo )
{
std::cout << "obj is Foo\n";
}
else
{
std::cout << "obj is not Foo\n";
}
}
int main()
{
Base* obj1 = new Foo();
Base* obj2 = new Bar();
rttiTestFoo(obj1);
rttiTestBar(obj1);
rttiTestFoo(obj2);
rttiTestBar(obj2);
delete obj1;
delete obj2;
return 0;
}
I use this solution:


inline unsigned int new_id()
{
static unsigned int previous_id = 0;

++previous_id;
return previous_id;
}

template< class Type >
class id_container
{
public:
const static unsigned int value;
};
template< class Type >
const unsigned int id_container< Type >::value = new_id();

template< class Type >
unsigned int type_id()
{
return id_container< Type >::value;
}


Keep in mind though that this won't work across dll boundaries, and you should make the new_id() function thread safe if you want to use type_id in mutliple threads.
Simliar to GorbGorb's solution:

Code:

template < typename T >
inline unsigned int GetRTTI()
{
static char s_rtti;
return &s_rtti;
}


Pro:
One of the quickest implementations ever. Don't have to write much at all.
Fast comparisons.
Low memory overhead

Con:
RTTI values are not absolute and will vary with memory layout. Tracking bugs between different code executions may be difficult.
May want more info other than a memaddress.

class TypeId {
public:
virtual unsigned getTypeId() const = 0;

template<class Type>
bool isInstanceOf() const {
return getTypeId() == Type::getClassTypeId();
}
};

template<class Type, class GUID>
class AcquireTypeId : public virtual TypeId {
public:
unsigned getTypeId() const {
return GUID;
}

static unsigned getClassTypeId() {
return GUID;
}
};

//
// Elsewhere!
//

class Component : public virtual TypeId {
/* Stuff */
};

class SpecificComponent
: public Component
, public AcquireTypeId<SpecificComponent, 0xF5B30CDF> {
};


Advantages:
+ Identifiers are static
+ No macros
+ Virtual inheritance allows for components to be written in script

Disadvantages:
- Unable to determine parent-child relationships
- Virtual inheritance
- No macros and no names; just a bunch of numbers
o V-table lookup required

Though my design can be improved by embedding the type-info with the object; thus avoiding the virtual inheritance and v-table lookup...
This is my implementation of your entity class:


template< class Type >
void destruct_function( void *obj_mem )
{
static_cast< Type* >( obj_mem )->~Type();
}

class entity
{
public:
~entity()
{
for( auto it = components.begin() ; it != components.end() ; ++it )
( *it->first )( it->second );
}
template< class Type >
Type *query()
{
auto it = components.find( &destruct_function< Type > );
if( it != components.end() )
return static_cast< Type* >( it->second );
else
return 0;
}
private:
std::map< void (*)( void * ) , void * > components;
};
Thank you very much for all of the recommendations!
<shameless blog plug>
A Floating Point
</shameless blog plug>
Just for some food for thought: it's entirely possible to implement a component/entity system without even providing a "get child component by type" function whatsoever wink.gif -- getting a component by type is just one way that you can choose use a component/entity system, with it's own robustness/predictability/maintenance pros/cons.

Just for some food for thought: it's entirely possible to implement a component/entity system without even providing a "get child component by type" function whatsoever wink.gif -- getting a component by type is just one way that you can choose use a component/entity system, with it's own robustness/predictability/maintenance pros/cons.

Would you mind clarifying on that point? Would you instead get a component by instance id? Is what you're suggesting use tables in someway?
<shameless blog plug>
A Floating Point
</shameless blog plug>

This topic is closed to new replies.

Advertisement