15.5Real‐WorldSituations 257
One simple step we can take when dealing with large groups of heterogene-
ous data like that is to group similar data types together. For example, we would
lay out all the health components for all entities in the game one right after the
other in the same memory block. Same thing with armor components, and every
other type of component.
If you just rearranged them and still updated them one game entity at a time,
you wouldn’t see any performance improvements. To gain a significant perfor-
mance boost, you need to change the update from being entity-centric to being
component-centric. You need to update all health components first, then all ar-
mor components, and proceed with all component types. At that point, your
memory access patterns will have improved significantly, and you should be able
to see much better performance.
BreakandBatch
It turns out that sometimes even updating a single game entity component seems
to need a lot of input data, and sometimes it’s even unpredictable what it’s going
to need. That’s a sign that we need to break the update into multiple steps, each
of them with smaller, more predictable input data.
For example, the navigation component casts several rays into the world.
Since the ray casts happen as part of the update, all of the data they touch is con-
sidered input data. In this case, it means that potentially the full world and other
entities’ collision data are part of the input data! Instead of collecting that data
ahead of time and feeding it as an input into each component update, we can
break the component update into two parts. The initial update figures out what
ray casts are required and generates ray-cast queries as part of its output. The se-
cond update takes the results from the ray casts requested by the first update and
finishes updating the component state.
The crucial step, once again, is the order of the updates. What we want to do
is perform the first update on all navigation components and gather all the ray-
cast queries. Then we take all those queries, cast all those rays, and save the re-
sults. Finally, we do another pass over the navigation components, calling the
second update for each of them and feeding them the results of the ray queries.
Notice how once again, we managed to take some code with that tree-like
memory access structure and turn it into something that is more linear and works
over similar sets of data. The ray-casting step isn’t the ideal linear traversal, but
at least it’s restricted to a single step, and maybe the world collision data might
fit in some cache so we won’t be getting too many misses to main memory.
Once you have this implemented, if the ray-casting part is still too slow, you
could analyze the data and try to speed things up. For example, if you often have