gled-core 2.33.0

The engine of gled without a user interface: inputs, timing, rendering, Art-Net/DMX output and storage
Documentation
//! The project's output settings, applied to the running output when they
//! change: channel overwrites and the Art-Net input's settings.

use gled_model::asset::project::{
    Project,
    io::{ArtnetConfig, ArtnetControlConfig, Bridge, ChannelOverwrites},
};

use crate::{input::artnet::ARTNET_CONFIG, pipeline::output_sender::set_channel_overwrites};

/// The project's output settings the running output was last given. The
/// project holds them; the output threads read them from their own globals,
/// which are updated only when the project's values change.
#[derive(Debug, Default)]
pub(super) struct AppliedOutput {
    channel_overwrites: Option<ChannelOverwrites>,
    artnet: Option<(ArtnetConfig, ArtnetControlConfig)>,
}

impl AppliedOutput {
    pub(super) fn apply(&mut self, project: &Project) {
        if self.channel_overwrites.as_ref() != Some(&project.channel_overwrites) {
            set_channel_overwrites(project.channel_overwrites.clone());
            self.channel_overwrites = Some(project.channel_overwrites.clone());
        }
        let artnet = (project.artnet_config.clone(), project.artnet_control_config);
        if self.artnet.as_ref() != Some(&artnet) {
            set_live_artnet(&artnet.0, artnet.1);
            self.artnet = Some(artnet);
        }
    }

    /// Takes the project's settings back out of the running output: no channel
    /// overwrites, and the Art-Net input as it is before any project.
    pub(super) fn unapply(&mut self) {
        set_channel_overwrites(ChannelOverwrites::default());
        let defaults = ArtnetConfig::default();
        set_live_artnet(&defaults, defaults.artnet_control_config);
        *self = Self::default();
    }
}

/// Makes `config` the running Art-Net input's settings. The running config
/// also counts the data arriving per universe, and keeps it: a universe
/// `config` does not bridge keeps its counters but loses its routing, and goes
/// when it never received data.
fn set_live_artnet(config: &ArtnetConfig, control: ArtnetControlConfig) {
    let mut live = ARTNET_CONFIG.lock();
    live.universe = config.universe;
    live.artnet_control_config = control;
    for universe in config.bridge.keys() {
        live.bridge.entry(*universe).or_default();
    }
    live.bridge
        .retain(|universe, bridge| config.bridge.contains_key(universe) || bridge.updates > 0);
    let unbridged = Bridge::default();
    for (universe, running) in &mut live.bridge {
        let bridge = config.bridge.get(universe).unwrap_or(&unbridged);
        running.output_routing = bridge.output_routing.clone();
        running.htp = bridge.htp;
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::pipeline::output_sender::current_channel_overwrites;
    use gled_model::asset::project::io::ChannelIdentifier;

    /// One test, because the running output is global: two tests changing it
    /// side by side would see each other's values.
    #[test]
    fn project_output_settings_come_and_go_with_the_project_and_keep_the_counters() {
        // Art-Net arrived on universe 3 before the project said anything.
        ARTNET_CONFIG.lock().bridge.insert(
            3,
            Bridge {
                updates: 42,
                ..Bridge::default()
            },
        );

        let mut project = Project::default();
        project.artnet_config.universe = 7;
        project.artnet_config.bridge.insert(
            3,
            Bridge {
                htp: true,
                ..Bridge::default()
            },
        );
        // Only the project knows universe 9; no data arrived there.
        project.artnet_config.bridge.insert(9, Bridge::default());
        project.channel_overwrites.0.insert(
            ChannelIdentifier {
                device: Default::default(),
                universe: Some(1),
                channel: 5,
            },
            255,
        );

        let mut applied = AppliedOutput::default();
        applied.apply(&project);

        let live = ARTNET_CONFIG.lock().clone();
        assert_eq!(live.universe, 7);
        let bridge = live.bridge.get(&3).expect("bridged universe");
        assert!(bridge.htp);
        assert_eq!(bridge.updates, 42);
        assert_eq!(current_channel_overwrites(), project.channel_overwrites);

        // Regression: a bridge removed from the loaded project kept its
        // routing and HTP until the project was closed.
        let mut edited = project.clone();
        edited.artnet_config.bridge.clear();
        applied.apply(&edited);
        let live = ARTNET_CONFIG.lock().clone();
        let bridge = live.bridge.get(&3).expect("universe that received data");
        assert!(!bridge.htp);
        assert_eq!(bridge.updates, 42);
        assert!(!live.bridge.contains_key(&9));
        applied.apply(&project);

        applied.unapply();

        let live = ARTNET_CONFIG.lock().clone();
        assert_eq!(live.universe, ArtnetConfig::default().universe);
        let bridge = live.bridge.get(&3).expect("universe that received data");
        assert!(!bridge.htp);
        assert_eq!(bridge.updates, 42);
        assert!(!live.bridge.contains_key(&9));
        assert_eq!(current_channel_overwrites(), ChannelOverwrites::default());

        // The same project loaded again is applied again.
        applied.apply(&project);
        assert_eq!(ARTNET_CONFIG.lock().universe, 7);
    }
}