3
\$\begingroup\$

I'm writing an entity component system for a project I've been working on. It's currently in a working condition, and overall i'm pretty happy with it, but I've noticed that adding new components and systems to it requires quite a lot of boilerplate code.

I've been researching ECS a bit, and I'm not entirely sure if that's normal or not. None of the things I've read mention how much boilerplate is typically necessary to add new components or systems.

Here is how I designed my ECS. The Entity class carries an id and a map of smart pointers to it's components. Entities are defined in YAML, and a factory class parses the YAML and creates all the components an entity needs. Entities are stored in the main engine class.

Systems hold a vector of nodes, each node containing only the components needed by the particular system. Each update, the system loops through the nodes, removing ones with invalid pointers and updating ones with valid pointers.

As of right now, boilerplate for component creation is not too bad. Each new component must also come with a function that returns an instance of itself, and the factory needs to have it's map of function pointers updated to include said function.

Boilerplate for making new systems is worse. The pointers in each node are hard coded. If a node needs to hold a lot of pointers to different components, things get messy quickly. The Node constructor lists every component pointer. The function for registering entities has to check if the entity contains any of the components. The update function has to check to validity of each pointer in the node.

Obviously all of these things need to be done, but making changes to the components a node holds requires updating the system in 4 different places, and creating a new component requires updating things in the factory class.

If lots of boilerplate code is the reality of working with entity component systems, then I'm alright with it because there are a lot of benefits. However, I'm not satisfied with not knowing if there is a better way to handle these things.

\$\endgroup\$

2 Answers 2

2
\$\begingroup\$

If your biggest problem is with the Systems, switch from hand-written nodes to tuples. Then you can make some function and class templates to handle most of the work.

template <typename ...ComponentTypes>
class AbstractSystem {
  using Node = tuple<weak_ptr<ComponentTypes>...>;

  /* bunch of template boilerplate */
};

Though I really don't think that using weak_ptr is a good idea (which I assume you're doing since you mention "checking if a pointer is valid"). Your entities should register from systems upon destruction. weak_ptr isn't as efficient as raw pointers or even shared/unique pointers. It kind of negates half the point of even using the boilerplate-ridden and constrictive ECS pattern if you're going to slap in something like weak_ptr for every component access in your systems.

I don't know why your components "need a function that return an instance of itself." Such Clone functions fly in the face of what an ECS is actually supposed to be, as your components should be pure data (ideally, a POD struct) and not have any member functions at all. Your factory can be responsible for cloning the components it creates, perhaps via the standard copy constructor, for instance.

template <typename ComponentT>
class AbstractFactory {
  ComponentT* Create(yaml& data) {
    ComponentT* cmp = new ComponentT;
    /*...*/
    return cmp;
  }

  ComponentT* Clone(ComponenT* src) {
    return new ComponentT(*src);
  }
};

A theme you might be noticing here is that you have to make a lot of use of templates in a C++ ECS if you want to get rid of the excessive boilerplate they tend to require. Which means that you have to be OK with making use of said templates and paying the (sometimes extreme) compilation-time overhead they can incur. That can be taken to either extreme; some major game companies' engines still all but ban templates entirely while other (mostly hobbyist) engines are written with extensive and complex templates using things like Boost.Hana or MPL.

If you're dead set on the ECS form of component-based design, you might look into "simple" C++ ECS implementations like entityx for some inspiration.

\$\endgroup\$
2
  • \$\begingroup\$ I'm glad to hear that weak_ptr isn't a good idea. I've already switched entirely to shared_ptr and it did wonders for cleaning up the system code. As for a function that returns and instance of the component, I just meant a function that returns an uninitialized pointer to a base component. Each time I write a new component, I also store its creator function in a map of strings & function pointers. That was the only way I could figure out how to go from the components specified in YAML to an actual instance of the component. \$\endgroup\$
    – djscrew
    Jan 19, 2016 at 17:57
  • \$\begingroup\$ @djscrew: gotcha. The name map to factory function sounds like what you want. I've found it useful to instead map to a factory class though since there are potentially multiple operations you want, e.g. create, clone, load, save, destroy, validate, etc. that can be part of a component's manager rather than member functions of the component itself (and the manager can just forward to component member functions for the cases where you're fine with a simpler object model). \$\endgroup\$ Jan 19, 2016 at 22:36
0
\$\begingroup\$

Well, to me the description is a bit vague, but sounds like the main problem is with the entities/components logistics within your systems? If so, then why not just upgrade the way you build your systems?

These upgrades could come in flavor of abstraction and encapsulation, where the base system class has a set of abstracted pointer-keepers, which incapsulate all logistics on components, and the base system class has methods to add a keeper for a component type, a generic loop that runs through all the tracked entities with the corresponding components and a method to update your pointers, removing the obsolete ones. Also, for reducing the boilerplate code even more, filters can be generalized in a similar way to traverse and select entities in real-time (where needed) based on components they have..

Then a particular system derived from this class would, in the simplest case, only have a method like update(EntityId id, HashMap<ComponentType, ComponentInstance>)? (the example is in Java, sorry, idk what would be the analogy in C++ but you get the idea).

All in all, this looks more like an OOP problem than something related to ECS concepts.

\$\endgroup\$
1
  • \$\begingroup\$ That's a solution I thought about. Components in entities are already stored as a hashmap of type and instance, because each entity could have different components. The reason I haven't done this for system nodes is because it feels like overkill. The components needed by a specific system don't change and are known at the time that I'm writing the code, so with a bit of extra boilerplate code, I can get away with hard coding the pointers into a node. It feels simpler, and I'd imagine hard coded pointers are going to be more speed and memory efficient than a hashmap. \$\endgroup\$
    – djscrew
    Jan 19, 2016 at 18:37

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .