use std::{
borrow::Cow,
collections::HashMap,
time::{Duration, Instant},
};
use gled_gpu::constants::UNIVERSE_BUFFER_SIZE;
use super::Recipient;
const EXPIRY: Duration = Duration::from_secs(1);
type Channels = [u8; UNIVERSE_BUFFER_SIZE as usize];
#[derive(Debug, Clone, Copy)]
struct Gled {
at: Instant,
changed: Instant,
black: bool,
}
impl Gled {
fn new(now: Instant) -> Self {
Self {
at: now,
changed: now,
black: false,
}
}
fn send(&mut self, data: &Channels, now: Instant) {
let black = is_black(data);
if !(self.black && black) {
self.changed = now;
}
self.black = black;
self.at = now;
}
fn is_fresh(&self, now: Instant) -> bool {
now.saturating_duration_since(self.at) < EXPIRY
}
}
#[derive(Debug)]
struct Input {
at: Instant,
data: Channels,
htp: bool,
}
#[derive(Debug, Default)]
pub(super) struct Merge {
inputs: HashMap<Recipient, Input>,
slots: Vec<Gled>,
frame: Vec<u8>,
extras: HashMap<Recipient, (Gled, Channels)>,
}
impl Merge {
pub(super) fn slot<'a>(
&mut self,
slot: usize,
recipient: &Recipient,
data: &'a Channels,
now: Instant,
) -> Cow<'a, Channels> {
if self.slots.len() <= slot {
self.slots.resize(slot + 1, Gled::new(now));
}
let gled = &mut self.slots[slot];
gled.send(data, now);
if self.inputs.is_empty() {
return Cow::Borrowed(data);
}
match self.inputs.get(recipient) {
Some(input) if is_fresh(input.at, now) => Cow::Owned(merged(gled, data, input)),
_ => Cow::Borrowed(data),
}
}
pub(super) fn keep_frame(&mut self, frame: Vec<u8>) {
self.frame = frame;
}
pub(super) fn extra(&mut self, recipient: Recipient, data: Channels, now: Instant) -> Channels {
let input = self
.inputs
.get(&recipient)
.filter(|input| is_fresh(input.at, now));
let (gled, kept) = self
.extras
.entry(recipient)
.or_insert_with(|| (Gled::new(now), data));
gled.send(&data, now);
*kept = data;
match input {
Some(input) => merged(gled, &data, input),
None => data,
}
}
pub(super) fn input(
&mut self,
recipient: Recipient,
data: Channels,
htp: bool,
slot: Option<usize>,
now: Instant,
) -> Channels {
let input = Input { at: now, data, htp };
let gled = slot
.and_then(|slot| {
let gled = self.slots.get(slot)?;
let channels = self
.frame
.as_chunks::<{ UNIVERSE_BUFFER_SIZE as usize }>()
.0;
Some((gled, channels.get(slot)?))
})
.or_else(|| self.extras.get(&recipient).map(|(gled, data)| (gled, data)))
.filter(|(gled, _)| gled.is_fresh(now));
let out = match gled {
Some((gled, gled_data)) => merged(gled, gled_data, &input),
None => data,
};
self.inputs.insert(recipient, input);
out
}
pub(super) fn expire(&mut self, now: Instant) {
self.inputs.retain(|_, input| is_fresh(input.at, now));
self.extras.retain(|_, (gled, _)| gled.is_fresh(now));
}
}
fn is_fresh(at: Instant, now: Instant) -> bool {
now.saturating_duration_since(at) < EXPIRY
}
fn merged(gled: &Gled, gled_data: &Channels, input: &Input) -> Channels {
if input.htp {
let mut out = *gled_data;
for (out, input) in out.iter_mut().zip(&input.data) {
*out = (*out).max(*input);
}
out
} else if input.at > gled.changed {
input.data
} else {
*gled_data
}
}
fn is_black(data: &Channels) -> bool {
data.as_chunks::<16>()
.0
.iter()
.all(|chunk| u128::from_ne_bytes(*chunk) == 0)
}
#[cfg(test)]
mod tests {
use super::*;
fn node() -> Recipient {
Recipient::Artnet {
addr: "10.0.0.1:6454".parse().expect("an address"),
universe: 3,
}
}
fn lit(value: u8) -> Channels {
let mut data = [0; 512];
data[0] = value;
data[511] = value;
data
}
fn later(now: Instant, millis: u64) -> Instant {
now + Duration::from_millis(millis)
}
#[test]
fn without_art_net_gled_goes_out_as_it_is() {
let mut merge = Merge::default();
let data = lit(9);
assert!(matches!(
merge.slot(0, &node(), &data, Instant::now()),
Cow::Borrowed(sent) if *sent == data
));
}
#[test]
fn art_net_without_gled_passes_through() {
let mut merge = Merge::default();
assert_eq!(
merge.input(node(), lit(5), false, Some(0), Instant::now()),
lit(5)
);
}
#[test]
fn htp_takes_the_higher_value_of_each_channel() {
let now = Instant::now();
let mut merge = Merge::default();
let mut gled = lit(40);
gled[7] = 1;
merge.slot(0, &node(), &gled, now);
merge.keep_frame(gled.to_vec());
let mut input = lit(20);
input[7] = 90;
let out = merge.input(node(), input, true, Some(0), later(now, 1));
assert_eq!((out[0], out[7], out[511]), (40, 90, 40));
let next = merge.slot(0, &node(), &gled, later(now, 2));
assert_eq!((next[0], next[7]), (40, 90));
}
#[test]
fn ltp_follows_whichever_changed_last() {
let now = Instant::now();
let mut merge = Merge::default();
merge.slot(0, &node(), &lit(40), now);
merge.keep_frame(lit(40).to_vec());
assert_eq!(
merge.input(node(), lit(7), false, Some(0), later(now, 1)),
lit(7)
);
assert_eq!(*merge.slot(0, &node(), &lit(40), later(now, 2)), lit(40));
}
#[test]
fn art_net_keeps_ltp_while_gled_stays_black() {
let now = Instant::now();
let mut merge = Merge::default();
merge.slot(0, &node(), &[0; 512], now);
merge.slot(0, &node(), &[0; 512], later(now, 1));
merge.input(node(), lit(7), false, Some(0), later(now, 2));
assert_eq!(*merge.slot(0, &node(), &[0; 512], later(now, 3)), lit(7));
}
#[test]
fn art_net_counts_for_a_second() {
let now = Instant::now();
let mut merge = Merge::default();
merge.input(node(), lit(7), true, Some(0), now);
let after = later(now, 1_001);
merge.expire(after);
assert_eq!(*merge.slot(0, &node(), &lit(1), after), lit(1));
}
#[test]
fn overwrites_outside_the_patch_merge_like_universes() {
let now = Instant::now();
let mut merge = Merge::default();
merge.extra(node(), lit(30), now);
let out = merge.input(node(), lit(60), true, None, later(now, 1));
assert_eq!(out, lit(60));
assert_eq!(merge.extra(node(), lit(90), later(now, 2)), lit(90));
}
#[test]
fn black_is_every_channel_zero() {
assert!(is_black(&[0; 512]));
assert!(!is_black(&lit(1)));
let mut last = [0; 512];
last[511] = 1;
assert!(!is_black(&last));
}
}