Entity-Component Design Questions

Started by
13 comments, last by Azaral 11 years ago

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.



Advertisement

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

There is no real difference to how you would provide different textures, position coordinates etc... to the component. You could eigther store an id to determine the type of AI you want to have ("ThisEnemy", "ThatEnemy"), or some sort of pointer. Actual AI calculations should probably, in case a generic AI isn't suitable, be handled by its own class. Your AI component would then just store an identifier for which AI you want to supply.

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.

My implementation queries the entities every time for each system. Having the systems store their own list of entities can become overly complicated, since you'd have to inform each system when:

- an entity gets added

- an component gets attached

- an component gets removed

- an entity gets removed

each system would then need to check whether the entity the component got attached is in the list, and if its new set of components fit, etc... . The approach by iterating through them every time is just more flexible. If you are concerned about performance, you can introduce some sort of caching system, that offers pre-sorted buckets for often used component groups that the systems query gets redirected too, instead of iterating over all entities every time. Note that you'd probably want to limit this "cache", since you'd have an enormous potential amount of possible combinations of components.

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.

shared_ptr is an option, alternatively you can store the name or an ID to the material in the component instead.

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).

It shouldn't be necessary to have (more than basic, to make everything function) inheritance. For once, I wouldn't even give a component a "vertex buffer component", this is too low level, entity/component is a rather high level component. You'd instead have a Mesh component with another pointer or ID, and if you need to play around with actual vertex data, I once again say that it is not in the responsibility of the systems to handle such low level tasks.

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?

From what I understand this is so that entity destruction etc.. is easier since you won't have dangling pointers that easy. As for the internals, entities are most of the time nothing more than a set of components stored in a vector - that way, having an actual "entity" class won't be more than a convenience-class wrapping around the ID with some expanded functions (directly accesing components, etc... ). If you feel safe you might as well implement such an Entity-class, I even went one step further and really stored the components inside the entity, didn't run in any problem yet. Note that however you shouldn't be required to store actual Entities anywhere anyhow. If you do, you probably have too much responsibility in the component system (e.g. a component system would never manage the low-level render interface via DirectX or such a libary, such a thing as a "render queue system" or "physics collision resolver system" shouldn't exist).

While I can't offer you an article, I can give you a link to EntityX, a C++11 entity/component where I took some inspiration for my e/c system.

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?



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.

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.

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.

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.

thanks! +1

I was going with strings set by the programmer, but this is a lot better and avoids type collision.

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?



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.

This topic is closed to new replies.

Advertisement