Without really commenting on the whole mess that is "data oriented programming" versus "object oriented programming," (because largely those arguments get in the way of actually getting things done), the crux of your concern seems to be: is this code going to make efficient utilization of the cache?
Making efficient use of the memory cache is fairly straightforward at the high level: it means structuring your code so that if you touch some address, the next addresses you touch are near (ideally next to) the last address you just touched.
Typically this kind of code looks like "put a bunch of data in a big array, loop over that entire array and do something."
With that said, your code
for (int i=0;i<nObjects-1;i++) { // where i is the object id
object_update_functions[object_ids[i]](i); // get the update function for the type of object
}
is not necessarily doing that. What you're doing is looping over an array of object IDs. If you were going to do something with each of those object IDs, that would probably be taking advantage of the contiguous nature of the object ID array. However, instead you use the ID to index into another array. This other array is (potentially) nowhere near object_ids
in memory. In the worst-case scenario both arrays fit exactly into one cache line and every iteration of the loop causes a cache miss, because first the memory containing object_ids
must be fetched to read object_ids[i]
, and then the memory for object_update_functions
must be read in to index into that array.
Further, you're using the result of this indirection to simply call out to some other block of code, which may do who-knows-what. Generally you'd want to consider organizing your code so you do some operation F()
on a bunch of data, and then some other operation G()
. Not, as you are potentially doing, calling F()
then G()
, then K()
, then F()
again.
What you'll want to do instead is start thinking about organizing data in terms of the operations that might need it. The operation "update the position" needs a certain set of data: the current position, the velocity, the acceleration, maybe other things. Try to organize your data so that all the "position data" for all your objects is in a big, contiguous list. Then blast through that list updating the positions of everything.
Similarly if your next task is "update the shield and HP status of every object," then you'll want to similarly have every object's shield level and HP stuffed into a contiguous array, and blast through that.
You can have, somewhere, a mapping from object ID to the set of position properties, and object ID to the set of shield properties. But the key is that you'll want both of those sets of properties to be stored contiguously for their respective operations already. You don't want to have to indirect through the object ID table to get to them; you just have that functionality around for other places in the code where you don't need to be maximally cache-coherent.
Similarly, I'd venture to say your "update" function pointers are leading you into a trap and you probably don't need them. Your top level game update function (the one you call in your game loop directly) can just call these individual update operations directly. There's really no particular need to abstract them into function pointers:
void UpdateTheGame(float elapsedSeconds) {
UpdateAllPositions(elapsedSeconds, positionDataArray);
UpdateShieldStatus(elapsedSeconds, shieldDataArray);
}
where positionDataArray
is an array of PositionData
:
struct PositionData {
float x, y, z;
float vx, vy, vz; // velocity
};
and so on. This means your "game objects" might end up looking like:
struct GameObject {
int positionDataIndex;
int shieldDataIndex;
};
so that you can retrieve the relevant data for any individual game object if you need to. The point is you want to avoid having to follow the object to get the data during the important, cache-sensitive update of all that data if you can.