Update examples to use StreamNode and Series with lookback
The `PipelineNode` has been refactored into `StreamNode`. This commit updates the example files to reflect this change and also updates the `series` constructor to accept a lookback parameter. The `PipelineNode` was an artifact of a previous implementation and is no longer needed. The `StreamNode` is the appropriate type for representing the output of a pipe operation. The `series` function now requires a `lookback` argument to specify the maximum number of items to store. This makes the behavior of series more explicit and prevents accidental unbounded memory growth.
This commit is contained in:
+42
-49
@@ -18,32 +18,28 @@ use crate::ast::types::{Purity, Signature};
|
||||
pub struct RingBuffer<T> {
|
||||
buffer: VecDeque<T>,
|
||||
total_count: u64,
|
||||
}
|
||||
|
||||
impl<T> Default for RingBuffer<T> {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
lookback_limit: usize,
|
||||
}
|
||||
|
||||
impl<T> RingBuffer<T> {
|
||||
pub fn new() -> Self {
|
||||
pub fn new(lookback_limit: usize) -> Self {
|
||||
// Pre-allocate up to 500 elements to minimize heap fragmentation
|
||||
let initial_capacity = std::cmp::min(lookback_limit, 500);
|
||||
Self {
|
||||
buffer: VecDeque::new(),
|
||||
buffer: VecDeque::with_capacity(initial_capacity),
|
||||
total_count: 0,
|
||||
lookback_limit,
|
||||
}
|
||||
}
|
||||
|
||||
/// Adds an element. Removes the oldest if the lookback limit is reached.
|
||||
#[inline]
|
||||
pub fn push(&mut self, item: T, lookback: Option<usize>) {
|
||||
pub fn push(&mut self, item: T) {
|
||||
self.buffer.push_back(item);
|
||||
self.total_count += 1;
|
||||
|
||||
if let Some(limit) = lookback {
|
||||
while self.buffer.len() > limit {
|
||||
self.buffer.pop_front();
|
||||
}
|
||||
while self.buffer.len() > self.lookback_limit {
|
||||
self.buffer.pop_front();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -107,9 +103,9 @@ pub struct ScalarSeries<T: ScalarValue> {
|
||||
}
|
||||
|
||||
impl<T: ScalarValue> ScalarSeries<T> {
|
||||
pub fn new(type_name: &'static str) -> Self {
|
||||
pub fn new(type_name: &'static str, lookback_limit: usize) -> Self {
|
||||
Self {
|
||||
data: std::cell::RefCell::new(RingBuffer::new()),
|
||||
data: std::cell::RefCell::new(RingBuffer::new(lookback_limit)),
|
||||
type_name,
|
||||
}
|
||||
}
|
||||
@@ -161,16 +157,10 @@ pub struct ValueSeries {
|
||||
pub data: std::cell::RefCell<RingBuffer<Value>>,
|
||||
}
|
||||
|
||||
impl Default for ValueSeries {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl ValueSeries {
|
||||
pub fn new() -> Self {
|
||||
pub fn new(lookback_limit: usize) -> Self {
|
||||
Self {
|
||||
data: std::cell::RefCell::new(RingBuffer::new()),
|
||||
data: std::cell::RefCell::new(RingBuffer::new(lookback_limit)),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -213,36 +203,36 @@ impl Series for ValueSeries {
|
||||
/// of the exact type (Type Erasure for member arrays).
|
||||
pub trait SeriesMember: Object + Series {
|
||||
/// Pushes a dynamic Value into the series, performing the necessary downcast.
|
||||
fn push_value(&self, value: Value, limit: Option<usize>);
|
||||
fn push_value(&self, value: Value);
|
||||
}
|
||||
|
||||
impl SeriesMember for ScalarSeries<f64> {
|
||||
fn push_value(&self, value: Value, limit: Option<usize>) {
|
||||
fn push_value(&self, value: Value) {
|
||||
if let Some(v) = value.as_float() {
|
||||
self.data.borrow_mut().push(v, limit);
|
||||
self.data.borrow_mut().push(v);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl SeriesMember for ScalarSeries<i64> {
|
||||
fn push_value(&self, value: Value, limit: Option<usize>) {
|
||||
fn push_value(&self, value: Value) {
|
||||
if let Some(v) = value.as_int() {
|
||||
self.data.borrow_mut().push(v, limit);
|
||||
self.data.borrow_mut().push(v);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl SeriesMember for ScalarSeries<bool> {
|
||||
fn push_value(&self, value: Value, limit: Option<usize>) {
|
||||
fn push_value(&self, value: Value) {
|
||||
if let Value::Bool(v) = value {
|
||||
self.data.borrow_mut().push(v, limit);
|
||||
self.data.borrow_mut().push(v);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl SeriesMember for ValueSeries {
|
||||
fn push_value(&self, value: Value, limit: Option<usize>) {
|
||||
self.data.borrow_mut().push(value, limit);
|
||||
fn push_value(&self, value: Value) {
|
||||
self.data.borrow_mut().push(value);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -260,7 +250,7 @@ pub struct RecordSeries {
|
||||
|
||||
impl RecordSeries {
|
||||
/// Creates a new RecordSeries matching the given layout.
|
||||
pub fn new(layout: Arc<RecordLayout>) -> Self {
|
||||
pub fn new(layout: Arc<RecordLayout>, lookback_limit: usize) -> Self {
|
||||
let mut fields: Vec<Rc<std::cell::RefCell<dyn SeriesMember>>> =
|
||||
Vec::with_capacity(layout.fields.len());
|
||||
|
||||
@@ -269,15 +259,17 @@ impl RecordSeries {
|
||||
let member: Rc<std::cell::RefCell<dyn SeriesMember>> = match static_type {
|
||||
StaticType::Float => Rc::new(std::cell::RefCell::new(ScalarSeries::<f64>::new(
|
||||
"FloatSeries",
|
||||
lookback_limit,
|
||||
))),
|
||||
StaticType::Int | StaticType::DateTime => Rc::new(std::cell::RefCell::new(
|
||||
ScalarSeries::<i64>::new("IntSeries"),
|
||||
ScalarSeries::<i64>::new("IntSeries", lookback_limit),
|
||||
)),
|
||||
StaticType::Bool => Rc::new(std::cell::RefCell::new(ScalarSeries::<bool>::new(
|
||||
"BoolSeries",
|
||||
lookback_limit,
|
||||
))),
|
||||
// Fallback for everything else (Text, Lists, nested Records without SoA, etc.)
|
||||
_ => Rc::new(std::cell::RefCell::new(ValueSeries::new())),
|
||||
_ => Rc::new(std::cell::RefCell::new(ValueSeries::new(lookback_limit))),
|
||||
};
|
||||
fields.push(member);
|
||||
}
|
||||
@@ -547,23 +539,24 @@ impl Series for SharedRecordSeries {
|
||||
// ============================================================================
|
||||
|
||||
pub fn register(env: &Environment) {
|
||||
// (series template_or_type_keyword) -> Series
|
||||
// (series lookback_limit template_or_type_keyword) -> Series
|
||||
env.register_native_fn(
|
||||
"series",
|
||||
StaticType::Function(Box::new(Signature {
|
||||
params: StaticType::Tuple(vec![StaticType::Any]),
|
||||
params: StaticType::Tuple(vec![StaticType::Int, StaticType::Any]),
|
||||
ret: StaticType::Any,
|
||||
})),
|
||||
Purity::Impure,
|
||||
|args: &[Value]| {
|
||||
let arg = &args[0];
|
||||
let lookback_limit = args[0].as_int().unwrap_or(0) as usize;
|
||||
let arg = &args[1];
|
||||
match arg {
|
||||
Value::Keyword(k) => {
|
||||
match k.name().to_lowercase().as_str() {
|
||||
"float" => Value::Object(Rc::new(ScalarSeries::<f64>::new("FloatSeries"))),
|
||||
"int" | "datetime" => Value::Object(Rc::new(ScalarSeries::<i64>::new("IntSeries"))),
|
||||
"bool" => Value::Object(Rc::new(ScalarSeries::<bool>::new("BoolSeries"))),
|
||||
"text" => Value::Object(Rc::new(ValueSeries::new())),
|
||||
"float" => Value::Object(Rc::new(ScalarSeries::<f64>::new("FloatSeries", lookback_limit))),
|
||||
"int" | "datetime" => Value::Object(Rc::new(ScalarSeries::<i64>::new("IntSeries", lookback_limit))),
|
||||
"bool" => Value::Object(Rc::new(ScalarSeries::<bool>::new("BoolSeries", lookback_limit))),
|
||||
"text" => Value::Object(Rc::new(ValueSeries::new(lookback_limit))),
|
||||
_ => panic!("Unknown or unsupported series type keyword: :{}", k.name()),
|
||||
}
|
||||
}
|
||||
@@ -586,9 +579,9 @@ pub fn register(env: &Environment) {
|
||||
fields.push((*name, ty));
|
||||
}
|
||||
let final_layout = RecordLayout::get_or_create(fields);
|
||||
Value::Object(Rc::new(RecordSeries::new(final_layout)))
|
||||
Value::Object(Rc::new(RecordSeries::new(final_layout, lookback_limit)))
|
||||
}
|
||||
_ => panic!("series expects a type keyword (:float, :int...) or a schema record (e.g., {{:price :float}})"),
|
||||
_ => panic!("series expects a lookback_limit and a type keyword (:float, :int...) or a schema record (e.g., {{:price :float}})"),
|
||||
}
|
||||
},
|
||||
);
|
||||
@@ -612,20 +605,20 @@ pub fn register(env: &Environment) {
|
||||
if let Value::Record(_, values) = val {
|
||||
for (i, v) in values.iter().enumerate() {
|
||||
if let Some(f) = rs.fields.get(i) {
|
||||
f.borrow().push_value(v.clone(), None);
|
||||
f.borrow().push_value(v.clone());
|
||||
}
|
||||
}
|
||||
} else {
|
||||
panic!("push to RecordSeries expects a record");
|
||||
}
|
||||
} else if let Some(s) = any.downcast_ref::<ScalarSeries<f64>>() {
|
||||
s.push_value(val.clone(), None);
|
||||
s.push_value(val.clone());
|
||||
} else if let Some(s) = any.downcast_ref::<ScalarSeries<i64>>() {
|
||||
s.push_value(val.clone(), None);
|
||||
s.push_value(val.clone());
|
||||
} else if let Some(s) = any.downcast_ref::<ScalarSeries<bool>>() {
|
||||
s.push_value(val.clone(), None);
|
||||
s.push_value(val.clone());
|
||||
} else if let Some(s) = any.downcast_ref::<ValueSeries>() {
|
||||
s.push_value(val.clone(), None);
|
||||
s.push_value(val.clone());
|
||||
} else {
|
||||
panic!("Object is not a mutable series");
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user