Sign in to follow this  
landagen

Entity-Component Design Questions

Recommended Posts

landagen    376

I have recently read the Entity-Component article on here and I got really interested in the design.  It sounds great.  Functionality through property definitions.  I have been waiting for the followup article about implementation, but it has not come yet.  I have been reading and thinking about my own implementation and have a few questions.  First, I want to start off with my assumptions/design.

 

1.  All components inherit from a base component class that contains some pure virtual functions to be overwritten.

2.  Components contain only data and do not directly have functionality.

3.  All entities will be kept in a single container as the master list of entities.

4.  Entities are a class that can contain one or more components.

5.  A system can query the master list looking for entities that contain all required components.  Once it finds one, it will then typically reference that entity by an ID number, not a pointer

 

 

Some of my design/assumptions may already be drifting away from the concept.  I am trying to stay close to the concept because I feel that if I drift away from the component, then I may end up running into the snags the design was supposed to prevent.  So without further ado, here are my questions:

 

1.  How to provide custom functionality for entities that contain the exact same components.  For instance, I may want to have different AI routines for different entities.  My thought was to have a component that is a functor to the particular AI routine I would want to run.  That would be a required component in order for the AI system to find that entity


2.  How do most people deal with changes to the component list?  Do they use the observer pattern to inform systems when an entity changes or does the system query the master list every time to find the entities that match its criteria.  The observer pattern is the way I was thinking.


3.  How do you handle shared components.  An example would be several entities using the same material.  My thought was to just have a component that contains a shared_ptr to the material. 

 

4.  Is it okay to have some inheritance at the component level or would this break the design?  Based on what I know, I can't imagine it would break the design.  I wanted for instance my graphics system to be able to find all entities that contain a VertexBuffer component, but I also want my material system to be looking for a particular type of VertexBuffer component (say one that provides texels).   

 

5.  My understanding is that most systems contain a reference to an entity by entity ID and not by pointer.  Why is that?  Is it common for the pointer to change, but the ID to remain the same?  If so, what scenarios would that occur in?  Does constant lookup not cause performance issues? 


Also, any articles that go into some specifics about an Entity-Component system would be very helpful.  Thanks. 

Share this post


Link to post
Share on other sites
landagen    376

Thanks Juliean, this helps a lot.  Could you give some examples of components so that I can have a better understanding of the scope of a component.  I understood it to be like a position component, or a mass component, etc.  Basically you would group data in your components in a way that fits your systems.  For instance, position would be in a component by itself because it is used by the render system and the physics system, but other types of data only make sense together such as material data so it may be all contained in one component.  Another question I have is what is an ideal amount of components on average in an entity system.  I can see the trade-off of flexibility versus performance, but where is the sweet spot?  

Edited by landagen

Share this post


Link to post
Share on other sites
Juliean    7067

It depends a lot on your needs. I can show you some of my components:

 

struct Position : Component<Position>
{
	Position(float x, float y, float z): m_x(x), m_y(y), m_z(z) {};

	float m_x, m_y, m_z;
};

struct Direction : Component<Direction>
{
	Direction(float x, float y, float z): m_x(x), m_y(y), m_z(z) {};

	float m_x, m_y, m_z;
};

struct Rotation : Component<Rotation>
{
	Rotation(float x, float y, float z): m_x(x), m_y(y), m_z(z) {};

	float m_x, m_y, m_z;
};

struct Scalation : Component<Scalation>
{
	Scalation(float x, float y, float z): m_x(x), m_y(y), m_z(z) {};

	float m_x, m_y, m_z;
};

struct Bounding : Component<Bounding>
{
	Bounding(const BoundingBox& box) : m_box(box) {};

	BoundingBox m_box;
};

/* Light components */



struct Ambient : Component<Ambient>
{
    Ambient(const FColor& ambient): m_ambient(ambient) {};

    FColor m_ambient;
};

struct Diffuse : Component<Diffuse>
{
    Diffuse(const FColor& diffuse): m_diffuse(diffuse) {};

    FColor m_diffuse;
};

struct Specular : Component<Specular>
{
    Specular(const FColor& specular): m_specular(specular) {};

    FColor m_specular;
};

 

Note that it is probably personal choice if you want to have position, scalation etc.. seperated or stored inside a single "Transformation" component holding a matrix. Also note that you could argue over whether it makes sense to have seperate components for light, etc... . As long as you do not overload your components with unnecessary data and/or function, and do not overload the system with low-level functionality, everything is fine.

 

Another question I have is what is an ideal amount of components on average in an entity system. I can see the trade-off of flexibility versus performance, but where is the sweet spot?

 

