use crate::collection::Collections;
use crate::patching::Patch;
use crate::patching::definition::ColorOrder;
use serde::{Deserialize, Serialize};
use std::collections::BTreeMap;
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
pub struct ColorOrders {
universes: BTreeMap<u16, Vec<ColorOrder>>,
}
impl ColorOrders {
pub fn set(&mut self, universe: u16, lamp: usize, order: ColorOrder) {
if order.is_identity() && !self.universes.contains_key(&universe) {
return;
}
let lamps = self.universes.entry(universe).or_default();
if lamps.len() <= lamp {
lamps.resize(lamp + 1, ColorOrder::Rgb);
}
lamps[lamp] = order;
}
pub fn order(&self, universe: u16, lamp: usize) -> ColorOrder {
self.universes
.get(&universe)
.and_then(|lamps| lamps.get(lamp))
.copied()
.unwrap_or_default()
}
pub fn is_empty(&self) -> bool {
self.universes.is_empty()
}
pub fn correct(&self, universe: u16, data: &mut [u8]) {
let Some(lamps) = self.universes.get(&universe) else {
return;
};
for (triple, order) in data.as_chunks_mut::<3>().0.iter_mut().zip(lamps) {
order.correct(triple);
}
}
}
impl Patch {
pub fn color_orders(&self, collections: &Collections) -> ColorOrders {
let mut orders = ColorOrders::default();
for (id, address) in self.addresses(collections) {
let Some(definition) = self.definition(id, collections) else {
continue;
};
let definition = &definition.data;
for (index, pixel) in definition.pixels.iter().enumerate() {
let Some(order) = pixel.color_order() else {
continue;
};
let lamp = address.lamp_index(definition.channel_offset(index));
orders.set(address.universe(), lamp, order);
}
}
orders
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::asset_id::AssetId;
use crate::patching::definition::FixtureDefinition;
use crate::patching::fixture::{Fixture, FixtureId, LayoutPosition};
use crate::test_support::{collections_with, rgb_bar};
fn bar(pixel_count: usize) -> FixtureDefinition {
rgb_bar(pixel_count, pixel_count as f32 * 0.1)
}
fn add(patch: &mut Patch, definition: AssetId<FixtureDefinition>) -> FixtureId {
patch.add_fixture(
Fixture::with_definition("bar", definition),
LayoutPosition::default(),
)
}
#[test]
fn a_grb_pixel_puts_its_order_into_its_lamp_slot() {
let mut definition = bar(4);
definition.pixels[2].set_color_order(ColorOrder::Grb);
let (collections, ids) = collections_with(vec![definition]);
let mut patch = Patch::default();
add(&mut patch, ids[0]);
let orders = patch.color_orders(&collections);
assert_eq!(orders.order(0, 2), ColorOrder::Grb);
assert_eq!(orders.order(0, 0), ColorOrder::Rgb);
}
#[test]
fn color_orders_follow_the_chain_address() {
let mut grb = bar(4);
grb.pixels[0].set_color_order(ColorOrder::Grb);
let (collections, ids) = collections_with(vec![bar(4), grb]);
let mut patch = Patch::default();
let head = add(&mut patch, ids[0]);
let tail = add(&mut patch, ids[1]);
patch.topology.make_successor(head, tail);
assert_eq!(
patch.color_orders(&collections).order(0, 4),
ColorOrder::Grb
);
}
#[test]
fn an_rgb_patch_needs_no_reordering() {
let (collections, ids) = collections_with(vec![bar(4)]);
let mut patch = Patch::default();
add(&mut patch, ids[0]);
assert!(patch.color_orders(&collections).is_empty());
}
#[test]
fn correcting_a_universe_touches_only_its_permuted_lamps() {
let mut orders = ColorOrders::default();
orders.set(0, 2, ColorOrder::Grb);
let mut data = [0u8; 512];
data[0..3].copy_from_slice(&[1, 2, 3]);
data[6..9].copy_from_slice(&[1, 2, 3]);
let mut other_universe = data;
orders.correct(0, &mut data);
orders.correct(1, &mut other_universe);
assert_eq!(&data[0..3], &[1, 2, 3]);
assert_eq!(&data[6..9], &[2, 1, 3]);
assert_eq!(&other_universe[6..9], &[1, 2, 3]);
}
}