Skip to main content

gled_protocol/
project_update.rs

1//! Project changes on the wire. A whole project is around 50 KB, mostly the
2//! SVG and the scene grid; an edit usually touches one scene. So after the
3//! first full state, an interface gets only the scenes that changed, the SVG
4//! when it changed, and the rest of the project when any of it changed.
5
6use std::collections::HashMap;
7
8use gled_model::asset::{
9    project::Project,
10    scene::{grid::GridLocation, instance::SceneInstance},
11    svg::Svg,
12};
13use serde::{Deserialize, Serialize};
14
15/// How a project replica becomes the engine's project.
16#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
17pub enum ProjectUpdate {
18    Unchanged,
19    /// Another project, or none.
20    Replaced(Option<Box<Project>>),
21    Patched(ProjectPatch),
22}
23
24/// The differences between two projects.
25#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
26pub struct ProjectPatch {
27    /// Scene instances that changed, `None` where one was removed.
28    pub scenes: Vec<(GridLocation, Option<SceneInstance>)>,
29    /// The new SVG, if it changed.
30    pub svg: Option<Option<Svg>>,
31    /// Everything but the scenes and the SVG, if any of it changed. Its own
32    /// scenes and SVG are empty.
33    pub rest: Option<Box<Project>>,
34}
35
36impl ProjectUpdate {
37    /// What turns `old` into `new`.
38    pub fn between(old: Option<&Project>, new: Option<&Project>) -> Self {
39        match (old, new) {
40            (None, None) => Self::Unchanged,
41            (_, None) => Self::Replaced(None),
42            (None, Some(new)) => Self::Replaced(Some(Box::new(new.clone()))),
43            (Some(old), Some(new)) if old == new => Self::Unchanged,
44            (Some(old), Some(new)) => Self::Patched(ProjectPatch::between(old, new)),
45        }
46    }
47
48    /// Applies the update to a replica that was equal to the `old` project
49    /// it was made from.
50    pub fn apply_to(self, project: &mut Option<Project>) {
51        match self {
52            Self::Unchanged => {}
53            Self::Replaced(new) => *project = new.map(|new| *new),
54            Self::Patched(patch) => {
55                if let Some(project) = project {
56                    patch.apply_to(project);
57                }
58            }
59        }
60    }
61}
62
63impl ProjectPatch {
64    fn between(old: &Project, new: &Project) -> Self {
65        let mut scenes: Vec<_> = new
66            .scenes_instances_grid
67            .iter()
68            .filter(|(location, scene)| old.scenes_instances_grid.get(location) != Some(scene))
69            .map(|(location, scene)| (*location, Some(scene.clone())))
70            .collect();
71        scenes.extend(
72            old.scenes_instances_grid
73                .keys()
74                .filter(|location| !new.scenes_instances_grid.contains_key(location))
75                .map(|location| (*location, None)),
76        );
77        let svg = (old.svg != new.svg).then(|| new.svg.clone());
78        let new_rest = rest(new);
79        let rest = (rest(old) != new_rest).then(|| Box::new(new_rest));
80        Self { scenes, svg, rest }
81    }
82
83    fn apply_to(self, project: &mut Project) {
84        if let Some(rest) = self.rest {
85            let scenes = std::mem::take(&mut project.scenes_instances_grid);
86            let svg = project.svg.take();
87            *project = *rest;
88            project.scenes_instances_grid = scenes;
89            project.svg = svg;
90        }
91        if let Some(svg) = self.svg {
92            project.svg = svg;
93        }
94        for (location, scene) in self.scenes {
95            match scene {
96                Some(scene) => {
97                    project.scenes_instances_grid.insert(location, scene);
98                }
99                None => {
100                    project.scenes_instances_grid.remove(&location);
101                }
102            }
103        }
104    }
105}
106
107/// The project without its scenes and SVG. Listing every field makes a new
108/// one a compile error here instead of a change that never reaches the
109/// interfaces.
110fn rest(project: &Project) -> Project {
111    let Project {
112        palette,
113        groups,
114        grid_width,
115        grid_height,
116        grid_highlight,
117        scenes_instances_grid: _,
118        svg: _,
119        channel_overwrites,
120        output_routings,
121        artnet_config,
122        tap_input_events,
123        blackout_input_events,
124        blackout_hold_input_events,
125        half_input_events,
126        double_input_events,
127        midi_active_mappings,
128        main_dimmer,
129        audio_input_device,
130        artnet_control_config,
131        osc_config,
132    } = project;
133    Project {
134        palette: palette.clone(),
135        groups: groups.clone(),
136        grid_width: *grid_width,
137        grid_height: *grid_height,
138        grid_highlight: *grid_highlight,
139        channel_overwrites: channel_overwrites.clone(),
140        output_routings: output_routings.clone(),
141        artnet_config: artnet_config.clone(),
142        tap_input_events: tap_input_events.clone(),
143        blackout_input_events: blackout_input_events.clone(),
144        blackout_hold_input_events: blackout_hold_input_events.clone(),
145        half_input_events: half_input_events.clone(),
146        double_input_events: double_input_events.clone(),
147        midi_active_mappings: midi_active_mappings.clone(),
148        main_dimmer: *main_dimmer,
149        audio_input_device: audio_input_device.clone(),
150        artnet_control_config: *artnet_control_config,
151        osc_config: *osc_config,
152        scenes_instances_grid: HashMap::new(),
153        svg: None,
154    }
155}