Skip to main content

alpha2_geometry/
hash.rs

1//! A stable 128-bit hash, identical across targets and runs (unlike `std`'s seeded hashers).
2
3use kurbo::{BezPath, PathEl, Point};
4
5/// Two FNV-style lanes over the written bytes, matching the render graph's keys.
6#[derive(Clone, Debug)]
7pub struct StableHash([u64; 2]);
8
9impl Default for StableHash {
10    fn default() -> Self {
11        Self::new()
12    }
13}
14
15impl StableHash {
16    /// An empty hash.
17    #[must_use]
18    pub const fn new() -> Self {
19        Self([0xcbf2_9ce4_8422_2325, 0x6c62_272e_07bb_0142])
20    }
21
22    /// Adds `bytes`, length first, so adjacent writes cannot run together.
23    pub fn bytes(&mut self, bytes: &[u8]) {
24        self.u64(bytes.len() as u64);
25        for byte in bytes {
26            self.mix(*byte);
27        }
28    }
29
30    /// Adds a 32-bit value.
31    pub fn u32(&mut self, value: u32) {
32        for byte in value.to_le_bytes() {
33            self.mix(byte);
34        }
35    }
36
37    /// Adds a 64-bit value.
38    pub fn u64(&mut self, value: u64) {
39        for byte in value.to_le_bytes() {
40            self.mix(byte);
41        }
42    }
43
44    /// Adds the bits of an `f32`.
45    pub fn f32(&mut self, value: f32) {
46        self.u32(value.to_bits());
47    }
48
49    /// Adds the bits of an `f64`.
50    pub fn f64(&mut self, value: f64) {
51        self.u64(value.to_bits());
52    }
53
54    /// Adds a point's bits.
55    pub fn point(&mut self, point: Point) {
56        self.f64(point.x);
57        self.f64(point.y);
58    }
59
60    /// Adds every element of a path, with its kind.
61    pub fn path(&mut self, path: &BezPath) {
62        self.u64(path.elements().len() as u64);
63        for element in path.elements() {
64            match *element {
65                PathEl::MoveTo(p) => {
66                    self.mix(b'M');
67                    self.point(p);
68                }
69                PathEl::LineTo(p) => {
70                    self.mix(b'L');
71                    self.point(p);
72                }
73                PathEl::QuadTo(a, b) => {
74                    self.mix(b'Q');
75                    self.point(a);
76                    self.point(b);
77                }
78                PathEl::CurveTo(a, b, c) => {
79                    self.mix(b'C');
80                    self.point(a);
81                    self.point(b);
82                    self.point(c);
83                }
84                PathEl::ClosePath => self.mix(b'Z'),
85            }
86        }
87    }
88
89    fn mix(&mut self, byte: u8) {
90        self.0[0] ^= u64::from(byte);
91        self.0[0] = self.0[0].wrapping_mul(0x0000_0100_0000_01b3);
92        self.0[1] ^= u64::from(byte).wrapping_add(0x9d);
93        self.0[1] = self.0[1].wrapping_mul(0x9e37_79b1_85eb_ca87);
94    }
95
96    /// The key of everything added so far.
97    #[must_use]
98    pub fn finish(&self) -> u128 {
99        (u128::from(self.0[1]) << 64) | u128::from(self.0[0])
100    }
101}