5
\$\begingroup\$

Is Entity Component System (ECS) considered a part of OOP or is it a completely different concept?

\$\endgroup\$
1
  • 1
    \$\begingroup\$ Considered by who? \$\endgroup\$ Dec 10, 2012 at 11:49

7 Answers 7

10
\$\begingroup\$

Short answer: Entity Component System (ECS) is not a part of OOP.

The discussion that most influenced my understanding of Entity Component Systems is on T-Machine and an example framework inspired by this discussion useful for reference is Artemis.

One of the fundamental concepts of OOP is encapsulation - application design breaks the domain into objects that have private state that can only be mutated via methods exposed by said object. Extending the functionality of an object can be done via inheritance or through composing existing objects into new objects that expose new accessor/mutator methods. In all cases the driving force is to create a system that is loosely coupled and thus easy to maintain and extend. A trivial game related example might be creating the notion of a GameEntity with the ability to draw itself via a public render method. This object can be extended by subclassing and the logic for rendering and all of its state is always encapsulated within.

In ECS, on the other hand, application design is based on the idea that applications are fundamentally transforming/processing structured data. In such a system a GameEntity is a bundle of data against which transformations are performed - there is no encapsulation. In the Artemis framework you will notice that Entity is finalized specifically so it cannot be subclassed. Of course, not all GameEntities will have the same data - the player GameEntity may have inventory data while MOB game entities do not. If this were an RDBMS we'd have one massive sparse table that would quickly get unwieldy as the game grew. Enter components. A component is a piece of structured data that can be linked to an Entity just as one row in an RDBMS table can link to one or more rows in another table. This provides a way of structuring the methods used for transforming the data. The RenderSystem (or RenderMethod), for instance, would want to process any entity with a SpriteComponent and the InputSystem (or InputMethod) would want to process any entity with a KeyboardInputComponent.

While there is a hardline position that true ECS means eschewing OOP entirely I think it is possible to incorporate OOP into ECS and still remain to true to ECS design goals. For instance, you'll notice in the Artemis framework that the systems responsible for processing entity data are objects themselves using encapsulation and inheritance. Similar functionality can be obtained without relying on inheritance or classes (I used function pointers and structs in my C version) but I don't think Artemis is any less an ECS framework, it may simply be more ripe for OO abuse.

Since an ECS is based on the idea that data should drive domain design as opposed to object hierarchies it is data oriented and best understood as a concept separate from, if not mutually exclusive with, OOP.

\$\endgroup\$
1
  • 1
    \$\begingroup\$ Great answer! Also want to add that ECS is really just the Component Pattern. When applying the Component Pattern in OOP, components are objects. In functional programming, components are functions (take a look at React.js). And in Data Oriented Design, components are just data! \$\endgroup\$
    – Vakey
    Nov 4, 2019 at 14:15
4
\$\begingroup\$

Component systems are an architecutre. Object-oriented programming is the combination of object-oriented features in a programming language and the design principles behind the inclusion of these features as well as how to effectively use them.

Some of the key aspects of object oriented programming are:

  • Language-level mechanism for tightly packaging data with the procedures that manipulate it. Methods/member functions are bound to an instance of an object

  • Additionally, the raw data is often hidden (encapsulated) in order to protect the integrity of the data and ensure the consistent state of an object. Bound methods have access to the encapsulated data, as the general idea is that the author of the class knows what the internal state should be at any given moment and can be reasonably trusted to maintain that state when writing methods that manipulate hidden data (keep in mind that the "hidden" part of that means different things in different languages).

  • Inheritance: A derived class can inherit behavior and data from a base class

  • Polymorphism: An instance of a derived class can be used as though it were an instance of its base class.

  • Implementation contracts: Polymorphism, but via the mechanism of interfaces or protocols

Some of the key aspects of component systems are:

  • The "primary objects" are lightweight containers for logic and data

  • Logic and data are often encouraged to be separated. At the very least, a behavior component will track its own data required to execute said behavior. Other systems treat components as pure data and behaviors as systems (not bound to a primary object) that operate on that data.

  • Behavior is added to a primary object at runtime, often in such a way that the construction of objects can be described by data/configuration rather than code

  • Because component systems can be data-driven, it's easier to build tools for non-programmers to create behavior and content

