diff --git a/editor/src/messages/portfolio/document/data_panel/data_panel_message_handler.rs b/editor/src/messages/portfolio/document/data_panel/data_panel_message_handler.rs index 1912f5df42..a210e52805 100644 --- a/editor/src/messages/portfolio/document/data_panel/data_panel_message_handler.rs +++ b/editor/src/messages/portfolio/document/data_panel/data_panel_message_handler.rs @@ -10,11 +10,10 @@ use graphene_std::blending::BlendMode; use graphene_std::color::SRGBA8; use graphene_std::gradient::GradientStops; use graphene_std::list::List; -use graphene_std::memo::IORecord; use graphene_std::raster_types::{CPU, GPU, Raster}; use graphene_std::vector::Vector; use graphene_std::vector::style::{FillChoice, FillChoiceUI, GradientSpreadMethod, GradientType}; -use graphene_std::{Artboard, Color, CtxSnapshot, Graphic}; +use graphene_std::{Artboard, Color, Graphic}; use std::any::Any; use std::sync::Arc; @@ -167,8 +166,8 @@ macro_rules! generate_layout_downcast { ($introspected_data:expr, $data:expr, [ $($ty:ty),* $(,)? ]) => { if false { None } $( - else if let Some(io) = $introspected_data.downcast_ref::>() { - Some(io.output.layout_with_breadcrumb($data)) + else if let Some(element) = $introspected_data.downcast_ref::<$ty>() { + Some(element.layout_with_breadcrumb($data)) } )* else { None } @@ -178,8 +177,8 @@ macro_rules! generate_layout_downcast { fn generate_layout(introspected_data: &Arc, data: &mut LayoutData) -> Option> { // `List` is interpreted as a path (e.g. the value produced by `path_of_subgraph`), shown as a // `List` where each item's NodeId resolves against the prefix made up of the items above it. - if let Some(io) = introspected_data.downcast_ref::>>() { - return Some(table_node_id_path_layout_with_breadcrumb(&io.output, data)); + if let Some(list) = introspected_data.downcast_ref::>() { + return Some(table_node_id_path_layout_with_breadcrumb(list, data)); } generate_layout_downcast!(introspected_data, data, [ List, diff --git a/editor/src/node_graph_executor.rs b/editor/src/node_graph_executor.rs index 2cbc23297a..59babbc62a 100644 --- a/editor/src/node_graph_executor.rs +++ b/editor/src/node_graph_executor.rs @@ -10,12 +10,11 @@ use graphene_std::application_io::{ExportFormat, NodeGraphUpdateMessage, RenderC use graphene_std::bounds::RenderBoundingBox; use graphene_std::color::SRGBA8; use graphene_std::list::List; -use graphene_std::memo::IORecord; use graphene_std::raster::{CPU, Raster}; use graphene_std::renderer::{RenderMetadata, graphic_list_bounding_box}; use graphene_std::transform::Footprint; use graphene_std::vector::{Vector, graphic_types}; -use graphene_std::{ATTR_TRANSFORM, CtxSnapshot, Graphic, NodeInputDecleration}; +use graphene_std::{ATTR_TRANSFORM, Graphic, NodeInputDecleration}; use interpreted_executor::dynamic_executor::ResolvedDocumentNodeTypesDelta; use std::any::Any; use std::sync::Arc; @@ -900,18 +899,9 @@ fn measure_fill_geometry(data: &Arc) -> Option<(DAffine2, } // TODO: Eventually remove this document upgrade code -/// Extract a monitor node's recorded output, trying each context type the runtime may have evaluated it under. +/// Extract a monitor node's captured output element. fn introspected_output(data: &Arc) -> Option { - if let Some(io) = data.downcast_ref::>() { - return Some(io.output.clone()); - } - if let Some(io) = data.downcast_ref::>() { - return Some(io.output.clone()); - } - if let Some(io) = data.downcast_ref::>() { - return Some(io.output.clone()); - } - None + data.downcast_ref::().cloned() } // Re-export for usage by tests in other modules @@ -927,9 +917,7 @@ mod test { use crate::test_utils::test_prelude::{self, NodeGraphLayer}; use graph_craft::ProtoNodeIdentifier; use graph_craft::document::NodeNetwork; - use graphene_std::CtxSnapshot; use graphene_std::NodeInputDecleration; - use graphene_std::memo::IORecord; use test_prelude::LayerNodeIdentifier; /// Stores all of the monitor nodes that have been attached to a graph @@ -990,17 +978,11 @@ mod test { where Input::Result: Send + Sync + Clone + 'static, { - // This is quite inflexible since it only allows the footprint as inputs. - if let Some(x) = dynamic.downcast_ref::>() { - Some(x.output.clone()) - } else if let Some(x) = dynamic.downcast_ref::>() { - Some(x.output.clone()) - } else if let Some(x) = dynamic.downcast_ref::>() { - Some(x.output.clone()) - } else { + let element = dynamic.downcast_ref::().cloned(); + if element.is_none() { warn!("cannot downcast type for introspection"); - None } + element } /// Grab all of the values of the input every time it occurs in the graph. diff --git a/editor/src/node_graph_executor/runtime.rs b/editor/src/node_graph_executor/runtime.rs index fbfa1f09b5..8c3b5cc428 100644 --- a/editor/src/node_graph_executor/runtime.rs +++ b/editor/src/node_graph_executor/runtime.rs @@ -11,7 +11,6 @@ use graphene_std::application_io::{ApplicationIo, ExportFormat, NodeGraphUpdateM use graphene_std::bounds::RenderBoundingBox; use graphene_std::core_types::gpoll::GPoll; use graphene_std::list::List; -use graphene_std::memo::IORecord; use graphene_std::ops::ConvertAsync; #[cfg(all(target_family = "wasm", feature = "gpu", feature = "wasm"))] use graphene_std::platform_application_io::canvas_utils::{Canvas, CanvasSurface, CanvasSurfaceHandle}; @@ -21,7 +20,7 @@ use graphene_std::runtime::{DynGraphRuntime, DynNotifier, DynSpawner, GraphRunti use graphene_std::transform::RenderQuality; use graphene_std::vector::Vector; use graphene_std::vector::style::RenderMode; -use graphene_std::{Artboard, CtxSnapshot, Graphic}; +use graphene_std::{Artboard, Graphic}; use interpreted_executor::dynamic_executor::{DynamicExecutor, ResolvedDocumentNodeTypesDelta}; use interpreted_executor::util::wrap_network_in_scope; use spin::Mutex; @@ -502,30 +501,30 @@ impl NodeRuntime { }; // Graphic list: thumbnail (text-aware bounds, since the `BoundingBox` trait can't lay out `Graphic::Text` content) - if let Some(io) = introspected_data.downcast_ref::>>() { + if let Some(list) = introspected_data.downcast_ref::>() { if update_thumbnails { - let bounds = graphene_std::renderer::graphic_list_bounding_box(&io.output, DAffine2::IDENTITY); - Self::render_thumbnail(&mut self.thumbnail_renders, parent_network_node_id, &io.output, bounds, responses) + let bounds = graphene_std::renderer::graphic_list_bounding_box(list, DAffine2::IDENTITY); + Self::render_thumbnail(&mut self.thumbnail_renders, parent_network_node_id, list, bounds, responses) } } // Artboard thumbnail bounds come from the clipping rectangles, not the content union, since the renderer // clips content to those rectangles so anything outside isn't visible - else if let Some(io) = introspected_data.downcast_ref::>>() { + else if let Some(list) = introspected_data.downcast_ref::>() { if update_thumbnails { - let bounds = artboard_clip_bounds(&io.output); - Self::render_thumbnail(&mut self.thumbnail_renders, parent_network_node_id, &io.output, bounds, responses) + let bounds = artboard_clip_bounds(list); + Self::render_thumbnail(&mut self.thumbnail_renders, parent_network_node_id, list, bounds, responses) } } // Vector list: vector modifications - else if let Some(io) = introspected_data.downcast_ref::>>() { + else if let Some(list) = introspected_data.downcast_ref::>() { // Insert the vector modify - self.vector_modify.insert(parent_network_node_id, io.output.element(0).cloned().unwrap_or_default()); + self.vector_modify.insert(parent_network_node_id, list.element(0).cloned().unwrap_or_default()); } // String list: thumbnail (bounds need text layout, which the `BoundingBox` trait can't do for a bare `String`) - else if let Some(io) = introspected_data.downcast_ref::>>() { + else if let Some(list) = introspected_data.downcast_ref::>() { if update_thumbnails { - let bounds = graphene_std::renderer::text_list_bounding_box(&io.output, DAffine2::IDENTITY); - Self::render_thumbnail(&mut self.thumbnail_renders, parent_network_node_id, &io.output, bounds, responses) + let bounds = graphene_std::renderer::text_list_bounding_box(list, DAffine2::IDENTITY); + Self::render_thumbnail(&mut self.thumbnail_renders, parent_network_node_id, list, bounds, responses) } } // Other diff --git a/node-graph/interpreted-executor/src/dynamic_executor.rs b/node-graph/interpreted-executor/src/dynamic_executor.rs index 45bf917e2f..acc203385f 100644 --- a/node-graph/interpreted-executor/src/dynamic_executor.rs +++ b/node-graph/interpreted-executor/src/dynamic_executor.rs @@ -145,12 +145,14 @@ impl DynamicExecutor { } /// Calls the `Node::serialize` for that specific node, returning for example the cached value for a monitor node. The node path must match the document node path. - /// A record capture materializes here against the arena, inside the introspection window. + /// A record capture materializes its element here against the arena, + /// inside the introspection window, so consumers downcast the element + /// type directly. pub fn introspect(&self, node_path: &[NodeId]) -> Result, IntrospectError> { let result = self.tree.introspect(node_path)?; if let Some(capture) = result.downcast_ref::() { let arena = self.arena.lock().unwrap_or_else(PoisonError::into_inner); - return capture.materialize(&arena).map(|fields| Arc::new(fields) as Arc<_>).ok_or(IntrospectError::NoData); + return capture.materialize_element(&arena).map(Arc::from).ok_or(IntrospectError::NoData); } Ok(result) } @@ -648,11 +650,9 @@ mod test { let executor = DynamicExecutor::new(network).unwrap(); assert_eq!((&executor).execute(()).unwrap(), GPoll::Final(TaggedValue::F64(7.))); - let fields = executor.introspect(&[NodeId(9)]).unwrap(); - let fields = fields - .downcast_ref::)>>() - .expect("a record capture materializes to its fields"); - assert!(fields.is_empty(), "an element-only record has no attribute fields"); + let element = executor.introspect(&[NodeId(9)]).unwrap(); + let element = element.downcast_ref::().expect("a record capture materializes to its element"); + assert_eq!(*element, 7.); } #[test] diff --git a/node-graph/interpreted-executor/src/node_registry.rs b/node-graph/interpreted-executor/src/node_registry.rs index 1636695e15..a9759ce206 100644 --- a/node-graph/interpreted-executor/src/node_registry.rs +++ b/node-graph/interpreted-executor/src/node_registry.rs @@ -2,7 +2,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::application_io::Texture; use graphene_std::brush::brush_stroke::BrushStroke; use graphene_std::gradient::GradientStops; use graphene_std::list::{AttributeDyn, AttributeValueDyn, List, ListDyn}; @@ -20,7 +19,7 @@ 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::{async_node, clone_node, convert_node, frame_memo_node, into_node, lend_node, record_extract_node, record_lift_node}; +use node_registry_macros::{clone_node, convert_node, frame_memo_node, into_node, lend_node, record_extract_node, record_lift_node}; use std::collections::HashMap; #[cfg(feature = "gpu")] use wgpu_executor::WgpuExecutorHandle; @@ -109,83 +108,6 @@ fn node_registry() -> HashMap> { // ============= // MONITOR NODES // ============= - async_node!(graphene_core::memo::MonitorNode<_, _>, input: Context, fn_params: [Context => ()]), - async_node!(graphene_core::memo::MonitorNode<_, _>, input: Context, fn_params: [Context => List]), - async_node!(graphene_core::memo::MonitorNode<_, _>, input: Context, fn_params: [Context => List]), - async_node!(graphene_core::memo::MonitorNode<_, _>, input: Context, fn_params: [Context => List]), - async_node!(graphene_core::memo::MonitorNode<_, _>, input: Context, fn_params: [Context => List>]), - #[cfg(feature = "gpu")] - async_node!(graphene_core::memo::MonitorNode<_, _>, input: Context, fn_params: [Context => List>]), - async_node!(graphene_core::memo::MonitorNode<_, _>, input: Context, fn_params: [Context => List]), - async_node!(graphene_core::memo::MonitorNode<_, _>, input: Context, fn_params: [Context => List]), - async_node!(graphene_core::memo::MonitorNode<_, _>, input: Context, fn_params: [Context => Image]), - async_node!(graphene_core::memo::MonitorNode<_, _>, input: Context, fn_params: [Context => String]), - async_node!(graphene_core::memo::MonitorNode<_, _>, input: Context, fn_params: [Context => IVec2]), - async_node!(graphene_core::memo::MonitorNode<_, _>, input: Context, fn_params: [Context => DVec2]), - async_node!(graphene_core::memo::MonitorNode<_, _>, input: Context, fn_params: [Context => DAffine2]), - async_node!(graphene_core::memo::MonitorNode<_, _>, input: Context, fn_params: [Context => Option]), - async_node!(graphene_core::memo::MonitorNode<_, _>, input: Context, fn_params: [Context => bool]), - async_node!(graphene_core::memo::MonitorNode<_, _>, input: Context, fn_params: [Context => f64]), - async_node!(graphene_core::memo::MonitorNode<_, _>, input: Context, fn_params: [Context => u32]), - async_node!(graphene_core::memo::MonitorNode<_, _>, input: Context, fn_params: [Context => u64]), - async_node!(graphene_core::memo::MonitorNode<_, _>, input: Context, fn_params: [Context => BlendMode]), - async_node!(graphene_core::memo::MonitorNode<_, _>, input: Context, fn_params: [Context => Texture]), - async_node!(graphene_core::memo::MonitorNode<_, _>, input: Context, fn_params: [Context => graphene_std::application_io::resource::Resource]), - async_node!(graphene_core::memo::MonitorNode<_, _>, input: Context, fn_params: [Context => graphene_std::transform::ReferencePoint]), - async_node!(graphene_core::memo::MonitorNode<_, _>, input: Context, fn_params: [Context => graphene_std::vector::misc::BooleanOperation]), - async_node!(graphene_core::memo::MonitorNode<_, _>, input: Context, fn_params: [Context => graphene_std::vector::style::StrokeCap]), - async_node!(graphene_core::memo::MonitorNode<_, _>, input: Context, fn_params: [Context => graphene_std::vector::style::StrokeJoin]), - async_node!(graphene_core::memo::MonitorNode<_, _>, input: Context, fn_params: [Context => graphene_std::vector::style::PaintOrder]), - async_node!(graphene_core::memo::MonitorNode<_, _>, input: Context, fn_params: [Context => graphene_std::vector::style::StrokeAlign]), - async_node!(graphene_core::memo::MonitorNode<_, _>, input: Context, fn_params: [Context => graphene_std::vector::style::Stroke]), - async_node!(graphene_core::memo::MonitorNode<_, _>, input: Context, fn_params: [Context => Box]), - async_node!(graphene_core::memo::MonitorNode<_, _>, input: Context, fn_params: [Context => graphene_std::vector::misc::CentroidType]), - async_node!(graphene_core::memo::MonitorNode<_, _>, input: Context, fn_params: [Context => graphene_std::vector::misc::PointSpacingType]), - async_node!(graphene_core::memo::MonitorNode<_, _>, input: Context, fn_params: [Context => Option]), - async_node!(graphene_core::memo::MonitorNode<_, _>, input: Context, fn_params: [Context => List]), - async_node!(graphene_core::memo::MonitorNode<_, _>, input: Context, fn_params: [Context => List]), - async_node!(graphene_core::memo::MonitorNode<_, _>, input: Context, fn_params: [Context => List]), - async_node!(graphene_core::memo::MonitorNode<_, _>, input: Context, fn_params: [Context => List]), - async_node!(graphene_core::memo::MonitorNode<_, _>, input: Context, fn_params: [Context => List]), - async_node!(graphene_core::memo::MonitorNode<_, _>, input: Context, fn_params: [Context => List]), - async_node!(graphene_core::memo::MonitorNode<_, _>, input: Context, fn_params: [Context => List]), - async_node!(graphene_core::memo::MonitorNode<_, _>, input: Context, fn_params: [Context => List]), - async_node!(graphene_core::memo::MonitorNode<_, _>, input: Context, fn_params: [Context => List]), - async_node!(graphene_core::memo::MonitorNode<_, _>, input: Context, fn_params: [Context => AttributeDyn]), - async_node!(graphene_core::memo::MonitorNode<_, _>, input: Context, fn_params: [Context => AttributeValueDyn]), - async_node!(graphene_core::memo::MonitorNode<_, _>, input: Context, fn_params: [Context => ListDyn]), - async_node!(graphene_core::memo::MonitorNode<_, _>, input: Context, fn_params: [Context => Graphic]), - async_node!(graphene_core::memo::MonitorNode<_, _>, input: Context, fn_params: [Context => graphene_std::text::Font]), - async_node!(graphene_core::memo::MonitorNode<_, _>, input: Context, fn_params: [Context => List]), - async_node!(graphene_core::memo::MonitorNode<_, _>, input: Context, fn_params: [Context => DocumentNode]), - async_node!(graphene_core::memo::MonitorNode<_, _>, input: Context, fn_params: [Context => graphene_std::transform::Footprint]), - async_node!(graphene_core::memo::MonitorNode<_, _>, input: Context, fn_params: [Context => graphene_std::blending::BlendMode]), - async_node!(graphene_core::memo::MonitorNode<_, _>, input: Context, fn_params: [Context => graphene_std::raster::adjustments::LuminanceCalculation]), - async_node!(graphene_core::memo::MonitorNode<_, _>, input: Context, fn_params: [Context => graphene_std::extract_xy::XY]), - async_node!(graphene_core::memo::MonitorNode<_, _>, input: Context, fn_params: [Context => graphene_std::text_nodes::StringCapitalization]), - async_node!(graphene_core::memo::MonitorNode<_, _>, input: Context, fn_params: [Context => graphene_std::raster::adjustments::RedGreenBlue]), - async_node!(graphene_core::memo::MonitorNode<_, _>, input: Context, fn_params: [Context => graphene_std::raster::adjustments::RedGreenBlueAlpha]), - async_node!(graphene_core::memo::MonitorNode<_, _>, input: Context, fn_params: [Context => graphene_std::animation::RealTimeMode]), - async_node!(graphene_core::memo::MonitorNode<_, _>, input: Context, fn_params: [Context => graphene_std::raster::adjustments::NoiseType]), - async_node!(graphene_core::memo::MonitorNode<_, _>, input: Context, fn_params: [Context => graphene_std::raster::adjustments::FractalType]), - async_node!(graphene_core::memo::MonitorNode<_, _>, input: Context, fn_params: [Context => graphene_std::raster::adjustments::CellularDistanceFunction]), - async_node!(graphene_core::memo::MonitorNode<_, _>, input: Context, fn_params: [Context => graphene_std::raster::adjustments::CellularReturnType]), - async_node!(graphene_core::memo::MonitorNode<_, _>, input: Context, fn_params: [Context => graphene_std::raster::adjustments::DomainWarpType]), - async_node!(graphene_core::memo::MonitorNode<_, _>, input: Context, fn_params: [Context => graphene_std::raster::adjustments::RelativeAbsolute]), - async_node!(graphene_core::memo::MonitorNode<_, _>, input: Context, fn_params: [Context => graphene_std::raster::adjustments::SelectiveColorChoice]), - async_node!(graphene_core::memo::MonitorNode<_, _>, input: Context, fn_params: [Context => graphene_std::vector::misc::GridType]), - async_node!(graphene_core::memo::MonitorNode<_, _>, input: Context, fn_params: [Context => graphene_std::vector::misc::ArcType]), - async_node!(graphene_core::memo::MonitorNode<_, _>, input: Context, fn_params: [Context => graphene_std::vector::misc::RowsOrColumns]), - async_node!(graphene_core::memo::MonitorNode<_, _>, input: Context, fn_params: [Context => graphene_std::vector::misc::MergeByDistanceAlgorithm]), - async_node!(graphene_core::memo::MonitorNode<_, _>, input: Context, fn_params: [Context => graphene_std::vector::misc::ExtrudeJoiningAlgorithm]), - async_node!(graphene_core::memo::MonitorNode<_, _>, input: Context, fn_params: [Context => graphene_std::vector::misc::PointSpacingType]), - async_node!(graphene_core::memo::MonitorNode<_, _>, input: Context, fn_params: [Context => graphene_std::vector::style::GradientType]), - async_node!(graphene_core::memo::MonitorNode<_, _>, input: Context, fn_params: [Context => graphene_std::vector::style::GradientSpreadMethod]), - async_node!(graphene_core::memo::MonitorNode<_, _>, input: Context, fn_params: [Context => graphene_std::transform::ReferencePoint]), - async_node!(graphene_core::memo::MonitorNode<_, _>, input: Context, fn_params: [Context => graphene_std::vector::misc::CentroidType]), - async_node!(graphene_core::memo::MonitorNode<_, _>, input: Context, fn_params: [Context => graphene_std::text::TextAlign]), - async_node!(graphene_core::memo::MonitorNode<_, _>, input: Context, fn_params: [Context => graphene_std::transform::ScaleType]), - async_node!(graphene_core::memo::MonitorNode<_, _>, input: Context, fn_params: [Context => graphene_std::vector::misc::InterpolationDistribution]), // ========== // MEMO NODES // ========== @@ -354,31 +276,6 @@ fn node_registry() -> HashMap> { record_lift_node!(wgpu_executor::WgpuPipelineCache), #[cfg(feature = "gpu")] record_extract_node!(wgpu_executor::WgpuPipelineCache), - ( - ProtoNodeIdentifier::new("graphene_core::memo::MonitorNode"), - RegistryEntry { - io: NodeIOTypes::new( - concrete!(Context), - core_types::Type::Record(Box::new(core_types::Type::Generic(std::borrow::Cow::Borrowed("T")))), - vec![core_types::registry::generic_record_edge_type("T")], - ), - constructor: |inputs| { - if inputs.len() != 1 { - return Err(ConstructionError::Arity { expected: 1, got: inputs.len() }); - } - let mut inputs = inputs.into_iter(); - let handle = inputs.next().unwrap(); - let ty = handle.ty().clone(); - let Some(layout) = handle.layout().cloned() else { - return Err(ConstructionError::MissingLayout); - }; - let edge = handle.downcast_erased::(ty.clone())?; - let node = core_types::record::RecordMonitor::new(edge, &layout); - Ok(EdgeHandle::new_erased(std::sync::Arc::new(node) as std::sync::Arc, ty)) - }, - }, - ), - clone_node!(f64), frame_memo_node!(f64), lend_node!(f32), clone_node!(f32), @@ -630,30 +527,6 @@ fn node_registry() -> HashMap> { pub static NODE_REGISTRY: once_cell::sync::Lazy>> = once_cell::sync::Lazy::new(node_registry); mod node_registry_macros { - macro_rules! async_node { - // This `params` variant of the macro wraps the normal `fn_params` variant and is used as a shorthand for writing `T` instead of `() => T` - ($path:ty, input: $input:ty, params: [$($type:ty),*]) => { - async_node!($path, input: $input, fn_params: [ $(() => $type),*]) - }; - ($path:ty, input: $input:ty, fn_params: [$first_arg:ty => $first:ty $(, $arg:ty => $type:ty)*]) => { - ( - ProtoNodeIdentifier::new(stringify!($path)), - RegistryEntry { - io: NodeIOTypes::new(concrete!($input), concrete!($first), vec![fn_type!($first_arg, $first) $(, fn_type!($arg, $type))*]), - constructor: |inputs| { - let expected = [stringify!($first) $(, stringify!($type))*].len(); - if inputs.len() != expected { - return Err(ConstructionError::Arity { expected, got: inputs.len() }); - } - let mut inputs = inputs.into_iter(); - let node = <$path>::new(inputs.next().unwrap().downcast::<$first>()? $(, inputs.next().unwrap().downcast::<$type>()?)*); - Ok(EdgeHandle::new(std::sync::Arc::new(node) as std::sync::Arc>)) - }, - }, - ) - }; - } - macro_rules! into_node { (from: $from:ty, to: $to:ty) => { ( @@ -854,7 +727,6 @@ mod node_registry_macros { }; } - pub(crate) use async_node; pub(crate) use clone_node; pub(crate) use convert_node; pub(crate) use frame_memo_node; diff --git a/node-graph/libraries/core-types/src/memo.rs b/node-graph/libraries/core-types/src/memo.rs index 80766fc860..64cff1a124 100644 --- a/node-graph/libraries/core-types/src/memo.rs +++ b/node-graph/libraries/core-types/src/memo.rs @@ -4,13 +4,6 @@ use std::hash::{Hash, Hasher}; use std::ops::Deref; use std::sync::Arc; -/// Stores both what a node was called with and what it returned. -#[derive(Clone, Debug)] -pub struct IORecord { - pub input: I, - pub output: O, -} - #[derive(Clone, Debug)] pub struct MemoHash { hash: u64, diff --git a/node-graph/libraries/core-types/src/record.rs b/node-graph/libraries/core-types/src/record.rs index 8353e82734..0eb155ea02 100644 --- a/node-graph/libraries/core-types/src/record.rs +++ b/node-graph/libraries/core-types/src/record.rs @@ -752,7 +752,26 @@ pub struct RecordCapture { bytes: crate::arena::ArenaWeak>, } +impl std::fmt::Debug for RecordCapture { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str("RecordCapture(..)") + } +} + impl RecordCapture { + /// # Safety + /// `rec` must be a live record of `layout`. + pub unsafe fn capture(layout: &Layout, rec: Rec, arena: &crate::arena::Arena) -> Option { + let bytes: Box<[u8]> = unsafe { std::slice::from_raw_parts(rec.ptr(), layout.size) }.into(); + arena.alloc(bytes).map(|(_, weak)| RecordCapture { layout: layout.clone(), bytes: weak }) + } + + /// The captured element, cloned out through the layout's erased glue. + pub fn materialize_element(&self, arena: &crate::arena::Arena) -> Option> { + let bytes = self.bytes.upgrade(arena)?; + Some(unsafe { (self.layout.element.clone_out)(bytes.as_ptr()) }) + } + pub fn materialize(&self, arena: &crate::arena::Arena) -> Option)>> { let bytes = self.bytes.upgrade(arena)?; Some( @@ -765,56 +784,6 @@ impl RecordCapture { } } -// TODO: Convert to a `#[node_macro::node]` node once routing nodes forward -// layouts and the macro grows a capture capability. -/// The monitor over a record wire: forwards the record and captures an arena -/// copy readable through the introspection window, like a frame memo. -pub struct RecordMonitor { - edge: N, - layout: Layout, - capture: std::sync::Mutex>, -} - -impl RecordMonitor { - pub fn new(edge: N, layout: &Layout) -> Self { - Self { - edge, - layout: layout.clone(), - capture: std::sync::Mutex::new(None), - } - } -} - -impl<'e, C, N> Node for RecordMonitor -where - C: crate::context::ExtractArena, - N: Node>, -{ - type Output = RecordValue<'e>; - - fn eval(&self, input: &C) -> GPoll> { - let value = self.edge.eval(input); - if let GPoll::Final(record) | GPoll::Partial(record) = &value { - let bytes: Box<[u8]> = unsafe { std::slice::from_raw_parts(self.layout.rec(record).ptr(), self.layout.size) }.into(); - let capture = input.arena().alloc(bytes).map(|(_, weak)| RecordCapture { - layout: self.layout.clone(), - bytes: weak, - }); - *self.capture.lock().unwrap_or_else(std::sync::PoisonError::into_inner) = capture; - } - value - } - - fn layout(&self) -> Option<&Layout> { - Some(&self.layout) - } - - fn serialize(&self) -> Option> { - let capture = self.capture.lock().unwrap_or_else(std::sync::PoisonError::into_inner).clone()?; - Some(std::sync::Arc::new(capture)) - } -} - /// A record deep-copied out of its evaluation: the packed bytes plus owned /// clones of every parked payload, replayable into a later evaluation's /// storage through the layout's erased glue. The layout stays with the diff --git a/node-graph/nodes/gcore/src/memo.rs b/node-graph/nodes/gcore/src/memo.rs index 36bf5cb474..5ae8e7dbc4 100644 --- a/node-graph/nodes/gcore/src/memo.rs +++ b/node-graph/nodes/gcore/src/memo.rs @@ -1,11 +1,10 @@ use core_types::arena::{Arena, ArenaCell}; -use core_types::context::{Ctx, CtxSnapshot, DeriveCtx, ExtractAll, ExtractArena}; +use core_types::context::{Ctx, ExtractArena}; use core_types::frame_table::{FrameTable, Lookup}; -use core_types::gpoll::{Extent, Finality, GPoll, Interrupt}; +use core_types::gpoll::{Extent, Finality, GPoll}; use core_types::graphene_hash::CacheHash; -use core_types::memo::*; use core_types::node::Node; -use core_types::record::{OwnedRecord, RecordValue}; +use core_types::record::{OwnedRecord, RecordCapture, RecordValue}; use core_types::registry::cache_key; use std::sync::Arc; use std::sync::Mutex; @@ -120,28 +119,23 @@ fn lend<'e, T: Send + Sync>(ctx: impl Ctx + ExtractArena<'e>, value: T) -> GPoll park(ctx.arena(), GPoll::Final(value)) } -type MonitorValue = Arc>>>>; +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"), skip_impl, plain)] -fn monitor( - ctx: impl Ctx + DeriveCtx + ExtractAll, - #[allow(clippy::type_complexity)] - #[data] - io: MonitorValue, - content: impl Node, Output = T>, -) -> Result { - let output = content.eval(&ctx.derived())?; - *io.lock().unwrap() = Some(Arc::new(IORecord { - input: CtxSnapshot::capture(ctx), - output: output.clone(), - })); - Ok(output) +#[node_macro::node(category(""), path(graphene_core::memo), serialize(serialize_monitor), properties("monitor_properties"))] +fn monitor<'e>(ctx: impl Ctx + ExtractArena<'e>, #[data] capture: MonitorValue, content: impl Node, Output = RecordValue<'e>>) -> GPoll> { + let result = content.eval(&ctx); + if let GPoll::Final(value) | GPoll::Partial(value) = &result { + // SAFETY: the value came from this edge, so it carries the edge's layout. + let captured = unsafe { RecordCapture::capture(content.layout(), content.layout().rec(value), ctx.arena()) }; + *capture.lock().unwrap() = captured; + } + result } -fn serialize_monitor(io: &MonitorValue) -> Option> { - let io = io.lock().unwrap(); - io.as_ref().map(|output| output.clone() as Arc) +fn serialize_monitor(capture: &MonitorValue) -> Option> { + let capture = capture.lock().unwrap(); + capture.as_ref().map(|capture| Arc::new(capture.clone()) as Arc) } #[cfg(test)] @@ -191,21 +185,26 @@ mod tests { } #[test] - fn monitor_serialize_exposes_the_io_record_through_the_edge() { + fn monitor_serialize_exposes_the_capture_through_the_edge() { let arena = Arena::new(1024).unwrap(); let generations = []; let scope = scope_fixture(&generations, &arena); let ctx = ContextImpl::root(&scope); - let handle = EdgeHandle::new(Arc::new(MonitorNode::new(ValueNode(11u32))) as Arc>); - assert!(handle.serialize().is_none(), "no record before the first eval"); + let layout = element_layout::(); + let monitor = MonitorNode::new(core_types::record::RecordLift::::new(ValueNode(11u32)), &layout); + let handle = EdgeHandle::new_record::(Arc::new(monitor) as Arc); + assert!(handle.serialize().is_none(), "no capture before the first eval"); - let edge = handle.duplicate().downcast::().unwrap(); - assert_eq!(edge.eval(&ctx), GPoll::Final(11)); + let edge = handle.duplicate().downcast_record::().unwrap(); + let GPoll::Final(_) = edge.eval(&ctx) else { + panic!("expected a final record"); + }; - let record = handle.serialize().expect("the eval landed a record"); - let record = record.downcast_ref::>().expect("the record is the monitor io"); - assert_eq!(record.output, 11); + let capture = handle.serialize().expect("the eval landed a capture"); + let capture = capture.downcast_ref::().expect("the capture is a record capture"); + let element = capture.materialize_element(&arena).expect("the capture materializes inside the window"); + assert_eq!(*element.downcast_ref::().unwrap(), 11); } #[test] diff --git a/node-graph/nodes/gcore/src/record.rs b/node-graph/nodes/gcore/src/record.rs index 6dc929b096..22680d64f7 100644 --- a/node-graph/nodes/gcore/src/record.rs +++ b/node-graph/nodes/gcore/src/record.rs @@ -464,7 +464,7 @@ mod tests { let layout = f64_layout(&["opacity"]); reserve_for(&[&layout, &layout]); - let monitor = core_types::record::RecordMonitor::new(f64_record_source(&layout, 4., vec![(layout.offset_of("opacity", 0).unwrap(), 0.25)]), &layout); + let monitor = crate::memo::MonitorNode::new(f64_record_source(&layout, 4., vec![(layout.offset_of("opacity", 0).unwrap(), 0.25)]), &layout); { let scope = scope_fixture(&generations, &arena); let ctx = ContextImpl::root(&scope);