TodoMVC implemented using a game architecture — ECS.

Building traditional GUIs with the Entity Component System

Andy Bulka
May 18 · 12 min read

Is the Entity Component System any good for building traditional GUIs?

It turns out that the answer is yes! Whilst ECS is most commonly used in building games, it can also be used for building a traditional web “form” style application like TodoMVC. However you will need to radically rethink how models, their data and behaviour is organised.

This is arguably a refreshing, mind-blowing lesson in GUI programming! 🤯😉

Image for post
Image for post
The TodoMVC web app

Live demo.

ECS — Entity Component System Architectural Pattern

The ECS (Entity Component System) architecture is the new hotness in game developer circles. Its a way of architecting your software that rejects Models as classes in favour of a more deconstructed, data driven approach. The results, in gaming apps at least, are massive reductions in complexity and huge increases in performance and maintainability.

When I read about ECS it got me thinking — why not use it for more traditional software, not just games?

You can read about Entity Systems in this Wikipedia article. The basic idea is to take the Object (that contains identity, data and behaviour) and ‘deconstruct it’.

  • Entity = like a model, but has no data and no behaviour 🤯 Entities are lightweight and dumb — just an id or name

Example — Traditional Class vs ECS

A traditional class Model of a Todo item would look like:

which would generate:

$ node example.js Todo item make lunch has id 1 and is completed
Todo item wash dishes has id 2 and is not completed

whereas the ECS approach would look like this:

Let’s break this down:

  1. Creating an entity is just engine.entity()

Notice there is no explicit looping. The System loops for us. All entities who have the ‘data’ component will be looped through. You can add additional components to the list, which will mean the system will loop through all components who have all those components (an and selector).

You can have multiple systems, they will run in the order declared. Each time engine.tick() is run, all Systems will be run.

Notice there is no need to store a master list of todos - the ECS holds all entities for us. If we did want to generate a list of todos, its actually a bit tricky. See the next section Gathering results whilst looping.

The freedom to attach any Component to any Entity

You now have the freedom to attach any Component to any Entity. But why bother?

You would think that a pure data Component like Position {x:0, y:0} is always closely tied with a specific Entity - so why break them apart? For example a Ball has a Position, a Person does not! What a Person entity surely needs instead is a Name {firstname:'John', surname:'Smith'} etc.

Well, it turns out that the opposite is often true — entities like Ball, Bullet, Car all need Position. And in more business oriented applications, Person, Employee, Manager all need a Name.

Thinking further along these lines, another useful component might be Address - many Entities would need that. Thus having a single declaration of Name and Address etc. which can be re-used many times saves having duplicate declarations and associated errors.

What about using Inheritance?

If you think that using class inheritance can achieve the goal of declaring Address only once - you would be right. However most languages support only single inheritance, so good luck with forcing disparate inheritance trees to inherit from a common Address class - that's a bit awkward.

With languages that support multiple inheritance (e.g. Python), you will have more luck — in fact multiple inheritance comes closest to matching the idea of ECS, where you can arbitrarily compose a model entity from as many Component data aspects as you like.

What about using Composition?

You could also solve the problem through composition — a class could reference a common instance of Address and any other common classes it needs. This would also achieve the goal of declaring Address only once and providing 'mix and match' composability of Models, that ECS has.

The ECS solution to composability

This is how we arbitrarily attach data Components to entities in ECS:

We have created an entity person 1 and attached both a Name and an Address Component. Running

results in:

person 1 {"firstname":"John","surname":"Smith"} {"number":12,"street":"Bounty Drive","state":"WA"}

You can attach even Components that have no actual data. Sometimes this technique is useful to mark entities e.g. a dirty Component flag for rendering purposes, or a delete Component flag etc. so that a System selector matches on this condition (of having this flag) and triggers that particular System to run. This technique is further discussed in the section entitled Components as Flags, later.

Here is an example of adding various ‘Flag’ Components to an entity — of course you would name the components better e.g. in TodoMVC-ECS editingmode is such a Component used as a flag. The presence of the flag Component on an entity will trigger a particular System. Notice that in this example the Component data is either an empty object {} or null. Typically (though your situation may be different) its the presence of the Component attached to the entity that matters, not the Component data.

This freedom to decouple data from entities is very powerful, especially when we take the next and final step in understanding ECS — Systems.

