use super::World;
use crate::query_pool::{QueryHandle, QueryHit, QueryPool};
use dynamis_abi::{MAX_HITS_PER_QUERY, QueryRecord};
use dynamis_model::{QueryFilter, Shape};
use std::mem::size_of;
pub(crate) struct Queries {
pub(crate) pending: Vec<QueryRecord>,
pub(crate) next_batch: u64,
pub(crate) pool: QueryPool,
}
impl Queries {
pub(crate) fn new() -> Self {
Self {
pending: Vec::new(),
next_batch: 0,
pool: QueryPool::new(),
}
}
}
impl World {
pub fn ray_query(
&mut self,
origin: [f32; 3],
direction: [f32; 3],
max_t: f32,
filter: &QueryFilter,
) -> QueryHandle {
assert!(max_t > 0.0, "raycast distance must be positive");
assert!(direction != [0.0; 3], "raycast direction must be non-zero");
self.submit_query(QueryRecord::ray(origin, direction, max_t, filter))
}
pub fn sphere_query(
&mut self,
center: [f32; 3],
radius: f32,
filter: &QueryFilter,
) -> QueryHandle {
assert!(radius > 0.0, "sphere query radius must be positive");
self.submit_query(QueryRecord::sphere(center, radius, filter))
}
pub fn cuboid_query(
&mut self,
center: [f32; 3],
half_extents: [f32; 3],
filter: &QueryFilter,
) -> QueryHandle {
assert!(
half_extents.iter().all(|extent| *extent > 0.0),
"cuboid query half extents must be strictly positive"
);
self.submit_query(QueryRecord::cuboid(center, half_extents, filter))
}
pub fn point_query(&mut self, origin: [f32; 3], filter: &QueryFilter) -> QueryHandle {
self.submit_query(QueryRecord::point(origin, filter))
}
pub fn overlap_query(
&mut self,
shape: &Shape,
orientation: [f32; 4],
position: [f32; 3],
filter: &QueryFilter,
) -> QueryHandle {
self.assert_unit(orientation);
assert!(shape.is_convex(), "overlap queries require a convex shape");
self.submit_query(QueryRecord::convex(shape, orientation, position, filter))
}
pub fn sweep_query(
&mut self,
shape: &Shape,
orientation: [f32; 4],
start: [f32; 3],
direction: [f32; 3],
length: f32,
filter: &QueryFilter,
) -> QueryHandle {
assert!(length > 0.0, "sweep length must be positive");
assert!(direction != [0.0; 3], "sweep direction must be non-zero");
self.assert_unit(orientation);
assert!(shape.is_convex(), "sweep queries require a convex shape");
self.submit_query(QueryRecord::sweep(
shape,
orientation,
start,
direction,
length,
filter,
))
}
fn submit_query(&mut self, record: QueryRecord) -> QueryHandle {
assert!(
record.max_hits <= MAX_HITS_PER_QUERY,
"a query returns at most {MAX_HITS_PER_QUERY} hits"
);
let index = self.queries.pending.len() as u32;
self.queries.pending.push(record);
QueryHandle {
batch: self.queries.next_batch,
index,
}
}
pub fn query_hit(&self, handle: QueryHandle) -> Option<QueryHit> {
self.validate_query(handle);
self.queries.pool.hit(handle)
}
pub fn query_hits(&self, handle: QueryHandle) -> &[QueryHit] {
self.validate_query(handle);
self.queries.pool.hits(handle)
}
pub fn query_overflow(&self, handle: QueryHandle) -> bool {
self.validate_query(handle);
self.queries.pool.overflow(handle)
}
pub fn query_ready(&self, handle: QueryHandle) -> bool {
self.queries.pool.is_ready(handle)
}
pub fn resolve_queries(&mut self) {
if self.queries.pending.is_empty() {
return;
}
self.backend.gpu.assert_alive();
self.collect_readbacks();
let live = self.live();
self.apply_plan(&live);
self.flush_rows();
self.apply_pending_commands();
let step = self.clock.step;
let queue = self.backend.gpu.queue().clone();
let device = self.backend.gpu.device().clone();
self.backend
.streams
.state
.query_records
.write(&queue, bytemuck::cast_slice(&self.queries.pending));
let count = self.queries.pending.len();
let work = self.host_work();
let params = self.step_params(self.clock.sub_dt);
let frames = self.frames(&live, &work, params);
self.backend
.streams
.state
.params
.write(&queue, bytemuck::cast_slice(&[params]));
let batch = self.queries.next_batch;
self.queries.pool.submit(batch, step, count);
self.queries.next_batch += 1;
let mut encoder = dynamis_gpu::SubmissionEncoder::new(&device, "dynamis query resolve");
self.backend
.passes
.encode_queries(&mut encoder, &self.backend.streams, &frames);
let bytes = count as u64 * size_of::<dynamis_abi::QueryResultRecord>() as u64;
let arrived = self.backend.readback.queries.enqueue(
&mut encoder,
self.backend.streams.state.query_results.buffer(),
0,
bytes,
batch,
);
self.submit(encoder);
if let Some((batch, bytes)) = arrived {
self.collect_query_batch(batch, &bytes);
}
self.queries.pending.clear();
let live = self.live();
self.apply_plan(&live);
}
pub fn wait_query(&mut self, handle: QueryHandle) {
self.backend.gpu.assert_alive();
self.collect_readbacks();
if !self.queries.pool.is_ready(handle) {
let pending = self.backend.readback.queries.drain();
for (batch, bytes) in pending {
self.collect_query_batch(batch, &bytes);
}
}
self.validate_query(handle);
}
pub(crate) fn collect_query_batch(&mut self, batch: u64, bytes: &[u8]) {
let colliders = self.colliders.records();
let shapes = &self.shapes.pool;
self.queries.pool.collect(batch, bytes, |collider, index| {
shapes.source_surface(colliders[collider as usize].source, index)
});
}
fn validate_query(&self, handle: QueryHandle) {
assert!(
self.queries.pool.is_current(handle),
"query handle {handle:?} belongs to a batch that has been retired"
);
assert!(
self.queries.pool.is_ready(handle),
"query handle {handle:?} has no results yet; call resolve_queries(), step() or wait_query()"
);
}
}