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