5

I have written an ECS but I have some questions about the update phase. (in systems) I have read many articles, but not found references to this sort of problems.

In order to have benefits from ECS (cache friendly, for example), they are the following requirements:

  • Entity must be just an ID.
  • Components must be only pure data (struct with no logic).
  • Systems contains the logic and update the components.
  • No interactions between systems (instead systems communicate by adding “Tag” components to entities).

So, the logic applied in each system is fine and all works when they are no “user code”. But, when we deal with user code (for example the user can attach C++ code to an object (like Unity, Unreal)), the problems come:

  1. Since, components contain only the data, when the user modify the local position, the world position is not updated (the world position will be computed when the Transform System will process each Transform Component. So if the user asks for the world position after modifying its local position, it will get the previous world position and not the actual.
  2. When an entity is removed, its children must be removed. Since the component contains only the data and not logic, the children will not be removed (it will be on the next Parent System update). So we have some “delay” (the children will still be accessible but will be removed on the next Parent System update).
  3. Supposing we have the entities A, B, C. B is a child of A. In the user code (c++ code attached to the entity), the user set the parent of B has C, then remove entity A. When the Parent System will update, it will detect that A has been removed, (it can also detect that the parent of Entity A has changed) but how the system can know if the entity A has been removed after the parent change of Entity B or before?

Adding logic into components will ruins advantage of pure ECS (doing the same actions on all same components, in a cache friendly way), so IMHO it's not a solution.

Anyone have the solution? I would like to know how are you deals with this sort of problems with your ECS implementation.

Thanks!

V3n0m
  • 51
  • 2

1 Answers1

4

I had the same questions like you.

I modeled my solution after reading that (This is a must read honestly):

Gamasutra: Syncing a data-oriented ECS with a stateful external system

A possible solution to that is to set up some rules regarding reading and writing components.

I follow the rule that is always ok to read from components data, but if you have to write data to the component inside an external system (one that it's not part of the components interface systems) you must use always a transform system function.

For example:

Having a transform component like that:

struct transform {
    glm::vec2 position = glm::vec2(0);
    glm::vec2 scale = glm::vec2(1);
    float rot_radians = 0.0f;
    glm::mat3 ltp = glm::mat3(1);
    glm::mat3 ltw = glm::mat3(1);
    entt::entity parent = entt::null;
    std::vector<entt::entity> children;
};

I will define some systems to write changes to it like this:

void set_position(entt::registry& r, entt::entity e, glm::vec2 position);
void set_rotation(entt::registry& r, entt::entity e, float rot_radians);
void set_scale(entt::registry& r, entt::entity e, glm::vec2 scale);
void set_parent(entt::registry& r, entt::entity to, entt::entity parent = entt::null);

Inside those functions, you are allowed to read/write transform component data freely.

The more I work with ECS, the more I tend to think like if I was programming in C. You have data and functions that change that data. Of course you can go and change the component data directly but I realized that It's not worth to spend time trying to avoid that, it's simply a bug or bad programming if you do it in a component that needs some more things after you update data.

FrameBuffer
  • 757
  • 1
  • 7
  • 26