Jump to content
Sign in to follow this  
  • entries
    11
  • comments
    17
  • views
    27426

Entities can be a plain integer too!

Sign in to follow this  
TheChubu

2057 views

[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]
Sign in to follow this  


3 Comments


Recommended Comments

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.

Share this comment


Link to comment

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

Share this comment


Link to comment

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.

Share this comment


Link to comment

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
  • Advertisement
×

Important Information

By using GameDev.net, you agree to our community Guidelines, Terms of Use, and Privacy Policy.

We are the game development community.

Whether you are an indie, hobbyist, AAA developer, or just trying to learn, GameDev.net is the place for you to learn, share, and connect with the games industry. Learn more About Us or sign up!

Sign me up!