ECS design

Started by
22 comments, last by DemonDar 7 years, 4 months ago

Yes, but this is not entirely an entity-system approach, because when one component has a reference to another, they are tightly coupled, and it is a step backwards, because you are using ECS to decouple stuff in order to have more flexible code, why coupling it again?

Yes I could see how this would become a problem.

From what I can tell, it might be best to just register the entity with the systems that would care for it, then just loop though those.

I have also seen examples that use nodes that store the needed components. Is this a good implementation idea or are their hidden problems. I would guess that you would need register a node with every system that would take a bit of time (but then again you would have to do this for entities aswell)

Advertisement

Just use one of the already implemented ones out there.

https://github.com/alecthomas/entityx

https://github.com/JuDelCo/Entitas-Cpp

Learn about it as you use it.

"I AM ZE EMPRAH OPENGL 3.3 THE CORE, I DEMAND FROM THEE ZE SHADERZ AND MATRIXEZ"

My journals: dustArtemis ECS framework and Making a Terrain Generator

Just use one of the already implemented ones out there.

Arr good to find some examples cheers for the links :).

I have one more question and it comes in relation to rendering. With using Opengl, creating a good render component and system seems pretty complex (due to getting the camera component, lights and data that might need to be linked to the shader for different objects). Has anyone got a good suggestion for solving this problem. I just dont see an easy way of creating this system without alot of small hacks (which is wat my current implementation has)

Again cheers for the help

I have one more question and it comes in relation to rendering. With using Opengl, creating a good render component and system seems pretty complex (due to getting the camera component, lights and data that might need to be linked to the shader for different objects). Has anyone got a good suggestion for solving this problem. I just dont see an easy way of creating this system without alot of small hacks (which is wat my current implementation has)

This is just API stuff. If you don't know how to wrap it, the best tutorial I know is www.learnopengl.com. I don't say the code is perfectly optimized, but it's great for starters, you can optimize it on your own. And maybe you can get some ideas from there. I'm sorry that I can't help you in a more specific way, but I've never encountered your problem.

Well.. one of the easiest ways to make sure that you don't have too many levels of communication between components is to make them higher level. I see a lot of times people separating components into extremely small functionalities.. So there may be a TransformComponent, then a PhysicsComponent, then a RenderComponent, VelocityComponent, HealthComponent, DamageComponent, and it keeps going on and on.

This is just API stuff.

Sorry, I didn't mean that I not know how to get everything drawn to screen (i have classes that deal with thinks like binding VAO's and using the shader program so all the linking is easy enough to do)

Ill try and give an example. So for my current rendering system I would have to have the following

  • A entity list for things that i want rendered
  • a entity list of things that contain a light component
  • the camera

This is fine for the most part and very much do able. For a standard model i would only have to like the model view and projection matrix, i would also have to link the texture if the model has one (simple bool check) and link the lights to the model (again easy enough). The trouble im having comes when a shader need more then this. It would seems that say i needed to pass another bool to a shader for some reason, i would have to define this with another component then bring it into the rendering system and check it exists then pass that components data to the shader. This would quickly get very complicated and messy for the system. The only other solution i can think of is the have a render class for each different type of object and have all the linked passed to it but this would still involve passing data from components.

Hope that gives a better explanation of my problem

My second version planned to use an unordered map for every component type where the key will be the id of the component. This was looking good for a while but i ran into a problem with performance again. For systems I can loop though a component type but if i want a different component type (say for rendering i need model and texture and transpose component). I would need to do a find check to make sure that a map has a component of that ID. sadly this causes a massive loss in performance and leaves me where i was before.

In one ECS design that I really like from research, but haven't had a chance to personally explore in code, Components are *only* data, Systems operate on the data, and Entities are just an ID.

Each System make use of containers of components, and map the EntityID to components when needed.

When starting off, here's a few things that people widely do that I personally think are bad design:

- Don't make Components inherit from a base component (even if it's CRTP).

- Ditto for Systems. Don't make Systems inherit from a base system (even CRTP).

