Skip to main content

dynamis_world/
query_pool.rs

1use dynamis_abi::{MAX_HITS_PER_QUERY, QueryResultHeaderRecord, QueryResultRecord};
2use dynamis_model::{BodyHandle, SurfaceDesc};
3use std::collections::VecDeque;
4
5#[derive(Clone, Copy, Debug, PartialEq, Eq)]
6pub struct QueryHandle {
7    pub batch: u64,
8    pub index: u32,
9}
10
11#[derive(Clone, Copy, Debug, PartialEq)]
12pub struct QueryHit {
13    pub body: BodyHandle,
14    pub collider: u32,
15    pub distance: f32,
16    pub point: [f32; 3],
17    pub normal: [f32; 3],
18    pub triangle: Option<u32>,
19    pub surface: Option<SurfaceDesc>,
20    pub step: u64,
21}
22
23pub(crate) struct QueryOutcome {
24    pub hits: Vec<Vec<QueryHit>>,
25    pub overflow: Vec<bool>,
26}
27
28struct QueryBatch {
29    batch: u64,
30    step: u64,
31    width: usize,
32    outcome: Option<QueryOutcome>,
33}
34
35pub(crate) struct QueryPool {
36    batches: VecDeque<QueryBatch>,
37}
38
39impl QueryPool {
40    pub(crate) fn new() -> Self {
41        Self {
42            batches: VecDeque::new(),
43        }
44    }
45
46    pub(crate) fn submit(&mut self, batch: u64, step: u64, width: usize) {
47        self.batches.push_back(QueryBatch {
48            batch,
49            step,
50            width,
51            outcome: None,
52        });
53    }
54
55    pub(crate) fn hit(&self, handle: QueryHandle) -> Option<QueryHit> {
56        self.hits(handle).first().copied()
57    }
58
59    pub(crate) fn hits(&self, handle: QueryHandle) -> &[QueryHit] {
60        &self.outcome(handle).hits[handle.index as usize]
61    }
62
63    pub(crate) fn overflow(&self, handle: QueryHandle) -> bool {
64        self.outcome(handle).overflow[handle.index as usize]
65    }
66
67    fn outcome(&self, handle: QueryHandle) -> &QueryOutcome {
68        self.batch(handle)
69            .and_then(|batch| batch.outcome.as_ref())
70            .expect("query outcome read before it arrived")
71    }
72
73    pub(crate) fn is_current(&self, handle: QueryHandle) -> bool {
74        self.batch(handle).is_some()
75    }
76
77    pub(crate) fn is_ready(&self, handle: QueryHandle) -> bool {
78        self.batch(handle)
79            .is_some_and(|batch| batch.outcome.is_some())
80    }
81
82    fn batch(&self, handle: QueryHandle) -> Option<&QueryBatch> {
83        self.batches
84            .iter()
85            .find(|batch| batch.batch == handle.batch)
86    }
87
88    pub(crate) fn collect(
89        &mut self,
90        batch_id: u64,
91        bytes: &[u8],
92        surface: impl Fn(u32, u32) -> SurfaceDesc,
93    ) {
94        let batch = self
95            .batches
96            .iter_mut()
97            .find(|batch| batch.batch == batch_id)
98            .unwrap_or_else(|| panic!("no query batch is registered for batch {batch_id}"));
99        let step = batch.step;
100        let records = dynamis_abi::decode::<QueryResultRecord>(bytes);
101        let mut hits = vec![Vec::new(); batch.width];
102        let mut overflow = vec![false; batch.width];
103        for (index, result) in records.iter().take(batch.width).enumerate() {
104            let QueryResultHeaderRecord {
105                count,
106                overflow: spilled,
107                ..
108            } = result.header;
109            assert!(
110                count <= MAX_HITS_PER_QUERY,
111                "GPU query result exceeds the hit lane count"
112            );
113            hits[index] = result.hits[..count as usize]
114                .iter()
115                .map(|record| QueryHit {
116                    body: BodyHandle {
117                        id: record.body_id,
118                        generation: record.body_generation,
119                    },
120                    collider: record.collider_index,
121                    distance: record.distance,
122                    point: record.point,
123                    normal: record.normal,
124                    triangle: (record.triangle != dynamis_abi::NO_TRIANGLE)
125                        .then_some(record.triangle),
126                    surface: (record.surface != dynamis_abi::NO_SURFACE)
127                        .then(|| surface(record.collider_index, record.surface)),
128                    step,
129                })
130                .collect();
131            overflow[index] = spilled != 0;
132        }
133        batch.outcome = Some(QueryOutcome { hits, overflow });
134        while self
135            .batches
136            .front()
137            .is_some_and(|oldest| oldest.outcome.is_some() && oldest.step < step)
138        {
139            self.batches.pop_front();
140        }
141    }
142}