use std::{error::Error, time::Duration};
use tracing::error;
use crate::songs::Song;
use super::Engine;
impl Engine {
pub fn validate_song_lighting(&self, song: &Song) -> Result<(), Box<dyn Error>> {
let dsl_lighting_shows = song.dsl_lighting_shows();
if dsl_lighting_shows.is_empty() {
return Ok(());
}
if let Some(ref lighting_config) = self.lighting_config {
for dsl_show in dsl_lighting_shows {
crate::lighting::validation::validate_light_shows(
dsl_show.shows(),
Some(lighting_config),
)
.map_err(|e| {
format!(
"Light show validation failed for {}: {}",
dsl_show.file_path().display(),
e
)
})?;
}
}
Ok(())
}
pub fn update_song_lighting(
&self,
song_time: std::time::Duration,
) -> Result<(), Box<dyn std::error::Error>> {
let timeline_update = {
let mut current_timeline = self.current_song_timeline.lock();
if let Some(timeline) = current_timeline.as_mut() {
timeline.update(song_time)
} else {
crate::lighting::timeline::TimelineUpdate::default()
}
};
self.apply_timeline_update(timeline_update)
}
pub fn start_lighting_timeline_at(&self, start_time: Duration) {
{
let mut effect_engine = self.effect_engine.lock();
effect_engine.stop_all_effects();
}
for universe in self.universes.values() {
universe.clear_effect_cache();
}
let timeline_update = {
let mut current_timeline = self.current_song_timeline.lock();
if let Some(timeline) = current_timeline.as_mut() {
if start_time == Duration::ZERO {
timeline.start();
crate::lighting::timeline::TimelineUpdate::default()
} else {
timeline.start_at(start_time)
}
} else {
crate::lighting::timeline::TimelineUpdate::default()
}
};
if start_time > Duration::ZERO {
if let Err(e) = self.apply_timeline_update(timeline_update) {
error!("Failed to apply historical timeline state: {}", e);
}
}
}
pub(super) fn apply_timeline_update(
&self,
timeline_update: crate::lighting::timeline::TimelineUpdate,
) -> Result<(), Box<dyn Error>> {
if !timeline_update.layer_commands.is_empty() {
let mut effects_engine = self.effect_engine.lock();
for cmd in &timeline_update.layer_commands {
effects_engine.apply_layer_command(cmd);
}
}
if !timeline_update.stop_sequences.is_empty() {
let mut effects_engine = self.effect_engine.lock();
for sequence_name in &timeline_update.stop_sequences {
effects_engine.stop_sequence(sequence_name);
}
}
let mut effects_sorted: Vec<_> = timeline_update.effects_with_elapsed.values().collect();
effects_sorted.sort_by_key(|(effect, _)| effect.cue_time.unwrap_or(Duration::ZERO));
for (effect, elapsed_time) in effects_sorted {
let resolved = self.resolve_effect_groups(effect.clone());
let mut effect_engine = self.effect_engine.lock();
if let Err(e) = effect_engine.start_effect_with_elapsed(resolved, *elapsed_time) {
error!("Failed to start lighting effect with elapsed time: {}", e);
}
}
let mut effects = timeline_update.effects;
effects.sort_by_key(|e| if e.id.starts_with("seq_") { 0 } else { 1 });
for effect in effects {
let resolved = self.resolve_effect_groups(effect);
if let Err(e) = self.start_effect(resolved) {
error!("Failed to start lighting effect: {}", e);
}
}
Ok(())
}
pub(super) fn resolve_effect_groups(
&self,
effect: crate::lighting::EffectInstance,
) -> crate::lighting::EffectInstance {
if let Some(lighting_system) = &self.lighting_system {
let mut lighting_system = lighting_system.lock();
lighting_system.resolve_effect_groups(effect)
} else {
effect
}
}
pub fn stop_lighting_timeline(&self) {
let mut current_timeline = self.current_song_timeline.lock();
if let Some(timeline) = current_timeline.as_mut() {
timeline.stop();
}
}
pub fn update_song_time(&self, song_time: Duration) {
self.current_song_time.store(
song_time.as_nanos() as u64,
std::sync::atomic::Ordering::Relaxed,
);
}
pub fn get_song_time(&self) -> Duration {
Duration::from_nanos(
self.current_song_time
.load(std::sync::atomic::Ordering::Relaxed),
)
}
pub fn get_timeline_cues(&self) -> Vec<(Duration, usize)> {
let timeline = self.current_song_timeline.lock();
if let Some(timeline) = timeline.as_ref() {
timeline.cues()
} else {
Vec::new()
}
}
}