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