Skip to main content

miniextendr_api/altrep_data/iter/
state.rs

1//! Core iterator-backed ALTREP data adaptors.
2//!
3//! Provides `IterState<I, T>` (the shared lazy-caching state machine) and
4//! data-adaptor types for the integer/real/logical/raw ALTREP families:
5//! `IterIntData`, `IterRealData`, `IterLogicalData`, `IterRawData`. The
6//! string/list/complex adaptors (`IterStringData`, `IterListData`,
7//! `IterComplexData`) live in `super::coerce`.
8//!
9//! See the [`super`](crate::altrep_data::iter) module docs for how to expose
10//! these adaptors to R: they implement only the data-level traits
11//! ([`AltrepLen`] + `Alt*Data`) and must be wrapped in a concrete
12//! `#[derive(Altrep*)]` + `#[altrep(manual)]` struct to back a live ALTREP
13//! vector.
14
15use std::cell::RefCell;
16use std::sync::OnceLock;
17
18use crate::altrep_data::{
19    AltIntegerData, AltLogicalData, AltRawData, AltRealData, AltrepLen, Logical, fill_region,
20};
21
22/// Core state for iterator-backed ALTREP vectors.
23///
24/// Provides lazy element generation with caching for random-access semantics.
25/// Iterator elements are cached as they're accessed, enabling repeatable reads.
26///
27/// # Type Parameters
28///
29/// - `I`: The iterator type (must be `ExactSizeIterator` or provide explicit length)
30/// - `T`: The element type produced by the iterator
31///
32/// # Design
33///
34/// - **Lazy:** Elements generated on-demand via `elt(i)`
35/// - **Cached:** Once generated, elements stored in cache for repeat access
36/// - **Materializable:** Can be fully materialized for `Dataptr` or serialization
37/// - **Safe:** Uses `RefCell` for interior mutability, protected by R's GC
38pub struct IterState<I, T> {
39    /// Vector length (from `ExactSizeIterator::len()` or explicit)
40    len: usize,
41    /// Iterator state (consumed as we advance)
42    iter: RefCell<Option<I>>,
43    /// Cache of generated elements (prefix of the vector)
44    cache: RefCell<Vec<T>>,
45    /// Full materialization (when all elements have been generated)
46    materialized: OnceLock<Vec<T>>,
47}
48
49impl<I, T> IterState<I, T>
50where
51    I: Iterator<Item = T>,
52{
53    /// Create a new iterator state with an explicit length.
54    ///
55    /// # Arguments
56    ///
57    /// - `iter`: The iterator to wrap
58    /// - `len`: The expected number of elements
59    ///
60    /// # Length Mismatch
61    ///
62    /// If the iterator produces a different number of elements than `len`:
63    /// - Fewer elements: Missing indices return `None`/NA/default values
64    /// - More elements: Extra elements are ignored (truncated to `len`)
65    ///
66    /// A warning is printed to stderr when a mismatch is detected.
67    pub fn new(iter: I, len: usize) -> Self {
68        Self {
69            len,
70            iter: RefCell::new(Some(iter)),
71            cache: RefCell::new(Vec::with_capacity(len.min(1024))),
72            materialized: OnceLock::new(),
73        }
74    }
75
76    /// Ensure the element at index `i` is in the cache and return it by value.
77    ///
78    /// Advances the iterator as needed. Only works for `Copy` types.
79    ///
80    /// # Returns
81    ///
82    /// - `Some(T)` if element exists and has been generated
83    /// - `None` if index is out of bounds or iterator exhausted before reaching index `i`
84    pub fn get_element(&self, i: usize) -> Option<T>
85    where
86        T: Copy,
87    {
88        // Check bounds
89        if i >= self.len {
90            return None;
91        }
92
93        // If fully materialized, return from materialized vec
94        if let Some(vec) = self.materialized.get() {
95            return vec.get(i).copied();
96        }
97
98        // Otherwise, check cache and advance iterator if needed
99        let mut cache = self.cache.borrow_mut();
100
101        // Already in cache?
102        if i < cache.len() {
103            return Some(cache[i]);
104        }
105
106        // Need to advance iterator to index i
107        let mut iter_opt = self.iter.borrow_mut();
108        {
109            let iter = iter_opt.as_mut()?;
110
111            // Fill cache up to and including index i
112            while cache.len() <= i {
113                if let Some(elem) = iter.next() {
114                    cache.push(elem);
115                } else {
116                    // Iterator exhausted before reaching expected length
117                    return None;
118                }
119            }
120        }
121
122        let value = cache[i];
123
124        // If we've generated the full vector via random-access, promote the cache
125        // to the materialized storage so `as_slice()` can expose it.
126        if cache.len() == self.len {
127            iter_opt.take();
128
129            let vec = std::mem::take(&mut *cache);
130            drop(cache);
131            drop(iter_opt);
132
133            let _ = self.materialized.set(vec);
134        }
135
136        Some(value)
137    }
138
139    /// Materialize all remaining elements from the iterator.
140    ///
141    /// After this call, all elements are guaranteed to be in memory and
142    /// `as_materialized()` will return `Some`.
143    ///
144    /// # Length Mismatch Handling
145    ///
146    /// If the iterator produces fewer elements than declared `len`, the missing
147    /// elements are left uninitialized in the cache (callers should handle this
148    /// via bounds checking). If the iterator produces more elements than declared,
149    /// extra elements are silently ignored (truncated to `len`).
150    ///
151    /// A warning is printed to stderr if a length mismatch is detected.
152    pub fn materialize_all(&self) -> &[T] {
153        // Already materialized?
154        if let Some(vec) = self.materialized.get() {
155            return vec;
156        }
157
158        // Consume iterator and move cache to materialized storage
159        let mut cache = self.cache.borrow_mut();
160        let mut iter_opt = self.iter.borrow_mut();
161
162        if let Some(iter) = iter_opt.take() {
163            // Drain remaining elements (up to self.len to avoid memory issues)
164            for elem in iter {
165                if cache.len() >= self.len {
166                    // Iterator produced more than expected - truncate and warn
167                    eprintln!(
168                        "[miniextendr warning] iterator ALTREP: iterator produced more elements than declared length ({}), truncating",
169                        self.len
170                    );
171                    break;
172                }
173                cache.push(elem);
174            }
175
176            // Check if iterator exhausted early
177            if cache.len() < self.len {
178                eprintln!(
179                    "[miniextendr warning] iterator ALTREP: iterator produced {} elements, expected {} - accessing missing indices will return NA/default",
180                    cache.len(),
181                    self.len
182                );
183            }
184        }
185
186        // Move cache to materialized (take ownership)
187        let vec = std::mem::take(&mut *cache);
188        drop(cache);
189        drop(iter_opt);
190
191        // Store in OnceLock and return reference
192        self.materialized.get_or_init(|| vec)
193    }
194
195    /// Get the materialized vector if all elements have been generated.
196    ///
197    /// Returns `None` if not yet fully materialized.
198    pub fn as_materialized(&self) -> Option<&[T]> {
199        self.materialized.get().map(|v| v.as_slice())
200    }
201
202    /// Get the current length.
203    pub fn len(&self) -> usize {
204        self.len
205    }
206
207    /// Check if the vector is empty.
208    pub fn is_empty(&self) -> bool {
209        self.len == 0
210    }
211}
212
213impl<I, T> IterState<I, T>
214where
215    I: ExactSizeIterator<Item = T>,
216{
217    /// Create a new iterator state from an `ExactSizeIterator`.
218    ///
219    /// The length is automatically determined from `iter.len()`.
220    pub fn from_exact_size(iter: I) -> Self {
221        let len = iter.len();
222        Self::new(iter, len)
223    }
224}
225
226/// Iterator-backed integer vector data adaptor.
227///
228/// Wraps an iterator producing `i32` values and implements the data-level
229/// traits ([`AltrepLen`] + [`AltIntegerData`]) for backing an ALTREP integer
230/// vector. To expose it to R, wrap it in a `#[derive(AltrepInteger)]` +
231/// `#[altrep(manual)]` struct (see the [module docs](crate::altrep_data::iter)).
232///
233/// # Example
234///
235/// ```ignore
236/// use miniextendr_api::altrep_data::IterIntData;
237///
238/// // Create from an iterator
239/// let data = IterIntData::from_iter((1..=10).map(|x| x * 2), 10);
240/// ```
241pub struct IterIntData<I: Iterator<Item = i32>> {
242    state: IterState<I, i32>,
243}
244
245impl<I: Iterator<Item = i32>> IterIntData<I> {
246    /// Create from an iterator with explicit length.
247    pub fn from_iter(iter: I, len: usize) -> Self {
248        Self {
249            state: IterState::new(iter, len),
250        }
251    }
252}
253
254impl<I: ExactSizeIterator<Item = i32>> IterIntData<I> {
255    /// Create from an ExactSizeIterator (length auto-detected).
256    pub fn from_exact_iter(iter: I) -> Self {
257        Self {
258            state: IterState::from_exact_size(iter),
259        }
260    }
261}
262
263impl<I: Iterator<Item = i32>> AltrepLen for IterIntData<I> {
264    fn len(&self) -> usize {
265        self.state.len()
266    }
267}
268
269impl<I: Iterator<Item = i32>> AltIntegerData for IterIntData<I> {
270    fn elt(&self, i: usize) -> i32 {
271        self.state
272            .get_element(i)
273            .unwrap_or(crate::altrep_traits::NA_INTEGER)
274    }
275
276    fn as_slice(&self) -> Option<&[i32]> {
277        self.state.as_materialized()
278    }
279
280    fn get_region(&self, start: usize, len: usize, buf: &mut [i32]) -> usize {
281        fill_region(start, len, self.len(), buf, |idx| self.elt(idx))
282    }
283}
284
285/// Iterator-backed real (f64) vector data adaptor.
286///
287/// Wraps an iterator producing `f64` values and implements the data-level
288/// traits ([`AltrepLen`] + [`AltRealData`]) for backing an ALTREP real vector.
289/// To expose it to R, wrap it in a `#[derive(AltrepReal)]` +
290/// `#[altrep(manual)]` struct (see the [module docs](crate::altrep_data::iter)).
291pub struct IterRealData<I: Iterator<Item = f64>> {
292    state: IterState<I, f64>,
293}
294
295impl<I: Iterator<Item = f64>> IterRealData<I> {
296    /// Create from an iterator with explicit length.
297    pub fn from_iter(iter: I, len: usize) -> Self {
298        Self {
299            state: IterState::new(iter, len),
300        }
301    }
302}
303
304impl<I: ExactSizeIterator<Item = f64>> IterRealData<I> {
305    /// Create from an ExactSizeIterator (length auto-detected).
306    pub fn from_exact_iter(iter: I) -> Self {
307        Self {
308            state: IterState::from_exact_size(iter),
309        }
310    }
311}
312
313impl<I: Iterator<Item = f64>> AltrepLen for IterRealData<I> {
314    fn len(&self) -> usize {
315        self.state.len()
316    }
317}
318
319impl<I: Iterator<Item = f64>> AltRealData for IterRealData<I> {
320    fn elt(&self, i: usize) -> f64 {
321        self.state.get_element(i).unwrap_or(f64::NAN)
322    }
323
324    fn as_slice(&self) -> Option<&[f64]> {
325        self.state.as_materialized()
326    }
327
328    fn get_region(&self, start: usize, len: usize, buf: &mut [f64]) -> usize {
329        fill_region(start, len, self.len(), buf, |idx| self.elt(idx))
330    }
331}
332
333/// Iterator-backed logical vector data adaptor.
334///
335/// Wraps an iterator producing `bool` values and implements the data-level
336/// traits ([`AltrepLen`] + [`AltLogicalData`]) for backing an ALTREP logical
337/// vector. To expose it to R, wrap it in a `#[derive(AltrepLogical)]` +
338/// `#[altrep(manual)]` struct (see the [module docs](crate::altrep_data::iter)).
339pub struct IterLogicalData<I: Iterator<Item = bool>> {
340    state: IterState<I, bool>,
341}
342
343impl<I: Iterator<Item = bool>> IterLogicalData<I> {
344    /// Create from an iterator with explicit length.
345    pub fn from_iter(iter: I, len: usize) -> Self {
346        Self {
347            state: IterState::new(iter, len),
348        }
349    }
350}
351
352impl<I: ExactSizeIterator<Item = bool>> IterLogicalData<I> {
353    /// Create from an ExactSizeIterator (length auto-detected).
354    pub fn from_exact_iter(iter: I) -> Self {
355        Self {
356            state: IterState::from_exact_size(iter),
357        }
358    }
359}
360
361impl<I: Iterator<Item = bool>> AltrepLen for IterLogicalData<I> {
362    fn len(&self) -> usize {
363        self.state.len()
364    }
365}
366
367impl<I: Iterator<Item = bool>> AltLogicalData for IterLogicalData<I> {
368    fn elt(&self, i: usize) -> Logical {
369        self.state
370            .get_element(i)
371            .map(Logical::from_bool)
372            .unwrap_or(Logical::Na)
373    }
374
375    fn get_region(&self, start: usize, len: usize, buf: &mut [i32]) -> usize {
376        fill_region(start, len, self.len(), buf, |idx| self.elt(idx).to_r_int())
377    }
378}
379
380/// Iterator-backed raw (u8) vector data adaptor.
381///
382/// Wraps an iterator producing `u8` values and implements the data-level
383/// traits ([`AltrepLen`] + [`AltRawData`]) for backing an ALTREP raw vector.
384/// To expose it to R, wrap it in a `#[derive(AltrepRaw)]` +
385/// `#[altrep(manual)]` struct (see the [module docs](crate::altrep_data::iter)).
386pub struct IterRawData<I: Iterator<Item = u8>> {
387    state: IterState<I, u8>,
388}
389
390impl<I: Iterator<Item = u8>> IterRawData<I> {
391    /// Create from an iterator with explicit length.
392    pub fn from_iter(iter: I, len: usize) -> Self {
393        Self {
394            state: IterState::new(iter, len),
395        }
396    }
397}
398
399impl<I: ExactSizeIterator<Item = u8>> IterRawData<I> {
400    /// Create from an ExactSizeIterator (length auto-detected).
401    pub fn from_exact_iter(iter: I) -> Self {
402        Self {
403            state: IterState::from_exact_size(iter),
404        }
405    }
406}
407
408impl<I: Iterator<Item = u8>> AltrepLen for IterRawData<I> {
409    fn len(&self) -> usize {
410        self.state.len()
411    }
412}
413
414impl<I: Iterator<Item = u8>> AltRawData for IterRawData<I> {
415    fn elt(&self, i: usize) -> u8 {
416        self.state.get_element(i).unwrap_or(0)
417    }
418
419    fn as_slice(&self) -> Option<&[u8]> {
420        self.state.as_materialized()
421    }
422
423    fn get_region(&self, start: usize, len: usize, buf: &mut [u8]) -> usize {
424        fill_region(start, len, self.len(), buf, |idx| self.elt(idx))
425    }
426}