0
\$\begingroup\$
// Ex.1
    for (Component& component : m_components)
    {
        UpdateComponent(component, dt);
    }
// Ex.2
    for (Component& component : m_components)
    {
        component.Update(dt);
    }

Assume that:

  1. UpdateComponent() and the class method Component::Update() have identical logic.
  2. Component::Update() is not a virtual function.
  3. m_compoents is a container that uses a contiguous memory block (e.g. std::vector).

From the caching perspective, is there any performance difference between the 2 examples?

\$\endgroup\$
  • 2
    \$\begingroup\$ This looks like a straightforward example where you could test both and profile them to verify whether you observe any differences in practice, rather than relying on Internet strangers to pontificate about theory. What do your tests so far suggest? \$\endgroup\$ – DMGregory Jun 19 at 3:00
  • 1
    \$\begingroup\$ What's dt in that code? \$\endgroup\$ – Philipp Jun 19 at 8:46
  • 1
    \$\begingroup\$ @Philipp dt is delta time, very common in games with a variable time step. \$\endgroup\$ – ratchet freak Jun 19 at 9:51
  • 1
    \$\begingroup\$ @ratchetfreak That's what I am assuming too, but I would prefer a confirmation from the original author. \$\endgroup\$ – Philipp Jun 19 at 10:24
  • \$\begingroup\$ @DMGregory I agreed. In my synthetic test neither can show a definite performance advantage over the other so I was hoping to get some feedback before I decide to spend the effort to further develop in a real world environment. \$\endgroup\$ – GameDevNoob Jun 19 at 20:51
0
\$\begingroup\$

If you do the Entity - Component - System pattern by the book, then components should be dumb data-holders while all the logic is in the systems. That means components shouldn't have update methods. They should not have any methods at all.

So the first example would be the canonical one while the second one is more of an OOP approach to game architecture where objects are supposed to be smart and contain their own logic.

But if you are looking at this from a pure performance point of view, then in this case it is likely more of a semantic choice. If you call a method of an object, you are actually calling a function to which you pass a this pointer in addition to all the other parameters. If the update method is sufficiently simple, then it wouldn't surprise me if a compiler would generate the exact same code for each (depending on the compiler and optimization settings, of course).

\$\endgroup\$
  • \$\begingroup\$ thank you for the explanation. That makes sense. My goal is to restructure my codebase into a somewhat component-based solution that has the similar cache efficiency as ECS while retaining the convenience of writing OOP code (having the logic and data in the same place). \$\endgroup\$ – GameDevNoob Jun 19 at 20:31
0
\$\begingroup\$

The idea of an ECS architecture is to avoid inheritance-like calls in components.

As you have read already, components are data containers. Any "updating" of a component should be handled by the appropriate sub-system.

As far as caching/performance goes, there is a simple-ish way to avoid cache misses: Use the observer pattern for each component: Each subsystem contains a cache friendly vector/array of observers, which copy data from the actual component, whenever it's data changes(via notification).

In such a way the component data is always available to the cache with no additional RAM fetches.

Having Logic and Data in the same object/class/structure is considered bad practice, and allows hackery that is likely to cause as many issues as it solves.

Restrict each subsystem to only read from, and change, a small subset of total data available (A motion system, which updates position according to velocity, should not be able to read/change character data, for example). By separating logic and data, you simplify your code, and restrict the upper limit of complexity, making it much easier to debug.

Now whilst the following is not a direct answer to your question, it does address some misconceptions that you seem to have:

You mentioned in a response to another answer that you wish to retain "the convenience of writing OOP code (having the logic and data in the same place)" :- This goes against one of the core tenets of OOP SOLID design: The Single Responsibility Principle.

OO Design is all about separating and minimizing concerns, and dependencies within a given software module. The convenience of OO code is that of maintenance and being extensible, not that of writing a megalithic (and thus, difficult to maintain, and impossible to extend) class to store all your game entity code.

Such a class also violates the Open/Closed Principle, in so far that it will require significant modifications over the course of the project's life, as you add more and more functionality and data storage to it. Additionally, if you attempt to create a family of entity like objects, you run the risk of the "deadly diamond" scenario.

A true ECS architecture avoids these issues completely.

By favouring data component composition over inheritance, and separating logic concerns into subsystems, you can create a simple game entity class, whose only concern is the storage, and retrieval of components, with no regard to component type, allowing the construction of any type of entity, just by adding new components, or omitting others, in new patterns.

No new logic or sub-classes necessary.

And if that wasn't enough to convince you: Because each subsystem only cares about a small part of the logic for each entity, it is very easy to write, debug and test.

This means fewer difficult-to-diagnose bugs in the long term, which will mean your coding experience will be more productive and enjoyable.

\$\endgroup\$
  • \$\begingroup\$ I should have made my comment more clear. I meant to say "having a component data and only its related logic in the same place". In my current implementation, each component class has its own logic and defined responsibility, just that within the component there is no clear semantic separation between the system and data. \$\endgroup\$ – GameDevNoob Jul 9 at 23:28
  • \$\begingroup\$ @GameDevNoob I understood what you meant. My point still stands. ECS stands for "Entity, Component, System" architecture. By having no "system", or "subsystem" objects to handle your logic, you just have an "EC" architecture, with no real benefits over polymorphic architecture. Logic and data should always be separate: Entities store components, components store data, systems process components, thus, the entity is updated. There are a plethora of other benefits, but that is a much longer conversation, best held elsewhere. \$\endgroup\$ – Ian Young Jul 10 at 10:23

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.