1use animato_core::Update;
4
5#[cfg(feature = "std")]
6use crate::recorder::AnimationRecorder;
7
8#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
12pub struct AnimationId(u64);
13
14struct Slot {
15 id: AnimationId,
16 animation: Box<dyn Update + Send>,
17 remove: bool,
18 #[cfg(feature = "std")]
19 recorder: Option<RecordedSampler>,
20}
21
22#[cfg(feature = "std")]
23struct RecordedSampler {
24 label: String,
25 sample: Box<dyn Fn() -> f64 + Send + 'static>,
26}
27
28#[cfg(feature = "std")]
29impl std::fmt::Debug for RecordedSampler {
30 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
31 f.debug_struct("RecordedSampler")
32 .field("label", &self.label)
33 .finish()
34 }
35}
36
37impl std::fmt::Debug for Slot {
38 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
39 f.debug_struct("Slot")
40 .field("id", &self.id)
41 .field("remove", &self.remove)
42 .finish()
43 }
44}
45
46#[derive(Debug, Default)]
70pub struct AnimationDriver {
71 slots: Vec<Slot>,
72 next_id: u64,
73}
74
75impl AnimationDriver {
76 pub fn new() -> Self {
78 Self {
79 slots: Vec::new(),
80 next_id: 0,
81 }
82 }
83
84 pub fn add<A: Update + Send + 'static>(&mut self, anim: A) -> AnimationId {
89 let id = AnimationId(self.next_id);
90 self.next_id += 1;
91 self.slots.push(Slot {
92 id,
93 animation: Box::new(anim),
94 remove: false,
95 #[cfg(feature = "std")]
96 recorder: None,
97 });
98 id
99 }
100
101 #[cfg(feature = "std")]
103 pub fn add_recorded<A, F>(
104 &mut self,
105 label: impl Into<String>,
106 anim: A,
107 sampler: F,
108 ) -> AnimationId
109 where
110 A: Update + Send + 'static,
111 F: Fn() -> f64 + Send + 'static,
112 {
113 let id = AnimationId(self.next_id);
114 self.next_id += 1;
115 self.slots.push(Slot {
116 id,
117 animation: Box::new(anim),
118 remove: false,
119 recorder: Some(RecordedSampler {
120 label: label.into(),
121 sample: Box::new(sampler),
122 }),
123 });
124 id
125 }
126
127 pub fn tick(&mut self, dt: f32) {
132 for slot in self.slots.iter_mut() {
133 if slot.remove {
134 continue;
135 }
136 let still_running = slot.animation.update(dt);
137 if !still_running {
138 slot.remove = true;
139 }
140 }
141 self.slots.retain(|s| !s.remove);
143 }
144
145 #[cfg(feature = "std")]
147 pub fn tick_recorded(&mut self, dt: f32, time: f32, recorder: &mut AnimationRecorder) {
148 for slot in self.slots.iter_mut() {
149 if slot.remove {
150 continue;
151 }
152 let still_running = slot.animation.update(dt);
153 if let Some(recorded) = &slot.recorder {
154 recorder.record(&recorded.label, time, (recorded.sample)());
155 }
156 if !still_running {
157 slot.remove = true;
158 }
159 }
160 self.slots.retain(|s| !s.remove);
161 }
162
163 pub fn cancel(&mut self, id: AnimationId) {
168 self.slots.retain(|s| s.id != id);
169 }
170
171 pub fn cancel_all(&mut self) {
173 self.slots.clear();
174 }
175
176 pub fn active_count(&self) -> usize {
178 self.slots.len()
179 }
180
181 pub fn is_active(&self, id: AnimationId) -> bool {
183 self.slots.iter().any(|s| s.id == id)
184 }
185}
186
187#[cfg(test)]
192mod tests {
193 use super::*;
194 use animato_core::Easing;
195 use animato_tween::Tween;
196
197 #[test]
198 fn auto_removes_completed() {
199 let mut driver = AnimationDriver::new();
200 let id = driver.add(Tween::new(0.0_f32, 1.0).duration(1.0).build());
201 assert!(driver.is_active(id));
202 driver.tick(2.0); assert!(!driver.is_active(id));
204 assert_eq!(driver.active_count(), 0);
205 }
206
207 #[test]
208 fn cancel_removes_mid_animation() {
209 let mut driver = AnimationDriver::new();
210 let id = driver.add(Tween::new(0.0_f32, 1.0).duration(10.0).build());
211 driver.tick(1.0);
212 assert!(driver.is_active(id));
213 driver.cancel(id);
214 assert!(!driver.is_active(id));
215 }
216
217 #[test]
218 fn cancel_noop_on_missing_id() {
219 let mut driver = AnimationDriver::new();
220 let id = driver.add(Tween::new(0.0_f32, 1.0).duration(1.0).build());
221 driver.tick(2.0); driver.cancel(id); assert_eq!(driver.active_count(), 0);
224 }
225
226 #[test]
227 fn active_count_tracks_correctly() {
228 let mut driver = AnimationDriver::new();
229 let _a = driver.add(Tween::new(0.0_f32, 1.0).duration(1.0).build());
230 let _b = driver.add(Tween::new(0.0_f32, 1.0).duration(2.0).build());
231 let _c = driver.add(Tween::new(0.0_f32, 1.0).duration(3.0).build());
232 assert_eq!(driver.active_count(), 3);
233
234 driver.tick(1.5); assert_eq!(driver.active_count(), 2);
236
237 driver.tick(1.0); assert_eq!(driver.active_count(), 1);
239 }
240
241 #[test]
242 fn cancel_all_clears_everything() {
243 let mut driver = AnimationDriver::new();
244 for _ in 0..10 {
245 driver.add(Tween::new(0.0_f32, 1.0).duration(5.0).build());
246 }
247 assert_eq!(driver.active_count(), 10);
248 driver.cancel_all();
249 assert_eq!(driver.active_count(), 0);
250 }
251
252 #[test]
253 fn multiple_concurrent_animations_tick_independently() {
254 let mut driver = AnimationDriver::new();
255 let slow = driver.add(Tween::new(0.0_f32, 1.0).duration(2.0).build());
256 let fast = driver.add(
257 Tween::new(0.0_f32, 1.0)
258 .duration(0.5)
259 .easing(Easing::Linear)
260 .build(),
261 );
262
263 driver.tick(1.0); assert!(!driver.is_active(fast));
265 assert!(driver.is_active(slow));
266
267 driver.tick(2.0); assert!(!driver.is_active(slow));
269 }
270
271 #[test]
272 fn animation_id_is_unique() {
273 let mut driver = AnimationDriver::new();
274 let ids: Vec<_> = (0..100)
275 .map(|_| driver.add(Tween::new(0.0_f32, 1.0).duration(1.0).build()))
276 .collect();
277 let unique: std::collections::HashSet<_> = ids.iter().collect();
278 assert_eq!(unique.len(), 100);
279 }
280}