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