From 100f81c30775234e7d3852d7c1bc532f889e88b8 Mon Sep 17 00:00:00 2001 From: Dennis Kobert Date: Thu, 6 Aug 2026 11:57:18 +0000 Subject: [PATCH] Retire the lend machinery and flip the clone node onto record wires --- node-graph/graph-craft/src/proto.rs | 42 ++-- .../src/dynamic_executor.rs | 21 +- .../interpreted-executor/src/node_registry.rs | 218 +----------------- node-graph/libraries/core-types/src/record.rs | 105 --------- .../libraries/core-types/src/registry.rs | 42 +--- node-graph/node-macro/src/codegen.rs | 64 ++--- node-graph/nodes/gcore/src/debug.rs | 5 +- node-graph/nodes/gcore/src/memo.rs | 31 +-- node-graph/nodes/gcore/src/record.rs | 31 +-- 9 files changed, 60 insertions(+), 499 deletions(-) diff --git a/node-graph/graph-craft/src/proto.rs b/node-graph/graph-craft/src/proto.rs index 8f2f2bcd70..7a35198703 100644 --- a/node-graph/graph-craft/src/proto.rs +++ b/node-graph/graph-craft/src/proto.rs @@ -930,12 +930,8 @@ fn ref_adapter(proposed: &Type, wanted: &Type) -> Option { return None; }; match (proposed_output.as_ref(), wanted_output.as_ref()) { - (Type::Ref(inner), wanted_output @ Type::Concrete(_)) if valid_type(inner, wanted_output) => Some(ProtoNodeIdentifier::new("graphene_core::debug::CloneNode")), - (proposed_output @ Type::Concrete(_), Type::Ref(inner)) if valid_type(proposed_output, inner) => Some(ProtoNodeIdentifier::new("graphene_core::memo::LendNode")), (Type::Record(inner), wanted_output @ Type::Concrete(_)) if valid_type(inner, wanted_output) => Some(ProtoNodeIdentifier::new("core_types::record::RecordExtractNode")), (proposed_output @ Type::Concrete(_), Type::Record(inner)) if valid_type(proposed_output, inner) => Some(ProtoNodeIdentifier::new("core_types::record::RecordLiftNode")), - (Type::Ref(inner), Type::Record(wanted_inner)) if valid_type(inner, wanted_inner) => Some(ProtoNodeIdentifier::new("core_types::record::RecordLiftLendNode")), - (Type::Record(inner), Type::Ref(wanted_inner)) if valid_type(inner, wanted_inner) => Some(ProtoNodeIdentifier::new("core_types::record::RecordExtractLendNode")), _ => None, } } @@ -1299,7 +1295,7 @@ mod test { } #[cfg(test)] -mod ref_adapter_test { +mod adapter_splice_test { use super::*; fn adapter_lookup() -> &'static HashMap> { @@ -1307,29 +1303,29 @@ mod ref_adapter_test { let unused: NodeConstructor = |_| Err(ConstructionError::Arity { expected: 0, got: 0 }); [ ( - ProtoNodeIdentifier::new("wants_ref"), + ProtoNodeIdentifier::new("wants_owned"), vec![RegistryEntry { - io: NodeIOTypes::new(concrete!(Context), concrete!(String), vec![lend_edge_type::()]), + io: NodeIOTypes::new(concrete!(Context), concrete!(String), vec![edge_type::()]), constructor: unused, }], ), ( - ProtoNodeIdentifier::new("wants_ref_ambiguously"), + ProtoNodeIdentifier::new("wants_owned_ambiguously"), vec![ RegistryEntry { - io: NodeIOTypes::new(concrete!(Context), concrete!(String), vec![lend_edge_type::()]), + io: NodeIOTypes::new(concrete!(Context), concrete!(String), vec![edge_type::()]), constructor: unused, }, RegistryEntry { - io: NodeIOTypes::new(concrete!(Context), concrete!(f64), vec![lend_edge_type::()]), + io: NodeIOTypes::new(concrete!(Context), concrete!(f64), vec![edge_type::()]), constructor: unused, }, ], ), ( - ProtoNodeIdentifier::new("core_types::record::RecordExtractLendNode"), + ProtoNodeIdentifier::new("core_types::record::RecordExtractNode"), vec![RegistryEntry { - io: NodeIOTypes::new(concrete!(Context), ref_type::(), vec![record_edge_type::()]), + io: NodeIOTypes::new(concrete!(Context), concrete!(String), vec![record_edge_type::()]), constructor: unused, }], ), @@ -1341,7 +1337,7 @@ mod ref_adapter_test { } fn string_value() -> ProtoNode { - ProtoNode::value(ConstructionArgs::Value(TaggedValue::String("lent".to_string()).into()), vec![]) + ProtoNode::value(ConstructionArgs::Value(TaggedValue::String("landed".to_string()).into()), vec![]) } fn consumer(identifier: &str, producer: NodeId) -> ProtoNode { @@ -1354,11 +1350,11 @@ mod ref_adapter_test { } #[test] - fn a_ref_only_mismatch_splices_the_lend_adapter() { + fn a_record_only_mismatch_splices_the_extract_adapter() { let mut network = ProtoNetwork { inputs: vec![], output: NodeId(1), - nodes: vec![(NodeId(0), string_value()), (NodeId(1), consumer("wants_ref", NodeId(0)))], + nodes: vec![(NodeId(0), string_value()), (NodeId(1), consumer("wants_owned", NodeId(0)))], }; let mut typing = TypingContext::new(adapter_lookup()); @@ -1366,10 +1362,10 @@ mod ref_adapter_test { assert_eq!(network.nodes.len(), 3); let (adapter_id, adapter) = &network.nodes[1]; - assert_eq!(adapter.identifier.as_str(), "core_types::record::RecordExtractLendNode"); + assert_eq!(adapter.identifier.as_str(), "core_types::record::RecordExtractNode"); assert_eq!(adapter.unwrap_construction_nodes(), vec![NodeId(0)]); assert_eq!(network.nodes[2].1.unwrap_construction_nodes(), vec![*adapter_id]); - assert_eq!(typing.type_of(*adapter_id).unwrap().return_value, ref_type::()); + assert_eq!(typing.type_of(*adapter_id).unwrap().return_value, concrete!(String)); assert_eq!(typing.type_of(NodeId(1)).unwrap().return_value, concrete!(String)); } @@ -1380,8 +1376,8 @@ mod ref_adapter_test { output: NodeId(2), nodes: vec![ (NodeId(0), string_value()), - (NodeId(1), consumer("wants_ref", NodeId(0))), - (NodeId(2), consumer("wants_ref", NodeId(0))), + (NodeId(1), consumer("wants_owned", NodeId(0))), + (NodeId(2), consumer("wants_owned", NodeId(0))), ], }; @@ -1392,7 +1388,7 @@ mod ref_adapter_test { let adapters: Vec = network .nodes .iter() - .filter(|(_, node)| node.identifier.as_str() == "core_types::record::RecordExtractLendNode") + .filter(|(_, node)| node.identifier.as_str() == "core_types::record::RecordExtractNode") .map(|(id, _)| *id) .collect(); let [adapter_id] = adapters.as_slice() else { @@ -1401,18 +1397,18 @@ mod ref_adapter_test { let consumers: Vec> = network .nodes .iter() - .filter(|(_, node)| node.identifier.as_str() == "wants_ref") + .filter(|(_, node)| node.identifier.as_str() == "wants_owned") .map(|(_, node)| node.unwrap_construction_nodes()) .collect(); assert_eq!(consumers, vec![vec![*adapter_id], vec![*adapter_id]]); } #[test] - fn an_ambiguous_ref_mismatch_stays_an_error() { + fn an_ambiguous_record_mismatch_stays_an_error() { let mut network = ProtoNetwork { inputs: vec![], output: NodeId(1), - nodes: vec![(NodeId(0), string_value()), (NodeId(1), consumer("wants_ref_ambiguously", NodeId(0)))], + nodes: vec![(NodeId(0), string_value()), (NodeId(1), consumer("wants_owned_ambiguously", NodeId(0)))], }; let mut typing = TypingContext::new(adapter_lookup()); diff --git a/node-graph/interpreted-executor/src/dynamic_executor.rs b/node-graph/interpreted-executor/src/dynamic_executor.rs index 0996256567..60f146bd72 100644 --- a/node-graph/interpreted-executor/src/dynamic_executor.rs +++ b/node-graph/interpreted-executor/src/dynamic_executor.rs @@ -582,18 +582,25 @@ mod test { } #[test] - fn a_lend_adapter_is_spliced_between_a_value_and_a_ref_consumer() { + fn the_clone_node_clones_the_element_out_of_its_record_wire() { + let raster_list = TaggedValue::from_type(&core_types::concrete!(graphene_std::list::List>)).unwrap(); let network = ProtoNetwork { inputs: vec![], - output: NodeId(1), + output: NodeId(2), nodes: vec![ - (NodeId(0), string_value("lent")), + (NodeId(0), ProtoNode::value(ConstructionArgs::Value(raster_list.into()), vec![])), (NodeId(1), proto_node("graphene_core::debug::CloneNode", vec![NodeId(0)])), + (NodeId(2), proto_node("core_types::record::RecordExtractNode", vec![NodeId(1)])), ], }; let executor = DynamicExecutor::new(network).unwrap(); - assert_eq!((&executor).execute(()).unwrap(), GPoll::Final(TaggedValue::String("lent".to_string()))); + let arena = Arena::new(1 << 20).unwrap(); + let generations = []; + let scope = EvalScope::new(None, None, None, &generations, &arena); + let ctx = ContextImpl::root(&scope); + let result: Option>>> = executor.tree().eval(NodeId(2), &ctx); + assert!(matches!(result, Some(GPoll::Final(_))), "the flipped clone must evaluate over record wires, got {result:?}"); } #[test] @@ -756,7 +763,7 @@ mod test { } #[test] - fn a_clone_out_adapter_is_spliced_between_a_lending_producer_and_an_owned_consumer() { + fn stacked_frame_memos_replay_over_record_wires() { let network = ProtoNetwork { inputs: vec![], output: NodeId(3), @@ -764,7 +771,7 @@ mod test { (NodeId(0), string_value("memoized")), (NodeId(1), proto_node("graphene_core::memo::FrameMemoNode", vec![NodeId(0)])), (NodeId(2), proto_node("graphene_core::memo::FrameMemoNode", vec![NodeId(1)])), - (NodeId(3), proto_node("graphene_core::debug::CloneNode", vec![NodeId(2)])), + (NodeId(3), proto_node("core_types::record::RecordExtractNode", vec![NodeId(2)])), ], }; @@ -773,7 +780,7 @@ mod test { assert_eq!( (&executor).execute(()).unwrap(), GPoll::Final(TaggedValue::String("memoized".to_string())), - "the frame memo lend path must replay across evaluations" + "the frame memo must replay across evaluations" ); } } diff --git a/node-graph/interpreted-executor/src/node_registry.rs b/node-graph/interpreted-executor/src/node_registry.rs index cc2e64bca6..17c8e01eee 100644 --- a/node-graph/interpreted-executor/src/node_registry.rs +++ b/node-graph/interpreted-executor/src/node_registry.rs @@ -1,8 +1,6 @@ use glam::{DAffine2, DVec2, IVec2}; use graph_craft::application_io::PlatformEditorApi; -use graph_craft::document::DocumentNode; use graph_craft::document::value::RenderOutput; -use graphene_std::brush::brush_stroke::BrushStroke; use graphene_std::gradient::GradientStops; use graphene_std::list::{AttributeDyn, AttributeValueDyn, List, ListDyn}; #[cfg(target_family = "wasm")] @@ -12,14 +10,14 @@ use graphene_std::raster::GPU; use graphene_std::raster::color::Color; use graphene_std::raster::*; use graphene_std::raster::{CPU, Raster}; -use graphene_std::registry::{ConstructionError, EdgeHandle, ErasedLendNode, ErasedNode, NodeIOTypes, RegistryEntry, lend_edge_type, ref_type}; +use graphene_std::registry::{ConstructionError, EdgeHandle, ErasedNode, NodeIOTypes, RegistryEntry}; use graphene_std::render_node::RenderIntermediate; use graphene_std::runtime::RuntimeHandle; use graphene_std::transform::Footprint; use graphene_std::uuid::NodeId; use graphene_std::vector::Vector; use graphene_std::{Artboard, Context, Graphic, ProtoNodeIdentifier, SourceId, concrete, fn_type}; -use node_registry_macros::{clone_node, convert_node, into_node, lend_node, record_extract_node, record_lift_node}; +use node_registry_macros::{convert_node, into_node, record_extract_node, record_lift_node}; use std::collections::HashMap; #[cfg(feature = "gpu")] use wgpu_executor::WgpuExecutorHandle; @@ -114,58 +112,9 @@ fn node_registry() -> HashMap> { // ============ // REF ADAPTERS // ============ - lend_node!(()), - clone_node!(()), - lend_node!(RuntimeHandle), - clone_node!(RuntimeHandle), - lend_node!(SourceId), - clone_node!(SourceId), - lend_node!(bool), - clone_node!(bool), - lend_node!(List), - clone_node!(List), - lend_node!(List), - clone_node!(List), - lend_node!(List), - clone_node!(List), - lend_node!(List>), - clone_node!(List>), - lend_node!(List), - clone_node!(List), - lend_node!(Image), - clone_node!(Image), - lend_node!(List), - clone_node!(List), - lend_node!(List), - clone_node!(List), - lend_node!(List), - clone_node!(List), - lend_node!(List), - clone_node!(List), - lend_node!(List), - clone_node!(List), - lend_node!(List), - clone_node!(List), - lend_node!(List), - clone_node!(List), - lend_node!(List), - clone_node!(List), - lend_node!(List), - clone_node!(List), - lend_node!(List), - clone_node!(List), - lend_node!(AttributeDyn), - clone_node!(AttributeDyn), - lend_node!(AttributeValueDyn), - clone_node!(AttributeValueDyn), - lend_node!(ListDyn), - clone_node!(ListDyn), #[cfg(target_family = "wasm")] - lend_node!(CanvasHandle), #[cfg(target_family = "wasm")] - clone_node!(CanvasHandle), #[cfg(target_family = "wasm")] - lend_node!(f64), record_lift_node!(f64), record_extract_node!(f64), record_lift_node!(()), @@ -252,129 +201,6 @@ fn node_registry() -> HashMap> { record_lift_node!(wgpu_executor::WgpuPipelineCache), #[cfg(feature = "gpu")] record_extract_node!(wgpu_executor::WgpuPipelineCache), - lend_node!(f32), - clone_node!(f32), - lend_node!(u32), - clone_node!(u32), - lend_node!(u64), - clone_node!(u64), - lend_node!(DVec2), - clone_node!(DVec2), - lend_node!(String), - clone_node!(String), - lend_node!(DAffine2), - clone_node!(DAffine2), - lend_node!(Footprint), - clone_node!(Footprint), - lend_node!(RenderOutput), - clone_node!(RenderOutput), - lend_node!(std::sync::Arc), - clone_node!(std::sync::Arc), - #[cfg(feature = "gpu")] - lend_node!(List>), - #[cfg(feature = "gpu")] - clone_node!(List>), - #[cfg(feature = "gpu")] - lend_node!(Option), - clone_node!(Option), - lend_node!(Option), - clone_node!(Option), - lend_node!(Graphic), - clone_node!(Graphic), - lend_node!(glam::f32::Vec2), - clone_node!(glam::f32::Vec2), - lend_node!(glam::f32::Affine2), - clone_node!(glam::f32::Affine2), - lend_node!(graphene_std::vector::style::Stroke), - clone_node!(graphene_std::vector::style::Stroke), - lend_node!(graphene_std::text::Font), - clone_node!(graphene_std::text::Font), - lend_node!(List), - clone_node!(List), - lend_node!(DocumentNode), - clone_node!(DocumentNode), - lend_node!(graphene_std::ContextModification), - clone_node!(graphene_std::ContextModification), - lend_node!(graphene_std::transform::Footprint), - clone_node!(graphene_std::transform::Footprint), - lend_node!(Box), - clone_node!(Box), - lend_node!(graphene_std::blending::BlendMode), - clone_node!(graphene_std::blending::BlendMode), - lend_node!(graphene_std::raster::LuminanceCalculation), - clone_node!(graphene_std::raster::LuminanceCalculation), - lend_node!(graphene_std::vector::QRCodeErrorCorrectionLevel), - clone_node!(graphene_std::vector::QRCodeErrorCorrectionLevel), - lend_node!(graphene_std::extract_xy::XY), - clone_node!(graphene_std::extract_xy::XY), - lend_node!(graphene_std::text_nodes::StringCapitalization), - clone_node!(graphene_std::text_nodes::StringCapitalization), - lend_node!(graphene_std::raster::RedGreenBlue), - clone_node!(graphene_std::raster::RedGreenBlue), - lend_node!(graphene_std::raster::RedGreenBlueAlpha), - clone_node!(graphene_std::raster::RedGreenBlueAlpha), - lend_node!(graphene_std::animation::RealTimeMode), - clone_node!(graphene_std::animation::RealTimeMode), - lend_node!(graphene_std::raster::NoiseType), - clone_node!(graphene_std::raster::NoiseType), - lend_node!(graphene_std::raster::FractalType), - clone_node!(graphene_std::raster::FractalType), - lend_node!(graphene_std::raster::CellularDistanceFunction), - clone_node!(graphene_std::raster::CellularDistanceFunction), - lend_node!(graphene_std::raster::CellularReturnType), - clone_node!(graphene_std::raster::CellularReturnType), - lend_node!(graphene_std::raster::DomainWarpType), - clone_node!(graphene_std::raster::DomainWarpType), - lend_node!(graphene_std::raster::RelativeAbsolute), - clone_node!(graphene_std::raster::RelativeAbsolute), - lend_node!(graphene_std::raster::SelectiveColorChoice), - clone_node!(graphene_std::raster::SelectiveColorChoice), - lend_node!(graphene_std::vector::misc::GridType), - clone_node!(graphene_std::vector::misc::GridType), - lend_node!(graphene_std::vector::misc::ArcType), - clone_node!(graphene_std::vector::misc::ArcType), - lend_node!(graphene_std::vector::misc::RowsOrColumns), - clone_node!(graphene_std::vector::misc::RowsOrColumns), - lend_node!(graphene_std::vector::misc::MergeByDistanceAlgorithm), - clone_node!(graphene_std::vector::misc::MergeByDistanceAlgorithm), - lend_node!(graphene_std::vector::misc::ExtrudeJoiningAlgorithm), - clone_node!(graphene_std::vector::misc::ExtrudeJoiningAlgorithm), - lend_node!(graphene_std::vector::misc::PointSpacingType), - clone_node!(graphene_std::vector::misc::PointSpacingType), - lend_node!(graphene_std::vector::style::StrokeCap), - clone_node!(graphene_std::vector::style::StrokeCap), - lend_node!(graphene_std::vector::style::StrokeJoin), - clone_node!(graphene_std::vector::style::StrokeJoin), - lend_node!(graphene_std::vector::style::StrokeAlign), - clone_node!(graphene_std::vector::style::StrokeAlign), - lend_node!(graphene_std::vector::style::PaintOrder), - clone_node!(graphene_std::vector::style::PaintOrder), - lend_node!(graphene_std::vector::style::GradientType), - clone_node!(graphene_std::vector::style::GradientType), - lend_node!(graphene_std::vector::style::GradientSpreadMethod), - clone_node!(graphene_std::vector::style::GradientSpreadMethod), - lend_node!(Option), - clone_node!(Option), - lend_node!(graphene_std::transform::ReferencePoint), - clone_node!(graphene_std::transform::ReferencePoint), - lend_node!(graphene_std::vector::misc::CentroidType), - clone_node!(graphene_std::vector::misc::CentroidType), - lend_node!(graphene_std::vector::misc::BooleanOperation), - clone_node!(graphene_std::vector::misc::BooleanOperation), - lend_node!(graphene_std::text::TextAlign), - clone_node!(graphene_std::text::TextAlign), - lend_node!(graphene_std::transform::ScaleType), - clone_node!(graphene_std::transform::ScaleType), - lend_node!(graphene_std::vector::misc::InterpolationDistribution), - clone_node!(graphene_std::vector::misc::InterpolationDistribution), - lend_node!(RenderIntermediate), - clone_node!(RenderIntermediate), - lend_node!(wgpu_executor::WgpuExecutorHandle), - clone_node!(wgpu_executor::WgpuExecutorHandle), - lend_node!(Option), - clone_node!(Option), - lend_node!(wgpu_executor::WgpuPipelineCache), - clone_node!(wgpu_executor::WgpuPipelineCache), ]; // ============= // CONVERT NODES @@ -545,25 +371,6 @@ mod node_registry_macros { }; } - macro_rules! lend_node { - ($type:ty) => { - ( - ProtoNodeIdentifier::new("graphene_core::memo::LendNode"), - RegistryEntry { - io: NodeIOTypes::new(concrete!(Context), ref_type::<$type>(), vec![fn_type!(Context, $type)]), - constructor: |inputs| { - if inputs.len() != 1 { - return Err(ConstructionError::Arity { expected: 1, got: inputs.len() }); - } - let mut inputs = inputs.into_iter(); - let node = graphene_core::memo::LendNode::new(inputs.next().unwrap().downcast::<$type>()?); - Ok(EdgeHandle::new_ref(std::sync::Arc::new(node) as std::sync::Arc>)) - }, - }, - ) - }; - } - macro_rules! record_lift_node { ($type:ty) => { ( @@ -604,29 +411,8 @@ mod node_registry_macros { }; } - macro_rules! clone_node { - ($type:ty) => { - ( - ProtoNodeIdentifier::new("graphene_core::debug::CloneNode"), - RegistryEntry { - io: NodeIOTypes::new(concrete!(Context), concrete!($type), vec![lend_edge_type::<$type>()]), - constructor: |inputs| { - if inputs.len() != 1 { - return Err(ConstructionError::Arity { expected: 1, got: inputs.len() }); - } - let mut inputs = inputs.into_iter(); - let node = graphene_core::debug::CloneNode::new(inputs.next().unwrap().downcast_lend::<$type>()?); - Ok(EdgeHandle::new(std::sync::Arc::new(node) as std::sync::Arc>)) - }, - }, - ) - }; - } - - pub(crate) use clone_node; pub(crate) use convert_node; pub(crate) use into_node; - pub(crate) use lend_node; pub(crate) use record_extract_node; pub(crate) use record_lift_node; } diff --git a/node-graph/libraries/core-types/src/record.rs b/node-graph/libraries/core-types/src/record.rs index 1214bd4bc4..cc25c82816 100644 --- a/node-graph/libraries/core-types/src/record.rs +++ b/node-graph/libraries/core-types/src/record.rs @@ -905,111 +905,6 @@ where } } -/// Lifts a lending producer onto a record wire: a parked element carries the -/// lent reference directly, a byte-carried one copies out of the borrow. -pub struct RecordLiftLend { - edge: N, - layout: Layout, - _marker: std::marker::PhantomData El>, -} - -impl RecordLiftLend { - pub fn new(edge: N) -> Self { - Self { - edge, - layout: Layout::default().with_writes(0, element_write::(), &[]), - _marker: std::marker::PhantomData, - } - } -} - -impl<'e, C, El, N> Node for RecordLiftLend -where - C: crate::context::ExtractArena, - El: Send + Sync + 'static, - N: Node, -{ - type Output = RecordValue<'e>; - - fn eval(&self, input: &C) -> GPoll> { - let build = |element: &'e El| { - let write = |dst: *mut u8| match element_parked::() { - true => unsafe { dst.cast::<&El>().write(element) }, - false => unsafe { std::ptr::copy_nonoverlapping((element as *const El).cast::(), dst, size_of::()) }, - }; - if self.layout.is_inline() { - let mut value = RecordValue::zeroed(); - write(value.as_mut_ptr()); - value - } else { - let dst = stack::push(self.layout.frame_bytes()); - write(dst); - stack::pop(dst); - RecordValue::spilled(unsafe { Rec::new(dst.cast_const()) }) - } - }; - self.edge.eval(input).map(build) - } - - fn layout(&self) -> Option<&Layout> { - Some(&self.layout) - } -} - -/// Lends a record wire's element: a parked element lends its arena-backed -/// reference directly, a byte-carried one parks a copy so the borrow -/// outlives the record. -pub struct RecordExtractLend { - edge: N, - layout: Layout, - _marker: std::marker::PhantomData El>, -} - -impl RecordExtractLend { - pub fn new(edge: N, layout: &Layout) -> Self { - Self { - edge, - layout: layout.clone(), - _marker: std::marker::PhantomData, - } - } -} - -impl<'e, C, El, N> Node for RecordExtractLend -where - C: crate::context::ExtractArena, - El: Clone + Send + Sync + 'static, - N: Node>, -{ - type Output = &'e El; - - fn eval(&self, input: &C) -> GPoll<&'e El> { - let exhausted = || { - GPoll::Error(Box::new(crate::gpoll::GraphError { - kind: crate::gpoll::ErrorKind::ArenaExhausted, - trace: Vec::new(), - })) - }; - let lend = |value: RecordValue<'e>| { - let rec = self.layout.rec(&value); - match element_parked::() { - true => Some(unsafe { borrow_element::(rec) }), - false => input.arena().alloc(unsafe { read_element::(rec) }).map(|(parked, _)| parked), - } - }; - match self.edge.eval(input) { - GPoll::Final(value) => lend(value).map_or_else(exhausted, GPoll::Final), - GPoll::Partial(value) => lend(value).map_or_else(exhausted, GPoll::Partial), - GPoll::Fallback(boxed) => { - let (value, error) = *boxed; - lend(value).map_or_else(exhausted, |element| GPoll::Fallback(Box::new((element, error)))) - } - GPoll::Pending => GPoll::Pending, - GPoll::Error(error) => GPoll::Error(error), - } - } -} - /// Extracts the element from a record wire for a plain consumer, cloning out /// of the parked reference when the element carries drop glue. pub struct RecordExtract { diff --git a/node-graph/libraries/core-types/src/registry.rs b/node-graph/libraries/core-types/src/registry.rs index 5e1093f748..b0b6882bb7 100644 --- a/node-graph/libraries/core-types/src/registry.rs +++ b/node-graph/libraries/core-types/src/registry.rs @@ -290,15 +290,13 @@ pub struct RegistryEntry { pub constructor: NodeConstructor, } -/// The four bridge rows of `T`: plain and lend producers onto record wires, -/// record wires into plain and lend consumers. One set exists per wire type -/// while the worlds coexist. -pub fn record_bridge_rows() -> [(crate::ProtoNodeIdentifier, RegistryEntry); 4] { +/// The bridge rows of `T`: a plain producer onto a record wire and a record +/// wire into a plain consumer. One pair exists per wire type while the +/// deferred plain classes (shader, batch) coexist with record wires. +pub fn record_bridge_rows() -> [(crate::ProtoNodeIdentifier, RegistryEntry); 2] { [ (crate::ProtoNodeIdentifier::new("core_types::record::RecordLiftNode"), record_lift_entry::()), (crate::ProtoNodeIdentifier::new("core_types::record::RecordExtractNode"), record_extract_entry::()), - (crate::ProtoNodeIdentifier::new("core_types::record::RecordLiftLendNode"), record_lift_lend_entry::()), - (crate::ProtoNodeIdentifier::new("core_types::record::RecordExtractLendNode"), record_extract_lend_entry::()), ] } @@ -335,38 +333,6 @@ pub fn record_extract_entry() -> RegistryEntry } } -/// The lend-lift bridge row for `T`: a lending producer onto a record wire. -pub fn record_lift_lend_entry() -> RegistryEntry { - RegistryEntry { - io: NodeIOTypes::new(concrete!(Context), record_type::(), vec![lend_edge_type::()]), - constructor: |inputs| { - if inputs.len() != 1 { - return Err(ConstructionError::Arity { expected: 1, got: inputs.len() }); - } - let mut inputs = inputs.into_iter(); - let node = crate::record::RecordLiftLend::::new(inputs.next().unwrap().downcast_lend::()?); - Ok(EdgeHandle::new_record::(std::sync::Arc::new(node) as std::sync::Arc)) - }, - } -} - -/// The lend-extract bridge row for `T`: a record wire into a lend consumer. -pub fn record_extract_lend_entry() -> RegistryEntry { - RegistryEntry { - io: NodeIOTypes::new(concrete!(Context), ref_type::(), vec![record_edge_type::()]), - constructor: |inputs| { - if inputs.len() != 1 { - return Err(ConstructionError::Arity { expected: 1, got: inputs.len() }); - } - let mut inputs = inputs.into_iter(); - let edge = inputs.next().unwrap(); - let layout = edge.layout().ok_or(ConstructionError::MissingLayout)?.clone(); - let node = crate::record::RecordExtractLend::::new(edge.downcast_record::()?, &layout); - Ok(EdgeHandle::new_ref(std::sync::Arc::new(node) as std::sync::Arc>)) - }, - } -} - pub fn construct(entry: &RegistryEntry, inputs: Vec) -> Result { if inputs.len() != entry.io.inputs.len() { return Err(ConstructionError::Arity { diff --git a/node-graph/node-macro/src/codegen.rs b/node-graph/node-macro/src/codegen.rs index 74cf24a59e..6a64a71161 100644 --- a/node-graph/node-macro/src/codegen.rs +++ b/node-graph/node-macro/src/codegen.rs @@ -780,27 +780,6 @@ pub(crate) fn generate_node_impl(crate_ident: &CrateIdent, parsed: &ParsedNodeFn ]); } - let has_lend = parsed.fields.iter().any(|field| matches!(&field.ty, ParsedFieldType::Regular(RegularParsedField { lend: Some(_), .. }))); - let declared_arena_lifetime = ctx_param.and_then(|ctx_param| { - ctx_param.bounds.iter().find_map(|bound| { - let TypeParamBound::Trait(trait_bound) = bound else { return None }; - let segment = trait_bound.path.segments.last()?; - if segment.ident != "ExtractArena" { - return None; - } - let PathArguments::AngleBracketed(args) = &segment.arguments else { return None }; - match args.args.first() { - Some(GenericArgument::Lifetime(lifetime)) => Some(lifetime.clone()), - _ => None, - } - }) - }); - let introduced_lend_lifetime = (has_lend && !flip && declared_arena_lifetime.is_none()).then(|| Lifetime::new("'__lend", proc_macro2::Span::call_site())); - let lend_lifetime = declared_arena_lifetime.or_else(|| introduced_lend_lifetime.clone()); - if let Some(lifetime) = &introduced_lend_lifetime { - ctx_bounds.push(quote!(#core_types::context::ExtractArena)); - } - let derives = ctx_param.is_some_and(|ctx_param| { ctx_param.bounds.iter().any(|bound| match bound { TypeParamBound::Trait(trait_bound) => trait_bound.path.segments.last().is_some_and(|segment| segment.ident == "DeriveCtx"), @@ -850,10 +829,6 @@ pub(crate) fn generate_node_impl(crate_ident: &CrateIdent, parsed: &ParsedNodeFn generics.push(ctx_generic.clone()); impl_generics.push(ctx_generic); } - if let Some(lifetime) = &introduced_lend_lifetime { - generics.insert(0, quote!(#lifetime)); - impl_generics.insert(0, quote!(#lifetime)); - } if routing.is_some() || record.is_some() || flip { impl_generics.insert(0, quote!('__record)); } @@ -1024,10 +999,6 @@ pub(crate) fn generate_node_impl(crate_ident: &CrateIdent, parsed: &ParsedNodeFn }, false => quote!(#node_generic: #core_types::node::Node<#ctx_ident, Output = #record_value_ty>), }, - ParsedFieldType::Regular(RegularParsedField { ty, lend: Some(_), .. }) => { - let lifetime = lend_lifetime.as_ref().expect("lend fields imply the lend lifetime"); - quote!(#node_generic: #core_types::node::Node<#ctx_ident, Output = &#lifetime #ty>) - } ParsedFieldType::Regular(_) if record.is_some() && !skips_carrier && index == 0 => { quote!(#node_generic: #core_types::node::Node<#ctx_ident, Output = #record_value_ty>) } @@ -1051,16 +1022,7 @@ pub(crate) fn generate_node_impl(crate_ident: &CrateIdent, parsed: &ParsedNodeFn } }); - let mut lend_outlives: Vec = regular_fields - .iter() - .filter_map(|field| match &field.ty { - ParsedFieldType::Regular(RegularParsedField { ty, lend: Some(_), .. }) if !flip => { - let lifetime = lend_lifetime.as_ref().expect("lend fields imply the lend lifetime"); - Some(quote!(#ty: #lifetime)) - } - _ => None, - }) - .collect(); + let mut lend_outlives: Vec = Vec::new(); if let Type::Reference(reference) = &trait_output && let Some(lifetime) = &reference.lifetime { @@ -1175,6 +1137,9 @@ pub(crate) fn generate_node_impl(crate_ident: &CrateIdent, parsed: &ParsedNodeFn ParsedFieldType::Node(_) if flip && raw_lazy => quote!(&#name), ParsedFieldType::Node(NodeParsedField { output_type, .. }) if opaque && raw_lazy && is_record_value(output_type) => quote!(&#name), ParsedFieldType::Node(_) if raw_lazy => quote!(&self.#name), + // A lend param binds an owned edge; the kernel borrows the + // evaluated value. + ParsedFieldType::Regular(RegularParsedField { lend: Some(_), .. }) if !flip => quote!(&#name), _ => quote!(#name), } }); @@ -1367,7 +1332,12 @@ pub(crate) fn generate_node_impl(crate_ident: &CrateIdent, parsed: &ParsedNodeFn .into_iter(); let value_args = regular_fields.iter().skip(if shape.skips_carrier() { 0 } else { 1 }).map(|field| { let name = &field.pat_ident.ident; - quote!(#name) + match &field.ty { + // A lend param binds an owned edge; the kernel borrows the + // evaluated value. + ParsedFieldType::Regular(RegularParsedField { lend: Some(_), .. }) => quote!(&#name), + _ => quote!(#name), + } }); let attr_args = parsed.attribute_reads.iter().map(|read| { let pat = &read.pat_ident.ident; @@ -2615,7 +2585,7 @@ fn record_entries_tokens(parsed: &ParsedNodeFn, struct_name: &Ident, regular_fie let names: Vec<&Ident> = regular_fields.iter().map(|field| &field.pat_ident.ident).collect(); let input_types = regular_fields.iter().enumerate().map(|(index, field)| { - let ParsedFieldType::Regular(RegularParsedField { ty, lend, .. }) = &field.ty else { + let ParsedFieldType::Regular(RegularParsedField { ty, .. }) = &field.ty else { unreachable!("record nodes take no lazy inputs") }; if carrier_in_fields && index == 0 { @@ -2628,14 +2598,11 @@ fn record_entries_tokens(parsed: &ParsedNodeFn, struct_name: &Ident, regular_fie RecordCarrier::None => unreachable!(), }; } - match lend.is_some() { - true => quote!(gcore::registry::lend_edge_type::<#ty>()), - false => quote!(gcore::registry::edge_type::<#ty>()), - } + quote!(gcore::registry::edge_type::<#ty>()) }); let downcasts = regular_fields.iter().enumerate().map(|(index, field)| { let name = &field.pat_ident.ident; - let ParsedFieldType::Regular(RegularParsedField { ty, lend, .. }) = &field.ty else { + let ParsedFieldType::Regular(RegularParsedField { ty, .. }) = &field.ty else { unreachable!("record nodes take no lazy inputs") }; if carrier_in_fields && index == 0 { @@ -2648,10 +2615,7 @@ fn record_entries_tokens(parsed: &ParsedNodeFn, struct_name: &Ident, regular_fie let #name = __carrier_handle.downcast_erased::(__carrier_ty.clone())?; }; } - match lend.is_some() { - true => quote!(let #name = inputs.next().unwrap().downcast_lend::<#ty>()?;), - false => quote!(let #name = inputs.next().unwrap().downcast::<#ty>()?;), - } + quote!(let #name = inputs.next().unwrap().downcast::<#ty>()?;) }); let wire_layout_arg = carrier_in_fields.then(|| quote!(&__carrier_layout,)).into_iter(); let (io_output, construct_output) = match (&shape.carrier, &shape.element_write) { diff --git a/node-graph/nodes/gcore/src/debug.rs b/node-graph/nodes/gcore/src/debug.rs index 4df880a47a..75ddc05acc 100644 --- a/node-graph/nodes/gcore/src/debug.rs +++ b/node-graph/nodes/gcore/src/debug.rs @@ -29,9 +29,8 @@ fn unwrap_option(_: impl Ctx, #[implementations(Option, Option< input.unwrap_or_default() } -/// Clones the value borrowed from a lending edge. Doubles as the checker-inserted clone-out -/// adapter, so it keeps the plain lowering while the record transition runs. -#[node_macro::node(category("Debug"), plain)] +/// Clones the element out of its record wire. +#[node_macro::node(category("Debug"))] fn clone(_: impl Ctx, #[implementations(List>)] value: &T) -> T { value.clone() } diff --git a/node-graph/nodes/gcore/src/memo.rs b/node-graph/nodes/gcore/src/memo.rs index b3a3229a77..c5482c0ca7 100644 --- a/node-graph/nodes/gcore/src/memo.rs +++ b/node-graph/nodes/gcore/src/memo.rs @@ -1,4 +1,4 @@ -use core_types::arena::{Arena, ArenaCell}; +use core_types::arena::ArenaCell; use core_types::context::{Ctx, CtxSnapshot, DeriveCtx, ExtractAll, ExtractArena}; use core_types::frame_table::{FrameTable, Lookup}; use core_types::gpoll::{Extent, Finality, GPoll}; @@ -102,34 +102,6 @@ where node.content.extent(ctx) } -pub fn park(arena: &Arena, result: GPoll) -> GPoll<&T> { - match result { - GPoll::Final(value) => match arena.alloc(value) { - Some((parked, _)) => GPoll::Final(parked), - None => GPoll::arena_exhausted(), - }, - GPoll::Partial(value) => match arena.alloc(value) { - Some((parked, _)) => GPoll::Partial(parked), - None => GPoll::arena_exhausted(), - }, - GPoll::Fallback(boxed) => { - let (value, error) = *boxed; - match arena.alloc(value) { - Some((parked, _)) => GPoll::Fallback(Box::new((parked, error))), - None => GPoll::arena_exhausted(), - } - } - GPoll::Pending => GPoll::Pending, - GPoll::Error(error) => GPoll::Error(error), - } -} - -/// Adapts an owned edge to a lending one by parking each result in the eval arena. -#[node_macro::node(category(""), path(graphene_core::memo), skip_impl)] -fn lend<'e, T: Send + Sync>(ctx: impl Ctx + ExtractArena<'e>, value: T) -> GPoll<&'e T> { - park(ctx.arena(), GPoll::Final(value)) -} - type MonitorValue = Arc>>>; /// The Monitor node is used by the editor to access the data flowing through it. @@ -156,6 +128,7 @@ fn serialize_monitor(io: &MonitorValue) -> Option(_: impl Ctx, element: T) -> T { element } - #[cfg(test)] mod tests { use super::*; @@ -180,11 +179,7 @@ mod tests { let stacked = multiply_opacity_layout(&modified); reserve_for(&[&source_layout, &modified, &stacked]); - let chain = MultiplyOpacityNode::new( - MultiplyOpacityNode::new(bare_source(&source_layout, 2.), ValueNode(0.5), &source_layout), - ValueNode(0.5), - &modified, - ); + let chain = MultiplyOpacityNode::new(MultiplyOpacityNode::new(bare_source(&source_layout, 2.), ValueNode(0.5), &source_layout), ValueNode(0.5), &modified); assert_eq!(chain.layout(), Some(&stacked)); let GPoll::Final(value) = chain.eval(&ctx) else { panic!("expected a final record"); @@ -372,18 +367,6 @@ mod tests { assert!(error.kind == "negative factor"); } - static FACTOR: f64 = 3.; - - struct StaticLendNode(&'static f64); - - impl<'e> Node> for StaticLendNode { - type Output = &'e f64; - - fn eval(&self, _input: &ContextImpl<'e>) -> GPoll<&'e f64> { - GPoll::Final(self.0) - } - } - #[test] fn lend_value_params_wire_into_record_kernels() { let arena = Arena::new(1024).unwrap(); @@ -396,11 +379,7 @@ mod tests { let scaled = scale_layout(&modified); reserve_for(&[&source_layout, &modified, &scaled]); - let chain = ScaleNode::new( - MultiplyOpacityNode::new(bare_source(&source_layout, 2.), ValueNode(0.5), &source_layout), - StaticLendNode(&FACTOR), - &modified, - ); + let chain = ScaleNode::new(MultiplyOpacityNode::new(bare_source(&source_layout, 2.), ValueNode(0.5), &source_layout), ValueNode(3.), &modified); let GPoll::Final(value) = chain.eval(&ctx) else { panic!("expected a final record"); }; @@ -585,11 +564,7 @@ mod tests { reserve_for(&[&layout]); let probed = |features: ContextFeatures| { - let node = crate::context_modification::ContextModificationNode::new( - RealTimeProbe { layout: layout.clone() }, - ValueNode(ContextModification::from_sources(features, &[])), - &layout, - ); + let node = crate::context_modification::ContextModificationNode::new(RealTimeProbe { layout: layout.clone() }, ValueNode(ContextModification::from_sources(features, &[])), &layout); assert_eq!(Node::::layout(&node), Some(&layout)); let GPoll::Final(value) = node.eval(&ctx) else { panic!("expected a final record");