1mod actions;
18mod applied_output;
19mod clients;
20mod devices;
21mod event_log;
22#[cfg(any(test, feature = "test-util"))]
23mod fake;
24mod midi_tools;
25mod outbox;
26mod patch_layout;
27mod project;
28mod publish;
29mod session;
30mod setup;
31mod status;
32#[cfg(test)]
33mod tick_bench;
34
35pub(crate) use event_log::{EventLog, InputLog, LearnLog, MidiLog};
36#[cfg(any(test, feature = "test-util"))]
37pub use fake::{FakeEngine, fake_engine};
38pub use gled_protocol::state::{
39 ClientInput, Devices, EngineEvent, EngineState, RenderStats, ServerSettings, ServerStatus,
40 SoundSnapshot, StorageCommand,
41};
42pub use outbox::Outbox;
43pub use session::{ClientConnection, ClientId, ClientState, EngineConnector};
44pub(crate) use setup::{EngineConfig, spawn};
45
46use std::{
47 collections::{BTreeSet, HashMap},
48 sync::Arc,
49 time::{Duration, Instant},
50};
51
52use kanal::Receiver;
53use tracing::debug;
54
55use crate::{
56 action::UiAction,
57 audio::{AudioPool, sound_data::SoundData},
58 input::{
59 Input, external_control::ExternalControlState, osc::OSCHandler, osc::OscConfig,
60 project_inputs::update_input_state,
61 },
62 output_state::ProjectState,
63 pipeline::extract_output::{OutputConfig, PreviewOutput},
64 runtime::{
65 persistent_state::PersistentState,
66 render_thread::{RenderSnapshot, RenderThread},
67 timing::Timing,
68 },
69 storage::{
70 asset::{Asset, project::Project},
71 asset_id::AssetId,
72 collections::Collections,
73 is_loading,
74 },
75};
76use applied_output::AppliedOutput;
77use gled_model::{
78 asset::{output_device::routing::OutputRouting, scene::grid::GridLocation},
79 collection::Collections as AssetCollections,
80};
81use patch_layout::PatchLayout;
82use uuid::Uuid;
83
84const TICK: Duration = Duration::from_micros(8_333);
89
90pub(crate) struct Engine {
91 startup: bool,
93 timing: Timing,
94 project: Option<Arc<Project>>,
99 project_id: Option<AssetId<Project>>,
100 selected_scene_instance: GridLocation,
101 blackout: bool,
102 blackout_hold: bool,
103 midi_output_active: bool,
104 actions: Receiver<UiAction>,
105 inputs: Receiver<session::EngineInput>,
106 sessions: HashMap<ClientId, session::Session>,
107 collections: Collections,
108 persistent_state: PersistentState,
109 output_config: OutputConfig,
110 render_thread: RenderThread,
111 external_control_state: ExternalControlState,
112 audio_pool: AudioPool,
113 sound_data: SoundData,
114 osc_handler: Option<Arc<OSCHandler>>,
115 osc_config: Option<OscConfig>,
117 applied_output: AppliedOutput,
119 device_scan: devices::DeviceScan,
120 watched_triggers: HashMap<ClientId, BTreeSet<Uuid>>,
122 midi: midi_tools::MidiTools,
123 input_log: Arc<InputLog>,
125 hovered_routing: Option<(ClientId, OutputRouting)>,
127 patch_layout: PatchLayout,
128 render_stats: RenderStats,
129 preview_output: Receiver<PreviewOutput>,
130 network_stats_receiver: Receiver<(f64, f64)>,
131 network_stats: (f64, f64),
132 temperature_receiver: Receiver<Option<f32>>,
133 temperature: Option<f32>,
134 host_name: String,
136 sound: Arc<SoundSnapshot>,
137 ticks_since_sound: u32,
138 preview: Option<PreviewOutput>,
140 published_assets: (u64, Arc<AssetCollections>),
142}
143
144impl Engine {
145 fn run(mut self) {
146 let mut next_tick = Instant::now();
147 loop {
148 if !self.tick() {
149 debug!("All user interfaces are gone, stopping the engine");
150 return;
151 }
152 next_tick += TICK;
153 let now = Instant::now();
154 match next_tick.checked_duration_since(now) {
155 Some(wait) => std::thread::sleep(wait),
156 None => next_tick = now,
159 }
160 }
161 }
162
163 #[cfg_attr(feature = "profiling", profiling::function)]
165 fn tick(&mut self) -> bool {
166 self.receive_render_feedback();
167 self.persistent_state.update();
168 self.sound_data.update();
169 self.timing.tick(&self.persistent_state);
170 self.collections.update();
171 self.sync_sound_triggers();
172
173 if !is_loading() && self.startup {
178 match self.persistent_state.last_project_id() {
179 Some(id) if Asset::get(id, &self.collections).is_some() => {
180 self.startup = false;
181 UiAction::SetProject(id).enqueue();
182 }
183 Some(_) if self.collections.get::<Project>().is_none() => {}
185 _ => self.startup = false,
186 }
187 }
188
189 if !self.receive_client_inputs() {
190 return false;
191 }
192 Input::tick();
193 self.midi.tick();
194 let new_events = Input::new_events();
195 if !new_events.is_empty() {
196 let log = Arc::make_mut(&mut self.input_log);
197 for event in new_events {
198 log.push(event);
199 }
200 }
201 self.handle_actions();
202
203 self.external_control_state
204 .process_events(&mut self.project, &self.collections);
205 self.evaluate_triggers();
206 if let Some(project) = &mut self.project {
207 update_input_state(project, self.timing.beat_progression());
208 }
209 self.apply_project_settings();
210
211 self.render_thread.send_snapshot(RenderSnapshot {
212 project: self.project.clone(),
213 blackout: self.blackout || self.blackout_hold,
214 output_config: self.output_config.clone(),
215 fps_limit: self.persistent_state.fps_limit(),
216 layout_generation: self.patch_layout.generation(),
217 });
218
219 ProjectState {
220 project: self.project.clone(),
221 selected_scene_instance: self.selected_scene_instance,
222 blackout: self.blackout || self.blackout_hold,
223 beats_per_minute: self.timing.beats_per_minute(),
224 beat_progression: self.timing.beat_progression(),
225 }
226 .enqueue();
227
228 self.publish_state();
229 true
230 }
231}