animation: move a few more steps towards a real generic animation framework
This commit is contained in:
201
src/animation.rs
201
src/animation.rs
@@ -1,8 +1,7 @@
|
||||
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, mem::MaybeUninit, ops::{Deref, DerefMut}};
|
||||
use core::{fmt::Debug, ops::{Deref, DerefMut}};
|
||||
use log::*;
|
||||
|
||||
use crate::graphics::display::DisplayControls;
|
||||
@@ -53,6 +52,10 @@ impl<S: Surface> AnimationActor<Fract8> for AnimatedSurface<S> {
|
||||
}
|
||||
}
|
||||
|
||||
trait Tickable {
|
||||
fn tick(&mut self) -> TickResult;
|
||||
}
|
||||
|
||||
struct Slot<'a, T> {
|
||||
from: T,
|
||||
to: T,
|
||||
@@ -62,6 +65,38 @@ struct Slot<'a, T> {
|
||||
target: &'a mut dyn AnimationActor<T>
|
||||
}
|
||||
|
||||
enum TickResult {
|
||||
Finished,
|
||||
Continue
|
||||
}
|
||||
|
||||
impl<'a, T> Slot<'a, T> {
|
||||
fn is_valid(&self) -> bool {
|
||||
self.step_time.as_ticks() != 0
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Tickable for Slot<'a, Fract8> {
|
||||
/// Advances the animation by one tick, and then returns whether or not the animation should continue or not
|
||||
fn tick(&mut self) -> TickResult {
|
||||
self.next_update += self.step_time;
|
||||
self.cur_step = if self.to > self.from {
|
||||
self.cur_step + Fract8::from_raw(1)
|
||||
} else {
|
||||
self.cur_step - Fract8::from_raw(1)
|
||||
};
|
||||
|
||||
self.target.set_value(self.cur_step);
|
||||
|
||||
if (self.to > self.from && self.cur_step >= self.to) ||
|
||||
(self.to <= self.from && self.cur_step <= self.to) {
|
||||
TickResult::Finished
|
||||
} else {
|
||||
TickResult::Continue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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")
|
||||
@@ -73,7 +108,14 @@ impl<'a, T: Debug> core::fmt::Debug for Slot<'a, T> {
|
||||
}
|
||||
}
|
||||
|
||||
impl Animation<Fract8> {
|
||||
struct Animator<'a, T, const ACTOR_COUNT: usize> {
|
||||
animators: [Slot<'a, T>; ACTOR_COUNT]
|
||||
}
|
||||
|
||||
impl<'a, T, const ACTOR_COUNT: usize> Animator<'a, T, ACTOR_COUNT> {
|
||||
}
|
||||
|
||||
impl<T> Animation<T> {
|
||||
pub const fn new() -> Self {
|
||||
Self {
|
||||
from: None,
|
||||
@@ -82,93 +124,21 @@ impl Animation<Fract8> {
|
||||
}
|
||||
}
|
||||
|
||||
pub const fn from(self, from: Fract8) -> Self {
|
||||
Self {
|
||||
from: Some(from),
|
||||
..self
|
||||
}
|
||||
}
|
||||
|
||||
pub const fn to(self, to: Fract8) -> Self {
|
||||
Self {
|
||||
to: Some(to),
|
||||
..self
|
||||
}
|
||||
}
|
||||
|
||||
pub const fn duration(self, duration: Duration) -> Self {
|
||||
Self {
|
||||
duration,
|
||||
..self
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
const MIN_ANIMATION_RATE: Duration = Duration::from_millis(5);
|
||||
|
||||
impl Animation<Fract8> {
|
||||
pub async fn apply<const ACTOR_COUNT: usize>(&self, actors: [&mut dyn AnimationActor<Fract8>; ACTOR_COUNT]) {
|
||||
|
||||
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 {
|
||||
// Zero ticks is an 'invalid' animator that shouldn't get processed because start == end already
|
||||
Duration::from_ticks(0)
|
||||
} else {
|
||||
// FIXME: if the resulting duration is less than the animation rate, we also need to re-scale the number added to animator.cur_step further down below. Otherwise a 0-255 animation with a 100ms duration actually ends up running for 255ms
|
||||
(self.duration / steps.to_raw().into()).max(MIN_ANIMATION_RATE)
|
||||
};
|
||||
Slot {
|
||||
from,
|
||||
to,
|
||||
cur_step: from,
|
||||
step_time,
|
||||
next_update: now,
|
||||
target
|
||||
}
|
||||
});
|
||||
|
||||
trace!("animators={animators:?}");
|
||||
|
||||
async fn execute<'a, const ACTOR_COUNT: usize>(mut animators: [Slot<'a, T>; ACTOR_COUNT]) where Slot<'a, T>: Tickable {
|
||||
let mut now: Instant = Instant::now();
|
||||
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 {
|
||||
if !animator.is_valid() {
|
||||
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;
|
||||
finished = match animator.tick() {
|
||||
TickResult::Finished => true,
|
||||
TickResult::Continue => finished
|
||||
}
|
||||
|
||||
animator.target.set_value(animator.cur_step);
|
||||
}
|
||||
|
||||
if next_keyframe_time <= now || animator.next_update < next_keyframe_time {
|
||||
@@ -185,8 +155,75 @@ impl Animation<Fract8> {
|
||||
Timer::after(keyframe_delay).await;
|
||||
now += keyframe_delay;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
trace!("finished animators={animators:?}");
|
||||
impl Animation<Fract8> {
|
||||
pub const fn duration(self, duration: Duration) -> Self {
|
||||
Self {
|
||||
duration,
|
||||
..self
|
||||
}
|
||||
}
|
||||
|
||||
pub const fn from(self, from: Fract8) -> Self {
|
||||
Self {
|
||||
from: Some(from),
|
||||
..self
|
||||
}
|
||||
}
|
||||
|
||||
pub const fn to(self, to: Fract8) -> Self {
|
||||
Self {
|
||||
to: Some(to),
|
||||
..self
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const MIN_ANIMATION_RATE: Duration = Duration::from_millis(5);
|
||||
|
||||
impl Animation<Fract8> {
|
||||
pub async fn apply<const ACTOR_COUNT: usize>(&self, actors: [&mut dyn AnimationActor<Fract8>; ACTOR_COUNT]) {
|
||||
|
||||
let now = Instant::now();
|
||||
trace!("start now={now:?} ACTOR_COUNT={ACTOR_COUNT}");
|
||||
|
||||
let mut actors = actors.into_iter();
|
||||
let 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 step_time = if to == from {
|
||||
// Zero ticks is an 'invalid' animator that shouldn't get processed because start == end already
|
||||
Duration::from_ticks(0)
|
||||
} else {
|
||||
let steps = to.abs_diff(from);
|
||||
// FIXME: if the resulting duration is less than the animation rate, we also need to re-scale the number added to animator.cur_step further down below. Otherwise a 0-255 animation with a 100ms duration actually ends up running for 255ms
|
||||
(self.duration / steps.to_raw().into()).max(MIN_ANIMATION_RATE)
|
||||
};
|
||||
Slot {
|
||||
from,
|
||||
to,
|
||||
cur_step: from,
|
||||
step_time,
|
||||
next_update: now,
|
||||
target
|
||||
}
|
||||
});
|
||||
|
||||
trace!("animators={animators:?}");
|
||||
|
||||
Self::execute(animators).await;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user