skippy_cache/payload/
bytes.rs1use 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}