need help with data oriented design

Started by
22 comments, last by cool mr croc 11 years, 10 months ago

This is an issue of proper design and layout of data. In a well-designed particle system you shouldn't try to pool together a bunch of different types of particles each with a differing data structure, you should try to design a particle in such a way that it can be used to build many different outcomes while retaining the same base data structure.
i think what i dont get is how can you encapsulate particles within a particle manager without pooling together a bunch of different types.


The point of DOD is to sort your data so that it's as consistent for as long as it can. If you need to switch states between entities all the time while looping, you're not doing it right. If the bricks and balls differ enough to require an "if ball else if brick" statement, you should definitely split them up and run separately. Think of them as two sets of unrelated particles.

For collisions, you take the required data (width and maybe position) of the objects, rather than the full objects, put them into a lean vector or two and rip through it in a data consistent manner. This opposed to loading a vector with the full fat objects draining cache and having to jump over the data in them that you don't touch anyway.

Where the data differ, you make separate passes. For example, all ball - ball collisions makes one set, all ball - brick in one set, and then run specialized functions for each.
i think this is what i do get, maybe i just need to practise a lot.
Advertisement

i think what i dont get is how can you encapsulate particles within a particle manager without pooling together a bunch of different types.

The type shouldn't matter, it's the data they contain. You group the same kind of data of the particles together, no matter what the particles actual type is, excluding what ever data they don't have in common for the task at hand. A bicycle and an ant aren't the same type, but they both can have data relating to position and speed, so you sort only the data relating to that into the array to be handled. Sorry if I might be repeating myself..

Getting into the mindset of sorting the data like this is really hard, and only pays off if you have huge sets of data that actually can be sorted in a continuous way. If you can't find an obvious way to sort something, then don't waste your time doing it. As time goes, you'll see more things that fits with DOD.

A suggestion to get past the OO mindset could be to skip C++ for a while and use strict C instead. This will force you to think in sets of data rather than data objects and strip away the distractions of classes. C++ has the great benefit that it can swap between OO and procedural, which a lot of C++ programmers tend to forget.
Hmm, I did think about combining the ball and brick types together for movement, e.g. each has a position and velocity (0 for bricks), then loop through all and move and check for collisions. You could keep the vector sorted by x co-ordinate for easy collision detection. However I don't think the payoff would be enough because it would be hard to match a brick/ball up with it's other attributes, e.g. color etc. It's easy when pos[1] matches with color[1].

[quote name='cool mr croc' timestamp='1338895200' post='4946392']
i think what i dont get is how can you encapsulate particles within a particle manager without pooling together a bunch of different types.

The type shouldn't matter, it's the data they contain. You group the same kind of data of the particles together, no matter what the particles actual type is, excluding what ever data they don't have in common for the task at hand. A bicycle and an ant aren't the same type, but they both can have data relating to position and speed, so you sort only the data relating to that into the array to be handled. Sorry if I might be repeating myself..
[/quote] with your example of bike and ant, would there be a struct containing a vector of speed and one of position? and what would you do with the data they have thats different. say bike had a seat object and ant has a head object. are they just left in the manager class or are they encapsulated in some way as well?

with your example of bike and ant, would there be a struct containing a vector of speed and one of position? and what would you do with the data they have thats different. say bike had a seat object and ant has a head object. are they just left in the manager class or are they encapsulated in some way as well?

The best way may be to keep all objects' values in global vectors. So that both the ant and the bike stores its position in a long position vector. A position is a position, no matter the type, no matter if it has a seat or head.

Now, just having a position might not give you much to relate to so it's not a great example. What you might want though, is if you've got a position and a velocity for your objects, they all go in a respective "global" vector. The function that calculates the new position takes these two vectors, runs through them in one fast linear loop and outputs a new vector with the new positions.

That is sorting data. Instead of going through each of your objects update routine which will change animation, do some health calculation and then calculate its new position, you rip out the calculation of the new position and put it where all those calculations can be called and them alone in a single pass.

You could do this with your OO objects, but that would include a lot more indirection than running through a vector that has only the relevant data lined up, resulting in cache misses all over the place.
something like this?


struct velAndPos
{
Vector2D velocity;
Vector2D position;
};

vector<velAndPos>bikePositions;
vector<velAndPos>antPositions;
vector<bool> bikeCollisions;
vector<bool> antCollisions;

class someKindOfBikeyAntGame
{
...
void updatePositions(velAndPos* theVelandPos, int size)
{
for(int i=0;i<size;i++)
theVelandPos.velocity+=theVelandPos.velocity;
}
void collisionDetect(velAndPos* theVelandPos1, int size, velAndPos* theVelandPos2, int size
bool* storecollision1, bool* storecollision2)
{
//loop through, chech collisions, store in respective bool vectors
}
void update()
{
updatePositions(&bikePositions[0], bikePositions.size());
updatePositions(&antPositions[0], antPositions.size());
collisionDetect(&bikePositions[0], bikePositions.size(), &antPositions[0], antPositions.size()
&bikeCollisions[0], &antCollisions[0]);
}
}
Have you ever studied the first three normal forms of relational database design? They will help you to clearly see the one-to-one, one-to-many, and many-to-many relationships that exist in all kinds of systems. I recommend it, even if you never plan on writing a single line of SQL in your life, because -- as zen and vague as it may sound -- everything is connected.

http://w3schools.in/...e-Normalization
Not quite, but closer.. my point is that bike and ant positions are just positions in a very general sense, so they can be in the same vector.


// These functions doesn't have to be members of a class, since they only operate on the input given.

void updatePositions( vector<Vector2D>& position /*is altered*/, const vector<Vector2D>& velocity )
{
assert( position.size() == velocity.size() ); // If they're not the same size at this point, something is probably not right.
size_t size = position.size();
for( size_t i = 0; i < size; ++i )
position += velocity;
}

// Since position and velocity exist in different vectors, no unnecessary data is passed to this collision function.
void collisionDetect( const vector<Vector2D>& position, vector<bool> collisionResult /*is altered*/ )
{
assert( position.size() == collisionResult.size() );
// A bit of brute force collision detection.
size_t size = position.size();
for( size_t i = 0; i < size; ++i )
{
for( size_t j = i+1; j < size; ++j ) // Check all following positions.
collision = distance( position, position[j] ) < 5; // Collides if distance is less than '5'
}
}

class AntsAndBicycleGame
{
void update()
{
updatePositions( positions, velocities );
collisionDetect( positions, collisions );
}

vector<Vector2D> positions; // Positions for all instances of all types
vector<Vector2D> velocities;
vector<bool> collisions;
};


Unless you actually want to handle the bicycles' positions in a different way than the ants' positions, then you should put them in separate vectors since that's sorting it for different purposes.
i understand wat you mean now. i hadnt done anything like that as i wanted an explicit return on the collision bools, but i suppose i can just keep track of the order after the function returns.
Excellent!
There's a bit more manual tracking, that's true. One of the downsides of DOD is the extra foresight required when designing the code for it.

Glad I could help ( and be sure to use the rep button <-- ;) )

This topic is closed to new replies.

Advertisement