Entity-Component System Implementation

Started by
5 comments, last by baystep 9 years, 10 months ago

So I have some questions. First, some background. I consider my self a Novice programmer. I've been coding in *some* way since I was 9 (25 now). But after leaving Web Development / System Design, I went back to my dream of Game Development and started back with Java. Then trying to follow my ultimate goal, went to the LOVELY C++ language. Now I'm not GREAT at all in C++ since I'm trying to learn as I go, but I can follow the basic programmers theories.

Now my issue is I'm trying to write a mostly basic 2D engine for Cross-Platform game dev in C++ using SDL. I have boost waiting, but I'm waiting for the absolute time where I'll need it before exponentially extending my code base. My goal with this is to have something.... headless? I dunno if that's the word, but basically I want this engine to work almost in a Java libgdx way of only needing to extend/inherit the needed base classes to get something hopefully AA game worthy out of it (not AAA). But in researching the ECS idea of Entity-Component-Systems it seems to be the best bet. I don't really want any game specific logic in the engine, the engine should just handle the memory accesses and what not.

So to explain it better. In trying to implement the ECS system I went to using a handle type system. Which is basically a Handle Manager that dishes out uint32's (unsigned int's) for a "handle", and stores the pointers to that object in a vector for hopefully easier memory management if I need to move objects around in RAM (and hopefully that vector helps boost cache speeds and limit cache misses). NOW, the thing I'm confused about, because all the research points towards theory instead of actual code implementation, is that:

  1. Entities should just be ID's and a holder of some sort, and Components should just be data/properties describing it. Finally Systems should actually dish out the logic in doing what-ever-it-is that the components want it to be. Whats the proper way to have the components interact with each other? And to register to the systems? Ex. A render component will need a position component to know where to render to correct? And a potential Fustrum Culling method might need both? Whats the best way to go about it?
  2. Is this an efficient to do it in this way at all? In the way that if my "game" is inheriting a base-class that just starts up SDL (window and renderer), saves some needed classes for file streams, entity manager, state engine etc. And the only thing really needed to "override" is the initialization and cleanup methods? Then inheriting a "state" base-class that adds itself to the state machine and overriding the init, cleanup, execute, event handler, etc methods? The states are then in charge of maintaining their own entity/handle systems and passing this to the "global" (in the sense of it's the main game object's property) render systems? I'm confused on how this should all be mapped together.
  3. Finally, to wrap all of these together, whats the best OOP/ECS fashion to connect all of this without breaking all the "rules" and creating debug/memory leak nightmares (ie: singletons and such).

I'm hoping that if we can get enough ideas and such that this might serve as a helpful incite for other beginners on how to implement these types of systems. I'll post my code too when I get it working (I'm all for open-source). And sorry to ramble if I did.

Advertisement

First of:

1.) There is no "the right way" in using component based entities. Having no explicit entities or concentrating behavior in systems are not necessarily characteristics of ECS.

2.) In OOP you have 2 ways of building your classes: Inheritance or composition. Inheritance was new with OOP, and so it was often represented as panacea. However, many many times composition is the better way to go. ECS is just a buzzword that means composition to create game objects. That does not mean that inheritance is to be dropped, and it does not mean that everything and all should be pressed into the ECS scheme.

That said, let's look at your questions:


… Whats the proper way to have the components interact with each other? ...

You want to establish an ECS using sub-systems. So components do not interact with each other in any way. Instead, sub-systems work on the components. They have access to the components managed by themselves, and they have access to other components by requesting other sub-systems.

Components of a specific type belong to a specific sub-system. E.g. the Placement component (an entities position and orientation in the world) belongs to the SpatialServices sub-system. There is no real need to have an own sub-system per component type. E.g. the bounding volume may be managed by the SpatialServices, too. If an entity is instantiated, an ID is generated for it, its component descriptions are interpreted, the belonging sub-systems resolved, the component description (along with the ID) handed over to the found sub-system, so that a new component instance is allocated and initialized therein. (Alternatively and perhaps easier to maintain: All existing sub-systems can be iterated and the entire description can be handed over, letting the sub-system look for belonging component descriptions.)

