feat: Add streams module for reactive pipeline

Introduces the streams module, defining core reactive primitives like
Signal,
Stream, Observer, RootStream, and PipeStream. This module lays the
groundwork
for building reactive data processing pipelines.

Includes new types for managing reactive data flow and a basic test
suite to
verify the functionality of RootStream and PipeStream.
This commit is contained in:
Michael Schimmel
2026-03-01 21:33:37 +01:00
parent b74ddcfd61
commit a98e51c762
3 changed files with 288 additions and 0 deletions
+81
View File
@@ -349,6 +349,87 @@ impl crate::ast::types::Series for SeriesView {
}
}
// ============================================================================
// 4b. Shared Series (Read-Only Reactive Storage)
// ============================================================================
/// A read-only series that shares its underlying buffer with the pipeline engine.
/// This is the basis for the "SharedSeries" in the Dual Series Architecture.
#[derive(Clone)]
pub struct SharedSeries<T: ScalarValue> {
pub buffer: Rc<std::cell::RefCell<RingBuffer<T>>>,
pub type_name: &'static str,
pub converter: fn(T) -> Value,
}
impl<T: ScalarValue> Debug for SharedSeries<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "SharedSeries<{}>[len: {}]", self.type_name, self.buffer.borrow().len())
}
}
impl<T: ScalarValue> Object for SharedSeries<T> {
fn type_name(&self) -> &'static str {
self.type_name
}
fn as_any(&self) -> &dyn Any {
self
}
fn as_series(&self) -> Option<&dyn crate::ast::types::Series> {
Some(self)
}
}
impl<T: ScalarValue> crate::ast::types::Series for SharedSeries<T> {
fn get_item(&self, index: usize) -> Option<Value> {
self.buffer.borrow().get(index).map(|&v| (self.converter)(v))
}
}
/// A read-only series for Records that shares its buffers with the pipeline.
#[derive(Clone)]
pub struct SharedRecordSeries {
pub layout: Arc<RecordLayout>,
/// Each field shares an Rc<RefCell<RingBuffer>> with the Pipe that produces it.
pub field_buffers: Vec<Rc<std::cell::RefCell<dyn SeriesMember>>>,
}
impl Debug for SharedRecordSeries {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "SharedRecordSeries[fields: {}]", self.field_buffers.len())
}
}
impl Object for SharedRecordSeries {
fn type_name(&self) -> &'static str {
"SharedRecordSeries"
}
fn as_any(&self) -> &dyn Any {
self
}
fn as_series(&self) -> Option<&dyn crate::ast::types::Series> {
Some(self)
}
}
impl crate::ast::types::Series for SharedRecordSeries {
fn get_item(&self, index: usize) -> Option<Value> {
if self.field_buffers.is_empty() {
return None;
}
let mut vals = Vec::with_capacity(self.field_buffers.len());
for buffer in &self.field_buffers {
match buffer.borrow().get_value(index) {
Some(v) => vals.push(v),
None => return None,
}
}
Some(Value::Record(self.layout.clone(), Rc::new(vals)))
}
}
// ============================================================================
// 5. Script Integration (RTL Registration)
// ============================================================================