use core::fmt::Debug;
use core::marker::PhantomData;
use alloc::{
boxed::Box,
string::{String, ToString},
sync::Arc,
vec,
};
use crate::transform::{CollectedTransform, TransformDescriptor, TransformProfiling};
pub struct TransformBuilder<I, O> {
input_key: String,
_phantom: PhantomData<(I, O)>,
}
impl<I, O> TransformBuilder<I, O>
where
I: Send + Sync + Clone + Debug + 'static,
O: Send + Sync + Clone + Debug + 'static,
{
pub(crate) fn new(input_key: String) -> Self {
Self {
input_key,
_phantom: PhantomData,
}
}
pub fn map<F>(self, f: F) -> TransformPipeline<I, O>
where
F: Fn(&I) -> Option<O> + Send + Sync + 'static,
{
TransformPipeline {
input_key: self.input_key,
spawn_factory: Box::new(move |input_key| {
let transform_fn = move |val: &I, _state: &mut ()| f(val);
create_single_transform_descriptor::<I, O, ()>(input_key, (), transform_fn)
}),
_phantom_i: PhantomData,
}
}
pub fn with_state<S: Send + Sync + 'static>(
self,
initial: S,
) -> StatefulTransformBuilder<I, O, S> {
StatefulTransformBuilder {
input_key: self.input_key,
initial_state: initial,
_phantom: PhantomData,
}
}
}
pub struct StatefulTransformBuilder<I, O, S> {
input_key: String,
initial_state: S,
_phantom: PhantomData<(I, O)>,
}
impl<I, O, S> StatefulTransformBuilder<I, O, S>
where
I: Send + Sync + Clone + Debug + 'static,
O: Send + Sync + Clone + Debug + 'static,
S: Send + Sync + 'static,
{
pub fn on_value<F>(self, f: F) -> TransformPipeline<I, O>
where
F: Fn(&I, &mut S) -> Option<O> + Send + Sync + 'static,
{
let initial = self.initial_state;
TransformPipeline {
input_key: self.input_key,
spawn_factory: Box::new(move |input_key| {
create_single_transform_descriptor::<I, O, S>(input_key, initial, f)
}),
_phantom_i: PhantomData,
}
}
}
pub struct TransformPipeline<I, O: Send + Sync + Clone + Debug + 'static> {
pub(crate) input_key: String,
pub(crate) spawn_factory: Box<dyn FnOnce(String) -> TransformDescriptor<O> + Send + Sync>,
pub(crate) _phantom_i: PhantomData<I>,
}
impl<I, O> TransformPipeline<I, O>
where
I: Send + Sync + Clone + Debug + 'static,
O: Send + Sync + Clone + Debug + 'static,
{
pub(crate) fn into_descriptor(self) -> TransformDescriptor<O> {
(self.spawn_factory)(self.input_key)
}
}
fn create_single_transform_descriptor<I, O, S>(
input_key: String,
initial_state: S,
transform_fn: impl Fn(&I, &mut S) -> Option<O> + Send + Sync + 'static,
) -> TransformDescriptor<O>
where
I: Send + Sync + Clone + Debug + 'static,
O: Send + Sync + Clone + Debug + 'static,
S: Send + Sync + 'static,
{
let input_key_clone = input_key.clone();
let input_keys = vec![input_key];
TransformDescriptor {
input_keys,
build_fn: Box::new(
move |producer, db, output_key, profiling| CollectedTransform {
task_future: Box::pin(run_single_transform::<I, O, S>(
db,
input_key_clone,
output_key.to_string(),
producer,
initial_state,
transform_fn,
profiling,
)),
fanin_futures: alloc::vec::Vec::new(),
},
),
}
}
#[allow(unused_variables)]
pub(crate) async fn run_single_transform<I, O, S>(
db: Arc<crate::AimDb>,
input_key: String,
output_key: String,
producer: crate::Producer<O>,
mut state: S,
transform_fn: impl Fn(&I, &mut S) -> Option<O> + Send + Sync + 'static,
profiling: TransformProfiling,
) where
I: Send + Sync + Clone + Debug + 'static,
O: Send + Sync + Clone + Debug + 'static,
S: Send + 'static,
{
log_info!("🔄 Transform started: '{}' → '{}'", input_key, output_key);
let consumer = match db.consumer::<I>(&input_key) {
Ok(c) => c,
Err(_e) => {
log_error!(
"🔄 Transform '{}' → '{}' FATAL: failed to resolve consumer: {:?}",
input_key,
output_key,
_e
);
return;
}
};
let mut reader = consumer.subscribe();
log_debug!(
"✅ Transform '{}' → '{}' subscribed, entering event loop",
input_key,
output_key
);
loop {
match reader.recv().await {
Ok(input_value) => {
#[cfg(feature = "observability")]
{
let started_ns = profiling.as_ref().map(|(_, clock)| clock());
let output_value = transform_fn(&input_value, &mut state);
if let (Some((metrics, clock)), Some(started_ns)) = (&profiling, started_ns) {
metrics.record(clock().saturating_sub(started_ns));
}
if let Some(output_value) = output_value {
producer.produce(output_value);
}
}
#[cfg(not(feature = "observability"))]
{
if let Some(output_value) = transform_fn(&input_value, &mut state) {
producer.produce(output_value);
}
}
}
Err(crate::DbError::BufferLagged { .. }) => {
log_warn!(
"🔄 Transform '{}' → '{}' lagged behind, some values skipped",
input_key,
output_key
);
continue;
}
Err(_) => {
log_warn!(
"🔄 Transform '{}' → '{}' input closed, task exiting",
input_key,
output_key
);
break;
}
}
}
}