use std::collections::HashMap;
use std::net::UdpSocket;
use std::time::{Duration, Instant};
use rosc::{OscPacket, OscType};
use crate::storage::asset::scene::effect::Effect;
use crate::storage::asset::scene::grid::GridLocation;
use crate::storage::asset::{project::Project, scene::instance::SceneInstance};
use crate::storage::asset_id::AssetId;
use crate::storage::collections::Collections;
use gled_model::patching::layout::LayoutSelection;
use super::scene::layout_feedback;
use super::snapshot::broadcast_snapshot;
use super::*;
fn msg(addr: &str, args: Vec<OscType>) -> OscMessage {
OscMessage {
addr: addr.to_string(),
args,
}
}
#[test]
fn parses_client_interest_subscribe_command() {
let message = msg("/osc/state/subscribe", vec![]);
assert_eq!(message.addr.parse(), Ok(ClientInterestCommand::Subscribe));
}
#[test]
fn parses_client_interest_unsubscribe_command() {
let message = msg("/osc/state/unsubscribe", vec![]);
assert_eq!(message.addr.parse(), Ok(ClientInterestCommand::Unsubscribe));
}
#[test]
fn parses_client_interest_heartbeat_command() {
let message = msg("/osc/state/heartbeat", vec![]);
assert_eq!(message.addr.parse(), Ok(ClientInterestCommand::Heartbeat));
}
#[test]
fn effect_layout_feedback_round_trips_through_the_parser() {
use crate::storage::asset::project::scene_instance_path::SceneInstanceUnion;
use gled_model::patching::layout::LayoutRef;
for selection in [
None,
Some(LayoutSelection::new(LayoutRef::Topology, 0)),
Some(LayoutSelection::new(LayoutRef::Layout(0), 0)),
Some(LayoutSelection::new(LayoutRef::Layout(4), 2)),
] {
let message = msg(
"/scene/selected/effect/0/layout",
vec![layout_feedback(selection)],
);
match parse_message(&message) {
Ok(UiAction::SetSceneEffectLayout(SceneInstanceUnion::Selected, 0, index)) => {
assert_eq!(
index,
selection.map(|selection| selection.layout.stack_index()),
"{selection:?}"
);
}
other => panic!("{selection:?} parsed as {other:?}"),
}
}
}
#[test]
fn broadcast_snapshot_sends_core_fields_to_subscriber() {
let sender = UdpSocket::bind("127.0.0.1:0").expect("failed to bind test socket");
let receiver = UdpSocket::bind("127.0.0.1:0").expect("failed to bind test socket");
receiver
.set_read_timeout(Some(Duration::from_millis(200)))
.expect("failed to set read timeout");
let receiver_addr = receiver.local_addr().expect("failed to get local addr");
let mut subscribers = HashMap::new();
subscribers.insert(receiver_addr, Instant::now());
let snapshot = OscStateSnapshot {
project: None,
selected_scene_instance: GridLocation { col: 0, row: 0 },
blackout: true,
beats_per_minute: 120.0,
beat_progression: 0.25,
};
broadcast_snapshot(&sender, &subscribers, &snapshot);
let mut found_blackout = false;
let mut found_bpm = false;
let mut buf = [0u8; rosc::decoder::MTU];
while let Ok((size, _)) = receiver.recv_from(&mut buf) {
if let Ok((_, OscPacket::Message(m))) = rosc::decoder::decode_udp(&buf[..size]) {
match m.addr.as_str() {
"/project/blackout" => {
assert_eq!(m.args, vec![OscType::Bool(true)]);
found_blackout = true;
}
"/timing/bpm" => {
assert_eq!(m.args, vec![OscType::Float(120.0)]);
found_bpm = true;
}
_ => {}
}
}
if found_blackout && found_bpm {
break;
}
}
assert!(found_blackout, "/project/blackout packet not received");
assert!(found_bpm, "/timing/bpm packet not received");
}
#[test]
fn broadcast_snapshot_sends_palette_fields() {
use crate::storage::asset::palette::Palette;
let sender = UdpSocket::bind("127.0.0.1:0").expect("failed to bind test socket");
let receiver = UdpSocket::bind("127.0.0.1:0").expect("failed to bind test socket");
receiver
.set_read_timeout(Some(Duration::from_millis(200)))
.expect("failed to set read timeout");
let receiver_addr = receiver.local_addr().expect("failed to get local addr");
let mut subscribers = HashMap::new();
subscribers.insert(receiver_addr, Instant::now());
let mut palette = Palette::default();
*palette.primary.rgb_mut() = [1.0, 0.0, 0.0];
let project = Project {
palette: Some(palette),
..Default::default()
};
let snapshot = OscStateSnapshot {
project: Some(std::sync::Arc::new(project)),
selected_scene_instance: GridLocation { col: 0, row: 0 },
blackout: false,
beats_per_minute: 90.0,
beat_progression: 0.0,
};
broadcast_snapshot(&sender, &subscribers, &snapshot);
let mut found_primary = false;
let mut buf = [0u8; rosc::decoder::MTU];
while let Ok((size, _)) = receiver.recv_from(&mut buf) {
if let Ok((_, OscPacket::Message(m))) = rosc::decoder::decode_udp(&buf[..size])
&& m.addr == "/project/palette/primary"
{
assert_eq!(
m.args,
vec![
OscType::Float(1.0),
OscType::Float(0.0),
OscType::Float(0.0)
]
);
found_primary = true;
break;
}
}
assert!(
found_primary,
"/project/palette/primary packet not received"
);
}
#[test]
fn broadcast_snapshot_sends_extended_scene_fields_for_grid_and_quick() {
let sender = UdpSocket::bind("127.0.0.1:0").expect("failed to bind test socket");
let receiver = UdpSocket::bind("127.0.0.1:0").expect("failed to bind test socket");
receiver
.set_read_timeout(Some(Duration::from_millis(300)))
.expect("failed to set read timeout");
let receiver_addr = receiver.local_addr().expect("failed to get local addr");
let mut subscribers = HashMap::new();
subscribers.insert(receiver_addr, Instant::now());
let mut project = Project::default();
let location = GridLocation {
col: 2,
row: project.quick_row_index(),
};
let mut scene_instance =
SceneInstance::from_scene_id(AssetId::default(), &Collections::default());
scene_instance.input_dimmer = 0.42;
scene_instance.ignore_main_dimmer = true;
scene_instance.scene.effects.push(Effect::default());
project.add_scene_instance(location, scene_instance);
let snapshot = OscStateSnapshot {
project: Some(std::sync::Arc::new(project)),
selected_scene_instance: location,
blackout: false,
beats_per_minute: 90.0,
beat_progression: 0.0,
};
broadcast_snapshot(&sender, &subscribers, &snapshot);
let grid_input_dimmer_path =
format!("/scene/grid/{}/{}/input_dimmer", location.col, location.row);
let grid_effect_count_path =
format!("/scene/grid/{}/{}/effect/count", location.col, location.row);
let quick_ignore_main_dimmer_path = format!("/scene/quick/{}/ignore_main_dimmer", location.col);
let quick_effect_opacity_path = format!("/scene/quick/{}/effect/0/opacity", location.col);
let mut found_grid_input_dimmer = false;
let mut found_grid_effect_count = false;
let mut found_quick_ignore_main_dimmer = false;
let mut found_quick_effect_opacity = false;
let mut buf = [0u8; rosc::decoder::MTU];
while let Ok((size, _)) = receiver.recv_from(&mut buf) {
if let Ok((_, OscPacket::Message(m))) = rosc::decoder::decode_udp(&buf[..size]) {
if m.addr == grid_input_dimmer_path {
assert_eq!(m.args, vec![OscType::Float(0.42)]);
found_grid_input_dimmer = true;
}
if m.addr == grid_effect_count_path {
assert_eq!(m.args, vec![OscType::Int(1)]);
found_grid_effect_count = true;
}
if m.addr == quick_ignore_main_dimmer_path {
assert_eq!(m.args, vec![OscType::Bool(true)]);
found_quick_ignore_main_dimmer = true;
}
if m.addr == quick_effect_opacity_path {
assert_eq!(m.args, vec![OscType::Float(1.0)]);
found_quick_effect_opacity = true;
}
}
if found_grid_input_dimmer
&& found_grid_effect_count
&& found_quick_ignore_main_dimmer
&& found_quick_effect_opacity
{
break;
}
}
assert!(
found_grid_input_dimmer,
"{grid_input_dimmer_path} packet not received"
);
assert!(
found_grid_effect_count,
"{grid_effect_count_path} packet not received"
);
assert!(
found_quick_ignore_main_dimmer,
"{quick_ignore_main_dimmer_path} packet not received"
);
assert!(
found_quick_effect_opacity,
"{quick_effect_opacity_path} packet not received"
);
}