2
\$\begingroup\$

We've built an entity-component architecture that works ok for our needs, but it has something that bugs me a bit from a software engineering point of view.

We use std::shared_ptr and std::weak_ptr for the Components: the Entity owns the component through an std::shared_ptr, while the ComponentManager and other Components refer to the component through a non-owning std::weak_ptr.

The issue is that the components are not actually shared, and they should not be shared either. They're owned by a single entity. Having an std::shared_ptr there suggests that anyone can take a copy of the shared_ptr and own it too.

Here is a diagram of how the classes are organized:

enter image description here

Entity:

  • real owner of the components through shared_ptr
  • does not know which components it holds

ComponentManager:

  • we call update() on the ComponentManager, which in turn calls update() on every component it knows about
  • there is one instance per type of component; we've done it like this to have control over which components get updated first (e.g. CmpRigidBody updates the CmpMatrixTransform, and from which the CmpGfx is then updated)
  • unofficial owners of the components through weak_ptrs; it's "unofficial" because it's through them that the components are updated
  • weak_ptrs are stored in a vector
  • once the (single) shared_ptr goes out of scope (e.g. when the entity gets deleted), a custom deleter tells the ComponentManager to remove the weak_ptr from the vector (this also adds confusion to this architecture)

Component and derived:

  • holds the core of the information and performs the main operations of the component
  • can reference other components through weak_ptr (hooked up at initialization, for now)

I strongly believe in using the right tool for the job, and although this architecture works, I'm curious to see if there are alternatives that would remove the confusion introduced by the shared_ptrs.

Maybe this is pertinent:

  • The amount of entities and components is relatively low, so we won't gain much by making efforts to keep them close together (the rendering and the physics simulation are taking most of our frame time).
  • We use this approach because of the re-usability offered by the components and entities from project to project.
  • The project is single-threaded.
  • The action takes place in the Component, not in the ComponentManager (i.e. it's a entity-component architecture, not an entity-component-system archiecture), so the Component needs access to other Components if they still exist, and these can be removed at any time.

I could use a kind of handle system through the ComponentManager and query the appropriate one every time I need an access to a Component, but this adds a dependency... (Maybe that's the price to pay..?!)

Do I have other options?

\$\endgroup\$
1
\$\begingroup\$

If there is a single owner and you can properly handle clearing dangling pointer it is fine to use unique_ptr for the owner and raw pointers for the non-owners.

You shouldn't be allocating separate components like that. This is a cache nightmare. Especially when you combine it with virtual function calls. Once the order of components is somewhat randomized due to add and remove churn the branch predictor will no help at all and you will hit cache misses a lot when trying to call update() on every component.

Instead put all components of the same type into a separate vector. And use a non-virtual function to update them.

foreach(CmpGfx& comp: manager.getGraficsComponents()){
    updateGfx(comp);
}

This lets it get inlined which in turn allows for interleaving of each iteration and further optimizations.

Even better if you can fix the index per entity then you don't need to keep a pointer in the Graphics component to the matrix component.

foreach(std::tuple<CmpGfx&, CmpMatrix&> comp: manager.getComponents<CmpGfx, CmpMatrix>()){
    updateGfx(comp.get<CmpGfx&>(), comp.get<CmpMatrix&>());
}
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.