1use std::marker::PhantomData;
4
5use crate::{
6 runtime::{
7 anim_dyn::AnimationDyn, motion::RawMotionId, settled::Settled, typed::TypedAnimation,
8 },
9 timing::{Duration, Timing},
10 traits::{Animatable, Animation, AnimationState},
11 tween::Tween,
12};
13
14mod anim_dyn;
15mod command;
16mod motion;
17mod settled;
18mod typed;
19
20pub use command::AnimationCommand;
21pub use motion::Motion;
22
23#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
25pub enum RetainPolicy {
26 #[default]
28 Keep,
29 DropWhenSettled,
31}
32
33struct AnimationSlot {
34 generation: u64,
35 transition: Timing,
36 retain_policy: RetainPolicy,
37 animation: Option<Box<dyn AnimationDyn>>,
38 active: bool,
39 queued: bool,
40}
41
42#[derive(Default)]
60pub struct MotionRuntime {
61 slots: Vec<AnimationSlot>,
62 free: Vec<usize>,
63 active: Vec<RawMotionId>,
64 next_active: Vec<RawMotionId>,
65 active_count: usize,
66 motion_count: usize,
67 last_tick: Option<std::time::Instant>,
68}
69
70impl MotionRuntime {
71 #[must_use]
73 pub fn new() -> Self {
74 Self::default()
75 }
76
77 pub fn motion<T: Animatable>(&mut self, initial: T) -> Motion<T> {
79 self.motion_with(initial, Timing::new(200.0))
80 }
81
82 pub fn motion_with<T: Animatable>(&mut self, initial: T, timing: Timing) -> Motion<T> {
84 self.insert(Tween::with_timing(initial, timing), timing)
85 }
86
87 pub fn insert<T: Animatable>(
89 &mut self,
90 animation: impl Animation<T>,
91 transition: Timing,
92 ) -> Motion<T> {
93 self.insert_with_policy(animation, transition, RetainPolicy::Keep)
94 }
95
96 pub fn play_once<T: Animatable>(&mut self, animation: impl Animation<T>) -> Motion<T> {
98 self.insert_with_policy(animation, Timing::default(), RetainPolicy::DropWhenSettled)
99 }
100
101 pub fn insert_with_policy<T: Animatable>(
103 &mut self,
104 animation: impl Animation<T>,
105 transition: Timing,
106 retain_policy: RetainPolicy,
107 ) -> Motion<T> {
108 let mut animation = TypedAnimation::new(animation);
109 animation.compact();
110 let animation: Box<dyn AnimationDyn> = Box::new(animation);
111 let id = if let Some(slot_index) = self.free.pop() {
112 let slot = &mut self.slots[slot_index];
113 slot.transition = transition;
114 slot.retain_policy = retain_policy;
115 slot.animation = Some(animation);
116 slot.active = false;
117 slot.queued = false;
118 RawMotionId::new(slot_index, slot.generation)
119 } else {
120 let slot = self.slots.len();
121 self.slots.push(AnimationSlot {
122 generation: 0,
123 transition,
124 retain_policy,
125 animation: Some(animation),
126 active: false,
127 queued: false,
128 });
129 RawMotionId::new(slot, 0)
130 };
131
132 self.motion_count += 1;
133 let motion = Motion::new(id, PhantomData);
134 if self.animation(id).is_some_and(AnimationDyn::is_active) {
135 self.activate(id);
136 } else if retain_policy == RetainPolicy::DropWhenSettled
137 && self.animation(id).is_some_and(|animation| {
138 matches!(
139 animation.state(),
140 AnimationState::Completed | AnimationState::Canceled
141 )
142 })
143 {
144 self.remove(motion);
145 }
146 motion
147 }
148
149 pub fn tick(&mut self, delta: impl Into<Duration>) {
151 let delta = delta.into();
152 self.next_active.clear();
153
154 for index in 0..self.active.len() {
155 let id = self.active[index];
156 let Some(slot) = self.slots.get_mut(id.slot()) else {
157 continue;
158 };
159 if slot.generation != id.generation() {
160 continue;
161 }
162 slot.queued = false;
163 if !slot.active {
164 continue;
165 }
166 let Some(animation) = slot.animation.as_mut() else {
167 continue;
168 };
169
170 animation.advance(delta);
171 if animation.is_active() {
172 self.next_active.push(id);
173 slot.queued = true;
174 } else {
175 slot.active = false;
176 self.active_count = self.active_count.saturating_sub(1);
177 if slot.retain_policy == RetainPolicy::DropWhenSettled
178 && matches!(
179 animation.state(),
180 AnimationState::Completed | AnimationState::Canceled
181 )
182 {
183 slot.animation = None;
184 slot.generation = slot.generation.wrapping_add(1);
185 self.motion_count = self.motion_count.saturating_sub(1);
186 self.free.push(id.slot());
187 } else {
188 animation.compact();
189 }
190 }
191 }
192
193 std::mem::swap(&mut self.active, &mut self.next_active);
194 if self.active_count == 0 {
195 self.active.clear();
196 self.next_active.clear();
197 self.last_tick = None;
198 }
199 }
200
201 pub fn tick_at(&mut self, now: std::time::Instant) {
205 let delta = self.last_tick.map_or(std::time::Duration::ZERO, |last| {
206 now.saturating_duration_since(last)
207 });
208 self.last_tick = Some(now);
209 self.tick(delta);
210 }
211
212 #[must_use]
214 pub fn active_count(&self) -> usize {
215 self.active_count
216 }
217
218 #[must_use]
220 pub fn has_active(&self) -> bool {
221 self.active_count > 0
222 }
223
224 #[must_use]
226 pub fn motion_count(&self) -> usize {
227 self.motion_count
228 }
229
230 #[must_use]
232 pub fn slot_capacity(&self) -> usize {
233 self.slots.capacity()
234 }
235
236 pub fn shrink_to_fit(&mut self) {
238 while self
239 .slots
240 .last()
241 .is_some_and(|slot| slot.animation.is_none())
242 {
243 self.slots.pop();
244 }
245 self.free.retain(|slot| *slot < self.slots.len());
246 self.slots.shrink_to_fit();
247 self.free.shrink_to_fit();
248 self.active.shrink_to_fit();
249 self.next_active.shrink_to_fit();
250 }
251
252 #[must_use]
254 pub fn value<T: Animatable>(&self, motion: Motion<T>) -> Option<&T> {
255 self.animation(motion.id())?.value_any().downcast_ref::<T>()
256 }
257
258 #[must_use]
260 pub fn state<T: Animatable>(&self, motion: Motion<T>) -> Option<AnimationState> {
261 self.animation(motion.id()).map(AnimationDyn::state)
262 }
263
264 #[must_use]
266 pub fn is_active<T: Animatable>(&self, motion: Motion<T>) -> bool {
267 self.animation(motion.id())
268 .is_some_and(AnimationDyn::is_active)
269 }
270
271 pub fn transition_to<T: Animatable>(&mut self, motion: Motion<T>, target: T) -> bool {
275 if self
276 .animation_mut(motion.id())
277 .is_some_and(|animation| animation.retarget_any(&target))
278 {
279 self.activate(motion.id());
280 return true;
281 }
282
283 let Some(current) = self.value(motion).cloned() else {
284 return false;
285 };
286 let transition = self.slots[motion.id().slot()].transition;
287 self.replace(
288 motion.id(),
289 TypedAnimation::new(Tween::between(current, target, transition)),
290 );
291 true
292 }
293
294 pub fn play<T: Animatable>(&mut self, motion: Motion<T>, animation: impl Animation<T>) -> bool {
298 if self.value(motion).is_none() {
299 return false;
300 }
301
302 self.replace(motion.id(), TypedAnimation::new(animation));
303 true
304 }
305
306 pub fn command<T: Animatable>(&mut self, motion: Motion<T>, command: AnimationCommand) -> bool {
310 let (active, state) = {
311 let Some(animation) = self.animation_mut(motion.id()) else {
312 return false;
313 };
314 animation.command(command);
315 (animation.is_active(), animation.state())
316 };
317
318 if active {
319 self.activate(motion.id());
320 } else {
321 self.deactivate(motion.id());
322 if self.slots[motion.id().slot()].retain_policy == RetainPolicy::DropWhenSettled
323 && matches!(state, AnimationState::Completed | AnimationState::Canceled)
324 {
325 self.remove(motion);
326 } else if let Some(animation) = self.animation_mut(motion.id()) {
327 animation.compact();
328 }
329 }
330 true
331 }
332
333 pub fn remove<T: Animatable>(&mut self, motion: Motion<T>) -> bool {
337 let Some(slot) = self.slots.get_mut(motion.id().slot()) else {
338 return false;
339 };
340 if slot.generation != motion.id().generation() || slot.animation.is_none() {
341 return false;
342 }
343
344 let was_active = slot.active;
345 slot.animation = None;
346 slot.active = false;
347 slot.queued = false;
348 slot.generation = slot.generation.wrapping_add(1);
349 if was_active {
350 self.active_count = self.active_count.saturating_sub(1);
351 }
352 self.motion_count = self.motion_count.saturating_sub(1);
353 self.free.push(motion.id().slot());
354
355 let slot_idx = motion.id().slot();
356 let new_gen = slot.generation;
357 self.active
358 .retain(|id| id.slot() != slot_idx || id.generation() == new_gen);
359 self.next_active
360 .retain(|id| id.slot() != slot_idx || id.generation() == new_gen);
361
362 if self.active_count == 0 {
363 self.active.clear();
364 self.next_active.clear();
365 self.last_tick = None;
366 }
367 true
368 }
369
370 fn animation(&self, id: RawMotionId) -> Option<&dyn AnimationDyn> {
371 let slot = self.slots.get(id.slot())?;
372 if slot.generation != id.generation() {
373 return None;
374 }
375 slot.animation.as_deref()
376 }
377
378 fn animation_mut(&mut self, id: RawMotionId) -> Option<&mut (dyn AnimationDyn + '_)> {
379 let slot = self.slots.get_mut(id.slot())?;
380 if slot.generation != id.generation() {
381 return None;
382 }
383 match slot.animation.as_mut() {
384 Some(animation) => Some(animation.as_mut()),
385 None => None,
386 }
387 }
388
389 fn replace(&mut self, id: RawMotionId, animation: TypedAnimation<impl Animatable>) {
390 let Some(slot) = self.slots.get_mut(id.slot()) else {
391 return;
392 };
393 if slot.generation != id.generation() {
394 return;
395 }
396 let mut animation = animation;
397 animation.compact();
398 slot.animation = Some(Box::new(animation));
399 if slot
400 .animation
401 .as_deref()
402 .is_some_and(AnimationDyn::is_active)
403 {
404 self.activate(id);
405 } else {
406 self.deactivate(id);
407 }
408 }
409
410 fn activate(&mut self, id: RawMotionId) {
411 let Some(slot) = self.slots.get_mut(id.slot()) else {
412 return;
413 };
414 if slot.generation != id.generation() || slot.animation.is_none() {
415 return;
416 }
417 if self.active_count == 0 {
418 self.last_tick = None;
419 }
420 if !slot.active {
421 slot.active = true;
422 self.active_count += 1;
423 }
424 if !slot.queued {
425 slot.queued = true;
426 self.active.push(id);
427 }
428 }
429
430 fn deactivate(&mut self, id: RawMotionId) {
431 let Some(slot) = self.slots.get_mut(id.slot()) else {
432 return;
433 };
434 if slot.generation != id.generation() || !slot.active {
435 return;
436 }
437 slot.active = false;
438 self.active_count = self.active_count.saturating_sub(1);
439 if self.active_count == 0 {
440 for active in &self.active {
441 if let Some(slot) = self.slots.get_mut(active.slot())
442 && slot.generation == active.generation()
443 {
444 slot.queued = false;
445 }
446 }
447 self.active.clear();
448 self.next_active.clear();
449 self.last_tick = None;
450 }
451 }
452}