Component Entity Model Without RTTI

Started by
66 comments, last by l0k0 12 years, 3 months ago
For my assignment of entities to components i use an array of arrays to contain components by type. When a component is added to the framework, a type Id is established. This is nothing more than assigning an incrementing integer to a static int in the component. Since its static, each and every component of that type will report the same exact type Id (i also perform an early registry when a system is established to ensure a type id is created as well). so when i need to retrieve component x from entity y i use x.id to index into the array of arrays to pull out the applicable component array, then use the y.id (entity) to retrieve the relevant component. (i.e., no type comparisons are ever required other than checking for index safety).

now of course this also adds some memory overhead, but the trade-off is access performance. of course my implementation uses RTTI to access the type id, as its generally accessed through an interface. Now if i could get a way to do that without having to go through an interface but still remain agnostic in implementation thats faster than an assembly table lookup (from my digging around, that is approximately how interfaces are implemented by the compiler), i'm all ears smile.png Or will i need to loose the agnostic approach?
Advertisement
Well, hrm... now that i think of it, i could make the component be a mapping container, where it holds a type id and then another mapping id which would say which index I would want in a dedicated array for that data type. The component manager and mapper would just use another layer of lookup to get/set the data needed. I would need to use something like a switch to direct it to the correct retrieval mechanisms, but would this be faster than an interface?

The component would be something like this:


class Component
{
int entityId;
int componentType;
int componentId;
}


though, the componentId and entityId could be merged to do the indexing if you dont mind an entity only able to own one component of any type. If you did that, you could remove the need for a class entirely as long as you knew the entity id and component type desired.

But with that approach, you would need a unique retrieval mechanisms for each and every component type to be defined in a component manager/mapper, even if you reduced that class down to a global constant...

At that point you no longer have an agnostic framework though =/

Well, hrm... now that i think of it, i could make the component be a mapping container, where it holds a type id and then another mapping id which would say which index I would want in a dedicated array for that data type. The component manager and mapper would just use another layer of lookup to get/set the data needed. I would need to use something like a switch to direct it to the correct retrieval mechanisms, but would this be faster than an interface?


Speed is not primary concern, at least not when it comes to design.

You talked about SQL-like mapping. Apply the same principle here. Take two systems, perhaps a physics one and perhaps a pickup trigger. We have this data:Vector3 position[];
Vector3 orientation[];
Vector3 velocities[];
TriggerRadius triggers[];
ItemTemplate templates[];
Physics requires position, orientation and velocities. Quest trigger requires position triggers and templates.

The logic that manipulates these would be akin to select statement (SELECT position, orientation, velocities ... and SELECT position, triggers, templates ...).

There is no presecribed way to express such relation or how items are connected. At most basic level, you could indeed to this:class RigidBody {
Vector3 position, orientation, velocities;
};
Then design listeners and whatnot to update these instances as they change. Then propagate them to pickup system, for example.

Alternatively, you can express all of these as pure code.void stepPhysics() {
// do stuff with velocities, orientation, position
}

void checkForPickups() {
for every item that has a trigger
check for trigger radius
if in_range of trigger
use item based on template
}



How to map these things? Whatever is most convenient. One might use 1:1 indices. So that position, orientation and velocity belong to same entity. For triggers, there might be a a separate map that contains indices mapping between needed values.


Such approach lends itself well to cases where there is many things and there is expected to be many things. It's not worth complicating for one-off objects.

For UI, we know we'll have many buttons and frames. We also know that each button has associated action. So perhaps something like this:Rect uiElements[];
delegate void UIAction(...); // note the abstract handler
UIAction actions[];

void onClick(int x, int y) {
find which element was clicked in uiElements, located at index i
invoke corresponding handler
}


At each level, there's absolute minimum amount of data stored. An UI element doesn't need to know its type at first pass, it's just a rectangle that can be matched to a click. Later, we can differentiate between buttons, listboxes, .... Also notice the use of "complex" delegate. On each click, we'll invoke one element (or perhaps a few on multitouch).


Now consider that we end up with many UI elements and clicking is slow. No problem, all we have is a bunch of rectangles. Maybe build a quad tree over them and use that to query. Also, for simplicity, examples use arrays. These can be any container, List<>, Map<> whatever is most suitable. For performance sensitive work, they'd be either aligned arrays or in C# arrays/lists of structs.
Figure i would write a minor test case, just to see how much performance i am loosing using the most basic of interface usage. i wrote this to test:



