use log::trace; use std::any::type_name; use std::fmt::Debug; use std::ops::Deref; use std::time::Instant; #[derive(Debug, Clone)] struct StateEntry { state: T, valid_until: Instant, priority: u8, } #[derive(Debug, Clone)] pub struct CommandedState { entries: [Option>; N], default: T, changed: bool, } impl CommandedState where T: Debug, { pub fn new(default: T) -> Self { trace!( "CommandedState::<{}>::new(default: {default:?})", type_name::() ); Self { entries: [const { None }; N], default, changed: true, } } pub fn consume_changed(&mut self) -> bool { trace!( "CommandedState::<{}>::consume_changed(self: {self:?})", type_name::() ); let res = self.changed; self.changed = false; res } fn evaluate_change_if(&mut self, current_time: Instant, f: F) -> &T where F: FnOnce(&T, &T) -> bool, { trace!( "CommandedState::<{}>::evaluate_change_if(self: {self:?}, current_time: {current_time:?}, f: {})", type_name::(), type_name::() ); let mut original_value: Option> = None; for i in 0..N { if let Some(entry) = &self.entries[0] { if entry.valid_until >= current_time { if let Some(original_value) = original_value { self.changed |= f(&original_value.state, &entry.state); } // SAFETY: we just checked that this index is Some // Unfortunately we can't use entry in order to satisfy the borrow checker return self.entries[0].as_ref().map(|x| &x.state).unwrap(); } // An expired entry - we need to remove it // Empty out this entry (keeping it in original value in case it is needed) if i == 0 { original_value = self.entries[0].take(); } else { self.entries[0] = None; } // Bump up all future entries self.entries.rotate_left(1); } else { break; } } if let Some(original_value) = original_value { self.changed |= f(&original_value.state, &self.default); } &self.default } fn insert_change_if(&mut self, state: T, valid_until: Instant, priority: u8, f: F) where F: FnOnce(&T, &T) -> bool, { trace!( "CommandedState::<{}>::insert_change_if(self: {self:?}, state: {state:?}, valid_until: {valid_until:?}, priority: {priority}, f: {})", type_name::(), type_name::() ); for i in 0..N { if let Some(entry) = &mut self.entries[i] { // The current entry exists - let's find out it replacing it is an option if priority >= entry.priority { // We have enough priority to modify this entry if priority == entry.priority || entry.valid_until <= valid_until { // If we are same priority (always replace) or // we will be valid for longer then we can just replace this entry if i == 0 { self.changed |= f(&state, &entry.state); } *entry = StateEntry { state, valid_until, priority, }; // Remove entries which will expire before this one for j in (i + 1)..N { if let Some(later_entry) = &self.entries[j] { // If the later entry exists and expires before this one if later_entry.valid_until <= valid_until { self.entries[j] = None; self.entries[j..].rotate_left(1); } } } return; } // We want to add a higher priority entry, but we will expire first if i == 0 { self.changed |= f(&state, &entry.state); } self.entries[i..].rotate_right(1); self.entries[i] = Some(StateEntry { state, valid_until, priority, }); return; } } else { // No entry exists so we can just put ourselves there if i == 0 { self.changed |= f(&state, &self.default); } self.entries[i] = Some(StateEntry { state, valid_until, priority, }); return; } } // If we got here then we didn't find a valid place to insert it } #[allow(unused)] #[inline] pub fn evaluate_changed(&mut self, current_time: Instant) -> &T { self.evaluate_change_if(current_time, |_, _| true) } #[allow(unused)] #[inline] pub fn insert_changed(&mut self, state: T, valid_until: Instant, priority: u8) { self.insert_change_if(state, valid_until, priority, |_, _| true); } } impl CommandedState where T: PartialEq + Debug, { #[inline] pub fn evaluate(&mut self, current_time: Instant) -> &T { self.evaluate_change_if(current_time, T::ne) } #[inline] pub fn insert(&mut self, state: T, valid_until: Instant, priority: u8) { self.insert_change_if(state, valid_until, priority, T::ne); } } impl Deref for CommandedState { type Target = T; fn deref(&self) -> &Self::Target { for i in 0..N { if let Some(entry) = &self.entries[i] { return &entry.state; } } &self.default } } #[cfg(test)] mod tests { use super::*; use log::LevelFilter; use std::time::Duration; #[test] fn commanded_state() { let _ = env_logger::builder() .is_test(true) .filter_level(LevelFilter::Trace) .try_init(); let mut state = CommandedState::<_, 2>::new(4); let now = Instant::now(); assert!(!state.consume_changed()); assert_eq!(4, *state.evaluate(now)); assert!(!state.consume_changed()); state.insert(5, now, 0); assert!(state.consume_changed()); assert!(!state.consume_changed()); assert_eq!(5, *state.evaluate(now)); assert!(!state.consume_changed()); let now = now + Duration::from_secs(1); assert_eq!(4, *state.evaluate(now)); assert!(state.consume_changed()); state.insert(6, now + Duration::from_secs(2), 0); assert!(state.consume_changed()); assert_eq!(6, *state.evaluate(now)); assert!(!state.consume_changed()); state.insert(7, now + Duration::from_secs(1), 1); assert!(state.consume_changed()); assert_eq!(7, *state); state.insert(8, now + Duration::from_secs(3), 0); assert!(!state.consume_changed()); assert_eq!(7, *state); state.insert(9, now + Duration::from_secs(2), 1); assert!(state.consume_changed()); assert_eq!(9, *state); assert_eq!(9, *state.evaluate(now)); assert!(!state.consume_changed()); let now = now + Duration::from_secs(2); assert_eq!(9, *state.evaluate(now)); assert!(!state.consume_changed()); let now = now + Duration::from_secs(1); assert_eq!(8, *state.evaluate(now)); assert!(state.consume_changed()); let now = now + Duration::from_secs(1); assert_eq!(4, *state.evaluate(now)); assert!(state.consume_changed()); state.insert(10, now + Duration::from_secs(1), 0); state.insert(11, now, 1); state.insert(12, now + Duration::from_secs(1), 1); assert!(state.consume_changed()); assert_eq!(12, *state); let now = now + Duration::from_secs(2); assert_eq!(4, *state.evaluate(now)); assert!(state.consume_changed()); state.insert(13, now + Duration::from_secs(1), 0); state.insert(14, now, 1); assert_eq!(14, *state); let now = now + Duration::from_secs(2); assert_eq!(4, *state.evaluate(now)); assert!(state.consume_changed()); } #[test] fn changed() { let _ = env_logger::builder() .is_test(true) .filter_level(LevelFilter::Trace) .try_init(); let mut state = CommandedState::<_, 2>::new(4); let now = Instant::now(); assert!(!state.consume_changed()); assert_eq!(4, *state); state.insert(4, now + Duration::from_secs(1), 0); assert!(!state.consume_changed()); let now = Instant::now() + Duration::from_secs(2); assert_eq!(4, *state.evaluate_changed(now)); assert!(state.consume_changed()); state.insert_changed(4, now + Duration::from_secs(1), 0); assert!(state.consume_changed()); let now = Instant::now() + Duration::from_secs(2); assert_eq!(4, *state.evaluate(now)); } }