Nomad Game Engine: Part 6— The World

The engine’s glue

This post is part of a series where I’m documenting my experience building an ECS game engine from scratch. Check out the homepage for this project for more posts, information, and source code.

I’ve gotten a few messages from readers about how parts of the engine fit together. In a theoretical way, we’ve already covered this, but when it comes to the practical code, there is a missing piece — the world.

The world is (mostly) a nicety that we’re applying to ECS (similar to the handles that we added in a previous post). It doesn’t actually have much functionality, it mostly provides communication in certain situations, for example:

  • A system wants to retrieve a few components for an entity
  • All systems need to be notified about something (e.g. a component/entity being added or removed)
  • A method needs to be run on all systems (e.g. update())

There are a few ways of providing these capabilities, but the model that I’ve chosen to use is the “World”. The world provides interop between all of the disparate parts of the ECS — The component managers, systems, and entity manager. There can be multiple worlds, each with their own component managers and systems, but in simpler situations there is likely only a need for one (we will cover scenarios with multiple worlds in future posts).

Before going any further I want to briefly state one of the main goals when building this engine: I want to abstract away as much of the underlying implementation of the ECS/Engine as possible, and let users of the engine interact with it solely through simple and well understood venues. The world helps accomplish this by routing a lot of behaviour through it. For example, instead of calling the entity manager directly to create an entity, the world “owns” the entity manager and exposes a method that calls the entity manager and wraps the returned entity in a handle. In addition, when we tackle multithreading in the future, the world will be the place where we manage some of it.

From this stage forward, for simplicity’s sake, we’re going to conceptually think about one world as “owning” both the systems and the component managers. In this way, we have the following hierarchy:

Simplified hierarchy of the Nomad Engine

Obviously this creates a bottleneck where all the systems make calls through the world, but since no functionality is added to the world we can assume it won’t be a huge performance hit.

Interface

The world’s interface will give a good idea of what it actually needs to cover:

Let’s first go through the methods that are just sugar wrappers for existing functionality.

Init

As we talked about in our discussion about Systems, we have an “init” method that is called after the basic game initialization happens (systems & first components have been added), but before the game begins to run. Calling init() on the world will be forwarded to all its systems.

CreateEntity

For CreateEntity(), the World communicates with the EntityManager (discussed in part 3) to create a new entity. As discussed in part 4.2, we then wrap the entity in a pretty handle.

Unpack

Unpack is one of the utility methods that we will use the most when working with our engine. Unpack gives us a pretty interface to get a bunch of components from an entity. For example, let’s say we have a system that wants the Transform, Motion, and Health component for Entity 3. Instead of needing references to all 3 of those component managers, we simply do the following:

// Unpack usage example
Entity e = {3};
TransformHandle transform;
MotionHandle motion;
HealthHandle health;
world->unpack(e, transform, motion, health);

This will populate each of these handles with the correct components, and they can then be used as you’d expect to be able to.

Implementation: Component IDs

Before we get into the fact that we can pack multiple components into one function call, let’s write a simpler version of unpack().

Now we run into an issue — we don’t know how to get the component manager for a given ComponentType. There might be a better solution to this (If there is, I’d be more than happy to hear it!), but my solution was to assign each component type a unique component family id. My initial solution was to manually define a family for each component I create, like this:

struct Health {
int family = 3;
...
}
struct Transform {
int family = 4;
...
}

Doing this, however, means that the developer needs to keep track of the family ids to make sure they don’t collide. This is obviously bad — humans are prone to error and these ids really aren’t that important. So how can we automate this?

The solution is elegantly simple: All components now inherit from a BaseComponent class which has a static member (shared across all Components) that counts up from 0. As each new component is called, they are assigned a family by a static line (this will only get run once).

Side note: You’ll notice below that I’ve chosen to use the word “family” for the component IDs. this is because a “component ID” could be easily confused — it could be the id of the individual component of an entity, or the id of the type of component. By using the word “family”, it’s clear that we’re discussing the type of the component.

Here’s the basic implementation:

The output for this will be:

Health family: 0
Transform family: 1

Note that the ID for a given component is not fixed. Health's ID will not always be 0, but we can be sure that it will be unique. Now we can simply update our unpack method to look like this (we use the component’s family as an index for the componentManagers array):

Performance note: The static line that assigns a component family will only get run once, after which the calculated value will be returned. Ideally we could determine IDs at compile time — a solution is proposed here, but it looks like it won’t be supported in the future, so we may have to wait more for the perfect solution here.

Implementation: Casting up

Our family id can now tell us what index to reference in our componentManagers array, but unfortunately every ComponentManager is specialized to be a different type (e.g. ComponentManager<Transform> and ComponentManager<Health>are different types)! How can we store all of them in one array? Well, instead of being a pointer to ComponentManager, we just make ComponentManager inherit from BaseComponentManager, and keep a list of BaseComponentManagers instead. This is pretty much the same as keeping a list of void *, but at this point we’re managing so much of our own memory that as long as we’re careful we should be fine.

Implementation: Variadic templates

If you’re not familiar with variadic templates, check out this article to bring you up to speed. In short, it’s a way of taking a “parameter pack” of templates that we can peel off one by one. For us, this means we can take in an arbitrary number of components to unpack() and only have to write a small amount of code to handle it.

AddComponent and RemoveComponent

All component adding and removal will be done through the world. This is because there are actually two things we need to worry about when adding a component:

  1. Allocating/deallocating the required space in the component manager
  2. Notifying systems that a component has been added/removed
Visual representation of the calls made when adding a component

Because the world holds on to references to all of the systems as well as all of the component managers, we can provide an easy interface to the engine user. I’ll discuss specifics below.

This implementation is one of the places where my engine’s structure deviates from what a lot of other engines I’ve seen do. I will be writing a separate blog post discussing my logic for choosing this design, as well as exploring alternatives. For now, we’ll just dive into my implementation.

The core idea is that the world is always aware of which components a given entity has at any given time. Note that this implementation assumes a maximum of one component of each type on a given entity. The world knows which components each system cares about as well, and keeps a “mask” which represents which components are currently on an entity. When the mask changes, the entity mask is checked against the system mask to see if the system should be processing that entity. This image may help clear things up:

Entity Mask

In the above example, a system like the Movement system needs to act on entities that have Transform and Motion. Therefore, the system mask would be 110000. Similarly, the system that deals with player input might need Motion and Joystick, in which case the system mask would be 010010.

The implementation of the ComponentMask struct is as follows:

And here’s how we use it in the world:

This looks like an intimidating amount of code but the actual concept is pretty simple. Hopefully between the text description, image, and code it all makes sense. As always if there’s anything not completely clear feel free to leave a comment or email me.

I’ve left a couple methods up to the reader to implement, notably destroyEntity and addSystem.

Conclusion

While the world houses no game logic and very little functionality, it provides a central hub for a lot of the inter-module functionality of the Nomad engine. By putting all of this logic in one place, it’s easier to reason about the engine as a whole.

The next couple posts you can expect on the engine will cover the discussion about possible world implementations and then a post about the event system for ECS engines and Nomad in particular. If there are any parts of the engine that are confusing feel free to let me know!