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}