tasks: use new animations api

This commit is contained in:
2026-02-28 16:59:29 +01:00
parent e8a7a18539
commit 816cc20087
4 changed files with 39 additions and 44 deletions

View File

@@ -104,6 +104,9 @@ impl Animation<Fract8> {
} }
} }
const MIN_ANIMATION_RATE: Duration = Duration::from_millis(5);
impl Animation<Fract8> { impl Animation<Fract8> {
pub async fn apply<const ACTOR_COUNT: usize>(&self, actors: [&mut dyn AnimationActor<Fract8>; ACTOR_COUNT]) { pub async fn apply<const ACTOR_COUNT: usize>(&self, actors: [&mut dyn AnimationActor<Fract8>; ACTOR_COUNT]) {
@@ -126,9 +129,11 @@ impl Animation<Fract8> {
let steps = to.abs_diff(from); let steps = to.abs_diff(from);
let step_time = if steps == Fract8::MIN { 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) Duration::from_ticks(0)
} else { } else {
(self.duration / steps.to_raw().into()).max(Duration::from_millis(1)) // 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 { Slot {
from, from,
@@ -163,10 +168,6 @@ impl Animation<Fract8> {
finished = true; finished = true;
} }
/*if animator.cur_step == animator.from || animator.cur_step == animator.to {
finished = true;
}*/
animator.target.set_value(animator.cur_step); animator.target.set_value(animator.cur_step);
} }
@@ -180,7 +181,7 @@ impl Animation<Fract8> {
} }
let keyframe_delay = next_keyframe_time - now; let keyframe_delay = next_keyframe_time - now;
trace!("delay {keyframe_delay:?}"); trace!("delay {:?}", keyframe_delay.as_millis());
Timer::after(keyframe_delay).await; Timer::after(keyframe_delay).await;
now += keyframe_delay; now += keyframe_delay;
} }

View File

