LightningdeGolem
LightningdeGolem

Reputation: 97

Sharing only some fields of a struct between threads

I'm writing a simulation program using two threads: one for the actual simulation and the other for rendering.

The simulation thread has a Vec of Entity structs that it uses each tick. My problem arises when the rendering thread needs to access the Vec of entities in order to render them, but some of the fields of the Entity struct do not implement the Send trait. However, these fields are not actually used for rendering.

The only solution I have thought of is to split each entity into two structs: one which contains the internal 'non-sendable' fields and the other which contains the fields necessary for rendering. I could then use two separate Vecs and wrap the 'sendable' one in a Mutex and share it with the rendering thread. However this means I will have two parts to each entity and that each struct will have to store the index of the other part to keep them tied together, but this feels like a massive headache to manage when entities are being created and deleted a lot.

Is there a standard way of doing this in Rust? It feels like a fairly common requirement for lots of programs, but I can't find an easy way to do it.

Upvotes: 1

Views: 678

Answers (1)

Mac O'Brien
Mac O'Brien

Reputation: 2907

You're on the right track with the split-Entity design, but you can take it one step further with cues from the Entity-Component-System (ECS) pattern. Rather than having a Vec<SimEntity> and a Vec<RenderEntity>, in which the two kinds of Entity must keep track of each other, the ECS pattern says that an entity is simply a collection of components. In a simple model, this would be something like

struct Sim { /* ... */ }
struct Render { /* ... */ }

struct Entity {
    sim_id: usize,
    render_id: usize,
}

struct World {
    entities: Vec<Entity>,
    sims: Vec<Sim>,
    renders: Arc<Mutex<Vec<Render>>>,
}

Then, a system in ECS terms is simply a function that acts on components rather than entities. This pattern parallelizes well because a given system can lock only those components which it needs, rather than locking all entity data. Here's a playground with a simple (and somewhat naive) proof-of-concept with a separate rendering thread which shares data with the simulation thread.

If you're looking for an implementation or inspiration, Rust has numerous ECS libraries. You can find extensive benchmarks of the various options in rust-gamedev/ecs-bench-suite.

Upvotes: 4

Related Questions