feat: Implement Pipeline support

Adds support for pipelines, allowing streams to be chained together and
transformed.
This includes new types for `PipeStream` and `SourceAdapter`, along with
modifications
to the `Environment` to manage pipeline execution. A new example
`pipeline.myc`
demonstrates its usage.
This commit is contained in:
Michael Schimmel
2026-03-02 00:32:07 +01:00
parent 807903efbb
commit 2457eec1bf
7 changed files with 103 additions and 29 deletions
+42 -20
View File
@@ -24,6 +24,19 @@ pub trait Observer {
fn notify(&mut self, source_index: usize, cycle_id: u64, value: Value);
}
/// A lightweight adapter to map an unknown source index to a specific target index.
/// This prevents index collisions when a Pipe listens to multiple independent RootStreams.
pub struct SourceAdapter {
pub target: Rc<RefCell<dyn Observer>>,
pub target_index: usize,
}
impl Observer for SourceAdapter {
fn notify(&mut self, _ignored_source: usize, cycle_id: u64, value: Value) {
self.target.borrow_mut().notify(self.target_index, cycle_id, value);
}
}
/// Polymorphic Interface for any stream that can accept observers (like Delphi's IStream).
pub trait ObservableStream {
fn add_observer(&self, observer: Rc<RefCell<dyn Observer>>);
@@ -95,6 +108,12 @@ impl ObservableStream for RootStream {
}
}
impl Default for RootStream {
fn default() -> Self {
Self::new()
}
}
impl RootStream {
pub fn new() -> Self {
Self {
@@ -136,32 +155,33 @@ pub struct PipeStream {
/// For the MVP, we assume the Pipe is notified by the Root or its parents.
pub input_count: usize,
/// Tracks the last cycle_id received from each input.
last_cycle_per_input: RefCell<Vec<u64>>,
last_cycle_per_input: Vec<u64>,
/// Stores the current value for each input to construct the argument tuple.
current_values: Vec<Value>,
/// The current output signal of this pipe.
current_signal: RefCell<Option<Signal>>,
/// The VM closure (lambda) to execute.
pub lambda: Option<Rc<dyn crate::ast::types::Object>>,
/// The executable closure representing the Lambda. Expects a Vec of arguments.
pub executor: Option<Box<dyn FnMut(Vec<Value>) -> Value>>,
/// Observers of THIS pipe.
observers: RefCell<Vec<Rc<RefCell<dyn Observer>>>>,
}
impl PipeStream {
pub fn new(name: String, input_count: usize, lambda: Option<Rc<dyn crate::ast::types::Object>>) -> Self {
pub fn new(
name: String,
input_count: usize,
executor: Option<Box<dyn FnMut(Vec<Value>) -> Value>>
) -> Self {
Self {
name,
input_count,
last_cycle_per_input: RefCell::new(vec![0; input_count]),
last_cycle_per_input: vec![0; input_count],
current_values: vec![Value::Void; input_count],
current_signal: RefCell::new(None),
lambda,
executor,
observers: RefCell::new(Vec::new()),
}
}
/// Internal: Checks if all inputs have reached the target cycle.
fn is_barrier_reached(&self, cycle_id: u64) -> bool {
let cycles = self.last_cycle_per_input.borrow();
cycles.iter().all(|&c| c == cycle_id)
}
}
impl ObservableStream for PipeStream {
@@ -179,22 +199,24 @@ impl Stream for PipeStream {
impl Observer for PipeStream {
fn notify(&mut self, source_index: usize, cycle_id: u64, value: Value) {
let barrier_reached = {
let mut cycles = self.last_cycle_per_input.borrow_mut();
if source_index < self.input_count {
cycles[source_index] = cycle_id;
self.last_cycle_per_input[source_index] = cycle_id;
self.current_values[source_index] = value;
}
// Check if all inputs reached the same cycle.
cycles.iter().all(|&c| c == cycle_id)
self.last_cycle_per_input.iter().all(|&c| c == cycle_id)
};
if barrier_reached {
// 1. Prepare Arguments for Lambda (Current values of all inputs)
let args = vec![value]; // Simplified for now
let args = self.current_values.clone();
// 2. Execute Lambda (This requires a VM instance!)
// TODO: Actually execute the lambda using a VM context.
// For now, we mock the result to be the input value itself.
let result = args[0].clone();
// 2. Execute Lambda using the encapsulated VM executor
let result = if let Some(exec) = &mut self.executor {
exec(args)
} else {
self.current_values[0].clone() // Identity bypass (defaults to first input)
};
// 3. Handle Void case! (Filter pattern)
if matches!(result, Value::Void) {