1use kevy_bytes::SmallBytes;
40
41#[derive(Clone)]
43pub struct SmallHashData {
44 count: u8,
45 used: u8,
46 buf: [u8; SMALL_HASH_BUF_CAP],
47}
48
49pub(crate) const SMALL_HASH_BUF_CAP: usize = 22;
50
51pub(crate) const SMALL_HASH_FIELD_MAX: usize = SMALL_HASH_BUF_CAP - 3;
55
56pub(crate) const SMALL_HASH_VALUE_MAX: usize = SMALL_HASH_BUF_CAP - 3;
58
59pub(crate) const SMALL_HASH_COUNT_MAX: usize = 8;
61
62pub(crate) enum AddResult {
64 Added,
66 Updated,
69 NoRoom,
71}
72
73impl SmallHashData {
74 pub(crate) fn new() -> Self {
75 Self { count: 0, used: 0, buf: [0; SMALL_HASH_BUF_CAP] }
76 }
77
78 pub(crate) fn with_one(field: &[u8], value: &[u8]) -> Option<Self> {
80 if field.len() > SMALL_HASH_FIELD_MAX || value.len() > SMALL_HASH_VALUE_MAX {
81 return None;
82 }
83 let need = 2 + field.len() + value.len();
84 if need > SMALL_HASH_BUF_CAP {
85 return None;
86 }
87 let mut s = Self::new();
88 s.write_pair_at(0, field, value);
89 s.count = 1;
90 s.used = need as u8;
91 Some(s)
92 }
93
94 pub fn len(&self) -> usize {
95 self.count as usize
96 }
97
98 pub fn is_empty(&self) -> bool {
99 self.count == 0
100 }
101
102 pub fn get(&self, field: &[u8]) -> Option<&[u8]> {
104 for (f, v) in self.iter() {
105 if f == field {
106 return Some(v);
107 }
108 }
109 None
110 }
111
112 pub fn contains_key(&self, field: &[u8]) -> bool {
114 self.get(field).is_some()
115 }
116
117 pub fn iter(&self) -> SmallHashIter<'_> {
118 SmallHashIter { buf: &self.buf[..self.used as usize], cursor: 0 }
119 }
120
121 pub(crate) fn try_set(&mut self, field: &[u8], value: &[u8]) -> AddResult {
123 if field.len() > SMALL_HASH_FIELD_MAX || value.len() > SMALL_HASH_VALUE_MAX {
124 return AddResult::NoRoom;
125 }
126 if let Some((off, flen, old_vlen)) = self.locate(field) {
128 let new_vlen = value.len();
129 let used = self.used as usize;
130 let val_off = off + 1 + flen + 1;
131 let old_end = val_off + old_vlen;
132 let delta: isize = new_vlen as isize - old_vlen as isize;
133 let new_used_i = used as isize + delta;
134 if new_used_i > SMALL_HASH_BUF_CAP as isize {
135 return AddResult::NoRoom;
136 }
137 let new_used = new_used_i as usize;
138 if delta != 0 {
140 self.buf.copy_within(old_end..used, (val_off + new_vlen) as usize);
141 if delta < 0 {
142 self.buf[new_used..used].fill(0);
144 }
145 }
146 self.buf[val_off - 1] = new_vlen as u8;
147 self.buf[val_off..val_off + new_vlen].copy_from_slice(value);
148 self.used = new_used as u8;
149 return AddResult::Updated;
150 }
151 if self.count as usize >= SMALL_HASH_COUNT_MAX {
153 return AddResult::NoRoom;
154 }
155 let need = 2 + field.len() + value.len();
156 let new_used = self.used as usize + need;
157 if new_used > SMALL_HASH_BUF_CAP {
158 return AddResult::NoRoom;
159 }
160 let off = self.used as usize;
161 self.write_pair_at(off, field, value);
162 self.used = new_used as u8;
163 self.count += 1;
164 AddResult::Added
165 }
166
167 pub(crate) fn try_remove(&mut self, field: &[u8]) -> bool {
169 let Some((off, flen, vlen)) = self.locate(field) else {
170 return false;
171 };
172 let used = self.used as usize;
173 let entry_end = off + 2 + flen + vlen;
174 self.buf.copy_within(entry_end..used, off);
175 let shifted = used - entry_end;
176 let new_used = off + shifted;
177 self.buf[new_used..used].fill(0);
178 self.used = new_used as u8;
179 self.count -= 1;
180 true
181 }
182
183 fn write_pair_at(&mut self, off: usize, field: &[u8], value: &[u8]) {
184 self.buf[off] = field.len() as u8;
185 let fstart = off + 1;
186 let fend = fstart + field.len();
187 self.buf[fstart..fend].copy_from_slice(field);
188 self.buf[fend] = value.len() as u8;
189 let vstart = fend + 1;
190 let vend = vstart + value.len();
191 self.buf[vstart..vend].copy_from_slice(value);
192 }
193
194 fn locate(&self, field: &[u8]) -> Option<(usize, usize, usize)> {
196 let mut cursor = 0usize;
197 let used = self.used as usize;
198 while cursor < used {
199 let flen = self.buf[cursor] as usize;
200 let fstart = cursor + 1;
201 let fend = fstart + flen;
202 let vlen = self.buf[fend] as usize;
203 if &self.buf[fstart..fend] == field {
204 return Some((cursor, flen, vlen));
205 }
206 cursor = fend + 1 + vlen;
207 }
208 None
209 }
210}
211
212pub struct SmallHashIter<'a> {
214 buf: &'a [u8],
215 cursor: usize,
216}
217
218impl<'a> Iterator for SmallHashIter<'a> {
219 type Item = (&'a [u8], &'a [u8]);
220 fn next(&mut self) -> Option<Self::Item> {
221 if self.cursor >= self.buf.len() {
222 return None;
223 }
224 let flen = self.buf[self.cursor] as usize;
225 let fstart = self.cursor + 1;
226 let fend = fstart + flen;
227 let vlen = self.buf[fend] as usize;
228 let vstart = fend + 1;
229 let vend = vstart + vlen;
230 self.cursor = vend;
231 Some((&self.buf[fstart..fend], &self.buf[vstart..vend]))
232 }
233}
234
235pub(crate) fn promote(inline: &SmallHashData) -> crate::value::HashData {
237 let mut h = crate::value::HashData::with_capacity(inline.len().max(1));
238 for (f, v) in inline.iter() {
239 h.insert(SmallBytes::from_slice(f), v.to_vec());
240 }
241 h
242}
243
244#[cfg(test)]
245mod tests {
246 use super::*;
247
248 #[test]
249 fn size_is_24_bytes() {
250 assert_eq!(std::mem::size_of::<SmallHashData>(), 24);
251 }
252
253 #[test]
254 fn with_one_and_get() {
255 let s = SmallHashData::with_one(b"name", b"alice").unwrap();
256 assert_eq!(s.len(), 1);
257 assert_eq!(s.get(b"name"), Some(b"alice".as_slice()));
258 assert!(s.contains_key(b"name"));
259 assert!(!s.contains_key(b"age"));
260 }
261
262 #[test]
263 fn with_one_too_big() {
264 let big = vec![b'x'; SMALL_HASH_FIELD_MAX + 1];
265 assert!(SmallHashData::with_one(&big, b"v").is_none());
266 }
267
268 #[test]
269 fn set_add_update_remove() {
270 let mut s = SmallHashData::new();
271 assert!(matches!(s.try_set(b"a", b"1"), AddResult::Added));
272 assert!(matches!(s.try_set(b"b", b"22"), AddResult::Added));
273 assert!(matches!(s.try_set(b"a", b"X"), AddResult::Updated));
274 assert_eq!(s.get(b"a"), Some(b"X".as_slice()));
275 assert_eq!(s.get(b"b"), Some(b"22".as_slice()));
276 assert!(s.try_remove(b"a"));
277 assert!(!s.contains_key(b"a"));
278 assert_eq!(s.get(b"b"), Some(b"22".as_slice()));
279 }
280
281 #[test]
282 fn set_no_room_overflow() {
283 let mut s = SmallHashData::new();
284 assert!(matches!(
286 s.try_set(b"fieldnam", b"valuevalue"),
287 AddResult::Added
288 ));
289 assert!(matches!(s.try_set(b"more", b"data"), AddResult::NoRoom));
291 }
292
293 #[test]
294 fn update_value_grows_within_budget() {
295 let mut s = SmallHashData::new();
296 s.try_set(b"k", b"v");
297 assert!(matches!(s.try_set(b"k", b"longerv!"), AddResult::Updated));
299 assert_eq!(s.get(b"k"), Some(b"longerv!".as_slice()));
300 }
301
302 #[test]
303 fn update_value_no_room() {
304 let mut s = SmallHashData::new();
305 s.try_set(b"a", b"x");
307 s.try_set(b"bbbbbbbb", b"yyyyyyyyyy"); let mut s = SmallHashData::new();
310 s.try_set(b"abc", b"defghijk"); s.try_set(b"x", b"yzwuv"); assert!(matches!(
314 s.try_set(b"x", b"yzwuvAB"),
315 AddResult::NoRoom
316 ));
317 }
318
319 #[test]
320 fn iter_in_insertion_order() {
321 let mut s = SmallHashData::new();
322 s.try_set(b"a", b"1");
323 s.try_set(b"b", b"2");
324 let v: Vec<(&[u8], &[u8])> = s.iter().collect();
325 assert_eq!(v, vec![(b"a".as_slice(), b"1".as_slice()), (b"b".as_slice(), b"2".as_slice())]);
326 }
327
328 #[test]
329 fn promote_preserves_pairs() {
330 let mut s = SmallHashData::new();
331 s.try_set(b"a", b"1");
332 s.try_set(b"bb", b"22");
333 let h = promote(&s);
334 assert_eq!(h.len(), 2);
335 assert_eq!(h.get(b"a".as_slice()).map(Vec::as_slice), Some(b"1".as_slice()));
336 assert_eq!(h.get(b"bb".as_slice()).map(Vec::as_slice), Some(b"22".as_slice()));
337 }
338}