Friday 10 July 2015

DOD-ECS implementation details


The abbreviation DOD stands for Data-Oriented Design.
This is a software programming paradigm that emphasizes the importance of data and the layout of data, the notion being that the hardware (especially cpu cache) performance can be improved by adopting hardware-friendly data layouts, certainly an admirable concept.
The DOD Paradigm identifies Data as being primary, and Behavior (Logic) that transforms (manipulates) Data as being secondary, and separates them accordingly.

ECS stands for Entity Component System.
This is a software programming paradigm that offers 'object composition' as an alternative to traditional OOP techniques such as polymorphism and multiple inheritance - you might be familiar with the term 'mix-ins'. Game engines such as Unity are heavily based on ECS concepts.
In abstract terms, entities are comprised of 'Aspects' which may be represented by Components.
An example of an Aspect is 'Transform' - anything you want to draw will need one.
Unity went to the extreme of making Transform a mandatory component of all entities.

When using an ECS for game development, users typically construct and manipulate game entities of whatever kind, but under the hood, the typical ECS does not process or update entities, but rather it processes and updates entire Systems, and all the Components they hold (regardless of which entity they are associated with), thereby updating one 'Aspect' of all entities at a time, rather than updating all 'Aspects' of one entity at a time (implying that Systems are updated in some particular order).

In order for an ECS to be considered as "DOD-Compliant", we simply need to arrange our data to suit that kind of iterating, and respect 'cache line alignment', and having done so, we can easily multithread the Update logic for most kinds of System safely, without needing any mutex mechanisms.

The FireStorm Game Engine provides an Entity Component System which is based strongly on data-oriented design principles. This means it's implementation is quite different to many ECS-based engines such as Unity, although it's user interface offers similar functionality.

If we already have a structured GameObject or Entity class that we want to convert to ECS, we need to analyze the existing structure to decide what 'Aspects' best describe each entity in our game, and for each Aspect, if necessary, define a new Component type, and a System that can Update components of that type.
Any decent ECS will provide 99% of the Components you will ever need for writing games, but adding new kinds of Components should always be possible for special cases.


FireStorm treats Components primarily as Plain Old Data objects that have no associated code methods (that has effect outside that object), while Behavioral Logic for Components resides within the Systems, which directly manipulate Data they own and manage, and may also indirectly (via Messages) manipulate data held by other Systems.

There is no GameObject or Entity class, no container for a 'bag of components'.
Under FireStorm's ECS, entities are merely IDs that can be associated with Components.

FireStorm's Components hold no reference to their owner Entity - those mappings are held by each System. Components can be POD (plain old data), but typically are 'dumb data classes' (like vec3) with methods that only affect member data. Components don't talk - not to other Components, nor to Systems, they are just data containers that might also have getter/setter or similar accessors.

FireStorm's Systems are containers for Components of a particular Type (implementing a pooled allocation scheme) and Messages from other Systems, decoupling Systems from one another during Updates (importantly, this makes multithreading the System Updates a whole lot easier).
Systems contain all the Logic for updating the Typed Components they hold.
They have no direct connection to other Systems, but may post Messages to other Systems, which may be addressed to all Components of a specific Entity in a remote System - that is to say, Messages are not 'broadcast to all Systems and Components', but only to 'all Components of Type X (held by System Y) associated with Entity Z'.
Each System provides at least two methods, which may or may not be used by a given System: Update (the Components owned by this System), and ProcessMessages (sent from other Systems).

Storing your Entity-to-Component Mappings within each System has its benefits, the most important being that each System is intimately aware of which entities own one or more Components held by that System, so introspection can be removed from the entity completely - we query Systems, not entities, although our queries can be broadcast to all systems, and can refer to specific entities.

When a System needs to be Updated, it 'knows' which Components it owns, and which Entities they belong to - if it requires data from other Systems, then it expects to receive that data through Messages from that System or Systems - no external queries are required (to other Systems).






No comments:

Post a Comment