1use animato_core::{AnimationIntrospection, Inspectable, Update};
4
5#[cfg(feature = "std")]
6use crate::recorder::AnimationRecorder;
7
8#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
12pub struct AnimationId(u64);
13
14#[derive(Clone, Debug, PartialEq)]
16pub struct DriverSnapshot {
17 pub id: AnimationId,
19 pub label: Option<String>,
21 pub introspection: AnimationIntrospection,
23}
24
25#[derive(Clone, Debug, PartialEq)]
27pub struct AnimationUpdateCost {
28 pub id: AnimationId,
30 pub label: Option<String>,
32 pub update_time_ms: f32,
34}
35
36#[derive(Clone, Debug, Default, PartialEq)]
38pub struct DriverFrameProfile {
39 pub dt: f32,
41 pub total_update_time_ms: f32,
43 pub animation_costs: Vec<AnimationUpdateCost>,
45}
46
47enum SlotAnimation {
48 Basic(Box<dyn Update + Send>),
49 Inspectable(Box<dyn Inspectable + Send>),
50}
51
52impl SlotAnimation {
53 fn update(&mut self, dt: f32) -> bool {
54 match self {
55 Self::Basic(animation) => animation.update(dt),
56 Self::Inspectable(animation) => animation.update(dt),
57 }
58 }
59
60 fn introspect(&self) -> Option<AnimationIntrospection> {
61 match self {
62 Self::Basic(_) => None,
63 Self::Inspectable(animation) => Some(animation.introspect()),
64 }
65 }
66}
67
68impl std::fmt::Debug for SlotAnimation {
69 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
70 match self {
71 Self::Basic(_) => f.write_str("Basic(..)"),
72 Self::Inspectable(_) => f.write_str("Inspectable(..)"),
73 }
74 }
75}
76
77struct Slot {
78 id: AnimationId,
79 label: Option<String>,
80 animation: SlotAnimation,
81 remove: bool,
82 #[cfg(feature = "std")]
83 recorder: Option<RecordedSampler>,
84}
85
86#[cfg(feature = "std")]
87struct RecordedSampler {
88 label: String,
89 sample: Box<dyn Fn() -> f64 + Send + 'static>,
90}
91
92#[cfg(feature = "std")]
93impl std::fmt::Debug for RecordedSampler {
94 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
95 f.debug_struct("RecordedSampler")
96 .field("label", &self.label)
97 .finish()
98 }
99}
100
101impl std::fmt::Debug for Slot {
102 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
103 f.debug_struct("Slot")
104 .field("id", &self.id)
105 .field("label", &self.label)
106 .field("remove", &self.remove)
107 .finish()
108 }
109}
110
111#[derive(Debug, Default)]
135pub struct AnimationDriver {
136 slots: Vec<Slot>,
137 next_id: u64,
138 completed_count: usize,
139}
140
141impl AnimationDriver {
142 pub fn new() -> Self {
144 Self {
145 slots: Vec::new(),
146 next_id: 0,
147 completed_count: 0,
148 }
149 }
150
151 pub fn add<A: Update + Send + 'static>(&mut self, anim: A) -> AnimationId {
156 let id = AnimationId(self.next_id);
157 self.next_id += 1;
158 self.slots.push(Slot {
159 id,
160 label: None,
161 animation: SlotAnimation::Basic(Box::new(anim)),
162 remove: false,
163 #[cfg(feature = "std")]
164 recorder: None,
165 });
166 id
167 }
168
169 pub fn add_inspectable<A>(&mut self, label: impl Into<String>, anim: A) -> AnimationId
176 where
177 A: Inspectable + Send + 'static,
178 {
179 let id = AnimationId(self.next_id);
180 self.next_id += 1;
181 self.slots.push(Slot {
182 id,
183 label: Some(label.into()),
184 animation: SlotAnimation::Inspectable(Box::new(anim)),
185 remove: false,
186 #[cfg(feature = "std")]
187 recorder: None,
188 });
189 id
190 }
191
192 #[cfg(feature = "std")]
194 pub fn add_recorded<A, F>(
195 &mut self,
196 label: impl Into<String>,
197 anim: A,
198 sampler: F,
199 ) -> AnimationId
200 where
201 A: Update + Send + 'static,
202 F: Fn() -> f64 + Send + 'static,
203 {
204 let label = label.into();
205 let id = AnimationId(self.next_id);
206 self.next_id += 1;
207 self.slots.push(Slot {
208 id,
209 label: Some(label.clone()),
210 animation: SlotAnimation::Basic(Box::new(anim)),
211 remove: false,
212 recorder: Some(RecordedSampler {
213 label,
214 sample: Box::new(sampler),
215 }),
216 });
217 id
218 }
219
220 pub fn tick(&mut self, dt: f32) {
225 for slot in self.slots.iter_mut() {
226 if slot.remove {
227 continue;
228 }
229 let still_running = slot.animation.update(dt);
230 if !still_running {
231 slot.remove = true;
232 }
233 }
234 self.drain_completed();
235 }
236
237 #[cfg(feature = "std")]
239 pub fn tick_recorded(&mut self, dt: f32, time: f32, recorder: &mut AnimationRecorder) {
240 for slot in self.slots.iter_mut() {
241 if slot.remove {
242 continue;
243 }
244 let still_running = slot.animation.update(dt);
245 if let Some(recorded) = &slot.recorder {
246 recorder.record(&recorded.label, time, (recorded.sample)());
247 }
248 if !still_running {
249 slot.remove = true;
250 }
251 }
252 self.drain_completed();
253 }
254
255 pub fn tick_profiled(&mut self, dt: f32) -> DriverFrameProfile {
257 let dt = dt.max(0.0);
258 let frame_start = std::time::Instant::now();
259 let mut animation_costs = Vec::with_capacity(self.slots.len());
260
261 for slot in self.slots.iter_mut() {
262 if slot.remove {
263 continue;
264 }
265 let animation_start = std::time::Instant::now();
266 let still_running = slot.animation.update(dt);
267 let update_time_ms = animation_start.elapsed().as_secs_f32() * 1000.0;
268 animation_costs.push(AnimationUpdateCost {
269 id: slot.id,
270 label: slot.label.clone(),
271 update_time_ms,
272 });
273 if !still_running {
274 slot.remove = true;
275 }
276 }
277
278 self.drain_completed();
279 DriverFrameProfile {
280 dt,
281 total_update_time_ms: frame_start.elapsed().as_secs_f32() * 1000.0,
282 animation_costs,
283 }
284 }
285
286 pub fn cancel(&mut self, id: AnimationId) {
291 self.slots.retain(|s| s.id != id);
292 }
293
294 pub fn cancel_all(&mut self) {
296 self.slots.clear();
297 }
298
299 pub fn active_count(&self) -> usize {
301 self.slots.len()
302 }
303
304 pub fn completed_count(&self) -> usize {
306 self.completed_count
307 }
308
309 pub fn snapshots(&self) -> Vec<DriverSnapshot> {
311 let mut snapshots = Vec::new();
312 self.snapshots_into(&mut snapshots);
313 snapshots
314 }
315
316 pub fn snapshots_into(&self, out: &mut Vec<DriverSnapshot>) {
318 out.clear();
319 out.extend(self.slots.iter().filter_map(|slot| {
320 slot.animation
321 .introspect()
322 .map(|introspection| DriverSnapshot {
323 id: slot.id,
324 label: slot.label.clone(),
325 introspection,
326 })
327 }));
328 }
329
330 pub fn is_active(&self, id: AnimationId) -> bool {
332 self.slots.iter().any(|s| s.id == id)
333 }
334
335 fn drain_completed(&mut self) {
336 let completed = self.slots.iter().filter(|slot| slot.remove).count();
337 self.completed_count = self.completed_count.saturating_add(completed);
338 self.slots.retain(|slot| !slot.remove);
339 }
340}
341
342#[cfg(test)]
347mod tests {
348 use super::*;
349 use animato_core::Easing;
350 use animato_tween::Tween;
351
352 #[test]
353 fn auto_removes_completed() {
354 let mut driver = AnimationDriver::new();
355 let id = driver.add(Tween::new(0.0_f32, 1.0).duration(1.0).build());
356 assert!(driver.is_active(id));
357 driver.tick(2.0); assert!(!driver.is_active(id));
359 assert_eq!(driver.active_count(), 0);
360 }
361
362 #[test]
363 fn cancel_removes_mid_animation() {
364 let mut driver = AnimationDriver::new();
365 let id = driver.add(Tween::new(0.0_f32, 1.0).duration(10.0).build());
366 driver.tick(1.0);
367 assert!(driver.is_active(id));
368 driver.cancel(id);
369 assert!(!driver.is_active(id));
370 }
371
372 #[test]
373 fn cancel_noop_on_missing_id() {
374 let mut driver = AnimationDriver::new();
375 let id = driver.add(Tween::new(0.0_f32, 1.0).duration(1.0).build());
376 driver.tick(2.0); driver.cancel(id); assert_eq!(driver.active_count(), 0);
379 }
380
381 #[test]
382 fn active_count_tracks_correctly() {
383 let mut driver = AnimationDriver::new();
384 let _a = driver.add(Tween::new(0.0_f32, 1.0).duration(1.0).build());
385 let _b = driver.add(Tween::new(0.0_f32, 1.0).duration(2.0).build());
386 let _c = driver.add(Tween::new(0.0_f32, 1.0).duration(3.0).build());
387 assert_eq!(driver.active_count(), 3);
388
389 driver.tick(1.5); assert_eq!(driver.active_count(), 2);
391
392 driver.tick(1.0); assert_eq!(driver.active_count(), 1);
394 }
395
396 #[test]
397 fn cancel_all_clears_everything() {
398 let mut driver = AnimationDriver::new();
399 for _ in 0..10 {
400 driver.add(Tween::new(0.0_f32, 1.0).duration(5.0).build());
401 }
402 assert_eq!(driver.active_count(), 10);
403 driver.cancel_all();
404 assert_eq!(driver.active_count(), 0);
405 }
406
407 #[test]
408 fn multiple_concurrent_animations_tick_independently() {
409 let mut driver = AnimationDriver::new();
410 let slow = driver.add(Tween::new(0.0_f32, 1.0).duration(2.0).build());
411 let fast = driver.add(
412 Tween::new(0.0_f32, 1.0)
413 .duration(0.5)
414 .easing(Easing::Linear)
415 .build(),
416 );
417
418 driver.tick(1.0); assert!(!driver.is_active(fast));
420 assert!(driver.is_active(slow));
421
422 driver.tick(2.0); assert!(!driver.is_active(slow));
424 }
425
426 #[test]
427 fn animation_id_is_unique() {
428 let mut driver = AnimationDriver::new();
429 let ids: Vec<_> = (0..100)
430 .map(|_| driver.add(Tween::new(0.0_f32, 1.0).duration(1.0).build()))
431 .collect();
432 let unique: std::collections::HashSet<_> = ids.iter().collect();
433 assert_eq!(unique.len(), 100);
434 }
435}