sva-engine 0.2.0

Renders a resolved graph into per-node buffers a query can be asked of
Documentation
// 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

mod disk;
mod evict;
mod label;
mod memory;

pub use disk::{DiskCache, IO_NANOS_PER_BYTE};
pub use memory::MemoryCache;
pub use sva_formula::Hash;

use std::path::Path;
use std::time::Duration;

use sva_formula::SpectralSum;
use sva_samples::{Buffer, FilterTrace, Frames, Label};

/// A spectral sum, one collapse of it, or one analysis of that collapse.
#[derive(Clone, Debug, PartialEq)]
pub enum Payload {
    Samples(Box<Buffer>),
    Frames(Box<Frames>),
    Symbolic(Box<SpectralSum>),
}

#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum PayloadKind {
    Samples,
    Frames,
    Symbolic,
}

/// An entry not matching this is a miss, never a coercion.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Expected {
    Samples {
        rate: u32,
        width: usize,
        samples: usize,
    },
    Frames,
    Symbolic,
}

#[derive(Clone, Debug, PartialEq)]
pub struct Entry {
    pub payload: Payload,
    pub traces: Vec<FilterTrace>,
    /// FORMAT 9.3: the label is part of the value, so a hit answers with the cold run's.
    pub label: Option<Label>,
}

impl Payload {
    pub fn kind(&self) -> PayloadKind {
        match self {
            Payload::Samples(_) => PayloadKind::Samples,
            Payload::Frames(_) => PayloadKind::Frames,
            Payload::Symbolic(_) => PayloadKind::Symbolic,
        }
    }

    pub fn samples(&self) -> Option<&Buffer> {
        match self {
            Payload::Samples(buffer) => Some(buffer),
            _ => None,
        }
    }

    pub fn symbolic(&self) -> Option<&SpectralSum> {
        match self {
            Payload::Symbolic(sum) => Some(sum),
            _ => None,
        }
    }

    pub fn bytes(&self) -> usize {
        match self {
            Payload::Samples(b) => b.len() * b.width * size_of::<f64>(),
            Payload::Frames(f) => f.width * f.frames * f.bins * 2 * size_of::<f64>(),
            Payload::Symbolic(n) => {
                size_of::<SpectralSum>()
                    + n.lanes
                        .iter()
                        .map(|lane| {
                            lane.atoms.len() * size_of::<sva_formula::SpectralAtom>()
                                + (lane.series.len() + lane.modal.len()) * size_of::<SpectralSum>()
                        })
                        .sum::<usize>()
            }
        }
    }

    pub fn answers(&self, expected: Expected) -> bool {
        match (self, expected) {
            (
                Payload::Samples(b),
                Expected::Samples {
                    rate,
                    width,
                    samples,
                },
            ) => b.rate == rate && b.width == width && b.len() == samples,
            (Payload::Frames(_), Expected::Frames) => true,
            (Payload::Symbolic(_), Expected::Symbolic) => true,
            _ => false,
        }
    }
}

/// What a render spent on one value. `wasm32-unknown-unknown` has no clock at all, so a
/// render there measures nothing.
#[derive(Clone, Copy)]
pub struct Cost {
    #[cfg(not(target_arch = "wasm32"))]
    began: std::time::Instant,
}

impl Cost {
    pub fn begun() -> Cost {
        Cost {
            #[cfg(not(target_arch = "wasm32"))]
            began: std::time::Instant::now(),
        }
    }

    pub fn elapsed(self) -> Duration {
        #[cfg(not(target_arch = "wasm32"))]
        return self.began.elapsed();
        #[cfg(target_arch = "wasm32")]
        return Duration::ZERO;
    }
}

/// A hash carries the table version, so a bump retires every symbolic entry.
pub fn symbolic_key(src: Hash) -> Hash {
    mixed(src, &[0x73_79_6d_62_6f_6c_69_63])
}

/// The buffer's own key and the window it was read through.
pub fn frames_key(buffer: Hash, window: usize, hop: usize) -> Hash {
    mixed(
        buffer,
        &[window as u64, hop as u64, 0x66_72_61_6d_65_73_00_01],
    )
}

/// One closed form at one rate, origin and width, scored or not: the label is part of the value.
pub fn buffer_key(
    symbolic: Hash,
    rate: u32,
    origin_secs: f64,
    width: usize,
    score: sva_samples::AliasScore,
) -> Hash {
    mixed(
        symbolic,
        &[
            u64::from(rate),
            origin_secs.to_bits(),
            width as u64,
            u64::from(score == sva_samples::AliasScore::Asked),
            0x62_75_66_66_65_72_00_01,
        ],
    )
}

const ADDRESS_ROTATE: u32 = 17;

fn mixed(seed: Hash, parts: &[u64]) -> Hash {
    let mut lanes = sva_formula::Lanes::<ADDRESS_ROTATE>::from(seed);
    for part in parts {
        lanes.word(*part);
    }
    lanes.finish()
}

pub trait Cache: Sync {
    fn load(&self, key: Hash, node: &str, expected: Expected) -> Option<Entry>;

    fn store(&self, key: Hash, payload: &Payload, traces: &[FilterTrace], label: Option<&Label>);

    fn holds(&self, key: Hash) -> bool;

    fn worth_storing(&self, cost: Duration, bytes: usize, kind: PayloadKind) -> bool;

    fn sweep(&self);

    fn held_bytes(&self) -> u64;

    fn evicted_bytes(&self) -> u64;

    fn faults(&self) -> u64 {
        0
    }

    fn max_bytes(&self) -> u64;

    fn dir(&self) -> Option<&Path> {
        None
    }
}