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