124 lines · 4.0 KB · Rust
RawDownload
/// Evaluate any iterator in their own thread.////// This is particularly useful if the wrapped iterator performs IO and/or heavy computations./// Use [`EagerIter::new()`] for instantiation.pub struct EagerIter<I: Iterator> {    receiver: std::sync::mpsc::Receiver<Vec<I::Item>>,    chunk: Option<std::vec::IntoIter<I::Item>>,    size_hint: (usize, Option<usize>),} impl<I> EagerIter<I>where    I: Iterator + Send + 'static,    <I as Iterator>::Item: Send,{    /// Return a new `EagerIter` which evaluates `iter` in its own thread,    /// with a given `chunk_size` allowing a maximum `chunks_in_flight`.    ///    /// * `chunk_size` describes how many items returned by `iter` will be a single item of this `EagerIter`.    ///    This helps to reduce the overhead imposed by transferring many small items.    ///    If this number is 1, each item will become a single chunk. 0 is invalid.    /// * `chunks_in_flight` describes how many chunks can be kept in memory in case the consumer of the `EagerIter`s items    ///    isn't consuming them fast enough. Setting this number to 0 effectively turns off any caching, but blocks `EagerIter`    ///    if its items aren't consumed fast enough.    pub fn new(iter: I, chunk_size: usize, chunks_in_flight: usize) -> Self {        let (sender, receiver) = std::sync::mpsc::sync_channel(chunks_in_flight);        let size_hint = iter.size_hint();        assert!(chunk_size > 0, "non-zero chunk size is needed");         std::thread::spawn(move || {            let mut out = Vec::with_capacity(chunk_size);            for item in iter {                out.push(item);                if out.len() == chunk_size {                    if sender.send(out).is_err() {                        return;                    }                    out = Vec::with_capacity(chunk_size);                }            }            if !out.is_empty() {                sender.send(out).ok();            }        });        EagerIter {            receiver,            chunk: None,            size_hint,        }    }     fn fill_buf_and_pop(&mut self) -> Option<I::Item> {        self.chunk = self.receiver.recv().ok().map(|v| {            assert!(!v.is_empty());            v.into_iter()        });        self.chunk.as_mut().and_then(|c| c.next())    }} impl<I> Iterator for EagerIter<I>where    I: Iterator + Send + 'static,    <I as Iterator>::Item: Send,{    type Item = I::Item;     fn next(&mut self) -> Option<Self::Item> {        match self.chunk.as_mut() {            Some(chunk) => chunk.next().or_else(|| self.fill_buf_and_pop()),            None => self.fill_buf_and_pop(),        }    }     fn size_hint(&self) -> (usize, Option<usize>) {        self.size_hint    }} /// An conditional `EagerIter`, which may become a just-in-time iterator running in the main thread depending on a condition.pub enum EagerIterIf<I: Iterator> {    /// A separate thread will eagerly evaluate iterator `I`.    Eager(EagerIter<I>),    /// The current thread evaluates `I`.    OnDemand(I),} impl<I> EagerIterIf<I>where    I: Iterator + Send + 'static,    <I as Iterator>::Item: Send,{    /// Return a new `EagerIterIf` if `condition()` returns true.    ///    /// For all other parameters, please see [`EagerIter::new()`].    pub fn new(condition: impl FnOnce() -> bool, iter: I, chunk_size: usize, chunks_in_flight: usize) -> Self {        if condition() {            EagerIterIf::Eager(EagerIter::new(iter, chunk_size, chunks_in_flight))        } else {            EagerIterIf::OnDemand(iter)        }    }}impl<I> Iterator for EagerIterIf<I>where    I: Iterator + Send + 'static,    <I as Iterator>::Item: Send,{    type Item = I::Item;     fn next(&mut self) -> Option<Self::Item> {        match self {            EagerIterIf::OnDemand(i) => i.next(),            EagerIterIf::Eager(i) => i.next(),        }    }     fn size_hint(&self) -> (usize, Option<usize>) {        match self {            EagerIterIf::OnDemand(i) => i.size_hint(),            EagerIterIf::Eager(i) => i.size_hint(),        }    }}