gled-core 2.34.0

The engine of gled without a user interface: inputs, timing, rendering, Art-Net/DMX output and storage
Documentation
//! Actions on the running show around the project: timing, blackout,
//! devices and outputs, loading and closing projects, and errors.

use std::sync::Arc;

use crate::action::UiAction;
use crate::audio::device_id::cpal_device_id;
use crate::engine::{Engine, EngineEvent};
use crate::storage::asset::Asset;

use super::ignore_without_project;

impl Engine {
    /// Runs the show around the project: timing, blackout, devices and
    /// outputs, the loaded project, errors.
    pub(super) fn apply_show_action(&mut self, action: UiAction) {
        // Only the audio device is a project setting; the project is not
        // taken mutably for the others, which would copy it.
        match (self.project.is_some(), action) {
            (true, UiAction::SetAudioDevice(device_id)) => {
                self.audio_pool.selected_device = device_id.as_ref().and_then(cpal_device_id);
                if let Some(project) = self.project.as_mut().map(Arc::make_mut) {
                    project.audio_input_device = device_id;
                }
                self.audio_pool.restart_fft();
            }
            (_, UiAction::Tap) => {
                self.timing.tap(&self.persistent_state);
            }
            (_, UiAction::SpeedAdd(delta)) => {
                self.timing.add_speed(delta, &self.persistent_state);
            }
            (_, UiAction::SpeedMultiply(multiplier)) => {
                self.timing
                    .multiply_speed(multiplier, &self.persistent_state);
            }
            (_, UiAction::SetBlackout(blackout)) => {
                self.blackout = blackout;
            }
            (_, UiAction::ToggleBlackout) => {
                self.blackout = !self.blackout;
            }
            (_, UiAction::SetProject(project)) => {
                // Nothing of the previous project stays behind.
                self.unload_project();
                if let Some(project) = Asset::get(project, &self.collections) {
                    self.persistent_state.set_last_project_id(project.id);
                    self.persistent_state.save();

                    self.project_id = Some(project.id);
                    let project = Arc::unwrap_or_clone(project).data;
                    self.selected_scene_instance = project
                        .all_scene_instance_locations()
                        .next()
                        .copied()
                        .unwrap_or_default();
                    self.output_config.routings = project.output_routings.clone();
                    self.audio_pool.selected_device =
                        project.audio_input_device.as_ref().and_then(cpal_device_id);
                    self.audio_pool.restart_fft();
                    // Channel overwrites and Art-Net settings reach the
                    // running output on this tick (`apply_project_settings`);
                    // the render thread builds the GPU pipelines of the new
                    // project when it receives the next snapshot.
                    self.replace_project(Some(project));
                }
            }
            (_, UiAction::MidiOutputActive(active)) => {
                self.midi_output_active = active;
            }
            (_, UiAction::Error(error)) => {
                tracing::error!("{error}");
                self.send_event(EngineEvent::Error(error));
            }
            (_, UiAction::CloseProject) => self.unload_project(),
            (_, UiAction::SetOutputRoutings(routings)) => {
                self.output_config.routings = routings;
            }
            (false, _) => ignore_without_project(),
            // `apply_action` routes only show actions here.
            (true, _) => {}
        }
    }
}