use crate::point::LaserPoint;
use super::{Frame, TransitionFn, TransitionPlan};
#[cfg_attr(feature = "testutils", doc(hidden))]
#[cfg_attr(not(feature = "testutils"), allow(dead_code))]
pub struct PresentationEngine {
pub(crate) current_base: Option<Frame>,
pub(crate) pending_base: Option<Frame>,
drawable: Vec<LaserPoint>,
drawable_dirty: bool,
cursor: usize,
transition_fn: TransitionFn,
transition_buf: Vec<LaserPoint>,
transition_cursor: usize,
transition_is_self_loop: bool,
frame_swap_transition_len: usize,
frame_capacity: Option<usize>,
}
impl PresentationEngine {
pub fn new(transition_fn: TransitionFn) -> Self {
Self {
current_base: None,
pending_base: None,
drawable: Vec::new(),
drawable_dirty: true,
cursor: 0,
transition_fn,
transition_buf: Vec::new(),
transition_cursor: 0,
transition_is_self_loop: false,
frame_swap_transition_len: 0,
frame_capacity: None,
}
}
pub fn set_frame_capacity(&mut self, cap: Option<usize>) {
self.frame_capacity = cap;
}
pub fn reset(&mut self) {
self.current_base = None;
self.pending_base = None;
self.drawable.clear();
self.drawable_dirty = true;
self.cursor = 0;
self.transition_buf.clear();
self.transition_cursor = 0;
self.transition_is_self_loop = false;
self.frame_swap_transition_len = 0;
}
pub fn has_logical_frame(&self) -> bool {
self.current_base.is_some()
}
pub fn set_pending(&mut self, frame: Frame) {
if self.current_base.is_none() {
self.current_base = Some(frame);
self.drawable_dirty = true;
self.cursor = 0;
} else {
self.pending_base = Some(frame);
}
}
pub fn fill_chunk(&mut self, buffer: &mut [LaserPoint], max_points: usize) -> usize {
let max_points = max_points.min(buffer.len());
if self.drawable_dirty {
self.refresh_drawable();
}
if self.current_base.is_none() || self.drawable.is_empty() {
buffer[..max_points].fill(LaserPoint::blanked(0.0, 0.0));
return max_points;
}
let mut written = 0;
while written < max_points {
if self.transition_cursor < self.transition_buf.len() {
if self.transition_is_self_loop
&& self.pending_base.is_some()
&& self.transition_cursor == 0
{
self.transition_buf.clear();
self.transition_is_self_loop = false;
self.promote_pending();
if self.drawable.is_empty() {
buffer[written..max_points].fill(LaserPoint::blanked(0.0, 0.0));
return max_points;
}
continue;
}
let src = &self.transition_buf[self.transition_cursor..];
let n = src.len().min(max_points - written);
buffer[written..written + n].copy_from_slice(&src[..n]);
written += n;
self.transition_cursor += n;
continue;
}
let src = &self.drawable[self.cursor..];
let n = src.len().min(max_points - written);
buffer[written..written + n].copy_from_slice(&src[..n]);
written += n;
self.cursor += n;
if self.cursor >= self.drawable.len() {
if self.pending_base.is_some() {
self.promote_pending();
if self.drawable.is_empty() {
buffer[written..max_points].fill(LaserPoint::blanked(0.0, 0.0));
return max_points;
}
} else {
let last = self.drawable.last().unwrap();
let first = self.drawable.first().unwrap();
match (self.transition_fn)(last, first) {
TransitionPlan::Transition(points) => {
self.transition_buf = points;
self.transition_cursor = 0;
self.transition_is_self_loop = true;
self.cursor = 0;
}
TransitionPlan::Coalesce => {
self.cursor = if self.drawable.len() > 1 { 1 } else { 0 };
}
}
}
}
}
written
}
pub fn compose_hardware_frame(&mut self) -> &[LaserPoint] {
if let Some(pending) = self.pending_base.take() {
let plan = match (
self.current_base.as_ref().and_then(|c| c.last_point()),
pending.first_point(),
) {
(Some(last), Some(first)) => (self.transition_fn)(last, first),
_ => TransitionPlan::Transition(vec![]),
};
self.drawable.clear();
match plan {
TransitionPlan::Transition(transition) => {
self.frame_swap_transition_len = transition.len();
self.drawable.extend_from_slice(&transition);
self.drawable.extend_from_slice(pending.points());
}
TransitionPlan::Coalesce => {
self.frame_swap_transition_len = 0;
let pts = pending.points();
self.drawable
.extend_from_slice(if pts.len() > 1 { &pts[1..] } else { pts });
}
}
if self.drawable.is_empty() {
self.drawable.push(LaserPoint::blanked(0.0, 0.0));
}
self.clamp_to_capacity();
self.current_base = Some(pending);
self.drawable_dirty = true;
return &self.drawable;
}
if self.drawable_dirty {
self.refresh_drawable_for_frame_swap();
}
&self.drawable
}
fn refresh_drawable_for_frame_swap(&mut self) {
self.drawable.clear();
self.drawable_dirty = false;
self.frame_swap_transition_len = 0;
let Some(current) = &self.current_base else {
return;
};
if current.is_empty() {
self.drawable.push(LaserPoint::blanked(0.0, 0.0));
return;
}
let points = current.points();
self.frame_swap_transition_len =
build_self_loop_drawable(&self.transition_fn, points, &mut self.drawable);
self.clamp_to_capacity();
}
fn clamp_to_capacity(&mut self) {
if let Some(cap) = self.frame_capacity {
if self.drawable.len() > cap {
let excess = self.drawable.len() - cap;
let trim = excess.min(self.frame_swap_transition_len);
self.drawable.drain(..trim);
self.frame_swap_transition_len -= trim;
}
}
}
fn promote_pending(&mut self) {
let pending = self.pending_base.take().unwrap();
let plan = match (
self.current_base.as_ref().and_then(|f| f.last_point()),
pending.first_point(),
) {
(Some(last), Some(first)) => (self.transition_fn)(last, first),
_ => TransitionPlan::Transition(vec![]),
};
self.current_base = Some(pending);
self.refresh_drawable();
match plan {
TransitionPlan::Transition(points) => {
self.transition_buf = points;
self.transition_cursor = 0;
self.transition_is_self_loop = false;
self.cursor = 0;
}
TransitionPlan::Coalesce => {
self.cursor = if self.drawable.len() > 1 { 1 } else { 0 };
}
}
}
fn refresh_drawable(&mut self) {
self.drawable.clear();
self.drawable_dirty = false;
let Some(current) = &self.current_base else {
return;
};
if current.is_empty() {
return;
}
self.drawable.extend_from_slice(current.points());
}
}
fn build_self_loop_drawable(
transition_fn: &TransitionFn,
base: &[LaserPoint],
drawable: &mut Vec<LaserPoint>,
) -> usize {
let last = base.last().unwrap();
let first = base.first().unwrap();
let plan = transition_fn(last, first);
match plan {
TransitionPlan::Transition(pts) => {
let transition_len = pts.len();
drawable.extend_from_slice(&pts);
drawable.extend_from_slice(base);
transition_len
}
TransitionPlan::Coalesce => {
let end = if base.len() > 1 {
base.len() - 1
} else {
base.len()
};
drawable.extend_from_slice(&base[..end]);
0
}
}
}
pub(crate) struct ColorDelayLine {
delay: usize,
carry: Vec<(u16, u16, u16, u16)>,
scratch: Vec<(u16, u16, u16, u16)>,
}
impl ColorDelayLine {
pub fn new(delay: usize) -> Self {
Self {
delay,
carry: vec![(0, 0, 0, 0); delay],
scratch: Vec::new(),
}
}
#[allow(dead_code)]
pub fn delay(&self) -> usize {
self.delay
}
pub fn reset(&mut self) {
self.carry.fill((0, 0, 0, 0));
}
pub fn resize(&mut self, new_delay: usize) {
if new_delay == self.delay {
return;
}
if new_delay == 0 {
self.delay = 0;
self.carry.clear();
return;
}
if new_delay > self.delay {
let extra = new_delay - self.delay;
let mut new_carry = vec![(0, 0, 0, 0); extra];
new_carry.extend_from_slice(&self.carry);
self.carry = new_carry;
} else {
self.carry.drain(..self.delay - new_delay);
}
self.delay = new_delay;
}
pub fn apply(&mut self, points: &mut [LaserPoint]) {
if self.delay == 0 || points.is_empty() {
return;
}
self.scratch.clear();
self.scratch
.extend(points.iter().map(|p| (p.r, p.g, p.b, p.intensity)));
for (i, point) in points.iter_mut().enumerate() {
(point.r, point.g, point.b, point.intensity) = if i < self.delay {
self.carry[i]
} else {
self.scratch[i - self.delay]
};
}
let n = self.scratch.len();
if n >= self.delay {
self.carry.clear();
self.carry
.extend_from_slice(&self.scratch[n - self.delay..]);
} else {
self.carry.drain(..n);
self.carry.extend_from_slice(&self.scratch);
debug_assert_eq!(self.carry.len(), self.delay);
}
}
}