Well in a good implementation, the amout of components should have little to no impact on performance, at least for processing, creation is another topic, but even that should come with only a very suddle cost. It all depends on the programming languege of your choice, though. In c++, you can use bitfields and static functions to quickly search for all entities with specifiy components, and access those components just as fast. variadic templates, e.g in c++11 make it even more easy.

Share this post


Link to post
Share on other sites
EWClay    659
1. Split the components even more. For AI I have a pathfinding component, a chase component, an attack component and so on. I don't have a problem with very specific components for certain tasks either, such as a sliding door component. You may be surprised at how easily a complex system can be broken up and how much simpler the design becomes when you do that.

2. Very low level systems such as physics and rendering that need to know about changes immediately should be managing their own data directly (and the components only carry an ID). For the rest I just iterate through the entity list.

3. Yes, share the data outside the component system and make the components distinct.

4. I would not use inheritance within components at all, except perhaps to derive from an abstract base component. The example of a vertex buffer is a poor one also as rendering APIs do not use inheritance in that way.

5. It doesn't matter much whether you use IDs or pointers. For me, pointers are slightly more accessible because you dont need to go back to the system to get at the data. I would use smart pointers at least to avoid the risk of memory leaks or dangling pointers.

Share this post


Link to post
Share on other sites
BloodyEpi    1152

One good article I read about entity-component-based-systems was this one about Ash in AS3:

http://www.richardlord.net/blog/what-is-an-entity-framework

 

It depends a lot on your needs. I can show you some of my components:

 

struct Position : Component<Position>
{
	Position(float x, float y, float z): m_x(x), m_y(y), m_z(z) {};

	float m_x, m_y, m_z;
};

struct Direction : Component<Direction>
{
	Direction(float x, float y, float z): m_x(x), m_y(y), m_z(z) {};

	float m_x, m_y, m_z;
};

struct Rotation : Component<Rotation>
{
	Rotation(float x, float y, float z): m_x(x), m_y(y), m_z(z) {};

	float m_x, m_y, m_z;
};

struct Scalation : Component<Scalation>
{
	Scalation(float x, float y, float z): m_x(x), m_y(y), m_z(z) {};

	float m_x, m_y, m_z;
};

struct Bounding : Component<Bounding>
{
	Bounding(const BoundingBox& box) : m_box(box) {};

	BoundingBox m_box;
};

/* Light components */



struct Ambient : Component<Ambient>
{
    Ambient(const FColor& ambient): m_ambient(ambient) {};

    FColor m_ambient;
};

struct Diffuse : Component<Diffuse>
{
    Diffuse(const FColor& diffuse): m_diffuse(diffuse) {};

    FColor m_diffuse;
};

struct Specular : Component<Specular>
{
    Specular(const FColor& specular): m_specular(specular) {};

    FColor m_specular;
};

 

I wonder,what are you using the templateparameters for? I'm still trying to find a good design, too.

Edited by BloodyEpi

Share this post


Link to post
Share on other sites
Juliean    7067

I wonder,what are you using the templateparameters for?

 

Ah, a speciality of my implementation - thats how I map my components to a bitfield. In short, the template is used determine the exact type of the component via a few static variables.

 

#pragma once

/*********************************
* Base component class
*********************************/

struct BaseComponent
{
	typedef unsigned int Family;

protected:
	static Family family_count;
};

/*********************************
* Component class
**********************************/

template <typename Derived> //template to pick implementation
struct Component : public BaseComponent {
	static Family family(void);
};

template<typename C>
unsigned int Component<C>::family(void) {
	//increase base family count on first access
	static BaseComponent::Family Family = family_count++; 
	return Family;
}

Now whenever I call "ComponentName::family()", it is completely assured that this meets the actual components "family" (or type, you could say), without special precautions.

Edited by Juliean

Share this post


Link to post
Share on other sites
landagen    376

2. Very low level systems such as physics and rendering that need to know about changes immediately should be managing their own data directly (and the components only carry an ID). For the rest I just iterate through the entity list.
 

 

Could you elaborate on this?  Are you saying that the ID in the component would essentially point to the data that is owned by another system (aka the physics system or the rendering system) and so for anyone to request data specific to one of those systems they would query that system by the ID in the component? 

Share this post


Link to post
Share on other sites
Juliean    7067

Could you elaborate on this? Are you saying that the ID in the component would essentially point to the data that is owned by another system (aka the physics system or the rendering system) and so for anyone to request data specific to one of those systems they would query that system by the ID in the component?

 

