Sign in to follow this  
  • entries
    72
  • comments
    104
  • views
    45408

The bottom of the system

Sign in to follow this  

163 views

Thought I'd at least pop my head in and say hi. My posting frequency is at an all time low and soon another semester will be upon us all.

I've had some time to spend on Citizen and as always these days it's gone straight into the Entity-Component system. I feel like it never gets to where I want it. The more I work on it the more I got left to do. I guess that is the typical story when you try to create something you've never done before, and I think that as long as I can afford the time it takes, this gives me more hard-boned experience than anything. It's just that I'm dying to get to the point where I can work on gameplay again. [sad]

Anyway since I don't have any pretty pictures to show you, and I want to give some substance beyond my confused rambling, I thought I'd post some code from the core of the entity system.

The problem:

As I've mentioned in earlier posts my implementation is based on a number of component managers containing components of their type. The entity object acts as a tag to associate with the components, and as an interface for the components to access entity-specific data. The managers do the real update work and they are completely isolated from each other. If they do need to access the state of the world they have a pointer to the entity manager object which is encapsulating the whole system, and which has a public interface for manipulating the entities in a controlled and safe way.

The problem I wanted to solve was to have a common foundation for the component managers. Up until now I had made a separate implementation for each manager, copy-pasting some common code (like adding and removing components) and modifying it slightly for each type. On the other hand there didn't seem to be much point in deriving from a basic component types since their constructors would have different signatures anyway. I ended up with the following template solution which turned out quite alright.

The code:

First there is the templated base class of the component managers.
ComponentManagerBase.hpp
class EntityManager;
class EntityData;
template < class ComponentType > class ComponentParameters;

// --------------------------------------------------------------

template < class ComponentType >
class ComponentManagerBase {
public:

// --------------------------------------------------------------

ComponentManagerBase( EntityManager *entityManager ) : entityManager( entityManager ) {}

// --------------------------------------------------------------

~ComponentManagerBase( ) {
if ( !components.empty( )) {
Log log;
log.printWarning( "~ComponentManagerBase: Some components had to be destroyed by manager destructor." );
}
for ( EntityIdComponentMap::iterator it = components.begin( );
it != components.end( ); ++it )
{
delete it->second;
}
}

// --------------------------------------------------------------

void addComponent( uint entityId, EntityData *entityData, const ComponentParameters ¶ms ) {
EntityIdComponentMap::iterator it = components.find( entityId );
if ( it != components.end( )) {
throw Exception( "ComponentManagerBase->AddComponent: Entity already has given component type." );
}
components[entityId] = new ComponentType( entityId, entityData, params );
}

// --------------------------------------------------------------

bool hasComponent( uint entityId ) {
EntityIdComponentMap::iterator it = components.find( entityId );
if ( it == components.end( )) {
return false;
}
return true;
}

// --------------------------------------------------------------

void removeComponent( uint entityId ) {
EntityIdComponentMap::iterator it = components.find( entityId );
if ( it == components.end( )) {
throw Exception( "ComponentManagerBase->RemoveComponent: Entity has no component of this type." );
}
delete it->second;
components.erase( it );
}

protected:

EntityManager *entityManager;

typedef std::map EntityIdComponentMap;
EntityIdComponentMap components;

};






The common trait among all the managers is that they have a container of components of their given type, here implemented as a std::map keyed with the numeric entity id the component belongs to. Furthermore they commonly need to provide a public interface to manipulate this collection, here the add-, remove- and hasComponent functions. They also make sure there is a valid pointer to the EntityManager object (which creates the managers) and that leftover components are destroyed properly when the manager dies.

The trick here is the forward declaration of the ComponentParameters class template, a so far undeclared template that holds the creation parameters for each component type. You can see that the addComponent function takes a reference to such an object, specialized on the manager's component type. Then it is simply passed along to the component's constructor so no specific details about it or what values the component wants are needed.

Then there is the minimalistic base class of the components.
ComponentBase.hpp
class EntityData;
class EntityManager;

// --------------------------------------------------------------

class ComponentBase {
public:

ComponentBase( uint entityId, EntityData *entityData )
: entityId( entityId ), entityData( entityData ) {}

uint getEntityId( ) { return entityId; }

protected:

uint entityId;
EntityData *entityData;

};






This doesn't do much more than manage some basic properties of all components, like a numeric id for the entity and the access into the entity-specific data.

Finally, here is Gun, a sample component manager and corresponding component built upon this foundation. I only post the declarations.
GunManager.hpp
class EntityManager;

// --------------------------------------------------------------

class GunManager : public ComponentManagerBase {
public:

GunManager( EntityManager *entityManager ) : ComponentManagerBase( entityManager ) {}

void updateByFrame( );
void shoot( uint entityId );

};





