If you’ve ever written games, especially ones with many types of “pieces” or needed high-performance, you may have stumbled into some variety of the ECS architecture or half-discovered it by yourself.
In the early nineties, when writing some small games, it seemed to me that using too much of the Object-Oriented style of programming being popularized at the time was harmful to development. I tried to meticulously model my game objects with inheritence but it never worked out well.
What ended up working was to simply use class members to hold most everything about a game object and keep the hierarchy flat. I could re-use types as class members, but had to avoid code -re-use in the form of inheritence. Having read source code to animation libraries and other games, I know I wasn’t the only one who gravitated towards this approach. Nevertheless, I always felt I was “doing it wrong” by not learning to really use “the power of O-O.” It was obviously pretty effectively used in a couple of user interface libraries I knew, but somehow it never served game design well.
By the mid 00’s, the notion of “composition over inheritence” became popularized – implementation inheritence specifically – as explained here by Alan Holub . Inheriting interfaces in Java, sub-typing more generally, will keep polymorphism but eliminate bad effects of inheriting code. Unfortunately lots of programmers didn’t get the message for a long time. Code re-use was thought to be a main advantage of an object oriented design. Game developers have to do whatever it takes to make a game work on limited hardware, but even so Large game projects continued to use complex object class hierarchies with problematic outcomes.
The Entity Component-System design pattern (ECS)
Recently I ran across a few articles on the topic of ECS and immediately recognized the validity of the approach. The Entity Component-System, or Entity-System design pattern has been publicized in the game development community for a while, as far back as 2002, but I’d completely dropped out of touch with that scene. ECS formallizes practices that many have stumbled on. Now we can study the pattern as a whole and consider merits of particular ECS frameworks.
Note about terminology: There are “Entity Component” programming frameworks. The “System” refers to a part of the design pattern / framework, not as a qualifier on the “Entity Component” term; a unfortunate mixing of terms where “system” and “framework” could mean similar things. The “System” is often tied to particular components in a one-to-one relationship, as in “Component-System” combined with “Entities.” A better term for “system” might be “processor.” More on all this shortly.
ECS solves a few problems:
- Inflexibility of object oriented designs with deep hierarchies. Adding new object types that don’t neatly fit into your existing class hierarchy require painful adjustments to the design.
- Functionality gets very scattered as the object model acrues complexity. Maintanence and refactoring in particular become difficult
- Poor performance on large simulation types of programs. Some designs work against how CPUs cache data. With the right implementation ECS can maximize use of the CPU cache by keeping related data together in mostly contiguous blocks. Serializing and de-serializing become trivial.
- Changing system behavior with a traditional OO model at runtime is hard. ECS makes it easy. This can mean the difference between waiting for software engineers to make difficult changes and instead allowing designers to make large changes without rebuilding the core application.
An ECS design takes the “composition over inheritance” idea to the extreme; ECS is pure composition. With object-oriented design, you think of your program as modeling objects with these concepts: “is an sub-type” and “has a type-instance” and “operates on type” ECS design instead describes a design with “ “has a” and “do with”, but without encapsulation. “Entities” have (are composed of) “Components,” and “Systems” will “do
Where OO groups functions and data together, re-using code with parent-child relationships, and managing state in objects, ECS take the extreme opposite approach: All state and functions are separated, with the most minimal connection possible in the form of an entity identifier. There is no encapsulation in ECS as such; all changes to state – the components – are made via the system (functions which transform or use component data.).
But isn’t this terrible design? Maybe. Or maybe it’s fantastic.
Consider the Model-View-Controller pattern: MVC solves the problem where business logic gets tightly coupled with interface rendering code. We’ve seen the mess of PHP projects out of control with page after page of cut-and-paste functions full of only slightly different queries. MVC – even in PHP – by comparison is great. I wouldn’t suggest a direct analogy here, but think of ECS as a similar separation of concerns, used to achieve a scalable and sustainable application design.
Another point: Separation of application code and business logic should be your aim; an ECS design can serve this goal. A pure OO application could implement a core application with external user driven business logic, but ECS will make for a more flexible system and avoid temptation to model logic implicitly in class hierarchies.
The following is in a pseudo-code I made up today. Hopefully it’s pretty obvious if you’re familiar with OO concepts. The second part showing an imaginary ECS design takes a few liberties with notation as it’s a less commonly written about topic.
class Furniture is an Object HAS Color color HAS Room location HAS Integer weight class Table IS A type of furniture HAS Legs legs HAS Shape tableTop HAS Integer size Integer METHOD paint(Color new_color) paint_used = 0 color = new_color paint_used = size * 0.08 for leg in legs paint_used += leg.paint(new_color) return paint_used
Not too bad… until we consider making classes of every possible kind of furniture. Right away we see problems: All furniture has colors and exists in a particular room (unless it doesn’t – you’re hauling it in your truck!) Only some furniture has legs; in this case tables. What about chairs? Do you really want a TableChair class in your system? Or do you simply model both types with legs. Or do you make an “leggedFurniture” abstract type from which you extend Table and Chair? And the size attribute is problematic. Does it refer to surface area, or what, exactly? For a table that’s clear; most tables are at a standard height and the table top is what’s interesting (but not always, some coffee tables are at odd heights.) Bookshelves have only shelves and legs, dressers have drawers and tops, desks have all these… things are getting complicated.
Don’t you want to know the shape of the furniture in general, not just the tables? But for furniture in general you may want to know the outer dimensions for shipping purposes, whereas with the table you want to know if it’s a square, circle, oval or rectangle. So you add dimensions to the Furniture class. Do the dimensions have any relationship to the table top shape or size? A folding table will have differing dimensions, too, depending on if it’s folded up or not.
We’re assuming the “paint” method on the Leg class is written correctly; maybe the paint volume used per unit of size has changed. We can implement that part in the parent class but then lose all flexibility between types of furniture.
We’re barely scratching the surface. Hierarchical object designs can be very painful to refactor and even when they work, a real tangle of relationships. To really appreciate how this version of OO can create so much difficulty, imagine the above example multiplied by some large number, say 752 for a large application. Functionality can end up getting scattered all over the place.
Performance-wise, these objects would typically get stored in the heap at seemingly random locations. Cycling through them to read atributes of objects, reading references to other objects and in turn reading those, executing code on them, certainly isn’t cache efficient which can matter a lot for programs with thousands of frequently visited objects.
Think of components as arrays of simple types of data.
component <Shape> shapes component <Color> colors component <Leg> legs component <Drawer> drawers component <Shelf> shelves component <Integer> sizes component <Room> locations component <House> houses component <City> cities component <Street> street component <Car> cars component <LatLon> gps
There is no “Furniture” type. You construct the equivalent of a piece of furniture by combining the necessary components and attaching them to an entity:
my_kitchen_table = new Entity my_kitchen_table << legs my_kitchen_table << colors my_kitchen_table << sizes my_kitchen_table << shapes my_kitchen_table << locations my_kitchen_table << rooms my_kitchen_table << gps
Now it’s easy to make a desk, which is a hybrid table and bookshelf and set of drawers:
my_desk = new Entity my_desk << legs my_desk << drawers my_desk << shelves my_desk << shapes my_desk << colors my_desk << locations my_desk << rooms
Think of the “«” operator as “register with”.
With the still unused components defined above, we can connect my house with some house components:
my_house = new Entity my_house << rooms my_house << streets my_house << gps
And let’s add one more:
my_car = new Entity my_car << colors my_car << gps my_car << shapes all_entities = [my_house, my_kitchen_table, my_car, my_desk]
Now we can make a paint system that paints a particular entity:
paint(entity, color) if entity has colors colors[entity] = color
Let’s make a paint explosion system coloring everything within a 100 foot distance of the exploding paint truck:
paint_nearby_stuff(paint_truck,color) lat_lon = gps[paint_truck] for entity in all_entities if entity has gps lat_lon = gps[entity] if abs(lat_lon.lat - lat) < 0.005 and abs(lat_lon.lon - lon) < 0.005 paint(entity,color)
While this is very effective, (even the paint truck itself will get painted!) and the “entity has component” function can be efficient, the method suffers from needing to examine every entity. Advanced features of an ECS framework would allow for signaling “events” and communicating between entities after every frame or “clock tick” , and possibly linking components together.
In situations where the number of entities is small nothing so elaborate would be needed; you simply loop over all the entities and call every system in order – updating each component of the given entity – one of those components being the color,, and checking the same entity’s location. In such a design you’re not getting a performance boost, but still taking advantage of the extreme flexibility of an ECS design.
However, in cases where performance is critical you should update an entire component’s worth of data at once whenever possible, in order of memory storage, then move on to the next component.
for each component in components update(component) update(component) for each element in component process(element)
The “process” function will call on different code according to the type of element, but crucially each component is of a single element type, e.g.
, where “color” is the element type.
This has two advantages:
- Assuming your component data is stored contiguously – which it should be – you get a boost because the data will get put into CPU cache before reading
- You may be able to process your components concurrently on different cores.
Advantage of ECS
No matter how you implement it, a big advantage of the Entity Component System design is architectural: Modifying particular entities to add or remove traits and behaviors is easily done and requires no updates to the object model, just code to actually change the traits.
For example you might want a group of tables to get their legs removed for shipping;you could model the shipping of the table-tops and shipping the legs independently. These entities would be literally tables with their legs removed. Adding wheels to a car entity simply means creating a “wheels” component and adding any car entities to that component when you’ve attached the wheels at the factory you’re simulating.
Depending on your implementation and language, component types may or may not be changeable at runtime, and you may or may not be able to add or remove component types. However, even in a fairly static inflexible ECS design, adding or removing components on entities, and adding or removing entities can be done at runtime rather than at compile time. The design will either live in fairly simple code or possibly in a script. This is a very powerful advantage the ECS design gives you for modeling dynamic systems such as complex games.
In fact, if you’re willing to sacrifice a bit of performance in your systems you can out-source the system logic to an interpreted language that can be changed even while the main application runs. Some games, for instance, use Lua, a very fast interpreted language for in-game logic while the core game engine is written in C++.
With the right implementation an ECS design can surpass a standard object oriented design in raw performance.
Existing ECS Frameworks
Here are a few modern C++ frameworks to try out. Even if they can’t adjust to your particular needs they’re worth learning from.
- A modern C++ header only library,, keeping it simple. Meant for quick prototyping. ECS
- ES inspired by earlier frameworks.
And a few helpful posts on the topic:
- Adam Martin’s series Entity Systems are the Future of MMOG Development
- The GDC 2002 presentation by Scott Bilas, first formalization of the pattern as far as I know
It would be interesting to explore how a new programming language or language extension could make an ECS design more natural to express. A number of nice ECS frameworks have recently been released for C++11. These are pretty clean all things considered, but you need to use an “Entity Manager”, register components with entities and a few other things that look a bit ugly from a C++ OO point of view. If components and entities were first class parts of a language the “managers” wouldn’t be needed in the code (they’d be part of the runtime.) The real-world value of such a language might not be great but it’s interesting to imagine.
Also, it might be useful to think about an ECS design for software I’m working on. Would this approach improve performance or flexibility?