Skip to main content

triblespace_core/patch/
entry.rs

1use super::*;
2use std::sync::Arc;
3
4/// Reference-counted handle to a heap-allocated leaf node in a PATCH trie.
5///
6/// `Entry` is the unit of insertion for the memory-only path: it owns a
7/// shared `Leaf<KEY_LEN, V>` and can be inserted into multiple PATCH
8/// instances (each PATCH gets its own Head pointing at the shared
9/// refcounted Leaf). The archive-backed counterpart is [`ArchiveEntry`],
10/// which only exists for `V = ()` since archive bytes carry no value
11/// field.
12#[derive(Debug)]
13#[repr(C)]
14pub struct Entry<const KEY_LEN: usize, V = ()> {
15    ptr: NonNull<Leaf<KEY_LEN, V>>,
16}
17
18impl<const KEY_LEN: usize> Entry<KEY_LEN> {
19    /// Creates a new entry with the given key and a unit value.
20    pub fn new(key: &[u8; KEY_LEN]) -> Self {
21        unsafe {
22            let ptr = Leaf::<KEY_LEN, ()>::new(key, ());
23            Self { ptr }
24        }
25    }
26}
27
28impl<const KEY_LEN: usize, V> Entry<KEY_LEN, V> {
29    /// Creates a new entry with the given key and associated value.
30    pub fn with_value(key: &[u8; KEY_LEN], value: V) -> Self {
31        unsafe {
32            let ptr = Leaf::<KEY_LEN, V>::new(key, value);
33            Self { ptr }
34        }
35    }
36
37    /// Returns a reference to the value stored in this entry.
38    pub fn value(&self) -> &V {
39        unsafe { &self.ptr.as_ref().value }
40    }
41
42    pub(super) fn leaf<O: KeySchema<KEY_LEN>>(&self) -> Head<KEY_LEN, O, V> {
43        unsafe { Head::new(0, Leaf::rc_inc(self.ptr)) }
44    }
45}
46
47impl<const KEY_LEN: usize, V> Clone for Entry<KEY_LEN, V> {
48    fn clone(&self) -> Self {
49        unsafe {
50            Self {
51                ptr: Leaf::rc_inc(self.ptr),
52            }
53        }
54    }
55}
56
57impl<const KEY_LEN: usize, V> Drop for Entry<KEY_LEN, V> {
58    fn drop(&mut self) {
59        unsafe {
60            Leaf::rc_dec(self.ptr);
61        }
62    }
63}
64
65/// Insertion entry for archive-backed PATCHes (`V = ()` only).
66///
67/// Holds a thin pointer into an archive's bytes plus a *borrow* of
68/// the `Arc<dyn ArchiveOwner>` that keeps those bytes alive. When
69/// inserted via [`PATCH::insert_archive`], the entry's key becomes a
70/// [`Head::new_local_leaf`] under a Branch whose `owner` matches; on
71/// owner mismatch the leaf is automatically reified into a heap-
72/// allocated `Leaf<KEY_LEN, ()>` so the result is owner-consistent.
73///
74/// The owner is borrowed (not owned) so the ingest hot loop pays
75/// **zero** atomic ref-count traffic per trible — the only clones
76/// happen lazily inside the receiving PATCH when a Branch actually
77/// adopts the owner. The caller (typically a chunked-archive
78/// decoder) keeps one `Arc` alive on the stack for the whole batch.
79///
80/// Only valid for `V = ()` because archive bytes don't carry a value
81/// field — the constructor's type parameter enforces this.
82pub struct ArchiveEntry<'a, const KEY_LEN: usize> {
83    pub(super) ptr: NonNull<[u8; KEY_LEN]>,
84    pub(super) owner: &'a Arc<dyn ArchiveOwner>,
85    /// Pre-computed siphash24 of the trible bytes (matches what
86    /// `Head::hash()` would compute on the resulting `LocalLeaf`).
87    /// Cached once at `ArchiveEntry::new` so the 6-way fan-out across
88    /// covering indexes runs one hash instead of six.
89    pub(super) hash: u128,
90}
91
92impl<'a, const KEY_LEN: usize> ArchiveEntry<'a, KEY_LEN> {
93    /// Creates an `ArchiveEntry` referencing a `[u8; KEY_LEN]` trible
94    /// inside an archive's bytes. Computes the siphash24 of the
95    /// trible's bytes eagerly so the 6 covering indexes can share it.
96    ///
97    /// # Safety
98    /// - `ptr` must remain valid for as long as `owner` is held.
99    /// - `ptr` must be 16-byte aligned (so [`Head::new_local_leaf`]'s
100    ///   tagged-pointer encoding has room for the `LocalLeaf` tag in
101    ///   the low 4 bits). Any `[u8; 64]` at an offset that's a
102    ///   multiple of 16 from a 16-byte aligned base satisfies this.
103    pub unsafe fn new(
104        ptr: NonNull<[u8; KEY_LEN]>,
105        owner: &'a Arc<dyn ArchiveOwner>,
106    ) -> Self {
107        debug_assert_eq!(
108            ptr.as_ptr() as usize & 0x0f,
109            0,
110            "ArchiveEntry pointer must be 16-byte aligned"
111        );
112        let hash = unsafe {
113            use siphasher::sip128::SipHasher24;
114            use std::ptr::addr_of;
115            let key = *addr_of!(crate::patch::SIP_KEY);
116            SipHasher24::new_with_key(&key).hash(&ptr.as_ref()[..]).into()
117        };
118        Self { ptr, owner, hash }
119    }
120
121    /// Returns a `LocalLeaf` head for this entry, the borrowed owner
122    /// Arc, and the pre-computed leaf hash.
123    pub(super) fn leaf<O: KeySchema<KEY_LEN>>(
124        &self,
125    ) -> (Head<KEY_LEN, O, ()>, &'a Arc<dyn ArchiveOwner>, u128) {
126        unsafe { (Head::new_local_leaf(0, self.ptr), self.owner, self.hash) }
127    }
128
129    /// Borrows the owner Arc without cloning.
130    pub fn owner(&self) -> &'a Arc<dyn ArchiveOwner> {
131        self.owner
132    }
133}
134
135impl<'a, const KEY_LEN: usize> Copy for ArchiveEntry<'a, KEY_LEN> {}
136
137impl<'a, const KEY_LEN: usize> Clone for ArchiveEntry<'a, KEY_LEN> {
138    fn clone(&self) -> Self {
139        *self
140    }
141}
142
143impl<'a, const KEY_LEN: usize> core::fmt::Debug for ArchiveEntry<'a, KEY_LEN> {
144    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
145        f.debug_struct("ArchiveEntry")
146            .field("ptr", &self.ptr)
147            .field("owner", &"<archive owner>")
148            .finish()
149    }
150}