//! The evaluation context (C8): the read-side window access a node sees in //! `eval`. The engine sizes and types each input from the node's `schema` at //! wiring, so the typed accessors below treat a kind mismatch as an engine bug //! (panic), not a user-facing error. use crate::{AnyColumn, Timestamp, Window}; /// Read-only, zero-copy view of a node's inputs for one `eval`, in schema /// order, plus the cycle's timestamp (C4). `Copy` because it is just a borrow of /// the input slice plus a `Copy` timestamp. #[derive(Clone, Copy)] pub struct Ctx<'a> { inputs: &'a [AnyColumn], now: Timestamp, } impl<'a> Ctx<'a> { /// Wrap the per-input columns (in schema-declared order) and the cycle /// timestamp for one `eval`. pub fn new(inputs: &'a [AnyColumn], now: Timestamp) -> Self { Self { inputs, now } } /// The current cycle's timestamp (C4). Causal — the present cycle's /// timestamp, never the future (C2) — so reading it introduces no look-ahead. pub fn now(&self) -> Timestamp { self.now } /// Zero-copy `f64` window into input `i` (index 0 = newest). Panics if input /// `i` is not an `f64` edge — a wiring bug, never reachable from a correctly /// wired graph. pub fn f64_in(&self, i: usize) -> Window<'a, f64> { let inputs: &'a [AnyColumn] = self.inputs; inputs[i] .as_f64() .expect("input kind mismatch (checked at wiring) — engine bug") .window() } /// Zero-copy `i64` window into input `i` (index 0 = newest). See `f64_in`. pub fn i64_in(&self, i: usize) -> Window<'a, i64> { let inputs: &'a [AnyColumn] = self.inputs; inputs[i] .as_i64() .expect("input kind mismatch (checked at wiring) — engine bug") .window() } /// Zero-copy `bool` window into input `i` (index 0 = newest). See `f64_in`. pub fn bool_in(&self, i: usize) -> Window<'a, bool> { let inputs: &'a [AnyColumn] = self.inputs; inputs[i] .as_bool() .expect("input kind mismatch (checked at wiring) — engine bug") .window() } /// Zero-copy `timestamp` window into input `i` (index 0 = newest). See /// `f64_in`. pub fn ts_in(&self, i: usize) -> Window<'a, Timestamp> { let inputs: &'a [AnyColumn] = self.inputs; inputs[i] .as_ts() .expect("input kind mismatch (checked at wiring) — engine bug") .window() } } #[cfg(test)] mod tests { use super::*; use crate::{Scalar, ScalarKind, Timestamp}; #[test] fn ctx_hands_financial_indexed_windows() { let mut inputs = vec![AnyColumn::with_capacity(ScalarKind::F64, 4)]; for v in [10.0_f64, 20.0, 30.0] { inputs[0].push(Scalar::f64(v)).unwrap(); } let ctx = Ctx::new(&inputs, Timestamp(0)); let w = ctx.f64_in(0); assert_eq!(w.len(), 3); assert_eq!(w[0], 30.0); // newest assert_eq!(w[2], 10.0); // oldest } #[test] fn ctx_addresses_multiple_inputs() { let mut inputs = vec![ AnyColumn::with_capacity(ScalarKind::F64, 2), AnyColumn::with_capacity(ScalarKind::I64, 2), ]; inputs[0].push(Scalar::f64(1.5)).unwrap(); inputs[1].push(Scalar::i64(42)).unwrap(); let ctx = Ctx::new(&inputs, Timestamp(0)); assert_eq!(ctx.f64_in(0)[0], 1.5); assert_eq!(ctx.i64_in(1)[0], 42); } #[test] #[should_panic(expected = "engine bug")] fn ctx_panics_on_kind_mismatch() { let mut inputs = vec![AnyColumn::with_capacity(ScalarKind::I64, 2)]; inputs[0].push(Scalar::i64(7)).unwrap(); let ctx = Ctx::new(&inputs, Timestamp(0)); let _ = ctx.f64_in(0); // wrong kind → panic } #[test] fn ctx_now_returns_cycle_timestamp() { let inputs: Vec = vec![]; let ctx = Ctx::new(&inputs, Timestamp(42)); assert_eq!(ctx.now(), Timestamp(42)); } }