Quickstart
This example demonstrates a two-pipeline architecture: a main pipeline processes frames by tagging each with a unique frame_id, encoding them with a dummy codec, and checking quality against a PSNR threshold, while an error pipeline catches and logs any frames that fall below the threshold.
Full source: code-samples/examples/quickstart.rs
1. Data Transfer Object (DTO)
The DTO carries a frame_id, a psnr score, an encoded_data buffer, and an optional error field. It implements FrameError<String> so pipeline stages can report errors on it.
#![allow(unused)] fn main() { use remotia::{ traits::{FrameError, FrameProcessor}, pipeline::{Pipeline, component::Component, registry::PipelineRegistry}, processors::{ ticker::Ticker, error_switch::OnErrorSwitch, }, }; use async_trait::async_trait; use rand::Rng; #[derive(Debug, Default)] struct MyDto { frame_id: u64, psnr: f64, encoded_data: Vec<u8>, error: Option<String>, } impl FrameError<String> for MyDto { fn report_error(&mut self, error: String) { self.error = Some(error); } fn get_error(&self) -> Option<String> { self.error.clone() } } }
2. Pipeline Processors
Each processor implements FrameProcessor<MyDto> and transforms or inspects the DTO as it moves through the pipeline.
FrameTagger — assigns a unique frame ID on each invocation:
#![allow(unused)] fn main() { struct FrameTagger { current_frame_id: u64, } #[async_trait] impl FrameProcessor<MyDto> for FrameTagger { async fn process(&mut self, mut dto: MyDto) -> Option<MyDto> { self.current_frame_id += 1; dto.frame_id = self.current_frame_id; Some(dto) } } }
DummyCodec — populates the DTO with random PSNR and encoded data:
#![allow(unused)] fn main() { struct DummyCodec; #[async_trait] impl FrameProcessor<MyDto> for DummyCodec { async fn process(&mut self, mut dto: MyDto) -> Option<MyDto> { let mut rng = rand::thread_rng(); dto.psnr = rng.gen_range(20.0..50.0); let len = rng.gen_range(100..1000); dto.encoded_data = (0..len).map(|_| rng.r#gen()).collect(); Some(dto) } } }
QualityGate — checks whether the PSNR is below a threshold and reports an error if so:
#![allow(unused)] fn main() { struct QualityGate { threshold: f64, } #[async_trait] impl FrameProcessor<MyDto> for QualityGate { async fn process(&mut self, mut dto: MyDto) -> Option<MyDto> { if dto.psnr < self.threshold { dto.report_error( format!("psnr {:.2} below threshold {}", dto.psnr, self.threshold), ); } println!( "main: frame_id = {}, psnr = {:.2}, data_len = {}, error = {}", dto.frame_id, dto.psnr, dto.encoded_data.len(), dto.error.is_some(), ); Some(dto) } } }
ErrorLogger — logs any error on a frame and discards it:
#![allow(unused)] fn main() { struct ErrorLogger; #[async_trait] impl FrameProcessor<MyDto> for ErrorLogger { async fn process(&mut self, dto: MyDto) -> Option<MyDto> { if let Some(ref err) = dto.error { println!("error pipeline: frame {} failed — {}", dto.frame_id, err); } None } } }
3. Pipeline Assembly
Register the error pipeline first, making it feedable so OnErrorSwitch can push frames into it. Then build the main pipeline with a tick rate-limiter, frame tagger, dummy codec, quality gate, and error switch. Finally register and run everything:
#[tokio::main] async fn main() { let mut registry = PipelineRegistry::<MyDto, &str>::new(); let error_pipeline = Pipeline::new() .link(Component::singleton(ErrorLogger)) .feedable() .tag("error"); registry.register("error", error_pipeline); let error_switch = { let error_pipe = registry.get_mut(&"error"); OnErrorSwitch::new(error_pipe) }; let main_pipeline = Pipeline::new() .link( Component::new() .append(Ticker::new(100)) .append(FrameTagger { current_frame_id: 0 }) .append(DummyCodec) .append(QualityGate { threshold: 30.0 }) .append(error_switch) .tag("main-step") ) .tag("main"); registry.register("main", main_pipeline); registry.run().await; }
What happens at runtime
- The
Tickerpaces the loop at ~10 Hz. FrameTaggerincrementsframe_idby 1 each tick.DummyCodecfillspsnrwith a random value between 20.0 and 50.0 andencoded_datawith random bytes (100–1000 bytes).QualityGatechecks whetherpsnris below the threshold (30.0); if so, it reports an error on the DTO. It always passes the frame forward.OnErrorSwitchinspects the DTO's error field and splits execution into two branches:- Main branch — no error is present: the frame is consumed within the main pipeline.
- Error branch — an error is present: the frame is forwarded to the registered error pipeline.
- In the error pipeline,
ErrorLoggerprints the failure details and the frame is discarded (returnsNone).
Next steps
- Read the Key concepts for the full trait and API reference.
- Browse the Processors catalog to find built-in processors for your pipeline.
- See Pipelines & lifecycle for shutdown, feedable pipelines, and the registry.
- Check the Crate map for feature flags and optional crates.