Entity Component Systems and Data Oriented Design

Started by
33 comments, last by Hodgman 5 years, 6 months ago
On 9/29/2018 at 7:32 PM, Strewya said:

The last quote i understand is a main loop that updates arrays of objects.

So how does it work? Do you updates arrays of Player, Monsters, Projectiles, CondumableItem etc, or do you update arrays of PhysicsData, InputData, CollisionData, RenderData, etc?

EC/ECS add this unnecessary restriction that objects are either an Entity or a Component. Entities have no logic and simply act as lifetime scopes for components, and a event router or service locator. Components have logic and data (or in ECS, the logic is moved to a System) but are banned from using composition themselves (i.e. They cannot own their own sub components), which is just silly. There's no need for a framework that supposedly encourages composition to put limits on composition. 

In OO, if you have a class with some logic (a component or system), then your main loop (or a sub loop) can interact with it. Yes, this could means your main loop has "for each movable in the scene, add velocity * dt to position". If you have a class with no logic and is just a lifetime scope for its members (what ECS would call an Entity), then your main loop doesn't need to interact with it. e.g. If the main loop is already updating Moveables, and my entity has-a Movable, then my entity doesn't need an Update method which updates its member Movable. 

As for the entity being a communication hub (event router or service locator), typically in OO you just plug objects together directly. e.g. If I make a new "entity" that has a position and sprite "component", and the sprite needs to read the position data, and the scene will update the "component" arrays in the appropriate order, you might have some code like:


MyWidget::MyWidget(Scene& scene) 
  : m_position(scene)
  , m_sprite(scene, m_position) 
{} 

Now the sprite can read the position (no need for bloated parent->GetComponent<Position>() implicit service locator nonsense) via the explicit connection that we've created. These "components" can register themselves to type-specific lists in the scene, so there's no need for polymorphism or virtual functions anywhere. The flow of control, the flow of data, and the order of mutations can all be easily reasoned about (which means easy to multi-thread). Also no need for globals/singletons as the scene connections are explicit, so you can easily have multiple scenes if you like. 

I'm a bit flippant with my "just write good code" shtick... But all of the above is just "normal" programming practice.  Writing a massive service locator framework, plus the code to use it, so you can skip a few lines of normal explicit initialization logic seems crazy to me... especially when explicit connections are (IMHO) superior to implicit connections!

If you throw out the bloated frameworks, EC style would basically mean: only ever use two layers of composition. Structures can't contain structures,unless the only thing it does is contain structures (and none of those structures contain structures). You can only ever use one member variable of a particular type. In a structure that contains structures, all member variables must be the name of the type, e.g. Foo foo. This makes finding the appropriate components straightforward. 

.... This is a ridiculous collection of programming advice... and the starting point of the talk (the simple EC framework) is not OO style code. It's built according to some made up "EC" paradigm, not according to the decades of OO paradigm advice. So the talk is actually ECS vs EC, not ECS vs OO.

ECS is better, but still contains a whole load of very silly restrictions that serve no positive use. It's worthwhile studying the paradigm that ECS is cloned from and using the general ideas without their bloated/restrictive dogmatic frameworks. 

If EC is a perversion of OO, then ECS is a perversion of relational. It's extremely worthwhile to actually study how OO and relational are actually meant to be used.

9 hours ago, Randy Gaul said:

@Hodgman So how's the blog post/code sample coming along? I've been eager to see it :)

I wrote a few pages on the weekend, but it seems half complete... maybe next weekend :)

Advertisement
16 hours ago, Hodgman said:

 ECS is better, but still contains a whole load of very silly restrictions that serve no positive use. It's worthwhile studying the paradigm that ECS is cloned from and using the general ideas without their bloated/restrictive dogmatic frameworks. 

This. Pretty much every "ECS" I've seen the code for has been a corruption of "data-oriented design" principles to the point of inefficiencies and even missing the point entirely. You can produce stuff that seems superficially like ECS using that paradigm, true, but following "pure ECS" is coding to a pattern, and not to principles. Or just trying to imitate Unity. Your architecture should follow from the requirements for your game ("make games not engines") and not the requirements for a generic engine that needs to be all things to all people. Data-oriented design means structuring your code around your data and the way your data needs to be laid out, not structuring both your code and data around an ideology. DOD is kind of anti-frameworks. 

