EntityID String<->Int resolution

Started by
15 comments, last by Inferiarum 11 years, 11 months ago
Hi,

I am working on my component based engine, and trying to implement an entityID system.
The way I am planning it is that each entity and component has a unique int ID and a unique string ID.

I thought of two ways to implement this, but not sure which would be better:


1. Each entity has their own map.
The entities are stored in a std::map<int, string> and components inside entities are also stored in a std::map<int, string> (there are multiple maps)

2. There is only 1 (or a fixed number of) global maps that resolves everything.
The entities and components only contain an int ID, on creation their string ID is going to be added in a single global std::map<int,string> that will resolve any id access by string. (or perhaps 1 map for entities, 1 for components)


For my peace of mind I would be more comfortable of knowing that all string IDs are stored in a single place, but not sure if it is a good idea, since it will take a longer time to search through a big global map to find a string.
On the other hand, I would imagine entities would be called with their int IDs, and their string IDs would be used more for debugging, so it wouldn't make a big difference (I might be completely wrong on this one.)

I would appreciate any suggestions on this.
Thanks in advance!

EDIT: Also, is it a good idea in general for each entity to contain a string id, or no, because it is inefficient?
Advertisement
Typically you will have one ID space per type of component. If all components have the same ID space, you can't just store a map<ID, component> because now you need a base class to represent a component and the whole point of a composition-oriented architecture goes out the window.

You shouldn't - generally speaking - ever have a situation where you know a component's ID but not its type, so this is not really a disadvantage. Plus, you can get clever:

std::vector<std::shared_ptr<FooComponent>> AllFoos;
std::map<int, std::shared_ptr<FooComponent>> FoosByNumericID;
std::map<std::string, std::shared_ptr<FooComponent>> FoosByStringID;


Now you can look up a Foo by any means you like: direct pointer (via shared_ptr passed by const reference), or by integer ID, or by string ID. Maintaining these mappings should be trivial if you have centralized your component creation logic correctly.



Here's an alternative approach which I personally prefer:

std::vector<FooComponent> AllFoos;
std::map<int, size_t> FooIndexByID;

std::string FooComponent::GetStringFormOfID()
{
std::ostringstream format;
format << "FooComponent #" << MyIntegerID << " at index " << FooIndexByID(MyIntegerID);
return format.str();
}


This removes a lot of indirection, and if you need to know where a Foo is, you either know its index, or its ID; you ask for its index given its ID, and then you can do a lookup straight into the vector to get the Foo itself.

Also, you get to keep your string-form IDs for debugging, but you never go from a string to an integer ID. Instead, you always get a string ID from an integer ID. This eliminates the cost of storing the strings and makes it trivial to find places where you rely on the string ID (just search for GetStringFormOfID!).

If you like, you can also maintain a second std::map<int, std::string> which maps from IDs to "comments." This is handy for situations where "FooComponent #5895 at index 384" isn't useful, but "Player's Left Hand Weapon" is useful. You can safely make the "comments" map sparse, i.e. only attach comments as you need them, so you don't have to worry about the overhead per-component. And since you don't need to look up components by their comment in well-designed architectures, you don't have to worry about the cost of searching for a commented component.

Wielder of the Sacred Wands
[Work - ArenaNet] [Epoch Language] [Scribblings]

Thanks a lot, this is very helpful!

So, based on what you have suggested, if I understand it correctly, would it be a good design if:

- I have a vector<std::shared_ptr<ComponentType>> globally, containing pointers to all components of a specific type that need to be accessed together by their managers, such as GraphicsManager for GraphicComponents, InputManager for InputComponents.

- The owner entity has a vector<std::shared_ptr<BaseComponent>> containing a pointer to every component it has, without really knowing or caring about their type, since the entity will only call their general onUpdate()


My concern about the above, maybe because of the lack of knowledge on std::shared_ptr:
- Is that ok, that 2 shared_ptr point to the same entity, but by different type (one is BaseComponent, the other is GraphicsComponent)?
- Will they still know about each other, counting their references, or like this they are separated, and each thinks they are the only one owning the component they point to?

Also, so far the way I have my design is when a component is added to an entity, it is added to the entity's std::vector<std::shared_ptr<BaseComponent>> entityComponentVector. The component was created by std::shared_ptr<BaseComponent> (new ComponentType(...)).
Each component has a onAddedToEntity() function, through which I would like to register the component by type to the corresponding global vector of pointers by component type.
From inside the object, is there a way to pass the shared_ptr that owns it to the corresponding manager for the type so it gets registered?


