Skip to main content

gled_model/patching/
addressing.rs

1//! The addresses of a patch's fixtures and the universes they fill, derived
2//! once and looked up per fixture: the output positions and the preview
3//! both need the output buffer slot of every fixture.
4
5use crate::collection::Collections;
6use crate::patching::Patch;
7use crate::patching::fixture::{ArtnetDMXAddress, FixtureId};
8use std::collections::{BTreeMap, BTreeSet};
9
10/// Every addressed fixture's DMX address, and the universes they occupy in
11/// ascending order. Output buffer slot `i` is the `i`-th universe.
12#[derive(Debug, Clone, Default, PartialEq, Eq)]
13pub struct Addressing {
14    addresses: BTreeMap<FixtureId, ArtnetDMXAddress>,
15    universes: Vec<u16>,
16}
17
18impl Addressing {
19    /// The output buffer slot of the fixture's universe, with its address;
20    /// `None` if it has none (see [`Patch::addresses`]).
21    pub fn slot(&self, id: FixtureId) -> Option<(usize, ArtnetDMXAddress)> {
22        let address = *self.addresses.get(&id)?;
23        let slot = self.universes.binary_search(&address.universe()).ok()?;
24        Some((slot, address))
25    }
26
27    /// Every universe a fixture is addressed in, ascending and distinct.
28    pub fn universes(&self) -> &[u16] {
29        &self.universes
30    }
31}
32
33impl Patch {
34    /// The addresses of every fixture and the universes they fill.
35    pub fn addressing(&self, collections: &Collections) -> Addressing {
36        let addresses = self.addresses(collections);
37        let universes: BTreeSet<u16> = addresses
38            .values()
39            .map(|address| address.universe())
40            .collect();
41        Addressing {
42            addresses,
43            universes: universes.into_iter().collect(),
44        }
45    }
46}
47
48#[cfg(test)]
49mod tests {
50    use super::*;
51    use crate::patching::fixture::{Fixture, LayoutPosition};
52    use crate::test_support::{collections_with, rgb_bar};
53
54    #[test]
55    fn a_fixture_s_slot_is_its_universe_s_place_among_the_universes() {
56        let (collections, ids) = collections_with(vec![rgb_bar(4, 1.0)]);
57        let mut patch = Patch::default();
58        let a = patch.add_fixture(
59            Fixture::with_definition("a", ids[0]),
60            LayoutPosition::default(),
61        );
62        let b = patch.add_fixture(
63            Fixture::with_definition("b", ids[0]),
64            LayoutPosition::default(),
65        );
66        patch.topology.ports.entry(b).or_default().universe = 5;
67
68        let addressing = patch.addressing(&collections);
69
70        assert_eq!(addressing.universes(), [0, 5]);
71        assert_eq!(addressing.slot(a).map(|(slot, _)| slot), Some(0));
72        assert_eq!(addressing.slot(b).map(|(slot, _)| slot), Some(1));
73    }
74
75    /// A malformed definition wider than a universe — here wider than a
76    /// `u16` counts — gets no address, and neither does the rest of its
77    /// chain, rather than one past the universe's end or a wrapped one.
78    #[test]
79    fn a_fixture_wider_than_a_universe_and_its_chain_get_no_address() {
80        let (collections, ids) = collections_with(vec![
81            rgb_bar(4, 1.0),
82            rgb_bar(171, 1.0),
83            rgb_bar(22_000, 1.0),
84        ]);
85        let mut patch = Patch::default();
86        let head = patch.add_fixture(
87            Fixture::with_definition("head", ids[0]),
88            LayoutPosition::default(),
89        );
90        let wide = patch.add_fixture(
91            Fixture::with_definition("wide", ids[1]),
92            LayoutPosition::default(),
93        );
94        let tail = patch.add_fixture(
95            Fixture::with_definition("tail", ids[0]),
96            LayoutPosition::default(),
97        );
98        patch.topology.make_successor(head, wide);
99        patch.topology.make_successor(wide, tail);
100        let huge = patch.add_fixture(
101            Fixture::with_definition("huge", ids[2]),
102            LayoutPosition::default(),
103        );
104
105        let addressing = patch.addressing(&collections);
106
107        assert_eq!(
108            addressing.slot(head).map(|(_, a)| a.into_pair()),
109            Some((0, 0))
110        );
111        assert_eq!(addressing.slot(wide), None);
112        assert_eq!(addressing.slot(tail), None);
113        assert_eq!(addressing.slot(huge), None);
114        assert_eq!(addressing.universes(), [0]);
115    }
116
117    #[test]
118    fn a_fixture_without_a_definition_has_no_slot() {
119        let (collections, _) = collections_with(vec![]);
120        let mut patch = Patch::default();
121        let orphan = patch.add_fixture(Fixture::new("orphan"), LayoutPosition::default());
122
123        let addressing = patch.addressing(&collections);
124
125        assert_eq!(addressing.slot(orphan), None);
126        assert!(addressing.universes().is_empty());
127    }
128}