Is The "entity" Of Ecs Really Necessary?

Started by
48 comments, last by Shannon Barber 7 years, 7 months ago
A lot of the "ECS vs inheritance" argument comes from a very specific series of blog posts which basically proposes ECS as the solution to all the evils of OOP. For some reason the opinions within seem to have taken off in the community without much questioning.

I'm on the fence with it - I reject the strong assertions of the "entity is an ID, everything comes from a database, no OOP whatsoever" crowd. But I do like components, when they're flexible enough and not too far from how I'm used to working.

The explicit vs. implicit coupling issue is interesting. For me, it's like Python's dynamic types vs C++'s static types - you gain some speed and flexibility and lose some safety, and the route that gives you most benefit at the start of the project may not be the same route that benefits you most at the end. Personally I'd find explicit component connection really awkward, but maybe there's a way to make it effective and drag-and-drop compatible (like in Unity), rather than without feeling like I'm wrestling with an Inversion of Control container.
Advertisement

Well, ECS is just an extended form of composition with data oriented and data driven design measures added in. They just counter the typical problems you get from deep inheritance trees, so I think it's natural to assume that stance of discussion and those comparisons are entirely fair. Of course, ECS is just one possible solution to the problem, which you probably don't need if you don't have all of those particular problems, but everyone should already be aware of that and we're just discussing specifics of ECS.

Regarding explicit vs implicit: The problem with explicit is that it's not data-driven at all. You can't treat different component combinations in a polymorphic manner when needed. If you have an entity 1 with components A,B,C and entity 2 with components B,C and a system that operatoes on components B and C together, you can't have a system that iterates through all entities just like that because they're different types, instead you would have to manually work on all of those different combinations when updating a system. Like, first looping through all ABC entities, then through all BC entities, if you later decide to add an ABCD entity, you have to introduce that as well, and if they interact, manage that somehow as well.

This is probably solvable with dynamic code generation and re-compilation while working in your editor or whatever, but even then you'd have to find a way to let the user define system logic and then have the code generation cobble those different entity types together...

So I think explicit only really works if the entities in your game are not "different but some subset of components is the same" like that. Or to put it differently: If you have no need for data-driveness of your core entity types. I wouldn't go that route with a generalist framework. It probably works pretty well when you're coding your engine in parallel with every game, and adapt it to that game's needs.

So overall, it's completely true this is basically a static vs dynamic typing discussion, with all the same trade-offs and reasons why you're sometimes forced to dynamic typing in the end.

