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 artnet_inputs = output_sender::start(output_receiver).map_err(StartError::Output)?;
61    let (artnet_bridge, artnet_control) = artnet::start_thread(artnet_inputs);
62    Input::init(artnet_bridge);
63    output_state::init();
64    Ok(EngineStart {
65        actions,
66        extract_output,
67        artnet_control,
68        preview_output,
69        network_stats,
70        temperature,
71        midi_monitor,
72        midi_learn,
73        midi_test,
74    })
75}
76
77impl EngineStart {
78    /// Starts the engine rendering on `adapter`.
79    pub fn spawn(self, adapter: wgpu::Adapter) -> Result<EngineConnector, StartError> {
80        engine::spawn(
81            EngineConfig {
82                adapter,
83                extract_output: self.extract_output,
84                artnet_control_receiver: self.artnet_control,
85                preview_output: self.preview_output,
86                network_stats: self.network_stats,
87                temperature: self.temperature,
88                midi_monitor: self.midi_monitor,
89                midi_learn: self.midi_learn,
90                midi_test: self.midi_test,
91                audio_pool: AudioPool::default(),
92            },
93            self.actions,
94        )
95        .map_err(StartError::Engine)
96    }
97}
98
99/// Opens a GPU without a window, for the server: the fastest that works,
100/// see `gpu_choice`. The backends can be narrowed with `WGPU_BACKEND`, e.g.
101/// `vulkan` on a Raspberry Pi.
102pub async fn headless_adapter() -> Result<wgpu::Adapter, StartError> {
103    let instance =
104        wgpu::Instance::new(wgpu::InstanceDescriptor::new_without_display_handle_from_env());
105    let adapters = instance.enumerate_adapters(wgpu::Backends::all()).await;
106    let adapter = crate::gpu_choice::choose(&adapters, |_| true).ok_or(StartError::NoAdapter)?;
107    let adapter_info = adapter.get_info();
108    info!(
109        name = %adapter_info.name,
110        backend = ?adapter_info.backend,
111        "rendering on GPU adapter"
112    );
113    Ok(adapter)
114}