Systems

Systems are where most of the application behaviour happens, including rendering/updating the DOM.

The idea is to have lots of Systems, one after each other, each doing a bit of work that can be reasoned about simply.

Image for post
Image for post
TodoMVC-ECS Architecture — ‘Literate Code Map’ diagram
  • The above diagram was generated semi-automatically from Javascript source code residing in GitHub using GitUML.

Selecting

Systems are code blocks which run across subsets of entities which have all the components listed in each System’s declaration e.g. ['data', 'dirty'] means all entities that possess both these components will be processed.

Here is an example of a two Systems, which run one after the other:

I like to think of the list of Components declared on each System as like a CSS selector, except it applies to ECS entities. The system will loop through all entities who possess all the Components listed (an and concept).

Pipeline approach

System blocks run in the order declared, one after the other, each time engine.tick() is called. Each System will iterate as needed, then exit and the next System will run.

Systems form a multi-stage processing pipeline. A System often builds on the work of the previous System — this is a strength of this ECS architecture, the ability to break down work into easy to understand stages.

See Systems in this Todo app, below, to see a table Systems I came up with to implement TodoMVC and what they do. Its suprising to discover that one can build an app in this way.

Looping

As you have probably noticed, looping is central to ECS. I guess it comes from the gaming heritage, where there are many game objects to process.

If you have an explicit for loop in your code you are probably doing ECS wrong! Use a System instead.

Systems magically iterate across entites that match certain Components e.g. all entities who have a Speed component and a Position component. Its like having a CSS selector and running a code block for all matches.

If you don’t want to loop through anything, and just have to do some miscellaneous work at any stage, simply insert a System that matches on a single component.

Read more about this technique below, in the section The housekeeping Component trick

The tick

When there is any change to the application, you need to trigger engine.tick() again, in order to re-render the GUI. The tick function simply re-runs all Systems again.

Remember to boot your app with a lone call to engine.tick() at the bottom of the app javascript file.

Game based ECS systems typically run tick in a loop at 60fps which is not appropriate for our form based GUI, but by all means, enable the automatic looping feature of your ECS if you feel that strategically calling tick is too much to think about!

You may also have a use for the handy 'tick:after' and 'tick:after' events to run code before and after each tick runs all the Systems.

ECS — extra useful tricks

I ended up creating a couple of tricks to get ECS to do what I wanted in certain cases.

Gathering results whilst looping

Generating a list of todos is pretty easy — just declare a System which matches a Component that all todo entities have, and push each entity onto a list.

You can gather anything you want in this way, e.g.

  • push the whole data Component with push(data)

The only problem is that next time you tick(), todos is not cleared and your list will double, triple, quadriple etc. The easiest way to solve this is to utilise the 'tick:before' event and clear todos before each tick run:

I use the above technique to generate the JSON of all the todo data to persist in the browser storage. Try the live demo and hit refresh — the same todo items survive, because of this persistence.

Advanced reset of a variable

If you want to reset some variables or do some processing in the middle of the pipeline e.g.

System1
System2
<-- reset a variable here
System3

then you will need to create a System whose codeblock runs once, and define it at the appropriate point in your code — after System2 and before System3.

To create a System whose codeblock runs once, use The housekeeping Component trick, below, which defines a special Component and Entity pair, then create a System matching on them.

The housekeeping Component trick

If you need to initialise variables in the middle of a system run (remember the order that systems run in is critical, with one system often feeding results into another system) then reseting at the start or beginning of a tick won’t work. We need to be able to define a System which does the reset or housekeeping tasks, which we can place anywhere in our list of Systems. And we don’t want that System to loop, we want it to run once. Which means it must match only one entity.

The solution is to define a special Component and Entity pair

then whever you need to run some code, insert a System like this:

The entity name 'single-step' is arbitrary and can be anything. The Component name 'housekeeping' is also arbitrary, but is referred to in Systems, as you can see in the above example.

You can use this technique to insert as many sytems like this as you like, at the appropriate places in your code. I’m not sure if it is kosher ECS usage, but its a technique I found I needed.

Full code for gathering a list of todos

Thus the full code for gathering a list of todos, using the above housekeeping system technique, is

Components as Flags

