gled-protocol 2.32.1

What gled's user interfaces and its engine send each other
Documentation
//! Project changes on the wire. A whole project is tens of KB, mostly the
//! scene grid and the patch; an edit usually touches one scene. So after the
//! first full state, an interface gets only the scenes that changed, the patch
//! when it changed, and the rest of the project when any of it changed.

use std::{collections::HashMap, sync::Arc};

use gled_model::{
    asset::{
        project::Project,
        scene::{grid::GridLocation, instance::SceneInstance},
    },
    patching::Patch,
};
use serde::{Deserialize, Serialize};

/// How a project replica becomes the engine's project.
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub enum ProjectUpdate {
    Unchanged,
    /// Another project, or none.
    Replaced(Option<Box<Project>>),
    Changed(ProjectDiff),
}

/// The differences between two projects.
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct ProjectDiff {
    /// Scene instances that changed, `None` where one was removed.
    pub scenes: Vec<(GridLocation, Option<SceneInstance>)>,
    /// The new patch, if it changed.
    pub patch: Option<Box<Patch>>,
    /// Everything but the scenes and the patch, if any of it changed. Its own
    /// scenes and patch are empty.
    pub rest: Option<Box<Project>>,
}

impl ProjectUpdate {
    /// What turns `old` into `new`.
    pub fn between(old: Option<&Project>, new: Option<&Project>) -> Self {
        match (old, new) {
            (None, None) => Self::Unchanged,
            (_, None) => Self::Replaced(None),
            (None, Some(new)) => Self::Replaced(Some(Box::new(new.clone()))),
            // The engine's unchanged project is the same value: no need to
            // compare it.
            (Some(old), Some(new)) if std::ptr::eq(old, new) => Self::Unchanged,
            // The diff compares every part once; an empty one means equal
            // projects, as `rest` holds every field but the scenes and patch.
            (Some(old), Some(new)) => {
                let diff = ProjectDiff::between(old, new);
                if diff.is_empty() {
                    Self::Unchanged
                } else {
                    Self::Changed(diff)
                }
            }
        }
    }

    /// Applies the update to a replica that was equal to the `old` project
    /// it was made from. A replica shared with others is copied first.
    pub fn apply_to(self, project: &mut Option<Arc<Project>>) {
        match self {
            Self::Unchanged => {}
            Self::Replaced(new) => *project = new.map(|new| Arc::new(*new)),
            Self::Changed(diff) => {
                if let Some(project) = project {
                    diff.apply_to(Arc::make_mut(project));
                }
            }
        }
    }
}

impl ProjectDiff {
    fn is_empty(&self) -> bool {
        self.scenes.is_empty() && self.patch.is_none() && self.rest.is_none()
    }

    fn between(old: &Project, new: &Project) -> Self {
        let mut scenes: Vec<_> = new
            .scenes_instances_grid
            .iter()
            .filter(|(location, scene)| old.scenes_instances_grid.get(location) != Some(scene))
            .map(|(location, scene)| (*location, Some(scene.clone())))
            .collect();
        scenes.extend(
            old.scenes_instances_grid
                .keys()
                .filter(|location| !new.scenes_instances_grid.contains_key(location))
                .map(|location| (*location, None)),
        );
        let patch = (old.patch != new.patch).then(|| Box::new(new.patch.clone()));
        let new_rest = rest(new);
        let rest = (rest(old) != new_rest).then(|| Box::new(new_rest));
        Self {
            scenes,
            patch,
            rest,
        }
    }

    fn apply_to(self, project: &mut Project) {
        if let Some(rest) = self.rest {
            let scenes = std::mem::take(&mut project.scenes_instances_grid);
            let patch = std::mem::take(&mut project.patch);
            *project = *rest;
            project.scenes_instances_grid = scenes;
            project.patch = patch;
        }
        if let Some(patch) = self.patch {
            project.patch = *patch;
        }
        for (location, scene) in self.scenes {
            match scene {
                Some(scene) => {
                    project.scenes_instances_grid.insert(location, scene);
                }
                None => {
                    project.scenes_instances_grid.remove(&location);
                }
            }
        }
    }
}

/// The project without its scenes and patch. Listing every field makes a new
/// one a compile error here instead of a change that never reaches the
/// interfaces.
fn rest(project: &Project) -> Project {
    let Project {
        palette,
        patch: _,
        grid_width,
        grid_height,
        grid_highlight,
        scenes_instances_grid: _,
        channel_overwrites,
        output_routings,
        artnet_config,
        tap_input_events,
        blackout_input_events,
        blackout_hold_input_events,
        half_input_events,
        double_input_events,
        midi_active_mappings,
        main_dimmer,
        audio_input_device,
        artnet_control_config,
        osc_config,
    } = project;
    Project {
        palette: palette.clone(),
        patch: Patch::default(),
        grid_width: *grid_width,
        grid_height: *grid_height,
        grid_highlight: *grid_highlight,
        channel_overwrites: channel_overwrites.clone(),
        output_routings: output_routings.clone(),
        artnet_config: artnet_config.clone(),
        tap_input_events: tap_input_events.clone(),
        blackout_input_events: blackout_input_events.clone(),
        blackout_hold_input_events: blackout_hold_input_events.clone(),
        half_input_events: half_input_events.clone(),
        double_input_events: double_input_events.clone(),
        midi_active_mappings: midi_active_mappings.clone(),
        main_dimmer: *main_dimmer,
        audio_input_device: audio_input_device.clone(),
        artnet_control_config: *artnet_control_config,
        osc_config: *osc_config,
        scenes_instances_grid: HashMap::new(),
    }
}