use crate::naming::unique_name;
use crate::patching::fixture::{Fixture, FixtureId, LayoutPosition};
use crate::patching::layout::Layout;
use bimap::BiMap;
use serde::{Deserialize, Serialize};
use std::collections::{BTreeMap, BTreeSet};
#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, Default)]
#[serde(default)]
pub struct Port {
pub universe: u16,
pub channel: u16,
}
impl Port {
pub fn new(universe: u16) -> Self {
Self {
universe,
channel: 0,
}
}
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
#[serde(default)]
pub struct Topology {
pub fixtures: BTreeMap<FixtureId, Fixture>,
pub default_layout: Layout,
pub edges: BiMap<FixtureId, FixtureId>,
pub ports: BTreeMap<FixtureId, Port>,
}
impl Default for Topology {
fn default() -> Self {
Self {
fixtures: BTreeMap::new(),
default_layout: Layout::new(Layout::DEFAULT_NAME),
edges: BiMap::new(),
ports: BTreeMap::new(),
}
}
}
impl Topology {
pub fn add_fixture(&mut self, mut fixture: Fixture, position: LayoutPosition) -> FixtureId {
fixture.name = unique_name(
&fixture.name,
self.fixtures.values().map(|other| other.name.as_str()),
);
let id = fixture.id;
self.fixtures.insert(id, fixture);
self.default_layout.members.insert(id, position);
self.ports
.insert(id, Port::new(self.lowest_unused_universe()));
debug_assert!(self.invariant_holds());
id
}
pub fn remove_fixture(&mut self, id: FixtureId, layouts: &mut [Layout]) {
self.make_leaf(id);
self.edges.remove_by_right(&id);
self.ports.remove(&id);
self.fixtures.remove(&id);
self.default_layout.forget(id);
for layout in layouts {
layout.forget(id);
}
debug_assert!(self.invariant_holds());
}
pub fn make_leaf(&mut self, id: FixtureId) {
if let Some((_, successor)) = self.edges.remove_by_left(&id) {
self.ports
.insert(successor, Port::new(self.lowest_unused_universe()));
}
}
pub fn chain_head_of(&self, id: FixtureId) -> FixtureId {
let mut head = id;
let mut seen = 0;
while let Some(parent) = self.edges.get_by_right(&head).copied() {
head = parent;
seen += 1;
if seen > self.fixtures.len() {
break;
}
}
head
}
pub fn make_successor(&mut self, parent: FixtureId, child: FixtureId) -> bool {
if parent == child || self.is_reachable(child, parent) {
return false;
}
let child_leads_chain = self.successor(child).is_some();
self.make_leaf(parent);
self.edges.remove_by_right(&child);
let inherited = self.ports.remove(&child);
self.edges.insert(parent, child);
if let Some(port) = inherited
&& child_leads_chain
{
let head = self.chain_head_of(parent);
self.ports.insert(head, port);
}
true
}
pub fn is_chain_head(&self, id: FixtureId) -> bool {
self.edges.get_by_right(&id).is_none()
}
pub fn successor(&self, id: FixtureId) -> Option<FixtureId> {
self.edges.get_by_left(&id).copied()
}
pub fn chain_from(&self, head: FixtureId) -> Vec<FixtureId> {
let mut chain = vec![];
let mut current = Some(head);
while let Some(id) = current {
if chain.contains(&id) {
break;
}
chain.push(id);
current = self.successor(id);
}
chain
}
pub fn chain_heads(&self) -> Vec<FixtureId> {
self.fixtures
.keys()
.copied()
.filter(|id| self.is_chain_head(*id))
.collect()
}
fn lowest_unused_universe(&self) -> u16 {
let used: BTreeSet<u16> = self.ports.values().map(|port| port.universe).collect();
(0..).find(|universe| !used.contains(universe)).unwrap_or(0)
}
pub fn is_reachable(&self, start: FixtureId, target: FixtureId) -> bool {
let mut seen = BTreeSet::new();
let mut current = Some(start);
while let Some(id) = current {
if id == target {
return true;
}
if !seen.insert(id) {
return false;
}
current = self.edges.get_by_left(&id).copied();
}
false
}
fn invariant_holds(&self) -> bool {
self.fixtures.len() == self.default_layout.members.len()
&& self
.fixtures
.keys()
.all(|id| self.default_layout.contains(*id))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn a_port_without_a_start_channel_loads() {
let port: Port = serde_json::from_str(r#"{"universe":5}"#).expect("deserialize");
assert_eq!(
port,
Port {
universe: 5,
channel: 0
}
);
}
#[test]
fn a_port_wanders_onto_a_newly_prepended_head() {
let mut topology = Topology::default();
let head = topology.add_fixture(Fixture::new("head"), LayoutPosition::default());
let tail = topology.add_fixture(Fixture::new("tail"), LayoutPosition::default());
let new_head = topology.add_fixture(Fixture::new("new head"), LayoutPosition::default());
topology.make_successor(head, tail);
topology.ports.insert(
head,
Port {
universe: 7,
channel: 24,
},
);
topology.make_successor(new_head, head);
assert_eq!(
topology.ports.get(&new_head),
Some(&Port {
universe: 7,
channel: 24
})
);
assert!(!topology.ports.contains_key(&head));
}
#[test]
fn appending_a_fixture_keeps_the_chains_port() {
let mut topology = Topology::default();
let head = topology.add_fixture(Fixture::new("head"), LayoutPosition::default());
let tail = topology.add_fixture(Fixture::new("tail"), LayoutPosition::default());
topology.ports.insert(
head,
Port {
universe: 3,
channel: 0,
},
);
topology.make_successor(head, tail);
assert_eq!(
topology.ports.get(&head),
Some(&Port {
universe: 3,
channel: 0
})
);
}
fn topology_with(count: usize) -> (Topology, Vec<FixtureId>) {
let mut topology = Topology::default();
let ids = (0..count)
.map(|i| topology.add_fixture(Fixture::new(format!("f{i}")), LayoutPosition::default()))
.collect();
(topology, ids)
}
#[test]
fn default_layout_always_holds_every_fixture() {
let (mut topology, ids) = topology_with(1);
assert!(topology.default_layout.contains(ids[0]));
topology.remove_fixture(ids[0], &mut []);
assert!(!topology.default_layout.contains(ids[0]));
assert!(topology.fixtures.is_empty());
}
#[test]
fn removing_a_fixture_removes_it_from_every_layout() {
let (mut topology, ids) = topology_with(1);
let mut layouts = vec![Layout::new("a"), Layout::new("b")];
for layout in &mut layouts {
layout.members.insert(ids[0], LayoutPosition::default());
}
topology.remove_fixture(ids[0], &mut layouts);
assert!(layouts.iter().all(|layout| !layout.contains(ids[0])));
}
#[test]
fn new_chains_take_the_lowest_unused_universe() {
let (topology, ids) = topology_with(3);
let universes: Vec<u16> = ids.iter().map(|id| topology.ports[id].universe).collect();
assert_eq!(universes, vec![0, 1, 2]);
}
#[test]
fn connecting_a_fixture_takes_away_its_port() {
let (mut topology, ids) = topology_with(2);
assert!(topology.make_successor(ids[0], ids[1]));
assert!(!topology.ports.contains_key(&ids[1]));
assert_eq!(topology.chain_from(ids[0]), vec![ids[0], ids[1]]);
assert_eq!(topology.chain_heads(), vec![ids[0]]);
}
#[test]
fn disconnecting_gives_the_successor_its_own_port() {
let (mut topology, ids) = topology_with(2);
topology.make_successor(ids[0], ids[1]);
topology.make_leaf(ids[0]);
assert!(topology.ports.contains_key(&ids[1]));
assert_eq!(topology.chain_from(ids[0]), vec![ids[0]]);
}
#[test]
fn cycles_are_rejected() {
let (mut topology, ids) = topology_with(3);
topology.make_successor(ids[0], ids[1]);
topology.make_successor(ids[1], ids[2]);
assert!(!topology.make_successor(ids[2], ids[0]));
assert!(!topology.make_successor(ids[0], ids[0]));
assert_eq!(topology.chain_from(ids[0]), vec![ids[0], ids[1], ids[2]]);
}
#[test]
fn removing_a_middle_fixture_reheads_its_successor() {
let (mut topology, ids) = topology_with(3);
topology.make_successor(ids[0], ids[1]);
topology.make_successor(ids[1], ids[2]);
topology.remove_fixture(ids[1], &mut []);
assert_eq!(topology.chain_from(ids[0]), vec![ids[0]]);
assert!(topology.ports.contains_key(&ids[2]));
}
#[test]
fn fixture_names_are_unique() {
let mut topology = Topology::default();
topology.add_fixture(Fixture::new("Light"), LayoutPosition::default());
let second = topology.add_fixture(Fixture::new("Light"), LayoutPosition::default());
assert_eq!(topology.fixtures[&second].name, "Light 2");
}
}