Because each sub-system stores the belonging components with respect to the entity ID, a sub-system can request foreign components from other sub-systems just by the ID. However, I've named sub-systems "services" because they may provide more sophisticated accessors as well. Looking again at the SpatialServices, it is a good place where to implement spatial proximity, collision, and containment requests. For this purpose the SpatialServices may internally use e.g. an octree structure. A client may request "give me a list of IDs for all entities where the bounding volume are at least partly contained in the overhanded test volume". So rendering may be supported (frustum culling), physics may be supported (collision detection), AI may be supported (proximity / sensing), and perhaps others, by implementing more or less general requests in the SpatialServices sub-system.

An advantage of sub-systems is that components naturally have a "me" centric view, while a sub-system sees all components (of a specific type) side by side. For example, when two dynamic entities collide, a sub-system sees a collision between entities A and B, but from a components point of view component A reports a collision with B and component B reports a collision with A. This is because sequencing of components is a feature of the sub-system but not of the components themselves.

Question now is, how does a sub-system get access to the other sub-systems? IMHO there is nothing wrong with using a singleton if you really be sure that there will be at most one single instance. However, when looking at the amount of sub-systems, I prefer to instantiate them and then link them to the project (or "game" if you prefer that term) structure; the resource libraries are linked there, too.


… Ex. A render component will need a position component to know where to render to correct? And a potential Fustrum Culling method might need both? ...

This is in general already answered above. However: A render sub-system (not the component, see above) is likely be implemented in layers. The upper layer performs view culling, collects all necessary data, and generates rendering jobs. The rendering jobs are enqueued, because they often are sorted depending on the rendering pass (opaque, translucent, depth-first, whatever) w.r.t. optimization by reducing state switching. The jobs are then processed by the lower layer, typically an abstraction of the underlying graphics API. If doing so you have both: The upper layer requests other sub-systems, while the lower layer is supported with the necessary data.


Question now is, how does a sub-system get access to the other sub-systems? IMHO there is nothing wrong with using a singleton if you really be sure that there will be at most one single instance. However, when looking at the amount of sub-systems, I prefer to instantiate them and then link them to the project (or "game" if you prefer that term) structure; the resource libraries are linked there, too.

