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    /// Counts the messages that changed the assets, so an interface rebuilds
27    /// what it derives from them only when this moved.
28    pub assets_changes: u64,
29}
30
31impl StateReplica {
32    /// Takes a message. Returns whether the state changed.
33    pub fn apply(&mut self, message: &ServerMessage) -> bool {
34        match message {
35            ServerMessage::State {
36                state,
37                applied_sequence,
38            } => {
39                self.state = EngineState::clone(state);
40                self.applied_sequence = *applied_sequence;
41                self.complete = true;
42                self.project_changes += 1;
43                true
44            }
45            ServerMessage::Update {
46                state,
47                project,
48                applied_sequence,
49            } if self.complete => {
50                if !matches!(project, ProjectUpdate::Unchanged) {
51                    self.project_changes += 1;
52                }
53                let mut replica_project = self.state.project.take();
54                project.clone().apply_to(&mut replica_project);
55                self.state = EngineState {
56                    project: replica_project,
57                    ..EngineState::clone(state)
58                };
59                self.applied_sequence = *applied_sequence;
60                true
61            }
62            ServerMessage::Assets(assets) => {
63                self.assets = collections_from(assets.clone());
64                self.assets_complete = true;
65                self.assets_changes += 1;
66                true
67            }
68            ServerMessage::AssetsChanged(changes) if self.assets_complete => {
69                changes.clone().apply_to(&mut self.assets);
70                self.assets_changes += 1;
71                true
72            }
73            ServerMessage::Timing { beat, render } if self.complete => {
74                self.state.beat = *beat;
75                self.state.render = *render;
76                true
77            }
78            _ => false,
79        }
80    }
81}
82
83#[cfg(test)]
84mod tests {
85    use std::sync::Arc;
86
87    use gled_model::asset::project::Project;
88
89    use super::*;
90
91    fn update(project: ProjectUpdate) -> ServerMessage {
92        ServerMessage::Update {
93            state: Arc::default(),
94            project,
95            applied_sequence: 0,
96        }
97    }
98
99    #[test]
100    fn only_messages_that_may_change_the_project_are_counted() {
101        let mut replica = StateReplica::default();
102        replica.apply(&ServerMessage::State {
103            state: Arc::default(),
104            applied_sequence: 0,
105        });
106        assert_eq!(replica.project_changes, 1, "a full state");
107
108        replica.apply(&update(ProjectUpdate::Unchanged));
109        assert_eq!(replica.project_changes, 1, "an update without one");
110
111        let project = Project::default();
112        replica.apply(&update(ProjectUpdate::between(None, Some(&project))));
113        assert_eq!(replica.project_changes, 2, "another project");
114        assert_eq!(replica.state.project.as_deref(), Some(&project));
115    }
116
117    #[test]
118    fn asset_messages_are_counted() {
119        let mut replica = StateReplica::default();
120        replica.apply(&ServerMessage::AssetsChanged(Default::default()));
121        assert_eq!(replica.assets_changes, 0, "changes before the assets");
122        replica.apply(&ServerMessage::Assets(Vec::new()));
123        replica.apply(&ServerMessage::AssetsChanged(Default::default()));
124        assert_eq!(replica.assets_changes, 2);
125    }
126}