#![allow(missing_docs)]
use core::mem::size_of;
pub type TileVertexId = u16;
pub type TileEdgeId = u16;
pub type FixedWeight = u16;
#[inline(always)]
pub const fn weight_to_f32(w: FixedWeight) -> f32 {
(w as f32) / 100.0
}
#[inline(always)]
pub const fn f32_to_weight(w: f32) -> FixedWeight {
let scaled = (w * 100.0) as i32;
if scaled < 0 {
0
} else if scaled > 65535 {
65535
} else {
scaled as u16
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(u8)]
pub enum DeltaTag {
Nop = 0,
EdgeAdd = 1,
EdgeRemove = 2,
WeightUpdate = 3,
Observation = 4,
BatchEnd = 5,
Checkpoint = 6,
Reset = 7,
}
impl From<u8> for DeltaTag {
fn from(v: u8) -> Self {
match v {
1 => DeltaTag::EdgeAdd,
2 => DeltaTag::EdgeRemove,
3 => DeltaTag::WeightUpdate,
4 => DeltaTag::Observation,
5 => DeltaTag::BatchEnd,
6 => DeltaTag::Checkpoint,
7 => DeltaTag::Reset,
_ => DeltaTag::Nop,
}
}
}
#[derive(Debug, Clone, Copy, Default)]
#[repr(C)]
pub struct EdgeAdd {
pub source: TileVertexId,
pub target: TileVertexId,
pub weight: FixedWeight,
pub flags: u16,
}
impl EdgeAdd {
#[inline]
pub const fn new(source: TileVertexId, target: TileVertexId, weight: FixedWeight) -> Self {
Self {
source,
target,
weight,
flags: 0,
}
}
#[inline]
pub const fn with_f32_weight(source: TileVertexId, target: TileVertexId, weight: f32) -> Self {
Self::new(source, target, f32_to_weight(weight))
}
}
#[derive(Debug, Clone, Copy, Default)]
#[repr(C)]
pub struct EdgeRemove {
pub source: TileVertexId,
pub target: TileVertexId,
pub _reserved: u32,
}
impl EdgeRemove {
#[inline]
pub const fn new(source: TileVertexId, target: TileVertexId) -> Self {
Self {
source,
target,
_reserved: 0,
}
}
}
#[derive(Debug, Clone, Copy, Default)]
#[repr(C)]
pub struct WeightUpdate {
pub source: TileVertexId,
pub target: TileVertexId,
pub new_weight: FixedWeight,
pub mode: u8,
pub _reserved: u8,
}
impl WeightUpdate {
pub const MODE_ABSOLUTE: u8 = 0;
pub const MODE_ADD: u8 = 1;
pub const MODE_MULTIPLY: u8 = 2;
#[inline]
pub const fn absolute(source: TileVertexId, target: TileVertexId, weight: FixedWeight) -> Self {
Self {
source,
target,
new_weight: weight,
mode: Self::MODE_ABSOLUTE,
_reserved: 0,
}
}
#[inline]
pub const fn add(source: TileVertexId, target: TileVertexId, delta: FixedWeight) -> Self {
Self {
source,
target,
new_weight: delta,
mode: Self::MODE_ADD,
_reserved: 0,
}
}
}
#[derive(Debug, Clone, Copy, Default)]
#[repr(C)]
pub struct Observation {
pub vertex: TileVertexId,
pub obs_type: u8,
pub flags: u8,
pub value: u32,
}
impl Observation {
pub const TYPE_CONNECTIVITY: u8 = 0;
pub const TYPE_CUT_MEMBERSHIP: u8 = 1;
pub const TYPE_FLOW: u8 = 2;
pub const TYPE_WITNESS: u8 = 3;
#[inline]
pub const fn connectivity(vertex: TileVertexId, connected: bool) -> Self {
Self {
vertex,
obs_type: Self::TYPE_CONNECTIVITY,
flags: if connected { 1 } else { 0 },
value: 0,
}
}
#[inline]
pub const fn cut_membership(vertex: TileVertexId, side: u8, confidence: u16) -> Self {
Self {
vertex,
obs_type: Self::TYPE_CUT_MEMBERSHIP,
flags: side,
value: confidence as u32,
}
}
}
#[derive(Clone, Copy)]
#[repr(C)]
pub union DeltaPayload {
pub edge_add: EdgeAdd,
pub edge_remove: EdgeRemove,
pub weight_update: WeightUpdate,
pub observation: Observation,
pub raw: [u8; 8],
}
impl Default for DeltaPayload {
fn default() -> Self {
Self { raw: [0u8; 8] }
}
}
#[derive(Clone, Copy)]
#[repr(C, align(16))]
pub struct Delta {
pub tag: DeltaTag,
pub sequence: u8,
pub source_tile: u8,
pub _reserved: u8,
pub timestamp: u32,
pub payload: DeltaPayload,
}
impl Default for Delta {
fn default() -> Self {
Self {
tag: DeltaTag::Nop,
sequence: 0,
source_tile: 0,
_reserved: 0,
timestamp: 0,
payload: DeltaPayload::default(),
}
}
}
impl Delta {
#[inline]
pub const fn nop() -> Self {
Self {
tag: DeltaTag::Nop,
sequence: 0,
source_tile: 0,
_reserved: 0,
timestamp: 0,
payload: DeltaPayload { raw: [0u8; 8] },
}
}
#[inline]
pub fn edge_add(source: TileVertexId, target: TileVertexId, weight: FixedWeight) -> Self {
Self {
tag: DeltaTag::EdgeAdd,
sequence: 0,
source_tile: 0,
_reserved: 0,
timestamp: 0,
payload: DeltaPayload {
edge_add: EdgeAdd::new(source, target, weight),
},
}
}
#[inline]
pub fn edge_remove(source: TileVertexId, target: TileVertexId) -> Self {
Self {
tag: DeltaTag::EdgeRemove,
sequence: 0,
source_tile: 0,
_reserved: 0,
timestamp: 0,
payload: DeltaPayload {
edge_remove: EdgeRemove::new(source, target),
},
}
}
#[inline]
pub fn weight_update(source: TileVertexId, target: TileVertexId, weight: FixedWeight) -> Self {
Self {
tag: DeltaTag::WeightUpdate,
sequence: 0,
source_tile: 0,
_reserved: 0,
timestamp: 0,
payload: DeltaPayload {
weight_update: WeightUpdate::absolute(source, target, weight),
},
}
}
#[inline]
pub fn observation(obs: Observation) -> Self {
Self {
tag: DeltaTag::Observation,
sequence: 0,
source_tile: 0,
_reserved: 0,
timestamp: 0,
payload: DeltaPayload { observation: obs },
}
}
#[inline]
pub const fn batch_end() -> Self {
Self {
tag: DeltaTag::BatchEnd,
sequence: 0,
source_tile: 0,
_reserved: 0,
timestamp: 0,
payload: DeltaPayload { raw: [0u8; 8] },
}
}
#[inline]
pub const fn is_nop(&self) -> bool {
matches!(self.tag, DeltaTag::Nop)
}
#[inline]
pub unsafe fn get_edge_add(&self) -> &EdgeAdd {
unsafe { &self.payload.edge_add }
}
#[inline]
pub unsafe fn get_edge_remove(&self) -> &EdgeRemove {
unsafe { &self.payload.edge_remove }
}
#[inline]
pub unsafe fn get_weight_update(&self) -> &WeightUpdate {
unsafe { &self.payload.weight_update }
}
#[inline]
pub unsafe fn get_observation(&self) -> &Observation {
unsafe { &self.payload.observation }
}
}
const _: () = assert!(size_of::<EdgeAdd>() == 8, "EdgeAdd must be 8 bytes");
const _: () = assert!(size_of::<EdgeRemove>() == 8, "EdgeRemove must be 8 bytes");
const _: () = assert!(
size_of::<WeightUpdate>() == 8,
"WeightUpdate must be 8 bytes"
);
const _: () = assert!(size_of::<Observation>() == 8, "Observation must be 8 bytes");
const _: () = assert!(size_of::<Delta>() == 16, "Delta must be 16 bytes");
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_weight_conversion() {
assert_eq!(weight_to_f32(100), 1.0);
assert_eq!(weight_to_f32(50), 0.5);
assert_eq!(weight_to_f32(0), 0.0);
assert_eq!(f32_to_weight(1.0), 100);
assert_eq!(f32_to_weight(0.5), 50);
assert_eq!(f32_to_weight(0.0), 0);
}
#[test]
fn test_delta_tag_roundtrip() {
for i in 0..=7 {
let tag = DeltaTag::from(i);
assert_eq!(tag as u8, i);
}
}
#[test]
fn test_edge_add_creation() {
let ea = EdgeAdd::new(1, 2, 150);
assert_eq!(ea.source, 1);
assert_eq!(ea.target, 2);
assert_eq!(ea.weight, 150);
}
#[test]
fn test_delta_edge_add() {
let delta = Delta::edge_add(5, 10, 200);
assert_eq!(delta.tag, DeltaTag::EdgeAdd);
unsafe {
let ea = delta.get_edge_add();
assert_eq!(ea.source, 5);
assert_eq!(ea.target, 10);
assert_eq!(ea.weight, 200);
}
}
#[test]
fn test_observation_creation() {
let obs = Observation::connectivity(42, true);
assert_eq!(obs.vertex, 42);
assert_eq!(obs.obs_type, Observation::TYPE_CONNECTIVITY);
assert_eq!(obs.flags, 1);
}
}