Skip to main content

miniextendr_api/altrep_data/
stream.rs

1//! Streaming ALTREP data adaptors backed by chunk-cached reader closures.
2//!
3//! These types provide backing data for ALTREP vectors where elements are
4//! loaded on-demand from a reader function in fixed-size chunks. Chunks are
5//! cached for repeated access within the same region.
6//!
7//! Like the iterator adaptors documented in
8//! [`altrep_data`](crate::altrep_data), these implement only
9//! the data-level traits ([`AltrepLen`] + `Alt*Data`); to expose one to R,
10//! wrap it in a concrete `#[derive(Altrep*)]` + `#[altrep(manual)]` struct
11//! that delegates the data-trait methods (see the
12//! [`altrep_data`](crate::altrep_data) module docs for the pattern).
13
14use std::cell::RefCell;
15use std::collections::BTreeMap;
16
17use super::{AltIntegerData, AltRealData, AltrepLen};
18
19// region: StreamingRealData
20
21/// Streaming data adaptor for ALTREP real (f64) vectors.
22///
23/// Elements are loaded on-demand via a reader closure in fixed-size chunks.
24/// Chunks are cached in a `BTreeMap` for repeated access.
25///
26/// # Reader Contract
27///
28/// The reader `F(start, buf) -> count` fills `buf` with elements starting
29/// at index `start` and returns the number of elements actually written.
30///
31/// # Example
32///
33/// ```ignore
34/// use miniextendr_api::altrep_data::StreamingRealData;
35///
36/// let data = StreamingRealData::new(1000, 64, |start, buf| {
37///     let count = buf.len().min(1000 - start);
38///     for (i, slot) in buf[..count].iter_mut().enumerate() {
39///         *slot = (start + i) as f64 * 0.1;
40///     }
41///     count
42/// });
43/// ```
44pub struct StreamingRealData<F: Fn(usize, &mut [f64]) -> usize> {
45    len: usize,
46    reader: F,
47    cache: RefCell<BTreeMap<usize, Vec<f64>>>,
48    chunk_size: usize,
49}
50
51impl<F: Fn(usize, &mut [f64]) -> usize> StreamingRealData<F> {
52    /// Create a new streaming real data source.
53    ///
54    /// - `len`: total number of elements
55    /// - `chunk_size`: number of elements per cache chunk
56    /// - `reader`: closure that fills a buffer starting at a given index
57    pub fn new(len: usize, chunk_size: usize, reader: F) -> Self {
58        Self {
59            len,
60            reader,
61            cache: RefCell::new(BTreeMap::new()),
62            chunk_size: chunk_size.max(1),
63        }
64    }
65
66    /// Load a chunk into the cache if not already present.
67    fn ensure_chunk(&self, chunk_idx: usize) {
68        let mut cache = self.cache.borrow_mut();
69        if cache.contains_key(&chunk_idx) {
70            return;
71        }
72        let start = chunk_idx * self.chunk_size;
73        let count = self.chunk_size.min(self.len.saturating_sub(start));
74        if count == 0 {
75            return;
76        }
77        let mut buf = vec![0.0f64; count];
78        let written = (self.reader)(start, &mut buf);
79        buf.truncate(written);
80        cache.insert(chunk_idx, buf);
81    }
82}
83
84impl<F: Fn(usize, &mut [f64]) -> usize> AltrepLen for StreamingRealData<F> {
85    fn len(&self) -> usize {
86        self.len
87    }
88}
89
90impl<F: Fn(usize, &mut [f64]) -> usize> AltRealData for StreamingRealData<F> {
91    fn elt(&self, i: usize) -> f64 {
92        if i >= self.len {
93            return f64::NAN;
94        }
95        let chunk_idx = i / self.chunk_size;
96        self.ensure_chunk(chunk_idx);
97        let cache = self.cache.borrow();
98        let offset = i % self.chunk_size;
99        cache
100            .get(&chunk_idx)
101            .and_then(|chunk| chunk.get(offset).copied())
102            .unwrap_or(f64::NAN)
103    }
104
105    fn get_region(&self, start: usize, len: usize, buf: &mut [f64]) -> usize {
106        let count = len.min(self.len.saturating_sub(start)).min(buf.len());
107        if count == 0 {
108            return 0;
109        }
110        (self.reader)(start, &mut buf[..count])
111    }
112}
113
114// endregion
115
116// region: StreamingIntData
117
118/// Streaming data adaptor for ALTREP integer (i32) vectors.
119///
120/// Elements are loaded on-demand via a reader closure in fixed-size chunks.
121/// Chunks are cached in a `BTreeMap` for repeated access.
122///
123/// # Reader Contract
124///
125/// The reader `F(start, buf) -> count` fills `buf` with elements starting
126/// at index `start` and returns the number of elements actually written.
127///
128/// # Example
129///
130/// ```ignore
131/// use miniextendr_api::altrep_data::StreamingIntData;
132///
133/// let data = StreamingIntData::new(1000, 64, |start, buf| {
134///     let count = buf.len().min(1000 - start);
135///     for (i, slot) in buf[..count].iter_mut().enumerate() {
136///         *slot = (start + i) as i32;
137///     }
138///     count
139/// });
140/// ```
141pub struct StreamingIntData<F: Fn(usize, &mut [i32]) -> usize> {
142    len: usize,
143    reader: F,
144    cache: RefCell<BTreeMap<usize, Vec<i32>>>,
145    chunk_size: usize,
146}
147
148impl<F: Fn(usize, &mut [i32]) -> usize> StreamingIntData<F> {
149    /// Create a new streaming integer data source.
150    ///
151    /// - `len`: total number of elements
152    /// - `chunk_size`: number of elements per cache chunk
153    /// - `reader`: closure that fills a buffer starting at a given index
154    pub fn new(len: usize, chunk_size: usize, reader: F) -> Self {
155        Self {
156            len,
157            reader,
158            cache: RefCell::new(BTreeMap::new()),
159            chunk_size: chunk_size.max(1),
160        }
161    }
162
163    /// Load a chunk into the cache if not already present.
164    fn ensure_chunk(&self, chunk_idx: usize) {
165        let mut cache = self.cache.borrow_mut();
166        if cache.contains_key(&chunk_idx) {
167            return;
168        }
169        let start = chunk_idx * self.chunk_size;
170        let count = self.chunk_size.min(self.len.saturating_sub(start));
171        if count == 0 {
172            return;
173        }
174        let mut buf = vec![0i32; count];
175        let written = (self.reader)(start, &mut buf);
176        buf.truncate(written);
177        cache.insert(chunk_idx, buf);
178    }
179}
180
181impl<F: Fn(usize, &mut [i32]) -> usize> AltrepLen for StreamingIntData<F> {
182    fn len(&self) -> usize {
183        self.len
184    }
185}
186
187impl<F: Fn(usize, &mut [i32]) -> usize> AltIntegerData for StreamingIntData<F> {
188    fn elt(&self, i: usize) -> i32 {
189        if i >= self.len {
190            return crate::altrep_traits::NA_INTEGER;
191        }
192        let chunk_idx = i / self.chunk_size;
193        self.ensure_chunk(chunk_idx);
194        let cache = self.cache.borrow();
195        let offset = i % self.chunk_size;
196        cache
197            .get(&chunk_idx)
198            .and_then(|chunk| chunk.get(offset).copied())
199            .unwrap_or(crate::altrep_traits::NA_INTEGER)
200    }
201
202    fn get_region(&self, start: usize, len: usize, buf: &mut [i32]) -> usize {
203        let count = len.min(self.len.saturating_sub(start)).min(buf.len());
204        if count == 0 {
205            return 0;
206        }
207        (self.reader)(start, &mut buf[..count])
208    }
209}
210
211// endregion