1use super::World;
2use crate::world::query_pool::{QueryHandle, QueryHit, QueryPool};
3use dynamis_layout::{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 flush_queries(&mut self) {
127 if self.queries.pending.is_empty() {
128 self.apply_plan();
129 return;
130 }
131 self.backend.gpu.assert_alive();
132 self.collect_readbacks();
133 self.apply_plan();
134 self.flush_rows();
135 self.apply_pending_commands();
136 let step = self.clock.step;
137 let queue = self.backend.gpu.queue().clone();
138 let device = self.backend.gpu.device().clone();
139 self.backend
140 .buffers
141 .queries
142 .records
143 .write(&queue, bytemuck::cast_slice(&self.queries.pending));
144 let params = dynamis_layout::StepParamsRecord::new(
145 &self.config,
146 self.clock.sub_dt,
147 self.bodies.dynamic_count as u32,
148 self.bodies.alive.len() as u32,
149 self.constraints.alive.len() as u32,
150 dynamis_layout::RowStreams {
151 edit_runs: self.bodies.last_edits,
152 body_moves: self.bodies.last_moves,
153 constraint_moves: self.constraints.last_moves,
154 },
155 self.event_slot_of(step),
156 );
157 self.backend
158 .buffers
159 .params
160 .write(&queue, bytemuck::cast_slice(&[params]));
161 let count = self.queries.pending.len();
162 let batch = self.queries.next_batch;
163 self.queries.pool.submit(batch, step, count);
164 self.queries.next_batch += 1;
165 let frame = crate::dynamics::FrameParams {
166 dynamic_count: self.bodies.dynamic_count as u32,
167 body_count: self.bodies.alive.len() as u32,
168 solve_iterations: self.config.solve_iterations,
169 island_rounds: self.island_rounds(),
170 query_count: count as u32,
171 constraint_count: self.constraints.alive.len() as u32,
172 body_move_count: self.bodies.last_moves,
173 constraint_move_count: self.constraints.last_moves,
174 edit_run_count: self.bodies.last_edits,
175 };
176 let mut encoder = dynamis_gpu::SubmissionEncoder::new(&device, "dynamis query flush");
177 self.backend
178 .pipeline
179 .encode_queries(&mut encoder, &self.backend.buffers, &frame);
180 let bytes = count as u64 * size_of::<dynamis_layout::QueryResultRecord>() as u64;
181 let arrived = self.backend.buffers.readback.queries.enqueue(
182 &mut encoder,
183 self.backend.buffers.queries.results.buffer(),
184 0,
185 bytes,
186 batch,
187 );
188 encoder.submit(&queue);
189 if let Some((batch, bytes)) = arrived {
190 self.queries.pool.collect(batch, &bytes);
191 }
192 for (batch, bytes) in self.backend.buffers.readback.queries.drain() {
193 self.queries.pool.collect(batch, &bytes);
194 }
195 self.queries.pending.clear();
196 self.apply_plan();
197 }
198
199 fn validate_query(&self, handle: QueryHandle) {
200 assert!(
201 self.queries.pool.is_current(handle),
202 "query handle {handle:?} belongs to a batch that has been retired"
203 );
204 assert!(
205 self.queries.pool.is_ready(handle),
206 "query handle {handle:?} has no results yet; call poll() or wait()"
207 );
208 }
209}