diff options
Diffstat (limited to 'crates/typst-utils/src/hash.rs')
| -rw-r--r-- | crates/typst-utils/src/hash.rs | 99 |
1 files changed, 54 insertions, 45 deletions
diff --git a/crates/typst-utils/src/hash.rs b/crates/typst-utils/src/hash.rs index 9687da20..eaf8a672 100644 --- a/crates/typst-utils/src/hash.rs +++ b/crates/typst-utils/src/hash.rs @@ -30,9 +30,10 @@ use siphasher::sip128::{Hasher128, SipHasher13}; /// # Usage /// If the value is expected to be cloned, it is best used inside of an `Arc` /// or `Rc` to best re-use the hash once it has been computed. +#[derive(Clone)] pub struct LazyHash<T: ?Sized> { /// The hash for the value. - hash: AtomicU128, + hash: HashLock, /// The underlying value. value: T, } @@ -48,16 +49,7 @@ impl<T> LazyHash<T> { /// Wraps an item without pre-computed hash. #[inline] pub fn new(value: T) -> Self { - Self { hash: AtomicU128::new(0), value } - } - - /// Wrap an item with a pre-computed hash. - /// - /// **Important:** The hash must be correct for the value. This cannot be - /// enforced at compile time, so use with caution. - #[inline] - pub fn reuse<U: ?Sized>(value: T, existing: &LazyHash<U>) -> Self { - LazyHash { hash: AtomicU128::new(existing.load_hash()), value } + Self { hash: HashLock::new(), value } } /// Returns the wrapped value. @@ -67,33 +59,11 @@ impl<T> LazyHash<T> { } } -impl<T: ?Sized> LazyHash<T> { - /// Get the hash, returns zero if not computed yet. - #[inline] - fn load_hash(&self) -> u128 { - // We only need atomicity and no synchronization of other operations, so - // `Relaxed` is fine. - self.hash.load(Ordering::Relaxed) - } -} - impl<T: Hash + ?Sized + 'static> LazyHash<T> { /// Get the hash or compute it if not set yet. #[inline] fn load_or_compute_hash(&self) -> u128 { - let mut hash = self.load_hash(); - if hash == 0 { - hash = hash_item(&self.value); - self.hash.store(hash, Ordering::Relaxed); - } - hash - } - - /// Reset the hash to zero. - #[inline] - fn reset_hash(&mut self) { - // Because we have a mutable reference, we can skip the atomic. - *self.hash.get_mut() = 0; + self.hash.get_or_insert_with(|| hash_item(&self.value)) } } @@ -140,23 +110,14 @@ impl<T: ?Sized> Deref for LazyHash<T> { } } -impl<T: Hash + ?Sized + 'static> DerefMut for LazyHash<T> { +impl<T: ?Sized + 'static> DerefMut for LazyHash<T> { #[inline] fn deref_mut(&mut self) -> &mut Self::Target { - self.reset_hash(); + self.hash.reset(); &mut self.value } } -impl<T: Hash + Clone + 'static> Clone for LazyHash<T> { - fn clone(&self) -> Self { - Self { - hash: AtomicU128::new(self.load_hash()), - value: self.value.clone(), - } - } -} - impl<T: Debug> Debug for LazyHash<T> { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { self.value.fmt(f) @@ -233,3 +194,51 @@ impl<T: Debug> Debug for ManuallyHash<T> { self.value.fmt(f) } } + +/// Storage for lazy hash computation. +pub struct HashLock(AtomicU128); + +impl HashLock { + /// Create a new unset hash cell. + pub const fn new() -> Self { + Self(AtomicU128::new(0)) + } + + /// Get the hash or compute it if not set yet. + #[inline] + pub fn get_or_insert_with(&self, f: impl FnOnce() -> u128) -> u128 { + let mut hash = self.get(); + if hash == 0 { + hash = f(); + self.0.store(hash, Ordering::Relaxed); + } + hash + } + + /// Reset the hash to unset. + #[inline] + pub fn reset(&mut self) { + // Because we have a mutable reference, we can skip the atomic. + *self.0.get_mut() = 0; + } + + /// Get the hash, returns zero if not computed yet. + #[inline] + fn get(&self) -> u128 { + // We only need atomicity and no synchronization of other operations, so + // `Relaxed` is fine. + self.0.load(Ordering::Relaxed) + } +} + +impl Default for HashLock { + fn default() -> Self { + Self::new() + } +} + +impl Clone for HashLock { + fn clone(&self) -> Self { + Self(AtomicU128::new(self.get())) + } +} |
