gled-core 2.31.1

The engine of gled without a user interface: inputs, timing, rendering, Art-Net/DMX output and storage
Documentation
//! A stand-in for the engine in tests of the transports: it needs no GPU,
//! records what clients send and publishes states only when told to.

use std::{
    collections::{BTreeSet, HashMap},
    sync::Arc,
    time::{Duration, Instant},
};

use gled_model::collection::Collections as AssetCollections;
use kanal::{Receiver, unbounded};

use super::{
    ClientId, ClientInput, ClientState, EngineConnector, EngineEvent, EngineState,
    session::{EngineInput, Session},
};

/// Opens a fake engine and the connector clients use to reach it.
pub fn fake_engine() -> (EngineConnector, FakeEngine) {
    let (inputs, receiver) = unbounded();
    (
        EngineConnector {
            inputs,
            next_id: Arc::default(),
        },
        FakeEngine {
            inputs: receiver,
            sessions: HashMap::new(),
            assets: Arc::default(),
            preview: None,
        },
    )
}

#[derive(Debug)]
pub struct FakeEngine {
    inputs: Receiver<EngineInput>,
    sessions: HashMap<ClientId, Session>,
    assets: Arc<AssetCollections>,
    preview: Option<Arc<Vec<u8>>>,
}

impl FakeEngine {
    /// Waits up to `timeout` for the next input of any client. Connections
    /// and disconnections are handled on the way.
    pub fn next_input(&mut self, timeout: Duration) -> Option<(ClientId, ClientInput)> {
        let deadline = Instant::now() + timeout;
        loop {
            let left = deadline.saturating_duration_since(Instant::now());
            let input = if left.is_zero() {
                self.inputs.try_recv().ok().flatten()
            } else {
                self.inputs.recv_timeout(left).ok()
            };
            match input? {
                EngineInput::Connect { id, state, events } => {
                    self.sessions.insert(
                        id,
                        Session {
                            state,
                            events,
                            applied_sequence: 0,
                            keys: BTreeSet::new(),
                        },
                    );
                }
                EngineInput::Client { id, input } => {
                    if let Some(session) = self.sessions.get_mut(&id) {
                        match &input {
                            ClientInput::Action { sequence, .. } => {
                                session.applied_sequence = session.applied_sequence.max(*sequence);
                            }
                            ClientInput::Keys(keys) => session.keys = keys.clone(),
                            ClientInput::SetBeatsPerMinute(_)
                            | ClientInput::SaveAsset(_)
                            | ClientInput::DeleteAsset(_)
                            | ClientInput::Storage(_)
                            | ClientInput::SetServerSettings(_)
                            | ClientInput::HoverOutputRouting(_)
                            | ClientInput::WatchDevices(_)
                            | ClientInput::WatchMidi(_)
                            | ClientInput::MidiLearning(_)
                            | ClientInput::MidiTest(_)
                            | ClientInput::WatchSoundTriggers(_) => {}
                        }
                    }
                    return Some((id, input));
                }
                EngineInput::Disconnect(id) => {
                    self.sessions.remove(&id);
                }
            }
        }
    }

    /// Handles connections and disconnections that arrived until now.
    pub fn poll_connections(&mut self) {
        while let Some(input) = self.next_input(Duration::ZERO) {
            // Client inputs are not expected here; tests that care call
            // `next_input`.
            drop(input);
        }
    }

    /// The connected clients.
    pub fn clients(&self) -> BTreeSet<ClientId> {
        self.sessions.keys().copied().collect()
    }

    /// The keys a client holds.
    pub fn keys(&self, id: ClientId) -> BTreeSet<gled_model::input::key_code::KeyCode> {
        self.sessions
            .get(&id)
            .map(|session| session.keys.clone())
            .unwrap_or_default()
    }

    /// The assets the next `publish` sends.
    pub fn set_assets(&mut self, assets: AssetCollections) {
        self.assets = Arc::new(assets);
    }

    /// The preview frame the next `publish` sends.
    pub fn set_preview(&mut self, output: Vec<u8>) {
        self.preview = Some(Arc::new(output));
    }

    /// Sends `state` to every client, with the number of its newest action.
    pub fn publish(&self, state: EngineState) {
        let shared = Arc::new(state);
        for session in self.sessions.values() {
            let _ = session.state.try_send(ClientState {
                shared: shared.clone(),
                applied_sequence: session.applied_sequence,
                assets: self.assets.clone(),
                preview: self.preview.clone(),
                sound: Arc::default(),
                input_log: Arc::default(),
                midi_log: Arc::default(),
                learn_log: Arc::default(),
            });
        }
    }

    pub fn send_event(&self, event: EngineEvent) {
        for session in self.sessions.values() {
            let _ = session.events.send(event.clone());
        }
    }
}