Skip to main content

aura_anim_iced/runtime/
tick.rs

1use super::{AnimationHandle, AnimationPlaybackState};
2use crate::runtime::{
3    registry::AnimationRegistry,
4    target::{AnimationTargetId, TargetedPropertySnapshot},
5};
6use crate::{property::PropertySnapshot, timing::Duration};
7
8/// Output produced by one runtime tick.
9#[derive(Debug, Clone, PartialEq)]
10pub struct AnimationTick {
11    timestamp: Duration,
12    properties: TargetedPropertySnapshot,
13    completed: Vec<AnimationHandle>,
14    removed: Vec<AnimationHandle>,
15    scratch: PropertySnapshot,
16}
17
18impl AnimationTick {
19    fn new(
20        timestamp: Duration,
21        properties: TargetedPropertySnapshot,
22        completed: Vec<AnimationHandle>,
23    ) -> Self {
24        Self {
25            timestamp,
26            properties,
27            completed,
28            removed: Vec::new(),
29            scratch: PropertySnapshot::new(),
30        }
31    }
32
33    /// Creates an empty reusable tick output.
34    #[must_use]
35    pub fn empty() -> Self {
36        Self::new(Duration::ZERO, TargetedPropertySnapshot::new(), Vec::new())
37    }
38
39    /// Returns the runtime timestamp used for this tick.
40    #[must_use]
41    pub const fn timestamp(&self) -> Duration {
42        self.timestamp
43    }
44
45    /// Returns the target-scoped property snapshots for view code.
46    #[must_use]
47    pub fn properties(&self) -> &TargetedPropertySnapshot {
48        &self.properties
49    }
50
51    /// Returns the property snapshot for a single target.
52    #[must_use]
53    pub fn properties_for(&self, target: AnimationTargetId) -> Option<&PropertySnapshot> {
54        self.properties.get(target)
55    }
56
57    /// Returns handles completed and removed during this tick.
58    #[must_use]
59    pub fn completed(&self) -> &[AnimationHandle] {
60        &self.completed
61    }
62
63    /// Returns whether this tick produced no property output.
64    #[must_use]
65    pub fn is_empty(&self) -> bool {
66        self.properties.is_empty()
67    }
68}
69
70pub(super) fn tick_registry(registry: &mut AnimationRegistry, now: Duration) -> AnimationTick {
71    let mut tick = AnimationTick::empty();
72
73    tick_registry_into(registry, now, &mut tick);
74
75    tick
76}
77
78pub(super) fn tick_registry_into(
79    registry: &mut AnimationRegistry,
80    now: Duration,
81    tick: &mut AnimationTick,
82) {
83    tick.timestamp = now;
84    tick.properties.clear();
85    tick.completed.clear();
86    tick.removed.clear();
87    tick.scratch.clear();
88
89    for entry in registry.entries_mut() {
90        let target = entry.target();
91        match entry.state() {
92            AnimationPlaybackState::Playing => {
93                let delta = now.checked_sub(entry.last_tick()).unwrap_or(Duration::ZERO);
94                entry.update_position(delta);
95                entry.set_last_tick(now);
96
97                if let Some(duration) = entry.source().total_duration()
98                    && source_is_complete(duration, entry.position())
99                {
100                    entry.set_position(duration);
101                    let has_snapshot = entry.source().completion_snapshot_into(&mut tick.scratch);
102
103                    update_last_snapshot(entry, has_snapshot, &tick.scratch);
104                    entry.mark_completed(now);
105                    tick.completed.push(entry.handle());
106                    tick.removed.push(entry.handle());
107                    merge_sampled_snapshot(
108                        &mut tick.properties,
109                        target,
110                        has_snapshot,
111                        &tick.scratch,
112                    );
113                } else {
114                    let has_snapshot = entry
115                        .source()
116                        .sample_into(entry.position(), &mut tick.scratch);
117
118                    update_last_snapshot(entry, has_snapshot, &tick.scratch);
119                    merge_sampled_snapshot(
120                        &mut tick.properties,
121                        target,
122                        has_snapshot,
123                        &tick.scratch,
124                    );
125                }
126            }
127            AnimationPlaybackState::Paused => {
128                entry.set_last_tick(now);
129                if let Some(snapshot) = entry.last_snapshot() {
130                    tick.properties.merge_entries(target, snapshot.entries());
131                }
132            }
133            AnimationPlaybackState::Canceled => {
134                tick.removed.push(entry.handle());
135            }
136            AnimationPlaybackState::Completed => {
137                tick.completed.push(entry.handle());
138                tick.removed.push(entry.handle());
139            }
140        }
141    }
142
143    tick.scratch.clear();
144
145    for handle in &tick.removed {
146        registry.remove_by_handle(*handle);
147    }
148
149    #[cfg(feature = "tracing")]
150    tracing::trace!(
151        target: "aura_anim_iced::runtime",
152        timestamp_ms = now.as_millis(),
153        output_targets = tick.properties.targets().count(),
154        completed = tick.completed.len(),
155        active = registry.active_count(),
156        "runtime tick"
157    );
158
159    #[cfg(feature = "inspector")]
160    tracing::debug!(
161        target: "aura_anim_iced::inspector",
162        timestamp_ms = now.as_millis(),
163        output_targets = tick.properties.targets().count(),
164        completed = tick.completed.len(),
165        removed = tick.removed.len(),
166        active = registry.active_count(),
167        "runtime inspector tick"
168    );
169}
170
171fn source_is_complete(total_duration: Duration, elapsed: Duration) -> bool {
172    elapsed.as_millis() >= total_duration.as_millis()
173}
174
175fn update_last_snapshot(
176    entry: &mut crate::runtime::entry::ActiveAnimation,
177    has_snapshot: bool,
178    snapshot: &PropertySnapshot,
179) {
180    if has_snapshot {
181        entry.replace_last_snapshot(snapshot);
182    } else {
183        entry.clear_last_snapshot();
184    }
185}
186
187fn merge_sampled_snapshot(
188    properties: &mut TargetedPropertySnapshot,
189    target: AnimationTargetId,
190    has_snapshot: bool,
191    snapshot: &PropertySnapshot,
192) {
193    if has_snapshot {
194        properties.merge_entries(target, snapshot.entries());
195    }
196}