What he is saying is probably like what I suggested, that physics and rendering should be as little related to the entity system as possible. So in the best case, you wouldn't need to query the phyiscs system in any of your entity systems at all - just one time to sync physic bodys transform with the entity. A pointer or an ID would do the job, depending on the modularity you want for your system, and whether you want to do "easy swapping" - changing physics body reference without any logic applied to it, by simply chaning an nurmic id, that is - or you find it more convienient to fetch a pointer when chaning the id externally. More a matter of choice again, as I think. Same applies to every other low-level functionality you want to use with your components.

Share this post


Link to post
Share on other sites
landagen    376

Okay.  That makes sense.  So for performance critical systems avoid the iterating through entities over and over again while processing them.  Instead, opt to synchronize with an optimized format for that system and use that data instead.  

Share this post


Link to post
Share on other sites
EWClay    659
Yes. You might for example use a physics engine such as Box 2D. That doesn't stop you having a physics component to handle the connection between physics and game logic, it would just not be responsible for actually updating the physics and would not need to store the physics data.

Share this post


Link to post
Share on other sites
larspensjo    1561

2.  How do most people deal with changes to the component list?  Do they use the observer pattern to inform systems when an entity changes or does the system query the master list every time to find the entities that match its criteria.  The observer pattern is the way I was thinking.

 

The preferred way to handle changes is to have other systems poll the components. E.g. the position of an entity is used by the render system every frame. This is okey for data that is updated most the time, but it can quickly become inefficient for transient changes.

 

An example of this could be when a monster dies. A possibly bad way to handle this is to add a component that indicates that this monster is now dead, ur using a flag in a component that already exists. This flag would then continuously have to be monitored at all monsters, but all systems that depend on the fact. E.g. sound system playing special sound, render system drawing special animations, score system updating player score, achievement system taking note of kills. In this case, it may be better to use the observer pattern.

 

4.  Is it okay to have some inheritance at the component level or would this break the design?  Based on what I know, I can't imagine it would break the design.  I wanted for instance my graphics system to be able to find all entities that contain a VertexBuffer component, but I also want my material system to be looking for a particular type of VertexBuffer component (say one that provides texels).

I wouldn't say it is wrong, but it would kind of be an anti pattern to ECS. The whole idea with ECS is to get away from inheritance hierarchies.

5.  My understanding is that most systems contain a reference to an entity by entity ID and not by pointer.  Why is that?  Is it common for the pointer to change, but the ID to remain the same?  If so, what scenarios would that occur in?  Does constant lookup not cause performance issues?

Pointer, shared pointer or entity reference, doesn't really matter. It depends a little on what language you use. Of course, you must make sure to avoid dangling pointers. It may be a good idea to use the observer pattern to catch the case where entities are destroyed.

Share this post


Link to post
Share on other sites
Azaral    467

In my system I do the following: (I program using C++)

 

An entity is nothing more than a unique ID value. This is what is going to bind the entity's components together. There will exist an entity manager which will be responsible for assigning ID's to new entities and add and removing components from the entity.

 

A component is nothing more than a very small and defined set of data. It has a little data as possible. The component data is stored in an unordered_map which is then stored in a vector. Each index of the vector represents a layer or state of the game (viewing the galaxy map vs viewing the research menus). The map uses entityID's as its key. Thus, to access the health component of an entity, it would just be a matter of healthComponent[currentLayer][entityID]. The component containers are also kept outside of everything, even the systems. This allows for the easiest access. Nothing is assumed. Position component is simply that, an x value and a y value. If it can be rotated, it will have the rotation component, which will just be a value denoting it's current orientation. The manner of how it is rotated is determined by other components. Components that are purely boolean (contain nothing more than a bool value) are probably going to be a reverse list. Instead of the entity being the key of a map, the entity will instead be a value stored in a vector. One such example would be the clickable component, which denotes if an entity can be clicked on with the mouse. It is faster to simply go through a vector and grab entityID's than to go through a map and get values of the keys.

 

A system is also defined with as few duties as possible. For example, the rendering system just draws images to the screen, nothing more. It doesn't occlude, the occlusion system does that. It doesn't determine animation state, the animation system does that. Each system is passed the maps of the components it cares about. Before it performs an action, it will check to see if the entity it is about to act upon has all the necessary components for the action, and if not it simply goes to the next entity. If a system makes a change to an entity that would be considered an event, such as an entity dying, then that system will return a list of entities that has that occured. If the damage system has reduced an entity's health to zero, it would return this list. Those entities would then gain the dead component (or to be more exact, added to the list of dead entities). Systems that act on dead entities would then act on this entity and systems that act on live ones would not.


 

Share this post


Link to post
Share on other sites

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

Sign in to follow this