• Register

CrossCode is A retro-inspired 2D Action RPG set in the distant future. Play the Demo and look for yourself!

Report RSS Optimizing an HTML5 game engine using composition over inheritance

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.

Posted by on

Hey, everybody!

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


Some time ago, Google released a very interesting video about optimizing JavaScript games for V8. Around 33:30 they describe how two properties of the code will decrease JavaScript performance, as they make it hard for V8 to apply its optimizations:

  1. Polymorphism: calling the same functions with lot of different input objects.
  2. 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.

It turns out that composition over inheritance in JavaScript will inherently lead to polymorphism and huge object sizes and therefore to performance issues. And the worst thing about this: these issues will not show right away but over time as you get a larger variety of complex 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:

entity-hierarchy

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:

entity-properties

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:

entity-process

And this, dear readers, is polymorphism to the max. To be precise, it wouldn’t be polymorphism for other object oriented languages such as C++. Those languages have an explicit object system, where the base class can be passed through these kinds of algorithms, guaranteeing fast property access. However, we’re talking about JavaScript here – a prototype-based language in which we merely simulate object oriented programming. As a result, we get flat object structures – one object containing the properties of all ancestor classes and the top class itself. So basically we have an individual object signature per entity class and therefore: polymorphism.

The consequence: the drawing and collision procedures are very hard to optimize for most of today’s JavaScript engines. And these procedures are the very bottlenecks of every game engine. So in short: bad news.

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.

entity-sprite-collentry

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.

Performance results


Optimizing JavaScript is tricky, since JavaScript engines do a lot of optimizations by themselves. This makes it more important than ever to avoid premature optimizations. You never exactly know what impact your changes will have on the overall performance. In our case, we detected the bottlenecks by comparing profiling results of the TechDemo++ and the current WIP version of CrossCode. Finally, to be certain that our optimizations did in fact help, we compared the overall performance before and after the changes.

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:

collision-test

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:

menu-test

Again, we got a general improvement:


Interesting: even though we basically applied the same optimization, we get more speedup in the other browsers this time, especially in Firefox. We have no idea why that is, really. Again, JavaScript performance is kinda like black magic - it's hard to tell why exactly things become faster.

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!
I hope you learned something new about the wild and frustrating world of high performance JavaScript.

Until next time!

Post comment Comments
booman
booman - - 3,651 comments

Thank you for taking the time to post your experiences in detail. Specially HTML5 optimization. Great Job!

Reply Good karma Bad karma+3 votes
GrindCrushLLC
GrindCrushLLC - - 81 comments

Great read.

Reply Good karma Bad karma+2 votes
Post a comment

Your comment will be anonymous unless you join the community. Or sign in with your social account: