Skip to main content

sva_engine/
cache.rs

1// Concern: declares what a store holds under a content hash and how a key is built | Non-concern: any one store's medium and budget (disk.rs, memory.rs) | IO: (Hash) -> a payload + traces
2
3mod disk;
4mod evict;
5mod label;
6mod memory;
7
8pub use disk::{DiskCache, IO_NANOS_PER_BYTE};
9pub use memory::MemoryCache;
10pub use sva_formula::Hash;
11
12use std::path::Path;
13use std::time::Duration;
14
15use sva_formula::SpectralSum;
16use sva_samples::{Buffer, FilterTrace, Frames, Label};
17
18/// A spectral sum, one collapse of it, or one analysis of that collapse.
19#[derive(Clone, Debug, PartialEq)]
20pub enum Payload {
21    Samples(Box<Buffer>),
22    Frames(Box<Frames>),
23    Symbolic(Box<SpectralSum>),
24}
25
26#[derive(Clone, Copy, Debug, PartialEq, Eq)]
27pub enum PayloadKind {
28    Samples,
29    Frames,
30    Symbolic,
31}
32
33/// An entry not matching this is a miss, never a coercion.
34#[derive(Clone, Copy, Debug, PartialEq, Eq)]
35pub enum Expected {
36    Samples {
37        rate: u32,
38        width: usize,
39        samples: usize,
40    },
41    Frames,
42    Symbolic,
43}
44
45#[derive(Clone, Debug, PartialEq)]
46pub struct Entry {
47    pub payload: Payload,
48    pub traces: Vec<FilterTrace>,
49    /// FORMAT 9.3: the label is part of the value, so a hit answers with the cold run's.
50    pub label: Option<Label>,
51}
52
53impl Payload {
54    pub fn kind(&self) -> PayloadKind {
55        match self {
56            Payload::Samples(_) => PayloadKind::Samples,
57            Payload::Frames(_) => PayloadKind::Frames,
58            Payload::Symbolic(_) => PayloadKind::Symbolic,
59        }
60    }
61
62    pub fn samples(&self) -> Option<&Buffer> {
63        match self {
64            Payload::Samples(buffer) => Some(buffer),
65            _ => None,
66        }
67    }
68
69    pub fn symbolic(&self) -> Option<&SpectralSum> {
70        match self {
71            Payload::Symbolic(sum) => Some(sum),
72            _ => None,
73        }
74    }
75
76    pub fn bytes(&self) -> usize {
77        match self {
78            Payload::Samples(b) => b.len() * b.width * size_of::<f64>(),
79            Payload::Frames(f) => f.width * f.frames * f.bins * 2 * size_of::<f64>(),
80            Payload::Symbolic(n) => {
81                size_of::<SpectralSum>()
82                    + n.lanes
83                        .iter()
84                        .map(|lane| {
85                            lane.atoms.len() * size_of::<sva_formula::SpectralAtom>()
86                                + (lane.series.len() + lane.modal.len()) * size_of::<SpectralSum>()
87                        })
88                        .sum::<usize>()
89            }
90        }
91    }
92
93    pub fn answers(&self, expected: Expected) -> bool {
94        match (self, expected) {
95            (
96                Payload::Samples(b),
97                Expected::Samples {
98                    rate,
99                    width,
100                    samples,
101                },
102            ) => b.rate == rate && b.width == width && b.len() == samples,
103            (Payload::Frames(_), Expected::Frames) => true,
104            (Payload::Symbolic(_), Expected::Symbolic) => true,
105            _ => false,
106        }
107    }
108}
109
110/// What a render spent on one value. `wasm32-unknown-unknown` has no clock at all, so a
111/// render there measures nothing.
112#[derive(Clone, Copy)]
113pub struct Cost {
114    #[cfg(not(target_arch = "wasm32"))]
115    began: std::time::Instant,
116}
117
118impl Cost {
119    pub fn begun() -> Cost {
120        Cost {
121            #[cfg(not(target_arch = "wasm32"))]
122            began: std::time::Instant::now(),
123        }
124    }
125
126    pub fn elapsed(self) -> Duration {
127        #[cfg(not(target_arch = "wasm32"))]
128        return self.began.elapsed();
129        #[cfg(target_arch = "wasm32")]
130        return Duration::ZERO;
131    }
132}
133
134/// A hash carries the table version, so a bump retires every symbolic entry.
135pub fn symbolic_key(src: Hash) -> Hash {
136    mixed(src, &[0x73_79_6d_62_6f_6c_69_63])
137}
138
139/// The buffer's own key and the window it was read through.
140pub fn frames_key(buffer: Hash, window: usize, hop: usize) -> Hash {
141    mixed(
142        buffer,
143        &[window as u64, hop as u64, 0x66_72_61_6d_65_73_00_01],
144    )
145}
146
147/// One closed form at one rate, origin and width, scored or not: the label is part of the value.
148pub fn buffer_key(
149    symbolic: Hash,
150    rate: u32,
151    origin_secs: f64,
152    width: usize,
153    score: sva_samples::AliasScore,
154) -> Hash {
155    mixed(
156        symbolic,
157        &[
158            u64::from(rate),
159            origin_secs.to_bits(),
160            width as u64,
161            u64::from(score == sva_samples::AliasScore::Asked),
162            0x62_75_66_66_65_72_00_01,
163        ],
164    )
165}
166
167const ADDRESS_ROTATE: u32 = 17;
168
169fn mixed(seed: Hash, parts: &[u64]) -> Hash {
170    let mut lanes = sva_formula::Lanes::<ADDRESS_ROTATE>::from(seed);
171    for part in parts {
172        lanes.word(*part);
173    }
174    lanes.finish()
175}
176
177pub trait Cache: Sync {
178    fn load(&self, key: Hash, node: &str, expected: Expected) -> Option<Entry>;
179
180    fn store(&self, key: Hash, payload: &Payload, traces: &[FilterTrace], label: Option<&Label>);
181
182    fn holds(&self, key: Hash) -> bool;
183
184    fn worth_storing(&self, cost: Duration, bytes: usize, kind: PayloadKind) -> bool;
185
186    fn sweep(&self);
187
188    fn held_bytes(&self) -> u64;
189
190    fn evicted_bytes(&self) -> u64;
191
192    fn faults(&self) -> u64 {
193        0
194    }
195
196    fn max_bytes(&self) -> u64;
197
198    fn dir(&self) -> Option<&Path> {
199        None
200    }
201}