Skip to main content

sva_engine/
cache.rs

1// Concern: declares what a store holds under a content hash and how a value's key is built | Non-concern: what the store keeps and evicts (store.rs) | IO: (Hash) -> a payload
2
3mod codec;
4pub(crate) mod log;
5mod persist;
6mod stats;
7mod store;
8
9pub(crate) use persist::node_key;
10pub use persist::{
11    Backend, DEFAULT_STORE_BYTES, NoStore, Persisted, STORE_VERSION, Store, Stored, Through,
12    VERSION_NAME,
13};
14pub(crate) use stats::Recording;
15pub use stats::{CacheStats, Lookup, Outcome};
16pub(crate) use store::joined;
17pub use store::{Cache, CachePolicy, DEFAULT_CACHE_BYTES, DEFAULT_MARK_EVERY, PrunePolicy};
18pub use sva_formula::Hash;
19
20use std::collections::BTreeMap;
21
22use sva_samples::{Buffer, Frames, Label, MachineState};
23
24/// A value's segments, a stateful value's run, or one analysis of a value.
25#[derive(Clone, Debug, PartialEq)]
26pub enum Payload {
27    Segments(Vec<Buffer>),
28    Frames(Box<Frames>),
29    Run(Box<Run>),
30}
31
32#[derive(Clone, Copy, Debug, PartialEq, Eq)]
33pub enum PayloadKind {
34    Segments,
35    Frames,
36    Run,
37}
38
39/// One segment of a run: samples, marked states, and the segment before it.
40#[derive(Clone)]
41pub struct Run {
42    pub samples: Buffer,
43    pub marks: BTreeMap<i64, MachineState>,
44    pub parent: Option<Hash>,
45}
46
47impl Run {
48    pub fn end(&self) -> i64 {
49        self.samples.extent().end
50    }
51
52    pub fn bytes(&self) -> usize {
53        let marks: usize = self.marks.values().map(MachineState::bytes).sum();
54        self.samples.len() * self.samples.width * size_of::<f64>() + marks
55    }
56}
57
58/// The state is the node's own where its samples end, so the samples decide.
59impl PartialEq for Run {
60    fn eq(&self, other: &Run) -> bool {
61        self.samples == other.samples
62    }
63}
64
65impl std::fmt::Debug for Run {
66    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
67        f.debug_struct("Run")
68            .field("samples", &self.samples.extent())
69            .finish()
70    }
71}
72
73/// An entry not matching this is a miss, never a coercion.
74#[derive(Clone, Copy, Debug, PartialEq, Eq)]
75pub enum Expected {
76    Segments { rate: u32, width: usize },
77    Frames,
78    Run { rate: u32, width: usize },
79}
80
81#[derive(Clone, Debug, PartialEq)]
82pub struct Entry {
83    pub payload: Payload,
84    /// FORMAT 9.3: the label is part of the value, so a hit answers with the cold run's.
85    pub label: Option<Label>,
86}
87
88impl Payload {
89    pub fn run(self) -> Option<Run> {
90        match self {
91            Payload::Run(run) => Some(*run),
92            _ => None,
93        }
94    }
95
96    pub fn bytes(&self) -> usize {
97        match self {
98            Payload::Segments(parts) => parts
99                .iter()
100                .map(|b| b.len() * b.width * size_of::<f64>())
101                .sum(),
102            Payload::Frames(f) => f.width * f.frames * f.bins * 2 * size_of::<f64>(),
103            Payload::Run(run) => run.bytes(),
104        }
105    }
106
107    pub fn answers(&self, expected: Expected) -> bool {
108        match (self, expected) {
109            (Payload::Segments(parts), Expected::Segments { rate, width }) => {
110                parts.iter().all(|b| b.rate == rate && b.width == width)
111            }
112            (Payload::Frames(_), Expected::Frames) => true,
113            (Payload::Run(run), Expected::Run { rate, width }) => {
114                run.samples.rate == rate && run.samples.width == width
115            }
116            _ => false,
117        }
118    }
119}
120
121pub fn frames_key(value: Hash, window: usize, hop: usize) -> Hash {
122    mixed(
123        value,
124        &[window as u64, hop as u64, 0x66_72_61_6d_65_73_00_01],
125    )
126}
127
128/// One value at one rate, width and profile, never where a reader places it.
129pub fn value_key(
130    identity: Hash,
131    step: (i128, i128),
132    rate: u32,
133    width: usize,
134    profile: &sva_samples::Profile,
135) -> Hash {
136    mixed(
137        identity,
138        &[
139            step.0 as u64,
140            step.1 as u64,
141            u64::from(rate),
142            width as u64,
143            profile.precision_bits as u64,
144            profile.ceiling_hz.to_bits(),
145            0x76_61_6c_75_65_00_00_01,
146        ],
147    )
148}
149
150const ADDRESS_ROTATE: u32 = 17;
151
152pub(crate) fn mixed(seed: Hash, parts: &[u64]) -> Hash {
153    let mut lanes = sva_formula::Lanes::<ADDRESS_ROTATE>::from(seed);
154    for part in parts {
155        lanes.word(*part);
156    }
157    lanes.finish()
158}