Skip to main content

rete_core/
reader.rs

1//! Range-reading abstraction (SPEC.md §9).
2//!
3//! A client doesn't need the whole file: it reads the 128-byte header, learns
4//! where each section lives, then fetches only those byte ranges. [`RangeReader`]
5//! is the seam — back it with a local file, an in-memory slice, or an HTTP
6//! `Range` client. [`CountingReader`] wraps any reader to measure how few bytes
7//! a given access pattern actually touches.
8
9use std::sync::atomic::{AtomicU64, Ordering};
10
11/// Something that can serve arbitrary byte ranges of a `.rete` resource.
12pub trait RangeReader {
13    /// Total resource length in bytes.
14    fn len(&self) -> u64;
15
16    /// True when the resource is empty.
17    fn is_empty(&self) -> bool {
18        self.len() == 0
19    }
20
21    /// Read `len` bytes starting at `offset`. Implementations should error on
22    /// an out-of-bounds range rather than truncating.
23    fn read_at(&self, offset: u64, len: u64) -> std::io::Result<Vec<u8>>;
24
25    /// Read several `(offset, len)` ranges, returning each range's bytes in
26    /// request order. These ranges are independent, so a reader whose backing
27    /// store is high-latency but parallelizable (an HTTP client) overrides this
28    /// to issue the reads concurrently — turning N round trips into ~N/P. The
29    /// default fetches them sequentially. Any range failing fails the batch.
30    fn read_many(&self, ranges: &[(u64, u64)]) -> std::io::Result<Vec<Vec<u8>>> {
31        ranges
32            .iter()
33            .map(|&(offset, len)| self.read_at(offset, len))
34            .collect()
35    }
36
37    /// How many ranges this reader can usefully have in flight at once — the
38    /// planner's hint for probe-vs-scan and batch-size decisions (a phone's
39    /// serial sync-XHR reader reports 1; the CLI's threaded HTTP client and the
40    /// browser's concurrent-fetch variants report their fan-out). Purely
41    /// advisory: correctness never depends on it. Defaults to 1 (sequential).
42    fn concurrency(&self) -> usize {
43        1
44    }
45}
46
47/// Sharing a reader (e.g. keeping a counting handle while a lazily-faulting
48/// [`Rete`](crate::Rete) owns another) just delegates.
49impl<R: RangeReader + ?Sized> RangeReader for std::sync::Arc<R> {
50    fn len(&self) -> u64 {
51        (**self).len()
52    }
53
54    fn read_at(&self, offset: u64, len: u64) -> std::io::Result<Vec<u8>> {
55        (**self).read_at(offset, len)
56    }
57
58    fn read_many(&self, ranges: &[(u64, u64)]) -> std::io::Result<Vec<Vec<u8>>> {
59        (**self).read_many(ranges)
60    }
61
62    fn concurrency(&self) -> usize {
63        (**self).concurrency()
64    }
65}
66
67/// A [`RangeReader`] over an in-memory byte slice (tests, embedded files).
68pub struct SliceReader<'a> {
69    data: &'a [u8],
70}
71
72impl<'a> SliceReader<'a> {
73    pub fn new(data: &'a [u8]) -> Self {
74        Self { data }
75    }
76}
77
78impl RangeReader for SliceReader<'_> {
79    fn len(&self) -> u64 {
80        self.data.len() as u64
81    }
82
83    fn read_at(&self, offset: u64, len: u64) -> std::io::Result<Vec<u8>> {
84        let start = offset as usize;
85        let end = start
86            .checked_add(len as usize)
87            .filter(|&e| e <= self.data.len())
88            .ok_or_else(|| {
89                std::io::Error::new(std::io::ErrorKind::UnexpectedEof, "range out of bounds")
90            })?;
91        Ok(self.data[start..end].to_vec())
92    }
93}
94
95/// Wraps a reader and tallies how many ranges were requested and how many bytes
96/// were returned — the metric that matters for a range-streamed format.
97/// Atomically counted, so it stays `Sync` (a lazily-faulting remote index holds
98/// its reader behind a shared loader).
99pub struct CountingReader<R> {
100    inner: R,
101    requests: AtomicU64,
102    bytes: AtomicU64,
103}
104
105impl<R: RangeReader> CountingReader<R> {
106    pub fn new(inner: R) -> Self {
107        Self {
108            inner,
109            requests: AtomicU64::new(0),
110            bytes: AtomicU64::new(0),
111        }
112    }
113
114    /// Number of `read_at` calls made so far.
115    pub fn requests(&self) -> u64 {
116        self.requests.load(Ordering::Relaxed)
117    }
118
119    /// Total bytes returned so far.
120    pub fn bytes_read(&self) -> u64 {
121        self.bytes.load(Ordering::Relaxed)
122    }
123}
124
125impl<R: RangeReader> RangeReader for CountingReader<R> {
126    fn len(&self) -> u64 {
127        self.inner.len()
128    }
129
130    fn read_at(&self, offset: u64, len: u64) -> std::io::Result<Vec<u8>> {
131        let out = self.inner.read_at(offset, len)?;
132        self.requests.fetch_add(1, Ordering::Relaxed);
133        self.bytes.fetch_add(out.len() as u64, Ordering::Relaxed);
134        Ok(out)
135    }
136
137    /// Delegate to the inner reader (preserving its parallelism) and tally each
138    /// returned range as one request — the count reflects the coalesced spans
139    /// actually fetched, however the inner reader issues them.
140    fn read_many(&self, ranges: &[(u64, u64)]) -> std::io::Result<Vec<Vec<u8>>> {
141        let out = self.inner.read_many(ranges)?;
142        self.requests.fetch_add(out.len() as u64, Ordering::Relaxed);
143        self.bytes
144            .fetch_add(out.iter().map(|b| b.len() as u64).sum(), Ordering::Relaxed);
145        Ok(out)
146    }
147
148    fn concurrency(&self) -> usize {
149        self.inner.concurrency()
150    }
151}
152
153#[cfg(test)]
154mod tests {
155    use super::*;
156
157    #[test]
158    fn slice_reader_serves_ranges_and_bounds_check() {
159        let data = (0u8..=255).collect::<Vec<_>>();
160        let r = SliceReader::new(&data);
161        assert_eq!(r.len(), 256);
162        assert_eq!(r.read_at(10, 4).unwrap(), vec![10, 11, 12, 13]);
163        assert!(r.read_at(254, 10).is_err()); // overruns
164    }
165
166    #[test]
167    fn counting_reader_tallies() {
168        let data = vec![0u8; 100];
169        let r = CountingReader::new(SliceReader::new(&data));
170        r.read_at(0, 10).unwrap();
171        r.read_at(50, 20).unwrap();
172        assert_eq!(r.requests(), 2);
173        assert_eq!(r.bytes_read(), 30);
174    }
175}