1use crate::{address::NodeId, clock::SyncedTimestamp};
7
8#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
10#[repr(C)]
11pub enum NetMessageType {
12 ParameterChange = 0,
14 Trigger = 1,
16 StateRequest = 2,
18 StateResponse = 3,
20 Ping = 4,
22 Pong = 5,
24}
25
26#[repr(C)]
31#[derive(Debug, Clone, Copy, Default)]
32pub enum NetPayload {
33 #[default]
35 None,
36 Value(f32),
38 Coordinates { x: f32, y: f32 },
40}
41
42impl NetPayload {
43 pub fn coordinates(&self) -> Option<(f32, f32)> {
45 match self {
46 NetPayload::Coordinates { x, y } => Some((*x, *y)),
47 _ => None,
48 }
49 }
50
51 pub fn value(&self) -> Option<f32> {
53 match self {
54 NetPayload::Value(v) => Some(*v),
55 _ => None,
56 }
57 }
58}
59
60#[repr(C)]
64#[derive(Debug, Clone, Copy)]
65pub struct NetMessage {
66 pub message_type: NetMessageType,
68 pub param_hash: u32,
70 pub payload: NetPayload,
72 pub node_id: NodeId,
74 pub timestamp: SyncedTimestamp,
76}
77
78impl NetMessage {
79 pub fn param_change(param_name: &str, value: f32, node_id: NodeId) -> Self {
81 Self {
82 message_type: NetMessageType::ParameterChange,
83 param_hash: hash_param_name(param_name),
84 payload: NetPayload::Value(value),
85 node_id,
86 timestamp: SyncedTimestamp::default(),
87 }
88 }
89
90 pub fn trigger(trigger_name: &str, node_id: NodeId) -> Self {
92 Self {
93 message_type: NetMessageType::Trigger,
94 param_hash: hash_param_name(trigger_name),
95 payload: NetPayload::None,
96 node_id,
97 timestamp: SyncedTimestamp::default(),
98 }
99 }
100
101 pub fn trigger_with_coordinates(trigger_name: &str, x: f32, y: f32, node_id: NodeId) -> Self {
103 Self {
104 message_type: NetMessageType::Trigger,
105 param_hash: hash_param_name(trigger_name),
106 payload: NetPayload::Coordinates { x, y },
107 node_id,
108 timestamp: SyncedTimestamp::default(),
109 }
110 }
111
112 pub fn ping(node_id: NodeId, timestamp: SyncedTimestamp) -> Self {
114 Self {
115 message_type: NetMessageType::Ping,
116 param_hash: 0,
117 payload: NetPayload::None,
118 node_id,
119 timestamp,
120 }
121 }
122
123 pub fn pong(node_id: NodeId, timestamp: SyncedTimestamp) -> Self {
125 Self {
126 message_type: NetMessageType::Pong,
127 param_hash: 0,
128 payload: NetPayload::None,
129 node_id,
130 timestamp,
131 }
132 }
133
134 pub fn with_timestamp(mut self, timestamp: SyncedTimestamp) -> Self {
136 self.timestamp = timestamp;
137 self
138 }
139}
140
141#[repr(C)]
146#[derive(Debug, Clone, Copy)]
147pub struct NetEvent {
148 pub message: NetMessage,
150 pub sample_offset: u32,
152}
153
154impl NetEvent {
155 pub fn new(message: NetMessage, sample_offset: u32) -> Self {
157 Self { message, sample_offset }
158 }
159
160 pub fn immediate(message: NetMessage) -> Self {
162 Self {
163 message,
164 sample_offset: 0,
165 }
166 }
167}
168
169#[inline]
174pub fn hash_param_name(name: &str) -> u32 {
175 const FNV_OFFSET: u32 = 2166136261;
176 const FNV_PRIME: u32 = 16777619;
177
178 let mut hash = FNV_OFFSET;
179 for byte in name.bytes() {
180 hash ^= byte as u32;
181 hash = hash.wrapping_mul(FNV_PRIME);
182 }
183 hash
184}
185
186#[cfg(test)]
187mod tests {
188 use super::*;
189
190 #[test]
191 fn test_hash_param_name_deterministic() {
192 let hash1 = hash_param_name("gain");
193 let hash2 = hash_param_name("gain");
194 assert_eq!(hash1, hash2);
195 }
196
197 #[test]
198 fn test_hash_param_name_different_strings() {
199 let hash1 = hash_param_name("gain");
200 let hash2 = hash_param_name("volume");
201 assert_ne!(hash1, hash2);
202 }
203
204 #[test]
205 fn test_hash_param_name_similar_strings() {
206 let hash1 = hash_param_name("param1");
207 let hash2 = hash_param_name("param2");
208 assert_ne!(hash1, hash2);
209 }
210
211 #[test]
212 fn test_net_message_param_change() {
213 let node_id = NodeId::from_parts(1, 2);
214 let msg = NetMessage::param_change("gain", 0.5, node_id);
215
216 assert_eq!(msg.message_type, NetMessageType::ParameterChange);
217 assert_eq!(msg.param_hash, hash_param_name("gain"));
218 assert!((msg.payload.value().unwrap() - 0.5).abs() < f32::EPSILON);
219 assert_eq!(msg.node_id, node_id);
220 }
221
222 #[test]
223 fn test_net_message_trigger() {
224 let node_id = NodeId::from_parts(3, 4);
225 let msg = NetMessage::trigger("start", node_id);
226
227 assert_eq!(msg.message_type, NetMessageType::Trigger);
228 assert_eq!(msg.param_hash, hash_param_name("start"));
229 assert!(matches!(msg.payload, NetPayload::None));
230 }
231
232 #[test]
233 fn test_net_message_trigger_with_coordinates() {
234 let node_id = NodeId::from_parts(5, 6);
235 let msg = NetMessage::trigger_with_coordinates("droplet", 0.25, 0.75, node_id);
236
237 assert_eq!(msg.message_type, NetMessageType::Trigger);
238 assert_eq!(msg.param_hash, hash_param_name("droplet"));
239 let (x, y) = msg.payload.coordinates().unwrap();
240 assert!((x - 0.25).abs() < f32::EPSILON);
241 assert!((y - 0.75).abs() < f32::EPSILON);
242 }
243
244 #[test]
245 fn test_net_event_immediate() {
246 let node_id = NodeId::from_parts(5, 6);
247 let msg = NetMessage::param_change("freq", 440.0, node_id);
248 let event = NetEvent::immediate(msg);
249
250 assert_eq!(event.sample_offset, 0);
251 }
252
253 #[test]
254 fn test_net_event_with_offset() {
255 let node_id = NodeId::from_parts(7, 8);
256 let msg = NetMessage::param_change("pan", 0.0, node_id);
257 let event = NetEvent::new(msg, 256);
258
259 assert_eq!(event.sample_offset, 256);
260 }
261}