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}