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