use super::common::create_test_fixture;
use crate::dmx::midi_dmx_store::MidiDmxStore;
use crate::lighting::effects::{EffectType, FixtureInfo};
use crate::lighting::engine::EffectEngine;
use crate::lighting::EffectInstance;
use std::collections::HashMap;
use std::sync::Arc;
use std::time::Duration;
fn create_store_for_wash1() -> Arc<parking_lot::RwLock<MidiDmxStore>> {
let mut store = MidiDmxStore::new();
store.register_slot(1, 1, "wash1", "dimmer"); store.register_slot(1, 2, "wash1", "red"); store.register_slot(1, 3, "wash1", "green"); store.register_slot(1, 4, "wash1", "blue"); store.register_slot(1, 5, "wash1", "white"); store.register_slot(1, 6, "wash1", "strobe"); store.register_universe(1);
Arc::new(parking_lot::RwLock::new(store))
}
#[test]
fn test_reverse_map_construction() {
let mut engine = EffectEngine::new();
let mut channels = HashMap::new();
channels.insert("dimmer".to_string(), 1);
channels.insert("red".to_string(), 2);
channels.insert("green".to_string(), 3);
channels.insert("blue".to_string(), 4);
let fixture = FixtureInfo::new(
"wash1".to_string(),
1,
10,
"RGBW_Par".to_string(),
channels,
None,
);
engine.register_fixture(fixture);
assert_eq!(
engine.lookup_dmx_channel(1, 10),
Some(&("wash1".to_string(), "dimmer".to_string()))
);
assert_eq!(
engine.lookup_dmx_channel(1, 11),
Some(&("wash1".to_string(), "red".to_string()))
);
assert_eq!(
engine.lookup_dmx_channel(1, 12),
Some(&("wash1".to_string(), "green".to_string()))
);
assert_eq!(
engine.lookup_dmx_channel(1, 13),
Some(&("wash1".to_string(), "blue".to_string()))
);
assert_eq!(engine.lookup_dmx_channel(1, 14), None);
assert_eq!(engine.lookup_dmx_channel(2, 10), None);
}
#[test]
fn test_midi_dmx_store_values_in_merged_states() {
let mut engine = EffectEngine::new();
let fixture = create_test_fixture("wash1", 1, 1);
engine.register_fixture(fixture);
let store = create_store_for_wash1();
engine.set_midi_dmx_store(store.clone());
{
let s = store.read();
s.write(1, 2, 255, false); s.write(1, 3, 128, false); s.write(1, 4, 0, false); s.tick();
}
let _commands = engine.update(Duration::from_millis(23), None).unwrap();
let states = engine.get_fixture_states();
let wash1_state = states
.get("wash1")
.expect("wash1 should be in merged states");
assert!(
(wash1_state.channels.get("red").unwrap().value - 1.0).abs() < 0.01,
"red should be ~1.0"
);
assert!(
(wash1_state.channels.get("green").unwrap().value - 128.0 / 255.0).abs() < 0.01,
"green should be ~0.502"
);
assert!(
wash1_state.channels.get("blue").unwrap().value.abs() < f64::EPSILON,
"blue should be 0.0"
);
}
#[test]
fn test_midi_dmx_store_values_generate_dmx_commands() {
let mut engine = EffectEngine::new();
let fixture = create_test_fixture("wash1", 1, 1);
engine.register_fixture(fixture);
let store = create_store_for_wash1();
engine.set_midi_dmx_store(store.clone());
{
let s = store.read();
s.write(1, 2, 255, false); s.tick();
}
let commands = engine.update(Duration::from_millis(23), None).unwrap();
let red_cmd = commands
.iter()
.find(|cmd| cmd.universe == 1 && cmd.channel == 2);
assert!(
red_cmd.is_some(),
"DMX command for MIDI DMX channel (universe 1, channel 2) should be generated"
);
assert_eq!(red_cmd.unwrap().value, 255);
}
#[test]
fn test_midi_dmx_store_values_overridden_by_effects() {
let mut engine = EffectEngine::new();
let fixture = create_test_fixture("wash1", 1, 1);
engine.register_fixture(fixture);
let store = create_store_for_wash1();
engine.set_midi_dmx_store(store.clone());
{
let s = store.read();
s.write(1, 2, 128, false);
s.tick();
}
let mut parameters = HashMap::new();
parameters.insert("red".to_string(), 1.0);
let effect = EffectInstance::new(
"override_test".to_string(),
EffectType::Static {
parameters,
duration: Duration::from_secs(5),
},
vec!["wash1".to_string()],
None,
None,
None,
);
engine.start_effect(effect).unwrap();
let _commands = engine.update(Duration::from_millis(23), None).unwrap();
let states = engine.get_fixture_states();
let wash1_state = states.get("wash1").unwrap();
assert!(
(wash1_state.channels.get("red").unwrap().value - 1.0).abs() < f64::EPSILON,
"effect should override MIDI DMX store value: got {}",
wash1_state.channels.get("red").unwrap().value
);
}
#[test]
fn test_midi_dmx_store_values_update_across_frames() {
let mut engine = EffectEngine::new();
let fixture = create_test_fixture("wash1", 1, 1);
engine.register_fixture(fixture);
let store = create_store_for_wash1();
engine.set_midi_dmx_store(store.clone());
{
let s = store.read();
s.write(1, 2, 128, false);
s.tick();
}
let _commands = engine.update(Duration::from_millis(23), None).unwrap();
let states = engine.get_fixture_states();
let red_val = states
.get("wash1")
.unwrap()
.channels
.get("red")
.unwrap()
.value;
assert!(
(red_val - 128.0 / 255.0).abs() < 0.01,
"frame 1: red should be ~0.502, got {}",
red_val
);
{
let s = store.read();
s.write(1, 2, 255, false);
s.tick();
}
let _commands = engine.update(Duration::from_millis(23), None).unwrap();
let states = engine.get_fixture_states();
let red_val = states
.get("wash1")
.unwrap()
.channels
.get("red")
.unwrap()
.value;
assert!(
(red_val - 1.0).abs() < 0.01,
"frame 2: red should be ~1.0, got {}",
red_val
);
{
let s = store.read();
s.write(1, 2, 0, false);
s.tick();
}
let _commands = engine.update(Duration::from_millis(23), None).unwrap();
let states = engine.get_fixture_states();
let red_val = states
.get("wash1")
.unwrap()
.channels
.get("red")
.unwrap()
.value;
assert!(
red_val.abs() < f64::EPSILON,
"frame 3: red should be 0.0, got {}",
red_val
);
}
#[test]
fn test_midi_dmx_store_values_not_persisted_as_permanent() {
let mut engine = EffectEngine::new();
let fixture = create_test_fixture("wash1", 1, 1);
engine.register_fixture(fixture);
let store = create_store_for_wash1();
engine.set_midi_dmx_store(store.clone());
{
let s = store.read();
s.write(1, 2, 204, false); s.tick();
}
let _commands = engine.update(Duration::from_millis(23), None).unwrap();
store.read().clear();
let _commands = engine.update(Duration::from_millis(23), None).unwrap();
let states = engine.get_fixture_states();
if let Some(wash1_state) = states.get("wash1") {
assert!(
!wash1_state.channels.contains_key("red"),
"red should not persist after clearing store"
);
}
}
#[test]
fn test_clear_midi_dmx_store() {
let mut engine = EffectEngine::new();
let fixture = create_test_fixture("wash1", 1, 1);
engine.register_fixture(fixture);
let store = create_store_for_wash1();
engine.set_midi_dmx_store(store.clone());
{
let s = store.read();
s.write(1, 2, 255, false);
s.write(1, 3, 128, false);
s.tick();
}
store.read().clear();
let _commands = engine.update(Duration::from_millis(23), None).unwrap();
let states = engine.get_fixture_states();
if let Some(wash1_state) = states.get("wash1") {
assert!(
wash1_state.channels.is_empty(),
"channels should be empty after clearing store"
);
}
}
#[test]
fn test_stop_all_effects_clears_midi_dmx_store() {
let mut engine = EffectEngine::new();
let fixture = create_test_fixture("wash1", 1, 1);
engine.register_fixture(fixture);
let store = create_store_for_wash1();
engine.set_midi_dmx_store(store.clone());
{
let s = store.read();
s.write(1, 2, 255, false);
s.tick();
}
let _commands = engine.update(Duration::from_millis(23), None).unwrap();
let states = engine.get_fixture_states();
assert!(states.contains_key("wash1"), "wash1 should have state");
engine.stop_all_effects();
let _commands = engine.update(Duration::from_millis(23), None).unwrap();
let states = engine.get_fixture_states();
if let Some(wash1_state) = states.get("wash1") {
assert!(
wash1_state.channels.is_empty(),
"channels should be empty after stop_all_effects"
);
}
}