1use crate::naming::unique_name;
2use crate::patching::fixture::{Fixture, FixtureId, LayoutPosition};
3use crate::patching::layout::Layout;
4use bimap::BiMap;
5use serde::{Deserialize, Serialize};
6use std::collections::{BTreeMap, BTreeSet};
7
8#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, Default)]
11#[serde(default)]
12pub struct Port {
13 pub universe: u16,
14 pub channel: u16,
17}
18
19impl Port {
20 pub fn new(universe: u16) -> Self {
22 Self {
23 universe,
24 channel: 0,
25 }
26 }
27}
28
29#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
32#[serde(default)]
33pub struct Topology {
34 pub fixtures: BTreeMap<FixtureId, Fixture>,
35 pub default_layout: Layout,
38 pub edges: BiMap<FixtureId, FixtureId>,
41 pub ports: BTreeMap<FixtureId, Port>,
42}
43
44impl Default for Topology {
45 fn default() -> Self {
46 Self {
47 fixtures: BTreeMap::new(),
48 default_layout: Layout::new(Layout::DEFAULT_NAME),
49 edges: BiMap::new(),
50 ports: BTreeMap::new(),
51 }
52 }
53}
54
55impl Topology {
56 pub fn add_fixture(&mut self, mut fixture: Fixture, position: LayoutPosition) -> FixtureId {
57 fixture.name = unique_name(
58 &fixture.name,
59 self.fixtures.values().map(|other| other.name.as_str()),
60 );
61 let id = fixture.id;
62 self.fixtures.insert(id, fixture);
63 self.default_layout.members.insert(id, position);
64 self.ports
65 .insert(id, Port::new(self.lowest_unused_universe()));
66 debug_assert!(self.invariant_holds());
67 id
68 }
69
70 pub fn remove_fixture(&mut self, id: FixtureId, layouts: &mut [Layout]) {
73 self.make_leaf(id);
76 self.edges.remove_by_right(&id);
77 self.ports.remove(&id);
78 self.fixtures.remove(&id);
79 self.default_layout.forget(id);
80 for layout in layouts {
81 layout.forget(id);
82 }
83 debug_assert!(self.invariant_holds());
84 }
85
86 pub fn make_leaf(&mut self, id: FixtureId) {
88 if let Some((_, successor)) = self.edges.remove_by_left(&id) {
89 self.ports
90 .insert(successor, Port::new(self.lowest_unused_universe()));
91 }
92 }
93
94 pub fn chain_head_of(&self, id: FixtureId) -> FixtureId {
96 let mut head = id;
97 let mut seen = 0;
98 while let Some(parent) = self.edges.get_by_right(&head).copied() {
99 head = parent;
100 seen += 1;
101 if seen > self.fixtures.len() {
102 break;
103 }
104 }
105 head
106 }
107
108 pub fn make_successor(&mut self, parent: FixtureId, child: FixtureId) -> bool {
114 if parent == child || self.is_reachable(child, parent) {
115 return false;
116 }
117 let child_leads_chain = self.successor(child).is_some();
120 self.make_leaf(parent);
121 self.edges.remove_by_right(&child);
122 let inherited = self.ports.remove(&child);
123 self.edges.insert(parent, child);
124 if let Some(port) = inherited
125 && child_leads_chain
126 {
127 let head = self.chain_head_of(parent);
128 self.ports.insert(head, port);
129 }
130 true
131 }
132
133 pub fn is_chain_head(&self, id: FixtureId) -> bool {
134 self.edges.get_by_right(&id).is_none()
135 }
136
137 pub fn successor(&self, id: FixtureId) -> Option<FixtureId> {
138 self.edges.get_by_left(&id).copied()
139 }
140
141 pub fn chain_from(&self, head: FixtureId) -> Vec<FixtureId> {
143 let mut chain = vec![];
144 let mut current = Some(head);
145 while let Some(id) = current {
146 if chain.contains(&id) {
147 break;
148 }
149 chain.push(id);
150 current = self.successor(id);
151 }
152 chain
153 }
154
155 pub fn chain_heads(&self) -> Vec<FixtureId> {
156 self.fixtures
157 .keys()
158 .copied()
159 .filter(|id| self.is_chain_head(*id))
160 .collect()
161 }
162
163 fn lowest_unused_universe(&self) -> u16 {
168 let used: BTreeSet<u16> = self.ports.values().map(|port| port.universe).collect();
169 (0..).find(|universe| !used.contains(universe)).unwrap_or(0)
170 }
171
172 pub fn is_reachable(&self, start: FixtureId, target: FixtureId) -> bool {
175 let mut seen = BTreeSet::new();
176 let mut current = Some(start);
177 while let Some(id) = current {
178 if id == target {
179 return true;
180 }
181 if !seen.insert(id) {
182 return false;
183 }
184 current = self.edges.get_by_left(&id).copied();
185 }
186 false
187 }
188
189 fn invariant_holds(&self) -> bool {
190 self.fixtures.len() == self.default_layout.members.len()
191 && self
192 .fixtures
193 .keys()
194 .all(|id| self.default_layout.contains(*id))
195 }
196}
197
198#[cfg(test)]
199mod tests {
200 use super::*;
201
202 #[test]
204 fn a_port_without_a_start_channel_loads() {
205 let port: Port = serde_json::from_str(r#"{"universe":5}"#).expect("deserialize");
206 assert_eq!(
207 port,
208 Port {
209 universe: 5,
210 channel: 0
211 }
212 );
213 }
214
215 #[test]
218 fn a_port_wanders_onto_a_newly_prepended_head() {
219 let mut topology = Topology::default();
220 let head = topology.add_fixture(Fixture::new("head"), LayoutPosition::default());
221 let tail = topology.add_fixture(Fixture::new("tail"), LayoutPosition::default());
222 let new_head = topology.add_fixture(Fixture::new("new head"), LayoutPosition::default());
223 topology.make_successor(head, tail);
224 topology.ports.insert(
225 head,
226 Port {
227 universe: 7,
228 channel: 24,
229 },
230 );
231
232 topology.make_successor(new_head, head);
233
234 assert_eq!(
235 topology.ports.get(&new_head),
236 Some(&Port {
237 universe: 7,
238 channel: 24
239 })
240 );
241 assert!(!topology.ports.contains_key(&head));
242 }
243
244 #[test]
247 fn appending_a_fixture_keeps_the_chains_port() {
248 let mut topology = Topology::default();
249 let head = topology.add_fixture(Fixture::new("head"), LayoutPosition::default());
250 let tail = topology.add_fixture(Fixture::new("tail"), LayoutPosition::default());
251 topology.ports.insert(
252 head,
253 Port {
254 universe: 3,
255 channel: 0,
256 },
257 );
258
259 topology.make_successor(head, tail);
260
261 assert_eq!(
262 topology.ports.get(&head),
263 Some(&Port {
264 universe: 3,
265 channel: 0
266 })
267 );
268 }
269
270 fn topology_with(count: usize) -> (Topology, Vec<FixtureId>) {
271 let mut topology = Topology::default();
272 let ids = (0..count)
273 .map(|i| topology.add_fixture(Fixture::new(format!("f{i}")), LayoutPosition::default()))
274 .collect();
275 (topology, ids)
276 }
277
278 #[test]
279 fn default_layout_always_holds_every_fixture() {
280 let (mut topology, ids) = topology_with(1);
281 assert!(topology.default_layout.contains(ids[0]));
282 topology.remove_fixture(ids[0], &mut []);
283 assert!(!topology.default_layout.contains(ids[0]));
284 assert!(topology.fixtures.is_empty());
285 }
286
287 #[test]
288 fn removing_a_fixture_removes_it_from_every_layout() {
289 let (mut topology, ids) = topology_with(1);
290 let mut layouts = vec![Layout::new("a"), Layout::new("b")];
291 for layout in &mut layouts {
292 layout.members.insert(ids[0], LayoutPosition::default());
293 }
294
295 topology.remove_fixture(ids[0], &mut layouts);
296
297 assert!(layouts.iter().all(|layout| !layout.contains(ids[0])));
298 }
299
300 #[test]
301 fn new_chains_take_the_lowest_unused_universe() {
302 let (topology, ids) = topology_with(3);
303 let universes: Vec<u16> = ids.iter().map(|id| topology.ports[id].universe).collect();
304 assert_eq!(universes, vec![0, 1, 2]);
305 }
306
307 #[test]
308 fn connecting_a_fixture_takes_away_its_port() {
309 let (mut topology, ids) = topology_with(2);
310 assert!(topology.make_successor(ids[0], ids[1]));
311 assert!(!topology.ports.contains_key(&ids[1]));
312 assert_eq!(topology.chain_from(ids[0]), vec![ids[0], ids[1]]);
313 assert_eq!(topology.chain_heads(), vec![ids[0]]);
314 }
315
316 #[test]
317 fn disconnecting_gives_the_successor_its_own_port() {
318 let (mut topology, ids) = topology_with(2);
319 topology.make_successor(ids[0], ids[1]);
320 topology.make_leaf(ids[0]);
321 assert!(topology.ports.contains_key(&ids[1]));
322 assert_eq!(topology.chain_from(ids[0]), vec![ids[0]]);
323 }
324
325 #[test]
326 fn cycles_are_rejected() {
327 let (mut topology, ids) = topology_with(3);
328 topology.make_successor(ids[0], ids[1]);
329 topology.make_successor(ids[1], ids[2]);
330 assert!(!topology.make_successor(ids[2], ids[0]));
331 assert!(!topology.make_successor(ids[0], ids[0]));
332 assert_eq!(topology.chain_from(ids[0]), vec![ids[0], ids[1], ids[2]]);
333 }
334
335 #[test]
336 fn removing_a_middle_fixture_reheads_its_successor() {
337 let (mut topology, ids) = topology_with(3);
338 topology.make_successor(ids[0], ids[1]);
339 topology.make_successor(ids[1], ids[2]);
340
341 topology.remove_fixture(ids[1], &mut []);
342
343 assert_eq!(topology.chain_from(ids[0]), vec![ids[0]]);
344 assert!(topology.ports.contains_key(&ids[2]));
345 }
346
347 #[test]
348 fn fixture_names_are_unique() {
349 let mut topology = Topology::default();
350 topology.add_fixture(Fixture::new("Light"), LayoutPosition::default());
351 let second = topology.add_fixture(Fixture::new("Light"), LayoutPosition::default());
352 assert_eq!(topology.fixtures[&second].name, "Light 2");
353 }
354}