use crate::MonotonicCurve;
use crate::math::{Rounding, UnitValue, next_target_value, quantize};
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub enum RepeatMode {
Once,
Repeat,
PingPong,
}
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub struct TicklessDeadline {
pub deadline_ms: u32,
pub current_val: u16,
}
#[derive(Copy, Clone, Debug)]
pub struct TicklessSchedule<C, T: UnitValue = u8> {
curve: C,
t0_ms: u32,
duration_ms: u32,
start_val: u16,
end_val: u16,
step: u16,
rounding: Rounding,
min_dt_ms: u32,
repeat: RepeatMode,
_marker: core::marker::PhantomData<T>,
}
impl<C, T> TicklessSchedule<C, T>
where
C: MonotonicCurve<T, T>,
T: UnitValue,
{
pub fn new(
curve: C,
t0_ms: u32,
duration_ms: u32,
start_val: u16,
end_val: u16,
step: u16,
rounding: Rounding,
min_dt_ms: u32,
) -> Self {
Self {
curve,
t0_ms,
duration_ms,
start_val,
end_val,
step: step.max(1),
rounding,
min_dt_ms,
repeat: RepeatMode::Once,
_marker: core::marker::PhantomData,
}
}
pub fn with_repeat(mut self, mode: RepeatMode) -> Self {
self.repeat = mode;
self
}
pub fn end_ms(&self) -> u32 {
self.t0_ms.saturating_add(self.duration_ms)
}
pub fn next_deadline(&self, now_ms: u32) -> TicklessDeadline {
let end_ms = self.end_ms();
let current_t = self.time_to_t(now_ms);
let w = self.curve.eval(current_t);
let raw_val = w.lerp_u16(self.start_val, self.end_val);
let current_val = quantize(raw_val, self.step, self.rounding);
let end_val_q = quantize(self.end_val, self.step, self.rounding);
if now_ms >= end_ms || current_val == end_val_q {
return TicklessDeadline {
deadline_ms: end_ms.max(now_ms),
current_val,
};
}
let increasing = self.end_val >= self.start_val;
let target_val = next_target_value(current_val, end_val_q, self.step, increasing);
let w_target = T::inv_lerp_u16(self.start_val, self.end_val, target_val);
let u_target = self.curve.inv(w_target);
let mut deadline_ms = self.t_to_time(u_target);
let min_deadline = now_ms.saturating_add(self.min_dt_ms);
if deadline_ms < min_deadline {
deadline_ms = min_deadline;
}
if deadline_ms > end_ms {
deadline_ms = end_ms;
}
if deadline_ms < now_ms {
deadline_ms = now_ms;
}
TicklessDeadline {
deadline_ms,
current_val,
}
}
pub fn iter(&self, now_ms: u32) -> TicklessIter<'_, C, T> {
TicklessIter {
schedule: self,
t0_ms: self.t0_ms,
start_val: self.start_val,
end_val: self.end_val,
now_ms,
done: false,
}
}
fn time_to_t(&self, now_ms: u32) -> T {
if self.duration_ms == 0 || now_ms >= self.end_ms() {
return T::one();
}
if now_ms <= self.t0_ms {
return T::zero();
}
T::from_time_frac(now_ms - self.t0_ms, self.duration_ms)
}
fn t_to_time(&self, t: T) -> u32 {
self.t0_ms
.saturating_add(t.to_time_offset(self.duration_ms))
}
}
#[derive(Debug)]
pub struct TicklessIter<'a, C, T: UnitValue = u8> {
schedule: &'a TicklessSchedule<C, T>,
t0_ms: u32,
start_val: u16,
end_val: u16,
now_ms: u32,
done: bool,
}
impl<C, T> TicklessIter<'_, C, T>
where
C: MonotonicCurve<T, T> + Copy,
T: UnitValue,
{
fn cycle_schedule(&self) -> TicklessSchedule<C, T> {
TicklessSchedule {
curve: self.schedule.curve,
t0_ms: self.t0_ms,
duration_ms: self.schedule.duration_ms,
start_val: self.start_val,
end_val: self.end_val,
step: self.schedule.step,
rounding: self.schedule.rounding,
min_dt_ms: self.schedule.min_dt_ms,
repeat: RepeatMode::Once,
_marker: core::marker::PhantomData,
}
}
fn advance_cycle(&mut self) -> bool {
match self.schedule.repeat {
RepeatMode::Once => false,
RepeatMode::Repeat => {
self.t0_ms = self.t0_ms.saturating_add(self.schedule.duration_ms);
true
}
RepeatMode::PingPong => {
self.t0_ms = self.t0_ms.saturating_add(self.schedule.duration_ms);
core::mem::swap(&mut self.start_val, &mut self.end_val);
true
}
}
}
}
impl<C, T> Iterator for TicklessIter<'_, C, T>
where
C: MonotonicCurve<T, T> + Copy,
T: UnitValue,
{
type Item = TicklessDeadline;
fn next(&mut self) -> Option<TicklessDeadline> {
if self.done {
return None;
}
let cycle = self.cycle_schedule();
let dl = cycle.next_deadline(self.now_ms);
let end_ms = cycle.end_ms();
let end_val_q = quantize(self.end_val, self.schedule.step, self.schedule.rounding);
let cycle_finished = dl.deadline_ms >= end_ms || dl.current_val == end_val_q;
if cycle_finished {
if !self.advance_cycle() {
self.done = true;
} else {
self.now_ms = end_ms;
}
} else {
self.now_ms = dl.deadline_ms;
}
Some(dl)
}
}
pub trait Tickless<T: UnitValue>: MonotonicCurve<T, T> + Sized + Copy {
fn tickless_schedule(
self,
t0_ms: u32,
duration_ms: u32,
start_val: u16,
end_val: u16,
step: u16,
rounding: Rounding,
min_dt_ms: u32,
) -> TicklessSchedule<Self, T> {
TicklessSchedule::new(
self,
t0_ms,
duration_ms,
start_val,
end_val,
step,
rounding,
min_dt_ms,
)
}
}
impl<C, T> Tickless<T> for C
where
C: MonotonicCurve<T, T> + Copy,
T: UnitValue,
{
}