gled-core 2.34.0

The engine of gled without a user interface: inputs, timing, rendering, Art-Net/DMX output and storage
Documentation
//! What the user interfaces send the engine, and their sessions coming and
//! going.

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

use gled_model::asset::{AssetTrait, output_device::routing::OutputRouting};
use gled_protocol::assets::{AssetIdVisitor, AssetVisitor};
use tracing::debug;

use super::{ClientId, ClientInput, Engine, StorageCommand, session};
use crate::{
    input::Input,
    pipeline::output_sender::HOVERED_OUTPUT_ROUTING,
    storage::{
        action::StorageAction,
        asset::Asset,
        asset_id::AssetId,
        collections::Collections,
        store::{AssetStore, delete_asset},
    },
};

impl Engine {
    /// Applies what the user interfaces sent. Returns `false` once no
    /// connector or connection is left.
    pub(super) fn receive_client_inputs(&mut self) -> bool {
        loop {
            match self.inputs.try_recv() {
                Ok(Some(session::EngineInput::Connect { id, state, events })) => {
                    debug!(id, "client connected");
                    self.sessions.insert(
                        id,
                        session::Session {
                            state,
                            events,
                            applied_sequence: 0,
                            keys: BTreeSet::new(),
                        },
                    );
                }
                Ok(Some(session::EngineInput::Client { id, input })) => {
                    self.apply_client_input(id, input)
                }
                Ok(Some(session::EngineInput::Disconnect(id))) => self.disconnect(id),
                Ok(None) => return true,
                Err(_) => return false,
            }
        }
    }

    fn apply_client_input(&mut self, id: ClientId, input: ClientInput) {
        match input {
            ClientInput::Action { sequence, action } => {
                if let Some(session) = self.sessions.get_mut(&id) {
                    session.applied_sequence = session.applied_sequence.max(sequence);
                }
                self.apply_action(action);
            }
            ClientInput::Keys(keys) => {
                debug!(id, ?keys, "keys held in the interface");
                if let Some(session) = self.sessions.get_mut(&id) {
                    session.keys = keys;
                }
                self.update_keys();
            }
            ClientInput::SetBeatsPerMinute(bpm) => self.timing.set_beats_per_minute(bpm),
            ClientInput::SaveAsset(asset) => asset.accept(&mut SaveInto(&mut self.collections)),
            ClientInput::DeleteAsset(id) => id.accept(&mut DeleteFrom(&mut self.collections)),
            ClientInput::Storage(command) => match command {
                StorageCommand::Commit { message } => StorageAction::Commit { message }.enqueue(),
                StorageCommand::Pull => StorageAction::Pull.enqueue(),
                StorageCommand::Push => StorageAction::Push.enqueue(),
                StorageCommand::SwitchBranch(branch) => {
                    StorageAction::SwitchBranch(branch).enqueue()
                }
                StorageCommand::Reclone => StorageAction::Nuke.enqueue(),
                StorageCommand::SetGitPassphrase(passphrase) => self
                    .persistent_state
                    .git_credentials_mut()
                    .set_passphrase(passphrase),
            },
            ClientInput::SetServerSettings(settings) => self.apply_server_settings(settings),
            ClientInput::WatchDevices(watch) => self.device_scan.watch(id, watch),
            ClientInput::WatchMidi(watch) => self.midi.watch(id, watch),
            ClientInput::MidiLearning(learning) => self.midi.learning(id, learning),
            ClientInput::MidiTest(command) => self.midi.test(command),
            ClientInput::WatchSoundTriggers(triggers) => {
                self.watched_triggers.insert(id, triggers);
            }
            ClientInput::SavePicture { path, bytes } => self.pictures.save(path, bytes),
            ClientInput::RequestPicture(path) => {
                if let Some(session) = self.sessions.get(&id) {
                    self.pictures.request(path, &session.events);
                }
            }
            ClientInput::HoverOutputRouting(routing) => {
                let owned = self
                    .hovered_routing
                    .as_ref()
                    .is_some_and(|(owner, _)| *owner == id);
                match routing {
                    Some(routing) => self.set_hovered_routing(Some((id, routing))),
                    None if owned => self.set_hovered_routing(None),
                    None => {}
                }
            }
        }
    }

    pub(super) fn disconnect(&mut self, id: ClientId) {
        if self.sessions.remove(&id).is_some() {
            debug!(id, "client disconnected");
            // Keys it held are released, and a routing it pointed at.
            self.update_keys();
            self.device_scan.disconnect(id);
            self.midi.disconnect(id);
            self.watched_triggers.remove(&id);
            if self
                .hovered_routing
                .as_ref()
                .is_some_and(|(owner, _)| *owner == id)
            {
                self.set_hovered_routing(None);
            }
        }
    }

    pub(super) fn set_hovered_routing(&mut self, hovered: Option<(ClientId, OutputRouting)>) {
        *HOVERED_OUTPUT_ROUTING.lock() = hovered.as_ref().map(|(_, routing)| routing.clone());
        self.hovered_routing = hovered;
    }

    /// A key counts as held while any interface holds it.
    fn update_keys(&self) {
        let keys = self
            .sessions
            .values()
            .flat_map(|session| session.keys.iter().copied())
            .collect();
        Input::set_keys(keys);
    }
}

/// Saves an asset a client sent, of whatever kind.
struct SaveInto<'a>(&'a mut Collections);

impl AssetVisitor for SaveInto<'_> {
    fn asset<T: AssetTrait>(&mut self, asset: Arc<Asset<T>>) {
        Arc::unwrap_or_clone(asset).save(self.0);
    }
}

/// Deletes an asset a client named, of whatever kind.
struct DeleteFrom<'a>(&'a mut Collections);

impl AssetIdVisitor for DeleteFrom<'_> {
    fn id<T: AssetTrait>(&mut self, id: AssetId<T>) {
        delete_asset(id, self.0);
    }
}