dynamis-world 0.7.0

Host scene registry, step orchestration, and readback
Documentation
use dynamis_abi::ColliderRecord;
use dynamis_state::StateStreams;

use crate::World;

fn flush_pool_range(
    state: &StateStreams,
    queue: &wgpu::Queue,
    head: Option<(u32, u32)>,
    records: &[ColliderRecord],
    owners: &[u32],
) {
    let Some((start, end)) = head else {
        return;
    };
    debug_assert_eq!((end - start) as usize, records.len());
    if records.is_empty() {
        return;
    }
    state.colliders.write_at(
        queue,
        start as u64 * std::mem::size_of::<ColliderRecord>() as u64,
        bytemuck::cast_slice(records),
    );
    state
        .collider_owners
        .write_at(queue, start as u64 * 4, bytemuck::cast_slice(owners));
}

fn contiguous_runs(slots: &[u32]) -> Vec<&[u32]> {
    let mut runs = Vec::new();
    let mut start = 0usize;
    for index in 1..=slots.len() {
        let breaks = index == slots.len() || slots[index] != slots[index - 1] + 1;
        if breaks {
            if index > start {
                runs.push(&slots[start..index]);
            }
            start = index;
        }
    }
    runs
}

impl World {
    pub(crate) fn flush_rows(&mut self) {
        let queue = self.backend.gpu.queue().clone();
        self.flush_observed();
        if self.shapes.dirty {
            self.upload_shapes(&queue);
        }
        self.soft.upload(&queue, &self.backend.streams.soft);
        self.bodies.dirty.sort_unstable();
        self.bodies.dirty.dedup();
        let dirty = std::mem::take(&mut self.bodies.dirty);
        for run in contiguous_runs(&dirty) {
            let descriptors = run
                .iter()
                .map(|slot| self.bodies.descriptors[self.bodies.alive[*slot as usize].id as usize])
                .collect::<Vec<_>>();
            self.backend.streams.state.body_descriptors.write_at(
                &queue,
                run[0] as u64 * self.backend.streams.state.body_descriptors.stride(),
                bytemuck::cast_slice(&descriptors),
            );
        }
        self.upload_colliders(&queue, &dirty);
        self.constraints.dirty.sort_unstable();
        self.constraints.dirty.dedup();
        for run in contiguous_runs(&std::mem::take(&mut self.constraints.dirty)) {
            let first = run[0];
            let records = run
                .iter()
                .map(|slot| self.constraints.records[*slot as usize])
                .collect::<Vec<_>>();
            self.backend.streams.state.constraint_descriptors.write_at(
                &queue,
                first as u64 * self.backend.streams.state.constraint_descriptors.stride(),
                bytemuck::cast_slice(&records),
            );
        }
    }

    fn upload_shapes(&mut self, queue: &wgpu::Queue) {
        let state = &self.backend.streams.state;
        self.shapes.pool.upload_pending(
            queue,
            &state.shape_sources,
            &state.shape_vertices,
            &state.shape_triangles,
            &state.shape_nodes,
        );
        self.shapes.dirty = false;
        self.shapes.uploaded = true;
    }

    fn upload_colliders(&mut self, queue: &wgpu::Queue, dirty: &[u32]) {
        for cleared in self.colliders.take_cleared() {
            let records = vec![ColliderRecord::cleared(); cleared.len as usize];
            let owners = vec![dynamis_abi::NO_BODY; cleared.len as usize];
            flush_pool_range(
                &self.backend.streams.state,
                queue,
                Some((cleared.offset, cleared.offset + cleared.len)),
                &records,
                &owners,
            );
        }
        let mut placed = Vec::with_capacity(dirty.len());
        for slot in dirty {
            let id = self.bodies.alive[*slot as usize].id;
            let run = self
                .colliders
                .run_of(id)
                .unwrap_or_else(|| panic!("row {slot} of body {id} has no collider run"));
            placed.push((run, *slot));
        }
        placed.sort_unstable_by_key(|(run, _)| run.offset);
        let mut records = Vec::new();
        let mut owners = Vec::new();
        let mut head: Option<(u32, u32)> = None;
        for (run, row) in placed {
            let contiguous = head.is_some_and(|(_, end)| end == run.offset);
            if !contiguous {
                flush_pool_range(&self.backend.streams.state, queue, head, &records, &owners);
                head = Some((run.offset, run.offset));
                records.clear();
                owners.clear();
            }
            let (_, end) = head.expect("a pool range is open");
            head = Some((head.expect("a pool range is open").0, end + run.len));
            let range = run.offset as usize..(run.offset + run.len) as usize;
            records.extend_from_slice(&self.colliders.records()[range]);
            owners.extend(std::iter::repeat_n(row, run.len as usize));
        }
        flush_pool_range(&self.backend.streams.state, queue, head, &records, &owners);
    }
}