To give an example of what I want, I have an Entity as the player. To this entity I give a SpriteComponent, that is a GraphicsComponent derivative, and the way I add it is by pushing its shared_ptr<BaseComponent> to the player's componentvector, so it knows what it has. After the component has been added its onAddedToEntity() function should pass either
1. its owning shared_ptr<BaseComponent> to the GraphicsManager, that would possibly cast and use it as shared_ptr<GraphicsComponent>
or
2. a shared_ptr<GraphicsComponent> of itself to the GraphicsManager.

Again, I am not really sure how I can do this

EDIT: Maybe I am approaching this whole thing from the wrong direction, I am not even sure if it is possible to have it such that each entity has pointers to its components as well as each manager has pointers to components it needs, all stored in different vectors of pointers.
Should there only be vectors of component types and a std::map, and managers access these vectors directly, while entities only know the indexes of the components they own, which they look up from the std::map?

(I'll also use one of the methods for string <-> intID access, for now I don't really have more concerns about that as you have provided some very clear implementations)
I'm not certain of the benefit of having a BaseComponent class at all, or why you would need a vector of them to call the updates. What if your components have ordering requirements for who updates first, but you store them in a different order in the "master vector"?

Consider:

- Player entity has an Input component and a Graphical component
- We call Graphical::Update()
- We call Input::Update()

Now we've lost the input for this frame because we drew the graphics too early.


In my experience it is safer to have each entity know what components it owns and how to update them in the correct order; this means you shouldn't have to use a base class for all components or worry about storing multiple references to the same component in different vectors. Part of the point of the second design I described earlier is that it avoids a lot of extra indirections due to having shared_ptrs all over the place, and makes reasoning about the system a bit easier.

Wielder of the Sacred Wands
[Work - ArenaNet] [Epoch Language] [Scribblings]

I have thought of the update priority and figured that each component has an int priority, and the entity's component list is sorted by the component's priority. I thought it wouldn't be inefficient since it only needs to be sorted when a new component is added.

If I wouldn't have a BaseComponent class, then I would need to change the Entity class whenever I would come up with a new Component type, so it has a corresponding container.

Do you mean the entities should do all the processing of components without the need of Managers? That's how I tried to do this first, but when I got to collision I thought I really need an external manager that oversees every entity's every collider, and thought of using a GraphicsManager would also be useful.

Or am I only supposed to use a handful of core component types without a lot of inheritance?
For example I have:
GraphicsAnimSprite---->GraphicsSprite------->GraphicsComponent---->BaseComponent
GraphicsParallaxSprite-^

Is that bad design, should I only have GraphicsComponent, and implement everything else from that without deriving classes?

I would just like each entity to know about all its components, and I would like globally each manager to be able to iterate through a specific type of component of every entity. I am not sure how I can achieve this. Since I have many derived classes of components, I can only store vectors of pointers to base classes (either the root ComponentBase, or by family as GraphicsComponent, InputComponent, Collider).


Sorry for all these questions, and my confusion, I am really new to component based design. :)
Your help is much appreciated.
The design we are using for NLS engine is such that each Entity has a vector of ComponentInterface*. Each module (graphics, physics etc) can extended that base type for its own components. Then the component is also stored in a vector for that module. The module itself contains the update method. The only reason entity even known about its components is for easier debugging and entity destruction to insure all components owned by that entity go bye-bye with it.

The important point is that it's perfectly fine for both entity and module to have a reference to component. However the distinction is that entity OWNS the component, and modules UPDATE using the components.

For reference you can catch NLS engine at https://bitbucket.org/adam4813/nls-engine . We are currently reworking for our v1. Within the next few days the biggest chunk of that rework will be live and you can see the interaction between components, modules, and entities.
Thanks for your reply adam4813,

I have a similar idea on the design of my system, although with mine Entities do actually get to call each component's Update() function, but that only does the general logic.
Graphical components further need to be processed by the GraphicsManager, that has access to more functions than Update() (Render for example).
Colliders position and transformations get updated by the Entity, but it will be the CollisionManager that will check for actual collision and send out events.

Most of this is still only on paper, so it might turn out to be better to separate all functionality from entities, I'll see what happens.

