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:
+42
-20
@@ -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) {
|
||||
|
||||
Reference in New Issue
Block a user