Heterogeneous Map

Started by
9 comments, last by Hodgman 12 years, 6 months ago
Is there such a thing in existence which acts like a map<K,V> but the Key can be an object type instead of value?

I'm working on a component system and thought it'd be nice if I didn't have to look up components by doing a linear search and dynamic casting each base pointer to find the right component of required type, or to not have to do look ups by an ID or string.

I found boost::fusion::map but you have to specify each supported type as a pair<> where as I want an arbitrary number of types so I can simply do something like this:

[source lang=cpp]
template <typename T>
T GetComponent()
{
map<ArbitraryType, Component>::iterator it = m_coms.find(T);
// obviously do some checking here before returning
return it->second;
}
[/source]
Advertisement
iirc, you can use any object type as your key providing the object implements the < comparison operator. On Visual Studio at least, if you try and apply an arbitrary object as the map's key parameter the compiler will inform you of this in the process of raising an error.

However, you may get better performance if you can make the comparison a little simpler. For example, I regularly have a string as a key, but will actually use a 32 bit hash to avoid a string comparison every time it inserts/whatever.
Don't think you really got what I was asking.

I want the key to actually represent the Type of object rather than storing an actual object. This would allow me to do something like this:

[source lang=cpp]
// a is some kind of map
a[int] = 1;
a[float] = 4.3f;
a[RenderComponent] = myRenderComponent;

etc
[/source]
ah I see.

I've never tried the specific thing you're doing, but have used type_info and typeid in other circumstances... couldn't you use them to create a map by type name, and convert? sorta like:

[source lang="cpp"]
class TypeKey {
std::string name;
TypeKey(type_info &t) { name = t.name(); }
...
};

std::map < TypeKey, V > mymap;

mymap[typeid(int)] = something;
mymap[typeid(char)] = something_else;
[/source]

I suspect you'll need RTTI enabled in your compiler to make it work, though.

[edit: though, it doesn't quite allow for any type-sensitive assignment such as you're asking for, though, so perhaps I've missed the point entirely.]
That's pretty much the sort of thing I want to achieve but I'm not sure whether using typeid is the best or only option.

Maybe some of the guru's could give some input on the topic?
Using typeid is fine as long as either: you don't intend on disabling RTTI, or you're targeting MSVC (which supports typeid even with RTTI disabled).

There's an ADBAD post about rolling your own RTTI system, which you might find interesting, and below is some more food-for-thought as to how you could make a value to use as your key.[source lang=cpp]struct TypeId() //#1 use a pointer to a type_info
{
public:
Type() : type() {}
Type(const type_info& t) : type(&t) {}
bool operator==( const TypeId& o ) { return type == o.type; }
private:
const type_info* type;
};

template<class T> int GetTypeId() // #2 hash type_info.name
{
const char* name = typeid(int).name();
static int id = Fnv32a(name);//N.B. in multi-threaded code, requies syncronisation of the static (e.g. corrected double-checked locking)
//In debug builds, add hash to a global set to check there are no has collisions
return id;
}

extern int g_nextTypeId; // #3 use a global counter
template<class T> int GetTypeId()
{
static int id = g_nextTypeId++;//N.B. in multi-threaded code, requies syncronisation of the static (e.g. corrected double-checked locking), and an atomic increment
return id;
}[/source]
Ah very interesting indeed. Looks like I might have to roll my own so there's no dependency on RTTI and the extra 40 bytes overhead per class.

In your opinion though Hodgman, would you even bother with this or just go with the typical linear search method? This is for a component based system and it's still kind of questionable to me whether I should even be allowing a GetComponent() function in the first place. It was mostly to allow components to access other components within the same object - but that is kind of against the whole idea of components but figured maybe in practise I might need that ability.
In your opinion though Hodgman, would you even bother with this or just go with the typical linear search method? This is for a component based system and it's still kind of questionable to me whether I should even be allowing a GetComponent() function in the first place. It was mostly to allow components to access other components within the same object - but that is kind of against the whole idea of components but figured maybe in practise I might need that ability.
I know this style of component system is somewhat trendy, and many people will disagree with my opinion, so take this post with a grain of salt -- but IMO, this style of component system is a complete anti-pattern, so, neither!


1) imagine trying to write 'regular' software using such a pattern. Every object can only have one member of each type?
[font="Courier New"]struct Person { int age; int height }[/font]? No!
[font="Courier New"]struct Age { int value; }; struct Height { int value; }; struct Person { Age age; Height height; }[/font]? Really?

