Advertisement

Should I have multiple vectors for the same object?

Started by August 24, 2023 10:23 PM
2 comments, last by lepraso 1 year, 2 months ago

Hi, I’m making a game from scratch using C++ and SDL2 for learning purposes and I’m trying to use composition. My current structure is having a game object class that is basically a container of components and when I create a new game object it gets added to a vector. Now the question I have is should I have multiple vectors for different type of game objects? my base component class has methods for mouse events and not all components/game objects will need it, but for the ones that do should they be put in their own vector so that I can loop through it and do mouse related stuff instead of using the vector that bas all game objects. looking for any tips/advice.

dominic0 said:
Now the question I have is should I have multiple vectors for different type of game objects? my base component class has methods for mouse events and not all components/game objects will need it

I lack experience with ECS and there should be better answers.
But i afaict the overall idea is something like so:

You have a system to handle player input, and you have a game object component to store related state. (Your game object eventually should not implement input interfaces at all - it's rather some input component which should.)
The input components are likely stored in a vector owned and processed by the input system. So the input system does or may not need to know about other systems and components.

Other examples would be a rigid body component, processed by the physics simulation system.
Or a movement component, which might access the input component during its update.

For the overall design you want to minimize dependencies across systems, eventually moving logic with complex dependencies further up to the higher levels.
The less dependencies at the lower levels, the more modular your stuff is, and the more likely it's still useful for your next game.
So beside better cache performance for systems iterating their stuff, modularity is another motivation to keep things separated.

The downside is that creating a new game object now may require to create multiple component objects, requiring multiple dynamic allocations, so memory management can become more costly.
And you also need a way to find all components for a game object, often done using a unique ID shared across all connected components, requiring some sort of search.
That's the whole problem with ECS as i see it, which alternative OOP class hierarchies does not have, just to mention.

The question you ask is very broad, and we can't tell a general ideal solution. It depends on your project, but also on your personal habits and preferences.
To figure out what you like, you'll likely do it wrong a few times, to learn what you don't like. The desire of improvement then gives you the answers with time, i would say.
But of coarse figuring out how other people do those things and seeking for patterns helps. And i would answer yes - you should use multiple vectors most likely, but it's hard to say at what granularity.

Advertisement

JoeJ gave a good response above, some additional info:

JoeJ said:
The downside is that creating a new game object now may require to create multiple component objects, requiring multiple dynamic allocations, so memory management can become more costly. And you also need a way to find all components for a game object, often done using a unique ID shared across all connected components, requiring some sort of search. That's the whole problem with ECS as i see it, which alternative OOP class hierarchies does not have, just to mention.

There are some ways around this. You can create all components of a certain type within a what I call a “persistent pool” data structure, which consists of an array of pointers to contiguous blocks of memory, each of which can store N components, where N is a template parameter. This is similar to the typical implementation of std::deque, with the addition of a “free list” stack which keeps track of the unused slots. This solves the problem of std::vector and similar reallocating the memory when the container grows, reduces the number of allocations compared to using new/delete, and keeps them more or less contiguous (though I think that consideration is way overstated for higher level gameplay systems).

As for the entity-is-a-unique-ID thing, I'm not convinced it is an improvement over simply storing pointers to all components of an entity in an array/vector that is in the Entity class. I use a std::vector<OpaqueReference>, where OpaqueReference is a void* pointer to the component plus a pointer to the ResourceID which contains type info, UUID, name string, etc. This allows efficient iteration over the components within an entity, at the cost of 1 allocation per entity. For iterating over components of a specific type, you can iterate over the pools discussed above.

Generally, you should try to implement all logic in centralized systems (e.g. InputSystem, PhysicsSystem, GraphicsSystem, MovementSystem), rather than in entities/components themselves. Systems can more easily have access to dependencies (connected when systems are created), and can more efficiently manage the components they use. I would strongly recommend NOT placing any game logic inside components unless it is completely self-contained. Components should be the data, systems have the logic that operates on the data.

This topic is closed to new replies.

Advertisement