Reusing Artemis entities by enabling, disabling and storing them

As we mentioned on a previous post, we were having some performance issues in Vampire Runner and we were trying different approaches to improve its performance.

Introduction

One limitation of Android when making games is you have to avoid generating garbage whenever you can since the garbage collection would generate pauses on your games and that leads to a bad user experience. Then, we should try to reuse already created object instead of creating new ones.

In Vampire Runner, one problem we were having was that we were creating a lot of entities at a specific moment of the game, when we detected a new obstacle should be created, and that was making some pauses on the Android version.

As we use Artemis, we should try to reuse some entities when we can. For example, if we make a shooting game (like the Jetpac prototype I made) it seems a good idea to reuse bullets since their life cycle is really short. Ziggy made two blog posts about this topic some weeks ago here and here, however we followed a slightly different approach and we will explain it in this post.

Storing entities to reuse them

We created a concept named Store (similar to LibGDX Pool class) which let us easily store objects, in this case entities of one kind (for example bullets).

	free(T t) // returns an entity to the Store to be reused later

	get() : t // returns an entity from the Store, it reuses an object from the free 
			collection if there is one or creates a new object otherwise.

The idea is to, for example, instead of creating a new bullet when a weapon is fired, calling store.get() and set the component values as they should be, and when the bullet collides with something call the store.free(e) instead of deleting the entity, so we can reuse it later.

This is a generic approach and we can use different stores to reuse different kind of entities but it has a big problem, those entities keep being in Artemis world, that means they keep being processed (collisions, render, etc). A basic solution to this problem was adding a new state to the entity, and we explain that in the following section.

Enabling and disabling Artemis entities

Artemis supports reuse of entities by internally caching created entities inside the World class, however their state (which components their have) is not easily reused, and that was one of the big problems when creating a new entity, we wanted to reuse their state.

Our current solution to the problem was adding a new state to the entities, if they are enabled or not. Being enabled means the entity is processed by all interested EntitySystems, being disabled means the entity is still in the Artemis world but it is not processed by any system.

So, in our customization of Artemis we added three new methods to Entity to be called whenever you want to enable or disable an entity:

	disable() : disables an entity to avoid it to be processed on EntitySystems

	enable() : enables again an entity to let it be processed on EntitySystems

	isEnabled() :  returns true if the entity is enabled, false otherwise.

Then, we added new methods to EntitySystem API to let each EntitySystem to be aware an entity of interest was enabled or disabled:

	disabled(Entity e) : called whenever an entity of this EntitySystem was disabled

	enabled(Entity e) : called whenever an entity of this EntitySystem was disabled

In our case, we are using them to enable and disable Box2D bodies in our PhysicsSystem, and also to remove them from our render layers in our RenderSystem.

As an example, we have a nice video of Vampire Runner we made by changing the zoom of the camera to see the behind the scenes:

As you can see, when entities like wall, fire and Christmas stuff are behind the main character, they disappear. That is because they are disabled and moved again to their stores so they stop being processed by Artemis, in particular, stop being rendered.

Conclusion

By combining both solutions, we have an easy way to reuse created entities of one kind, like our obstacles tiles in Vampire Runner, while at the same time we can disable them when they are on a store to avoid them being processed.

In case of Vampire Runner, this solution improved Vampire Runner performance since we now pre create a lot of entities we need during the game and then disable them and enable them only when needed, in this way, we could avoid creating a lot of entities in one update after the game was started.

This is a first approach solution to the problem and seems good for our current games but it may not fit other type of games or bigger games, we don't know that yet.

If you use Artemis and you had this problem too, hope this blog post is helpful to you.

VN:F [1.9.22_1171]
Rating: 3.4/5 (5 votes cast)
Reusing Artemis entities by enabling, disabling and storing them, 3.4 out of 5 based on 5 ratings

Tags: , , , , ,

  • http://www.typhonrt.org/ Michael Leahy

    Nice blog and interesting in seeing some folks use Artemis on Android. One of the problems with various entity system efforts so far in Java is that there hasn't been wide use cases applied to them. You certainly came across a key performance aspect. 

    I have been working on a large fully featured component oriented architecture + general entity system that will hopefully get out there soon. What I do is even go a step further when an entity is no longer in use it is ripped apart ("derezzed" if you will) and its components are recycled. There is no pooling of the complete entity itself. An enemy may die, get recycled, and its position component or what have you may get instantly pulled back into a bullet entity or any other entity. The same with entity replication it pulls from the component pool before creating a new component. This works for everything in my component architecture efforts too / not just the ES. On Android the component pool is part of the Application level, so this applies across Activities. It works so well that I'm considering removing the "dispose" method from the architecture entirely which just cleans up the entity moving the components into GC and only offer "recycle". If the dev wants to fully dispose of components they'll just interface with the component pool. 

  • arielsan

    That approach is sounds very interesting to us since we thought something similar, one problem we found thought it was you have to go even more granular since you need to reuse assets too. For example, in our games we have Components with stuff like sounds or sprites of LibGDX. If we let any new entity reuse a SpriteComponent, we can't know if that Component had the Sprite we wanted, and we can't afford creating a new Sprite or Sound each time. A possible solution to that is to perform some logic when the Component is moved to the pool, like moving the Sound or Sprite (or whatever) to another Pool and reuse them when a new entity is created.

    Despite it sounds more aligned with the Artemis design to avoid the enable/disable of the entity by pooling components, it doesn't seem so simple and we wanted a quick solution for now. On the other hand, we have this enabled for all our games now instead of having to add different "layers" of pools in each of our games.

    However, we were probably going to prototype something like that in the future since we like the idea.

  • http://www.typhonrt.org/ Michael Leahy

    Indeed.. If I recall from looking over Artemis a bit ago it does follow the single component depth pattern. In my efforts components may be nested to any depth. A potential SpriteAnimation system would have a nested Sprite component or it can be located as a data component of the entity itself. Likewise a texture is actually a component in my efforts instead of wrapping an OOP API like libgdx. In that regard things are more granular.

    In the process of recycling individual components one can pass in a bundle / map of data or any object as necessary when requesting a component from the pool and the component will attempt to set internal state as necessary.

  • Ziggy

    I like the idea of adding an enabled/disabled flag to the entity. Are the Artemis changes checked in to your own Artemis fork? If so, I'll take a look. I didn't consider changing Artemis when I was working on my solution, and your approach seems like it might be cleaner.

  • arielsan

    Yeah, all changes are available in our github, at gemserk branch. Hope it can be of help for you. Write us if you need more detail of how to use it.