Due to some vacations and other distractions we're not quite ready to give you our bi-weekly CrossUpdate.
However, as promised, we now finally post our technical article about performance.
Here we go!
Incoming performance problems
We started with HTML5 game development around the end of 2011. We bought an impact.js license and started working on CrossCode. And since CrossCode demanded 3D collision, we modified the engine – and continued doing so until almost every nook and cranny was changed in one way or the other. So it’s safe to say that we did not only develop a game but a whole game engine with it.
And of course, whether you create a game or a game engine, performance is always an issue. To be frank, initially we did not pay too much attention to performance. We saw that our game was running with 60 fps on modern machines. 60 fps for an HTML5 game - what more do you want, right?
However, about half a year after we released the TechDemo++, things changed as we had a terrible realization:
Our game has become slower.
Yes, it was slower. Not because there was more content or complexity. When running the same maps with about the same number of entities, we experience an decrease in performance when comparing the latest development version with the TechDemo++.
Fortunately, we managed to fix these performance issues. And with this technical post, we want to share our experiences.
The core of the problem
- Polymorphism: calling the same functions with lot of different input objects.
- Object size: a large number of properties per object.
Our game, as any game based on impact.js, uses composition over inheritance to implement entities.
Bummer, right? Well, let’s first explain the issue with composition over inheritance in more detail. Afterwards we will show you how you can still fix all this without implementing the whole entity system from scratch.
Composition over inheritance under the looking glass
What does composition over inheritance mean in our context? The idea is that the game engine provides a generic entity base class from which all other game entities should be derived. Derived entities classes can add more properties and implement new functionality. In a game engine such as impact.js it is encouraged that each new entity is created as a new class. So what we get is something like this:
The first problem becomes apparent once our entities grow in complexity: since all functionality is stuffed into entity classes, we accumulate a huge amount of object properties. Here is an example how this applied to our ActorEntity class:
So in short: we easily passed the object property limit until which object access is optimized in V8.
The next problem lies in to the use of our entities within the game engine – the collision engine and rendering. Because collision and rendering is one process that is the same for all kind of entities, we inevitably arrive at this situation:
But fear not, as there are ways to fix this. And we don’t even need to throw away the whole composition over inheritance pattern.
Fixing the performance issues
Now that we understand the reason for the performance issues, it’s time to fix them. Since all these issues rise from the composition over inheritance pattern, an obvious “fix” would be to simply not use this pattern and move to something else, e.g. an entity component system. At this point we can basically overhaul the whole game engine itself – and that is not always an option. However, there is also the option to go for a mixture of both patterns, where you introduce components only for performance-critical aspects of entities and use composition over inheritance for everything else.
In our case, we decided to split the drawing and collision aspects of entities into separate components:
- The drawing aspect is enclosed into Sprite components
- The collision aspect is enclosed into CollEntry (Collision Entry) components
Both the Sprite and CollEntry components are free from any class hierarchy – there is only one class for each component, which is used for all entities. This is essential to make this fix work.
Entity properties are distributed among the entity class and the two new components. Since the drawing and collision aspects take a major share of these properties, the size of the entities class is greatly reduced.
To retain control over all properties, the entity will simply keep a reference to both the Sprite and CollEntry components
Finally, performance-critical algorithms of the game engine are modified to work on the new components only: the collision detection operates on CollEntries and the rendering on Sprites.
And that is all. In practice, these modifications demand a huge amount of code refactoring. However, most of these changes are of syntactical nature, e.g. “this.pos.x” needs to be changed to “this.coll.pos.x”, whereas “coll” is the reference to the CollEntry from within the entity. We managed to refactor our (fairly blown up) game code in about 2 days.
First, we compared the collision performance before and after we introduced CollEntries. For this we used a collision stress-test, where plenty of NPCs were simply running against each other:
We got the following results:
As you can see, we got a general improvement for all browsers. Especially the performance in Chrome increased a lot.
Second, we applied a similar optimization in our GUI system, where we extracted the most common parameters from the GUI base class into a separate Gui Hook class.
Again we used a stress-test, displaying the part of our menu with the most visible content:
Again, we got a general improvement:
Moral of the story
So what did we learn from all of this? How about: Drop composition over inheritance because it is slow! Right?
Frankly, we wouldn't make this kind of statement. It is clear now that approaches such as the entity component system are a better choice with respect to performance. However, composition over inheritance still has other advantages. For instance, we think that our inheritance based entity classes are much easier to read and understand. In the end inheritance will only lead to performance problems if your game reaches a high degree of complexity. So if your game only includes a small number of entities with a low number of properties (something close to the regular impact.js engine), you'll be fine. And even if you arrive at complex territory, you can still isolate bottlenecks and apply improvements as described in this article.
So in the end: there is no need to drop composition over inheritance all together.
And this concludes our technical report about performance!
Until next time!