Skip to main content

sva_engine/cache/
codec.rs

1// Concern: one stored node value, tagged with the store's format, and one staged run, as bytes and back | Non-concern: where the bytes live or when | IO: (Stored or Buffer) <-> bytes, a bad one None
2
3use sva_formula::Codomain;
4use sva_samples::{
5    Buffer, Cost, Detail, Dropped, Extent, Grid, Label, PSYCHOACOUSTIC_V1, Rule, Source,
6};
7
8use sva_formula::Hash;
9
10use super::Stored;
11use super::stored::{Header, Laid, Samples};
12
13/// Bumped by, and only by, a change to a stored value's bytes or to the key it is stored under.
14pub const STORE_FORMAT: u32 = 28;
15
16/// Every entry opens with its format, so one another format wrote is never read as a value,
17/// even where a wipe left it.
18fn entry_tag() -> Vec<u8> {
19    [&b"SVAh"[..], &STORE_FORMAT.to_le_bytes()].concat()
20}
21
22const STAGED_RUN: &[u8; 4] = b"SVAc";
23
24/// Samples per chunk of a run: what one checksum covers, and the least a read loads. Samples
25/// stay f64, as the render computes them: a stored value is read by others, and f32 would
26/// change the bits of every value that reads it.
27pub(crate) const CHUNK: usize = 4096;
28
29/// A header's length, then the header, then each run's samples chunk by chunk, every plane of
30/// a chunk together, so a stretch of a run is one read.
31pub(crate) fn entry(head: &Header, runs: &[Buffer]) -> Vec<u8> {
32    let mut laid = Vec::new();
33    let mut body = Vec::new();
34    for run in runs {
35        let mut sums = Vec::new();
36        let at = body.len() as u64;
37        for from in (0..run.len()).step_by(CHUNK) {
38            let to = (from + CHUNK).min(run.len());
39            let chunk = body.len();
40            for plane in &run.planes {
41                for sample in &plane[from..to] {
42                    word(&mut body, sample.to_bits());
43                }
44            }
45            sums.push(checksum(&body[chunk..]));
46        }
47        laid.push(Laid {
48            rate: run.rate,
49            start: run.start,
50            width: run.width,
51            len: run.len(),
52            at,
53            sums,
54        });
55    }
56    let head = sealed(header(head, &laid));
57    let mut out = Vec::with_capacity(8 + head.len() + body.len());
58    word(&mut out, head.len() as u64);
59    out.extend_from_slice(&head);
60    out.extend_from_slice(&body);
61    out
62}
63
64fn header(head: &Header, laid: &[Laid]) -> Vec<u8> {
65    let stored = head.stored();
66    let mut out = entry_tag();
67    word(&mut out, stored.key.0);
68    word(&mut out, stored.key.1);
69    word(&mut out, stored.identity.0);
70    word(&mut out, stored.identity.1);
71    labelled(&mut out, &stored.label);
72    out.push(stored.width);
73    out.push(match stored.codomain {
74        Codomain::Real => 0,
75        Codomain::Complex => 1,
76    });
77    maybe(&mut out, stored.rate.map(u64::from));
78    word(&mut out, u64::from(stored.grid.rate));
79    out.extend_from_slice(&stored.grid.a.to_le_bytes());
80    out.extend_from_slice(&stored.grid.d.to_le_bytes());
81    word(&mut out, stored.support.start as u64);
82    word(&mut out, stored.support.end as u64);
83    out.extend_from_slice(&stored.priced.to_le_bytes());
84    word(&mut out, stored.moved.to_bits());
85    out.push(u8::from(stored.readable));
86    out.push(u8::from(stored.sampled));
87    word(&mut out, stored.cuts.len() as u64);
88    for (cut, at) in &stored.cuts {
89        word(&mut out, cut.0);
90        word(&mut out, cut.1);
91        word(&mut out, *at as u64);
92    }
93    match head.samples() {
94        &Samples::Of { key, by } => {
95            out.push(1);
96            word(&mut out, key.0);
97            word(&mut out, key.1);
98            word(&mut out, by as u64);
99        }
100        _ => out.push(0),
101    }
102    word(&mut out, laid.len() as u64);
103    for run in laid {
104        word(&mut out, u64::from(run.rate));
105        word(&mut out, run.start as u64);
106        word(&mut out, run.width as u64);
107        word(&mut out, run.len as u64);
108        for sum in &run.sums {
109            word(&mut out, *sum);
110        }
111    }
112    out
113}
114
115/// How many of an entry's first bytes its header takes, off its first eight.
116pub(crate) fn head_len(first: &[u8]) -> Option<usize> {
117    let len = u64::from_le_bytes(first.get(..8)?.try_into().ok()?);
118    8usize.checked_add(usize::try_from(len).ok()?)
119}
120
121/// The meta an entry's header states, its samples laid in `file`, and the entry's length; a
122/// truncated, corrupt or foreign header is `None`, never a partial value.
123pub(crate) fn read_head(bytes: &[u8], file: Hash) -> Option<(Header, u64)> {
124    let span = head_len(bytes)?;
125    let mut r = Reader(opened(bytes.get(8..span)?, &entry_tag())?);
126    let key = Hash(r.word()?, r.word()?);
127    let identity = Hash(r.word()?, r.word()?);
128    let label = r.label()?;
129    let width = r.byte()?;
130    let codomain = match r.byte()? {
131        0 => Codomain::Real,
132        1 => Codomain::Complex,
133        _ => return None,
134    };
135    let rate = match r.maybe()? {
136        Some(rate) => Some(u32::try_from(rate).ok()?),
137        None => None,
138    };
139    let grid = Grid {
140        rate: u32::try_from(r.word()?).ok()?,
141        a: r.wide()? as i128,
142        d: r.wide()? as i128,
143    };
144    let (start, end) = (r.word()? as i64, r.word()? as i64);
145    let support = (start <= end).then(|| Extent::new(start, end))?;
146    let priced = r.wide()?;
147    let moved = f64::from_bits(r.word()?);
148    let readable = r.flag()?;
149    let sampled = r.flag()?;
150    let mut cuts = Vec::new();
151    for _ in 0..r.word()?.min(r.0.len() as u64) {
152        cuts.push((Hash(r.word()?, r.word()?), r.word()? as i64));
153    }
154    let of = match r.byte()? {
155        0 => None,
156        1 => Some(Samples::Of {
157            key: Hash(r.word()?, r.word()?),
158            by: r.word()? as i64,
159        }),
160        _ => return None,
161    };
162    let count = r.word()? as usize;
163    let mut runs = Vec::new();
164    let mut at = span as u64;
165    for _ in 0..count.min(r.0.len()) {
166        let rate = u32::try_from(r.word()?).ok()?;
167        let start = r.word()? as i64;
168        let (width, len) = (r.word()? as usize, r.word()? as usize);
169        let sums = (0..len.div_ceil(CHUNK).min(r.0.len()))
170            .map(|_| r.word())
171            .collect::<Option<Vec<u64>>>()?;
172        if sums.len() != len.div_ceil(CHUNK) {
173            return None;
174        }
175        runs.push(Laid {
176            rate,
177            start,
178            width,
179            len,
180            at,
181            sums,
182        });
183        at = at.checked_add((width.checked_mul(len)?.checked_mul(8)?) as u64)?;
184    }
185    let samples = match (of, runs.is_empty()) {
186        (Some(of), true) => of,
187        (Some(_), false) => return None,
188        (None, true) => Samples::None,
189        (None, false) => Samples::Entry {
190            file,
191            runs,
192            shift: 0,
193        },
194    };
195    let stored = Stored {
196        key,
197        identity,
198        label,
199        width,
200        codomain,
201        rate,
202        grid,
203        support,
204        priced,
205        moved,
206        readable,
207        sampled,
208        cuts,
209        held: Vec::new(),
210    };
211    let whole = runs_whole(count, &samples) && r.0.is_empty();
212    whole.then(|| (Header::new(stored, samples), at))
213}
214
215fn runs_whole(count: usize, samples: &Samples) -> bool {
216    match samples {
217        Samples::Entry { runs, .. } => runs.len() == count,
218        _ => count == 0,
219    }
220}
221
222/// Where in its file a run's chunks `from..to` lie, and how many bytes.
223pub(crate) fn span_of(run: &Laid, from: usize, to: usize) -> (u64, u64) {
224    let bytes = |chunks: usize| ((chunks * CHUNK).min(run.len) * run.width * 8) as u64;
225    (run.at + bytes(from), bytes(to) - bytes(from))
226}
227
228/// A run's chunks `from..to` off the bytes `span_of` names; `None` where a checksum fails.
229pub(crate) fn read_chunks(bytes: &[u8], run: &Laid, from: usize, to: usize) -> Option<Buffer> {
230    let first = from * CHUNK;
231    let len = (to * CHUNK).min(run.len).checked_sub(first)?;
232    if bytes.len() != len * run.width * 8 {
233        return None;
234    }
235    let mut planes = vec![Vec::with_capacity(len); run.width];
236    let mut at = 0;
237    for (k, sum) in run.sums[from..to].iter().enumerate() {
238        let n = (CHUNK).min(run.len - first - k * CHUNK);
239        let chunk = &bytes[at..at + n * run.width * 8];
240        if checksum(chunk) != *sum {
241            return None;
242        }
243        for (c, plane) in planes.iter_mut().enumerate() {
244            let own = &chunk[c * n * 8..(c + 1) * n * 8];
245            plane.extend(
246                own.chunks_exact(8).map(|b| {
247                    f64::from_bits(u64::from_le_bytes(b.try_into().expect("eight bytes")))
248                }),
249            );
250        }
251        at += chunk.len();
252    }
253    let mut out = Buffer::of_planes(run.rate, planes);
254    out.start = run.start + first as i64;
255    Some(out)
256}
257
258/// Every run an entry under `key` lays out; `None` where any is corrupt.
259pub(crate) fn read_runs(bytes: &[u8], key: Hash) -> Option<Vec<Buffer>> {
260    let (head, _) = read_head(bytes, key)?;
261    let Samples::Entry { runs, .. } = head.samples() else {
262        return Some(Vec::new());
263    };
264    let read = |run: &Laid| {
265        let n = run.len.div_ceil(CHUNK);
266        let (at, len) = span_of(run, 0, n);
267        let span = bytes.get(at as usize..(at + len) as usize)?;
268        read_chunks(span, run, 0, n)
269    };
270    runs.iter().map(read).collect()
271}
272
273pub(crate) fn chunk(samples: &Buffer) -> Vec<u8> {
274    let mut out = STAGED_RUN.to_vec();
275    segments(&mut out, std::slice::from_ref(samples));
276    sealed(out)
277}
278
279pub(crate) fn read_chunk(bytes: &[u8]) -> Option<Buffer> {
280    let mut r = Reader(opened(bytes, STAGED_RUN)?);
281    let mut parts = r.segments()?;
282    (r.0.is_empty() && parts.len() == 1).then(|| parts.remove(0))
283}
284
285fn sealed(mut out: Vec<u8>) -> Vec<u8> {
286    let sum = checksum(&out);
287    word(&mut out, sum);
288    out
289}
290
291/// The body past `magic`, where the checksum holds.
292fn opened<'b>(bytes: &'b [u8], magic: &[u8]) -> Option<&'b [u8]> {
293    let body = bytes.len().checked_sub(8)?;
294    let (body, sum) = bytes.split_at(body);
295    (checksum(body).to_le_bytes() == sum && body.starts_with(magic)).then(|| &body[magic.len()..])
296}
297
298fn segments(out: &mut Vec<u8>, parts: &[Buffer]) {
299    word(out, parts.len() as u64);
300    for part in parts {
301        word(out, u64::from(part.rate));
302        word(out, part.start as u64);
303        word(out, part.width as u64);
304        word(out, part.len() as u64);
305        for plane in &part.planes {
306            for sample in plane {
307                word(out, sample.to_bits());
308            }
309        }
310    }
311}
312
313fn checksum(bytes: &[u8]) -> u64 {
314    bytes.iter().fold(0xcbf2_9ce4_8422_2325, |h, b| {
315        (h ^ u64::from(*b)).wrapping_mul(0x0100_0000_01b3)
316    })
317}
318
319fn word(out: &mut Vec<u8>, w: u64) {
320    out.extend_from_slice(&w.to_le_bytes());
321}
322
323fn float(out: &mut Vec<u8>, v: Option<f64>) {
324    maybe(out, v.map(f64::to_bits));
325}
326
327fn maybe(out: &mut Vec<u8>, w: Option<u64>) {
328    match w {
329        None => out.push(0),
330        Some(w) => {
331            out.push(1);
332            word(out, w);
333        }
334    }
335}
336
337fn text(out: &mut Vec<u8>, s: &str) {
338    word(out, s.len() as u64);
339    out.extend_from_slice(s.as_bytes());
340}
341
342fn labelled(out: &mut Vec<u8>, label: &Label) {
343    out.push(match label.source {
344        Source::Exact => 0,
345        Source::Measured => 1,
346    });
347    text(out, label.profile);
348    word(out, u64::from(label.rate));
349    detailed(out, &label.detail);
350    match label.cost {
351        None => out.push(0),
352        Some(Cost { flops, budget }) => {
353            out.push(1);
354            out.extend_from_slice(&flops.to_le_bytes());
355            out.extend_from_slice(&budget.to_le_bytes());
356        }
357    }
358    float(out, label.moved);
359    match &label.pruned {
360        None => out.push(0),
361        Some(pruned) => {
362            out.push(1);
363            word(out, pruned.db.to_bits());
364            word(out, pruned.cuts.len() as u64);
365            for (node, at) in &pruned.cuts {
366                text(out, node);
367                word(out, *at as u64);
368            }
369        }
370    }
371}
372
373fn detailed(out: &mut Vec<u8>, detail: &Detail) {
374    let rule = |out: &mut Vec<u8>, rule: &Rule| text(out, rule.as_str());
375    match detail {
376        Detail::Lines {
377            rule: r,
378            placed,
379            summed,
380            dropped,
381            dropped_more,
382            terms,
383            tail_db,
384        } => {
385            out.push(0);
386            rule(out, r);
387            word(out, *placed as u64);
388            word(out, *summed as u64);
389            word(out, dropped.len() as u64);
390            for d in dropped {
391                word(out, d.hz.to_bits());
392                word(out, d.db.to_bits());
393            }
394            word(out, *dropped_more as u64);
395            maybe(out, terms.map(|t| t as u64));
396            float(out, *tail_db);
397        }
398        Detail::Continuous { rule: r } => {
399            out.push(1);
400            rule(out, r);
401        }
402        Detail::Cropped { rule: r, tail_db } => {
403            out.push(2);
404            rule(out, r);
405            float(out, *tail_db);
406        }
407        Detail::Point {
408            rule: r,
409            alias_db,
410            tail_db,
411        } => {
412            out.push(3);
413            rule(out, r);
414            float(out, *alias_db);
415            float(out, *tail_db);
416        }
417        Detail::Spectrum { rule: r, wrap_db } => {
418            out.push(4);
419            rule(out, r);
420            word(out, wrap_db.to_bits());
421        }
422        Detail::Roundtrip { rule: r, edited } => {
423            out.push(5);
424            rule(out, r);
425            out.push(u8::from(*edited));
426        }
427        Detail::Reading { rule: r } => {
428            out.push(6);
429            rule(out, r);
430        }
431        Detail::Added { parts } => {
432            out.push(7);
433            word(out, parts.len() as u64);
434            for part in parts {
435                detailed(out, part);
436            }
437        }
438    }
439}
440
441struct Reader<'b>(&'b [u8]);
442
443impl Reader<'_> {
444    fn take(&mut self, n: usize) -> Option<&[u8]> {
445        if self.0.len() < n {
446            return None;
447        }
448        let (head, rest) = self.0.split_at(n);
449        self.0 = rest;
450        Some(head)
451    }
452
453    fn byte(&mut self) -> Option<u8> {
454        self.take(1).map(|b| b[0])
455    }
456
457    fn flag(&mut self) -> Option<bool> {
458        match self.byte()? {
459            0 => Some(false),
460            1 => Some(true),
461            _ => None,
462        }
463    }
464
465    fn word(&mut self) -> Option<u64> {
466        Some(u64::from_le_bytes(self.take(8)?.try_into().ok()?))
467    }
468
469    fn segments(&mut self) -> Option<Vec<Buffer>> {
470        let count = self.word()? as usize;
471        let mut parts = Vec::new();
472        for _ in 0..count.min(self.0.len()) {
473            let rate = u32::try_from(self.word()?).ok()?;
474            let start = self.word()? as i64;
475            let (width, len) = (self.word()? as usize, self.word()? as usize);
476            if width.saturating_mul(len).saturating_mul(8) > self.0.len() {
477                return None;
478            }
479            let planes = (0..width)
480                .map(|_| (0..len).map(|_| self.word().map(f64::from_bits)).collect())
481                .collect::<Option<Vec<Vec<f64>>>>()?;
482            let mut part = Buffer::of_planes(rate, planes);
483            part.start = start;
484            parts.push(part);
485        }
486        (parts.len() == count).then_some(parts)
487    }
488
489    fn wide(&mut self) -> Option<u128> {
490        Some(u128::from_le_bytes(self.take(16)?.try_into().ok()?))
491    }
492
493    fn maybe(&mut self) -> Option<Option<u64>> {
494        match self.byte()? {
495            0 => Some(None),
496            1 => Some(Some(self.word()?)),
497            _ => None,
498        }
499    }
500
501    fn float(&mut self) -> Option<Option<f64>> {
502        Some(self.maybe()?.map(f64::from_bits))
503    }
504
505    fn text(&mut self) -> Option<&str> {
506        let len = self.word()? as usize;
507        std::str::from_utf8(self.take(len)?).ok()
508    }
509
510    fn rule(&mut self) -> Option<Rule> {
511        Rule::named(self.text()?)
512    }
513
514    fn label(&mut self) -> Option<Label> {
515        let source = match self.byte()? {
516            0 => Source::Exact,
517            1 => Source::Measured,
518            _ => return None,
519        };
520        let profile = match self.text()? {
521            name if name == PSYCHOACOUSTIC_V1.name => PSYCHOACOUSTIC_V1.name,
522            _ => return None,
523        };
524        let rate = u32::try_from(self.word()?).ok()?;
525        let detail = self.detail()?;
526        let cost = match self.byte()? {
527            0 => None,
528            1 => Some(Cost {
529                flops: self.wide()?,
530                budget: self.wide()?,
531            }),
532            _ => return None,
533        };
534        let moved = self.float()?;
535        let pruned = match self.byte()? {
536            0 => None,
537            1 => {
538                let db = f64::from_bits(self.word()?);
539                let count = usize::try_from(self.word()?).ok()?;
540                let cuts = (0..count)
541                    .map(|_| Some((self.text()?.to_string(), self.word()? as i64)))
542                    .collect::<Option<Vec<_>>>()?;
543                Some(sva_samples::Pruned { db, cuts })
544            }
545            _ => return None,
546        };
547        Some(Label {
548            source,
549            profile,
550            rate,
551            detail,
552            cost,
553            moved,
554            pruned,
555        })
556    }
557
558    fn detail(&mut self) -> Option<Detail> {
559        Some(match self.byte()? {
560            0 => {
561                let rule = self.rule()?;
562                let (placed, summed) = (self.word()? as usize, self.word()? as usize);
563                let count = self.word()? as usize;
564                let dropped = (0..count.min(self.0.len()))
565                    .map(|_| {
566                        Some(Dropped {
567                            hz: f64::from_bits(self.word()?),
568                            db: f64::from_bits(self.word()?),
569                        })
570                    })
571                    .collect::<Option<Vec<_>>>()?;
572                if dropped.len() != count {
573                    return None;
574                }
575                Detail::Lines {
576                    rule,
577                    placed,
578                    summed,
579                    dropped,
580                    dropped_more: self.word()? as usize,
581                    terms: self.maybe()?.map(|t| t as usize),
582                    tail_db: self.float()?,
583                }
584            }
585            1 => Detail::Continuous { rule: self.rule()? },
586            2 => Detail::Cropped {
587                rule: self.rule()?,
588                tail_db: self.float()?,
589            },
590            3 => Detail::Point {
591                rule: self.rule()?,
592                alias_db: self.float()?,
593                tail_db: self.float()?,
594            },
595            4 => Detail::Spectrum {
596                rule: self.rule()?,
597                wrap_db: f64::from_bits(self.word()?),
598            },
599            5 => Detail::Roundtrip {
600                rule: self.rule()?,
601                edited: match self.byte()? {
602                    0 => false,
603                    1 => true,
604                    _ => return None,
605                },
606            },
607            6 => Detail::Reading { rule: self.rule()? },
608            7 => {
609                let count = self.word()? as usize;
610                let parts = (0..count.min(self.0.len()))
611                    .map(|_| self.detail())
612                    .collect::<Option<Vec<_>>>()?;
613                if parts.len() != count {
614                    return None;
615                }
616                Detail::Added { parts }
617            }
618            _ => return None,
619        })
620    }
621}