Look at these bullet points and think about them long enough and you'll realize they can't really be classified in the same way. Object-oriented programming can be helpful in implementing a component system, but is not strictly required. And except for prototype-based languages like Lua and Javascript (where it's rarely useful to think on this kind of scale, and are often thinking of code as data), component systems are generally not baked into a language and are more of a architecture pattern.

You could just as well implement a component system in idiomatic C and have it serve you as well as something similar in C++ or C# (Type-safety problems and less syntax sugar not withstanding). People interfacing with your C component system wouldn't care that it's C because they're typically just manipulating data to produce complex behavior.

As for object oriented design and where it fits into this spectrum, I think that it doesn't. In my arrogant, young opinion, SOLID is essentially a list of platitudes that, while useful in a broad, rule-of-thumb kind of way, ultimately don't provide us with that much useful information beyond achieving a good sense of general organization and structure. And that's certainly not OOP-specific. Much more useful are design patterns and principles in specific languages. I just find it weird that OOP gets its own qualifier to stick in front of the word "design." When's the last time you saw a Teach Yourself Imperative Design in 24 Hours or Functional Programming Architecture for Dummies book?

Having been raised on OOP, many of us generally find it easier to implement component systems in an object-oriented language because we're familiar with the design patterns and best practices of this paradigm. Haskell nerds, Lisp hackers and C gurus will have a completely different opinion on how one would approach to implementing (or possibly refusing to implement, because their languages offer superior idioms that accomplish similar goals in ways more appealing to their programming palette) this pattern.

To understand the benefit and principles of component systems in game programming, try this: Imagine constantly for every second that you're building out your game's systems, that as soon as it's built you won't ever be able to actually build content for it. Another person who has no coding skill must work with it and you can only affect the outcome of their work orthogonally by updating the tools. Components are a pattern for maximimizing flexibility of creating a variety of different behaviors at runtime and enabling this behavior to be determined by non-code data. OOP can help, or it can not.

\$\endgroup\$
3
\$\begingroup\$

A component system is just a way to organize the data belonging to your objects, and how to create processes that act on this data. It can be done with and without object oriented programming (which, really, is just a very broad term for a set of loosely related programming techniques, with a lot of dogma attached).

\$\endgroup\$
3
\$\begingroup\$

Component based systems are not part of OOP.

Originally, they don't rely on OOP functions. Component based architectures exist since long. They have been used in financial and banking apps for decades. They enable strong modularity, parallelization (even across servers) thanks to the segmented data, very simple storage mapping (database <-> component) and composition without the overhead of inheritance (and OOP).

Since video games grew in complexity it became obvious that inheritance is not an efficient option when it comes to the structure of game entities. Segmenting games into systems which handle the specifics of each function simplifies the reuse and maintenance of code (across entities and across games). It also simplifies the execution of parallel tasks (i.e. run multiple systems or segments of a system in parallel).

But all of this can be achieved without OOP. Here is a very primitive example of of how a component based system would look like in C:

#include <stdio.h>
#include <unistd.h>

struct TransformComponent {
    int x;
    int y;
};

struct ScriptComponent {
    void (*scriptPtr)(int,int);
    void (*handleMessagePtr)(int,int,void*);
};

struct RenderComponent {
    char* model;
};


#define MAX_ENTITIES            4
#define GRID_X                  16
#define GRID_Y                  10

#define noStatus                0x00
#define goStatus                0x01


int componentsStatus[MAX_ENTITIES];
struct TransformComponent positionComponents[MAX_ENTITIES];
struct TransformComponent movementComponents[MAX_ENTITIES];
struct ScriptComponent scriptComponents[MAX_ENTITIES];
struct RenderComponent renderComponents[MAX_ENTITIES];


void RunScriptSystem(int time)
{
    for (int i=0; i<MAX_ENTITIES; ++i) {
        if (goStatus == componentsStatus[i] && 0 != scriptComponents[i].scriptPtr) {
            (*scriptComponents[i].scriptPtr)(i, time);
        }
    }
}

void RunTransformSystem(int time)
{
    for (int i=0; i<MAX_ENTITIES; ++i) {
        switch (componentsStatus[i]) {
            case goStatus:
                positionComponents[i].x += movementComponents[i].x;
                positionComponents[i].y += movementComponents[i].y;
                while (GRID_X < positionComponents[i].x) {positionComponents[i].x %= GRID_X; }
                while (GRID_Y < positionComponents[i].y) {positionComponents[i].y %= GRID_Y; }
                while (positionComponents[i].x<0) {positionComponents[i].x += GRID_X; }
                while (positionComponents[i].y<0) {positionComponents[i].y += GRID_Y; }

                movementComponents[i].x = movementComponents[i].y= 0;

                break;

            default:
                break;
        }
    }
}

void RunRenderSystem(int time)
{
    printf("\n=======================================\n");
    for (int i=0; i<MAX_ENTITIES; ++i) {
        switch (componentsStatus[i]) {
            case goStatus:
                printf("%s: (%i,%i)\n"
                       ,renderComponents[i].model
                       , positionComponents[i].x
                       , positionComponents[i].y);
                break;

            default:
                break;
        }
    }
    printf("=======================================\n");
}

void initCmps(int time);
void RunSystems(int time) {
    printf("Run simulation %i.\n", time);
    initCmps(time);
    RunScriptSystem(time);
    RunTransformSystem(time);
    RunRenderSystem(time);
}

int main(int argc, const char * argv[])
{
    int time = 0;
    for (int i=0; i<MAX_ENTITIES; ++i) {
        componentsStatus[i] = noStatus;
    }
    printf("Starting simulation!\n");
    while (1) {
        RunSystems(time++);
        sleep(1);
    }
    return 0;
}


//Game stuff:

char chickenModel[] = "kooot";
char coyoteModel1[] = "yip";
char coyoteModel2[] = "howwwwwwwwl";

void moveScript(int id, int time)
{
    movementComponents[id].x += rand() % 3 - 1;
    movementComponents [id].y += rand() % 3 - 1;
}

void coyoteScript(int id, int time)
{
    if(rand()%5 < 3)
    {
        renderComponents[id].model = coyoteModel1;
        moveScript(id, time);
    }
    else{
        renderComponents[id].model = coyoteModel2;
    }
}

void initCmps(int time) {

    for (int i=0; i<MAX_ENTITIES; ++i) {
        if (componentsStatus[i] == noStatus) {
            if (i==0) {
                scriptComponents[i].scriptPtr = &coyoteScript;
                renderComponents[i].model = coyoteModel1;
            }
            else {
                scriptComponents[i].scriptPtr = &moveScript;
                renderComponents[i].model = chickenModel;
            }
            positionComponents[i].x = rand() % GRID_X;
            positionComponents[i].y = rand() % GRID_Y;
            componentsStatus[i] = goStatus;
        }
    }
}

A real implementation would allocate IDs to each component and would not rely on the array index as the entity ID.

OOP languages simplify the implementation of features in entity systems, most notably the management of the components themselves, the handling of messages and other tasks shared by components... But ECS and OOP are not directly related.

\$\endgroup\$
1
\$\begingroup\$

If you are referring to a Components-Based Entity System (CBES), then the answer is "it is reliant on OOP" rather than "it is a kind of OOP", since components refer to an OOP class that holds both state and functionality for that entity aspect represented by the component (whether it be physics, or AI, or a weapon slot, or a view representation).

In contrast, I couldn't quite see a CBES existing in something like functional programming, where there cannot be a single "object" representing both state and functionality, since these are not bundled together in FP as they are in OOP.

\$\endgroup\$
5
  • 1
    \$\begingroup\$ There is no need for OOP capacity for component based architectures. Their existence predates their use in video games and they don't necessarily rely on object oriented languages. "it is reliant on OOP" is simply not true. It is done using ANSIS C for example. \$\endgroup\$
    – Coyote
    Dec 10, 2012 at 9:37
  • 1
    \$\begingroup\$ +1 from what I know of modern CBES, they are reliant on OOP. \$\endgroup\$
    – House
    Dec 10, 2012 at 18:48
  • \$\begingroup\$ @Byte56 I have to admit. In the field of video games engines I haven't seen any non object oriented component based implementations. So "components refer to an OOP class" and "it is reliant on OOP" might be true in this context. \$\endgroup\$
    – Coyote
    Dec 10, 2012 at 22:58
  • \$\begingroup\$ @Coyote If you could read minds, as you claim, you'd know I didn't downvote your friend. Please remove your comment, and in future try not to make false allegations. Bear in mind there are 20000 users on this site. \$\endgroup\$
    – Engineer
    Dec 11, 2012 at 11:43
  • \$\begingroup\$ Sorry looking at your profile I thought you down voted him... I was wrong. Can you edit your answer so I could remove my down-vote as I am apparently wrong. \$\endgroup\$
    – Coyote
    Dec 11, 2012 at 15:49
1
\$\begingroup\$

I think that Entity-Component Systems lend themselves much better to data-oriented design rather than object-oriented design, as long as you're willing to use components only for storing data, in which case I would choose the term "properties" over "components". You can think of all the data that the entities hold as being in a big table. Each row of the table stores all the data for a single property for every entity. Each column of the table represents an entity as a whole.

// Property table
Vector position     [MAX_ENTITIES];
Vector velocity     [MAX_ENTITIES];
Vector acceleration [MAX_ENTITIES];
Sprite sprite       [MAX_ENTITIES];
Vector spriteOffset [MAX_ENTITIES];

// The entity with ID 2 is the aggregation of position[2], velocity[2], etc.

Systems are responsible for doing operations on that data, where the same operation is performed on every entity. For example, one operation the movement system might perform would be calculating the effects of an object's acceleration and velocity.

// Accelerates and moves entities
void UpdateEntityMovement(float deltaTime)
{
    for(int i = 0; i < MAX_ENTITIES; i++)
    {
        acceleration[i].y -= 9.8 * deltaTime; // Gravity
        velocity[i] += acceleration[i] * deltaTime; // v = at
        position[i] += velocity[i] * deltaTime; // d = vt
    }
}

// Draws all sprite-based entities
void DrawEntities()
{
    for(int i = 0; i < MAX_ENTITIES; i++)
    {
        DrawSprite(sprite[i], position[i] - spriteOffset[i]);
    }
}

The advantages of this design are not limited to the flexibility you get. Data-oriented programming is much friendlier in terms of cache performance, and it's easy to multithread any independent operation like in UpdateEntityMovement(), so DOD is very scalable in terms of current-gen hardware developments.


More information on DOD:

http://gamesfromwithin.com/data-oriented-design

http://knight666.com/blog/tutorial-a-practical-example-of-data-oriented-design/

\$\endgroup\$
0
\$\begingroup\$

It's definitely OOP - just advanced OOP. The classic (Gamma) Design Pattern book even documents this kind of solution, though not as an explicit pattern but under others. The section 'inheritance versus composition' explains the concept well. Somewhere the disadvantage of the 'component systems' is mentioned: systems relying heavily on object composition are difficult to learn and harder to debug.

Each component encapsulates a different aspect about your game entities. The data and logic pertaining to this aspect is moved out from the entity. This is normal, not a violence of the encapsulation. The Visitor pattern also does something similar - you move out operations/methods of a class into different classes, in order to have a maintenable system.

\$\endgroup\$
3
  • \$\begingroup\$ Component based entity systems are not part of OOP. \$\endgroup\$
    – Coyote
    Dec 10, 2012 at 14:47
  • \$\begingroup\$ You gave an implementation without any encapsulation, without Entities. I'm not sure you can call it an ECS (maybe an external one)? A typical ECS implementation is a classic OO topic, involving encapsulation and polymorphism. But anything OO can be programmed in C, anything involving inheritance can be done via delegation so I guess clear theoretical boundaries can't be drawn. \$\endgroup\$
    – Karl
    Dec 10, 2012 at 15:27
  • \$\begingroup\$ Entities don't need to be represented with an object or a structure, an id is enough. My example C implementation is simplistic and uses the index of fixed size arrays in order to avoid overloading the code with lookups etc. In the implementations I have seen in video games the engines keep pointers to components in entity objects. This surely speeds-up the lookup process and random access to components, but it's not the essential part of the topic. In systems where components register for messages from sources upon creation, encapsulation is not so important. \$\endgroup\$
    – Coyote
    Dec 10, 2012 at 16:01

Not the answer you're looking for? Browse other questions tagged .