Redoing the Entity Component System
A few people have noticed that I’m currently giving the Entity Component System (ECS) a complete makeover. When people hear about this they tend to have a pretty obvious reaction.
The ECS in MonoGame.Extended as you know it today first appeared in April 2017. At the time, the goal was to get some sort of ECS into Extended by porting an existing library so that we didn’t have to write the whole thing from scratch.
What we did was port a C# port of Artemis which itself was a port of a Java implementation of Artemis. Oh, and that Java one was also a fork of the original Artemis project created in 2012. It seemed like a good idea at the time, and I guess it was, for the purpose of getting an ECS up and running fairly quickly.
Finding information about Entity Component Systems in 2017 wasn’t particularly difficult. There’s a fairly famous blog post from 2007 that gets referenced quite often. Keep on digging and you’ll find people talking about ECS in C++. Implementations in ClojureScript and they even turn up in some books.
However, even today there’s actually not that many well known ECS implementations. It’s quite difficult to find a definitive list but if you poke around the internet long enough you can get a feel for it.
Some libraries that have caught my attention include:
I’ve spent a bunch of time over the past year or so studying the differences between these frameworks. Some of them have great API designs, others are particularly good performance wise and others have some interesting features.
Then a new one caught my attention in late 2017.
Honestly, at the time I didn’t think it would be something worth persuing. After all, we already had an ECS and it was already an Artemis based one.
Fast forward to a couple of weeks ago. I found myself poking through our ECS code trying to track down a particularly difficult bug. As I got deeper and deeper into the inner workings of our port of a port of a fork I realized that there were layers and layers of complexity in this code.
I could see what the code was trying to achieve. Most of the complexity was coming from (attempted) performance optimizations. Although, some of the complexity appeared to be different kinds of “cleverness” added to the code as it got ported and ported from the original fork.
What is at the end of all this complexity? Bugs. Oh, and guess what, the performance isn’t that good either compared to some of the newer frameworks available.
So as I sat there and reasoned about how to fix these bugs that we’ve encountered from time to time, I started comparing our code to other ECS frameworks and relaized that all this complexity wasn’t getting us much. If anything, it’s hurting us.
Most of the other ECS frameworks have simpler code bases than ours. Even the ones that have heavy performance optimizations are well organized and easier to understand.
So the way forward was pretty clear. Take what I’ve learned by studying these other ECS frameworks and rebuild our ECS from scratch.
Rather than porting another framework directly, I’m building it up slowly and carefully, testing each bit as I go. I’m also taking care to remove complexity from the API to produce something that’s easier to use, keeps you in control and doesn’t get in your way. An elegant balance between performance and simplicity.
In the next post, I’ll show some examples of the new API design and why it works they way it does.