1use super::World;
2use dynamis_abi::{BodyStateRecord, NO_SLOT};
3use dynamis_gpu::SubmissionEncoder;
4use dynamis_model::{BodyHandle, BodyState};
5use std::mem::size_of;
6
7pub(crate) struct View {
8 observed: Vec<u32>,
9 slot_of: Vec<u32>,
10 dirty: bool,
11 epoch: Option<u64>,
12}
13
14impl View {
15 pub(crate) const fn new() -> Self {
16 Self {
17 observed: Vec::new(),
18 slot_of: Vec::new(),
19 dirty: false,
20 epoch: None,
21 }
22 }
23
24 pub(crate) fn len(&self) -> u32 {
25 self.observed.len() as u32
26 }
27
28 pub(crate) fn ids(&self) -> &[u32] {
29 &self.observed
30 }
31
32 pub(crate) fn observe(&mut self, id: u32) {
33 if self.slot_of.len() <= id as usize {
34 self.slot_of.resize(id as usize + 1, NO_SLOT);
35 }
36 if self.slot_of[id as usize] != NO_SLOT {
37 return;
38 }
39 self.slot_of[id as usize] = self.observed.len() as u32;
40 self.observed.push(id);
41 self.dirty = true;
42 }
43
44 pub(crate) fn forget(&mut self, id: u32) {
45 let Some(slot) = self
46 .slot_of
47 .get(id as usize)
48 .copied()
49 .filter(|slot| *slot != NO_SLOT)
50 else {
51 return;
52 };
53 self.slot_of[id as usize] = NO_SLOT;
54 self.observed.swap_remove(slot as usize);
55 if let Some(moved) = self.observed.get(slot as usize) {
56 self.slot_of[*moved as usize] = slot;
57 }
58 self.dirty = true;
59 }
60
61 fn take_dirty(&mut self) -> bool {
62 std::mem::take(&mut self.dirty)
63 }
64}
65
66impl World {
67 pub fn poll(&mut self) {
68 self.backend.gpu.assert_alive();
69 self.collect_readbacks();
70 }
71
72 pub fn wait(&mut self) {
73 self.backend.gpu.assert_alive();
74 self.resolve_queries();
75 loop {
76 self.drain_readbacks();
77 if self.states_current() {
78 return;
79 }
80 self.submit_states();
81 }
82 }
83
84 pub fn try_state(&mut self, handle: BodyHandle) -> Option<BodyState> {
85 self.validate(handle);
86 self.view.observe(handle.id);
87 self.view.epoch?;
88 self.bodies.states[handle.id as usize]
89 }
90
91 pub(crate) fn states_current(&self) -> bool {
92 self.bodies
93 .alive
94 .iter()
95 .all(|handle| self.body_current(handle.id))
96 }
97
98 pub(crate) fn body_current(&self, id: u32) -> bool {
99 self.bodies.covered[id as usize] == self.clock.step
100 }
101
102 fn completed_step(&self) -> Option<u64> {
103 self.clock.step.checked_sub(1)
104 }
105
106 pub(crate) fn flush_observed(&mut self) {
107 if !self.view.take_dirty() {
108 return;
109 }
110 let ids = self.view.ids();
111 if ids.is_empty() {
112 return;
113 }
114 self.backend
115 .streams
116 .state
117 .observed_ids
118 .write(self.backend.gpu.queue(), bytemuck::cast_slice(ids));
119 }
120
121 pub(crate) fn copy_observations(&mut self, encoder: &mut SubmissionEncoder, step: u64) {
122 let count = self.view.len();
123 if count == 0 {
124 return;
125 }
126 let stride = self.backend.streams.state.observed_states.stride();
127 let bytes = count as u64 * stride;
128 let displaced = self.backend.readback.observations.enqueue(
129 encoder,
130 self.backend.streams.state.observed_states.buffer(),
131 0,
132 bytes,
133 step,
134 );
135 if let Some((sequence, bytes)) = displaced {
136 self.consume_observations(sequence, &bytes);
137 }
138 }
139
140 pub(crate) fn consume_observations(&mut self, sequence: u64, bytes: &[u8]) {
141 let (records, remainder) = bytes.as_chunks::<{ size_of::<BodyStateRecord>() }>();
142 assert!(
143 remainder.is_empty(),
144 "an observation readback must be a whole number of records"
145 );
146 for chunk in records {
147 self.accept_body(sequence, bytemuck::pod_read_unaligned(chunk));
148 }
149 self.view.epoch = Some(sequence);
150 }
151
152 fn submit_states(&mut self) {
153 let step = self
154 .completed_step()
155 .expect("a body state snapshot requires a completed step");
156 let bytes = u64::from(self.bodies.device_count) * size_of::<BodyStateRecord>() as u64;
157 if bytes == 0 {
158 self.view.epoch = Some(step);
159 return;
160 }
161 let device = self.backend.gpu.device().clone();
162 self.backend
163 .readback
164 .open_states(&device, self.backend.streams.state.body_states.slots());
165 let mut encoder = SubmissionEncoder::new(&device, "dynamis body state readback");
166 let displaced = self
167 .backend
168 .readback
169 .states
170 .as_mut()
171 .expect("a body state snapshot opens its readback ring")
172 .enqueue(
173 &mut encoder,
174 self.backend.streams.state.body_states.buffer(),
175 0,
176 bytes,
177 step,
178 );
179 self.submit(encoder);
180 if let Some((sequence, bytes)) = displaced {
181 self.consume_states(sequence, &bytes);
182 }
183 }
184
185 pub(crate) fn consume_states(&mut self, sequence: u64, bytes: &[u8]) {
186 let (records, remainder) = bytes.as_chunks::<{ size_of::<BodyStateRecord>() }>();
187 assert!(
188 remainder.is_empty(),
189 "a body state readback must be a whole number of records"
190 );
191 for chunk in records {
192 self.accept_body(sequence, bytemuck::pod_read_unaligned(chunk));
193 }
194 self.view.epoch = Some(sequence);
195 }
196
197 fn accept_body(&mut self, sequence: u64, record: BodyStateRecord) {
198 let id = record.body_id as usize;
199 assert!(
200 id < self.bodies.ids.len(),
201 "a state readback returned an out-of-range body id"
202 );
203 if record.generation != self.bodies.ids.generation(record.body_id)
204 || self.bodies.index_of[id] == u32::MAX
205 {
206 return;
207 }
208 self.bodies.states[id] = Some(BodyState {
209 position: record.position,
210 prev_position: record.prev_position,
211 orientation: record.orientation,
212 velocity: record.velocity,
213 angular_velocity: record.angular_velocity,
214 inverse_mass: self.bodies.descriptors[id].inverse_mass,
215 com: self.bodies.descriptors[id].com,
216 sleeping: record.sleeping != 0,
217 step: sequence,
218 });
219 self.bodies.covered[id] = sequence + 1;
220 }
221}