#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
use std::fmt;
use crate::device::DacInfo;
#[derive(Debug)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct StreamConfig {
pub pps: u32,
#[cfg_attr(feature = "serde", serde(with = "duration_millis"))]
pub target_buffer: std::time::Duration,
pub idle_policy: IdlePolicy,
#[cfg_attr(feature = "serde", serde(with = "duration_millis"))]
pub drain_timeout: std::time::Duration,
#[cfg_attr(feature = "serde", serde(with = "duration_micros"))]
pub color_delay: std::time::Duration,
#[cfg_attr(feature = "serde", serde(with = "duration_micros"))]
pub startup_blank: std::time::Duration,
#[cfg_attr(feature = "serde", serde(skip))]
pub reconnect: Option<ReconnectConfig>,
}
#[cfg(feature = "serde")]
macro_rules! duration_serde_module {
($mod_name:ident, $as_unit:ident, $from_unit:ident) => {
mod $mod_name {
use serde::{Deserialize, Deserializer, Serialize, Serializer};
use std::time::Duration;
pub fn serialize<S>(duration: &Duration, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let value = duration.$as_unit().min(u64::MAX as u128) as u64;
value.serialize(serializer)
}
pub fn deserialize<'de, D>(deserializer: D) -> Result<Duration, D::Error>
where
D: Deserializer<'de>,
{
let value = u64::deserialize(deserializer)?;
Ok(Duration::$from_unit(value))
}
}
};
}
#[cfg(feature = "serde")]
duration_serde_module!(duration_millis, as_millis, from_millis);
#[cfg(feature = "serde")]
duration_serde_module!(duration_micros, as_micros, from_micros);
impl Default for StreamConfig {
fn default() -> Self {
Self {
pps: 30_000,
target_buffer: Self::DEFAULT_TARGET_BUFFER,
idle_policy: IdlePolicy::default(),
drain_timeout: std::time::Duration::from_secs(1),
color_delay: std::time::Duration::ZERO,
startup_blank: std::time::Duration::from_millis(1),
reconnect: None,
}
}
}
impl StreamConfig {
pub const DEFAULT_TARGET_BUFFER: std::time::Duration = std::time::Duration::from_millis(20);
pub const NETWORK_DEFAULT_TARGET_BUFFER: std::time::Duration =
std::time::Duration::from_millis(50);
pub fn new(pps: u32) -> Self {
Self {
pps,
..Default::default()
}
}
pub fn with_target_buffer(mut self, duration: std::time::Duration) -> Self {
self.target_buffer = duration;
self
}
pub fn with_idle_policy(mut self, policy: IdlePolicy) -> Self {
self.idle_policy = policy;
self
}
#[deprecated(since = "0.8.0", note = "renamed to with_idle_policy")]
pub fn with_underrun(self, policy: IdlePolicy) -> Self {
self.with_idle_policy(policy)
}
pub fn with_drain_timeout(mut self, timeout: std::time::Duration) -> Self {
self.drain_timeout = timeout;
self
}
pub fn with_color_delay(mut self, delay: std::time::Duration) -> Self {
self.color_delay = delay;
self
}
pub fn with_startup_blank(mut self, duration: std::time::Duration) -> Self {
self.startup_blank = duration;
self
}
pub fn with_reconnect(mut self, config: ReconnectConfig) -> Self {
self.reconnect = Some(config);
self
}
}
#[derive(Clone, Debug, PartialEq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[derive(Default)]
pub enum IdlePolicy {
RepeatLast,
#[default]
Blank,
Park { x: f32, y: f32 },
Stop,
}
#[deprecated(since = "0.8.0", note = "renamed to IdlePolicy")]
pub type UnderrunPolicy = IdlePolicy;
type DisconnectCb = Box<dyn FnMut(&crate::Error) + Send + 'static>;
type ReconnectCb = Box<dyn FnMut(&DacInfo) + Send + 'static>;
pub struct ReconnectConfig {
pub(crate) max_retries: Option<u32>,
pub(crate) backoff: std::time::Duration,
pub(crate) on_disconnect: Option<DisconnectCb>,
pub(crate) on_reconnect: Option<ReconnectCb>,
}
impl ReconnectConfig {
pub fn new() -> Self {
Self {
max_retries: None,
backoff: std::time::Duration::from_secs(1),
on_disconnect: None,
on_reconnect: None,
}
}
pub fn max_retries(mut self, max_retries: u32) -> Self {
self.max_retries = Some(max_retries);
self
}
pub fn backoff(mut self, backoff: std::time::Duration) -> Self {
self.backoff = backoff;
self
}
pub fn on_disconnect<F>(mut self, f: F) -> Self
where
F: FnMut(&crate::Error) + Send + 'static,
{
self.on_disconnect = Some(Box::new(f));
self
}
pub fn on_reconnect<F>(mut self, f: F) -> Self
where
F: FnMut(&DacInfo) + Send + 'static,
{
self.on_reconnect = Some(Box::new(f));
self
}
}
impl Default for ReconnectConfig {
fn default() -> Self {
Self::new()
}
}
impl fmt::Debug for ReconnectConfig {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("ReconnectConfig")
.field("max_retries", &self.max_retries)
.field("backoff", &self.backoff)
.field("on_disconnect", &self.on_disconnect.as_ref().map(|_| ".."))
.field("on_reconnect", &self.on_reconnect.as_ref().map(|_| ".."))
.finish()
}
}
#[cfg(all(test, feature = "serde"))]
mod tests {
use super::*;
#[test]
fn test_stream_config_serde_roundtrip() {
use std::time::Duration;
let config = StreamConfig {
pps: 45000,
target_buffer: Duration::from_millis(50),
idle_policy: IdlePolicy::Park { x: 0.5, y: -0.3 },
drain_timeout: Duration::from_secs(2),
color_delay: Duration::from_micros(150),
startup_blank: Duration::from_micros(800),
reconnect: None,
};
let json = serde_json::to_string(&config).expect("serialize to JSON");
let restored: StreamConfig = serde_json::from_str(&json).expect("deserialize from JSON");
assert_eq!(restored.pps, config.pps);
assert_eq!(restored.target_buffer, config.target_buffer);
assert_eq!(restored.drain_timeout, config.drain_timeout);
assert_eq!(restored.color_delay, config.color_delay);
assert_eq!(restored.startup_blank, config.startup_blank);
match restored.idle_policy {
IdlePolicy::Park { x, y } => {
assert!((x - 0.5).abs() < f32::EPSILON);
assert!((y - (-0.3)).abs() < f32::EPSILON);
}
_ => panic!("Expected Park policy"),
}
}
#[test]
fn test_duration_millis_roundtrip_consistency() {
use std::time::Duration;
let test_durations = [
Duration::from_millis(0),
Duration::from_millis(1),
Duration::from_millis(10),
Duration::from_millis(100),
Duration::from_millis(1000),
Duration::from_millis(u64::MAX / 1000), ];
for &duration in &test_durations {
let config = StreamConfig {
target_buffer: duration,
..StreamConfig::default()
};
let json = serde_json::to_string(&config).expect("serialize");
let restored: StreamConfig = serde_json::from_str(&json).expect("deserialize");
assert_eq!(
restored.target_buffer, duration,
"Duration {:?} did not round-trip correctly",
duration
);
}
}
}