Skip to main content

yog_data/frame/
mod.rs

1//! Frames: Yog Data packed for the bus — both sides know the schema, so
2//! nothing says what a value is or what it's called. A record's fields go
3//! in the order they're declared, after the record's length (so a reader
4//! that knows fewer fields skips the rest; one that knows more takes their
5//! defaults: fields are only ever appended). Strings go in a table at the
6//! end, each once; the body refers to them by index.
7//!
8//! ```text
9//! frame  = body, table, table length (a varint written backwards)
10//! table  = (varint length, UTF-8) per string, in the order first used
11//! record = varint length, its fields
12//! ```
13//!
14//! | Value | Packed |
15//! |---|---|
16//! | bool | a byte, 0 or 1 |
17//! | int | zigzag varint |
18//! | `f64`, `f32` | 8, 4 bytes, little-endian |
19//! | string | varint index into the table |
20//! | bytes | varint length, the bytes |
21//! | `Option` | a byte, 0 (none) or 1, then the value |
22//! | list | varint count, varint byte length (a reader steps over it), the values |
23//! | list of ints | varint count, bits each (a byte), the values bit-packed in lanes ([`lanes`]; signed ones zigzagged) |
24//! | list of floats | varint count, `f32`-exact (a byte), the values raw — 4 bytes each if they all are `f32`s |
25//! | unit enum | varint variant index (variants only appended) |
26//! | anything else | its binary form ([`crate::binary`]), as bytes |
27//!
28//! Nothing is aligned, every value takes at least a byte (a count is
29//! checked against the bytes left), and a malformed frame reads as `None`,
30//! never a panic.
31//!
32//! ```
33//! #[derive(yog_data::Schema, Debug, PartialEq)]
34//! struct Hit { id: String, count: u32, ratio: f32, next: Option<Box<Hit>> }
35//!
36//! let hit = Hit { id: "minecraft:stone".into(), count: 3, ratio: 0.5, next: None };
37//! let frame = yog_data::frame::to_frame(&hit);
38//! assert_eq!(yog_data::frame::read_frame::<Hit>(&frame), Some(hit));
39//! ```
40
41pub mod floats;
42pub mod lanes;
43mod shaped;
44mod view;
45
46pub use shaped::{read_shaped, unpack_shaped};
47pub use view::{Floats, FromF64, Ints, List, ListIter, View, list, read_view};
48
49use yog_sync::Arc;
50
51use bumpalo::Bump;
52
53use crate::binary::interner::Interner;
54use crate::binary::writer::START_BYTES;
55use crate::binary::{
56    Decoder, MAX_DEPTH, VARINT_BITS, VARINT_MAX_SHIFT, VARINT_MORE, VARINT_SHIFT, Writer,
57    read_varint, unzigzag, varint, zigzag,
58};
59use crate::schema::{Schema, read_ref_value};
60
61/// The bytes of a `f64` and of a `f32`.
62const F64_BYTES: usize = 8;
63const F32_BYTES: usize = 4;
64/// A record's length, reserved before its fields are known: one byte, the
65/// common case; a longer record moves its fields to make room.
66const LENGTH_RESERVED: usize = 1;
67/// The biggest length one varint byte holds.
68const ONE_BYTE_MAX: usize = 0x7F;
69/// Strings a reader keeps in itself before it needs the heap.
70const INLINE_STRINGS: usize = 16;
71
72/// `v` as a frame.
73pub fn to_frame<T: Schema>(v: &T) -> Vec<u8> {
74    let mut p = Packer::new();
75    v.pack(&mut p);
76    p.finish()
77}
78
79thread_local! {
80    /// This thread's packer, reused: a frame built without allocating once
81    /// its buffers have grown to the thread's messages.
82    static PACKER: std::cell::RefCell<Packer> = std::cell::RefCell::new(Packer::new());
83}
84
85/// `f` on `v` as a frame, packed in this thread's reused buffers (a fresh
86/// packer when they're busy — `f` packing another).
87pub fn with_frame<T: Schema, R>(v: &T, f: impl FnOnce(&[u8]) -> R) -> R {
88    PACKER.with(|cell| match cell.try_borrow_mut() {
89        Ok(mut p) => {
90            p.clear();
91            v.pack(&mut p);
92            let p = &mut *p;
93            p.seal();
94            let out = f(&p.out);
95            p.out.clear();
96            out
97        }
98        Err(_) => f(&to_frame(v)),
99    })
100}
101
102/// A `T` out of a frame; `None` if the frame isn't one.
103pub fn read_frame<T: Schema>(bytes: &[u8]) -> Option<T> {
104    let mut u = Unpacker::open(bytes)?;
105    let v = T::unpack(&mut u)?;
106    u.at_end().then_some(v)
107}
108
109/// Writes a frame.
110pub struct Packer {
111    out: Vec<u8>,
112    table: Vec<u8>,
113    strings: Interner,
114}
115
116impl Default for Packer {
117    fn default() -> Self {
118        Self::new()
119    }
120}
121
122impl Packer {
123    pub fn new() -> Self {
124        Packer {
125            out: Vec::with_capacity(START_BYTES),
126            table: Vec::new(),
127            strings: Interner::new(),
128        }
129    }
130
131    /// An unsigned number (a count, an index).
132    pub fn put_u(&mut self, n: u64) {
133        varint(&mut self.out, n);
134    }
135
136    pub fn put_i(&mut self, n: i64) {
137        varint(&mut self.out, zigzag(n));
138    }
139
140    pub fn put_bool(&mut self, b: bool) {
141        self.out.push(u8::from(b));
142    }
143
144    pub fn put_f64(&mut self, f: f64) {
145        self.out.extend_from_slice(&f.to_le_bytes());
146    }
147
148    pub fn put_f32(&mut self, f: f32) {
149        self.out.extend_from_slice(&f.to_le_bytes());
150    }
151
152    /// Floats, raw: a count, whether they're kept as `f32` (when every one
153    /// is one exactly — half the bytes), then each one's bytes.
154    pub fn put_f64s(&mut self, items: &[f64]) {
155        self.put_u(items.len() as u64);
156        let narrow = floats::fits_f32(items);
157        self.put_bool(narrow);
158        if narrow {
159            floats::put_narrow(&mut self.out, items);
160        } else {
161            floats::put_wide(&mut self.out, items);
162        }
163    }
164
165    /// Unsigned numbers bit-packed: a count, how many bits each takes (the
166    /// biggest one's, one at least), then the bits ([`lanes`]).
167    pub fn put_uints(&mut self, len: usize, items: impl Iterator<Item = u64> + Clone) {
168        self.put_u(len as u64);
169        let max = items.clone().fold(0, u64::max);
170        let bits = (u64::BITS - max.leading_zeros()).max(1);
171        self.out.push(bits as u8);
172        lanes::pack(&mut self.out, len, bits, items);
173    }
174
175    pub fn put_f32s(&mut self, items: &[f32]) {
176        self.put_u(items.len() as u64);
177        floats::put_f32s(&mut self.out, items);
178    }
179
180    /// A string: its index in the table, which gets it the first time.
181    pub fn put_str(&mut self, s: &str) {
182        let i = match self.strings.find(&self.table, s) {
183            Some(i) => i,
184            None => {
185                varint(&mut self.table, s.len() as u64);
186                let at = self.table.len();
187                self.table.extend_from_slice(s.as_bytes());
188                self.strings.add(&self.table, s, at)
189            }
190        };
191        self.put_u(i as u64);
192    }
193
194    pub fn put_bytes(&mut self, b: &[u8]) {
195        self.put_u(b.len() as u64);
196        self.out.extend_from_slice(b);
197    }
198
199    /// `v` in its binary form, as bytes: what a frame holds of a type that
200    /// doesn't pack itself.
201    pub fn put_tagged<T: Schema>(&mut self, v: &T) {
202        let mut w = Writer::body();
203        v.encode(&mut w);
204        self.put_bytes(&w.finish());
205    }
206
207    /// A record starts: where its length goes ([`Packer::end`] fills it in).
208    pub fn begin(&mut self) -> usize {
209        let at = self.out.len();
210        self.out.extend_from_slice(&[0; LENGTH_RESERVED]);
211        at
212    }
213
214    /// A list of `len` items packed one by one: its count, then its length
215    /// in bytes like a record's ([`Packer::end`] writes it), so a reader
216    /// can step over it.
217    pub fn begin_list(&mut self, len: usize) -> usize {
218        self.put_u(len as u64);
219        self.begin()
220    }
221
222    /// The record (or list) begun at `at` ends: its length written.
223    pub fn end(&mut self, at: usize) {
224        let len = self.out.len() - at - LENGTH_RESERVED;
225        if len <= ONE_BYTE_MAX {
226            self.out[at] = len as u8;
227            return;
228        }
229        let mut length = Vec::new();
230        varint(&mut length, len as u64);
231        self.out.splice(at..at + LENGTH_RESERVED, length);
232    }
233
234    /// The frame: its body, its strings, their length backwards.
235    pub fn finish(mut self) -> Vec<u8> {
236        self.seal();
237        self.out
238    }
239
240    /// The table and its length put after the body: `out` is the frame.
241    fn seal(&mut self) {
242        let table_len = self.table.len();
243        self.out.extend_from_slice(&self.table);
244        // The length written backwards: read from the frame's end.
245        let at = self.out.len();
246        varint(&mut self.out, table_len as u64);
247        self.out[at..].reverse();
248    }
249
250    /// Starts over, keeping its buffers.
251    fn clear(&mut self) {
252        self.out.clear();
253        self.table.clear();
254        self.strings.clear();
255    }
256}
257
258/// Reads a frame. Every read answers `None` for bytes that aren't what
259/// [`Packer`] writes there.
260pub struct Unpacker<'b> {
261    body: &'b [u8],
262    i: usize,
263    table: &'b [u8],
264    /// How far the table is read.
265    table_at: usize,
266    strings: Strings<'b>,
267    /// Strings handed out shared ([`Unpacker::get_shared_str`]), by table index:
268    /// a string a frame repeats is allocated once.
269    shared: Vec<Option<Arc<str>>>,
270    depth: usize,
271}
272
273/// The table's strings read so far: the first few in place, the rest on the heap.
274struct Strings<'b> {
275    inline: [&'b str; INLINE_STRINGS],
276    len: usize,
277    spill: Vec<&'b str>,
278}
279
280impl<'b> Strings<'b> {
281    fn new() -> Self {
282        Strings {
283            inline: [""; INLINE_STRINGS],
284            len: 0,
285            spill: Vec::new(),
286        }
287    }
288
289    fn len(&self) -> usize {
290        self.len
291    }
292
293    fn push(&mut self, s: &'b str) {
294        match self.inline.get_mut(self.len) {
295            Some(slot) => *slot = s,
296            None => self.spill.push(s),
297        }
298        self.len += 1;
299    }
300
301    fn get(&self, i: usize) -> Option<&'b str> {
302        if i >= self.len {
303            return None;
304        }
305        Some(if i < INLINE_STRINGS {
306            self.inline[i]
307        } else {
308            self.spill[i - INLINE_STRINGS]
309        })
310    }
311}
312
313impl<'b> Unpacker<'b> {
314    /// The frame's body and table found; the root value next.
315    pub fn open(bytes: &'b [u8]) -> Option<Self> {
316        let (table_len, trailer) = read_varint_back(bytes)?;
317        let body_end = bytes.len().checked_sub(trailer)?.checked_sub(table_len)?;
318        Some(Unpacker {
319            body: &bytes[..body_end],
320            i: 0,
321            table: &bytes[body_end..bytes.len() - trailer],
322            table_at: 0,
323            strings: Strings::new(),
324            shared: Vec::new(),
325            depth: 0,
326        })
327    }
328
329    /// Whether the body is read through.
330    pub fn at_end(&self) -> bool {
331        self.i == self.body.len()
332    }
333
334    fn take(&mut self, len: usize) -> Option<&'b [u8]> {
335        let end = self.i.checked_add(len).filter(|&e| e <= self.body.len())?;
336        let out = &self.body[self.i..end];
337        self.i = end;
338        Some(out)
339    }
340
341    pub fn get_u(&mut self) -> Option<u64> {
342        read_varint(self.body, &mut self.i)
343    }
344
345    pub fn get_i(&mut self) -> Option<i64> {
346        self.get_u().map(unzigzag)
347    }
348
349    pub fn get_bool(&mut self) -> Option<bool> {
350        match self.take(1)? {
351            [0] => Some(false),
352            [1] => Some(true),
353            _ => None,
354        }
355    }
356
357    pub fn get_f64(&mut self) -> Option<f64> {
358        Some(f64::from_le_bytes(self.take(F64_BYTES)?.try_into().ok()?))
359    }
360
361    pub fn get_f32(&mut self) -> Option<f32> {
362        Some(f32::from_le_bytes(self.take(F32_BYTES)?.try_into().ok()?))
363    }
364
365    /// A count of things each at least `min` bytes: no bigger than what's left.
366    fn count_of(&mut self, min: usize) -> Option<usize> {
367        let n = usize::try_from(self.get_u()?).ok()?;
368        (n <= (self.body.len() - self.i) / min.max(1)).then_some(n)
369    }
370
371    /// A list's count (every value takes a byte at least).
372    pub fn count(&mut self) -> Option<usize> {
373        self.count_of(1)
374    }
375
376    pub fn get_f64s(&mut self) -> Option<Vec<f64>> {
377        let (_, narrow, raw) = self.packed_floats(true)?;
378        Some(if narrow {
379            floats::widen(raw)
380        } else {
381            raw.as_chunks::<F64_BYTES>()
382                .0
383                .iter()
384                .map(|b| f64::from_le_bytes(*b))
385                .collect()
386        })
387    }
388
389    /// Bit-packed numbers ([`Packer::put_uints`]), each made a `T` by `of`.
390    pub fn get_uints<T>(&mut self, of: impl Fn(u64) -> Option<T>) -> Option<Vec<T>> {
391        let (n, bits, raw) = self.packed_ints()?;
392        let mut out = Vec::with_capacity(n);
393        let mut ok = true;
394        lanes::unpack(raw, n, bits, |v| match of(v) {
395            Some(t) if ok => out.push(t),
396            _ => ok = false,
397        });
398        ok.then_some(out)
399    }
400
401    pub fn get_f32s(&mut self) -> Option<Vec<f32>> {
402        let (_, _, raw) = self.packed_floats(false)?;
403        Some(
404            raw.as_chunks::<F32_BYTES>()
405                .0
406                .iter()
407                .map(|b| f32::from_le_bytes(*b))
408                .collect(),
409        )
410    }
411
412    /// A packed list of ints, unread: how many, bits each, their bytes.
413    pub fn packed_ints(&mut self) -> Option<(usize, u32, &'b [u8])> {
414        let n = usize::try_from(self.get_u()?).ok()?;
415        let bits = u32::from(*self.take(1)?.first()?);
416        if !(1..=u64::BITS).contains(&bits)
417            || n > (self.body.len() - self.i) * u8::BITS as usize / bits as usize
418        {
419            return None;
420        }
421        let bytes = self.take(lanes::packed_bytes(n, bits)?)?;
422        Some((n, bits, bytes))
423    }
424
425    /// A list of floats, unread: how many, whether they're `f32`s, their
426    /// bytes. A list of `f64` says whether it's narrowed (`wide`); one of
427    /// `f32` always is.
428    pub fn packed_floats(&mut self, wide: bool) -> Option<(usize, bool, &'b [u8])> {
429        let n = self.count()?;
430        let narrow = if wide { self.get_bool()? } else { true };
431        let width = if narrow { F32_BYTES } else { F64_BYTES };
432        Some((n, narrow, self.take(n.checked_mul(width)?)?))
433    }
434
435    /// A string, borrowed from the frame: the table read as far as it's needed.
436    pub fn get_str(&mut self) -> Option<&'b str> {
437        let i = self.string_index()?;
438        self.strings.get(i)
439    }
440
441    /// A string as an `Arc<str>`: one allocation for every time the frame has it.
442    pub fn get_shared_str(&mut self) -> Option<Arc<str>> {
443        let i = self.string_index()?;
444        if self.shared.len() <= i {
445            self.shared.resize(i + 1, None);
446        }
447        let s = self.strings.get(i)?;
448        Some(self.shared[i].get_or_insert_with(|| s.into()).clone())
449    }
450
451    /// A string's index, the table read up to it.
452    fn string_index(&mut self) -> Option<usize> {
453        let i = usize::try_from(self.get_u()?).ok()?;
454        while self.strings.len() <= i {
455            let len = usize::try_from(read_varint(self.table, &mut self.table_at)?).ok()?;
456            let end = self
457                .table_at
458                .checked_add(len)
459                .filter(|&e| e <= self.table.len())?;
460            let s = std::str::from_utf8(&self.table[self.table_at..end]).ok()?;
461            self.table_at = end;
462            self.strings.push(s);
463        }
464        Some(i)
465    }
466
467    pub fn get_bytes(&mut self) -> Option<&'b [u8]> {
468        let len = self.count()?;
469        self.take(len)
470    }
471
472    /// A value held in its binary form ([`Packer::put_tagged`]).
473    pub fn get_tagged<T: Schema>(&mut self) -> Option<T> {
474        let bytes = self.get_bytes()?;
475        let mut d = Decoder::body(bytes);
476        if let Some(v) = T::decode(&mut d)
477            && d.at_end()
478        {
479            return Some(v);
480        }
481        let arena = Bump::new();
482        let mut d = Decoder::body(bytes);
483        let v = d.value_ref(&arena).ok()?;
484        d.at_end().then_some(())?;
485        read_ref_value(&v).ok()
486    }
487
488    /// A record starts: where it ends.
489    pub fn begin(&mut self) -> Option<usize> {
490        let len = self.count()?;
491        self.depth += 1;
492        (self.depth <= MAX_DEPTH).then_some(self.i + len)
493    }
494
495    /// A list ([`Packer::begin_list`]): how many items, and where it ends.
496    pub fn list(&mut self) -> Option<(usize, usize)> {
497        let n = self.count()?;
498        let len = self.count()?;
499        Some((n, self.i + len))
500    }
501
502    /// Steps to `end` (a list's), past what's left of it.
503    pub fn skip_to(&mut self, end: usize) -> Option<()> {
504        (self.i <= end && end <= self.body.len()).then(|| self.i = end)
505    }
506
507    /// Where it's read up to.
508    pub fn position(&self) -> usize {
509        self.i
510    }
511
512    /// A reader of the same frame from `at`, with a string cache of its own.
513    pub fn fork(&self, at: usize) -> Unpacker<'b> {
514        Unpacker {
515            body: self.body,
516            i: at,
517            table: self.table,
518            table_at: 0,
519            strings: Strings::new(),
520            shared: Vec::new(),
521            depth: self.depth,
522        }
523    }
524
525    /// Whether the record ending at `end` has more fields.
526    pub fn more(&self, end: usize) -> bool {
527        self.i < end
528    }
529
530    /// The record ending at `end` is done: fields this reader doesn't know skipped.
531    pub fn leave(&mut self, end: usize) -> Option<()> {
532        self.depth -= 1;
533        (self.i <= end).then(|| self.i = end)
534    }
535}
536
537/// A varint written backwards at the end of `bytes`: its value, and how
538/// many bytes it took.
539fn read_varint_back(bytes: &[u8]) -> Option<(usize, usize)> {
540    let mut n = 0u64;
541    let mut shift = 0;
542    for (taken, b) in bytes.iter().rev().enumerate() {
543        if shift >= VARINT_MAX_SHIFT {
544            return None;
545        }
546        n |= (u64::from(*b) & VARINT_BITS) << shift;
547        if b & VARINT_MORE == 0 {
548            return Some((usize::try_from(n).ok()?, taken + 1));
549        }
550        shift += VARINT_SHIFT;
551    }
552    None
553}