0
\$\begingroup\$

I don't understand the purpose of separate SystemClass for functions. My composition looks like this:

class Color {
   void Blend(uint32_t entityID, float factor) {
      red[entityID] *= factor;
      green[entityID] *= factor;
      blue[entityID] *= factor;
   }

   static std::vector<float> red;
   static std::vector<float> green;
   static std::vector<float> blue;
}
std::vector<float> Color::red;
std::vector<float> Color::green;
std::vector<float> Color::blue;

I am keeping vectors of members to have possibility to iterate over specific member instead of loading whole struct.

My question is: what exactly is the reason for SystemClass with only logic?

class ColorComponent {
   static std::vector<float> red;
   static std::vector<float> green;
   static std::vector<float> blue;
}
std::vector<float> ColorComponent::red;
std::vector<float> ColorComponent::green;
std::vector<float> ColorComponent::blue;

class ColorSystem {
   void Blend(uint32_t entityID, float factor) {
      ColorComponent::red[entityID] *= factor;
      ColorComponent::green[entityID] *= factor;
      ColorComponent::blue[entityID] *= factor;
   }
}

How the second composition suppose to be better for performance?

I will be very grateful for explanation.

\$\endgroup\$
  • 1
    \$\begingroup\$ Why do you say that it should be better for performance? Where do you get that idea from? Do you have any reference material? You might want to give an actual example of what you have instead of "example" and "function2" and "ints". It's not really clear what's going on. \$\endgroup\$ – Alexandre Vaillancourt Aug 6 at 19:20
  • \$\begingroup\$ Sorry, you are right I should be more specific. It could be for example Color class with vectors of red,green,blue and Function1 could apply Blending or something. I am not sure where this idea of performance benefit comes from, thats actually what I wanted to clarify. If having functions and static vectors of members in the same class does not impact performance I'm happy to hear that, but if it is I want to know why exactly. \$\endgroup\$ – Mikołaj Gogola Aug 6 at 19:32
  • \$\begingroup\$ Could you edit the question and add all of this in? It should make the question clearer. \$\endgroup\$ – Alexandre Vaillancourt Aug 6 at 19:46
  • 2
    \$\begingroup\$ The real advantages of the Entity - Component - System pattern become clearer when you have multiple systems which operate on multiple components, with some components being used by more than one system. \$\endgroup\$ – Philipp Aug 6 at 20:31
  • \$\begingroup\$ note that a class with no non-static stuff inside it doesn't need to be a class. \$\endgroup\$ – immibis Aug 7 at 1:12
2
\$\begingroup\$

I'll take a stab at it. Some people are much more knowledgeable about c++ and how all that stuff work, so please feel free to comment and vote accordingly.


Both examples that you give are roughly equivalent in terms of efficiency. In both cases, the values for each element is contiguous. You're practicing 'struct-of-arrays' instead of 'arrays-of-structs' in both cases.

This practice is interesting to avoid trashing the cache when accessing only a subset of elements in a struct. That's because the data you need is pulled in the cache, with a little bit of data that is stored before it, and a little bit of data that is stored after it. So if you need that data that is immediately after it, then the CPU does not have to go fetch additional data, it is already in the cache.

In both cases here, you don't benefit from it as you access always the same data. (If this Blend function is accessed in a tight loop, the data may still be hot in the cache.)

This approach of separating the data from the logic is when the data needs to be accessed by multiple 'logic aspects' i.e. multiple un-related systems.

A Transform component is useful. For multiple systems. But in what system do you store it? Could be the one responsible for the display. Or the one responsible for the audio. What about the one responsible for the physics? The answer to this is to take the data out of the systems, let it be managed by something that does just that, and give a way to the systems to access the data they need, when they need it, and in a fast way.

Having the components designed as fine grain chunks of data, close together in memory, let the system loop over them fast, benefiting from the fact that the data is more often already in the cache when it is needed.

Another point of having the data outside the System, is that it can allow for easier testing. It's been the fashion lately: dependency injection 🎉. Roughly, if you supply the data to a class, you have better control over it, and it allows you to pass whatever you want, which means it lets you more freedom to pass trash data and see how the system reacts.

Keep in mind that there are multiple ways to approach this, and they will generally be done so to fix particular issues or improve specific aspects of a particular software. There is not really a one way that fits all the needs.

\$\endgroup\$
  • \$\begingroup\$ Thank you, I think I was just short-sighted and didn't consider Systems which use multiple Components. \$\endgroup\$ – Mikołaj Gogola Aug 7 at 5:07
  • \$\begingroup\$ I wonder if another factor in this recommendation might be psychological/habit. Encapsulating behaviour in a component type encourages us to write functions that act on the current instance in isolation: foo.DoAllUpdatesRequiredByThisFoo() { if (this.NeedsUpdateA()) this.DoUpdateA(); else this.DoUpdateB()}; rather than thinking of them as collections to manage in batches: fooSystem.DoUpdateA(foosNeedingUpdateA); fooSystem.DoUpdateB(foosNeedingUpdateB);. It's not impossible to write data-oriented code in the component class, just less familiar/intuitive for our conventional OOP habits. \$\endgroup\$ – DMGregory Aug 7 at 20:01

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.