Skip to main content

gled_core/
start.rs

1//! Starting gled without a user interface: the background services first,
2//! then the engine once a GPU adapter is known. The desktop app gets its
3//! adapter from the window; the server opens one headless.
4
5use kanal::{Receiver, Sender};
6use thiserror::Error;
7use tracing::info;
8
9use crate::{
10    action::UiAction,
11    audio::AudioPool,
12    engine::{self, EngineConfig, EngineConnector},
13    host_temperature,
14    input::{Input, artnet},
15    midi::{self, monitor::MidiMonitorEvent, runtime::TestCommand},
16    network_stats, output_state,
17    pipeline::{
18        extract_output::{ExtractOutput, PreviewOutput},
19        output_sender,
20    },
21    storage,
22};
23
24#[derive(Debug, Error)]
25pub enum StartError {
26    #[error("the action queue was already set up")]
27    ActionQueue,
28    #[error("could not start the output: {0:#}")]
29    Output(anyhow::Error),
30    #[error("no GPU works")]
31    NoAdapter,
32    #[error("could not start the engine: {0:#}")]
33    Engine(anyhow::Error),
34}
35
36/// What the engine needs from the services, until it is spawned.
37#[derive(Debug)]
38pub struct EngineStart {
39    actions: Receiver<UiAction>,
40    extract_output: ExtractOutput,
41    artnet_control: Receiver<Vec<u8>>,
42    preview_output: Receiver<PreviewOutput>,
43    network_stats: Receiver<(f64, f64)>,
44    temperature: Receiver<Option<f32>>,
45    midi_monitor: Receiver<MidiMonitorEvent>,
46    midi_learn: Receiver<[u8; 3]>,
47    midi_test: Sender<TestCommand>,
48}
49
50/// Starts storage, MIDI, Art-Net, the output and the input polling, and
51/// returns what the engine needs of them. Call once per process.
52pub fn services() -> Result<EngineStart, StartError> {
53    let actions = UiAction::init_queue().ok_or(StartError::ActionQueue)?;
54    storage::start_thread();
55    let (midi_monitor, midi_test) = midi::start_threads();
56    let midi_learn = midi::learn::init();
57    let network_stats = network_stats::start_thread();
58    let temperature = host_temperature::start_thread();
59    let (extract_output, output_receiver, preview_output) = ExtractOutput::new();
60    let output_package_sender =
61        output_sender::start(output_receiver).map_err(StartError::Output)?;
62    let (artnet_bridge, artnet_control) = artnet::start_thread(output_package_sender);
63    Input::init(artnet_bridge);
64    output_state::init();
65    Ok(EngineStart {
66        actions,
67        extract_output,
68        artnet_control,
69        preview_output,
70        network_stats,
71        temperature,
72        midi_monitor,
73        midi_learn,
74        midi_test,
75    })
76}
77
78impl EngineStart {
79    /// Starts the engine rendering on `adapter`.
80    pub fn spawn(self, adapter: wgpu::Adapter) -> Result<EngineConnector, StartError> {
81        engine::spawn(
82            EngineConfig {
83                adapter,
84                extract_output: self.extract_output,
85                artnet_control_receiver: self.artnet_control,
86                preview_output: self.preview_output,
87                network_stats: self.network_stats,
88                temperature: self.temperature,
89                midi_monitor: self.midi_monitor,
90                midi_learn: self.midi_learn,
91                midi_test: self.midi_test,
92                audio_pool: AudioPool::default(),
93            },
94            self.actions,
95        )
96        .map_err(StartError::Engine)
97    }
98}
99
100/// Opens a GPU without a window, for the server: the fastest that works,
101/// see `gpu_choice`. The backends can be narrowed with `WGPU_BACKEND`, e.g.
102/// `vulkan` on a Raspberry Pi.
103pub async fn headless_adapter() -> Result<wgpu::Adapter, StartError> {
104    let instance =
105        wgpu::Instance::new(wgpu::InstanceDescriptor::new_without_display_handle_from_env());
106    let adapters = instance.enumerate_adapters(wgpu::Backends::all()).await;
107    let adapter = crate::gpu_choice::choose(&adapters, |_| true).ok_or(StartError::NoAdapter)?;
108    let adapter_info = adapter.get_info();
109    info!(
110        name = %adapter_info.name,
111        backend = ?adapter_info.backend,
112        "rendering on GPU adapter"
113    );
114    Ok(adapter)
115}