da37f596f2c7d3fee5a68376d5c4cca2734aaa2c
4 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
82635fa259 |
refactor: Cell becomes the hot-path value carrier (C7/C8 realization)
Motivation ---------- The C7 realization note ( |
||
|
|
cd3d1ca9ed |
refactor(aura-core): split Scalar into a tag-free Cell + ScalarKind
Motivation
----------
`Scalar` was a tagged enum (I64/F64/Bool/Ts), so every scalar value
physically carried its own kind tag. But the kind is already known from
the schema/port/column the value flows through (C7: the type is a
property of the column, not of the value — the hot path is already
columnar `Column<T>`, and `AnyColumn::get` *reconstructs* the tag from
the column on the way out). The per-value tag was therefore redundant
with the kind the surrounding context already holds.
That redundancy had three costs:
* It baked an implicit `match` (a branch) into every function that read
a Scalar payload — even where the caller statically knew the type.
The tag could never be exploited away.
* Size: a tagged enum is tag + payload = 16 bytes (f64/i64 alignment),
twice the 8 bytes the value needs. A `Column<Scalar>` would be double
the memory and half the cache utilisation.
* It is the shared root of several downstream papercuts we keep hitting
— the lossy f64 manifest field, the `unreachable!` panic on a
non-numeric param, the serde-tag question — all symptoms of "the type
is baked into the value".
Change
------
Introduce `Cell`: a type-erased 64-bit word (`struct Cell(u64)`) that is
not readable without external type context. It is constructed per base
type (`from_i64/from_f64/from_bool/from_ts`) and read only by naming the
type at the call site (`i64()/f64()/bool()/ts()`) — each a branch-free
bit-cast. The hot path resolves the kind once at the boundary (from the
schema) and then reads natively, with no per-value branch. `Cell` knows
nothing of `Scalar` or `ScalarKind`; the dependency is strictly one-way,
and it lives in its own `cell.rs` (more is planned on top of it).
`Scalar` becomes `struct { kind: ScalarKind, cell: Cell }` — the
self-describing form for the dynamic boundaries (builder binding,
serialization, rendering), built on top of `Cell`. Its `as_*` accessors
now `debug_assert` the kind and return the native value (free in
release); calling the wrong accessor is a caller bug, not a checked
`Option`. The variant constructors `Scalar::I64(..)` become associated
fns `Scalar::i64(..)`.
`PartialEq` is hand-written (not derived) to preserve the former enum's
value semantics: kinds must match, then native payloads compare, so f64
keeps IEEE-754 behaviour (`NaN != NaN`, `+0.0 == -0.0`) and a kind
mismatch is never equal even when the raw words coincide. A fixture
(`scalar_eq_is_value_not_bitwise`) pins exactly the cases where bit- and
value-equality diverge, so it can't silently regress. `Cell`'s own
`Eq`/`Hash` stay bitwise — correct for a raw word.
The change is behaviour-preserving: Scalar's observable behaviour is
identical to the pre-Cell enum (the value-equality fixture proves it);
only the internal representation changed. The ~440 call sites across the
workspace are a mechanical constructor rename plus ~12 destructuring
sites (match-arms / `let`-patterns) rewritten to `kind()` + `as_*`.
Verified: cargo build --workspace --all-targets, cargo clippy --workspace
--all-targets -- -D warnings, cargo test --workspace — all green.
|
||
|
|
77a1d26017 |
feat(core): the node contract — Node, schema/eval, Ctx, and a worked SMA
Cycle 0002, the second walking-skeleton slice on top of the 0001 substrate:
the interface every node forever implements (C8), proven end-to-end by a real
node with no engine present.
- `Node` (aura-core) — `schema() -> NodeSchema` declares inputs (kind +
lookback) and the single output kind; `eval(&mut self, Ctx) -> Option<Scalar>`
computes one cycle's output (`None` = filter / not-yet-warmed-up). `&mut self`
so a node may keep its own derived state.
- `Ctx<'a>` (aura-core) — a `Copy` borrow-wrapper handing `eval` zero-copy,
financial-indexed `Window`s per input (`ctx.f64_in(i)[k]`, index 0 = newest).
A kind mismatch panics ("engine bug") — wiring guarantees the kind, so a
mismatch can only mean the wiring layer is broken; this keeps node-author code
clean (`w[k]`, not `w?[k]`).
- `AnyColumn::as_f64/as_i64/as_bool/as_ts` (aura-core) — the read-side mirror of
the existing `as_*_mut`, the mechanism `Ctx` uses to get a typed window from a
type-erased edge. Closes the read-side gap the cycle-0001 audit recorded.
- `Sma` (aura-std) — the skeleton's first real block: a producer node computing
the moving mean of one f64 input, emitting `None` until warmed up. Authored in
a downstream crate (proving aura-core's contract is usable across the crate
boundary) and driven by a hand-written test that mimics exactly what the sim
loop will later generalize: push fresh input, eval, collect.
Deliberately deferred (spec 0002 "Out of scope", recorded as decisions): the
firing policies (C6 — InputSpec carries no firing field yet), the sim loop (C4)
and freshness gating (C5), schema-level tunable params (C12/C19), and the
no-output sink refinement (C8 consumer side).
Gates green: cargo build/test (20: 18 aura-core + 2 aura-std)/clippy -D warnings
all clean; surface-purity grep (no dyn-Any / Rc / RefCell) clean.
refs walking-skeleton
|
||
|
|
a4760497ec |
feat(core): the streaming substrate — scalars, columns, type-erased edges
Cycle 0001 of the walking-skeleton milestone: the aura-core data substrate
everything streams through.
- scalar.rs: the closed four-type set — Scalar (Copy POD carrier), ScalarKind
(the kind tag), Timestamp (i64 epoch-ns newtype). No boxed Value, no dyn Any,
no heap (C7).
- column.rs: Column<T>, a fixed-capacity bounded-lookback ring pre-sized at
construction (no hot-loop realloc, C8), financial-indexed (index 0 = newest),
carrying a monotonic run_count freshness primitive (C5). Window<'_,T> is its
zero-copy read view with newest-at-0 Index. No RefCell/Rc — a sim is
single-threaded (C1), so push is &mut and read is &.
- any.rs: AnyColumn, the type-erased edge over the four Column kinds. push()
kind-checks at the edge (wiring time) and rejects a wrong-kind value with
KindMismatch, leaving the column untouched; the hot path bypasses the check
via the monomorphic as_*_mut accessors (C7).
- error.rs: KindMismatch { expected, got }, the guard error.
Sharpens the RustAst rtl/ reference (VecDeque + RefCell + boxed Value) into a
fixed ring of Copy scalars with direct dispatch. Node trait, Ctx, firing
policies, and the sim loop are deferred to subsequent cycles.
Verified independently: cargo build --workspace (0 warnings), cargo test
--workspace (14 aura-core tests green incl. ring-wrap, run_count monotonicity,
zero-copy window, and the wrong_kind_push_is_rejected guard), cargo clippy
--workspace --all-targets -D warnings (clean), and a surface-purity grep
confirming no RefCell/Rc/dyn Any on the substrate.
refs #0001-core-streaming-substrate
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|