gled-protocol 2.31.2

What gled's user interfaces and its engine send each other
Documentation
//! What a remote interface knows of the engine, rebuilt from the server's
//! messages.

use gled_model::collection::Collections;

use crate::{
    assets::collections_from, message::ServerMessage, project_update::ProjectUpdate,
    state::EngineState,
};

#[derive(Debug, Clone, Default)]
pub struct StateReplica {
    pub state: EngineState,
    /// The number of the newest action of this interface the engine applied.
    pub applied_sequence: u64,
    /// Whether a full state has arrived; updates before it are ignored.
    pub complete: bool,
    /// The engine's assets.
    pub assets: Collections,
    /// Whether all assets have arrived; changes before them are ignored.
    pub(crate) assets_complete: bool,
    /// Counts the messages that may have changed the project: full states
    /// and updates with a project change. An interface compares its own
    /// project with the engine's only when this moved.
    pub project_changes: u64,
    /// Counts the messages that changed the assets, so an interface rebuilds
    /// what it derives from them only when this moved.
    pub assets_changes: u64,
}

impl StateReplica {
    /// Takes a message. Returns whether the state changed.
    pub fn apply(&mut self, message: &ServerMessage) -> bool {
        match message {
            ServerMessage::State {
                state,
                applied_sequence,
            } => {
                self.state = EngineState::clone(state);
                self.applied_sequence = *applied_sequence;
                self.complete = true;
                self.project_changes += 1;
                true
            }
            ServerMessage::Update {
                state,
                project,
                applied_sequence,
            } if self.complete => {
                if !matches!(project, ProjectUpdate::Unchanged) {
                    self.project_changes += 1;
                }
                let mut replica_project = self.state.project.take();
                project.clone().apply_to(&mut replica_project);
                self.state = EngineState {
                    project: replica_project,
                    ..EngineState::clone(state)
                };
                self.applied_sequence = *applied_sequence;
                true
            }
            ServerMessage::Assets(assets) => {
                self.assets = collections_from(assets.clone());
                self.assets_complete = true;
                self.assets_changes += 1;
                true
            }
            ServerMessage::AssetsChanged(changes) if self.assets_complete => {
                changes.clone().apply_to(&mut self.assets);
                self.assets_changes += 1;
                true
            }
            ServerMessage::Timing { beat, render } if self.complete => {
                self.state.beat = *beat;
                self.state.render = *render;
                true
            }
            _ => false,
        }
    }
}

#[cfg(test)]
mod tests {
    use std::sync::Arc;

    use gled_model::asset::project::Project;

    use super::*;

    fn update(project: ProjectUpdate) -> ServerMessage {
        ServerMessage::Update {
            state: Arc::default(),
            project,
            applied_sequence: 0,
        }
    }

    #[test]
    fn only_messages_that_may_change_the_project_are_counted() {
        let mut replica = StateReplica::default();
        replica.apply(&ServerMessage::State {
            state: Arc::default(),
            applied_sequence: 0,
        });
        assert_eq!(replica.project_changes, 1, "a full state");

        replica.apply(&update(ProjectUpdate::Unchanged));
        assert_eq!(replica.project_changes, 1, "an update without one");

        let project = Project::default();
        replica.apply(&update(ProjectUpdate::between(None, Some(&project))));
        assert_eq!(replica.project_changes, 2, "another project");
        assert_eq!(replica.state.project.as_deref(), Some(&project));
    }

    #[test]
    fn asset_messages_are_counted() {
        let mut replica = StateReplica::default();
        replica.apply(&ServerMessage::AssetsChanged(Default::default()));
        assert_eq!(replica.assets_changes, 0, "changes before the assets");
        replica.apply(&ServerMessage::Assets(Vec::new()));
        replica.apply(&ServerMessage::AssetsChanged(Default::default()));
        assert_eq!(replica.assets_changes, 2);
    }
}