
Entity–component–system (ECS) back and forth - skypjack
https://skypjack.github.io/ecs-baf-part-1/
======
rgoulter
Catherine West's RustConf keynote provides a good discussion about why you
might want to organise "a program where objects interact" using an ECS system.
She discusses the "how you might write it with standard-OOP", some drawbacks
to this, and iterates this towards using an ECS system.
[https://kyren.github.io/2018/09/14/rustconf-
talk.html](https://kyren.github.io/2018/09/14/rustconf-talk.html)

I like that both this and TFA mention a focus on the data, and "row"/"column"
roughly compares to (say) an SQL table. - It's an interesting way of thinking
about objects/entities.

~~~
skypjack
Thanks for the link. I haven't read it before. It looks really interesting
indeed.

~~~
platz
And the response by Jonathan blow
[https://youtu.be/4t1K66dMhWk](https://youtu.be/4t1K66dMhWk)

One of his points is that you still have to solve synchronization for
allocations and deletions from the arrays.

------
jayd16
I feel like blogs about ECS are needlessly verbose when the idea is really
simple.

Problem: Most games are built in with a game loop ticking frames through many
gameobjects/actors. Many actors share some functionality like physics
collision, but there is also a lot of unique functionality on the same
objects.

Naive solution: Use inheritance to build out your core functionality and
override functionality as needed.

-This leads to a mess of interacting parts a massive type tree with many permutations of functionality.

-The deep type hierarchies blow out cache locality as the natural way is to tick through all functionality for a single object as you loop through all objects

ECS: Break functionality apart into discrete systems. Use composition to build
the permutations of functionality.

-Its easier to to manage what you intended and also easier to experiment with functionality through composition than inheritance.

-The natural way to loop through functionality is one system at a time. You loop over your entities many times but because you can run the same system code in a tight loop its really speeds things up.

-Systems provide singleton like functionality to store global state in a private way. In a heavy actor model we have to rely on static fields, which can be ok, but now you have a large cumbersome hierarchy of functionality with global access to state.

tl;dr

-Composition over inheritance is good in all walks of coding.

-Many discrete tight loops of systems is usually better than one loop of complex logic.

~~~
skypjack
I don't think the idea is difficult actually. What I've found difficult when I
decided to implement my own tool
([https://github.com/skypjack/entt](https://github.com/skypjack/entt)) was
that technical details on how to design something that was both easy to use
and with good performance were scattered all around the web. I'm just trying
to summarize what I've discovered so far for the "future me". :-)

~~~
ah-
Sadly the article doesn't mention how ECS typically solve the "holes" problem,
where your arrays might be quite sparse which leads to inefficiency.

Do you happen to know how that's usually done?

~~~
skypjack
Yeah, I'm the author of EnTT C++ ECS
([https://github.com/skypjack/entt](https://github.com/skypjack/entt)) and I
can guarantee you there are no holes there. :-) The next post (part 2 of the
series) will be more or less all about this point. I hope to publish it as
soon as possible. The idea is exactly to guide the reader through different
models, from the easiest to implement to the ones that are probably hardest to
develop but have no holes, perfect matches, higher performance and so on. Stay
tuned.

------
bitwize
ECS is one of those things you "get religion" about, especially as a game
developer. Like, "Oh, I've been fighting the class hierarchy and
overcomplicating my code for so long... it doesn't have to be this way!" I've
written about this as well, whilst developing my own game:
[https://nullawesome.tumblr.com/post/146127692039/entities-
an...](https://nullawesome.tumblr.com/post/146127692039/entities-and-
components-in-nullawesome)

~~~
w0utert
I went through a very similar thing writing my own toy/hobby game-project. It
started out as a classical straightforward OOP design, but once I started
adding extra stuff to it like animation, a scripting engine, etc. I really
started to feel a lot of inertia from the design and the awkward forced
hierarchies I had built. Over time I've been incrementally flattening the
design to end up with the kind of hybrid/half-way design mentioned in the
article, which borrows some things from ECS, but still has distinct game
objects implemented as classes.

I'm not fully on-board with the (between-the-lines) assessment in the article
that this is somehow inferior to, or should just be a stepping stone towards a
full-blown ECS though. At least not for a simple game where performance is not
really a concern. My view is that a hybrid approach can actually be _better_
than a 'real' ECS for those kinds of cases, taking the best of both worlds. In
my case I don't use any inheritance, only composition, I don't have to fight
OOP, but I can still write very clear and concise code to access and
manipulate properties of my game objects. It may not be cache-efficient and
whatnot, but that really is no concern for my simple game at all, having at
most 20 to 40 lightweight entities active at the same time. Physics updates
(which could drag down performance if too inefficient) _are_ treated like in a
traditional ECS, simply advancing the full physics sim for all entities
completely separate from the game objects.

Looking forward to the next article!

~~~
skypjack
Good point. I put it in wrong wording probably. I didn't want to say that it's
a shitty thing, just let the reader know that it's not a great improvement in
terms of performance. However, I point out also how the halfway approach
already is worth it and brings in some benefits, the same you nicely described
in your comments. ;-)

------
hortense
I've been wondering for a long time about how ECS can be cache friendly.

If you look at Overwatch's ECS (
[https://youtu.be/W3aieHjyNvw?t=326](https://youtu.be/W3aieHjyNvw?t=326) ),
there are many systems that can read from 10+ different components. For every
entity in this system, at least 10 reads with no locality whatsoever are done.
That seems crazy slow to me.

~~~
meheleventyone
The point of data oriented patterns is to structure data how it is used. So if
these lookups were bottlenecks you'd collapse components together if they were
always used together.

The other idea is that where there is one access pattern there will be more of
the same. So by updating by System you keep as much as possible hot in cache
by doing all the similar lookups together. So whilst the first lookup might
have ten complete misses the next one probably won't.

At a gameplay level there's a lot of chaos going on and entities are not
generally doing things that are easy to organize in a way that avoids cache
misses anyway. On top of which you are juggling ease of change with
optimization. At which point you just need to be fast enough. I'd also bet
most bottlenecks for Overwatch were not in gameplay code.

Mostly though people should be thinking in a data oriented way rather than
grabbing an ECS framework and expecting that to magically make things cache
efficient.

------
valkum
Here is a talk by Blizzard about Overwatch which uses ECS:
[https://youtu.be/W3aieHjyNvw](https://youtu.be/W3aieHjyNvw)

------
Jyaif
One day I'll write a blog post myself writing about how I went from ECS to
OOP.

The short version is that ECS works against creativity. I want my entities to
be unique: they move differently, they react to damage differently, they die
differently, etc... In a ECS world, this means having at least 5 different
components and 5 different systems per... type of entities. With just 10
entities you already have 100 different classes... that are never going to be
reused. Also, because I reimplemented my game to OOP I was also able to
compare the performance and noticed that the ECS performed worse (although
maybe my implementation was not good?).

~~~
dkersten
> In a ECS world, this means having at least 5 different components and 5
> different systems per... type of entities.

Shouldn't it be more data data-driven than that? That is, instead of 5
components and 5 systems, you have one or two components and one or two
systems and the components declarative describe the differing behaviours in a
data-driven manner.

In my personal experience, if you have objects or inheritance to define every
possible behaviour, the codebase becomes a huge tangle and performance will
suck and actually creating the content becomes a pain. I'd personally much
rather have components that define the general flavour of the behaviour and
then use data to fine-tune it.

And for the edgy edge cases.. have a script component that runs Lua or
whatever.

Of course, you can do that with OOP too and you can certainly write high
performance OOP, I just personally find it much harder due to how it
encourages distributing state across the codebase, _especially_ when you want
to do it in a multithreaded environment. But if you've had better experiences
with OOP, then more power to you, use what works for you. I would, however,
not write my own ECS, but use a well designed and well tuned existing one,
like the article authors EnTT.

------
pault
This is interesting. The vertical slicing the author talks about sounds a lot
like the conventional way to structure a functional component based UI.
Components + state tree + selectors + reducers in a typical react app fit
together in this way and it makes it very difficult to apply traditional OOP
design patterns. This is a huge source of contention on my team between the
front end and the back end developers who are all c# dotnet guys. If anyone
has any suggestions on where to find more information about functional, flow-
based application architecture I would really like to learn more.

~~~
bjornroberg
I's suggest reading a bit on the Elm Architecture. You don't have to use Elm
to appreciate or use the architecture.

------
eXpl0it3r
I really like the following article on ECS, as it shows the complete setup in
a very compact way, making it not only easy to understand, but actually quick
to implement yourself: [https://blog.therocode.net/2018/08/simplest-entity-
component...](https://blog.therocode.net/2018/08/simplest-entity-component-
system)

------
plopz
I'd be interested to read a follow-up on how to incorporate other data
structures such as quad trees, spatial hashes or scene graphs into an ECS.

~~~
meheleventyone
I look at it as EC and S. It's a datastore keyed by Entity handles that lets
look up Components. Data transformation is then driven by Systems. The
behavior of a game is then driven by the tick order of the Systems.

From this view of the world it's fairly obvious that other structures need to
reference entities through their handle. Or in the case of middleware with its
own view of the world a translation layer keeps things synchronized.

------
vbuwivbiu
is this right ?:

'entities' ~= object references (ids of objects)

'components' ~= properties (instance/member variables) of objects (maps of
entity ids to components)

'systems' ~= algorithms that operate on lists of components

------
x3n0ph3n3
A definition of "ECS" would have been nice.

~~~
fenomas
Here's my simple explanation. Making a game with object hierarchies, you might
do the equivalent of this:

    
    
        monster = {}              // create an entity
        monster.hp = 10           // give it properties
        monster.pos = [0,0,0]
    

Using ECS, you instead do the equivalent of this:

    
    
        currID = 1                // ECS init
        hpData = {}
        posData = {}
    
        id = currID++             // create an entity
        hpData[id] = 10           // give it properties
        posData[id] = [0,0,0]
    

Obviously neither version is really implemented that way, but hopefully it
shows the key insight - instead of storing properties on the object they
describe, you store them in tables full of like data, _keyed by the ID of_ the
object they describe.

~~~
reificator
Showing the how without showing the why is probably not helpful IMO. Why is
important here.

Using ECS is somewhat like treating your game state like a normalized
database. It lets you perform operations on all the items of a certain type at
once.

Say in my game I need to recenter my origin on a regular basis. In an open
world game, you want to make sure that players don't start to see errors
caused by floating point arithmetic, and the further away their position gets
from 0, the worse this gets. To fix it, one option is to transform all
positions by the same amount to keep the player's absolute position near (0,
0, 0).

To do this in the hierarchy style, the code might look like this: (Using JS
because I was writing it today so that's where my brain is)

    
    
        var gameObjects = world.getAllGameObjects();
        var offset = [-1000, 0, 500]; // Pretend it's a real vector
    
        // Iterate through every single object in the game
        // (Not using foreach or filtering in either example, sue me)
        for(var gameObject in gameObjects) {
    
            // Branch on every game object just to see if it's positioned in the world
            if(gameObject.position !== null) {
                // Do what I really wanted to do
                gameObject.position.add(offset);
            }
        }
    

Meanwhile, the ECS version would look more like this:

    
    
        // This can vary greatly based on implementation
        // I'm going to go with an option that reads easily
        // This should just give back the list of components, which we'll say is an array.
        // Lookup time in this fictional system is the cost of a dictionary lookup.
        var positions = world.getComponents('position');
        var offset = [-1000, 0, 500]; // Pretend it's a real vector
    
        // Iterate through only the things we care about
        // Not using map here, sue me
        for(var position in positions) {
            // Do what I really wanted to do
            position.add(offset);
        }
    

Not only is the second example shorter, but it's also doing a _lot_ less work.
We're adding a vector to an array of vectors. No branching, no touching
objects we don't care about, nothing extra.

Many ECS systems will allow you to filter down to the set of entities that
have two or more components at the same time. Obviously more expensive, but
still not as wasteful as checking every object or adding an update function to
run every tick on every relevant entity.

The above code was written just before bedtime and not against a real ECS
library or OO hierarchy, so please forgive any obvious errors.

~~~
infogulch
> like treating your game state like a normalized database

This makes me curious, how bad would it be to _actually store_ game state in
say, an in-memory sqlite database? I feel like this could get you some nice
things, like auto-destroying all components of an entity via foreign key
deletion, being able to merge/join entities with the intersection of multiple
components etc. Assuming the clunkiness of interacting with sqlite is
abstracted away, I wonder what the real performance differences would be.

~~~
imtringued
Compared to iterating over an array of structs (or a struct of arrays if you
want to be fancy) it's probably multiple orders of magnitude slower.

And lets be honest, half of my performance problems in webapps are caused by
the overhead of sending a query, the database is rarely bottlenecked by
inserting or updating the data on the physical storage device if I do not use
bulk insert or bulk update methods.

~~~
setr
I assume you mean sending a query over the network, as most webapps do;
sending it to a local sqlite file should be far less impactful.

~~~
reificator
But still not free.

Please try it out though, I would love to see a proof of concept that took the
ECS-as-database approach. Like I said earlier in the thread I expect it would
not do well, but I'd be interested to see the results either way.