namespace InterfaceTest
{
class Program
{
static void Main(string[] args)
{
Test test = new Test();
test.runTest();
}
}

class Test
{
private IInterface interfaceClass = new ExtendedClass();
private NormalClass normalClass = new NormalClass();
private ExtendedClass extendedClass = new ExtendedClass();
private int test;
private long interfaceStart, interfaceFinish, normalStart, normalFinish, extendedStart, extendedFinish;

public void runTest()
{
interfaceStart = DateTime.Now.Ticks;
for (int i = 0; i < 100000000; i++)
{
test = interfaceClass.getInt();
}
interfaceFinish = DateTime.Now.Ticks;
normalStart = DateTime.Now.Ticks;
for (int i = 0; i < 100000000; i++)
{
test = normalClass.getInt();
}
normalFinish = DateTime.Now.Ticks;
extendedStart = DateTime.Now.Ticks;
for (int i = 0; i < 100000000; i++)
{
test = extendedClass.getInt();
}
extendedFinish = DateTime.Now.Ticks;
Console.WriteLine("interface duration: " + (interfaceFinish - interfaceStart));
Console.WriteLine("normal duration: " + (normalFinish - normalStart));
Console.WriteLine("extended duration: " + (extendedFinish - extendedStart));
Console.Read();
}
}
}


inteface:



namespace InterfaceTest
{
interface IInterface
{
int getInt();
}
}


extended:



namespace InterfaceTest
{
class ExtendedClass : IInterface
{
int i;
public int getInt()
{
return i;
}
}
}


and normal


namespace InterfaceTest
{
public class NormalClass
{
int i;

public int getInt()
{
return i;
}
}
}


interface: 2964005
normal: 312000
extended: 312001

so, its roughly 9.5 times slower than a direct call. interesting.

edit: realized i had a type conversion from int to long in there, its fixed now.
interestingly enough, using an abstract class vs an interface results in a about 1716003 ticks, or ~5.5x as long as a direct class call and an internal call costs the same as a direct class call. So when in doubt, go abstract over interface... Interesting things to ponder...

interestingly enough, using an abstract class vs an interface results in a about 1716003 ticks, or ~5.5x as long as a direct class call and an internal call costs the same as a direct class call. So when in doubt, go abstract over interface... Interesting things to ponder...


I don't really want this to be about synthetic microbenchmarking, it's more about showing what happens if you turn your world completely upside down and throw away all existing notions of how a solution should be structured.

There's also another test that is missing above:
public class NormalClass
{
int i;

public void testInt(int n)
{
for (int i = 0; i < n; i++)
{
test = i;
}
}
}

It's considerably more representative of real world difference. Original test is a NOP, so it's measuring the overhead of pointless assignment. The changed example moves the separation line from outside of interface into implementation itself.

Ironically, this version is better OO than other tests. Getters break encapsulation.

I use pointers to the base component class to work with derived component classes. I only use RTTI casting when a base class pointer needs to access a member of the derived class. My initial reason for this approach was make my life easier wrapping objects from 3rd Party libraries like Ogre::Mesh into a Component. A Derived Component inherits from both the Base Component and Ogre::Mesh Classes or contains members to work Ogre::Meshes directly. I'm relatively new to C++ and still trying to wrap my head around a components implementation.


A cleaner solution would be to treat the ogre mesh as a property of your derived component and through an interface, keeps the Ogre API out of the ECS framework.


/* Base mesh object */
class IMeshObject {
};
/* Ogre mesh object */
class OgreMeshObject : public IMeshObject {
};
/* CryEngine mesh object */
class CryMeshObject : public IMeshObject {
};
/* Base component */
class IComponent {
};
/* Renderable Component */
class IRenderComponent : public IComponent {
};
/* Mesh component */
class MeshComponent : public IRenderComponent {
public:
void setMeshObject(IMeshObject* meshObject);
IMeshObject* getMeshObject();
};


If you wanted to keep it simple and refactor later, you could avoid wrapping the ogre API if you wanted and simply use get/set methods for the Ogre::MeshPtr object; however, I certainly would not inherit from any render API classes when creating my ECS components.

It also has the problem of enforcing the there can only be one philosophy onto the user -- each entity can only have one transform and one health component -- which may seem like a trivial burden at the time of writing, but at some point your users will find a reason for there to be two of something, and will be forced to construct ungracious work-arounds involving several entities cooperating to function as one "entity".


My current system uses said system without that restriction like so:


template<class T> void getComponents(T *components[], int32 &n) const {
n = 0;
uint32 i;
for (i = 0; i < componentList.size(); ++i) {
if (componentList->asType<T>()) {
components[n++] = static_cast<T *>(componentList);
}
}
}


I think you make a great point about the hidden dependencies though. Accessing the pointers directly is also faster than a linear search/hash table look up when it matters too.
<shameless blog plug>
A Floating Point
</shameless blog plug>

This topic is closed to new replies.

Advertisement