use crate::MonotonicCurve;
use crate::math::{Rounding, UnitValue, quantize};
const HALF_RANGE_MS: u32 = i32::MAX as u32;
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
enum SegmentProgress {
BeforeStart,
InSegment(u32),
PastEnd,
}
fn segment_progress(t0_ms: u32, duration_ms: u32, now_ms: u32) -> SegmentProgress {
if duration_ms == 0 {
return SegmentProgress::PastEnd;
}
let elapsed = now_ms.wrapping_sub(t0_ms);
if duration_ms <= HALF_RANGE_MS && elapsed > HALF_RANGE_MS {
SegmentProgress::BeforeStart
} else if elapsed >= duration_ms {
SegmentProgress::PastEnd
} else {
SegmentProgress::InSegment(elapsed)
}
}
#[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.wrapping_add(self.duration_ms)
}
pub fn next_deadline(&self, now_ms: u32) -> TicklessDeadline {
let end_ms = self.end_ms();
let progress = segment_progress(self.t0_ms, self.duration_ms, now_ms);
let current_t = match progress {
SegmentProgress::BeforeStart => T::zero(),
SegmentProgress::PastEnd => T::one(),
SegmentProgress::InSegment(elapsed) => {
if elapsed == 0 {
T::zero()
} else {
T::from_time_frac(elapsed, self.duration_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 matches!(progress, SegmentProgress::PastEnd) || current_val == end_val_q {
let deadline_ms = if matches!(progress, SegmentProgress::PastEnd) {
now_ms
} else {
end_ms
};
return TicklessDeadline {
deadline_ms,
current_val,
};
}
let (now_off, min_off) = match progress {
SegmentProgress::InSegment(elapsed) => {
(elapsed, elapsed.saturating_add(self.min_dt_ms))
}
SegmentProgress::BeforeStart => (
0,
self.min_dt_ms
.saturating_sub(self.t0_ms.wrapping_sub(now_ms)),
),
SegmentProgress::PastEnd => (self.duration_ms, self.duration_ms),
};
let mut dl_off = self.first_quantized_change_offset(now_off, current_val);
if dl_off < min_off {
dl_off = min_off;
}
if dl_off > self.duration_ms {
dl_off = self.duration_ms;
}
if dl_off < now_off {
dl_off = now_off;
}
TicklessDeadline {
deadline_ms: self.t0_ms.wrapping_add(dl_off),
current_val,
}
}
fn quantized_at_offset(&self, elapsed_ms: u32) -> u16 {
let t = if elapsed_ms == 0 {
T::zero()
} else {
T::from_time_frac(elapsed_ms, self.duration_ms)
};
let raw = self.curve.eval(t).lerp_u16(self.start_val, self.end_val);
quantize(raw, self.step, self.rounding)
}
fn first_quantized_change_offset(&self, now_off: u32, current_val: u16) -> u32 {
let mut lo = now_off;
let mut hi = self.duration_ms;
if self.quantized_at_offset(hi) == current_val {
return hi;
}
while lo + 1 < hi {
let mid = lo + (hi - lo) / 2;
if self.quantized_at_offset(mid) != current_val {
hi = mid;
} else {
lo = mid;
}
}
hi
}
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,
}
}
}
#[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 {
if self.schedule.duration_ms == 0 {
return false;
}
match self.schedule.repeat {
RepeatMode::Once => false,
RepeatMode::Repeat => {
self.t0_ms = self.t0_ms.wrapping_add(self.schedule.duration_ms);
true
}
RepeatMode::PingPong => {
self.t0_ms = self.t0_ms.wrapping_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);
if dl.current_val == end_val_q {
if !self.advance_cycle() {
self.done = true;
} else {
self.now_ms = end_ms;
}
} else if dl.deadline_ms == self.now_ms {
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,
{
}