use std::{
marker::PhantomData,
sync::{
Arc,
atomic::{AtomicBool, Ordering},
},
};
use crossbeam::queue::ArrayQueue;
use super::CellValue;
use crate::{
pipeline::{Definite, Pipeline, PipelineInstall, PipelineSeed},
signal::Signal,
subscription::SubscriptionGuard,
};
pub struct DropNewestPipeline<S, T> {
source: S,
capacity: usize,
_t: PhantomData<fn(T)>,
}
impl<S, T> PipelineInstall<T> for DropNewestPipeline<S, T>
where
S: PipelineInstall<T> + Send + Sync + 'static,
T: CellValue,
{
fn install(&self, callback: Arc<dyn Fn(&Signal<T>) + Send + Sync>) -> SubscriptionGuard {
let buffer = Arc::new(ArrayQueue::new(self.capacity));
let first = AtomicBool::new(true);
self.source.install(Arc::new(move |signal| match signal {
Signal::Value(value) => {
if first.swap(false, Ordering::SeqCst) {
return;
}
let value = value.as_ref().clone();
if buffer.push(value.clone()).is_ok() {
callback(&Signal::value(value));
}
}
Signal::Complete => callback(&Signal::Complete),
Signal::Error(error) => callback(&Signal::Error(error.clone())),
}))
}
}
impl<S, T> PipelineSeed<T> for DropNewestPipeline<S, T>
where
S: PipelineSeed<T>,
T: CellValue,
{
fn seed(&self) -> T {
self.source.seed()
}
}
#[allow(private_bounds)]
impl<S, T> Pipeline<T, Definite> for DropNewestPipeline<S, T>
where
S: Pipeline<T, Definite> + PipelineSeed<T>,
T: CellValue,
{
}
#[allow(private_bounds)]
pub trait BackpressureExt<T: CellValue>: Pipeline<T, Definite> + PipelineSeed<T> {
#[track_caller]
#[must_use]
fn drop_oldest(self, capacity: usize) -> Self {
assert!(capacity > 0, "capacity must be positive");
self
}
#[track_caller]
fn drop_newest(self, capacity: usize) -> impl crate::Materialize<T, Definite> {
assert!(capacity > 0, "capacity must be positive");
DropNewestPipeline {
source: self,
capacity,
_t: PhantomData,
}
}
#[track_caller]
#[must_use]
fn sample_latest(self) -> Self {
self
}
}
impl<T: CellValue, P: Pipeline<T, Definite> + PipelineSeed<T>> BackpressureExt<T> for P {}
#[cfg(test)]
mod tests {
use std::sync::atomic::{AtomicU32, Ordering};
use super::*;
use crate::{Cell, Gettable, Materialize, Mutable, Signal, traits::Watchable};
#[test]
fn drop_oldest_is_lazy_passthrough() {
let source = Cell::new(0);
let initial_subscribers = crate::traits::DepNode::subscriber_count(&source);
let pipeline = source.clone().drop_oldest(3);
assert_eq!(
crate::traits::DepNode::subscriber_count(&source),
initial_subscribers
);
let buffered = pipeline.materialize();
assert_eq!(
crate::traits::DepNode::subscriber_count(&source),
initial_subscribers
);
source.set(4);
assert_eq!(buffered.get(), 4);
}
#[test]
fn drop_newest_installs_only_when_materialized() {
let source = Cell::new(0);
let initial_subscribers = crate::traits::DepNode::subscriber_count(&source);
let pipeline = source.clone().drop_newest(3);
assert_eq!(
crate::traits::DepNode::subscriber_count(&source),
initial_subscribers
);
let buffered = pipeline.materialize();
assert_eq!(
crate::traits::DepNode::subscriber_count(&source),
initial_subscribers + 1
);
let count = Arc::new(AtomicU32::new(0));
let c = count.clone();
let _guard = buffered.subscribe(move |_| {
c.fetch_add(1, Ordering::SeqCst);
});
assert_eq!(count.load(Ordering::SeqCst), 1);
source.set(1);
source.set(2);
source.set(3);
assert_eq!(count.load(Ordering::SeqCst), 4);
source.set(4);
assert_eq!(count.load(Ordering::SeqCst), 4);
}
#[test]
fn sample_latest_is_lazy_passthrough() {
let source = Cell::new(0);
let initial_subscribers = crate::traits::DepNode::subscriber_count(&source);
let latest = source.clone().sample_latest().materialize();
assert_eq!(
crate::traits::DepNode::subscriber_count(&source),
initial_subscribers
);
source.set(1);
source.set(2);
source.set(3);
assert_eq!(latest.get(), 3);
}
#[test]
fn drop_newest_forwards_complete() {
let source = Cell::new(0);
let buffered = source.clone().drop_newest(3).materialize();
let completed = Arc::new(AtomicBool::new(false));
let c = completed.clone();
let _guard = buffered.subscribe(move |signal| {
if matches!(signal, Signal::Complete) {
c.store(true, Ordering::SeqCst);
}
});
source.complete();
assert!(completed.load(Ordering::SeqCst));
}
}