My rational for this is stated in another thread - others disagree. I'd start off NOT making them inherit at all, because I think it messes up the entire way your interfaces will be designed; but if you later find you *need* it, then later you can rewrite the interfaces easily enough (easier than going the other way).

Additionally:

- Don't limit Systems to one component type. A System might want more than one type of component. Don't assume a one-to-one mapping of system and component types.

- Many Systems might need access to the same components. Don't assume only one System can ever use the same container of components.

- Some component containers are public (used by multiple systems), others are private (a single System uses them internally for bookkeeping).

- Don't assume every type of component needs to be stored in the same way. Some types of component are better in arrays, others in maps.

- If a component contains a single variable, something's likely wrong and you might be breaking your components up into too fine a granularity. However, if your game genuinely needs it (again, I think it's a code-smell), then you may want to make a Property system for your ECS.

For Components in arrays, this is done for speed in many cases - in those cases, you should double-indirect to map your EntityID to the component:


//Conceptually:
std::unordered_map<EntityID, IndexOfComponentInArray> componentIndexMap;
std::vector<Component> components;

myEntityID = 357;

auto iterator = componentIndexMap.find(myEntityID);

if(iterator == componentIndexMap.end())
{
    //...entity doesn't have this kind of component!
    //This indicates a problem in your run-time *data* (your scripts, or whatever), and should be hidden gracefully (and reported to the log).
}
else
{
    IndexOfComponentInArray index = *iterator;

    if(index >= components.size())
    {
         //Error! Out of bounds index. This indicates a flaw in your *code*.
         //If performance is genuinely required and proven (for example, if this is running on a server), this check can be disabled in release mode (but by default, shouldn't be).
    }
    
    Component &myComponent = components[index];

}

(I'd wrap this type of container in it's own class)

This only needs to be done when looking up a component using an EntityID, which is much rarer than just iterating over entities. (and again, this is only needed for some types of components where performance is measured to be necessary). Two benefits of this is that you save space for the many entities that *don't* use the components (you don't have empty unused elements in the container), and also that the System can re-arrange the components for optimization without invalidating the EntityIDs trying to access the components.

I'd also suggest one other thing: I personally believe making general-purpose engines are a waste of time :P, unless you are selling the engine itself commercially. I prefer engines that are designed for a specific genre of games and with a specific game in mind, that can be easily modified to accommodate additional games of the same genre, or heavily modified to accommodate games of different genres when actually needed. Over-genericness can waste more time than it saves. So while making your code, I strongly suggest you do so focusing on a specific game you are making, so you don't over-engineer yourself into not releasing any games and only having an engine that works for no games.

might try this:

an entity is a vector of ints. each int is the index of a component in a vector of components, or -1 for "doesn't have one"

components are kept in separate vectors for each type of component. their indices are used in the "entities list" to indicate which entity "owns" them. use "add at end" and "swap and dec" to keep all vectors packed and cache friendly. all iterations are performed on component lists, not the entity list.

animation controllers are just another component. they have one or more skinned meshes, animations, textures, and materials associated with them (all stored in shared resource pools and referenced by IDs). with later versions of DirectX etc, this also means they may have one or more shaders associated with them (just another resource).

so the entity in the "active entities list" points to the animation controller in the "active animation controller component list", which points to the resources.in the various shared resource pools (meshes, textures, animations, materials, shaders, etc).

when you add an entity, you add all its components to the various component lists, and set all its component IDs. this includes things like passing the animation, mesh etc to the animation controller when you create it.

when you remove an entity, you remove all its components from the various component lists, including destroying or releasing or reducing the reference count on animation controllers, etc.

and the list of animation controllers IS the list of renderable entities.

