livesplit_core/rendering/
entity.rs

1use core::{
2    hash::{Hash, Hasher},
3    mem,
4};
5
6use super::{
7    resource::{Handle, LabelHandle},
8    FillShader, Rgba, Transform,
9};
10
11struct FxHasher(u64);
12
13impl Hasher for FxHasher {
14    #[inline]
15    fn write_u8(&mut self, i: u8) {
16        self.write_u64(i as u64);
17    }
18
19    #[inline]
20    fn write_u16(&mut self, i: u16) {
21        self.write_u64(i as u64);
22    }
23
24    #[inline]
25    fn write_u32(&mut self, i: u32) {
26        self.write_u64(i as u64);
27    }
28
29    #[inline]
30    fn write_u64(&mut self, i: u64) {
31        self.0 = (self.0.rotate_left(5) ^ i).wrapping_mul(0x517cc1b727220a95);
32    }
33
34    #[inline]
35    fn write_u128(&mut self, i: u128) {
36        let [a, b]: [u64; 2] = bytemuck::cast(i);
37        self.write_u64(a);
38        self.write_u64(b);
39    }
40
41    #[inline]
42    fn write_usize(&mut self, i: usize) {
43        self.write(bytemuck::bytes_of(&i))
44    }
45
46    #[inline]
47    fn finish(&self) -> u64 {
48        self.0
49    }
50
51    #[inline]
52    fn write(&mut self, bytes: &[u8]) {
53        let (_, chunks, rem) = bytemuck::pod_align_to::<_, [u8; 8]>(bytes);
54        for chunk in chunks {
55            self.write_u64(bytemuck::cast(*chunk));
56        }
57        let (_, chunks, rem) = bytemuck::pod_align_to::<_, [u8; 4]>(rem);
58        for chunk in chunks {
59            self.write_u32(bytemuck::cast(*chunk));
60        }
61        for byte in rem {
62            self.write_u8(*byte);
63        }
64    }
65}
66
67/// An entity describes an element positioned on a [`Scene's`](super::Scene)
68/// [`Layer`](super::Layer) that is meant to be visualized.
69pub enum Entity<P, I, L> {
70    /// A path where the inside is filled with the [`FillShader`]. For
71    /// determining what's inside the [non-zero fill
72    /// rule](https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/fill-rule#nonzero)
73    /// is supposed to be used.
74    FillPath(Handle<P>, FillShader, Transform),
75    /// A path where only the path lines themselves are supposed to be drawn.
76    /// There is no notion of an inside region. The floating point number
77    /// determines the thickness of the path.
78    StrokePath(Handle<P>, f32, Rgba, Transform),
79    /// An image.
80    Image(Handle<I>, Transform),
81    /// A text label.
82    Label(LabelHandle<L>, FillShader, Transform),
83}
84
85pub fn calculate_hash<P, I, L>(
86    background: &Option<FillShader>,
87    entities: &[Entity<P, I, L>],
88) -> u64 {
89    let mut hasher = FxHasher(0x517cc1b727220a95);
90    mem::discriminant(background).hash(&mut hasher);
91    if let Some(background) = background {
92        hash_shader(background, &mut hasher);
93    }
94    entities.hash(&mut hasher);
95    hasher.finish()
96}
97
98#[inline]
99fn hash_float(f: f32, state: &mut impl Hasher) {
100    u32::hash(&bytemuck::cast(f), state);
101}
102
103#[inline]
104fn hash_transform(f: &Transform, state: &mut impl Hasher) {
105    const _: () = assert!(core::mem::size_of::<Transform>() == 16);
106    let [a, b]: [u64; 2] = bytemuck::cast(*f);
107    u64::hash(&a, state);
108    u64::hash(&b, state);
109}
110
111#[inline]
112fn hash_floats(f: &[f32; 4], state: &mut impl Hasher) {
113    let [a, b]: [u64; 2] = bytemuck::cast(*f);
114    u64::hash(&a, state);
115    u64::hash(&b, state);
116}
117
118fn hash_shader(shader: &FillShader, state: &mut impl Hasher) {
119    mem::discriminant(shader).hash(state);
120    match shader {
121        FillShader::SolidColor(c) => hash_floats(c, state),
122        FillShader::VerticalGradient(t, b) => {
123            hash_floats(t, state);
124            hash_floats(b, state);
125        }
126        FillShader::HorizontalGradient(l, r) => {
127            hash_floats(l, state);
128            hash_floats(r, state);
129        }
130    }
131}
132
133impl<P, I, L> Hash for Entity<P, I, L> {
134    fn hash<H: Hasher>(&self, state: &mut H) {
135        mem::discriminant(self).hash(state);
136        match self {
137            Entity::FillPath(path, shader, transform) => {
138                path.hash(state);
139                hash_shader(shader, state);
140                hash_transform(transform, state);
141            }
142            Entity::StrokePath(path, stroke_width, color, transform) => {
143                path.hash(state);
144                hash_float(*stroke_width, state);
145                hash_floats(color, state);
146                hash_transform(transform, state);
147            }
148            Entity::Image(image, transform) => {
149                image.hash(state);
150                hash_transform(transform, state);
151            }
152            Entity::Label(label, shader, transform) => {
153                label.hash(state);
154                hash_shader(shader, state);
155                hash_transform(transform, state);
156            }
157        }
158    }
159}