tellur_core/dyn_compare.rs
1//! Trait-object equality and hashing for component trees.
2//!
3//! [`RasterComponent`](crate::raster::RasterComponent) and
4//! [`VectorComponent`](crate::vector::VectorComponent) need to participate in
5//! `PartialEq` and `Hash` so that render results can be memoized in a
6//! `RenderContext` cache keyed by component identity. The trait-object form
7//! `dyn Component` cannot derive these directly — `PartialEq::eq` is not
8//! dyn-safe and `Hash::hash` is parameterised over `H: Hasher`.
9//!
10//! [`DynEq`] and [`DynHash`] are the standard workaround: they expose the
11//! type-erased shape of `==` and `hash` via [`Any`] downcast and a
12//! `&mut dyn Hasher` argument, and have blanket impls for any `T: PartialEq +
13//! Hash + 'static`. A component trait adds them as super-traits, and a
14//! manual `impl PartialEq for dyn Component` / `impl Hash for dyn Component`
15//! delegates through them. With that wired up, `Box<dyn Component>`,
16//! `Vec<Box<dyn Component>>`, etc. pick up `PartialEq + Hash` automatically
17//! through the standard library blanket impls, so `#[derive]` on containers
18//! that hold trait objects just works.
19//!
20//! [`hash_f32`] / [`hash_f32_slice`] are the canonical hashers for `f32`
21//! fields and slices in cache-key types. They use `to_bits()` so `-0.0`,
22//! `+0.0`, and `NaN` keep distinct hashes (matching the way `PartialEq` on
23//! `f32` already treats them distinctly when sourced from bit patterns).
24
25use std::any::Any;
26use std::hash::{Hash, Hasher};
27
28/// Object-safe equality. Implemented for all `T: PartialEq + 'static`.
29///
30/// Used as a super-trait on `RasterComponent` / `VectorComponent` so a
31/// `dyn Component` can compare itself with another `dyn Component` by
32/// downcasting to its concrete type. Two trait objects of different
33/// concrete types compare as not-equal.
34///
35/// The `dyn_eq` argument is `&dyn Any` (not `&dyn DynEq`) so callers can
36/// hand it `other.as_any()` directly — this sidesteps trait-object
37/// upcasting between component traits and `DynEq`.
38pub trait DynEq: Any {
39 fn as_any(&self) -> &dyn Any;
40 fn dyn_eq(&self, other: &dyn Any) -> bool;
41 /// Concrete type name behind this trait object, suitable for
42 /// diagnostics (e.g. `tellur_renderer::shadow::DropShadow`). Backed
43 /// by [`std::any::type_name`] specialized to the impl's `Self`, so
44 /// callers can introspect a `&dyn Component` without a downcast.
45 fn type_name(&self) -> &'static str;
46}
47
48impl<T: PartialEq + Any> DynEq for T {
49 fn as_any(&self) -> &dyn Any {
50 self
51 }
52
53 fn dyn_eq(&self, other: &dyn Any) -> bool {
54 match other.downcast_ref::<T>() {
55 Some(o) => self == o,
56 None => false,
57 }
58 }
59
60 fn type_name(&self) -> &'static str {
61 std::any::type_name::<T>()
62 }
63}
64
65/// Object-safe hashing. Implemented for all `T: Hash + ?Sized`.
66///
67/// `Hash::hash` is generic over the hasher, so `dyn Component` cannot
68/// implement it directly. `DynHash` erases the hasher type behind
69/// `&mut dyn Hasher`, which works because the standard library has
70/// `impl<H: Hasher + ?Sized> Hasher for &mut H` — i.e. `&mut dyn Hasher`
71/// is itself a `Hasher`, so calling `Hash::hash(&self, &mut state)` from
72/// inside the impl is legal.
73pub trait DynHash {
74 fn dyn_hash(&self, state: &mut dyn Hasher);
75}
76
77impl<T: Hash + ?Sized> DynHash for T {
78 fn dyn_hash(&self, mut state: &mut dyn Hasher) {
79 self.hash(&mut state);
80 }
81}
82
83/// Hashes an `f32` by its bit pattern.
84///
85/// `f32` does not implement `Hash` because its `PartialEq` is not
86/// reflexive (`NaN != NaN`), so the standard library refuses to derive a
87/// hash that would violate `a == b => hash(a) == hash(b)`. For cache-key
88/// use we want a total hash and treat equal bit patterns as equal — this
89/// helper makes that intent explicit and consistent across types.
90#[inline]
91pub fn hash_f32<H: Hasher>(v: f32, state: &mut H) {
92 v.to_bits().hash(state);
93}
94
95/// Hashes an `&[f32]` by hashing each element's bit pattern in order.
96#[inline]
97pub fn hash_f32_slice<H: Hasher>(vs: &[f32], state: &mut H) {
98 vs.len().hash(state);
99 for v in vs {
100 hash_f32(*v, state);
101 }
102}