dynamis-world 0.7.0

Host scene registry, step orchestration, and readback
Documentation
use super::World;
use dynamis_abi::{BodyStateRecord, NO_SLOT};
use dynamis_gpu::SubmissionEncoder;
use dynamis_model::{BodyHandle, BodyState};
use std::mem::size_of;

pub(crate) struct View {
    observed: Vec<u32>,
    slot_of: Vec<u32>,
    dirty: bool,
    epoch: Option<u64>,
}

impl View {
    pub(crate) const fn new() -> Self {
        Self {
            observed: Vec::new(),
            slot_of: Vec::new(),
            dirty: false,
            epoch: None,
        }
    }

    pub(crate) fn len(&self) -> u32 {
        self.observed.len() as u32
    }

    pub(crate) fn ids(&self) -> &[u32] {
        &self.observed
    }

    pub(crate) fn observe(&mut self, id: u32) {
        if self.slot_of.len() <= id as usize {
            self.slot_of.resize(id as usize + 1, NO_SLOT);
        }
        if self.slot_of[id as usize] != NO_SLOT {
            return;
        }
        self.slot_of[id as usize] = self.observed.len() as u32;
        self.observed.push(id);
        self.dirty = true;
    }

    pub(crate) fn forget(&mut self, id: u32) {
        let Some(slot) = self
            .slot_of
            .get(id as usize)
            .copied()
            .filter(|slot| *slot != NO_SLOT)
        else {
            return;
        };
        self.slot_of[id as usize] = NO_SLOT;
        self.observed.swap_remove(slot as usize);
        if let Some(moved) = self.observed.get(slot as usize) {
            self.slot_of[*moved as usize] = slot;
        }
        self.dirty = true;
    }

    fn take_dirty(&mut self) -> bool {
        std::mem::take(&mut self.dirty)
    }
}

impl World {
    pub fn poll(&mut self) {
        self.backend.gpu.assert_alive();
        self.collect_readbacks();
    }

    pub fn wait(&mut self) {
        self.backend.gpu.assert_alive();
        self.resolve_queries();
        loop {
            self.drain_readbacks();
            if self.states_current() {
                return;
            }
            self.submit_states();
        }
    }

    pub fn try_state(&mut self, handle: BodyHandle) -> Option<BodyState> {
        self.validate(handle);
        self.view.observe(handle.id);
        self.view.epoch?;
        self.bodies.states[handle.id as usize]
    }

    pub(crate) fn states_current(&self) -> bool {
        self.bodies
            .alive
            .iter()
            .all(|handle| self.body_current(handle.id))
    }

    pub(crate) fn body_current(&self, id: u32) -> bool {
        self.bodies.covered[id as usize] == self.clock.step
    }

    fn completed_step(&self) -> Option<u64> {
        self.clock.step.checked_sub(1)
    }

    pub(crate) fn flush_observed(&mut self) {
        if !self.view.take_dirty() {
            return;
        }
        let ids = self.view.ids();
        if ids.is_empty() {
            return;
        }
        self.backend
            .streams
            .state
            .observed_ids
            .write(self.backend.gpu.queue(), bytemuck::cast_slice(ids));
    }

    pub(crate) fn copy_observations(&mut self, encoder: &mut SubmissionEncoder, step: u64) {
        let count = self.view.len();
        if count == 0 {
            return;
        }
        let stride = self.backend.streams.state.observed_states.stride();
        let bytes = count as u64 * stride;
        let displaced = self.backend.readback.observations.enqueue(
            encoder,
            self.backend.streams.state.observed_states.buffer(),
            0,
            bytes,
            step,
        );
        if let Some((sequence, bytes)) = displaced {
            self.consume_observations(sequence, &bytes);
        }
    }

    pub(crate) fn consume_observations(&mut self, sequence: u64, bytes: &[u8]) {
        let (records, remainder) = bytes.as_chunks::<{ size_of::<BodyStateRecord>() }>();
        assert!(
            remainder.is_empty(),
            "an observation readback must be a whole number of records"
        );
        for chunk in records {
            self.accept_body(sequence, bytemuck::pod_read_unaligned(chunk));
        }
        self.view.epoch = Some(sequence);
    }

    fn submit_states(&mut self) {
        let step = self
            .completed_step()
            .expect("a body state snapshot requires a completed step");
        let bytes = u64::from(self.bodies.device_count) * size_of::<BodyStateRecord>() as u64;
        if bytes == 0 {
            self.view.epoch = Some(step);
            return;
        }
        let device = self.backend.gpu.device().clone();
        self.backend
            .readback
            .open_states(&device, self.backend.streams.state.body_states.slots());
        let mut encoder = SubmissionEncoder::new(&device, "dynamis body state readback");
        let displaced = self
            .backend
            .readback
            .states
            .as_mut()
            .expect("a body state snapshot opens its readback ring")
            .enqueue(
                &mut encoder,
                self.backend.streams.state.body_states.buffer(),
                0,
                bytes,
                step,
            );
        self.submit(encoder);
        if let Some((sequence, bytes)) = displaced {
            self.consume_states(sequence, &bytes);
        }
    }

    pub(crate) fn consume_states(&mut self, sequence: u64, bytes: &[u8]) {
        let (records, remainder) = bytes.as_chunks::<{ size_of::<BodyStateRecord>() }>();
        assert!(
            remainder.is_empty(),
            "a body state readback must be a whole number of records"
        );
        for chunk in records {
            self.accept_body(sequence, bytemuck::pod_read_unaligned(chunk));
        }
        self.view.epoch = Some(sequence);
    }

    fn accept_body(&mut self, sequence: u64, record: BodyStateRecord) {
        let id = record.body_id as usize;
        assert!(
            id < self.bodies.ids.len(),
            "a state readback returned an out-of-range body id"
        );
        if record.generation != self.bodies.ids.generation(record.body_id)
            || self.bodies.index_of[id] == u32::MAX
        {
            return;
        }
        self.bodies.states[id] = Some(BodyState {
            position: record.position,
            prev_position: record.prev_position,
            orientation: record.orientation,
            velocity: record.velocity,
            angular_velocity: record.angular_velocity,
            inverse_mass: self.bodies.descriptors[id].inverse_mass,
            com: self.bodies.descriptors[id].com,
            sleeping: record.sleeping != 0,
            step: sequence,
        });
        self.bodies.covered[id] = sequence + 1;
    }
}