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 gled_midi::{HostEvent, native::NativeHost};
6use kanal::Receiver;
7use thiserror::Error;
8use tracing::info;
9
10use crate::{
11    action::UiAction,
12    audio::AudioPool,
13    engine::{self, EngineConfig, EngineConnector},
14    host_temperature,
15    input::{Input, artnet},
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: NativeHost,
46    midi_events: Receiver<HostEvent>,
47}
48
49/// Starts storage, MIDI, Art-Net, the output and the input polling, and
50/// returns what the engine needs of them. Call once per process.
51pub fn services() -> Result<EngineStart, StartError> {
52    let actions = UiAction::init_queue().ok_or(StartError::ActionQueue)?;
53    storage::start_thread();
54    let (midi_events, midi_event_receiver) = kanal::unbounded();
55    let midi = NativeHost::start(midi_events);
56    let network_stats = network_stats::start_thread();
57    let temperature = host_temperature::start_thread();
58    let (extract_output, output_receiver, preview_output) = ExtractOutput::new();
59    let artnet_inputs = output_sender::start(output_receiver).map_err(StartError::Output)?;
60    let (artnet_bridge, artnet_control) = artnet::start_thread(artnet_inputs);
61    Input::init(artnet_bridge);
62    output_state::init();
63    Ok(EngineStart {
64        actions,
65        extract_output,
66        artnet_control,
67        preview_output,
68        network_stats,
69        temperature,
70        midi,
71        midi_events: midi_event_receiver,
72    })
73}
74
75impl EngineStart {
76    /// Starts the engine rendering on `adapter`.
77    pub fn spawn(self, adapter: wgpu::Adapter) -> Result<EngineConnector, StartError> {
78        engine::spawn(
79            EngineConfig {
80                adapter,
81                extract_output: self.extract_output,
82                artnet_control_receiver: self.artnet_control,
83                preview_output: self.preview_output,
84                network_stats: self.network_stats,
85                temperature: self.temperature,
86                midi: self.midi,
87                midi_events: self.midi_events,
88                audio_pool: AudioPool::default(),
89            },
90            self.actions,
91        )
92        .map_err(StartError::Engine)
93    }
94}
95
96/// Opens a GPU without a window, for the server: the fastest that works,
97/// see `gpu_choice`. The backends can be narrowed with `WGPU_BACKEND`, e.g.
98/// `vulkan` on a Raspberry Pi.
99pub async fn headless_adapter() -> Result<wgpu::Adapter, StartError> {
100    let instance =
101        wgpu::Instance::new(wgpu::InstanceDescriptor::new_without_display_handle_from_env());
102    let adapters = instance.enumerate_adapters(wgpu::Backends::all()).await;
103    let adapter = crate::gpu_choice::choose(&adapters, |_| true).ok_or(StartError::NoAdapter)?;
104    let adapter_info = adapter.get_info();
105    info!(
106        name = %adapter_info.name,
107        backend = ?adapter_info.backend,
108        "rendering on GPU adapter"
109    );
110    Ok(adapter)
111}