Gun.hpp
class EntityData;
class Gun;

// --------------------------------------------------------------

template<>
class ComponentParameters {
public:
ComponentParameters( uint cooldown, real exitSpeed )
: cooldown( cooldown ), exitSpeed( exitSpeed ) {}

uint cooldown;
real exitSpeed;
};

// --------------------------------------------------------------

class Gun : public ComponentBase {
public:

Gun( uint entityId, EntityData *entityData, const ComponentParameters ¶ms )
: ComponentBase( entityId, entityData ), params( params ),
currentCooldown( params.cooldown ) {}

Vec2 getPos( );
real getAngle( );
real getSpeed( ) { return params.exitSpeed; }
uint getCooldown( ) { return currentCooldown; }
void decreaseCooldown( );
void resetCooldown( );

private:

ComponentParameters params;

uint currentCooldown;

};






Note that in order for this to work the GunManager must inherit ComponentManagerBase, obviously, and there must also be an explicit specialization of the ComponentParameters<> class template along with the declaration of Gun. This way only the Gun component and the party calling the GunManager's addComponent function need to know the inside of ComponentParameters. GunManager doesen't even see it and ComponentManagerBase only passes it on. [smile]

That's all there is to it. Adding component types now is easy following the two criteria above, and the common component creation and keeping code is reused independent of type. I hope you've found this post useful. If you have any questions, ideas or reservations against it, you know what to do.
Sign in to follow this  


2 Comments


Recommended Comments

Good post, thank you for explaining with code. :)
I like the design, but I have a few questions, if you don't mind. Look out, ordered list!


  1. Is ComponentType supposed to be ComponentBase in "ComponentManagerBase.hpp"? It seems that Gun inherits from ComponentBase, but ComponentParameter uses ComponentType as a parameter.

  2. What's the role of EntityData, and why isn't there an EntityData subclass for Gun in Gun.hpp?


  3. Are the Entity IDs declared in an enumeration?


It would be nice to see a small example of how EntityData instances are created. I know that's a lot to ask, but I like the design so far and I might be able to use something similiar in my project. I'm sorry if I missed something, or if you explained this stuff before.

Share this comment


Link to comment
No problem. The code is a bit torn out of context I guess so I'll try to clarify.
Quote:
Is ComponentType supposed to be ComponentBase in "ComponentManagerBase.hpp"? It seems that Gun inherits from ComponentBase, but ComponentParameter uses ComponentType as a parameter.

No, ComponentType is the template parameter and ComponentBase is the base class for all components. They are not related. When the template is specialized ComponentType is replaced with the actual component type, Gun in the example. The fact that Gun inherits from ComponentBase is a separate mechanism, and is not necessary for the template part to work. In fact the ComponentManagerBase template could be specialized on any type as long as (A) it has the same constructor signature as the sample component (otherwise addComponent will break) and (B) there exists a ComponentParameters specialization for that same type. Otherwise the compiler will complain.
Quote:
What's the role of EntityData, and why isn't there an EntityData subclass for Gun in Gun.hpp?

EntityData is not specified here but it is the coordinator class for sharing data between components. Simplified, it has a container for data that belongs to the entity and not to specific components. Every component that is associated with a given entity gets a pointer to the same EntityData object through which they can update the shared data without knowing who else is. EntityData has no relation to the component types so having a subclass for Gun would be meaningless.
Quote:
Are the Entity IDs declared in an enumeration?

No, the entityId is simply an unsigned int (typedefed to uint because I'm lazy) that is unique to each entity. The reason I don't use pointers to the Entity object is that I want the front end interface of the system to only use basic types. I hope that this in turn will make my world a whole lot easier once I introduce scripting into the system. If the system front end only uses basic types then a scripting language like Lua should work right out of the box.
Quote:
It would be nice to see a small example of how EntityData instances are created.

Sure. [smile] The EntityData instances are simply created as part of the Entity instances.
class Entity {


EntityData entityData;

// .. other things

};

Then, when the system is asked to add a gun component to an entity, identified by id, the EntityData instance is sent to the addComponent function (of the manager template). All this is done by the front end addGun function of the EntityManager, which is the encapsulating object for the whole entity system.
void EntityManager::addGun( uint id, uint cooldown, real exitSpeed ) {

Entity *entity = findEntity( id );
gunManager.addComponent( id, entity->getDataTable( ),
ComponentParameters<Gun>( cooldown, exitSpeed ));
}

Here you can see how a the constructor of a specialized ComponentParameters class is used to pass the arguments to addComponent.

Don't hesitate to ask again if you need to. I don't mind explaining.

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