gled-core 2.31.2

The engine of gled without a user interface: inputs, timing, rendering, Art-Net/DMX output and storage
Documentation
//! What the engine reports: the render thread's feedback in, the state and
//! events out to every connected user interface.

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

use gled_model::{collection::Collections as AssetCollections, sound::trigger_ref::SoundLevels};
use gled_protocol::bytes::SharedBytes;
use uuid::Uuid;

use super::{ClientState, Engine, EngineEvent, EngineState, SoundSnapshot};
use crate::runtime::render_thread::RenderFeedback;

/// Sound levels are published every this many ticks, 30 times a second.
const SOUND_EVERY_TICKS: u32 = 4;

impl Engine {
    pub(super) fn receive_render_feedback(&mut self) {
        while let Ok(Some(stats)) = self.network_stats_receiver.try_recv() {
            self.network_stats = stats;
        }
        while let Ok(Some(temperature)) = self.temperature_receiver.try_recv() {
            self.temperature = temperature;
        }
        while let Ok(Some(output)) = self.preview_output.try_recv() {
            self.preview = Some(output);
        }
        if self.project.is_none() {
            self.preview = None;
        }
        while let Some(feedback) = self.render_thread.try_recv_feedback() {
            match feedback {
                RenderFeedback::FrameStats(stats) => {
                    self.timing.set_framerate(stats.fps);
                    self.render_stats = stats;
                }
            }
        }
    }

    pub(super) fn publish_state(&mut self) {
        let shared = Arc::new(EngineState {
            project: self.project.clone(),
            project_id: self.project_id,
            selected_scene_instance: self.selected_scene_instance,
            selected_scene_effect: self
                .effect_selection
                .effect_in(self.selected_scene_instance),
            blackout: self.blackout,
            blackout_hold: self.blackout_hold,
            midi_output_active: self.midi_output_active,
            output_config: self.output_config.clone(),
            beat: self.timing.beat_state(),
            tap_count: self.timing.tap_count(),
            layout_generation: self.patch_layout.generation(),
            render: self.render_stats,
            status: self.server_status(),
        });
        self.ticks_since_sound += 1;
        if self.ticks_since_sound >= SOUND_EVERY_TICKS {
            self.ticks_since_sound = 0;
            let watched: BTreeSet<Uuid> =
                self.watched_triggers.values().flatten().copied().collect();
            self.sound = Arc::new(SoundSnapshot {
                levels: self.sound_data.trigger_levels().collect(),
                impulses: self
                    .sound_data
                    .triggers
                    .iter()
                    .map(|(id, trigger)| (*id, trigger.impulse))
                    .collect(),
                spectrum: SharedBytes(Arc::new(self.sound_data.spectrum_bytes().to_vec())),
                trigger_spectra: watched
                    .iter()
                    .filter_map(|id| {
                        let trigger = self.sound_data.triggers.get(id)?;
                        let bytes = trigger
                            .spectrum
                            .iter()
                            .flat_map(|value| value.to_le_bytes())
                            .collect();
                        Some((*id, SharedBytes(Arc::new(bytes))))
                    })
                    .collect(),
            });
        }
        if self.published_assets.0 != self.collections.generation() {
            self.published_assets = (
                self.collections.generation(),
                Arc::new(AssetCollections::clone(&self.collections)),
            );
        }
        let mut gone = Vec::new();
        for (id, session) in &self.sessions {
            // A full channel means the client has not taken the last states
            // yet; it only ever wants the newest, so this one is dropped.
            let state = ClientState {
                shared: shared.clone(),
                applied_sequence: session.applied_sequence,
                assets: self.published_assets.1.clone(),
                preview: self.preview.clone(),
                sound: self.sound.clone(),
                input_log: self.input_log.clone(),
                midi_log: self.midi.monitor_log.clone(),
                learn_log: self.midi.learn_log.clone(),
            };
            if session.state.try_send(state).is_err() {
                gone.push(*id);
            }
        }
        for id in gone {
            self.disconnect(id);
        }
    }

    pub(super) fn send_event(&self, event: EngineEvent) {
        for session in self.sessions.values() {
            // A closed channel is noticed by the next `publish_state`.
            let _ = session.events.send(event.clone());
        }
    }
}