@@ -32,7 +32,7 @@ impl Shader<OledUniforms, Matrix2DSpace, BinaryColor> for OverlayShader {
} }
impl<S: core::fmt::Debug + Surface<CoordinateSpace = Matrix2DSpace, Pixel = BinaryColor, Uniforms = OledUniforms>> OledUI<S> { impl<S: core::fmt::Debug + Surface<CoordinateSpace = Matrix2DSpace, Pixel = BinaryColor, Uniforms = OledUniforms>> OledUI<S> {
pub fn new<SS: Surfaces<Matrix2DSpace, Surface = S>>(surfaces: &mut SS, controls: DisplayControls, uniforms: LockedUniforms) -> Self where SS::Error: core::fmt::Debug { pub fn new<SS: Surfaces<Surface = S>>(surfaces: &mut SS, controls: DisplayControls, uniforms: LockedUniforms) -> Self where SS::Error: core::fmt::Debug {
Self { Self {
overlay: SurfaceBuilder::build(surfaces) overlay: SurfaceBuilder::build(surfaces)
.rect(Rectangle::everything()) .rect(Rectangle::everything())
@@ -48,12 +48,12 @@ impl<S: core::fmt::Debug + Surface<CoordinateSpace = Matrix2DSpace, Pixel = Bina
const FADE_IN: Animation<Fract8> = Animation::new().from(Fract8::MIN).to(Fract8::MAX).duration(Duration::from_millis(300)); const FADE_IN: Animation<Fract8> = Animation::new().from(Fract8::MIN).to(Fract8::MAX).duration(Duration::from_millis(300));
const FADE_OUT: Animation<Fract8> = Animation::new().from(Fract8::MAX).to(Fract8::MIN).duration(Duration::from_millis(300)); const FADE_OUT: Animation<Fract8> = Animation::new().from(Fract8::MAX).to(Fract8::MIN).duration(Duration::from_millis(300));
info!("Fading in to screen {next_screen:?}"); info!("Fading in to screen {next_screen:?}");
FADE_IN.apply(&mut self.overlay).await; FADE_IN.apply([&mut self.overlay]).await;
{ {
let mut locked = self.uniforms.lock().await; let mut locked = self.uniforms.lock().await;
locked.current_screen = next_screen; locked.current_screen = next_screen;
} }
FADE_OUT.apply(&mut self.overlay).await; FADE_OUT.apply([&mut self.overlay]).await;
} }
pub async fn on_event(&mut self, event: Prediction) { pub async fn on_event(&mut self, event: Prediction) {

View File

@@ -1,6 +1,6 @@
use embassy_sync::pubsub::DynSubscriber; use embassy_sync::pubsub::DynSubscriber;
use embassy_time::Duration; use embassy_time::Duration;
use figments::prelude::*; use figments::{liber8tion::interpolate::Fract8, prelude::*};
use figments_render::output::Brightness; use figments_render::output::Brightness;
use rgb::Rgba; use rgb::Rgba;
use core::fmt::Debug; use core::fmt::Debug;
@@ -48,7 +48,7 @@ impl<S: Debug + Surface<Uniforms = Uniforms, CoordinateSpace = SegmentSpace, Pix
pub async fn sleep(&mut self) { pub async fn sleep(&mut self) {
info!("Running sleep sequence"); info!("Running sleep sequence");
let mut disp_anim = AnimDisplay(&mut self.display); let mut disp_anim = AnimDisplay(&mut self.display);
TURN_OFF.apply(&mut disp_anim).await; TURN_OFF.apply([&mut disp_anim]).await;
warn!("Resetting safety lights"); warn!("Resetting safety lights");
self.brakelight.set_visible(false); self.brakelight.set_visible(false);
@@ -76,20 +76,17 @@ impl<S: Debug + Surface<Uniforms = Uniforms, CoordinateSpace = SegmentSpace, Pix
trace!("Fading in brightness with overlay={:?}", self.overlay); trace!("Fading in brightness with overlay={:?}", self.overlay);
self.overlay.set_opacity(Fract8::MAX); self.overlay.set_opacity(Fract8::MAX);
self.overlay.set_visible(true); self.overlay.set_visible(true);
fade_in.apply(&mut AnimDisplay(&mut self.display)).await; fade_in.apply([&mut AnimDisplay(&mut self.display)]).await;
warn!("Turning on safety lights"); warn!("Turning on safety lights");
self.headlight.set_opacity(Fract8::MIN); self.headlight.set_opacity(Fract8::MIN);
self.headlight.set_visible(true); self.headlight.set_visible(true);
self.brakelight.set_opacity(Fract8::MIN); self.brakelight.set_opacity(Fract8::MIN);
self.brakelight.set_visible(true); self.brakelight.set_visible(true);
embassy_futures::join::join( fade_in.apply([&mut self.headlight, &mut self.brakelight]).await;
fade_in.apply(&mut self.headlight),
fade_in.apply(&mut self.brakelight)
).await;
info!("Fade out overlay"); info!("Fade out overlay");
TURN_OFF.apply(&mut self.overlay).await; TURN_OFF.apply([&mut self.overlay]).await;
self.overlay.set_visible(false); self.overlay.set_visible(false);
info!("Wakeup complete!"); info!("Wakeup complete!");
} }
@@ -99,17 +96,17 @@ impl<S: Debug + Surface<Uniforms = Uniforms, CoordinateSpace = SegmentSpace, Pix
Personality::Active => { Personality::Active => {
// FIXME: These should be a Off/Low/High enum, so the stopping brake looks different from the dayrunning brake. // FIXME: These should be a Off/Low/High enum, so the stopping brake looks different from the dayrunning brake.
warn!("Active personality: Turning on safety lights"); warn!("Active personality: Turning on safety lights");
embassy_futures::join::join( TURN_ON.apply([
TURN_ON.apply(&mut self.brakelight), &mut self.brakelight,
TURN_ON.apply(&mut self.headlight) &mut self.headlight
).await; ]).await;
}, },
Personality::Parked => { Personality::Parked => {
warn!("Idle personality: Turning off safety lights"); warn!("Idle personality: Turning off safety lights");
embassy_futures::join::join( TURN_OFF.apply([
TURN_OFF.apply(&mut self.brakelight), &mut self.brakelight,
TURN_OFF.apply(&mut self.headlight) &mut self.headlight
).await; ]).await;
}, },
Personality::Sleeping => { Personality::Sleeping => {
warn!("Sleeping personality: Safety UI is going to sleep"); warn!("Sleeping personality: Safety UI is going to sleep");
@@ -127,7 +124,7 @@ const TURN_ON: Animation<Fract8> = Animation::new().duration(Duration::from_secs
const TURN_OFF: Animation<Fract8> = Animation::new().duration(Duration::from_secs(1)).from(Fract8::MAX).to(Fract8::MIN); const TURN_OFF: Animation<Fract8> = Animation::new().duration(Duration::from_secs(1)).from(Fract8::MAX).to(Fract8::MIN);
#[embassy_executor::task] #[embassy_executor::task]
pub async fn safety_ui_main(mut events: DynSubscriber<'static, Prediction>, mut ui: SafetyUi<<UiSurfacePool as Surfaces<SegmentSpace>>::Surface>) { pub async fn safety_ui_main(mut events: DynSubscriber<'static, Prediction>, mut ui: SafetyUi<<UiSurfacePool as Surfaces>::Surface>) {
// Wait for the renderer to start running // Wait for the renderer to start running
//ui.display.render_is_running.wait().await; //ui.display.render_is_running.wait().await;
trace!("spooling until render starts ui={ui:?}"); trace!("spooling until render starts ui={ui:?}");

View File

@@ -1,9 +1,8 @@
use embassy_sync::pubsub::DynSubscriber; use embassy_sync::pubsub::DynSubscriber;
use embassy_time::{Duration, Timer}; use embassy_time::{Duration, Timer};
use figments::prelude::*; use figments::{liber8tion::interpolate::Fract8, prelude::*};
use rgb::{Rgb, Rgba}; use rgb::{Rgb, Rgba};
use core::fmt::Debug; use core::fmt::Debug;
use futures::join;
use log::*; use log::*;
use crate::{animation::{AnimatedSurface, Animation}, ego::engine::MotionState, events::{Personality, Prediction, Scene, SensorSource, SensorState}, graphics::{display::{SegmentSpace, Uniforms}, shaders::*}}; use crate::{animation::{AnimatedSurface, Animation}, ego::engine::MotionState, events::{Personality, Prediction, Scene, SensorSource, SensorState}, graphics::{display::{SegmentSpace, Uniforms}, shaders::*}};
@@ -64,15 +63,15 @@ impl<S: Debug + Surface<Uniforms = Uniforms, CoordinateSpace = SegmentSpace, Pix
self.notification.set_shader(Background::from_color(color)); self.notification.set_shader(Background::from_color(color));
fade_in.apply(&mut self.notification).await; fade_in.apply([&mut self.notification]).await;
// Pulse quickly 5 times // Pulse quickly 5 times
for _ in 0..5 { for _ in 0..5 {
pulse_out.apply(&mut self.notification).await; pulse_out.apply([&mut self.notification]).await;
pulse_in.apply(&mut self.notification).await; pulse_in.apply([&mut self.notification]).await;
} }
fade_out.apply(&mut self.notification).await; fade_out.apply([&mut self.notification]).await;
self.notification.set_visible(false); self.notification.set_visible(false);
} }
@@ -100,10 +99,10 @@ impl<S: Debug + Surface<Uniforms = Uniforms, CoordinateSpace = SegmentSpace, Pix
let bg = Animation::default().duration(Duration::from_millis(300)).to(Fract8::from_raw(32)); let bg = Animation::default().duration(Duration::from_millis(300)).to(Fract8::from_raw(32));
let motion = Animation::default().duration(Duration::from_secs(1)).to(Fract8::MIN); let motion = Animation::default().duration(Duration::from_secs(1)).to(Fract8::MIN);
embassy_futures::join::join4( embassy_futures::join::join4(
tail.apply(&mut self.tail), tail.apply([&mut self.tail]),
panels.apply(&mut self.panels), panels.apply([&mut self.panels]),
bg.apply(&mut self.background), bg.apply([&mut self.background]),
motion.apply(&mut self.motion) motion.apply([&mut self.motion])
).await; ).await;
self.background.set_shader(Background::default()); self.background.set_shader(Background::default());
}, },
@@ -114,20 +113,18 @@ impl<S: Debug + Surface<Uniforms = Uniforms, CoordinateSpace = SegmentSpace, Pix
let bg_fade = Animation::default().duration(Duration::from_millis(300)).to(Fract8::from_raw(128)); let bg_fade = Animation::default().duration(Duration::from_millis(300)).to(Fract8::from_raw(128));
// FIXME: The scenes shouldn't be touching the brake/headlights at all here. In fact, they should be dealt with in a whole separate task from the main UI, maybe running on the motion prediction executor // FIXME: The scenes shouldn't be touching the brake/headlights at all here. In fact, they should be dealt with in a whole separate task from the main UI, maybe running on the motion prediction executor
embassy_futures::join::join4( embassy_futures::join::join(
fg_fade.apply(&mut self.tail), fg_fade.apply([&mut self.tail, &mut self.panels, &mut self.motion]),
fg_fade.apply(&mut self.panels), bg_fade.apply([&mut self.background])
bg_fade.apply(&mut self.background),
fg_fade.apply(&mut self.motion)
).await; ).await;
}, },
Scene::Accelerating => { Scene::Accelerating => {
self.motion.set_shader(Movement::default()); self.motion.set_shader(Movement::default());
Animation::default().duration(Duration::from_secs(1)).to(255).apply(&mut self.motion).await; Animation::default().duration(Duration::from_secs(1)).to(Fract8::MAX).apply([&mut self.motion]).await;
}, },
Scene::Decelerating => { Scene::Decelerating => {
self.motion.set_shader(Movement::default().reversed()); self.motion.set_shader(Movement::default().reversed());
Animation::default().duration(Duration::from_secs(1)).to(255).apply(&mut self.motion).await; Animation::default().duration(Duration::from_secs(1)).to(Fract8::MAX).apply([&mut self.motion]).await;
} }
} }
} }
@@ -199,7 +196,7 @@ pub type UiSurfacePool = NullBufferPool<Uniforms, SegmentSpace, Rgba<u8>>;
pub type UiSurfacePool = BufferedSurfacePool<Uniforms, SegmentSpace, Rgba<u8>>; pub type UiSurfacePool = BufferedSurfacePool<Uniforms, SegmentSpace, Rgba<u8>>;
#[embassy_executor::task] #[embassy_executor::task]
pub async fn ui_main(mut events: DynSubscriber<'static, Prediction>, mut ui: Ui<<UiSurfacePool as Surfaces<SegmentSpace>>::Surface>) { pub async fn ui_main(mut events: DynSubscriber<'static, Prediction>, mut ui: Ui<<UiSurfacePool as Surfaces>::Surface>) {
// FIXME: This should instead wait on some kind of flag set by the safety UI, or else we risk painting before we even have a display up and running // FIXME: This should instead wait on some kind of flag set by the safety UI, or else we risk painting before we even have a display up and running
Timer::after_secs(3).await; Timer::after_secs(3).await;
ui.show().await; ui.show().await;