animation: rewrite the apply() algorithm to use fewer timers and multiple targets at once

This commit is contained in:
2026-02-02 19:43:43 +01:00
parent 2aaa64374b
commit 08a3c65346

View File

@@ -1,29 +1,30 @@
use embassy_time::{Duration, Timer};
use figments::surface::Surface;
use embassy_time::{Duration, Instant, Timer};
use esp_rtos::CurrentThreadHandle;
use figments::{liber8tion::interpolate::Fract8, surface::Surface};
use figments_render::output::Brightness;
use core::{fmt::Debug, ops::{Deref, DerefMut}};
use core::{fmt::Debug, mem::MaybeUninit, ops::{Deref, DerefMut}};
use log::*;
use crate::graphics::display::DisplayControls;
#[derive(Default, Debug, Clone, Copy)]
pub struct Animation {
from: Option<u8>,
to: Option<u8>,
pub struct Animation<T> {
from: Option<T>,
to: Option<T>,
duration: Duration
}
pub trait AnimationActor {
fn get_opacity(&self) -> u8;
fn set_opacity(&mut self, opacity: u8);
pub trait AnimationActor<T> {
fn get_value(&self) -> T;
fn set_value(&mut self, value: T);
}
impl<S: Surface> AnimationActor for S {
fn get_opacity(&self) -> u8 {
0
impl<S: Surface> AnimationActor<Fract8> for S {
fn get_value(&self) -> Fract8 {
unimplemented!()
}
fn set_opacity(&mut self, opacity: u8) {
fn set_value(&mut self, opacity: Fract8) {
self.set_opacity(opacity);
}
}
@@ -31,28 +32,48 @@ impl<S: Surface> AnimationActor for S {
#[derive(Debug)]
pub struct AnimDisplay<'a>(pub &'a mut DisplayControls);
impl<'a> AnimationActor for AnimDisplay<'a> {
fn get_opacity(&self) -> u8 {
impl<'a> AnimationActor<Fract8> for AnimDisplay<'a> {
fn get_value(&self) -> Fract8 {
self.0.brightness()
}
fn set_opacity(&mut self, opacity: u8) {
fn set_value(&mut self, opacity: Fract8) {
self.0.set_brightness(opacity);
}
}
impl<S: Surface> AnimationActor for AnimatedSurface<S> {
fn get_opacity(&self) -> u8 {
impl<S: Surface> AnimationActor<Fract8> for AnimatedSurface<S> {
fn get_value(&self) -> Fract8 {
self.opacity
}
fn set_opacity(&mut self, opacity: u8) {
fn set_value(&mut self, opacity: Fract8) {
self.surface.set_opacity(opacity);
self.opacity = opacity;
}
}
impl Animation {
struct Slot<'a, T> {
from: T,
to: T,
cur_step: T,
step_time: Duration,
next_update: Instant,
target: &'a mut dyn AnimationActor<T>
}
impl<'a, T: Debug> core::fmt::Debug for Slot<'a, T> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("Slot")
.field("from", &self.from)
.field("to", &self.to)
.field("cur_step", &self.cur_step)
.field("step_time", &self.step_time)
.field("next_update", &self.next_update).finish()
}
}
impl Animation<Fract8> {
pub const fn new() -> Self {
Self {
from: None,
@@ -60,61 +81,111 @@ impl Animation {
duration: Duration::from_ticks(0)
}
}
pub const fn from(self, from: u8) -> Self {
pub const fn from(self, from: Fract8) -> Self {
Self {
from: Some(from),
to: self.to,
duration: self.duration
..self
}
}
pub const fn to(self, to: u8) -> Self {
pub const fn to(self, to: Fract8) -> Self {
Self {
from: self.from,
to: Some(to),
duration: self.duration
..self
}
}
pub const fn duration(self, duration: Duration) -> Self {
Self {
from: self.from,
to: self.to,
duration
duration,
..self
}
}
}
pub async fn apply<S: AnimationActor>(&self, sfc: &mut S) {
let from = if let Some(val) = self.from {
val
} else {
sfc.get_opacity()
};
let to = if let Some(val) = self.to {
val
} else {
sfc.get_opacity()
};
let steps = from.abs_diff(to);
if steps == 0 {
return;
}
let step_time = self.duration / steps.into();
trace!("fade={self:?} steps={steps} time={step_time}");
if from > to {
let range = (to..=from).rev();
for opacity in range {
sfc.set_opacity(opacity);
Timer::after(step_time).await;
}
} else {
let range = from..=to;
impl Animation<Fract8> {
pub async fn apply<const ACTOR_COUNT: usize>(&self, actors: [&mut dyn AnimationActor<Fract8>; ACTOR_COUNT]) {
for opacity in range {
sfc.set_opacity(opacity);
Timer::after(step_time).await;
let mut now = Instant::now();
trace!("start now={now:?} ACTOR_COUNT={ACTOR_COUNT}");
let mut actors = actors.into_iter();
let mut animators: [Slot<Fract8>; ACTOR_COUNT] = core::array::from_fn(|_| {
let target = actors.next().unwrap();
let from = if let Some(val) = self.from {
val
} else {
target.get_value()
};
let to = if let Some(val) = self.to {
val
} else {
target.get_value()
};
let steps = to.abs_diff(from);
let step_time = if steps == Fract8::MIN {
Duration::from_ticks(0)
} else {
(self.duration / steps.to_raw().into()).max(Duration::from_millis(1))
};
Slot {
from,
to,
cur_step: from,
step_time,
next_update: now,
target
}
});
trace!("animators={animators:?}");
loop {
// Find the next shortest delay
let mut next_keyframe_time = animators[0].next_update;
let mut finished = false;
for animator in &mut animators {
if animator.step_time.as_ticks() == 0 {
continue;
}
if animator.next_update <= now {
animator.next_update += animator.step_time;
animator.cur_step = if animator.to > animator.from {
animator.cur_step + Fract8::from_raw(1)
} else {
animator.cur_step - Fract8::from_raw(1)
};
if (animator.to > animator.from && animator.cur_step >= animator.to) ||
(animator.to <= animator.from && animator.cur_step <= animator.to) {
finished = true;
}
/*if animator.cur_step == animator.from || animator.cur_step == animator.to {
finished = true;
}*/
animator.target.set_value(animator.cur_step);
}
if next_keyframe_time <= now || animator.next_update < next_keyframe_time {
next_keyframe_time = animator.next_update;
}
}
if finished {
break;
}
let keyframe_delay = next_keyframe_time - now;
trace!("delay {keyframe_delay:?}");
Timer::after(keyframe_delay).await;
now += keyframe_delay;
}
trace!("finished animators={animators:?}");
}
}
@@ -122,7 +193,7 @@ impl Animation {
pub struct AnimatedSurface<S: Surface> {
surface: S,
is_on: bool,
opacity: u8
opacity: Fract8
}
impl<S: Surface> Deref for AnimatedSurface<S> {
@@ -145,7 +216,7 @@ impl<S: Surface + Debug> From<S> for AnimatedSurface<S> {
AnimatedSurface {
surface,
is_on: false,
opacity: 255
opacity: Fract8::MAX
}
}
}
@@ -155,11 +226,11 @@ impl<S: Surface + Debug> AnimatedSurface<S> {
if self.is_on != is_on {
let anim = if is_on {
self.surface.set_visible(true);
Animation::default().duration(Duration::from_secs(1)).from(0).to(255)
Animation::default().duration(Duration::from_secs(1)).from(Fract8::MIN).to(Fract8::MAX)
} else {
Animation::default().duration(Duration::from_secs(1)).from(255).to(0)
Animation::default().duration(Duration::from_secs(1)).from(Fract8::MAX).to(Fract8::MIN)
};
anim.apply(&mut self.surface).await;
anim.apply([&mut self.surface]).await;
self.is_on = true;
if !is_on {
self.surface.set_visible(false);