Cache Coherency and Object Update

Started by
37 comments, last by LorenzoGatti 6 years, 6 months ago

As far as i'm aware, there are two major paradigms when it comes to game object / game component updating.

The first is updating each "entity" at a time, including it's components"

The second is updating all components of a specific type at once.

The first approach seems to be easier to code , especially when you take into account update dependencies (i.e parent update before child) , but seems to be less efficient in terms of cache coherency and etc.

The second approach seems to be complicated to design , but helps with cache coherency and general optimizations  i.e animating all skeletal meshes at once , performing all navigation queries , etc.

It seems that most game engines , like unreal, that were built a decade + ago have turned their old game object hierarchy into the first approach , but I wanted to ask, if you were creating a game engine today, which approach would you choose?

Advertisement

Both or niether, as needed.

Cache coherency is great, but it's trivial to design cache-coherent algorithms in a vacuum ("oh, just put all the components in a big array, update them in a loop, DONE"). It's much harder to design cache-coherent algorithms that adapt to actual practical use ("oh, it turns out that this component needs to read the position, which means now we might be blowing cache coherency to read that from a different component now, or blowing it to copy it into this component earlier," et cetera).

What you want to do is design how you store your data in memory in a fashion that is efficient for the way you will access and transform that data. "Access," importantly, includes more than just how the memory will actually be fetched by the CPU, but how you will actually get at and use, connect, et cetera, that data in your APIs.

If you bend over backwards to make some components stored in a big cache-coherent array, but you never actually need to update all those components at once, have you really gained anything? Worse, if by doing so you've made it vastly more complex to use those components at the API level, have you actually improved anything?

The reason that there are no generalized, broad, great answers to this problem is that the devil is in the details. So consider the purpose of each component or other piece of data or functionality you're adding to your system, and how you want to interact with it at the API level, and how you need to interact with it at the implementation level, and weigh the pros and cons of every available approach with that in mind. And make a decision for that problem that might be different than the decision you'll make for the next.

In general, I'd aim for cache coherency when I can, as long as it doesn't sacrifice usability in any fundamental way.

 

Thank you for the response!

I've actually been thinking about the way i'd handle data layouts. Each component would have what I refer to as "BusData" , bus data contains all of the memory that would be required for updating the component , and each component class also has a class called "BusDataProcessor" , so a skeletal mesh would have SkeletalMeshBusData and SkeletalMeshBusDataProcessor. When an object wants to be updated, it allocates an instance of it's BusData , therefore removing the issue of " you never actually need to update all those components at once" . If there is an allocated BusData in the processor, it is because the component wants to be updated. 

My main issue with the ecs model is that it seems to make certain issues like interobject dependencies , function overloading , and things of that nature more complicated. I've tried to cope with that by doing dependencies on a system basis.I have a "SpatialComponent" base class, which contains a transform/bounds/etc. Examples of the classes that subclass from this are skeletalmeshcomponent, staticmeshcomponent, etc. I also have a movement component and an input component. If the entity is a player , the movement component is dependent on the input component, but if the player is an ai, the movement component is dependent on the aicomponent. I could make it so that the movement component system is dependent on the ai and input component, but you can see how it can become convoluted. 

11 minutes ago, AxeGuywithanAxe said:

My main issue with the ecs model is that it seems to make certain issues like interobject dependencies , function overloading , and things of that nature more complicated.

That's because "ECS" is an overengineering trap. These issues are only as complicated as you choose to make them, and you choose to make them more complex by ascribing more magic and dogma to what an "ECS" is than you should. Even your described "bus data" paradigm strikes me as overengineered by virtue of focusing on a generic, one-size-fits-all solution to everything and then trying to cast specific problem scenarios into that mold.
 

 

11 minutes ago, AxeGuywithanAxe said:

I also have a movement component and an input component. If the entity is a player , the movement component is dependent on the input component, but if the player is an ai, the movement component is dependent on the aicomponent. I could make it so that the movement component system is dependent on the ai and input component, but you can see how it can become convoluted. 

But remove "component" from the names of all these interfaces. You are left with the same "convoluted dependency" problem. The issue here isn't the component approach, per se. It's just the same old API interface design problems re-made with different names. The ECS magic bullet has been billed so hard (incorrectly) as a solution to these problems, but it isn't at all. It's a solution to an entirely different problem that gone through some kind of collective-consciousness mutation. Just like scene graphs did, back in the day.

