ECS design

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

I really do believe that even the most optimized engines out there will hit a bottleneck with the sheer number of entities and components.

any code will bog if pushed too hard on too low end hardware.

a data oriented ECS designed to maximize cache friendliness ought to theoretically be perhaps the architecture (if not one of the architectures) that would bog down last.

data oriented ECS cache friendly optimization is what you do when you've already squeezed every clock cycle out of render and you're still not quite fast enough. Its an optimization method of last resort. oinly heard of one real world case ever needing it. that case is written up on gamasutra. might have been Spyro 2, or maybe Sonic 2. Spyro 2 i think maybe. use of containers with built in runtime checks would not typically be used in such a system due to the additional overhead. you going ECS out of desperation to become more cache friendly, the last thing you want to do is burn clock cycles on runtime checks.

there are actually may types of ECS's, with two primary possible benefits:

1. the ability to define new entity types from pre-defined component types without recompiling (IE data driven). most all ECS's have this. never heard of one that wasn't data driven.

2. cache friendly design (data oriented - as opposed to data driven). note that an ECS can be both. but data driven ECS != data oriented ECS. data oriented ECS is a specialized design above and beyond just data driven ECS.

its the data oriented ECS that would probably be the most performant of all architectures. but given the fact that render is almost always the bottleneck compared to update, data oriented ECS is typically an unnecessary optimization.

Norm Barrows

Rockland Software Productions

"Building PC games since 1989"

rocklandsoftware.net

PLAY CAVEMAN NOW!

http://rocklandsoftware.net/beta.php

Advertisement

where is it written that components must be data?

what about an AI component?

Nowhere said that components *must* be data, actually. It's often given as advice (much like immutability) to ease up things such as parallelism, which can become quite a challenge on its own. I've looked on both approaches, and both seems valid with their respective pros and cons, and affect heavily the architecture and the way that things are used or executed.

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?

I write that components should be data. :wink:

If Components are data (generally), and Systems execute code, it's far easier to parallelize, as well as generally makes the code cleaner because it's clear what part of the architecture is responsible for what.

The key is to realize that logic can be parameterized and transformed by data, and that code itself can be data (for example, callbacks and scripts (which are just runtime callbacks)).

But by thinking of Systems as Logic and Components as Data (i.e. input and parameters to the logic), I think it helps you keep your architecture organized and lets you know what is responsible for what, helping you recognize what parts of your code are actually sharable (and optimizable). It's obvious that code transforms data, but don't forget the subtler reality that data can also transform code.

Obviously this depends on what style of ECS you are using, but I think it's a good guideline; "Hold to it until you absolutely need otherwise (and until you triple-check your need)" kind of thing.

What is an Entity? A list of components, but you do not deduce systems on components, you deduce components on systems!

?A entity should just not be more than a bunch of systems working togheter on some data


// spawn wall for a 2D tile-based RPG
void SpawnWall(int x, int y){
    Entity wall;

    wall.RegisterInSystem( renderSystem).Default( "wall.png");
    wall.RegisterInSystem( positionSystem).Default( x, y);
    wall.RegisterInSystem( tileSystem).Default( false); //not passable
}

This is similiar to what I'm doing. Basically I'm looping this way:

?For each Entity type (a different spawn function)

For each System in Entity Type

For each Entity

UpdateSystem on that entity

we do not have fastest possible iteration because each entity have more components so we skip several bytes each time we want to access the next component.

we do not have fastest possible code cache because it is possible each system is called more than once in different entity types.

But we avoid most bottlenecks (no lookup cost of hashmaps, no huge BUS usage for iterating each time again over Whole RAM memory) while keeping flexibility (and no need to filter entities into groups for fast search which is just an API complication), as a side note, if components for a particular entity type are independent, we can layout the entities like struct of arrays instead of array of structs to improve cache locality (in theory we could partially arrange the layout internally according to some euristic, but that add much more complexity for almost no benefit), but that would be an auotmatic check of easy implementation and without any additional effort from the user.

?Oh and to detect entity type, I just have a order over systems. It is pretty fast finding if another entity with same systems (!= entiy with same components) exist and we will allocate it there. It is nothing more than checking if a string is in a dictionary, if yes increase the count for that word. Using a dictionary lookup when spawning something, and just once and only for entities, is zillion times faster than doing dictionary lookup for all components every frame.

?Note that I do not used any particular complex cache or Group system. Just dead simple and yet effective code.

?Assign each system a letter of the alphabet A,B,C,D,E,F

?then a entity will have some systems

?AB

DE

ABD

Entity update order will be the same of the string sorted alphabetically so:

?AB
ABD

DE

But we are really doing no sorting, the entity is added in the right bucket just right after spawning.

This topic is closed to new replies.

Advertisement