Define the monitor through the record-opaque class and move introspection to element reads

This commit is contained in:
Dennis Kobert
2026-08-06 09:07:57 +00:00
parent a2de2a9c09
commit daf3f25d21
9 changed files with 80 additions and 267 deletions
@@ -10,11 +10,10 @@ use graphene_std::blending::BlendMode;
use graphene_std::color::SRGBA8; use graphene_std::color::SRGBA8;
use graphene_std::gradient::GradientStops; use graphene_std::gradient::GradientStops;
use graphene_std::list::List; use graphene_std::list::List;
use graphene_std::memo::IORecord;
use graphene_std::raster_types::{CPU, GPU, Raster}; use graphene_std::raster_types::{CPU, GPU, Raster};
use graphene_std::vector::Vector; use graphene_std::vector::Vector;
use graphene_std::vector::style::{FillChoice, FillChoiceUI, GradientSpreadMethod, GradientType}; 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::any::Any;
use std::sync::Arc; use std::sync::Arc;
@@ -167,8 +166,8 @@ macro_rules! generate_layout_downcast {
($introspected_data:expr, $data:expr, [ $($ty:ty),* $(,)? ]) => { ($introspected_data:expr, $data:expr, [ $($ty:ty),* $(,)? ]) => {
if false { None } if false { None }
$( $(
else if let Some(io) = $introspected_data.downcast_ref::<IORecord<CtxSnapshot, $ty>>() { else if let Some(element) = $introspected_data.downcast_ref::<$ty>() {
Some(io.output.layout_with_breadcrumb($data)) Some(element.layout_with_breadcrumb($data))
} }
)* )*
else { None } else { None }
@@ -178,8 +177,8 @@ macro_rules! generate_layout_downcast {
fn generate_layout(introspected_data: &Arc<dyn std::any::Any + Send + Sync + 'static>, data: &mut LayoutData) -> Option<Vec<LayoutGroup>> { fn generate_layout(introspected_data: &Arc<dyn std::any::Any + Send + Sync + 'static>, data: &mut LayoutData) -> Option<Vec<LayoutGroup>> {
// `List<NodeId>` is interpreted as a path (e.g. the value produced by `path_of_subgraph`), shown as a // `List<NodeId>` 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. // `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::<IORecord<CtxSnapshot, List<NodeId>>>() { if let Some(list) = introspected_data.downcast_ref::<List<NodeId>>() {
return Some(table_node_id_path_layout_with_breadcrumb(&io.output, data)); return Some(table_node_id_path_layout_with_breadcrumb(list, data));
} }
generate_layout_downcast!(introspected_data, data, [ generate_layout_downcast!(introspected_data, data, [
List<Artboard>, List<Artboard>,
+6 -24
View File
@@ -10,12 +10,11 @@ use graphene_std::application_io::{ExportFormat, NodeGraphUpdateMessage, RenderC
use graphene_std::bounds::RenderBoundingBox; use graphene_std::bounds::RenderBoundingBox;
use graphene_std::color::SRGBA8; use graphene_std::color::SRGBA8;
use graphene_std::list::List; use graphene_std::list::List;
use graphene_std::memo::IORecord;
use graphene_std::raster::{CPU, Raster}; use graphene_std::raster::{CPU, Raster};
use graphene_std::renderer::{RenderMetadata, graphic_list_bounding_box}; use graphene_std::renderer::{RenderMetadata, graphic_list_bounding_box};
use graphene_std::transform::Footprint; use graphene_std::transform::Footprint;
use graphene_std::vector::{Vector, graphic_types}; 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 interpreted_executor::dynamic_executor::ResolvedDocumentNodeTypesDelta;
use std::any::Any; use std::any::Any;
use std::sync::Arc; use std::sync::Arc;
@@ -900,18 +899,9 @@ fn measure_fill_geometry(data: &Arc<dyn Any + Send + Sync>) -> Option<(DAffine2,
} }
// TODO: Eventually remove this document upgrade code // 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<T: Clone + Send + Sync + 'static>(data: &Arc<dyn Any + Send + Sync>) -> Option<T> { fn introspected_output<T: Clone + Send + Sync + 'static>(data: &Arc<dyn Any + Send + Sync>) -> Option<T> {
if let Some(io) = data.downcast_ref::<IORecord<(), T>>() { data.downcast_ref::<T>().cloned()
return Some(io.output.clone());
}
if let Some(io) = data.downcast_ref::<IORecord<Footprint, T>>() {
return Some(io.output.clone());
}
if let Some(io) = data.downcast_ref::<IORecord<CtxSnapshot, T>>() {
return Some(io.output.clone());
}
None
} }
// Re-export for usage by tests in other modules // Re-export for usage by tests in other modules
@@ -927,9 +917,7 @@ mod test {
use crate::test_utils::test_prelude::{self, NodeGraphLayer}; use crate::test_utils::test_prelude::{self, NodeGraphLayer};
use graph_craft::ProtoNodeIdentifier; use graph_craft::ProtoNodeIdentifier;
use graph_craft::document::NodeNetwork; use graph_craft::document::NodeNetwork;
use graphene_std::CtxSnapshot;
use graphene_std::NodeInputDecleration; use graphene_std::NodeInputDecleration;
use graphene_std::memo::IORecord;
use test_prelude::LayerNodeIdentifier; use test_prelude::LayerNodeIdentifier;
/// Stores all of the monitor nodes that have been attached to a graph /// Stores all of the monitor nodes that have been attached to a graph
@@ -990,17 +978,11 @@ mod test {
where where
Input::Result: Send + Sync + Clone + 'static, Input::Result: Send + Sync + Clone + 'static,
{ {
// This is quite inflexible since it only allows the footprint as inputs. let element = dynamic.downcast_ref::<Input::Result>().cloned();
if let Some(x) = dynamic.downcast_ref::<IORecord<(), Input::Result>>() { if element.is_none() {
Some(x.output.clone())
} else if let Some(x) = dynamic.downcast_ref::<IORecord<Footprint, Input::Result>>() {
Some(x.output.clone())
} else if let Some(x) = dynamic.downcast_ref::<IORecord<CtxSnapshot, Input::Result>>() {
Some(x.output.clone())
} else {
warn!("cannot downcast type for introspection"); warn!("cannot downcast type for introspection");
None
} }
element
} }
/// Grab all of the values of the input every time it occurs in the graph. /// Grab all of the values of the input every time it occurs in the graph.
+12 -13
View File
@@ -11,7 +11,6 @@ use graphene_std::application_io::{ApplicationIo, ExportFormat, NodeGraphUpdateM
use graphene_std::bounds::RenderBoundingBox; use graphene_std::bounds::RenderBoundingBox;
use graphene_std::core_types::gpoll::GPoll; use graphene_std::core_types::gpoll::GPoll;
use graphene_std::list::List; use graphene_std::list::List;
use graphene_std::memo::IORecord;
use graphene_std::ops::ConvertAsync; use graphene_std::ops::ConvertAsync;
#[cfg(all(target_family = "wasm", feature = "gpu", feature = "wasm"))] #[cfg(all(target_family = "wasm", feature = "gpu", feature = "wasm"))]
use graphene_std::platform_application_io::canvas_utils::{Canvas, CanvasSurface, CanvasSurfaceHandle}; 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::transform::RenderQuality;
use graphene_std::vector::Vector; use graphene_std::vector::Vector;
use graphene_std::vector::style::RenderMode; 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::dynamic_executor::{DynamicExecutor, ResolvedDocumentNodeTypesDelta};
use interpreted_executor::util::wrap_network_in_scope; use interpreted_executor::util::wrap_network_in_scope;
use spin::Mutex; 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) // 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::<IORecord<CtxSnapshot, List<Graphic>>>() { if let Some(list) = introspected_data.downcast_ref::<List<Graphic>>() {
if update_thumbnails { if update_thumbnails {
let bounds = graphene_std::renderer::graphic_list_bounding_box(&io.output, DAffine2::IDENTITY); let bounds = graphene_std::renderer::graphic_list_bounding_box(list, DAffine2::IDENTITY);
Self::render_thumbnail(&mut self.thumbnail_renders, parent_network_node_id, &io.output, bounds, responses) 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 // 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 // clips content to those rectangles so anything outside isn't visible
else if let Some(io) = introspected_data.downcast_ref::<IORecord<CtxSnapshot, List<Artboard>>>() { else if let Some(list) = introspected_data.downcast_ref::<List<Artboard>>() {
if update_thumbnails { if update_thumbnails {
let bounds = artboard_clip_bounds(&io.output); let bounds = artboard_clip_bounds(list);
Self::render_thumbnail(&mut self.thumbnail_renders, parent_network_node_id, &io.output, bounds, responses) Self::render_thumbnail(&mut self.thumbnail_renders, parent_network_node_id, list, bounds, responses)
} }
} }
// Vector list: vector modifications // Vector list: vector modifications
else if let Some(io) = introspected_data.downcast_ref::<IORecord<CtxSnapshot, List<Vector>>>() { else if let Some(list) = introspected_data.downcast_ref::<List<Vector>>() {
// Insert the vector modify // 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`) // 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::<IORecord<CtxSnapshot, List<String>>>() { else if let Some(list) = introspected_data.downcast_ref::<List<String>>() {
if update_thumbnails { if update_thumbnails {
let bounds = graphene_std::renderer::text_list_bounding_box(&io.output, DAffine2::IDENTITY); let bounds = graphene_std::renderer::text_list_bounding_box(list, DAffine2::IDENTITY);
Self::render_thumbnail(&mut self.thumbnail_renders, parent_network_node_id, &io.output, bounds, responses) Self::render_thumbnail(&mut self.thumbnail_renders, parent_network_node_id, list, bounds, responses)
} }
} }
// Other // Other
@@ -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. /// 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<Arc<dyn std::any::Any + Send + Sync + 'static>, IntrospectError> { pub fn introspect(&self, node_path: &[NodeId]) -> Result<Arc<dyn std::any::Any + Send + Sync + 'static>, IntrospectError> {
let result = self.tree.introspect(node_path)?; let result = self.tree.introspect(node_path)?;
if let Some(capture) = result.downcast_ref::<core_types::record::RecordCapture>() { if let Some(capture) = result.downcast_ref::<core_types::record::RecordCapture>() {
let arena = self.arena.lock().unwrap_or_else(PoisonError::into_inner); 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) Ok(result)
} }
@@ -648,11 +650,9 @@ mod test {
let executor = DynamicExecutor::new(network).unwrap(); let executor = DynamicExecutor::new(network).unwrap();
assert_eq!((&executor).execute(()).unwrap(), GPoll::Final(TaggedValue::F64(7.))); assert_eq!((&executor).execute(()).unwrap(), GPoll::Final(TaggedValue::F64(7.)));
let fields = executor.introspect(&[NodeId(9)]).unwrap(); let element = executor.introspect(&[NodeId(9)]).unwrap();
let fields = fields let element = element.downcast_ref::<f64>().expect("a record capture materializes to its element");
.downcast_ref::<Vec<(&'static str, Box<dyn core_types::list::AnyAttributeValue>)>>() assert_eq!(*element, 7.);
.expect("a record capture materializes to its fields");
assert!(fields.is_empty(), "an element-only record has no attribute fields");
} }
#[test] #[test]
@@ -2,7 +2,6 @@ use glam::{DAffine2, DVec2, IVec2};
use graph_craft::application_io::PlatformEditorApi; use graph_craft::application_io::PlatformEditorApi;
use graph_craft::document::DocumentNode; use graph_craft::document::DocumentNode;
use graph_craft::document::value::RenderOutput; use graph_craft::document::value::RenderOutput;
use graphene_std::application_io::Texture;
use graphene_std::brush::brush_stroke::BrushStroke; use graphene_std::brush::brush_stroke::BrushStroke;
use graphene_std::gradient::GradientStops; use graphene_std::gradient::GradientStops;
use graphene_std::list::{AttributeDyn, AttributeValueDyn, List, ListDyn}; 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::uuid::NodeId;
use graphene_std::vector::Vector; use graphene_std::vector::Vector;
use graphene_std::{Artboard, Context, Graphic, ProtoNodeIdentifier, SourceId, concrete, fn_type}; 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; use std::collections::HashMap;
#[cfg(feature = "gpu")] #[cfg(feature = "gpu")]
use wgpu_executor::WgpuExecutorHandle; use wgpu_executor::WgpuExecutorHandle;
@@ -109,83 +108,6 @@ fn node_registry() -> HashMap<ProtoNodeIdentifier, Vec<RegistryEntry>> {
// ============= // =============
// MONITOR NODES // 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<Artboard>]),
async_node!(graphene_core::memo::MonitorNode<_, _>, input: Context, fn_params: [Context => List<Graphic>]),
async_node!(graphene_core::memo::MonitorNode<_, _>, input: Context, fn_params: [Context => List<Vector>]),
async_node!(graphene_core::memo::MonitorNode<_, _>, input: Context, fn_params: [Context => List<Raster<CPU>>]),
#[cfg(feature = "gpu")]
async_node!(graphene_core::memo::MonitorNode<_, _>, input: Context, fn_params: [Context => List<Raster<GPU>>]),
async_node!(graphene_core::memo::MonitorNode<_, _>, input: Context, fn_params: [Context => List<Color>]),
async_node!(graphene_core::memo::MonitorNode<_, _>, input: Context, fn_params: [Context => List<GradientStops>]),
async_node!(graphene_core::memo::MonitorNode<_, _>, input: Context, fn_params: [Context => Image<Color>]),
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<DAffine2>]),
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<graphene_std::vector::VectorModification>]),
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<f64>]),
async_node!(graphene_core::memo::MonitorNode<_, _>, input: Context, fn_params: [Context => List<String>]),
async_node!(graphene_core::memo::MonitorNode<_, _>, input: Context, fn_params: [Context => List<NodeId>]),
async_node!(graphene_core::memo::MonitorNode<_, _>, input: Context, fn_params: [Context => List<f64>]),
async_node!(graphene_core::memo::MonitorNode<_, _>, input: Context, fn_params: [Context => List<u8>]),
async_node!(graphene_core::memo::MonitorNode<_, _>, input: Context, fn_params: [Context => List<bool>]),
async_node!(graphene_core::memo::MonitorNode<_, _>, input: Context, fn_params: [Context => List<DAffine2>]),
async_node!(graphene_core::memo::MonitorNode<_, _>, input: Context, fn_params: [Context => List<BlendMode>]),
async_node!(graphene_core::memo::MonitorNode<_, _>, input: Context, fn_params: [Context => List<graphene_std::vector::style::GradientType>]),
async_node!(graphene_core::memo::MonitorNode<_, _>, input: Context, fn_params: [Context => List<graphene_std::vector::style::GradientSpreadMethod>]),
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<BrushStroke>]),
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 // MEMO NODES
// ========== // ==========
@@ -354,31 +276,6 @@ fn node_registry() -> HashMap<ProtoNodeIdentifier, Vec<RegistryEntry>> {
record_lift_node!(wgpu_executor::WgpuPipelineCache), record_lift_node!(wgpu_executor::WgpuPipelineCache),
#[cfg(feature = "gpu")] #[cfg(feature = "gpu")]
record_extract_node!(wgpu_executor::WgpuPipelineCache), 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::<core_types::registry::ErasedRecordNode>(ty.clone())?;
let node = core_types::record::RecordMonitor::new(edge, &layout);
Ok(EdgeHandle::new_erased(std::sync::Arc::new(node) as std::sync::Arc<core_types::registry::ErasedRecordNode>, ty))
},
},
),
clone_node!(f64),
frame_memo_node!(f64), frame_memo_node!(f64),
lend_node!(f32), lend_node!(f32),
clone_node!(f32), clone_node!(f32),
@@ -630,30 +527,6 @@ fn node_registry() -> HashMap<ProtoNodeIdentifier, Vec<RegistryEntry>> {
pub static NODE_REGISTRY: once_cell::sync::Lazy<HashMap<ProtoNodeIdentifier, Vec<RegistryEntry>>> = once_cell::sync::Lazy::new(node_registry); pub static NODE_REGISTRY: once_cell::sync::Lazy<HashMap<ProtoNodeIdentifier, Vec<RegistryEntry>>> = once_cell::sync::Lazy::new(node_registry);
mod node_registry_macros { 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<ErasedNode<$first>>))
},
},
)
};
}
macro_rules! into_node { macro_rules! into_node {
(from: $from:ty, to: $to:ty) => { (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 clone_node;
pub(crate) use convert_node; pub(crate) use convert_node;
pub(crate) use frame_memo_node; pub(crate) use frame_memo_node;
@@ -4,13 +4,6 @@ use std::hash::{Hash, Hasher};
use std::ops::Deref; use std::ops::Deref;
use std::sync::Arc; use std::sync::Arc;
/// Stores both what a node was called with and what it returned.
#[derive(Clone, Debug)]
pub struct IORecord<I, O> {
pub input: I,
pub output: O,
}
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub struct MemoHash<T: CacheHash> { pub struct MemoHash<T: CacheHash> {
hash: u64, hash: u64,
+19 -50
View File
@@ -752,7 +752,26 @@ pub struct RecordCapture {
bytes: crate::arena::ArenaWeak<Box<[u8]>>, bytes: crate::arena::ArenaWeak<Box<[u8]>>,
} }
impl std::fmt::Debug for RecordCapture {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str("RecordCapture(..)")
}
}
impl 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<RecordCapture> {
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<Box<dyn std::any::Any + Send + Sync>> {
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<Vec<(&'static str, Box<dyn crate::list::AnyAttributeValue>)>> { pub fn materialize(&self, arena: &crate::arena::Arena) -> Option<Vec<(&'static str, Box<dyn crate::list::AnyAttributeValue>)>> {
let bytes = self.bytes.upgrade(arena)?; let bytes = self.bytes.upgrade(arena)?;
Some( 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<N> {
edge: N,
layout: Layout,
capture: std::sync::Mutex<Option<RecordCapture>>,
}
impl<N> RecordMonitor<N> {
pub fn new(edge: N, layout: &Layout) -> Self {
Self {
edge,
layout: layout.clone(),
capture: std::sync::Mutex::new(None),
}
}
}
impl<'e, C, N> Node<C> for RecordMonitor<N>
where
C: crate::context::ExtractArena<ArenaRef = &'e crate::arena::Arena>,
N: Node<C, Output = RecordValue<'e>>,
{
type Output = RecordValue<'e>;
fn eval(&self, input: &C) -> GPoll<RecordValue<'e>> {
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<std::sync::Arc<dyn std::any::Any + Send + Sync>> {
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 /// A record deep-copied out of its evaluation: the packed bytes plus owned
/// clones of every parked payload, replayable into a later evaluation's /// clones of every parked payload, replayable into a later evaluation's
/// storage through the layout's erased glue. The layout stays with the /// storage through the layout's erased glue. The layout stays with the
+29 -30
View File
@@ -1,11 +1,10 @@
use core_types::arena::{Arena, ArenaCell}; 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::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::graphene_hash::CacheHash;
use core_types::memo::*;
use core_types::node::Node; 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 core_types::registry::cache_key;
use std::sync::Arc; use std::sync::Arc;
use std::sync::Mutex; 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)) park(ctx.arena(), GPoll::Final(value))
} }
type MonitorValue<T> = Arc<Mutex<Option<Arc<IORecord<CtxSnapshot, T>>>>>; type MonitorValue = Arc<Mutex<Option<RecordCapture>>>;
/// The Monitor node is used by the editor to access the data flowing through it. /// 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)] #[node_macro::node(category(""), path(graphene_core::memo), serialize(serialize_monitor), properties("monitor_properties"))]
fn monitor<T: Clone + 'static + Send + Sync>( fn monitor<'e>(ctx: impl Ctx + ExtractArena<'e>, #[data] capture: MonitorValue, content: impl Node<Context<'_>, Output = RecordValue<'e>>) -> GPoll<RecordValue<'e>> {
ctx: impl Ctx + DeriveCtx + ExtractAll, let result = content.eval(&ctx);
#[allow(clippy::type_complexity)] if let GPoll::Final(value) | GPoll::Partial(value) = &result {
#[data] // SAFETY: the value came from this edge, so it carries the edge's layout.
io: MonitorValue<T>, let captured = unsafe { RecordCapture::capture(content.layout(), content.layout().rec(value), ctx.arena()) };
content: impl Node<Context<'_>, Output = T>, *capture.lock().unwrap() = captured;
) -> Result<T, Interrupt> { }
let output = content.eval(&ctx.derived())?; result
*io.lock().unwrap() = Some(Arc::new(IORecord {
input: CtxSnapshot::capture(ctx),
output: output.clone(),
}));
Ok(output)
} }
fn serialize_monitor<T: Clone + 'static + Send + Sync>(io: &MonitorValue<T>) -> Option<Arc<dyn std::any::Any + Send + Sync>> { fn serialize_monitor(capture: &MonitorValue) -> Option<Arc<dyn std::any::Any + Send + Sync>> {
let io = io.lock().unwrap(); let capture = capture.lock().unwrap();
io.as_ref().map(|output| output.clone() as Arc<dyn std::any::Any + Send + Sync>) capture.as_ref().map(|capture| Arc::new(capture.clone()) as Arc<dyn std::any::Any + Send + Sync>)
} }
#[cfg(test)] #[cfg(test)]
@@ -191,21 +185,26 @@ mod tests {
} }
#[test] #[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 arena = Arena::new(1024).unwrap();
let generations = []; let generations = [];
let scope = scope_fixture(&generations, &arena); let scope = scope_fixture(&generations, &arena);
let ctx = ContextImpl::root(&scope); let ctx = ContextImpl::root(&scope);
let handle = EdgeHandle::new(Arc::new(MonitorNode::new(ValueNode(11u32))) as Arc<ErasedNode<u32>>); let layout = element_layout::<u32>();
assert!(handle.serialize().is_none(), "no record before the first eval"); let monitor = MonitorNode::new(core_types::record::RecordLift::<u32, _>::new(ValueNode(11u32)), &layout);
let handle = EdgeHandle::new_record::<u32>(Arc::new(monitor) as Arc<ErasedRecordNode>);
assert!(handle.serialize().is_none(), "no capture before the first eval");
let edge = handle.duplicate().downcast::<u32>().unwrap(); let edge = handle.duplicate().downcast_record::<u32>().unwrap();
assert_eq!(edge.eval(&ctx), GPoll::Final(11)); let GPoll::Final(_) = edge.eval(&ctx) else {
panic!("expected a final record");
};
let record = handle.serialize().expect("the eval landed a record"); let capture = handle.serialize().expect("the eval landed a capture");
let record = record.downcast_ref::<IORecord<CtxSnapshot, u32>>().expect("the record is the monitor io"); let capture = capture.downcast_ref::<RecordCapture>().expect("the capture is a record capture");
assert_eq!(record.output, 11); let element = capture.materialize_element(&arena).expect("the capture materializes inside the window");
assert_eq!(*element.downcast_ref::<u32>().unwrap(), 11);
} }
#[test] #[test]
+1 -1
View File
@@ -464,7 +464,7 @@ mod tests {
let layout = f64_layout(&["opacity"]); let layout = f64_layout(&["opacity"]);
reserve_for(&[&layout, &layout]); 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 scope = scope_fixture(&generations, &arena);
let ctx = ContextImpl::root(&scope); let ctx = ContextImpl::root(&scope);