Even the example we're discussing in this thread isn't a pure ECS! Notice that the "avoidance system" stores data per-entity. I thought all state was supposed to be stored in components in a big database? :D

On 9/29/2018 at 12:41 AM, noizex said:
  • doesn't need to be overly generic (I'll have limited set of "modules" building the low level game object that I may need - transform, appearance, physics, skeleton, animation controller etc) 
  • allow me to not have some of the "component" on a given object - if half of the objects in the world are static I don't want them to have skeleton or animation controller needlessly allocated 

As the game engine book mentions, the idea of an "archetype" (an set of uniform objects decomposed into a structure of arrays) serves this purpose well. You can kind of think of it as a "mini-ECS", if you like, that is specialized to one type of object. Then you'd just have several of these "archetypes" in your program. Any state that wasn't needed for every object in an archetype would be segregated off into "services" (what ECS calls "systems").

To Unity's credit, it appears that their new ECS frameworks might end up supporting archetypes. Of course, it's still very generic and framework-y.

On 9/29/2018 at 12:41 AM, noizex said:
  • good on performance side (would rather avoid paying costs of magnitude 10x or more...)
  • have a good interface to access given functionality (though as I mentioned previously this would be alleviated by a scripting layer where I can hide certain design decisions - I'm a strong believer that game logic should not be so low level to make people think of performance all the time, something they're pushing in Unity now, I'd like the low level layer to be able to cope with whatever high level game logic throws at it)

The first bullet here and the bolded part are somewhat contradictory, unless your game logic does not need to scale much (at all). :)

Performance is a concern across every part of the app that is executed often. If a piece of code is executed many times per frame, it doesn't matter whether it's "engine" code or "gameplay" code - it's still code that is being executed in some finite amount of time. The distinction between the two is kind of arbitrary from the computer's perspective. 

Unless you're doing something turn-based, or very simple, your gameplay code will be executed quite often. If you want fast gameplay code, you're going to want to analyze and optimize it as if it were engine code. That seems like it would be generally easier to do if your gameplay code is written in the same language you're using for your engine code.

On 9/29/2018 at 12:41 AM, noizex said:

(nice to have) would be ability to substitute certain functions, like being able to add different kind of beaviour in place, like Physics and WeirdPhysics - but this gets us into virtual land, and requires some common interface to be shared by classes implementing this behaviour (not so common as Component, but something like IPhysicsController etc.)

Suppose you structure your physics system as a "service" that has a number of game objects registered to it. The physics system's job is to go through each of the game objects registered and apply certain physical behaviours to them. This works pretty well - we only update the physics of objects that actually need it, objects themselves don't necessarily know about the physics system (so coupling is reduced), and depending on how our data is formatted, we may even get the nice cache performance that comes from smaller objects that are only in memory when they're actually being used.

Now suppose you want WeirdPhysics. All you need to do, provided the input and output formats of both physics is the same, is register the objects with a different physics service. That's it. Now they're using WeirdPhysics. The code linked in the tweet at the start of the thread does this with both the "MoveSystem" and the "AvoidanceSystem," if you want an example of that kind of thing. The author could easily create a ChaseSystem that did the opposite of the AvoidanceSystem and merely by registering different entities to it, get different behaviour.

From this point of view, you don't really need virtual functions to get runtime polymorphism. I wouldn't even call this not object-oriented. Some OO practitioners would actually argue that it's more OO than having the objects own their physics behaviour because now the objects have fewer responsibilities (single-responsibility principle), the physics behaviour is encapsulated in a service object, and the interface to physics behaviour is segregated from the interface of the game object (interface segregation principle).

Object-orientation does not mean that your code reflects your domain model!

Thanks for sharing! I have also run into similar problems recently.

@Hodgman article coming along? :)

10 hours ago, Randy Gaul said:

@Hodgman article coming along? :)

Here you go: 

 

 

This topic is closed to new replies.

Advertisement