mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-09-15 22:28:10 +08:00
Define the monitor through the record-opaque class and move introspection to element reads
This commit is contained in:
@@ -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::<IORecord<CtxSnapshot, $ty>>() {
|
||||
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<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` 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>>>() {
|
||||
return Some(table_node_id_path_layout_with_breadcrumb(&io.output, data));
|
||||
if let Some(list) = introspected_data.downcast_ref::<List<NodeId>>() {
|
||||
return Some(table_node_id_path_layout_with_breadcrumb(list, data));
|
||||
}
|
||||
generate_layout_downcast!(introspected_data, data, [
|
||||
List<Artboard>,
|
||||
|
||||
@@ -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<dyn Any + Send + Sync>) -> 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<T: Clone + Send + Sync + 'static>(data: &Arc<dyn Any + Send + Sync>) -> Option<T> {
|
||||
if let Some(io) = data.downcast_ref::<IORecord<(), T>>() {
|
||||
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
|
||||
data.downcast_ref::<T>().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::<IORecord<(), Input::Result>>() {
|
||||
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 {
|
||||
let element = dynamic.downcast_ref::<Input::Result>().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.
|
||||
|
||||
@@ -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::<IORecord<CtxSnapshot, List<Graphic>>>() {
|
||||
if let Some(list) = introspected_data.downcast_ref::<List<Graphic>>() {
|
||||
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::<IORecord<CtxSnapshot, List<Artboard>>>() {
|
||||
else if let Some(list) = introspected_data.downcast_ref::<List<Artboard>>() {
|
||||
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::<IORecord<CtxSnapshot, List<Vector>>>() {
|
||||
else if let Some(list) = introspected_data.downcast_ref::<List<Vector>>() {
|
||||
// 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::<IORecord<CtxSnapshot, List<String>>>() {
|
||||
else if let Some(list) = introspected_data.downcast_ref::<List<String>>() {
|
||||
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
|
||||
|
||||
@@ -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<Arc<dyn std::any::Any + Send + Sync + 'static>, IntrospectError> {
|
||||
let result = self.tree.introspect(node_path)?;
|
||||
if let Some(capture) = result.downcast_ref::<core_types::record::RecordCapture>() {
|
||||
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::<Vec<(&'static str, Box<dyn core_types::list::AnyAttributeValue>)>>()
|
||||
.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::<f64>().expect("a record capture materializes to its element");
|
||||
assert_eq!(*element, 7.);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -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<ProtoNodeIdentifier, Vec<RegistryEntry>> {
|
||||
// =============
|
||||
// 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
|
||||
// ==========
|
||||
@@ -354,31 +276,6 @@ fn node_registry() -> HashMap<ProtoNodeIdentifier, Vec<RegistryEntry>> {
|
||||
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::<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),
|
||||
lend_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);
|
||||
|
||||
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 {
|
||||
(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;
|
||||
|
||||
@@ -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<I, O> {
|
||||
pub input: I,
|
||||
pub output: O,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct MemoHash<T: CacheHash> {
|
||||
hash: u64,
|
||||
|
||||
@@ -752,7 +752,26 @@ pub struct RecordCapture {
|
||||
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 {
|
||||
/// # 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>)>> {
|
||||
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<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
|
||||
/// clones of every parked payload, replayable into a later evaluation's
|
||||
/// storage through the layout's erased glue. The layout stays with the
|
||||
|
||||
@@ -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<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.
|
||||
#[node_macro::node(category(""), path(graphene_core::memo), serialize(serialize_monitor), properties("monitor_properties"), skip_impl, plain)]
|
||||
fn monitor<T: Clone + 'static + Send + Sync>(
|
||||
ctx: impl Ctx + DeriveCtx + ExtractAll,
|
||||
#[allow(clippy::type_complexity)]
|
||||
#[data]
|
||||
io: MonitorValue<T>,
|
||||
content: impl Node<Context<'_>, Output = T>,
|
||||
) -> Result<T, Interrupt> {
|
||||
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<Context<'_>, Output = RecordValue<'e>>) -> GPoll<RecordValue<'e>> {
|
||||
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<T: Clone + 'static + Send + Sync>(io: &MonitorValue<T>) -> Option<Arc<dyn std::any::Any + Send + Sync>> {
|
||||
let io = io.lock().unwrap();
|
||||
io.as_ref().map(|output| output.clone() as Arc<dyn std::any::Any + Send + Sync>)
|
||||
fn serialize_monitor(capture: &MonitorValue) -> Option<Arc<dyn std::any::Any + Send + Sync>> {
|
||||
let capture = capture.lock().unwrap();
|
||||
capture.as_ref().map(|capture| Arc::new(capture.clone()) as Arc<dyn std::any::Any + Send + Sync>)
|
||||
}
|
||||
|
||||
#[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<ErasedNode<u32>>);
|
||||
assert!(handle.serialize().is_none(), "no record before the first eval");
|
||||
let layout = element_layout::<u32>();
|
||||
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();
|
||||
assert_eq!(edge.eval(&ctx), GPoll::Final(11));
|
||||
let edge = handle.duplicate().downcast_record::<u32>().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::<IORecord<CtxSnapshot, u32>>().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::<RecordCapture>().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::<u32>().unwrap(), 11);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user