Skip to main content

skippy_cache/payload/
bytes.rs

1use std::{borrow::Cow, sync::Arc, time::Instant};
2
3use anyhow::{Result, anyhow};
4
5#[derive(Debug, Clone)]
6pub struct CacheBytes {
7    pub(super) len: u64,
8    pub(super) repr: CacheBytesRepr,
9}
10
11#[derive(Debug, Clone)]
12pub(super) enum CacheBytesRepr {
13    Inline(Arc<Vec<u8>>),
14    Blocks(Arc<[CacheBlockRef]>),
15}
16
17#[derive(Debug, Clone)]
18pub(super) struct CacheBlockRef {
19    pub(super) hash: String,
20    pub(super) bytes: Arc<Vec<u8>>,
21}
22
23impl CacheBlockRef {
24    pub(super) fn new(hash: String, bytes: Arc<Vec<u8>>) -> Self {
25        Self { hash, bytes }
26    }
27}
28
29impl CacheBytes {
30    pub fn inline(bytes: Vec<u8>) -> Self {
31        Self {
32            len: bytes.len() as u64,
33            repr: CacheBytesRepr::Inline(Arc::new(bytes)),
34        }
35    }
36
37    pub(super) fn blocks(len: u64, blocks: Vec<CacheBlockRef>) -> Self {
38        Self {
39            len,
40            repr: CacheBytesRepr::Blocks(blocks.into()),
41        }
42    }
43
44    pub fn len(&self) -> u64 {
45        self.len
46    }
47
48    pub fn is_empty(&self) -> bool {
49        self.len == 0
50    }
51
52    pub fn as_cow(&self) -> Result<Cow<'_, [u8]>> {
53        match &self.repr {
54            CacheBytesRepr::Inline(bytes) => Ok(Cow::Borrowed(bytes.as_slice())),
55            CacheBytesRepr::Blocks(blocks) => {
56                let capacity = usize::try_from(self.len)
57                    .map_err(|_| anyhow!("cache payload too large to reconstruct"))?;
58                let mut out = Vec::with_capacity(capacity);
59                for block in blocks.iter() {
60                    out.extend_from_slice(block.bytes.as_slice());
61                }
62                if out.len() as u64 != self.len {
63                    return Err(anyhow!(
64                        "cache payload reconstruction length mismatch: expected {} got {}",
65                        self.len,
66                        out.len()
67                    ));
68                }
69                Ok(Cow::Owned(out))
70            }
71        }
72    }
73
74    pub fn as_cow_timed(&self) -> Result<(Cow<'_, [u8]>, CacheBytesReconstructStats)> {
75        let started = Instant::now();
76        let blocks = self.block_ref_count();
77        let bytes = self.as_cow()?;
78        Ok((
79            bytes,
80            CacheBytesReconstructStats {
81                reconstruct_ms: started.elapsed().as_secs_f64() * 1000.0,
82                reconstruct_bytes: self.len,
83                reconstruct_blocks: blocks,
84            },
85        ))
86    }
87
88    fn block_ref_count(&self) -> usize {
89        match &self.repr {
90            CacheBytesRepr::Inline(_) => 0,
91            CacheBytesRepr::Blocks(blocks) => blocks.len(),
92        }
93    }
94
95    pub(super) fn block_hashes(&self) -> impl Iterator<Item = &str> {
96        match &self.repr {
97            CacheBytesRepr::Inline(_) => CacheBlockHashIter::Empty,
98            CacheBytesRepr::Blocks(blocks) => CacheBlockHashIter::Blocks {
99                iter: blocks.iter(),
100            },
101        }
102    }
103}
104
105enum CacheBlockHashIter<'a> {
106    Empty,
107    Blocks {
108        iter: std::slice::Iter<'a, CacheBlockRef>,
109    },
110}
111
112impl<'a> Iterator for CacheBlockHashIter<'a> {
113    type Item = &'a str;
114
115    fn next(&mut self) -> Option<Self::Item> {
116        match self {
117            Self::Empty => None,
118            Self::Blocks { iter } => iter.next().map(|block| block.hash.as_str()),
119        }
120    }
121}
122
123#[derive(Debug, Clone, Copy, Default)]
124pub struct CacheBytesReconstructStats {
125    pub reconstruct_ms: f64,
126    pub reconstruct_bytes: u64,
127    pub reconstruct_blocks: usize,
128}