If your agent movement is currently depending on pulling data from either the input system or the AI system, depending on the state of the agent (player controlled versus AI controlled), perhaps the solution is to have something push the movement commands to the agent movement object through the unified single interface of that object. Thus reducing the double dependency by inverting the relationship.

I guess maybe I should of focused on the ideology behind ecs than the ecs paradigm in general. The bus data approach helps in the sense that you only load and focus on the data that is required on updating, no cache misses, no etc. I read the gdc presentation in which insomniac increased the speed of their code through smd by just focusing on the simple data that is required for updating.

Even in a game object model , or game object with component, the movement of a character is dependent on input. In UE4 they have a "controller" which is ticked prior to the movement component , through a dependency graph, and pushes commands depending on input , either by the playercontroller or the aicontroller, to the movement component so no matter the paradigm , my ideology is still to push the commands instead of reading. To my point about the "movement component being dependent" it is dependent on the others in that it has to be updated after it's had it's commands pushed, or there will be one frame of latency. 

Data access patterns in an ECS are highly unpredictable, since each entity can have different behaviors that require different data at different times. This is entirely at odds with data-oriented design. You'll go mad trying to reconcile the two.

14 minutes ago, Zipster said:

Data access patterns in an ECS are highly unpredictable, since each entity can have different behaviors that require different data at different times. This is entirely at odds with data-oriented design. You'll go mad trying to reconcile the two.

I don't know if I necessarily agree. Systems would provide the same update logic that an entity would do, but across all components of the same type at once. So in a game object model, you'd have a SkeletalMeshComponent that does the animation processing in a tick function, in ECS you would have a SkeletalMeshComponentSystem which updates all animations at once , even across threads.

I think the data access patterns are quite explicit. That seems to be one of the biggest advantages over the game object model.

56 minutes ago, Zipster said:

Data access patterns in an ECS are highly unpredictable, since each entity can have different behaviors that require different data at different times. This is entirely at odds with data-oriented design. You'll go mad trying to reconcile the two.

If you're doing ECS "properly" (if such a thing can be said to be possible, given the disagreement over what ECS actually means) then you aren't updating each entity every frame, because an "entity" is just a binding mechanism that identifies groups of components. The most common operation should be updating homogeneous lists of components all at once, with operations that touch multiple components being comparatively "rare." Actually accomplishing this can indeed be "maddening" if you aren't used to thinking in those terms and don't have gameplay that's well-suited to that kind of decomposition.

Of course, I would like to point out that you can use non-dogmatic, data-oriented "outboard composition" without locking yourself into dogmatic ECS...

3 hours ago, Oberon_Command said:

If you're doing ECS "properly" (if such a thing can be said to be possible, given the disagreement over what ECS actually means) then you aren't updating each entity every frame, because an "entity" is just a binding mechanism that identifies groups of components. The most common operation should be updating homogeneous lists of components all at once, with operations that touch multiple components being comparatively "rare." Actually accomplishing this can indeed be "maddening" if you aren't used to thinking in those terms and don't have gameplay that's well-suited to that kind of decomposition.

Of course, I would like to point out that you can use non-dogmatic, data-oriented "outboard composition" without locking yourself into dogmatic ECS...

I feel like the only maddening aspect I have about is that I can't seem to find a way to maintain the required entity update order (i.e update parent before child) , that is easily achievable in a entity component model , In that method I would just have an entity put it's parent as it's dependencies , and the component's would put it's owning entity as a dependency, and it's done. With system wide updating, it seems impossible in my mind to maintain this. 

23 minutes ago, AxeGuywithanAxe said:

I feel like the only maddening aspect I have about is that I can't seem to find a way to maintain the required entity update order (i.e update parent before child) , that is easily achievable in a entity component model , In that method I would just have an entity put it's parent as it's dependencies , and the component's would put it's owning entity as a dependency, and it's done. With system wide updating, it seems impossible in my mind to maintain this. 

My first question would be, why do you need parent before child updating at the component level in the first place? What's the use case for that? Remember, a "component" is ideally a bundle of state that, in the most common operation over that state, is contiguous in memory. Can you rethink your decomposition to avoid the issue?

My second question would be, why can't you just sort the component array to enforce the update order? Or partition it, then update the partition with parents before the partition before children?

This topic is closed to new replies.

Advertisement