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