1#![allow(missing_docs)]
8
9use core::mem::size_of;
10
11pub type TileVertexId = u16;
13
14pub type TileEdgeId = u16;
16
17pub type FixedWeight = u16;
20
21#[inline(always)]
23pub const fn weight_to_f32(w: FixedWeight) -> f32 {
24 (w as f32) / 100.0
25}
26
27#[inline(always)]
29pub const fn f32_to_weight(w: f32) -> FixedWeight {
30 let scaled = (w * 100.0) as i32;
31 if scaled < 0 {
32 0
33 } else if scaled > 65535 {
34 65535
35 } else {
36 scaled as u16
37 }
38}
39
40#[derive(Debug, Clone, Copy, PartialEq, Eq)]
42#[repr(u8)]
43pub enum DeltaTag {
44 Nop = 0,
46 EdgeAdd = 1,
48 EdgeRemove = 2,
50 WeightUpdate = 3,
52 Observation = 4,
54 BatchEnd = 5,
56 Checkpoint = 6,
58 Reset = 7,
60}
61
62impl From<u8> for DeltaTag {
63 fn from(v: u8) -> Self {
64 match v {
65 1 => DeltaTag::EdgeAdd,
66 2 => DeltaTag::EdgeRemove,
67 3 => DeltaTag::WeightUpdate,
68 4 => DeltaTag::Observation,
69 5 => DeltaTag::BatchEnd,
70 6 => DeltaTag::Checkpoint,
71 7 => DeltaTag::Reset,
72 _ => DeltaTag::Nop,
73 }
74 }
75}
76
77#[derive(Debug, Clone, Copy, Default)]
79#[repr(C)]
80pub struct EdgeAdd {
81 pub source: TileVertexId,
83 pub target: TileVertexId,
85 pub weight: FixedWeight,
87 pub flags: u16,
89}
90
91impl EdgeAdd {
92 #[inline]
94 pub const fn new(source: TileVertexId, target: TileVertexId, weight: FixedWeight) -> Self {
95 Self {
96 source,
97 target,
98 weight,
99 flags: 0,
100 }
101 }
102
103 #[inline]
105 pub const fn with_f32_weight(source: TileVertexId, target: TileVertexId, weight: f32) -> Self {
106 Self::new(source, target, f32_to_weight(weight))
107 }
108}
109
110#[derive(Debug, Clone, Copy, Default)]
112#[repr(C)]
113pub struct EdgeRemove {
114 pub source: TileVertexId,
116 pub target: TileVertexId,
118 pub _reserved: u32,
120}
121
122impl EdgeRemove {
123 #[inline]
125 pub const fn new(source: TileVertexId, target: TileVertexId) -> Self {
126 Self {
127 source,
128 target,
129 _reserved: 0,
130 }
131 }
132}
133
134#[derive(Debug, Clone, Copy, Default)]
136#[repr(C)]
137pub struct WeightUpdate {
138 pub source: TileVertexId,
140 pub target: TileVertexId,
142 pub new_weight: FixedWeight,
144 pub mode: u8,
146 pub _reserved: u8,
148}
149
150impl WeightUpdate {
151 pub const MODE_ABSOLUTE: u8 = 0;
153 pub const MODE_ADD: u8 = 1;
155 pub const MODE_MULTIPLY: u8 = 2;
157
158 #[inline]
160 pub const fn absolute(source: TileVertexId, target: TileVertexId, weight: FixedWeight) -> Self {
161 Self {
162 source,
163 target,
164 new_weight: weight,
165 mode: Self::MODE_ABSOLUTE,
166 _reserved: 0,
167 }
168 }
169
170 #[inline]
172 pub const fn add(source: TileVertexId, target: TileVertexId, delta: FixedWeight) -> Self {
173 Self {
174 source,
175 target,
176 new_weight: delta,
177 mode: Self::MODE_ADD,
178 _reserved: 0,
179 }
180 }
181}
182
183#[derive(Debug, Clone, Copy, Default)]
187#[repr(C)]
188pub struct Observation {
189 pub vertex: TileVertexId,
191 pub obs_type: u8,
193 pub flags: u8,
195 pub value: u32,
197}
198
199impl Observation {
200 pub const TYPE_CONNECTIVITY: u8 = 0;
202 pub const TYPE_CUT_MEMBERSHIP: u8 = 1;
204 pub const TYPE_FLOW: u8 = 2;
206 pub const TYPE_WITNESS: u8 = 3;
208
209 #[inline]
211 pub const fn connectivity(vertex: TileVertexId, connected: bool) -> Self {
212 Self {
213 vertex,
214 obs_type: Self::TYPE_CONNECTIVITY,
215 flags: if connected { 1 } else { 0 },
216 value: 0,
217 }
218 }
219
220 #[inline]
222 pub const fn cut_membership(vertex: TileVertexId, side: u8, confidence: u16) -> Self {
223 Self {
224 vertex,
225 obs_type: Self::TYPE_CUT_MEMBERSHIP,
226 flags: side,
227 value: confidence as u32,
228 }
229 }
230}
231
232#[derive(Clone, Copy)]
237#[repr(C)]
238pub union DeltaPayload {
239 pub edge_add: EdgeAdd,
241 pub edge_remove: EdgeRemove,
243 pub weight_update: WeightUpdate,
245 pub observation: Observation,
247 pub raw: [u8; 8],
249}
250
251impl Default for DeltaPayload {
252 fn default() -> Self {
253 Self { raw: [0u8; 8] }
254 }
255}
256
257#[derive(Clone, Copy)]
259#[repr(C, align(16))]
260pub struct Delta {
261 pub tag: DeltaTag,
263 pub sequence: u8,
265 pub source_tile: u8,
267 pub _reserved: u8,
269 pub timestamp: u32,
271 pub payload: DeltaPayload,
273}
274
275impl Default for Delta {
276 fn default() -> Self {
277 Self {
278 tag: DeltaTag::Nop,
279 sequence: 0,
280 source_tile: 0,
281 _reserved: 0,
282 timestamp: 0,
283 payload: DeltaPayload::default(),
284 }
285 }
286}
287
288impl Delta {
289 #[inline]
291 pub const fn nop() -> Self {
292 Self {
293 tag: DeltaTag::Nop,
294 sequence: 0,
295 source_tile: 0,
296 _reserved: 0,
297 timestamp: 0,
298 payload: DeltaPayload { raw: [0u8; 8] },
299 }
300 }
301
302 #[inline]
304 pub fn edge_add(source: TileVertexId, target: TileVertexId, weight: FixedWeight) -> Self {
305 Self {
306 tag: DeltaTag::EdgeAdd,
307 sequence: 0,
308 source_tile: 0,
309 _reserved: 0,
310 timestamp: 0,
311 payload: DeltaPayload {
312 edge_add: EdgeAdd::new(source, target, weight),
313 },
314 }
315 }
316
317 #[inline]
319 pub fn edge_remove(source: TileVertexId, target: TileVertexId) -> Self {
320 Self {
321 tag: DeltaTag::EdgeRemove,
322 sequence: 0,
323 source_tile: 0,
324 _reserved: 0,
325 timestamp: 0,
326 payload: DeltaPayload {
327 edge_remove: EdgeRemove::new(source, target),
328 },
329 }
330 }
331
332 #[inline]
334 pub fn weight_update(source: TileVertexId, target: TileVertexId, weight: FixedWeight) -> Self {
335 Self {
336 tag: DeltaTag::WeightUpdate,
337 sequence: 0,
338 source_tile: 0,
339 _reserved: 0,
340 timestamp: 0,
341 payload: DeltaPayload {
342 weight_update: WeightUpdate::absolute(source, target, weight),
343 },
344 }
345 }
346
347 #[inline]
349 pub fn observation(obs: Observation) -> Self {
350 Self {
351 tag: DeltaTag::Observation,
352 sequence: 0,
353 source_tile: 0,
354 _reserved: 0,
355 timestamp: 0,
356 payload: DeltaPayload { observation: obs },
357 }
358 }
359
360 #[inline]
362 pub const fn batch_end() -> Self {
363 Self {
364 tag: DeltaTag::BatchEnd,
365 sequence: 0,
366 source_tile: 0,
367 _reserved: 0,
368 timestamp: 0,
369 payload: DeltaPayload { raw: [0u8; 8] },
370 }
371 }
372
373 #[inline]
375 pub const fn is_nop(&self) -> bool {
376 matches!(self.tag, DeltaTag::Nop)
377 }
378
379 #[inline]
381 pub unsafe fn get_edge_add(&self) -> &EdgeAdd {
382 unsafe { &self.payload.edge_add }
383 }
384
385 #[inline]
387 pub unsafe fn get_edge_remove(&self) -> &EdgeRemove {
388 unsafe { &self.payload.edge_remove }
389 }
390
391 #[inline]
393 pub unsafe fn get_weight_update(&self) -> &WeightUpdate {
394 unsafe { &self.payload.weight_update }
395 }
396
397 #[inline]
399 pub unsafe fn get_observation(&self) -> &Observation {
400 unsafe { &self.payload.observation }
401 }
402}
403
404const _: () = assert!(size_of::<EdgeAdd>() == 8, "EdgeAdd must be 8 bytes");
406const _: () = assert!(size_of::<EdgeRemove>() == 8, "EdgeRemove must be 8 bytes");
407const _: () = assert!(size_of::<WeightUpdate>() == 8, "WeightUpdate must be 8 bytes");
408const _: () = assert!(size_of::<Observation>() == 8, "Observation must be 8 bytes");
409const _: () = assert!(size_of::<Delta>() == 16, "Delta must be 16 bytes");
410
411#[cfg(test)]
412mod tests {
413 use super::*;
414
415 #[test]
416 fn test_weight_conversion() {
417 assert_eq!(weight_to_f32(100), 1.0);
418 assert_eq!(weight_to_f32(50), 0.5);
419 assert_eq!(weight_to_f32(0), 0.0);
420
421 assert_eq!(f32_to_weight(1.0), 100);
422 assert_eq!(f32_to_weight(0.5), 50);
423 assert_eq!(f32_to_weight(0.0), 0);
424 }
425
426 #[test]
427 fn test_delta_tag_roundtrip() {
428 for i in 0..=7 {
429 let tag = DeltaTag::from(i);
430 assert_eq!(tag as u8, i);
431 }
432 }
433
434 #[test]
435 fn test_edge_add_creation() {
436 let ea = EdgeAdd::new(1, 2, 150);
437 assert_eq!(ea.source, 1);
438 assert_eq!(ea.target, 2);
439 assert_eq!(ea.weight, 150);
440 }
441
442 #[test]
443 fn test_delta_edge_add() {
444 let delta = Delta::edge_add(5, 10, 200);
445 assert_eq!(delta.tag, DeltaTag::EdgeAdd);
446 unsafe {
447 let ea = delta.get_edge_add();
448 assert_eq!(ea.source, 5);
449 assert_eq!(ea.target, 10);
450 assert_eq!(ea.weight, 200);
451 }
452 }
453
454 #[test]
455 fn test_observation_creation() {
456 let obs = Observation::connectivity(42, true);
457 assert_eq!(obs.vertex, 42);
458 assert_eq!(obs.obs_type, Observation::TYPE_CONNECTIVITY);
459 assert_eq!(obs.flags, 1);
460 }
461}