In ECS, why is there no problem with components as a kind of global variables?

In traditional OOP, global variables are often regarded as a problem. One of the key problems is that the scope of global variables is too large, so that after a bug occurs, your scope of error detection is expanded from a function to the entire Code base, because you don’t know which global variable assignment is wrong, so you have to check one by one.

The components of ECS also have a global nature. If a component is only used by one system, it is of course no problem, but if a component is used by many systems, is this the same as the global variable of OOP?

Why isn’t this an issue?

It is actually an issue if you have poorly-designed dataflow. However, with good dataflow, and with systems being separate pieces of code, it is easy to insert diagnostic tap points to bisect your frame and narrow down issues.

I’m notorious for having lots of little typo bugs all across my code. As soon as I’m aware of the issue, it does not take me very long to isolate the problem. Much faster than OOP.

In OOP, the order of the execution is often blurry because different objects can access the same objects at various places. So, to ensure that a given object state is modified in the correct way, it is expected to encapsulate the object variables so that you can ensure that the object doesn’t goes into an invalid state.

In ECS, the execution order is explicitly defined by the systems, and those (or their jobs) are the ones responsible for modifying the state of the application. Systems also often operates on top of entities with a given set of components, so alongside ensuring the correct execution order you will ensure that all the structural changes happen in the correct time (and those are always on sync points, so easier to track).

TL;DR: the execution order in ECS is way easier to manage than in traditional OOP, so having most of the data as “global” is not a real issue (as long you have designed your dataflow well, as latios mentioned).

2 Likes

Global variables are an issue to be aware of. Remember, in OOP, you still often have methods or functions that effectively mutate global states.

I am not sure that I agree that traditional OOP has a poorly defined execution order. What has poorly defined execution order is MonoBehaviour and I don’t know that I would call MonoBehaviour traditional OOP.

Global variables do get worse the more people have access to the code. ECS does strip away layers of abstraction. If it is just your development team, then it can be managed. The issues get worse for third-party code. You can still have something like encapsulation by adding extra components, even if they sometimes have duplicated data.

As long as you understand your code fully and all third-party code, you are fine. In ECS, you generally design our code with the idea that a variable can only be updated once, or in the case of control logic twice, per frame. This is important if you want to have understand able control flow. Third-party code has the potential to break this contract.

I take this like the Python approach:

Treat your API users like adults. If they want to write position from the UI job, let them. They probably have a good reason.

Encapsulation is one of the weakest “strengths” of OOP, and one we can totally live without.

However, you can just make the components or their fields internal that you don’t want other systems to mess with.

1 Like

As mentioned, execution order matters.

In traditional OOP, at a large project scale - you’re dealing with a spaghetti of events, intertwined method calls, and sometimes even circular dependencies. This is pretty much unavoidable, even with decently maintained project.

Which makes process of tracking down what changes what data notoriously hard, if you don’t know even where to start.
In fact, I’ve been refactoring my home project lately, to allow it to work with jobs / Entities. So, as an example, events are pretty much everywhere, and this quickly became a real pain-point. Because data changes have untrackable, unpredictable nature. Which in the end results in duplicate calls, which modify visual / presentation layer, and side-effects go on and on.

DOD approaches this problem differently. It has clear and defined order of system execution. As long as you know what variable is changing, you can step through usages fairly quickly to figure out what’s going on. So it does not matter if components accessable for mutation from anywhere. Since you can see what systems change it.

Even if you don’t know precise variable, you can fairly “guess” what kind of logic could affect the result of data transformation. And as such, just plain turn off system, use a debug statement, or step through.
Easy to debug → easy to maintain → easy to expand and add new features.

  • Its harder to introduce side-effects that go unnoticed.
5 Likes