C++ | Storing different types in a unordered_map

Started by
17 comments, last by ankhd 8 years, 7 months ago

Hi,

for my current project I need a fast data-oriented yet flexible entity component system which integrates good with my build system (Qt Build Suite, CMake's syntax gives me cancer).

I looked for some projects, came across "EntityX" and "Anax" and few more, but none was easy to integrate into my project.

So I had to implement my own one. I thought it won't be as hard as it appears now.

My naiv approach was to create an "std::unordered_map" with an RTTI-ID as key and an "std::vector<IBaseComponent>" as a value.

Well, today I learned the principle of "object slicing".

So, how do I go about this?

I want my "Scene" class (aka "World" in some implementations) to hold every component in a contiguous memory block.

How do I go about this?

P.S: Are there any baseclasses for a "std::vector" ? :)

It's not a shame to make mistakes. As a programmer I am doing mistakes on a daily basis. - JonathanKlein

Advertisement
You can use a thin template. Usually you'd want to keep the components strongly separated into a structure-of-arrays, though.

I haven't written any ECS architectures myself, I've just read alot about them.

Bearing my inexperience in mind, why do your components have a base class?

Why can't you do:

class Scene
{
     std::vector<GraphicsComponent> graphicComponents;
     std::vector<PhysicsComponent> physicsComponents;
     //...etc...
};

But I guess another question is, why does the Scene own the components? Aren't Systems supposed to own their own components?

class GraphicSystem
{
public:
     void Render();
private:
     std::vector<GraphicsComponent> graphicComponents;
};
 
class PhysicsSystem
{
public:
     void HandlePhysics();
private:
     std::vector<PhysicsComponent> physicsComponents;
};
Although I agree that this is probably not the solution you are looking for, there are ways to make containers of polymorphic objects. You need to use some sort of pointer to a base class to get polymorphic behavior and no slicing. A good first choice is `std::vector<std::unique_ptr<BaseComponent>>' (in C++11).
Currently the only solution that comes to mind, is to preallocate some storage. Instances are then being created using placement new syntax. I could still use pointers but in a DOD manner.

Are there any alternatives?

It's not a shame to make mistakes. As a programmer I am doing mistakes on a daily basis. - JonathanKlein

Currently the only solution that comes to mind, is to preallocate some storage. Instances are then being created using placement new syntax. I could still use pointers but in a DOD manner.

That would work (though waste a little memory), but the question remains: Why do this? smile.png

(And yes, you can do it without wasting any memory using one of two different ways, but one of those ways is extra slow, and the other is extra silly if you look at the bigger picture of your architecture).

If you are trying to keep everything in a single array, then it seems like you are trying to do it for performance reasons... but if for performance reasons, why are you trying to cram multiple different types at that low level?

Here's an interesting thing about components: They mostly don't share data, and they mostly don't share behavior. So why cram them polymorphically into the same interface, to pretend that they are mostly the same when they mostly aren't? Over generalization of code can create problems, just as under generalization can (though they create different types of problems).

Worse, performance-wise, is that even if you are accessing them as if they are the same, you'll still end up doing something like this:


void UpdateAllPhysics()
{
     for(every component) //10,000 components
     {
           if(is physics component)  <-- slow branch
           {
                 physicsComponent->UpdatePhysics(); //Only actually using 1000 of the 10k components.
           }
     }
}

And repeat that for every different type of component. This anti-pattern of un-conforming your types would occur far more often than the iterating over the conforming interface.

That's not how it's supposed to be done. You are putting too much logic into the components (slowing them down, though keeping the design simple), and then trying to cram the components into a common interface (slowing them down and convoluting the architecture), which you'll then have to un-cram to operate on the specific component types again.

If someone mentions 'components' and 'virtual functions' in the same sentence, they're (probably) doing it wrong.

Components should be data only. Systems hold the logic that operate on the data. Entities are practically there in concept only, usually being nothing more than an ID that shows what components of different types in different systems logically belong together to create the behavior your game is wanting to present.

ECS = Entities. Components. Systems

From what little I'm hearing of you describing your architecture, I get the impression you're neglecting the 'Systems' part. smile.png

Basically, normal C++ polymorphism and contiguous blocks of memory are almost antithesis of each other, being at opposite ends of abstraction. Putting them together sounds like you are having difficulty figuring out what level of abstraction this ECS sub-module of your project belongs in.

And if you are working at the level of contiguous blocks of memory, you probably want to re-read the Typical C++ Bullshit series of slides.

Note:

  • I love C++
  • I love polymorphism
  • I love templates and reducing code redundancy
  • I love contiguous blocks of memory
  • I love performance and optimization
  • I love abstraction and simplification

The key, though, is the right tool at the right time. I may be wrong, but I'm getting the impression you picked up the wrong tool to solve your current problem.

Not that I'd suggest using them for an ECS system, but there are options for dynamic types beyond standard inheritance. boost::variant and boost::any are two examples.

Thanks @ServantOfTheLord,

but I think I've got you wrong at some point.

I am not going to store every component in one array. Instead I wanted to

have an dictionary of vectors. Every vector belongs to exactly one component type

as they're indexed by the component id.

Is there actually a reference for creating an ECS that is data oriented?

"Anax" & "EntityX" aren't or probably not sure. I'll give it one more look for sure.

Thanks to everybody who replied ;)

It's not a shame to make mistakes. As a programmer I am doing mistakes on a daily basis. - JonathanKlein

A dictionary of vectors makes no sense to me. How do you envision using such a container?

There already exist standard containers for what you are doing yourself:

std::multimap and std::unordered_multimap

The other points still hold true though. This is usually not how ECS are implemented.

This topic is closed to new replies.

Advertisement