use super::{AnimationHandle, AnimationPlaybackState};
use crate::runtime::{
registry::AnimationRegistry,
target::{AnimationTargetId, TargetedPropertySnapshot},
};
use crate::{property::PropertySnapshot, timing::Duration};
#[derive(Debug, Clone, PartialEq)]
pub struct AnimationTick {
timestamp: Duration,
properties: TargetedPropertySnapshot,
completed: Vec<AnimationHandle>,
removed: Vec<AnimationHandle>,
scratch: PropertySnapshot,
}
impl AnimationTick {
fn new(
timestamp: Duration,
properties: TargetedPropertySnapshot,
completed: Vec<AnimationHandle>,
) -> Self {
Self {
timestamp,
properties,
completed,
removed: Vec::new(),
scratch: PropertySnapshot::new(),
}
}
#[must_use]
pub fn empty() -> Self {
Self::new(Duration::ZERO, TargetedPropertySnapshot::new(), Vec::new())
}
#[must_use]
pub const fn timestamp(&self) -> Duration {
self.timestamp
}
#[must_use]
pub fn properties(&self) -> &TargetedPropertySnapshot {
&self.properties
}
#[must_use]
pub fn properties_for(&self, target: AnimationTargetId) -> Option<&PropertySnapshot> {
self.properties.get(target)
}
#[must_use]
pub fn completed(&self) -> &[AnimationHandle] {
&self.completed
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.properties.is_empty()
}
}
pub(super) fn tick_registry(registry: &mut AnimationRegistry, now: Duration) -> AnimationTick {
let mut tick = AnimationTick::empty();
tick_registry_into(registry, now, &mut tick);
tick
}
pub(super) fn tick_registry_into(
registry: &mut AnimationRegistry,
now: Duration,
tick: &mut AnimationTick,
) {
tick.timestamp = now;
tick.properties.clear();
tick.completed.clear();
tick.removed.clear();
tick.scratch.clear();
for entry in registry.entries_mut() {
let target = entry.target();
match entry.state() {
AnimationPlaybackState::Playing => {
let delta = now.checked_sub(entry.last_tick()).unwrap_or(Duration::ZERO);
entry.update_position(delta);
entry.set_last_tick(now);
if let Some(duration) = entry.source().total_duration()
&& source_is_complete(duration, entry.position())
{
entry.set_position(duration);
let has_snapshot = entry.source().completion_snapshot_into(&mut tick.scratch);
update_last_snapshot(entry, has_snapshot, &tick.scratch);
entry.mark_completed(now);
tick.completed.push(entry.handle());
tick.removed.push(entry.handle());
merge_sampled_snapshot(
&mut tick.properties,
target,
has_snapshot,
&tick.scratch,
);
} else {
let has_snapshot = entry
.source()
.sample_into(entry.position(), &mut tick.scratch);
update_last_snapshot(entry, has_snapshot, &tick.scratch);
merge_sampled_snapshot(
&mut tick.properties,
target,
has_snapshot,
&tick.scratch,
);
}
}
AnimationPlaybackState::Paused => {
entry.set_last_tick(now);
if let Some(snapshot) = entry.last_snapshot() {
tick.properties.merge_entries(target, snapshot.entries());
}
}
AnimationPlaybackState::Canceled => {
tick.removed.push(entry.handle());
}
AnimationPlaybackState::Completed => {
tick.completed.push(entry.handle());
tick.removed.push(entry.handle());
}
}
}
tick.scratch.clear();
for handle in &tick.removed {
registry.remove_by_handle(*handle);
}
#[cfg(feature = "tracing")]
tracing::trace!(
target: "aura_anim_iced::runtime",
timestamp_ms = now.as_millis(),
output_targets = tick.properties.targets().count(),
completed = tick.completed.len(),
active = registry.active_count(),
"runtime tick"
);
#[cfg(feature = "inspector")]
tracing::debug!(
target: "aura_anim_iced::inspector",
timestamp_ms = now.as_millis(),
output_targets = tick.properties.targets().count(),
completed = tick.completed.len(),
removed = tick.removed.len(),
active = registry.active_count(),
"runtime inspector tick"
);
}
fn source_is_complete(total_duration: Duration, elapsed: Duration) -> bool {
elapsed.as_millis() >= total_duration.as_millis()
}
fn update_last_snapshot(
entry: &mut crate::runtime::entry::ActiveAnimation,
has_snapshot: bool,
snapshot: &PropertySnapshot,
) {
if has_snapshot {
entry.replace_last_snapshot(snapshot);
} else {
entry.clear_last_snapshot();
}
}
fn merge_sampled_snapshot(
properties: &mut TargetedPropertySnapshot,
target: AnimationTargetId,
has_snapshot: bool,
snapshot: &PropertySnapshot,
) {
if has_snapshot {
properties.merge_entries(target, snapshot.entries());
}
}