From c0f50d41d71f3f6e2a798697dcc272b8f26b5263 Mon Sep 17 00:00:00 2001 From: Dennis Kobert Date: Sat, 22 Aug 2026 15:55:49 +0000 Subject: [PATCH] Drop the manual extent overrides in favor of the derivation --- .../libraries/graphic-types/src/graphic.rs | 14 ++++++++-- node-graph/node-macro/src/codegen.rs | 27 ++++++++++++++++--- .../nodes/gcore/src/context_modification.rs | 9 ++----- node-graph/nodes/gcore/src/debug.rs | 1 - node-graph/nodes/gcore/src/memo.rs | 21 +++------------ node-graph/nodes/gcore/src/ops.rs | 6 +---- 6 files changed, 43 insertions(+), 35 deletions(-) diff --git a/node-graph/libraries/graphic-types/src/graphic.rs b/node-graph/libraries/graphic-types/src/graphic.rs index 6582c67b2b..8813887580 100644 --- a/node-graph/libraries/graphic-types/src/graphic.rs +++ b/node-graph/libraries/graphic-types/src/graphic.rs @@ -200,16 +200,26 @@ pub fn is_paint_present(graphic_list: &List) -> bool { /// Look up the paint graphics stored under attribute for a vector item, in the canonical `List` form. pub fn graphic_list_at<'a>(list: &'a List, index: usize, attribute: &str) -> Option>> { - list.attribute::>(attribute, index) + paint_at(list, index, attribute) .map(Cow::Borrowed) // Treat a blank paint attribute as absent so an empty attribute doesn't count as painted .filter(|graphic_list| is_paint_present(graphic_list)) } +/// The paint attribute's list, in either transitional storage form: the +/// canonical `List` value, or the fill marker's owned +/// `Option>` clone as the render bridge copies it. +fn paint_at<'a, T>(list: &'a List, index: usize, attribute: &str) -> Option<&'a List> { + if let Some(graphic_list) = list.attribute::>(attribute, index) { + return Some(graphic_list); + } + list.attribute::>>(attribute, index).and_then(|optional| optional.as_ref()) +} + /// Whether the item carries a non-blank canonical `List` paint attribute, /// checked by borrowing without cloning the renderable list. pub fn has_paint_at(list: &List, index: usize, attribute: &str) -> bool { - list.attribute::>(attribute, index).is_some_and(is_paint_present) + paint_at(list, index, attribute).is_some_and(is_paint_present) } /// Stores a paint attribute in its canonical `List` form, the only representation paint readers accept. diff --git a/node-graph/node-macro/src/codegen.rs b/node-graph/node-macro/src/codegen.rs index 9ae2e53729..2b74706558 100644 --- a/node-graph/node-macro/src/codegen.rs +++ b/node-graph/node-macro/src/codegen.rs @@ -1442,11 +1442,32 @@ pub(crate) fn generate_node_impl(crate_ident: &CrateIdent, parsed: &ParsedNodeFn } } } else if let Some(subject_index) = ir::forwarded_subject(&node).filter(|_| node.output.shape.depth == 0) { - // A level-preserving passthrough forwards its subject's extents. - let name = ®ular_fields[subject_index].pat_ident.ident; + // A level-preserving passthrough forwards its subject's extents, + // through the same per-binding query forms the explicit surface uses. + let field = ®ular_fields[subject_index]; + let name = &field.pat_ident.ident; + let query = match &field.ty { + ParsedFieldType::Node(_) => match ir::lazy_binding(&node, subject_index) { + ir::LazyBinding::DeriveRouting | ir::LazyBinding::DeriveCarrier => quote! { + let __query = |__copy: u64, __lvl: u8| { + let __head = #core_types::context::DeriveCtx::index_head(__input); + #core_types::record::DerivedRecordEdge::extent_at_derived(&self.#name, &#core_types::context::DeriveCtx::promoted(__input, &__head, __copy), __lvl) + }; + }, + _ => quote! { + let __query = |_: u64, __lvl: u8| #core_types::node::Node::extent_at(&self.#name, __input, __lvl); + }, + }, + ParsedFieldType::Regular(_) => quote! { + let __query = |_: u64, __lvl: u8| #core_types::node::Node::extent_at(&self.#name, __input, __lvl); + }, + }; quote! { fn extent_at(&self, __input: &#ctx_ident, __level: u8) -> #core_types::gpoll::GPoll<#core_types::gpoll::Extent> { - #core_types::node::Node::extent_at(&self.#name, __input, __level) + #query + let __arg = #core_types::extent::ExtentIn::new(&__query); + let __level_in = #core_types::extent::LevelIn::new(__level, >::layout(self).depth); + __arg.at(__level_in) } } } else if node.output.shape.depth > 0 { diff --git a/node-graph/nodes/gcore/src/context_modification.rs b/node-graph/nodes/gcore/src/context_modification.rs index ba11716c02..ef62f277e2 100644 --- a/node-graph/nodes/gcore/src/context_modification.rs +++ b/node-graph/nodes/gcore/src/context_modification.rs @@ -1,10 +1,9 @@ use core_types::context::{ContextModification, Ctx, DeriveCtx}; -use core_types::extent::{ExtentIn, LevelIn, ValueIn}; -use core_types::gpoll::{Extent, GPoll, Interrupt}; +use core_types::gpoll::Interrupt; /// Filters out what should be unused components of the context based on the specified requirements. /// This node is inserted by the compiler to "zero out" unused context components. -#[node_macro::node(category(""), extent(context_modification_extent))] +#[node_macro::node(category(""))] fn context_modification( ctx: impl Ctx + DeriveCtx, /// The data to pass through, evaluated with the stripped down context. @@ -15,7 +14,3 @@ fn context_modification( let scope = ctx.scope().nullified(modification.features, Some(modification.sources())); value.eval(&ctx.nullified(modification.features, &scope)) } - -fn context_modification_extent(value: ExtentIn<'_>, _modification: ValueIn<'_, ContextModification>, level: LevelIn) -> GPoll { - value.at(level) -} diff --git a/node-graph/nodes/gcore/src/debug.rs b/node-graph/nodes/gcore/src/debug.rs index 757e240cae..812225bd91 100644 --- a/node-graph/nodes/gcore/src/debug.rs +++ b/node-graph/nodes/gcore/src/debug.rs @@ -1,5 +1,4 @@ use core_types::Ctx; -use core_types::list::List; use glam::{DAffine2, DVec2}; use raster_types::{CPU, Raster}; diff --git a/node-graph/nodes/gcore/src/memo.rs b/node-graph/nodes/gcore/src/memo.rs index ca6ae69c0c..d31c8c0039 100644 --- a/node-graph/nodes/gcore/src/memo.rs +++ b/node-graph/nodes/gcore/src/memo.rs @@ -1,8 +1,7 @@ use core_types::arena::ArenaCell; use core_types::context::{Ctx, CtxSnapshot, DeriveCtx, ExtractAll, InjectIndex}; -use core_types::extent::{ExtentIn, LevelIn}; use core_types::frame_table::{FrameTable, Lookup}; -use core_types::gpoll::{Extent, Finality, GPoll}; +use core_types::gpoll::{Finality, GPoll}; use core_types::graphene_hash::CacheHash; use core_types::memo::IORecord; use core_types::record::{LevelStatus, OwnedRecord, RecordCapture, RecordValue, copy_record_bytes, record_from_bytes}; @@ -13,7 +12,7 @@ use std::sync::Mutex; /// Helps speed up repeated renders in a computationally-heavy part of the node graph. /// /// Stores a deep copy of the last record that flowed through this node and replays it on subsequent renders if the context has not changed. -#[node_macro::node(category("General"), path(graphene_core::memo), extent(memoize_extent))] +#[node_macro::node(category("General"), path(graphene_core::memo))] fn memoize<'e>( ctx: impl Ctx + CacheHash + ExtractArena<'e>, #[data] cache: Arc>>, @@ -45,11 +44,7 @@ fn memoize<'e>( result } -fn memoize_extent(content: ExtentIn<'_>, level: LevelIn) -> GPoll { - content.at(level) -} - -#[node_macro::node(category(""), path(graphene_core::memo), extent(frame_memo_extent))] +#[node_macro::node(category(""), path(graphene_core::memo))] fn frame_memo<'e>( ctx: impl Ctx + CacheHash + ExtractArena<'e>, #[data] cell: ArenaCell, 32>>, @@ -92,14 +87,10 @@ fn frame_memo<'e>( } } -fn frame_memo_extent(content: ExtentIn<'_>, level: LevelIn) -> GPoll { - content.at(level) -} - type MonitorValue = Arc>>>; /// The Monitor node is used by the editor to access the data flowing through it. -#[node_macro::node(category(""), path(graphene_core::memo), serialize(serialize_monitor), properties("monitor_properties"), extent(monitor_extent))] +#[node_macro::node(category(""), path(graphene_core::memo), serialize(serialize_monitor), properties("monitor_properties"))] fn monitor<'e>( ctx: impl Ctx + DeriveCtx + ExtractAll + ExtractArena<'e> + InjectIndex + Copy, #[data] io: MonitorValue, @@ -126,10 +117,6 @@ fn monitor<'e>( result } -fn monitor_extent(content: ExtentIn<'_>, level: LevelIn) -> GPoll { - content.at(level) -} - fn serialize_monitor(io: &MonitorValue) -> Option> { let io = io.lock().unwrap(); io.as_ref().map(|io| Arc::new(io.clone()) as Arc) diff --git a/node-graph/nodes/gcore/src/ops.rs b/node-graph/nodes/gcore/src/ops.rs index 3cc15c7409..5c47a5dcaa 100644 --- a/node-graph/nodes/gcore/src/ops.rs +++ b/node-graph/nodes/gcore/src/ops.rs @@ -4,15 +4,11 @@ use core_types::{Ctx, ops::Convert, ops::ConvertAsync, transform::Footprint}; use std::marker::PhantomData; /// Passes-through the input value without changing it. This is useful for rerouting wires for organization purposes. -#[node_macro::node(category("General"), skip_impl, extent(passthrough_extent))] +#[node_macro::node(category("General"), skip_impl)] fn passthrough(_: impl Ctx, content: T) -> T { content } -fn passthrough_extent(content: core_types::extent::ExtentIn<'_>, level: core_types::extent::LevelIn) -> core_types::gpoll::GPoll { - content.at(level) -} - #[node_macro::node(category(""), skip_impl)] fn into, O: Send>(_: impl Ctx, value: T, #[data] _out_ty: PhantomData) -> O { value.into()