If there is a dependency between "sub-systems", I prefer to pass a reference to one sub-system to the other at creation time. Using some kind of service locator pattern (which I think is what you're referring to) is ok too, but less ideal (and using a singleton is just lazy).


If there is a dependency between "sub-systems", I prefer to pass a reference to one sub-system to the other at creation time. Using some kind of service locator pattern (which I think is what you're referring to) is ok too, but less ideal (and using a singleton is just lazy).

I agree that I resolving sub-systems again and again when the game is already running is not ideal w.r.t. performance. However, I actually meant that a sub-system can look up other sub-systems whenever it wants to do so. But passing required references at creation time means that a mediator knows of all dependencies and hence creates the sub-systems in a correct order.

The method I use is as follows: A sub-system is created, and a reference to the project is passed in at that moment. The sub-system is requested to initialize. In this routine the sub-system looks up for all sub-systems depending on, and if it founds all of them and all of them are in a workable state, then the new sub-system reports self as workable. All sub-systems are then notified that another sub-system was added, allowing them to rerun its initialization e.g. if they were not workable before. This way the sub-systems resolve their dependencies (as long as they are not circular) without any need for a specific order of creation or a mediator.

Thanks for the super detailed replies!

I like the sounds of a sub system idea. So far I have a class for System Management, which stores all the "sub-systems" and should handle creation, clean-up, updating, etc. So just like entities it stores this list in a std::vector object. The manager also handles a messaging sub-system (unless I should put that into it's own system?) that handles sending out messages between systems and entities almost like events. This works right?

For example, when two dynamic entities collide, a sub-system sees a collision between entities A and B, but from a components point of view component A reports a collision with B and component B reports a collision with A. This is because sequencing of components is a feature of the sub-system but not of the components themselves.

Who checks that? Isn't that a job for a Physics sub-system to check it's list of entities for collisions? And why does a component need to know about the collision at all, isn't the components only job to hold the data and essentially "flag" the entity as needing that service?

The rendering jobs are enqueued, because they often are sorted depending on the rendering pass (opaque, translucent, depth-first, whatever) w.r.t. optimization by reducing state switching. The jobs are then processed by the lower layer, typically an abstraction of the underlying graphics API.

In terms of "jobs" and queuing, what's the best way to handle this in C++? I'm guessing a vector of structs to hold the job info, but even then, what kind of data should the struct hold? My only guess is it's really just a list of entity handles that need to be drawn then, and the render system then get's all the info it needs from the other sub systems.

Also, you mentioned that the sub-systems should talk to the other systems to get the info they need, ex: A render-system, calls on the spatial-system to get the spatial data it needs for the requested entity. But isn't this almost un-necessary calls? Why couldn't I just *cheat* and let the render-system find the spatial component?

And finally, what's the semantically correct way to check if a property or type exists within a vector array in C++? For instance, my entities contain a list of components. All my components inherit the base component class which really only contains an update (which I'm going to remove since it's not needed) and a handleMessage function for messaging. But since I want components to just be data/flagging I should really remove both of those and let the systems handle that. Anyways, how would a render-system check an entity to see if a spatial-component exists?


Who checks that? Isn't that a job for a Physics sub-system to check it's list of entities for collisions? And why does a component need to know about the collision at all, isn't the components only job to hold the data and essentially "flag" the entity as needing that service?

Please read again carefully. I mentioned this as an example to demonstrate that sub-systems may be better suited due to their outside view.


In terms of "jobs" and queuing, what's the best way to handle this in C++? I'm guessing a vector of structs to hold the job info, but even then, what kind of data should the struct hold? My only guess is it's really just a list of entity handles that need to be drawn then, and the render system then get's all the info it needs from the other sub systems.

The jobs are structures, yes. The job usually consist of a key for sorting, a render state description, and a draw call. Whether data is embedded or referred to depends on the data. Of course, heavy weighted things like resources (e.g. meshes) are referred to, but light weighted things like render states are usually copied.

If interested in, you can find detailed information in a couple of posts:

Advanced Render Queue API

Frostbite rendering architecture question

Sorting draw calls when stages are executed multiple times

Reducing state changes

and others


Also, you mentioned that the sub-systems should talk to the other systems to get the info they need, ex: A render-system, calls on the spatial-system to get the spatial data it needs for the requested entity. But isn't this almost un-necessary calls? Why couldn't I just *cheat* and let the render-system find the spatial component?

Cheating, if I understand you correctly, means to make the internal structure of one sub-system visible to another sub-system. That breaks encapsulation with all associated disadvantages. Having a clean interface is the ground for easy maintenance, so to say.

However, let's look at some use cases:

a.) The engine supports "mechanisms" which are essentially manipulators of a Placement component due to external conditions; examples are Parenting, Tracking, Aiming, and so on. They are self components and can be understood as pluggable behavior. When a mechanism is installed in the belonging sub-system, said sub-system can of course resolve the targeted Placement once and store a reference within the mechanism component itself. This does not break encapsulation, because the Placement component, although assumed to be managed by another sub-system, is part of the public interface. However, it is required that the sub-system that manages Placement components can be requested to return the component of interest.

b.) Letting the SpatialServices sub-system deal with hit tests has the advantage to implement this once. There are several other sub-systems that use it, e.g. rigid body collision detection and resolution, event triggers, sensing for AI, and perhaps others. Whether the SpatialServices does this with a full fledged octree, an adaptive octree, a sorted list of bounding volumes, or whatever, is an internal aspect. Another sub-system should not (I'd say must not) need to know about. So, if the interface of SpatialServices provides requests like

uint collectPlacementsHitBy(Ray const&, Collection<Hit>& collector)

uint collectPlacementsTouchedBy(Frustum const&, Collection<Placement>& collector)

...

the inner structure can be changed without the need to adapt any of the clients.


And finally, what's the semantically correct way to check if a property or type exists within a vector array in C++? For instance, my entities contain a list of components. All my components inherit the base component class which really only contains an update (which I'm going to remove since it's not needed) and a handleMessage function for messaging. But since I want components to just be data/flagging I should really remove both of those and let the systems handle that. Anyways, how would a render-system check an entity to see if a spatial-component exists?

With sub-systems: It requests the SpatialServices for the Placement component that is associated with a given entity ID.

Thanks a ton for answering my questions! I'm off to more reading.

This topic is closed to new replies.

Advertisement