Explicit can be data-driven. That's why I gave the example of an IoC container - that's data-driven composition as used in enterprise software. And it works fine with interfaces, inheritance, polymorphism, etc. (In fact, that's the whole reason it exists.) Personally I think there are too many hoops to jump through to make it worthwhile, but some people and companies swear by it.

Regarding explicit vs implicit: The problem with explicit is that it's not data-driven at all.
...snip...

Sure it can be - the system I was describing was meant to load Entity definitions from a data file! And the rest of ...snip... doesn't have to be true; you can still have systems that operate on pools of specific components, with no knowledge of the parent entities whatsoever. i.e. you can use the typical ECS idea of having pools of components being operated on by systems, regardless of whether you use implicit or explicit component links.

Explicit can actually be more data driven than implicit. With implicit, the connections are always hard-coded -- e.g. Mesh will find it's transform by asking the parent entity for a Transform component GetSibling<Transform>(). With explicit, your data files can create inter-component relationships in different ways without needing to change any code.

At a previous job, we used an ECS, which supported implicit links (via something like GetSibling<Transform>()), but also supported explicit component links and data-driven entity definitions. As well as declaring entities in text files, as in my previous post, there was also a GUI tool for editing them and linking them up into interesting gameplay objects.
Designers could build Entities out of components in a node-based GUI tool, dragging and dropping to create components and define the explicit links between them, similar to:
7lijUzC0w.png and then you could use any entity as a component in a bigger entity: 7lmDkQtfc.png
In this system, the components themselves were written using extremely clean C++ code, plus some macros to create the "binding" code for the entity system:


class Mesh
{
public:
  Mesh( Transform& transform );
  void Foo();
private:
  int m_foo;
};
BEGIN_COMPONENT( Mesh );
COMPONENT_CONSTRUCTOR( Transform& );
COMPONENT_METHOD( Foo );
COMPONENT_DATA( m_foo );
END_COMPONENT();

The macros would generate all the required code to serialize/deserialize them, create the component editor GUI nodes including all the draggable connection points, and allow the editor to enforce the construction requirements (e.g. above, a Mesh component must be explicitly linked against a Transform component). Declaring methods to the system allowed designers to write something akin to blueprint visual scripts.

The entities didn't exist as code at all, and were just text files in the data directory (compiled into binary files for shipping builds).

ECS is just an extended form of composition with data oriented and data driven design measures added in. They just counter the typical problems you get from deep inheritance trees, so I think it's natural to assume that stance of discussion and those comparisons are entirely fair.

The reason I say it's a straw man is simply because deep inheritance trees are usually just bad code, so these arguments compare their big entity framework against bad code in order to show that their framework is good, which is not an honest thing to do. Deep inheritance trees are usually not OOP code despite people calling it OOP. Lots of the blogs on ECS make the argument that "OOP is bad because deep inheritance, therefore let's use ECS for composition, as composition is better than inheritance, problem solved!"... However, they've skipped a really important step in the middle there, and have solved a false problem. The fact that composition is better than inheritance is a core rule of OOP, so it should be obeyed there too. You can (should) be writing composition based code without the need to set up a big composition framework in the form of an ECS library.

Just to get on the same page... what are we talking about when we say explicit connections? I'm thinking about


struct ABCEnt
{
     A *a;  //points to an A component in the big linear array of A components
     B *b;  //same
     C *c;  //same
}

Of course, you can do that at runtime with something like


struct Entity
{
    Component *components; //can be filled in a data-driven manner
    int numComponents;
}

Entity e;
A *a = GetComponent<A>(ent); //linear search in components

But that kind of stuff in my opinion is the opposite of explicit, on a code-implementation level. Are we talking about the same thing?

It's more about the connections between components than the connection between an entity and its components.

If you have a function like GetComponent<A>(ent), then you're trusting that the entity has such a component, because nothing has explicitly guaranteed that it would be there.

But if you have no function like that, and instead a component takes references to other components in the constructor, you've explicitly supplied those connections to it, which is more robust. It is potentially more complex to initialise such a system, but it's less likely to be afflicted by run-time errors caused by missing components.

@agleed Your first example is a hard-coded entity, but doesn't say antying about how the components link to each other.

Here's three examples of how components might communicate -- using the example that Mesh has a function "Foo", which relies on Transform's "Bar" function:
//Explicit:
class Mesh
{
public:
  Mesh( Transform& t ) : transform(t) {}
  void Foo() { t.Bar(); }
private:
  Transform& transform;
};
//Implicit, component has explicit link to parent entity
class Mesh
{
public:
  Mesh( Entity& e ) : parent(e) {}
  void Foo() { parent.GetComponent<Transform>.Bar(); }
private:
  Entity& parent;
};
//Implicit, shared ID's between global systems:
class Mesh
{
public:
  void Foo() { EntityId id = MeshSystem::GetId(this); TransformSystem::Get(id).Bar(); }
};
In all three examples, the components might be allocated inside large pools owned by systems, and the entities might be defined in data files instead of code.

[edit]
Or in an ECS where the logic is in the "systems" not the "components":
//Explicit:
struct Mesh
{
  Transform* transform;
};
class MeshSystem
{
  std::vector<Mesh> data;
  void Bar() { for(const Mesh& m : data) data.transform->Foo(); }
}
//Implicit, component has explicit link to parent entity
struct Mesh
{
  Entity* parent;
};
class MeshSystem
{
  std::vector<Mesh> data;
  void Bar() { for(const Mesh& m : data) data.parent->GetComponent<Transform>().Foo(); }
}
//Implicit, shared ID's between systems:
struct Mesh
{
};
class MeshSystem
{
  std::vector<Mesh> data;
  void Bar(TransformSystem& transforms) { for(uint id=0, end=data.size(); id!=end; ++id) transforms.data[id].Bar(); }
};

Note that in the explicit system, the entity only needs to exist in the data files. It's a template created by a designer, a kind of pre-fab of components that get spawned together and share a lifetime.
In the "Implicit, component has explicit link to parent entity" version, the entity is a "bag of components".
In the "Implicit, shared ID's between systems" version, the entity is "just an ID".

Or in an ECS where the logic is in the "systems" not the "components":
I'd argue that if you have logic on your components you have an "entity-component" architecture (Unity-like I guess) instead of an ECS.

I think nowadays, ECS's Wikipedia page is quite clear of what an ECS is, what each letter means, and what is the difference with entity-component architecture, it also provides nice links (like t-machine one) about it: https://en.wikipedia.org/wiki/Entity_component_system

And I'd also argue that it isn't "deep hierarchies" versus "composition", but actually object oriented programming versus aspect oriented programming.

First comparison focuses too much in the means ECS uses to achieve its goal, it focuses on it viewing it purely from an object oriented perspective where its either composition or hierarchies, and nothing else. It so happens you gravitate towards composition of objects because 95% of the time you're using an object oriented programming language to do these things and it happens to be the right tool for the job in that environment.

The higher level objective in itself isn't composition, is simulating aspects of entities. Thus you try to have a framework in which you don't deal with strict "types" of entities but with entities that can have aspects that can change the behavior of the entity, either individually or via a combination of aspects.

"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

I think nowadays, ECS's Wikipedia page is quite clear of what an ECS is, what each letter means, and what is the difference with entity-component architecture, it also provides nice links (like t-machine one) about it: https://en.wikipedia.org/wiki/Entity_component_system

Note the massive disclaimer at the top though: "This article is written like a personal reflection or opinion essay that states the Wikipedia editor's personal feelings about a topic, rather than the opinions of experts." :wink:

That particular strict form of ECS is basically just relational data modelling, rediscovered, without the rigor or flexibility.

For the most part, whether your logic is in a system or in the component is just syntactic sugar and makes no functional difference (assuming you still have systems of components that get batch-processed in both cases).

BTW The T-Machine blog is one of the many that give a dishonest comparison between (bad/incorrect-)OO and their flavor of ECS, while also failing to compare it with relational.

That particular strict form of ECS is basically just relational data modelling, rediscovered, without the rigor or flexibility.
Eh no. Because you have a one to many relationship model between entities and components it doesnt means its relational. Unless you focus on a very tiny aspect, in which case the comparison would be meaningless, an object with a list of things would be "relational modelling" with the same criteria. Specially since relational models are about data, not behavior. As I said, ECS tackles behavior.

For the most part, whether your logic is in a system or in the component is just syntactic sugar and makes no functional difference
Yeah, in the same way as the lack of functional difference you'd have by say, writing your entire game in a single function. Functionally, they're the same. You'll still see pretty pictures on the screen.

Having logic in the components themselves means that components will need to know about other kinds of components, or about all of the other components of the same type. And you end up with these kinds of questions in the forums "What do I put in my components?" "How can I make component X relate to component Y?" "Omg I cant make the component logic isolated from other components! ECS sucks!". What if the same component is used for two different things? You just shoehorn the two different functions in the component and call it a day? It opens a whole can of worms.

The "system" part deals with that issue, providing a platform where you can place these dependencies between components and between entities. Systems know about entities, know about other components, and know about other systems. It is the place where you solve your data passing and communication issues.

Most of the component handling issues I see being asked in this forum stem from forgetting what systems have to do, and making components and entities do more than they're supposed to. So you end up having components having to know about other components, systems managing components and having to share them among other systems, which ends up in fighting hard with which system owns what components, and other kind of issues that shouldn't be really there if you follow ECS strictly. Then you end up with people implementing event systems for all the apparent shortcomings of ECS, and just pumping the data through there.

BTW The T-Machine blog is one of the many that give a dishonest comparison between (bad/incorrect-)OO and their flavor of ECS
Thats irrelevant. I did not argue with that point. I just said T-Machine blog is a nice source to learn what an ECS is, not what is good for. If it pairs it against bad OO or whatever is irrelevant for that purpose.

"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

This topic is closed to new replies.

Advertisement