Skip to main content

dynamis_world/
query.rs

1use super::World;
2use crate::query_pool::{QueryHandle, QueryHit, QueryPool};
3use dynamis_abi::{MAX_HITS_PER_QUERY, QueryRecord};
4use dynamis_model::{QueryFilter, Shape};
5use std::mem::size_of;
6
7pub(crate) struct Queries {
8    pub(crate) pending: Vec<QueryRecord>,
9    pub(crate) next_batch: u64,
10    pub(crate) pool: QueryPool,
11}
12
13impl Queries {
14    pub(crate) fn new() -> Self {
15        Self {
16            pending: Vec::new(),
17            next_batch: 0,
18            pool: QueryPool::new(),
19        }
20    }
21}
22
23impl World {
24    pub fn ray_query(
25        &mut self,
26        origin: [f32; 3],
27        direction: [f32; 3],
28        max_t: f32,
29        filter: &QueryFilter,
30    ) -> QueryHandle {
31        assert!(max_t > 0.0, "raycast distance must be positive");
32        assert!(direction != [0.0; 3], "raycast direction must be non-zero");
33        self.submit_query(QueryRecord::ray(origin, direction, max_t, filter))
34    }
35
36    pub fn sphere_query(
37        &mut self,
38        center: [f32; 3],
39        radius: f32,
40        filter: &QueryFilter,
41    ) -> QueryHandle {
42        assert!(radius > 0.0, "sphere query radius must be positive");
43        self.submit_query(QueryRecord::sphere(center, radius, filter))
44    }
45
46    pub fn cuboid_query(
47        &mut self,
48        center: [f32; 3],
49        half_extents: [f32; 3],
50        filter: &QueryFilter,
51    ) -> QueryHandle {
52        assert!(
53            half_extents.iter().all(|extent| *extent > 0.0),
54            "cuboid query half extents must be strictly positive"
55        );
56        self.submit_query(QueryRecord::cuboid(center, half_extents, filter))
57    }
58
59    pub fn point_query(&mut self, origin: [f32; 3], filter: &QueryFilter) -> QueryHandle {
60        self.submit_query(QueryRecord::point(origin, filter))
61    }
62
63    pub fn overlap_query(
64        &mut self,
65        shape: &Shape,
66        orientation: [f32; 4],
67        position: [f32; 3],
68        filter: &QueryFilter,
69    ) -> QueryHandle {
70        self.assert_unit(orientation);
71        assert!(shape.is_convex(), "overlap queries require a convex shape");
72        self.submit_query(QueryRecord::convex(shape, orientation, position, filter))
73    }
74
75    pub fn sweep_query(
76        &mut self,
77        shape: &Shape,
78        orientation: [f32; 4],
79        start: [f32; 3],
80        direction: [f32; 3],
81        length: f32,
82        filter: &QueryFilter,
83    ) -> QueryHandle {
84        assert!(length > 0.0, "sweep length must be positive");
85        assert!(direction != [0.0; 3], "sweep direction must be non-zero");
86        self.assert_unit(orientation);
87        assert!(shape.is_convex(), "sweep queries require a convex shape");
88        self.submit_query(QueryRecord::sweep(
89            shape,
90            orientation,
91            start,
92            direction,
93            length,
94            filter,
95        ))
96    }
97
98    fn submit_query(&mut self, record: QueryRecord) -> QueryHandle {
99        assert!(
100            record.max_hits <= MAX_HITS_PER_QUERY,
101            "a query returns at most {MAX_HITS_PER_QUERY} hits"
102        );
103        let index = self.queries.pending.len() as u32;
104        self.queries.pending.push(record);
105        QueryHandle {
106            batch: self.queries.next_batch,
107            index,
108        }
109    }
110
111    pub fn query_hit(&self, handle: QueryHandle) -> Option<QueryHit> {
112        self.validate_query(handle);
113        self.queries.pool.hit(handle)
114    }
115
116    pub fn query_hits(&self, handle: QueryHandle) -> &[QueryHit] {
117        self.validate_query(handle);
118        self.queries.pool.hits(handle)
119    }
120
121    pub fn query_overflow(&self, handle: QueryHandle) -> bool {
122        self.validate_query(handle);
123        self.queries.pool.overflow(handle)
124    }
125
126    pub fn query_ready(&self, handle: QueryHandle) -> bool {
127        self.queries.pool.is_ready(handle)
128    }
129
130    pub fn resolve_queries(&mut self) {
131        if self.queries.pending.is_empty() {
132            return;
133        }
134        self.backend.gpu.assert_alive();
135        self.collect_readbacks();
136        let live = self.live();
137        self.apply_plan(&live);
138        self.flush_rows();
139        self.apply_pending_commands();
140        let step = self.clock.step;
141        let queue = self.backend.gpu.queue().clone();
142        let device = self.backend.gpu.device().clone();
143        self.backend
144            .streams
145            .state
146            .query_records
147            .write(&queue, bytemuck::cast_slice(&self.queries.pending));
148        let count = self.queries.pending.len();
149        let work = self.host_work();
150        let params = self.step_params(self.clock.sub_dt);
151        let frames = self.frames(&live, &work, params);
152        self.backend
153            .streams
154            .state
155            .params
156            .write(&queue, bytemuck::cast_slice(&[params]));
157        let batch = self.queries.next_batch;
158        self.queries.pool.submit(batch, step, count);
159        self.queries.next_batch += 1;
160        let mut encoder = dynamis_gpu::SubmissionEncoder::new(&device, "dynamis query resolve");
161        self.backend
162            .passes
163            .encode_queries(&mut encoder, &self.backend.streams, &frames);
164        let bytes = count as u64 * size_of::<dynamis_abi::QueryResultRecord>() as u64;
165        let arrived = self.backend.readback.queries.enqueue(
166            &mut encoder,
167            self.backend.streams.state.query_results.buffer(),
168            0,
169            bytes,
170            batch,
171        );
172        self.submit(encoder);
173        if let Some((batch, bytes)) = arrived {
174            self.collect_query_batch(batch, &bytes);
175        }
176        self.queries.pending.clear();
177        let live = self.live();
178        self.apply_plan(&live);
179    }
180
181    pub fn wait_query(&mut self, handle: QueryHandle) {
182        self.backend.gpu.assert_alive();
183        self.collect_readbacks();
184        if !self.queries.pool.is_ready(handle) {
185            let pending = self.backend.readback.queries.drain();
186            for (batch, bytes) in pending {
187                self.collect_query_batch(batch, &bytes);
188            }
189        }
190        self.validate_query(handle);
191    }
192
193    pub(crate) fn collect_query_batch(&mut self, batch: u64, bytes: &[u8]) {
194        let colliders = self.colliders.records();
195        let shapes = &self.shapes.pool;
196        self.queries.pool.collect(batch, bytes, |collider, index| {
197            shapes.source_surface(colliders[collider as usize].source, index)
198        });
199    }
200
201    fn validate_query(&self, handle: QueryHandle) {
202        assert!(
203            self.queries.pool.is_current(handle),
204            "query handle {handle:?} belongs to a batch that has been retired"
205        );
206        assert!(
207            self.queries.pool.is_ready(handle),
208            "query handle {handle:?} has no results yet; call resolve_queries(), step() or wait_query()"
209        );
210    }
211}