use crate::error::Error;
use crate::sample::Sample;
#[non_exhaustive]
pub enum TransformResult {
Sample(Sample),
Samples(Vec<Sample>),
Skip,
Error(Error),
}
pub trait Transform: Send + Sync {
fn apply(&self, sample: Sample) -> TransformResult;
fn name(&self) -> &str;
}
pub trait StatefulTransform: Send {
fn push(&mut self, sample: Sample) -> Vec<Sample>;
fn finish(&mut self) -> Vec<Sample>;
fn name(&self) -> &str;
}
pub struct MapTransform<F> {
func: F,
}
impl<F> MapTransform<F>
where
F: Fn(Sample) -> crate::error::Result<Sample> + Send + Sync,
{
pub fn new(func: F) -> Self {
Self { func }
}
}
impl<F> Transform for MapTransform<F>
where
F: Fn(Sample) -> crate::error::Result<Sample> + Send + Sync,
{
fn apply(&self, sample: Sample) -> TransformResult {
match (self.func)(sample) {
Ok(s) => TransformResult::Sample(s),
Err(e) => TransformResult::Error(e),
}
}
#[allow(clippy::needless_borrows_for_generic_args)]
fn name(&self) -> &str {
"map"
}
}
pub struct FlatMapTransform<F> {
func: F,
}
impl<F> FlatMapTransform<F>
where
F: Fn(Sample) -> crate::error::Result<Vec<Sample>> + Send + Sync,
{
pub fn new(func: F) -> Self {
Self { func }
}
}
impl<F> Transform for FlatMapTransform<F>
where
F: Fn(Sample) -> crate::error::Result<Vec<Sample>> + Send + Sync,
{
fn apply(&self, sample: Sample) -> TransformResult {
match (self.func)(sample) {
Ok(samples) => TransformResult::Samples(samples),
Err(e) => TransformResult::Error(e),
}
}
#[allow(clippy::needless_borrows_for_generic_args)]
fn name(&self) -> &str {
"flat_map"
}
}
pub struct FilterTransform<F> {
predicate: F,
}
impl<F> FilterTransform<F>
where
F: Fn(&Sample) -> bool + Send + Sync,
{
pub fn new(predicate: F) -> Self {
Self { predicate }
}
}
impl<F> Transform for FilterTransform<F>
where
F: Fn(&Sample) -> bool + Send + Sync,
{
fn apply(&self, sample: Sample) -> TransformResult {
if (self.predicate)(&sample) {
TransformResult::Sample(sample)
} else {
TransformResult::Skip
}
}
fn name(&self) -> &str {
"filter"
}
}
pub struct ShuffleBuffer {
buffer: Vec<Sample>,
capacity: usize,
rng_state: u64,
}
impl ShuffleBuffer {
pub fn new(capacity: usize, seed: Option<u64>) -> Self {
Self {
buffer: Vec::with_capacity(capacity),
capacity: capacity.max(1),
rng_state: seed.unwrap_or(crate::pipeline::DEFAULT_SHUFFLE_SEED),
}
}
fn next_rand(&mut self) -> u64 {
let mut x = self.rng_state;
x ^= x << 13;
x ^= x >> 7;
x ^= x << 17;
self.rng_state = x;
x
}
}
impl StatefulTransform for ShuffleBuffer {
fn push(&mut self, sample: Sample) -> Vec<Sample> {
if self.buffer.len() < self.capacity {
self.buffer.push(sample);
Vec::new()
} else {
#[allow(clippy::cast_possible_truncation)]
let idx = (self.next_rand() as usize) % self.buffer.len();
let evicted = std::mem::replace(&mut self.buffer[idx], sample);
vec![evicted]
}
}
fn finish(&mut self) -> Vec<Sample> {
let mut remaining = std::mem::take(&mut self.buffer);
for i in (1..remaining.len()).rev() {
#[allow(clippy::cast_possible_truncation)]
let j = (self.next_rand() as usize) % (i + 1);
remaining.swap(i, j);
}
remaining
}
fn name(&self) -> &str {
"shuffle"
}
}
pub struct BatchAccumulator {
batch_size: usize,
drop_last: bool,
buffer: Vec<Sample>,
}
impl BatchAccumulator {
pub fn new(batch_size: usize, drop_last: bool) -> Self {
Self {
batch_size: batch_size.max(1),
drop_last,
buffer: Vec::new(),
}
}
}
impl StatefulTransform for BatchAccumulator {
fn push(&mut self, sample: Sample) -> Vec<Sample> {
self.buffer.push(sample);
if self.buffer.len() >= self.batch_size {
std::mem::replace(&mut self.buffer, Vec::with_capacity(self.batch_size))
} else {
Vec::new()
}
}
fn finish(&mut self) -> Vec<Sample> {
if self.drop_last && self.buffer.len() < self.batch_size {
self.buffer.clear();
Vec::new()
} else {
std::mem::take(&mut self.buffer)
}
}
fn name(&self) -> &str {
"batch"
}
}