use std::time::Duration;
use futures::{Stream, StreamExt as _};
use super::buffer::{take_window, window_capacity};
pub fn debounce<S, T>(stream: S, delay: Duration) -> impl Stream<Item = T> + Send + 'static
where
S: Stream<Item = T> + Send + 'static,
T: Send + 'static,
{
async_stream::stream! {
tokio::pin!(stream);
let mut pending: Option<T> = None;
loop {
let has_pending = pending.is_some();
tokio::select! {
item = stream.next() => {
if let Some(v) = item {
pending = Some(v);
} else {
if let Some(v) = pending.take() { yield v; }
break;
}
}
() = async move {
if has_pending {
tokio::time::sleep(delay).await;
} else {
std::future::pending::<()>().await;
}
} => {
if let Some(v) = pending.take() { yield v; }
}
}
}
}
}
pub fn debounce_batch<S, T>(
stream: S,
quiet: Duration,
max_items: usize,
) -> impl Stream<Item = Vec<T>> + Send + 'static
where
S: Stream<Item = T> + Send + 'static,
T: Send + 'static,
{
async_stream::stream! {
tokio::pin!(stream);
let (max_items, initial_capacity) = window_capacity(max_items);
let mut buf: Vec<T> = Vec::with_capacity(initial_capacity);
let mut deadline: Option<tokio::time::Instant> = None;
loop {
tokio::select! {
item = stream.next() => {
if let Some(v) = item {
buf.push(v);
deadline = Some(tokio::time::Instant::now() + quiet);
if buf.len() >= max_items {
deadline = None;
yield take_window(&mut buf, initial_capacity);
}
} else {
if !buf.is_empty() {
yield take_window(&mut buf, initial_capacity);
}
break;
}
}
() = async {
match deadline {
Some(d) => tokio::time::sleep_until(d).await,
None => std::future::pending::<()>().await,
}
} => {
deadline = None;
if !buf.is_empty() {
yield take_window(&mut buf, initial_capacity);
}
}
}
}
}
}
pub fn throttle<S, T>(stream: S, interval: Duration) -> impl Stream<Item = T> + Send + 'static
where
S: Stream<Item = T> + Send + 'static,
T: Send + 'static,
{
async_stream::stream! {
tokio::pin!(stream);
let mut last_emit: Option<tokio::time::Instant> = None;
while let Some(item) = stream.next().await {
let now = tokio::time::Instant::now();
let due = last_emit.is_none_or(|prev| now.duration_since(prev) >= interval);
if due {
last_emit = Some(now);
yield item;
}
}
}
}