Entities can be a plain integer too!

Published February 23, 2015
Advertisement
[font=arial]

In this update I'll talk about the latest features of dustArtemis, int based entities and component pooling.

[/font]


[font=arial]dustArtemis[/font]



[font=arial]

dustArtemis is a fork of Artemis Entity System, which is a BSD-licenced small Java framework for setting up Entities, Components and Systems.

Entity Objects



Artemis used Entity class as a sort of abstraction, it gave the impression than entities were objects rather than plain IDs. Entity objects were kinda heavy weight to be fair, they had a reference to a ComponentManager, to a World instance, an UUID instance, a bit set instance for keeping track of components and a long ID.

First two were for providing convenience methods say, entity.addComponent(cmp) instead of world.getComponentManager().addComponent(entity,cmp) for example, then the UUID was probably there for serialization purposes.

This makes creating Entity instances kinda cumbersome, whatever thats in charge of creating it needs a World instance, a ComponentManager instance and the Entity will create an UUID instance and a bit set instance by itself.

First step I took is narrowing its scope: Getting rid of the UUID (you can always add it as a Component if you want) and using a plain 'int' ID. So now we're left with the World and ComponentManager instances.

This situation makes Entity instances ideal for pooling. At least in my mind, dustArtemis should provide fast and hopefully garbage free ways to make new entities, associate entities with components and process them.

Complementing Features



I implemented Entity instance pooling into the framework. I also added a nifty thing, a sort of ID allocator that would guarantee that every time you needed a new Entity it would have the lowest free available ID.

This is great since ever incrementing IDs are awful for the backing component arrays (they'd always get bigger and bigger for holding ever increasing entity IDs).

Integer Entities



Now the next step in my trail of thought was: If the only things in Entity instance are World and ComponentManager instance, and they're only there for providing convenience methods, Entity now was just behaving just like a Java Integer instance, ie, a crappy boxed int.

What if I just remove such conveniences and just use plain 'int' IDs for representing entities?[/font]// This turns this code:Entity e = world.createEntity();e.addComponent( new Position() );// Into this:int eid = world.createEntity();world.componentManager().addComponent( eid, new Position() );
[font=arial]

More verbose? Yeah, kinda, you should totally hang onto that ComponentManager reference, easier to do it that way. It also creates no garbage and no pointer indirection to fetch the ID.

Moreover, it removes the necessity of doing entity pooling, now the ID allocator does it for free since its what it was doing in the first place, managing unused ranges of IDs.

Thing I sidestepped was the bit set instance, that was actual useful data that its needed to keep track of the components of each entity. So I made it a ComponentManager detail, it holds an array of bit sets, and by just indexing it with the entity ID it can find out what its components are.

This makes a round trip through the ComponentManager necessary when creating entities, since it needs to create the bit instance for that entity. Inside that World.createEntity call, it notifies the ComponentManager passing the new entity ID, so it can do a null check to see if the entity has a bit set at that index, if it doesn't, it creates a new one. Not very pretty but straightforward.

Pooled Components



There are some components that might be handy if they were pooled. This is the interface I came up with for these cases:[/font]// Registering a pooled component.world.registerPoolable( Position.class, Position::new, Position::resetPosition );// Adding a pooled component to an entity.int eid = world.createEntity();world.componentManager().addPooledComponent( Position.class );
[font=arial]

Most defintively not in "fluent style" but it works well enough biggrin.png

registerPoolable needs a way to create new components of that type, that's why the second parameter is a Supplier of that component, which can be a static factory method, a reference to Position's constructor like in the example, an object that implements the Supplier interface, and so on. Pretty fexible.

Third parameter is optional, its a Consumer that can manipulate the component when the ComponentManager fetches an existing one from the pool, in the example its a reference to a static method in Position. You might want to say, reset the position component to 0 before its gets used by another entity, or you might deem it an unnecessary cost, in that case you can just avoid providing a resetter.

This has an impact in the ComponentManager since now you cant just iterate over an entity's bits and remove them, since some of them might be pooled. So now entity "cleaning" (which happens when an entity is deleted) is done in a two step process: First pooled components are removed and returned to the pool, then regular components are removed.

This became rather easy using the fixed bit sets introduced in the last entry, just copy the entity bits, & with pooled bits, iterate and return to the pool.

Imagine the Possibilities!



Together the fixed bit sets, plain int entities and the way ComponentManager handles components and mappers allowed for a few tweaks around the framework plus a few shortcuts I added to do some operations more efficiently (for example, adding/removing components without hash lookups).

That will be for another entry though, cya![/font]
7 likes 3 comments

Comments

CC Ricers

It can be counter-intuitive at first, but I quickly learned how awesome ECS is once you have most of the framework put together and working. I'll be writing up my own article on my recent work with ECS soon.

February 23, 2015 08:22 PM
nhold

I don't know how I would feel about using an ID in a language that supports objects but it's looking nice.

September 02, 2015 12:16 AM
TheChubu

As I alluded to in the entry, it simplifies everything. Entity pools stop making sense. Code gets simplified all over the place. Now I no longer pay 16 bytes (or more!) per entity instance, but just the 4 bytes for storing an int. And so on.

Objects are nice... just not always. Specially without value semantics, like its the case with Java.

September 03, 2015 01:29 AM
You must log in to join the conversation.
Don't have a GameDev.net account? Sign up!
Profile
Author
Advertisement
Advertisement