summaryrefslogtreecommitdiff
path: root/crates/typst-utils/src/hash.rs
diff options
context:
space:
mode:
Diffstat (limited to 'crates/typst-utils/src/hash.rs')
-rw-r--r--crates/typst-utils/src/hash.rs99
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()))
+ }
+}