From 32068fc8f5a29e84d42ac84dda9d99958eacf778 Mon Sep 17 00:00:00 2001 From: Dennis Kobert Date: Tue, 4 Aug 2026 23:24:50 +0200 Subject: [PATCH] Remove concept of sampled nodes --- node-graph/rfcs/attribute-model.md | 43 +++++++++++------------------- 1 file changed, 16 insertions(+), 27 deletions(-) diff --git a/node-graph/rfcs/attribute-model.md b/node-graph/rfcs/attribute-model.md index 183e0c659d..8653a60737 100644 --- a/node-graph/rfcs/attribute-model.md +++ b/node-graph/rfcs/attribute-model.md @@ -330,7 +330,6 @@ that decomposition is hoisted per run. | plain parameters | wired value inputs | as today | | `impl Node, Output = Concrete>` | lazy value input | the value flows, attributes do not | | `impl Node, Output = T>` (unbounded) | source of an opaque record family | routing, see below | -| `impl SampledNode, Output = T>` (unbounded) | sampled record input | routing at a kernel-chosen index, see below | | `-> List` with `level_extent =` | per-lane level production | structural skeleton emitted by the macro | | `-> List` without | store form | whole-level body, node owns storage | @@ -344,8 +343,8 @@ the tuple's, so multi-write lanes pay no extra nesting. A literal the feature gate while parsing the item, before attribute macros run. The rule behind all the lazy forms: kernels control whether, when, and -(for sampled inputs) at which index their inputs are evaluated, but -never how the records move. Attributes travel inside record values or +at which index their inputs are evaluated, but never how the records +move. Attributes travel inside record values or through generated machinery, so kernel-controlled evaluation cannot misalign them, and domain declarations stay with the extent system. @@ -408,8 +407,7 @@ multiplexers, and per-lane data-driven choice among inputs are all plain kernels over the same mechanism, and none of them needs anything from the macro beyond the family lowering. Whole-list switching vs. per-item zip is just the residency of the condition: an invariant condition -collapses through nullification, a varying one selects per lane. This -is why Bundle and Unbundle are no longer needed. +collapses through nullification, a varying one selects per lane. The one-source shape also covers the registry's infrastructure rows. Monitor, context modification, memoize, and the lend and clone adapters @@ -428,38 +426,28 @@ The genuine conversion rows (the Into and Convert matrix) remain, because those do real per-type work; the type-erased attribute conversion rows disappear with the representation they serve. -Sampling extends routing with a chosen index. A sampled input's handle -takes the index as an evaluation argument, and the value it yields -carries the record of that lane. The rule that used to forbid -kernel-chosen indices existed to protect attribute alignment, and record -values dissolve it: the record moves as one unit, so a sampled row -cannot come apart. Soundness needs one addition, because a different -index yields a genuinely different record rather than an identical one: -each sampled input gets an intermediate slot in the sampling node's -frame allocation, and a sampled value is stable until the same input is -sampled again. One live sample per declared input, and a kernel that -needs two rows of one input side by side declares the input twice. - -This makes index-computable reorders plain kernels: +A kernel that modifies the index on the context evaluates an input at a +lane other than its own, which makes index-computable reorders plain +kernels: ```rs /// Reverses the order of the input list. #[node_macro::node(category("General"))] fn reverse( - ctx: impl Ctx + ExtractIndex, + ctx: impl Ctx + DeriveCtx + ModifyIndex, _: (), - content: impl SampledNode, Output = T>, + content: impl Node, Output = T>, ) -> T { - content.eval(ctx, content.extent() - 1 - ctx.index()) + content.eval(&ctx.with_index(content.extent(&ctx)? - 1 - ctx.innermost_index())) } ``` Shift, slice, and read-item-at-index are the same shape. Sort and shuffle still compute a whole-extent permutation once per sweep, which a pure per-lane kernel cannot hold, so they keep the remap-returning -kernel. Applying a remap now has a spec, though: per lane, sample the -input at the permuted index. The generated batch kernel is the -law-bound override of that spec, materializing the input's columns once +kernel. Applying a remap has a spec: per lane, evaluate the input at the +permuted index. The generated batch kernel is the law-bound override of +that spec, materializing the input's columns once and gathering each index-varying column through the wiring-resolved table with index-invariant columns skipped (about 1.4ns per varying column per lane; the comparison work of the sort itself does not depend @@ -578,9 +566,10 @@ from shading languages for residency. contain. - The graph UX of the map/enter construct, and whether attribute-typed parameters can ever be ordinary exposed inputs. -- Naming: `Attribute` trait vs. `Attr` wrapper, whether the authoring - `List` sharing the wire type's name helps or confuses, and the sampled - input trait's name. +- Naming: `Attribute` trait vs. `Attr` wrapper, and whether the + authoring `List` sharing the wire type's name helps or confuses. +- Whether evaluating at a lane outside the input's extent is clamped, + wrapped, or a debug assertion. - `List>` outputs, i.e. one node pushing two levels. - How chatty the editor boundary becomes per frame, given that tools consume materialized views today.