Systems ‘doing stuff’ typically means updating component or other data and rendering based on component data. Interestingly, it can even mean adding and removing components from entities, which may cause other Systems to run, which hadn’t previously been running because entities with the right combination of components that the Systems were looking for did not exist.

For example, my 'think-todoitem' System decides whether a todo entity needs the GUI DOM <li> created or updated, adding either the component 'insert' or 'update' to each entity. Note that the 'insert' and 'update' components have empty data {} attached thus the component acts like a flag.

I use this trick extensively in this Todo implementation.

When I asked an ECS expert about this approach, he said:

Attaching and detaching components in order to activate a system is absolutely the way to go. In fact, it is one of the biggest advantages of using ECS, since, in this way, you are changing the behaviour by taking advantage of the composition.

Encapsulating Systems into classes

Just because we are deconstructing classes into an ECS approach doesn’t mean we can’t use classes to help us. Systems still work when created inside classes — they are registered with the engine and will run ok.

You might want to use a class to group variables that are private to specific Systems together, so they do not pollute the outer namespaces e.g. this.todos_data in the example below. Classes are also handy places to define functions that are private to specific Systems, too e.g. the save() method in the example below.

This use of Systems inside Classes is arguably nicer and more encapsulated.

The only thing to watch out for is that you need to instantiate the classes in the correct order, so that their Systems are created (via their constructors) at the appropriate point in the ‘pipeline’ of Systems.

Other thoughts on ECS

No Events needed

Whilst there are GUI events from the DOM, notice that there are no ‘internal’ events in this implementation. This is a very real benefit, as event flow can be hard to follow.

The efficiency of the ECS approach is not as good as an event based approach however, because we are re-rendering more than we need to. Internal events give us a finer grained control over what needs to be updated in the GUI. See my TodoMVC-OO implementation to see how efficiently event notifications from the model to controllers can work.

Optimising GUI updates

Adding a dirty flag to entities that need updating can fix this brute force re-rendering inefficiency. I’d recommend a Component called 'dirty' which can be attached to entities. Then refine your Systems to only match on todo data that is dirty e.g.

Notice that the last line of this System removes the dirty flag/component. When you change the data of the entity, you would add the 'dirty' component e.g.

Systems in this Todo app

Systems are where most of the behaviour lives. The idea is that each System is a loop which ‘queries’ our little database of entities and components and does something with the matching entities and components. I came up with the following Systems, which run in the following order, top to bottom, pipeline:

Image for post
Image for post
Systems in the TodoMVC-ECS app

View the TodoMVC-ECS GitHub project for a text version of the above table.

That’s a lot of Systems! But each System runs after the other, and each one is easy to reason about. That’s the benefit of ECS — its more ‘flat’ I suppose.

Remember, the systems will only run when you call engine.tick(), so don't forget to do that. When you want all the Systems to run again, simply call engine.tick() again.

Choosing an ECS framework

Entity Component System frameworks are actually relatively simple. They offer ways of:

  1. defining Entities,

For this project I chose to use the javascript Jecs library.

Image for post
Image for post
Jecs ESC Framework for Javascript — UML diagram
  • The above diagram was generated semi-automatically from Javascript source code residing in GitHub using GitUML.

The single file jecs.js can be copied into your project and with the usual <script src="jecs.js"></script> you are all set to go. Or you can npm install jecs and require it in your node projects.

For my Python ECS projects I use Esper which is a lightweight Entity System for Python, with a focus on performance.

Conclusion

This approach to wiring up GUI’s has been most refreshing. I find the ECS approach fascinating and will be looking for ways to use decoupled Systems in my future projects.

Resources

Demo

  • Running demo here, fully implements the TodoMVC specification.

ECS

TodoMVC related

  • TodoMVC-OO GitHub Repo — another of my TodoMVC implementations. The classic Javascript TodoMVC app implemented without a framework, using plain Object Oriented programming.

Diagramming

Credit

Created by Andy Bulka

Note: This project is not not officially part of the TodoMVC project — as it does it meet the criterion of “having a community” around it.

Other Links:

  • GitUML my biggest Javascript (and Python) project to date, with a complex GUI, which I am refactoring using the techniques in this article. GitUML lets you visualise your Github source code as UML via point and click, and have UML diagrams update automatically as you check in code.

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch

Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore

Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store