1use serde::{Deserialize, Serialize};
3use std::f64::consts::PI;
4use tokio::time::{Duration, Instant};
5use tracing::trace;
6
7#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
8pub struct LatencyConfig {
9 #[serde(deserialize_with = "humantime_serde::deserialize")]
10 pub base: Duration,
11 pub saw: Option<Shape>,
12 pub sine: Option<Shape>,
13 pub square: Option<Shape>,
14 pub triangle: Option<Shape>,
15}
16
17impl Default for LatencyConfig {
18 fn default() -> Self {
19 Self {
20 base: Duration::from_millis(5),
21 saw: None,
22 sine: Some(Shape {
23 amplitude: Duration::from_millis(2),
24 period: Duration::from_secs(10),
25 }),
26 square: None,
27 triangle: None,
28 }
29 }
30}
31
32#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
33pub struct Shape {
34 #[serde(deserialize_with = "humantime_serde::deserialize")]
35 pub amplitude: Duration,
36 #[serde(deserialize_with = "humantime_serde::deserialize")]
37 pub period: Duration,
38}
39
40#[derive(Debug, Clone, Copy)]
41pub struct LatencyGenerator {
42 start: Instant,
43 cfg: LatencyConfig,
44}
45
46impl LatencyGenerator {
47 pub fn new(cfg: LatencyConfig) -> Self {
48 Self {
49 start: Instant::now(),
50 cfg,
51 }
52 }
53
54 pub fn generate(&self, when: Instant) -> Duration {
55 let mut latency_ms = self.cfg.base.as_millis() as u64;
56 let elapsed_ms = when.duration_since(self.start).as_millis() as u64;
57
58 trace!("Base latency: {latency_ms}");
59 trace!("Elapsed: {elapsed_ms}");
60
61 if let Some(saw) = self.cfg.saw {
62 latency_ms += saw_ms(saw, elapsed_ms);
63 }
64 if let Some(sine) = self.cfg.sine {
65 latency_ms += sine_ms(sine, elapsed_ms);
66 }
67 if let Some(square) = self.cfg.square {
68 latency_ms += square_ms(square, elapsed_ms);
69 }
70 if let Some(triangle) = self.cfg.triangle {
71 latency_ms += triangle_ms(triangle, elapsed_ms);
72 }
73
74 trace!("Final latency: {latency_ms}");
75 Duration::from_millis(latency_ms)
76 }
77}
78
79#[inline(always)]
80fn saw_ms(Shape { amplitude, period }: Shape, elapsed: u64) -> u64 {
81 let amplitude = amplitude.as_millis() as u64;
82 let period = period.as_millis() as u64;
83
84 trace!(
85 amplitude = amplitude,
86 period = period,
87 elapsed = elapsed,
88 "Computing saw value",
89 );
90
91 let result = ((elapsed % period) * amplitude) / period;
92
93 trace!(result = result, "Saw value computed");
94
95 result
96}
97
98#[inline(always)]
99fn sine_ms(Shape { amplitude, period }: Shape, elapsed: u64) -> u64 {
100 let amplitude = amplitude.as_millis() as u64;
101 let period = period.as_millis() as u64;
102
103 trace!(
104 amplitude = amplitude,
105 period = period,
106 elapsed = elapsed,
107 "Computing sine value",
108 );
109
110 let sine_value = ((elapsed as f64) / (period as f64) * PI * 2.0).sin(); let normalized = (sine_value + 1.0) / 2.0; let result = (normalized * amplitude as f64).round() as u64; trace!(
115 sine_value = sine_value,
116 normalized = normalized,
117 result = result,
118 "Sine value computed"
119 );
120
121 result
122}
123
124#[inline(always)]
125fn square_ms(Shape { amplitude, period }: Shape, elapsed: u64) -> u64 {
126 let amplitude = amplitude.as_millis() as u64;
127 let period = period.as_millis() as u64;
128
129 trace!(
130 amplitude = amplitude,
131 period = period,
132 elapsed = elapsed,
133 "Computing square value",
134 );
135
136 let result = if elapsed % period < period / 2 {
137 amplitude
138 } else {
139 0
140 };
141
142 trace!(result = result, "Square value computed");
143
144 result
145}
146
147#[inline(always)]
148fn triangle_ms(Shape { amplitude, period }: Shape, elapsed: u64) -> u64 {
149 let amplitude = amplitude.as_millis() as u64;
150 let period = period.as_millis() as u64;
151
152 trace!(
153 amplitude = amplitude,
154 period = period,
155 elapsed = elapsed,
156 "Computing triangle value",
157 );
158
159 let position_in_period = elapsed % period;
160 let half_period = period / 2;
161
162 let result = if position_in_period < half_period {
163 (position_in_period * amplitude) / half_period
164 } else {
165 ((period - position_in_period) * amplitude) / half_period
166 };
167
168 trace!(result = result, "Triangle value computed");
169
170 result
171}