Another Question:
When I am trying to handle events, such as an KeyboardInputComponent maps a key to an event.
For example: (I'm doing this in SFML2)
std::map<sf::Keyboard::Key, String> inputTable
if any of the keys on the map are pressed, send an eventMessage to the evenChannel, such as if (sf::Keyboard::IsPressed(sf::Keyboard::Space)) sendEvent("Fire");

The eventHandler will decide what to do with "Fire".

What I would like though is that I can work with all events being human readable strings, but I don't want the program to actually be comparing strings because it is probably inefficient. Also, I want this in a way that the event words wouldn't need to be hardcoded into the engine, it could be parsed from an xml or txt file, and a new event could be generated even during runtime.

How can I make this work, so it would work like enums (without always having to extend the values inside the enum if I want to extend it), so I work with readable words, but the program deals with more computer-friendly data.

Would I also use 2 maps, one as String->Int and another for Int->String?
When I add the event by string it would only put the corresponding int ID on the event channel, and when the eventHandler checks for events by string it would get the corresponding int ID?
We trying having a general multi-purpose messaging system, but it wasn't really worth it. We have fully encapsulated each module and component so that there is no events or messages between them. The glue however is our scripting system. Scripts can register to events and act accordingly.

You might want to check out boost::bimap is it is similar to what you are doing with your 2 maps, but in one nice container.
My personal view (and this is not to say that other opinions are not valid) is that the purpose of a component system is to avoid inheritance as much as possible. Inheritance-heavy designs tend to become either very brittle or very convoluted as they get more complex. There are typically three outcomes:

- Huge amounts of multiple inheritance
- "God" base classes and lots of thin derived classes
- Lots of code duplication

The whole idea of composition-based design is to avoid this by dividing your code into totally distinct "components" and linking them via composition in "entity" and "system" classes.

Ideally, a component should not need a base interface, because every component should do unique things. An entity might have a simple abstract interface base, but even that isn't really necessary. Systems control interactions between entities and do not need base classes because they talk directly to the components involved.


It may seem like it is a disadvantage that you have to explicitly write code every time an entity gets a new type of component, but the reality is that this is a huge advantage. It makes the relationships between things very clear in your code, and has practical benefits beyond that. For instance, you remove a lot of pointer indirection and virtual function dispatch, as I outlined above. It also guarantees that you only build game data that is "sensible" - you will never have a Tree entity holding a Weapon component because that just doesn't make sense, and the code just plain won't let you.

Now, does that mean you need a Tree entity and a Bush entity and a Rock entity? Absolutely not! The goal is to find common elements between Tree and Bush and Rock, and notice that they all make sense together as a Scenery entity, or whatever you want to call it. Then you combine your component model with data-driven techniques so that a Scenery entity has a Graphical component which points to the image for a Tree, or a Bush, or a Rock. Now your code enforces that the game world always makes sense but doesn't require a huge nest of inheritance hierarchies to accomplish that.

If you want to know if a Scenery is a Rock, just ask it; no need for RTTI, dynamic_cast, or any other potentially messy reflection-style systems. Each entity should hold a tag that tells you what variant of that entity it represents, and you just retrieve that tag. So your Collision system can query Scenery to find out if it should be a blocking obstacle (Trees and Rocks) or if you can walk through it (Bushes). And the Scenery needs to know nothing about the collision system at all!

This is the power of good component-based design. My general rule of thumb is: if you find yourself using inheritance anywhere, think extremely hard about what you're doing. It's probably not the best alternative.

Wielder of the Sacred Wands
[Work - ArenaNet] [Epoch Language] [Scribblings]

The whole idea of composition-based design is to avoid this by dividing your code into totally distinct "components" and linking them via composition in "entity" and "system" classes[/quote]
I agree.

However, I think I've gotten confused with the terminology, since different "component systems" use the words "component", "entity", and "system" in various (and sometimes conflicting) contexts. In something like Artemis, which uses "systems" to process functionality while "components" are just data that are associated with an entity (but the entity is merely a container itself), there is a base Component interface but it's just an empty marker to use as a reflection platform. The basic idea, as far as I can tell, is similar but there doesn't seem to be a reason to have explicit entity classes.

If you didn't mind, could you elaborate - or provide a simple example - on why/how you'd create two different entities in the system you proposed?

This topic is closed to new replies.

Advertisement