2) Finding objects by type seems wonderful at first, e.g. add a particle system to your character, and have it automatically create footstep puffs:
[font="Courier New"]struct WalkController { void OnStep() { if(parent.Has<ParticleSystem>) parent.Get<ParticleSystem>().Emit(); }[/font]
...but wait, designers now want one foot to emit red sparks, and the other make blue sparks... but only spark when the player is supercharged...
Hmm, I know, I'll make some interfaces so that they now are different types:
[font="Courier New"]struct LeftFootParticleSystem : public ParticleSystem {}[/font]
[font="Courier New"]struct WalkController { void OnStepLeft() { if(parent.Has<LeftFootParticleSystem>) parent.Get<LeftFootParticleSystem>().Emit(); }[/font]
So, now we've dragged in the problems of the traditional deep-inheritance tree entity systems... let's fix that using composition:
[font="Courier New"]struct LeftFootParticleSystem { ParticleSystem effect; void Emit() { effect.emit(); } }[/font]
...but wait, the pattern says that "entities" are the owners of Components, and as [font="Courier New"]ParticleSystem[/font] is a component, [font="Courier New"]LeftFootParticleSystem[/font] can't own one?
So... we make LeftFootParticleSystem into an entity instead, and make WalkController link to another entity?
[font="Courier New"]struct WalkController { void OnStep() { if(leftLink && leftLink->Has<ParticleSystem>) leftLink->Get<ParticleSystem>().Emit(); }[/font]
Ok, that works... but splitting my character across multiple entities now feels like my design is being forced to fit the system, rather than the system fitting the design. And wasn't the point of this system to make things flexible via composition? Why are we defining one framework that all composition must fit into?

3) Entities have a concrete definition.
Why does the UML diagram tell me that [font="Courier New"]Entity[/font] has many [font="Courier New"]Component[/font], and [font="Courier New"]Component[/font] has one [font="Courier New"]Entity[/font]?
Why can't components own components? Why do we need a concrete Entity base-class anyway?
What's wrong with this definition of 'Entity':
[font="Courier New"][color="#008000"]//An entity is any logical object that owns components. N.B. no common base class[/font]
And likewise:
[font="Courier New"][color="#008000"]//A component is a class designed for composition. N.B. no common base class.[/font]

4) Components point back to their parent Entity.
A tree structure that requires bi-directional pointers is a common code-smell. I would expect people to question the necessity of such a design.
If the goal is to reduce dependencies, this two-way link should be the first thing to go.

As an aside, here's some entity/component code that doesn't have the above quirks. N.B. there's no "entity/component system" framework required (besides modern programming language features for the sake of brevity)function CreateWalkController( ent, leftFoot )
local component = engine.NewWalkController()
component.OnStep = function()
if ent[leftFoot] ~= nil then ent[leftFoot]:Emit() end
end
local oldOnDestroy = ent.OnDestroy
ent.OnDestory = function()
component:Destroy()
if oldOnDestroy ~= nil then oldOnDestroy(ent) end
end
return component
end

local player = {} -- create a new entity
player.OnDestroy = function() if player.leftFoot ~= nil then player.leftFoot:Destroy() end end
player.myWalkController = CreateWalkController( player, 'leftfoot' )
player.OnSuperCharge = function()
if player.leftFoot ~= nil then return end
player.leftFoot = CreateParticleSystem( player, 'redfoot.particles' )
end
level:AddToDestructionList( player )--delete it at the end of the level
/end rant/
Thanks for taking the time to rant :D
I do agree with most of what you have said. It does seem like an anti-pattern to enforce this two-way bond between an Entity object and Component objects but in all my searching I have not seen it done any other way - even some slides from a game studio talk about doing it this way.

If I'm reading your pseudo-code correctly, are you basically saying each "component" knows exactly what other components it has and therefore there would be no base component class or entity class that contains them? This kinda brings me to my other thread about update order of components and how to manage them. Where is the higher level management of this player object? Who updates or contains it? Do you have sub systems that handle this?

Maybe you could show a more concrete example of how you have designed your system so I can get a better understanding of how this could be used in a practical sense?

Cheers
*bump*?

This topic is closed to new replies.

Advertisement