From 246c16d7b0d4e94005cb4301f2e4c357938e4bb2 Mon Sep 17 00:00:00 2001 From: llMBQll Date: Sat, 18 Jan 2025 13:42:01 +0100 Subject: [PATCH] Refactor animated image an text handling This allows for widgets to be animated independently of each other --- config/scripts.lua | 8 +- omni-led/src/common/common.rs | 8 +- omni-led/src/renderer/animation.rs | 73 ++--- omni-led/src/renderer/animation_group.rs | 130 ++++++++ omni-led/src/renderer/mod.rs | 3 +- omni-led/src/renderer/renderer.rs | 299 +++++++++--------- .../src/script_handler/script_data_types.rs | 22 +- omni-led/src/script_handler/script_handler.rs | 62 ++-- 8 files changed, 357 insertions(+), 248 deletions(-) create mode 100644 omni-led/src/renderer/animation_group.rs diff --git a/config/scripts.lua b/config/scripts.lua index a9acc30..68e8f65 100644 --- a/config/scripts.lua +++ b/config/scripts.lua @@ -11,12 +11,12 @@ local function volume() Text { text = AUDIO.Name, scrolling = true, + repeats = 'Once', position = { x = 0, y = SCREEN.Height / 2 }, size = { width = SCREEN.Width, height = SCREEN.Height / 2 }, }, }, duration = 2000, - repeats = 'Once', } end @@ -34,13 +34,16 @@ local function spotify() Text { text = string.format("%s - %s", SPOTIFY.Artist, SPOTIFY.Title), scrolling = true, + animation_group = 1, position = { x = 0, y = 2 }, size = { width = SCREEN.Width, height = 20 }, }, Text { text = string.format("%02d:%02d", CLOCK.Hours, CLOCK.Minutes), + scrolling = true, + animation_group = 1, position = { x = 0, y = SCREEN.Height - 18 }, - size = { width = 50, height = 18 }, + size = { width = 25, height = 18 }, }, Text { text = string.format("%.3s%02d", CLOCK.MonthNames[CLOCK.Month], CLOCK.MonthDay), @@ -55,7 +58,6 @@ local function spotify() }, }, duration = SPOTIFY_DURATION, - repeats = 'ForDuration', } end diff --git a/omni-led/src/common/common.rs b/omni-led/src/common/common.rs index 1da58c6..8f3e5e8 100644 --- a/omni-led/src/common/common.rs +++ b/omni-led/src/common/common.rs @@ -145,14 +145,12 @@ pub fn proto_to_lua_value(lua: &Lua, field: Field) -> mlua::Result { Ok(Value::Table(table)) } Some(FieldEntry::FImageData(image)) => { - let mut image_data = ImageData { + let hash = hash(&image.data); + let image_data = ImageData { format: image.format().into(), bytes: image.data, - hash: None, + hash: Some(hash), }; - let hash = hash(&image_data); - image_data.hash = Some(hash); - let user_data = lua.create_any_userdata(image_data)?; Ok(Value::UserData(user_data)) } diff --git a/omni-led/src/renderer/animation.rs b/omni-led/src/renderer/animation.rs index aeeb03e..194fa19 100644 --- a/omni-led/src/renderer/animation.rs +++ b/omni-led/src/renderer/animation.rs @@ -16,24 +16,28 @@ * along with this program. If not, see . */ -#[derive(Clone)] +use crate::script_handler::script_data_types::Repeat; + +#[derive(Clone, Debug)] pub struct Animation { edge_step_time: usize, step_time: usize, steps: usize, - last_update_tick: usize, total_time: usize, + repeat: Repeat, current_tick: usize, + can_wrap: bool, } -#[derive(Debug, PartialEq)] -pub struct Step { - pub offset: usize, - pub can_wrap: bool, +#[derive(Copy, Clone, Debug, PartialEq)] +pub enum State { + InProgress, + Finished, + CanFinish, } impl Animation { - pub fn new(edge_step_time: usize, step_time: usize, steps: usize, tick: usize) -> Self { + pub fn new(edge_step_time: usize, step_time: usize, steps: usize, repeat: Repeat) -> Self { let total_time = match steps { 1 => 0, _ => edge_step_time * 2 + (steps - 2) * step_time, @@ -43,13 +47,14 @@ impl Animation { edge_step_time, step_time, steps, - last_update_tick: tick, total_time, + repeat, current_tick: 1, + can_wrap: false, } } - pub fn step(&mut self, tick: usize) -> Step { + pub fn step(&mut self) -> usize { let (step, can_wrap) = if self.current_tick >= self.total_time { (self.steps - 1, true) } else if self.current_tick > self.total_time - self.edge_step_time { @@ -63,19 +68,22 @@ impl Animation { ) }; - if tick != self.last_update_tick { - self.current_tick += 1; - self.last_update_tick = tick; - } + self.current_tick += 1; + self.can_wrap = can_wrap; + + step + } - Step { - offset: step, - can_wrap, + pub fn state(&self) -> State { + match (self.repeat, self.can_wrap) { + (Repeat::Once, false) => State::InProgress, + (Repeat::Once, true) => State::Finished, + (Repeat::ForDuration, _) => State::CanFinish, } } - pub fn last_update_time(&self) -> usize { - self.last_update_tick + pub fn can_wrap(&self) -> bool { + self.can_wrap } pub fn reset(&mut self) { @@ -88,21 +96,15 @@ mod tests { use super::*; macro_rules! step_and_assert_eq { - ($tick:ident, $anim:ident, $step:expr, $can_wrap:expr) => { - $tick += 1; - assert_eq!( - $anim.step($tick), - Step { - offset: $step, - can_wrap: $can_wrap - } - ); + ($anim:ident, $step:expr, $can_wrap:expr, $state:expr) => { + assert_eq!($anim.step(), $step); + assert_eq!($anim.can_wrap(), $can_wrap); + assert_eq!($anim.state(), $state); }; } fn run_test(edge_time: usize, step_time: usize, steps: usize) { - let mut tick = 0; - let mut animation = Animation::new(edge_time, step_time, steps, 0); + let mut animation = Animation::new(edge_time, step_time, steps, Repeat::Once); let total_time = if steps == 1 { 0 @@ -113,19 +115,19 @@ mod tests { assert_eq!(animation.total_time, total_time); for _ in 0..edge_time { - step_and_assert_eq!(tick, animation, 0, false); + step_and_assert_eq!(animation, 0, false, State::InProgress); } for step in 0..steps - 2 { for _ in 0..step_time { - step_and_assert_eq!(tick, animation, step + 1, false); + step_and_assert_eq!(animation, step + 1, false, State::InProgress); } } for _ in 0..edge_time - 1 { - step_and_assert_eq!(tick, animation, steps - 1, false); + step_and_assert_eq!(animation, steps - 1, false, State::InProgress); } - step_and_assert_eq!(tick, animation, steps - 1, true); + step_and_assert_eq!(animation, steps - 1, true, State::Finished); } #[test] @@ -161,11 +163,10 @@ mod tests { const STEP_TIME: usize = 5; const STEPS: usize = 1; - let mut tick = 0; - let mut animation = Animation::new(EDGE_TIME, STEP_TIME, STEPS, 0); + let mut animation = Animation::new(EDGE_TIME, STEP_TIME, STEPS, Repeat::Once); assert_eq!(animation.total_time, 0); - step_and_assert_eq!(tick, animation, 0, true); + step_and_assert_eq!(animation, 0, true, State::Finished); } } diff --git a/omni-led/src/renderer/animation_group.rs b/omni-led/src/renderer/animation_group.rs new file mode 100644 index 0000000..c7a458c --- /dev/null +++ b/omni-led/src/renderer/animation_group.rs @@ -0,0 +1,130 @@ +use crate::renderer::animation::{Animation, State}; + +#[derive(Clone)] +pub struct AnimationGroup { + items: Vec, + new_data: bool, + keep_in_sync: bool, +} + +#[derive(Clone)] +struct Item { + hash: u64, + animation: Animation, + accessed: bool, +} + +impl AnimationGroup { + pub fn new(keep_in_sync: bool) -> Self { + Self { + items: Vec::new(), + new_data: false, + keep_in_sync, + } + } + + pub fn entry(&mut self, hash: u64) -> Entry { + let mut index = None; + for (i, item) in self.items.iter_mut().enumerate() { + if item.hash == hash { + index = Some(i); + break; + } + } + + match index { + Some(index) => Entry::Occupied(OccupiedEntry { + _hash: hash, + item: &mut self.items[index], + }), + None => Entry::Vacant(VacantEntry { hash, group: self }), + } + } + + pub fn pre_sync(&mut self) { + self.items.retain_mut(|item| { + if item.accessed { + if self.new_data && self.keep_in_sync { + item.animation.reset(); + item.accessed = false; + } + true + } else { + false + } + }); + self.new_data = false; + } + + pub fn sync(&mut self) { + if self.keep_in_sync { + let all_can_wrap = self.items.iter().all(|item| item.animation.can_wrap()); + if all_can_wrap { + for item in &mut self.items { + item.animation.reset(); + } + } + } else { + for item in &mut self.items { + if item.animation.can_wrap() { + item.animation.reset(); + } + } + } + } + + pub fn states(&self) -> Vec { + self.items + .iter() + .map(|item| item.animation.state()) + .collect() + } +} + +pub enum Entry<'a> { + Occupied(OccupiedEntry<'a>), + Vacant(VacantEntry<'a>), +} + +impl<'a> Entry<'a> { + pub fn or_insert_with Animation>(self, f: F) -> &'a mut Animation { + match self { + Entry::Occupied(entry) => { + entry.item.accessed = true; + &mut entry.item.animation + } + Entry::Vacant(entry) => { + entry.group.new_data = true; + entry.group.items.push(Item { + hash: entry.hash, + animation: f(), + accessed: true, + }); + let index = entry.group.items.len() - 1; + &mut entry.group.items[index].animation + } + } + } + + pub fn unwrap(self) -> &'a mut Animation { + match self { + Entry::Occupied(entry) => { + entry.item.accessed = true; + &mut entry.item.animation + } + Entry::Vacant(entry) => { + panic!("Entry with hash {} doesn't exist", entry.hash); + } + } + } +} + +pub struct OccupiedEntry<'a> { + _hash: u64, + item: &'a mut Item, +} + +pub struct VacantEntry<'a> { + hash: u64, + group: &'a mut AnimationGroup, +} diff --git a/omni-led/src/renderer/mod.rs b/omni-led/src/renderer/mod.rs index 53ef0f7..b93e0e1 100644 --- a/omni-led/src/renderer/mod.rs +++ b/omni-led/src/renderer/mod.rs @@ -16,11 +16,12 @@ * along with this program. If not, see . */ +pub mod animation; +pub mod animation_group; pub mod buffer; pub mod font_selector; pub mod renderer; -mod animation; mod bit; mod font_manager; mod images; diff --git a/omni-led/src/renderer/renderer.rs b/omni-led/src/renderer/renderer.rs index ee2d560..169a7e0 100644 --- a/omni-led/src/renderer/renderer.rs +++ b/omni-led/src/renderer/renderer.rs @@ -20,9 +20,11 @@ use mlua::Lua; use num_traits::clamp; use std::cmp::max; use std::collections::HashMap; +use std::hash::{DefaultHasher, Hash, Hasher}; use crate::common::user_data::UserDataRef; -use crate::renderer::animation::{Animation, Step}; +use crate::renderer::animation::{Animation, State}; +use crate::renderer::animation_group::AnimationGroup; use crate::renderer::buffer::{BitBuffer, Buffer, BufferTrait, ByteBuffer}; use crate::renderer::font_manager::FontManager; use crate::renderer::images; @@ -49,9 +51,7 @@ macro_rules! get_animation_settings { pub struct Renderer { font_manager: FontManager, image_cache: ImageCache, - animation_data: AnimationData, animation_settings: AnimationSettings, - counter: usize, } impl Renderer { @@ -62,36 +62,54 @@ impl Renderer { Self { font_manager: FontManager::new(font_selector), image_cache: ImageCache::new(), - animation_data: AnimationData::new(), animation_settings: AnimationSettings::new(lua), - counter: 0, } } pub fn render( &mut self, - key: ContextKey, + animation_groups: &mut HashMap, size: Size, - widgets: Vec, + mut widgets: Vec, memory_representation: MemoryRepresentation, - ) -> (bool, Buffer) { - self.counter += 1; - + ) -> (State, Buffer) { let mut buffer = match memory_representation { MemoryRepresentation::BitPerPixel => Buffer::new(BitBuffer::new(size)), MemoryRepresentation::BytePerPixel => Buffer::new(ByteBuffer::new(size)), }; - let (end_auto_repeat, steps) = self.precalculate_text(&key, &widgets); + + self.calculate_animations(animation_groups, &mut widgets); for operation in widgets { match operation { Widget::Bar(bar) => Self::render_bar(&mut buffer, bar), - Widget::Image(image) => self.render_image(&mut buffer, image, &key), - Widget::Text(text) => self.render_text(&mut buffer, text, &steps), + Widget::Image(image) => self.render_image(&mut buffer, image, animation_groups), + Widget::Text(text) => self.render_text(&mut buffer, text, animation_groups), } } - (end_auto_repeat, buffer) + animation_groups + .iter_mut() + .for_each(|(_, group)| group.sync()); + + let states = animation_groups + .iter() + .map(|(_, group)| group.states()) + .flatten() + .collect::>(); + + let all_finished = states.iter().all(|state| *state == State::Finished); + let any_in_progress = states.iter().any(|state| *state == State::InProgress); + + let state = if all_finished { + State::Finished + } else if any_in_progress { + State::InProgress + } else { + State::CanFinish + }; + + (state, buffer) } fn clear_background(buffer: &mut Buffer, position: Point, size: Size, modifiers: &Modifiers) { @@ -133,7 +151,12 @@ impl Renderer { } } - fn render_image(&mut self, buffer: &mut Buffer, widget: Image, key: &ContextKey) { + fn render_image( + &mut self, + buffer: &mut Buffer, + widget: Image, + animation_groups: &mut HashMap, + ) { if widget.size.width == 0 || widget.size.height == 0 { return; } @@ -147,27 +170,11 @@ impl Renderer { ); let frame = if widget.animated { - let animation = self - .animation_data - .get_image_context(key) - .entry(widget.image.hash.unwrap()) - .or_insert_with(|| { - let settings = get_animation_settings!(self.animation_settings, widget); - - Animation::new( - settings.ticks_at_edge, - settings.ticks_per_move, - image.len(), - self.counter, - ) - }); - - let step = animation.step(self.counter); - if step.can_wrap { - animation.reset(); - } - - &image[step.offset] + let hash = widget.image.hash.unwrap(); + let group = Self::get_animation_group(animation_groups, widget.animation_group); + let animation = group.entry(hash).unwrap(); + let step = animation.step(); + &image[step] } else { &image[0] }; @@ -194,7 +201,12 @@ impl Renderer { } } - fn render_text(&mut self, buffer: &mut Buffer, widget: Text, steps: &HashMap) { + fn render_text( + &mut self, + buffer: &mut Buffer, + widget: Text, + animation_groups: &mut HashMap, + ) { if widget.modifiers.clear_background { Self::clear_background(buffer, widget.position, widget.size, &widget.modifiers); } @@ -205,12 +217,12 @@ impl Renderer { let mut characters = widget.text.chars(); if widget.scrolling { - let offset = steps - .get(&widget.text) - .and_then(|step| Some(step.offset)) - .unwrap_or(0); + let hash = widget.hash.unwrap(); + let group = Self::get_animation_group(animation_groups, widget.animation_group); + let animation = group.entry(hash).unwrap(); + let step = animation.step(); - for _ in 0..offset { + for _ in 0..step { _ = characters.next(); } } @@ -264,102 +276,107 @@ impl Renderer { } } - fn precalculate_text( + fn calculate_animations( &mut self, - key: &ContextKey, - widgets: &Vec, - ) -> (bool, HashMap) { - let ctx = self.animation_data.get_text_context(&key); - - let mut all_can_wrap: bool = true; - let mut any_new_data: bool = false; - - let steps = widgets - .iter() - .filter_map(|widget| match widget { - Widget::Text(text) => Self::precalculate_single( - ctx, - &mut self.font_manager, - &self.animation_settings, - text, - self.counter, - ) - .and_then(|(new_data, step)| { - if new_data { - any_new_data = true; - } - if !step.can_wrap { - all_can_wrap = false; + animation_groups: &mut HashMap, + widgets: &mut Vec, + ) { + for widget in widgets { + match widget { + Widget::Bar(_) => continue, + Widget::Image(image) => { + if !image.animated { + continue; } - Some((text.text.clone(), step)) - }), - _ => None, - }) - .collect(); - *ctx = ctx - .iter() - .filter_map(|(text, animation)| { - if animation.last_update_time() == self.counter { - let text = text.clone(); - let mut animation = animation.clone(); - if any_new_data || all_can_wrap { - animation.reset(); + Self::calculate_animation_hash(&image.image.bytes, &mut image.image.hash); + + let group = Self::get_animation_group(animation_groups, image.animation_group); + group.entry(image.image.hash.unwrap()).or_insert_with(|| { + let settings = get_animation_settings!(self.animation_settings, image); + let rendered = images::render_image( + &mut self.image_cache, + &image.image, + image.size, + image.threshold, + image.animated, + ); + Animation::new( + settings.ticks_at_edge, + settings.ticks_per_move, + rendered.len(), + image.repeats, + ) + }); + } + Widget::Text(text) => { + if !text.scrolling { + continue; } - Some((text, animation)) - } else { - None + + Self::calculate_animation_hash(&text.text, &mut text.hash); + + let group = Self::get_animation_group(animation_groups, text.animation_group); + group.entry(text.hash.unwrap()).or_insert_with(|| { + let settings = get_animation_settings!(self.animation_settings, text); + let steps = Self::pre_render_text(&mut self.font_manager, text); + Animation::new( + settings.ticks_at_edge, + settings.ticks_per_move, + steps, + text.repeats, + ) + }); } - }) - .collect(); + }; + } - if any_new_data { - (false, HashMap::new()) - } else { - (all_can_wrap, steps) + for (_, group) in animation_groups { + group.pre_sync(); } } - fn precalculate_single( - ctx: &mut HashMap, - font_manager: &mut FontManager, - settings: &AnimationSettings, - text: &Text, - counter: usize, - ) -> Option<(bool, Step)> { - if !text.scrolling { - return None; - } + fn get_animation_group( + animation_groups: &mut HashMap, + number: Option, + ) -> &mut AnimationGroup { + let (number, synced) = match number { + Some(usize::MAX) | None => (usize::MAX, false), + Some(number) => (number, true), + }; - let mut new_data = false; - let animation = ctx.entry(text.text.to_string()).or_insert_with(|| { - new_data = true; + animation_groups + .entry(number) + .or_insert(AnimationGroup::new(synced)) + } - let font_size = match (text.font_size, text.text_offset) { - (Some(font_size), _) => font_size, - (None, offset_type) => font_manager.get_font_size(text.size.height, &offset_type), - }; - let text_width = text.size.width; - let character = font_manager.get_character('a', font_size); - let char_width = character.metrics.advance as usize; - let max_characters = text_width / max(char_width, 1); - let len = text.text.chars().count(); - - if len <= max_characters { - Animation::new(0, 0, 1, counter) - } else { - let settings = get_animation_settings!(settings, text); - - Animation::new( - settings.ticks_at_edge, - settings.ticks_per_move, - len - max_characters + 1, - counter, - ) + fn calculate_animation_hash(value: &H, hash: &mut Option) { + *hash = match hash { + Some(hash) => Some(*hash), + None => { + let mut s = DefaultHasher::new(); + value.hash(&mut s); + Some(s.finish()) } - }); + } + } - Some((new_data, animation.step(counter))) + fn pre_render_text(font_manager: &mut FontManager, text: &Text) -> usize { + let font_size = match (text.font_size, text.text_offset) { + (Some(font_size), _) => font_size, + (None, offset_type) => font_manager.get_font_size(text.size.height, &offset_type), + }; + let text_width = text.size.width; + let character = font_manager.get_character('a', font_size); + let char_width = character.metrics.advance as usize; + let max_characters = text_width / max(char_width, 1); + let len = text.text.chars().count(); + + if len <= max_characters { + 1 + } else { + len - max_characters + 1 + } } } @@ -377,35 +394,3 @@ impl AnimationSettings { } } } - -struct AnimationData { - text_contexts: HashMap>, - image_contexts: HashMap>, -} - -impl AnimationData { - pub fn new() -> Self { - Self { - text_contexts: HashMap::new(), - image_contexts: HashMap::new(), - } - } - - pub fn get_text_context(&mut self, ctx: &ContextKey) -> &mut HashMap { - self.text_contexts - .entry(ctx.clone()) - .or_insert(HashMap::new()) - } - - pub fn get_image_context(&mut self, ctx: &ContextKey) -> &mut HashMap { - self.image_contexts - .entry(ctx.clone()) - .or_insert(HashMap::new()) - } -} - -#[derive(Eq, Hash, PartialEq, Clone)] -pub struct ContextKey { - pub script: usize, - pub device: usize, -} diff --git a/omni-led/src/script_handler/script_data_types.rs b/omni-led/src/script_handler/script_data_types.rs index a17be41..18d7a83 100644 --- a/omni-led/src/script_handler/script_data_types.rs +++ b/omni-led/src/script_handler/script_data_types.rs @@ -18,7 +18,7 @@ use mlua::{ErrorContext, FromLua, Lua, Table, UserData, UserDataFields, Value}; use omni_led_derive::FromLuaValue; -use std::hash::{Hash, Hasher}; +use std::hash::Hash; #[derive(Debug, Clone, Copy, FromLuaValue)] pub struct Point { @@ -58,12 +58,6 @@ pub struct ImageData { pub hash: Option, } -impl Hash for ImageData { - fn hash(&self, state: &mut H) { - self.bytes.hash(state); - } -} - /// This is a private enum used just to facilitate easier parsing into image::ImageFormat type #[derive(Clone, Debug, FromLuaValue)] enum ImageFormatEnum { @@ -159,6 +153,12 @@ pub struct Bar { impl UserData for Bar {} +#[derive(FromLuaValue, Debug, PartialEq, Copy, Clone)] +pub enum Repeat { + Once, + ForDuration, +} + #[derive(Clone, Debug, FromLuaValue)] pub struct Image { pub image: ImageData, @@ -166,6 +166,9 @@ pub struct Image { pub animated: bool, #[mlua(default(128))] pub threshold: u8, + #[mlua(default(Repeat::ForDuration))] + pub repeats: Repeat, + pub animation_group: Option, pub animation_ticks_delay: Option, pub animation_ticks_rate: Option, pub position: Point, @@ -185,10 +188,15 @@ pub struct Text { pub font_size: Option, #[mlua(default(false))] pub scrolling: bool, + #[mlua(default(Repeat::ForDuration))] + pub repeats: Repeat, + pub animation_group: Option, pub animation_ticks_delay: Option, pub animation_ticks_rate: Option, pub position: Point, pub size: Size, + #[mlua(default(None))] + pub hash: Option, #[mlua(default)] pub modifiers: Modifiers, diff --git a/omni-led/src/script_handler/script_handler.rs b/omni-led/src/script_handler/script_handler.rs index 901f214..f8c4032 100644 --- a/omni-led/src/script_handler/script_handler.rs +++ b/omni-led/src/script_handler/script_handler.rs @@ -20,6 +20,7 @@ use log::warn; use mlua::{chunk, ErrorContext, FromLua, Function, Lua, Table, UserData, UserDataMethods, Value}; use omni_led_derive::{FromLuaValue, UniqueUserData}; use std::cell::RefCell; +use std::collections::HashMap; use std::rc::Rc; use std::time::Duration; @@ -29,7 +30,9 @@ use crate::create_table_with_defaults; use crate::devices::device::Device; use crate::devices::devices::{DeviceStatus, Devices}; use crate::events::shortcuts::Shortcuts; -use crate::renderer::renderer::{ContextKey, Renderer}; +use crate::renderer::animation::State; +use crate::renderer::animation_group::AnimationGroup; +use crate::renderer::renderer::Renderer; use crate::script_handler::script_data_types::{load_script_data_types, Widget}; use crate::settings::settings::get_full_path; @@ -44,11 +47,11 @@ struct DeviceContext { device: Box, name: String, layouts: Vec, + animation_groups: Vec>, marked_for_update: Vec, time_remaining: Duration, last_priority: usize, - repeats: Option, - index: usize, + state: State, } const DEFAULT_UPDATE_TIME: Duration = Duration::from_millis(1000); @@ -112,7 +115,7 @@ impl ScriptHandler { ctx.marked_for_update = vec![false; ctx.marked_for_update.len()]; ctx.time_remaining = Duration::ZERO; ctx.last_priority = 0; - ctx.repeats = None; + ctx.state = State::Finished; } None => { warn!("Device {} not found", device_name); @@ -129,18 +132,17 @@ impl ScriptHandler { let mut devices = UserDataRef::::load(lua); let device = devices.get_mut().load_device(lua, device_name.clone())?; - let device_count = self.devices.len(); let layout_count = layouts.len(); let context = DeviceContext { device, name: device_name, layouts, + animation_groups: vec![HashMap::new(); layout_count], marked_for_update: vec![false; layout_count], time_remaining: Default::default(), last_priority: 0, - repeats: None, - index: device_count, + state: State::Finished, }; self.devices.push(context); @@ -168,25 +170,21 @@ impl ScriptHandler { std::mem::swap(&mut ctx.marked_for_update, &mut marked_for_update); let mut to_update = None; - let mut update_modifier = None; + let mut new_update = false; for (priority, marked_for_update) in marked_for_update.into_iter().enumerate() { - if !ctx.time_remaining.is_zero() && ctx.last_priority < priority { - if let Some(Repeat::ForDuration) = ctx.repeats { - to_update = Some(ctx.last_priority); - update_modifier = Some(Repeat::ForDuration); - } - break; - } + let can_finish = !ctx.time_remaining.is_zero() + && ctx.last_priority < priority + && ctx.state == State::CanFinish; + let in_progress = ctx.last_priority == priority && ctx.state == State::InProgress; - if ctx.last_priority == priority && ctx.repeats == Some(Repeat::Once) { + if can_finish || in_progress { to_update = Some(ctx.last_priority); - update_modifier = Some(Repeat::Once); break; } if marked_for_update && Self::test_predicate(&ctx.layouts[priority].predicate)? { to_update = Some(priority); - update_modifier = None; + new_update = true; break; } } @@ -201,11 +199,8 @@ impl ScriptHandler { env.set("SCREEN", size)?; let output: LayoutData = ctx.layouts[to_update].layout.call(())?; - let (end_auto_repeat, image) = renderer.render( - ContextKey { - script: to_update, - device: ctx.index, - }, + let (animation_state, image) = renderer.render( + &mut ctx.animation_groups[to_update], size, output.widgets, memory_representation, @@ -213,16 +208,12 @@ impl ScriptHandler { ctx.device.update(lua, image)?; - ctx.repeats = match (output.repeats, end_auto_repeat) { - (Repeat::ForDuration, _) => Some(Repeat::ForDuration), - (Repeat::Once, false) => Some(Repeat::Once), - (_, _) => None, - }; - ctx.time_remaining = match update_modifier { - Some(Repeat::ForDuration) => ctx.time_remaining, + ctx.time_remaining = match (new_update, animation_state) { + (false, State::CanFinish) => ctx.time_remaining, _ => output.duration, }; ctx.last_priority = to_update; + ctx.state = animation_state; Ok(()) } @@ -283,21 +274,14 @@ struct Layout { run_on: Vec, } -#[derive(FromLuaValue, Debug, PartialEq, Copy, Clone)] -enum Repeat { - Once, - ForDuration, -} - #[derive(FromLuaValue, Clone)] struct LayoutData { widgets: Vec, #[mlua(transform(Self::transform_duration))] duration: Duration, - - #[mlua(default(Repeat::Once))] - repeats: Repeat, + // #[mlua(default(Repeat::Once))] + // repeats: Repeat, } impl LayoutData {