gled-model 2.31.1

The data model of gled: projects, scenes, effects, animations, curves and assets
Documentation
//! The addresses of a patch's fixtures and the universes they fill, derived
//! once and looked up per fixture: the output positions and the preview
//! both need the output buffer slot of every fixture.

use crate::collection::Collections;
use crate::patching::Patch;
use crate::patching::fixture::{ArtnetDMXAddress, FixtureId};
use std::collections::{BTreeMap, BTreeSet};

/// Every addressed fixture's DMX address, and the universes they occupy in
/// ascending order. Output buffer slot `i` is the `i`-th universe.
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct Addressing {
    addresses: BTreeMap<FixtureId, ArtnetDMXAddress>,
    universes: Vec<u16>,
}

impl Addressing {
    /// The output buffer slot of the fixture's universe, with its address;
    /// `None` if it has none (see [`Patch::addresses`]).
    pub fn slot(&self, id: FixtureId) -> Option<(usize, ArtnetDMXAddress)> {
        let address = *self.addresses.get(&id)?;
        let slot = self.universes.binary_search(&address.universe()).ok()?;
        Some((slot, address))
    }

    /// Every universe a fixture is addressed in, ascending and distinct.
    pub fn universes(&self) -> &[u16] {
        &self.universes
    }
}

impl Patch {
    /// The addresses of every fixture and the universes they fill.
    pub fn addressing(&self, collections: &Collections) -> Addressing {
        let addresses = self.addresses(collections);
        let universes: BTreeSet<u16> = addresses
            .values()
            .map(|address| address.universe())
            .collect();
        Addressing {
            addresses,
            universes: universes.into_iter().collect(),
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::patching::fixture::{Fixture, LayoutPosition};
    use crate::test_support::{collections_with, rgb_bar};

    #[test]
    fn a_fixture_s_slot_is_its_universe_s_place_among_the_universes() {
        let (collections, ids) = collections_with(vec![rgb_bar(4, 1.0)]);
        let mut patch = Patch::default();
        let a = patch.add_fixture(
            Fixture::with_definition("a", ids[0]),
            LayoutPosition::default(),
        );
        let b = patch.add_fixture(
            Fixture::with_definition("b", ids[0]),
            LayoutPosition::default(),
        );
        patch.topology.ports.entry(b).or_default().universe = 5;

        let addressing = patch.addressing(&collections);

        assert_eq!(addressing.universes(), [0, 5]);
        assert_eq!(addressing.slot(a).map(|(slot, _)| slot), Some(0));
        assert_eq!(addressing.slot(b).map(|(slot, _)| slot), Some(1));
    }

    /// A malformed definition wider than a universe — here wider than a
    /// `u16` counts — gets no address, and neither does the rest of its
    /// chain, rather than one past the universe's end or a wrapped one.
    #[test]
    fn a_fixture_wider_than_a_universe_and_its_chain_get_no_address() {
        let (collections, ids) = collections_with(vec![
            rgb_bar(4, 1.0),
            rgb_bar(171, 1.0),
            rgb_bar(22_000, 1.0),
        ]);
        let mut patch = Patch::default();
        let head = patch.add_fixture(
            Fixture::with_definition("head", ids[0]),
            LayoutPosition::default(),
        );
        let wide = patch.add_fixture(
            Fixture::with_definition("wide", ids[1]),
            LayoutPosition::default(),
        );
        let tail = patch.add_fixture(
            Fixture::with_definition("tail", ids[0]),
            LayoutPosition::default(),
        );
        patch.topology.make_successor(head, wide);
        patch.topology.make_successor(wide, tail);
        let huge = patch.add_fixture(
            Fixture::with_definition("huge", ids[2]),
            LayoutPosition::default(),
        );

        let addressing = patch.addressing(&collections);

        assert_eq!(
            addressing.slot(head).map(|(_, a)| a.into_pair()),
            Some((0, 0))
        );
        assert_eq!(addressing.slot(wide), None);
        assert_eq!(addressing.slot(tail), None);
        assert_eq!(addressing.slot(huge), None);
        assert_eq!(addressing.universes(), [0]);
    }

    #[test]
    fn a_fixture_without_a_definition_has_no_slot() {
        let (collections, _) = collections_with(vec![]);
        let mut patch = Patch::default();
        let orphan = patch.add_fixture(Fixture::new("orphan"), LayoutPosition::default());

        let addressing = patch.addressing(&collections);

        assert_eq!(addressing.slot(orphan), None);
        assert!(addressing.universes().is_empty());
    }
}