How to implement data-oriented design in Rust?

Background

When developing a game engine, we usually use a data-oriented design for optimal memory and computing performance.

Take the particle system as an example.

In a particle system, we have many particles, and each particle can have several attributes, such as positions, velocities, etc.

A typical implementation in C ++ would be:

struct Particle { float positionX, positionY, positionZ; float velocityX, velocityY, velocityZ; float mass; // ... }; struct ParticleSystem { vector<Particle> particles; // ... }; 

One of the problems with this implementation is that the attributes of the particles alternate with each other. This memory layout is not a cache and may not be suitable for SIMD calculations.

Instead of data-driven design, we write the following code:

 struct ParticleAttribute { size_t size; size_t alignment; const char* semantic; }; struct ParticleSystem { ParticleSystem( size_t numParticles, const ParticleAttribute* attributes, size_t bufferSize) { for (size_t i = 0; i < numAttributes; ++i) { bufferSize += attributes[i].size * numParticles; // Also add paddings to satisfy the alignment requirements. } particleBuffer = malloc(bufferSize); } uint8* getAttribute(const char* semantic) { // Locate the semantic in attributes array. // Compute the offset to the starting address of that attribute. } uint8* particleBuffer; }; 

Now we have only one distribution, and each attribute is constantly in memory. To simulate particles, we can write the following code:

 symplecticEuler(ps.getAttribute("positionX"), ps.getAttribute("velocityX"), dt); 

The getAttribute function will get the start address of a specific attribute.

Question

I would like to know how to implement this in Rust.

My idea is to first create a class called ParticleSystem that takes several ParticleAttribute to calculate the total size of the buffer, then allocate memory for the buffer. I think this can be done in Rust's secure code.

The next step is to implement the getAttribute function, which will return a link to the start address of a specific attribute. I need your help here. How to get the source address with an offset and apply it to the desired type (for example, float *) and transfer this raw pointer to the mutable link in Rust?

Also, I think I should wrap this raw pointer to a mutable array reference, because I need to use SIMD lib to load four elements through this link. How to achieve this with Rust?


Update: Provide additional attribute information. The number and details of attributes are determined at runtime. Attribute types may vary, but I think we should only support primitives (f32, f64, ints ...).

+6
source share
1 answer

This is a very complicated way to implement DOD, and the idea of ​​using time search for getters makes me cringe.

A simple version is to simply allocate memory for each attribute:

 struct Particles { x: Vec<f32>, y: Vec<f32>, } 

which requires knowledge of attributes in advance.

Then there is no Shenigan to get all ys, they just sit there, already dialed, waiting for you.


Extending this parameter to dynamically defined attributes is not so difficult:

  • we can use HashMap<String, xxx> to search for a given attribute at runtime
  • we can use enum to have one Value that will be stored in a hash map, which can take various forms (another solution will use a sign)

It will be:

 #[derive(Debug, Hash, PartialEq, Eq)] enum Value { UniformInt(i64), UniformFloat32(f32), UniformFloat64(f64), DistinctInt(Vec<i64>), DistinctFloat32(Vec<f32>), DistinctFloat64(Vec<f64>), } struct Particles { store: HashMap<String, Value>, } 

As an alternative, we could use 6 hash cards ... but if you do not know a priori what the type is (when the only thing it has is a string), then you need to look at all the hash cards one by one: annoying, and waste time.

+8
source

All Articles