gled 2.29.0

gled is an application for creating animations and effects on artnet or dmx installations
use crate::{
    pipeline::{
        extract_output::OutputConfig, group::Group, preview_indices::PreviewIndices,
        render_to_output::positions::Positions,
    },
    svg::{measurement_point::MeasurementPoints, universe_color_channels::UniverseColorChannels},
    ui::action::UiAction,
};
use anyhow::Result;
use egui::{Context, TextureHandle, mutex::Mutex};
use once_cell::sync::Lazy;
use serde::{Deserialize, Serialize};
use std::{collections::BTreeSet, path::Path, sync::Arc};

/// Measurement points of the currently loaded SVG. Written by the UI thread when
/// an SVG is parsed, read by the render thread when positions are uploaded. The
/// lock only guards an `Arc` swap, so readers never wait on any real work.
static MEASUREMENT_POINTS: Lazy<Mutex<Arc<MeasurementPoints>>> =
    Lazy::new(|| Mutex::new(Arc::new(MeasurementPoints::new())));

#[derive(Serialize, Deserialize, PartialEq)]
pub struct Svg {
    /// Shared so that the per-frame project snapshot does not copy the file.
    svg_contents: Arc<str>,
    #[serde(skip)]
    image: Option<TextureHandle>,
}

impl std::fmt::Debug for Svg {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.debug_struct("Svg")
            .field("svg_contents", &self.svg_contents)
            .finish()
    }
}

impl Clone for Svg {
    fn clone(&self) -> Self {
        Self {
            svg_contents: self.svg_contents.clone(),
            image: None,
        }
    }
}

impl Svg {
    pub fn load(path: &Path) -> Result<Self> {
        let svg_contents = std::fs::read_to_string(path)?;

        Ok(Self {
            svg_contents: svg_contents.into(),
            image: None,
        })
    }

    pub fn save(&self, path: &Path) -> Result<()> {
        std::fs::write(path, self.svg_contents.as_bytes())?;

        Ok(())
    }

    pub fn image(
        &mut self,
        context: &Context,
        output_config: &mut OutputConfig,
    ) -> Option<TextureHandle> {
        if self.image.is_none() {
            self.image = {
                let svg = crate::svg::ParsedSvg::parse(&self.svg_contents).ok()?;
                let measurement_points = MeasurementPoints::from(&svg);
                UniverseColorChannels::from(&svg).set();
                let universes = measurement_points.universes();
                // Own statement: the guard must be released before
                // `PreviewIndices::get()` below, whose lazy init reads it again.
                *MEASUREMENT_POINTS.lock() = Arc::new(measurement_points);
                let image = svg.render().ok()?;
                PreviewIndices::get().send_positions();
                output_config.universes = Arc::new(universes);
                UiAction::SendPositions.enqueue();

                let texture_handle =
                    context.load_texture("svg_preview", image, egui::TextureOptions::default());

                Some(texture_handle)
            };
        }
        self.image.clone()
    }

    pub fn reset() {
        *MEASUREMENT_POINTS.lock() = Arc::new(MeasurementPoints::new());
    }

    fn measurement_points() -> Arc<MeasurementPoints> {
        MEASUREMENT_POINTS.lock().clone()
    }

    pub fn groups() -> Vec<Group> {
        Self::measurement_points().groups()
    }

    pub fn positions(group: &Group) -> Positions {
        Self::measurement_points().positions(group)
    }

    pub fn preview_positions() -> Positions {
        Self::measurement_points().preview_positions()
    }

    pub fn universes() -> BTreeSet<u16> {
        Self::measurement_points().universes()
    }
}