gled-core 2.33.0

The engine of gled without a user interface: inputs, timing, rendering, Art-Net/DMX output and storage
Documentation
//! Connections of user interfaces to the engine. Any number can be open at
//! once: the desktop app in-process, and browsers or remote desktops over the
//! network. Each has its own numbered actions and held keys; all get the same
//! state.

use std::{
    collections::BTreeSet,
    sync::{
        Arc,
        atomic::{AtomicU64, Ordering::Relaxed},
    },
};

use kanal::{Receiver, Sender, bounded, unbounded};
use tracing::warn;

use super::{ClientInput, EngineEvent, EngineState, InputLog, LearnLog, MidiLog, SoundSnapshot};
use crate::{action::UiAction, input::key_code::KeyCode, pipeline::extract_output::PreviewOutput};
use gled_model::collection::Collections as AssetCollections;

/// Identifies a connection while it is open.
pub type ClientId = u64;

/// What the engine's input channel carries.
#[derive(Debug)]
pub(super) enum EngineInput {
    Connect {
        id: ClientId,
        state: Sender<ClientState>,
        events: Sender<EngineEvent>,
    },
    Client {
        id: ClientId,
        input: ClientInput,
    },
    Disconnect(ClientId),
}

/// The engine's state as one client sees it: the shared state, and how far
/// the engine has got with this client's actions.
#[derive(Debug, Clone)]
pub struct ClientState {
    pub(crate) shared: Arc<EngineState>,
    /// The number of the newest action of this client the engine applied.
    pub(crate) applied_sequence: u64,
    /// The engine's assets. A new value whenever they changed.
    pub(crate) assets: Arc<AssetCollections>,
    /// The newest rendered output for the preview, a new value per frame.
    pub(crate) preview: Option<PreviewOutput>,
    /// The sound levels, a new value 30 times a second.
    pub(crate) sound: Arc<SoundSnapshot>,
    /// The input events of the last ticks.
    pub(crate) input_log: Arc<InputLog>,
    /// MIDI traffic of the last ticks, while an interface watches it.
    pub(crate) midi_log: Arc<MidiLog>,
    /// Messages captured for MIDI learn.
    pub(crate) learn_log: Arc<LearnLog>,
}

/// The engine's record of an open connection.
#[derive(Debug)]
pub(super) struct Session {
    pub(super) state: Sender<ClientState>,
    pub(super) events: Sender<EngineEvent>,
    pub(super) applied_sequence: u64,
    pub(super) keys: BTreeSet<KeyCode>,
}

/// Opens connections to the engine. Cheap to clone; the engine runs as long
/// as a connector or a connection exists.
#[derive(Debug, Clone)]
pub struct EngineConnector {
    pub(super) inputs: Sender<EngineInput>,
    pub(super) next_id: Arc<AtomicU64>,
}

impl EngineConnector {
    /// Opens a new connection.
    pub fn connect(&self) -> ClientConnection {
        let id = self.next_id.fetch_add(1, Relaxed);
        // A client that falls behind only needs the newest state.
        let (state_sender, state) = bounded(4);
        let (event_sender, events) = unbounded();
        if let Err(err) = self.inputs.send(EngineInput::Connect {
            id,
            state: state_sender,
            events: event_sender,
        }) {
            warn!("The engine has stopped: {err}");
        }
        ClientConnection {
            id,
            inputs: self.inputs.clone(),
            sequence: Arc::default(),
            state,
            events,
        }
    }
}

/// One client's end of the engine. Closes when dropped.
#[derive(Debug)]
pub struct ClientConnection {
    id: ClientId,
    inputs: Sender<EngineInput>,
    /// Number of the last action sent.
    sequence: Arc<AtomicU64>,
    state: Receiver<ClientState>,
    events: Receiver<EngineEvent>,
}

impl ClientConnection {
    pub fn id(&self) -> ClientId {
        self.id
    }

    /// Sends an action with the client's own number, for clients that number
    /// their actions themselves (remote ones).
    pub fn send_numbered_action(&self, sequence: u64, action: UiAction) {
        self.sequence.fetch_max(sequence, Relaxed);
        self.send(ClientInput::Action { sequence, action });
    }

    /// Sends other input.
    pub fn send(&self, input: ClientInput) {
        if let Err(err) = self.inputs.send(EngineInput::Client { id: self.id, input }) {
            warn!("The engine has stopped: {err}");
        }
    }

    /// The newest state since the last call, if any.
    pub fn latest_state(&self) -> Option<ClientState> {
        let mut latest = None;
        while let Ok(Some(state)) = self.state.try_recv() {
            latest = Some(state);
        }
        latest
    }

    /// The receiver of states, for clients that wait on several sources.
    pub fn state_receiver(&self) -> &Receiver<ClientState> {
        &self.state
    }

    /// The receiver of events, for clients that wait on several sources.
    pub fn event_receiver(&self) -> &Receiver<EngineEvent> {
        &self.events
    }

    /// The next event, if any.
    pub fn next_event(&self) -> Option<EngineEvent> {
        self.events.try_recv().ok().flatten()
    }
}

impl Drop for ClientConnection {
    fn drop(&mut self) {
        // Fails only when the engine is gone, which needs no goodbye.
        let _ = self.inputs.send(EngineInput::Disconnect(self.id));
    }
}