use crate::streaming::stream::Stream;
pub type StateStep<S, A, B> = Box<dyn FnMut(S, A) -> (S, Option<B>) + Send>;
#[inline]
pub fn state_scan<S, A, B, E, R>(
stream: Stream<A, E, R>,
init: S,
mut step: impl FnMut(S, A) -> (S, Option<B>) + Send + 'static,
) -> Stream<B, E, R>
where
S: 'static,
A: Send + 'static,
B: Send + 'static,
E: Send + 'static,
R: 'static,
{
Stream::new(move |r: &mut R| {
Box::pin(async move {
let mut upstream = stream;
let mut state = init;
let mut out = Vec::new();
loop {
let Some(chunk) = upstream.poll_next_chunk(r).await? else {
break;
};
for item in chunk.into_vec() {
let (next, maybe) = step(state, item);
state = next;
if let Some(b) = maybe {
out.push(b);
}
}
}
Ok(out)
})
})
}
impl<A, E, R> Stream<A, E, R>
where
A: Send + 'static,
E: Send + 'static,
R: 'static,
{
#[inline]
pub fn state_scan<S, B, F>(self, init: S, step: F) -> Stream<B, E, R>
where
S: 'static,
B: Send + 'static,
F: FnMut(S, A) -> (S, Option<B>) + Send + 'static,
{
state_scan(self, init, step)
}
}
#[cfg(test)]
mod tests {
use super::state_scan;
use super::*;
fn block_on<F: core::future::Future>(fut: F) -> F::Output {
pollster::block_on(fut)
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
enum St {
Idle,
Active,
}
#[test]
fn emits_only_on_transition_to_active() {
let stream = Stream::from_iterable([0u8, 1, 1, 0, 1]).state_scan(St::Idle, |st, x| {
let next = if x > 0 { St::Active } else { St::Idle };
let emit = matches!((st, next), (St::Idle, St::Active));
(next, emit.then_some(x))
});
let out = block_on(stream.run_collect().run(&mut ())).expect("collect");
assert_eq!(out, vec![1, 1]);
}
#[test]
fn state_scan_no_transition_skips_emit() {
let stream = Stream::from_iterable([1u8, 1, 1])
.state_scan(0u8, |st, x| if x == st { (st, None) } else { (x, Some(x)) });
let out = block_on(stream.run_collect().run(&mut ())).expect("collect");
assert_eq!(out, vec![1]);
}
#[test]
fn state_scan_emits_each_change() {
let stream = Stream::from_iterable([1u8, 2, 3]).state_scan(0u8, |_st, x| (x, Some(x)));
let out = block_on(stream.run_collect().run(&mut ())).expect("collect");
assert_eq!(out, vec![1, 2, 3]);
}
#[test]
fn free_state_scan_function() {
let stream = state_scan(Stream::from_iterable([1u8, 2]), 0u32, |s, x| {
(s + x as u32, Some(s))
});
let out = block_on(stream.run_collect().run(&mut ())).expect("collect");
assert_eq!(out, vec![0, 1]);
}
#[test]
fn empty_upstream_yields_empty() {
let stream = Stream::<u8, (), ()>::from_iterable(core::iter::empty())
.state_scan(0u32, |s, x| (s + x as u32, Some(s)));
let out = block_on(stream.run_collect().run(&mut ())).expect("collect");
assert!(out.is_empty());
}
}