you may also find it helpful to use data structures that hold just the info for a task, such as render. many tasks require more than one component. if i were using an ECS , it would take the ani controller and other component info (IE mesh ID, texture ID, animation ID, frame #, material ID, location, orientation, scale, cull. clamp. alpha test, alpha blend, texture scale, etc) and place it into a struct for passing to the render queue.

EDIT:

Note that swap and dec and add at end is a two edged sword. it keeps your lists nice and contiguous, but you have to fixup IDs when you move stuff. since you only move at most one of each type of component when you remove an entity, this isn't that bad.

the other option is an "is_active" boolean for each component. you add at first inactive, and set active = 0 to delete. but then you get inactive components in the list (and thus the cache) when you iterate. and you have to check for "is_active" and only process if its true.

Norm Barrows

Rockland Software Productions

"Building PC games since 1989"

rocklandsoftware.net

PLAY CAVEMAN NOW!

http://rocklandsoftware.net/beta.php

So, I didn't read all the replies properly because I am a Vasari adept, and time indeed is short. I am mostly sure that someone somewhere said that if you hit a performance bottleneck twice, it's likely that the game logic is the cause of slowness, not iterating over the components. Anyway, here are the conclusions that I've arrived while programming my components and systems, but before...

Over-engineering is hell! If you're programming alone on your free time, over-engineering can take as much time as fixing stuff for jumping on too many template too early. Stay with the simplest approach that solve your problem, refactor when needed. And be careful with premature optimization.

Okay, now to the stuff. My (FAKE) ECS recently went through the third refactor. One of the causes of the last refactoring is that I wanted to use the simplest component storing system possible (a vector of smart pointer to components), while keeping a door open to the lightning fast, vector of classes contiguous on memory, ordered by entity id, thus increasing the chances of cache hit while iterating upwards. To achieve this, I've decided on some compromises:

- Components are stored on special containers, whose single responsibility is to allocate and deallocate components from whichever data structure they use.

- Entities stores a type of unique handle to components, that will bridge the communication with the container. An unsafe, raw pointer can be obtained for performance when needed, but overall, the indirection impact has been immeasurable. If the entity releases this unique handle, the component is destroyed.

So, the entities are the unique owners of the components (even though they're stored on containers), and they can create weak references for other components. The point of the weak references is that I decided that I want my components to have some logic, instead of being just data. All sort of problems came with this decision, but I'm sure all sort of other types of problems would come if I followed a pure ECS approach. Pick your favorite.

Back to the containers, the components can be iterated by a ranged for loop, directly accessing the component through an iterator instead of a handle. It is up to the programmer to guarantee that the container won't be "changed" while anything is iterating over it. The systems will simply use this ranged for feature to iterate over the elements.

Now, about the containers with multiple types, I'm thinking in adding a new type of set container that references entities that have a set of specific components. The entity, when receiving a new component, would fire an event that'd be consumed by the sets containers, which would analyse the entity's components and decide if the entity would be referenced or not. When removing a component, the entity would fire an event as well, so that the set containers could unregister it. But I didn't need this yet, since even my rendering system is way too simple for now (just the bare minimum for a deferred rendering).

I don't have any statistical data or citations to back up my belief, but I really do believe that even the most optimized engines out there will hit a bottleneck with the sheer number of entities and components. Usually, they'd go for alternatives such as batching stuff, building some tree structure to organize data and reduce the amount of objects instances, do some algorithmic changes to the way things behave, or even making an entire group of special cases - such as rendering, which eventually you'll worry about overdraw, draw calls, order of rendering, order of applying effects and whole new worlds of challenges and tears. I'd advise to double check if your bottleneck can't be solved by means of architecture, before dealing to heavily optimizing the components system.

When the optimization is no longer algorithmic or structural, code tends to get really, really really harder to maintain, a a lot more bug-prone over time.

Edit: changed a few things for easier reading, and added the last paragraphs.

The point of the weak references is that I decided that I want my components to have some logic, instead of being just data.

where is it written that components must be data?

what about an AI component?

Norm Barrows

Rockland Software Productions

"Building PC games since 1989"

rocklandsoftware.net

PLAY CAVEMAN NOW!

http://rocklandsoftware.net/beta.php

This topic is closed to new replies.

Advertisement