mod actions;
mod applied_output;
mod clients;
mod devices;
mod event_log;
#[cfg(any(test, feature = "test-util"))]
mod fake;
mod midi_tools;
mod outbox;
mod patch_layout;
mod project;
mod publish;
mod selection;
mod session;
mod setup;
mod status;
#[cfg(test)]
mod tick_bench;
pub(crate) use event_log::{EventLog, InputLog, LearnLog, MidiLog};
#[cfg(any(test, feature = "test-util"))]
pub use fake::{FakeEngine, fake_engine};
pub use gled_protocol::state::{
ClientInput, Devices, EngineEvent, EngineState, RenderStats, ServerSettings, ServerStatus,
SoundSnapshot, StorageCommand,
};
pub use outbox::Outbox;
pub use session::{ClientConnection, ClientId, ClientState, EngineConnector};
pub(crate) use setup::{EngineConfig, spawn};
use std::{
collections::{BTreeSet, HashMap},
sync::Arc,
time::{Duration, Instant},
};
use kanal::Receiver;
use tracing::debug;
use crate::{
action::UiAction,
audio::{AudioPool, sound_data::SoundData},
input::{
Input, external_control::ExternalControlState, osc::OSCHandler, osc::OscConfig,
project_inputs::update_input_state,
},
output_state::ProjectState,
pipeline::extract_output::{OutputConfig, PreviewOutput},
runtime::{
persistent_state::PersistentState,
render_thread::{RenderSnapshot, RenderThread},
timing::Timing,
},
storage::{
asset::{Asset, project::Project},
asset_id::AssetId,
collections::Collections,
is_loading,
},
};
use applied_output::AppliedOutput;
use gled_model::{
asset::{output_device::routing::OutputRouting, scene::grid::GridLocation},
collection::Collections as AssetCollections,
};
use patch_layout::PatchLayout;
use uuid::Uuid;
const TICK: Duration = Duration::from_micros(8_333);
pub(crate) struct Engine {
startup: bool,
timing: Timing,
project: Option<Arc<Project>>,
project_id: Option<AssetId<Project>>,
selected_scene_instance: GridLocation,
effect_selection: selection::EffectSelection,
blackout: bool,
blackout_hold: bool,
midi_output_active: bool,
actions: Receiver<UiAction>,
inputs: Receiver<session::EngineInput>,
sessions: HashMap<ClientId, session::Session>,
collections: Collections,
persistent_state: PersistentState,
output_config: OutputConfig,
render_thread: RenderThread,
external_control_state: ExternalControlState,
audio_pool: AudioPool,
sound_data: SoundData,
osc_handler: Option<Arc<OSCHandler>>,
osc_config: Option<OscConfig>,
applied_output: AppliedOutput,
device_scan: devices::DeviceScan,
watched_triggers: HashMap<ClientId, BTreeSet<Uuid>>,
midi: midi_tools::MidiTools,
input_log: Arc<InputLog>,
hovered_routing: Option<(ClientId, OutputRouting)>,
patch_layout: PatchLayout,
render_stats: RenderStats,
preview_output: Receiver<PreviewOutput>,
network_stats_receiver: Receiver<(f64, f64)>,
network_stats: (f64, f64),
temperature_receiver: Receiver<Option<f32>>,
temperature: Option<f32>,
host_name: String,
sound: Arc<SoundSnapshot>,
ticks_since_sound: u32,
preview: Option<PreviewOutput>,
published_assets: (u64, Arc<AssetCollections>),
}
impl Engine {
fn run(mut self) {
let mut next_tick = Instant::now();
loop {
if !self.tick() {
debug!("All user interfaces are gone, stopping the engine");
return;
}
next_tick += TICK;
let now = Instant::now();
match next_tick.checked_duration_since(now) {
Some(wait) => std::thread::sleep(wait),
None => next_tick = now,
}
}
}
#[cfg_attr(feature = "profiling", profiling::function)]
fn tick(&mut self) -> bool {
self.receive_render_feedback();
self.persistent_state.update();
self.sound_data.update();
self.timing.tick(&self.persistent_state);
self.collections.update();
self.sync_sound_triggers();
if !is_loading() && self.startup {
match self.persistent_state.last_project_id() {
Some(id) if Asset::get(id, &self.collections).is_some() => {
self.startup = false;
UiAction::SetProject(id).enqueue();
}
Some(_) if self.collections.get::<Project>().is_none() => {}
_ => self.startup = false,
}
}
if !self.receive_client_inputs() {
return false;
}
Input::tick();
self.midi.tick();
let new_events = Input::new_events();
if !new_events.is_empty() {
let log = Arc::make_mut(&mut self.input_log);
for event in new_events {
log.push(event);
}
}
self.handle_actions();
self.external_control_state
.process_events(&mut self.project, &self.collections);
self.evaluate_triggers();
if let Some(project) = &mut self.project {
update_input_state(project, self.timing.beat_progression());
}
self.apply_project_settings();
self.render_thread.send_snapshot(RenderSnapshot {
project: self.project.clone(),
blackout: self.blackout || self.blackout_hold,
output_config: self.output_config.clone(),
fps_limit: self.persistent_state.fps_limit(),
layout_generation: self.patch_layout.generation(),
});
ProjectState {
project: self.project.clone(),
selected_scene_instance: self.selected_scene_instance,
blackout: self.blackout || self.blackout_hold,
beats_per_minute: self.timing.beats_per_minute(),
beat_progression: self.timing.beat_progression(),
}
.enqueue();
self.publish_midi();
self.publish_state();
true
}
}