Skip to main content

gled_protocol/
replica.rs

1//! What a remote interface knows of the engine, rebuilt from the server's
2//! messages.
3
4use gled_model::collection::Collections;
5
6use crate::{
7    assets::collections_from, message::ServerMessage, project_update::ProjectUpdate,
8    state::EngineState,
9};
10
11#[derive(Debug, Clone, Default)]
12pub struct StateReplica {
13    pub state: EngineState,
14    /// The number of the newest action of this interface the engine applied.
15    pub applied_sequence: u64,
16    /// Whether a full state has arrived; updates before it are ignored.
17    pub complete: bool,
18    /// The engine's assets.
19    pub assets: Collections,
20    /// Whether all assets have arrived; changes before them are ignored.
21    pub(crate) assets_complete: bool,
22    /// Counts the messages that may have changed the project: full states
23    /// and updates with a project change. An interface compares its own
24    /// project with the engine's only when this moved.
25    pub project_changes: u64,
26}
27
28impl StateReplica {
29    /// Takes a message. Returns whether the state changed.
30    pub fn apply(&mut self, message: &ServerMessage) -> bool {
31        match message {
32            ServerMessage::State {
33                state,
34                applied_sequence,
35            } => {
36                self.state = EngineState::clone(state);
37                self.applied_sequence = *applied_sequence;
38                self.complete = true;
39                self.project_changes += 1;
40                true
41            }
42            ServerMessage::Update {
43                state,
44                project,
45                applied_sequence,
46            } if self.complete => {
47                if !matches!(project, ProjectUpdate::Unchanged) {
48                    self.project_changes += 1;
49                }
50                let mut replica_project = self.state.project.take();
51                project.clone().apply_to(&mut replica_project);
52                self.state = EngineState {
53                    project: replica_project,
54                    ..EngineState::clone(state)
55                };
56                self.applied_sequence = *applied_sequence;
57                true
58            }
59            ServerMessage::Assets(assets) => {
60                self.assets = collections_from(assets.clone());
61                self.assets_complete = true;
62                true
63            }
64            ServerMessage::AssetsChanged(changes) if self.assets_complete => {
65                changes.clone().apply_to(&mut self.assets);
66                true
67            }
68            ServerMessage::Timing { beat, render } if self.complete => {
69                self.state.beat = *beat;
70                self.state.render = *render;
71                true
72            }
73            _ => false,
74        }
75    }
76}
77
78#[cfg(test)]
79mod tests {
80    use std::sync::Arc;
81
82    use gled_model::asset::project::Project;
83
84    use super::*;
85
86    fn update(project: ProjectUpdate) -> ServerMessage {
87        ServerMessage::Update {
88            state: Arc::default(),
89            project,
90            applied_sequence: 0,
91        }
92    }
93
94    #[test]
95    fn only_messages_that_may_change_the_project_are_counted() {
96        let mut replica = StateReplica::default();
97        replica.apply(&ServerMessage::State {
98            state: Arc::default(),
99            applied_sequence: 0,
100        });
101        assert_eq!(replica.project_changes, 1, "a full state");
102
103        replica.apply(&update(ProjectUpdate::Unchanged));
104        assert_eq!(replica.project_changes, 1, "an update without one");
105
106        let project = Project::default();
107        replica.apply(&update(ProjectUpdate::between(None, Some(&project))));
108        assert_eq!(replica.project_changes, 2, "another project");
109        assert_eq!(replica.state.project.as_deref(), Some(&project));
110    }
111}