Add monitor node for graph introspection

Adds a `serialize` function on Node which can be implemented by nodes to
allow introspecting their content. This pr also adds a Monitor Node that
always caches the last value it was evaluated with.

Test Plan: -

Reviewers: 0HyperCube

Reviewed By: 0HyperCube

Pull Request: https://github.com/GraphiteEditor/Graphite/pull/1165
This commit is contained in:
Dennis Kobert 2023-04-26 23:21:50 +02:00 committed by Keavon Chambers
parent 9cf93a9156
commit 271f9d5158
6 changed files with 41 additions and 8 deletions

View file

@ -3,8 +3,6 @@ name: Continuous Integration
on:
push:
pull_request:
env:
CARGO_TERM_COLOR: always

1
Cargo.lock generated
View file

@ -1671,6 +1671,7 @@ dependencies = [
"proc-macro2",
"quote",
"serde",
"serde_json",
"syn 1.0.109",
"tempfile",
"vulkan-executor",

View file

@ -37,6 +37,10 @@ pub trait Node<'i, Input: 'i>: 'i {
type Output: 'i;
fn eval(&'i self, input: Input) -> Self::Output;
fn reset(self: Pin<&mut Self>) {}
#[cfg(feature = "alloc")]
fn serialize(&self) -> Option<String> {
None
}
}
#[cfg(feature = "alloc")]

View file

@ -11,12 +11,7 @@ license = "MIT OR Apache-2.0"
[features]
memoization = ["once_cell"]
default = ["memoization"]
gpu = [
"graphene-core/gpu",
"gpu-compiler-bin-wrapper",
"compilation-client",
"gpu-executor",
]
gpu = ["graphene-core/gpu", "gpu-compiler-bin-wrapper", "compilation-client", "gpu-executor"]
vulkan = ["gpu", "vulkan-executor"]
wgpu = ["gpu", "wgpu-executor"]
quantization = ["autoquant"]
@ -63,6 +58,7 @@ glam = { version = "0.22", features = ["serde"] }
node-macro = { path = "../node-macro" }
boxcar = "0.1.0"
xxhash-rust = { workspace = true }
serde_json = "1.0.96"
[dependencies.serde]
version = "1.0"

View file

@ -1,9 +1,11 @@
use graphene_core::Node;
use serde::Serialize;
use std::hash::{Hash, Hasher};
use std::marker::PhantomData;
use std::pin::Pin;
use std::sync::atomic::AtomicBool;
use std::sync::Mutex;
use xxhash_rust::xxh3::Xxh3;
/// Caches the output of a given Node and acts as a proxy
@ -49,6 +51,37 @@ impl<T, CachedNode> CacheNode<T, CachedNode> {
}
}
/// Caches the output of the last graph evaluation for introspection
#[derive(Default)]
pub struct MonitorNode<T, CachedNode> {
output: Mutex<Option<T>>,
node: CachedNode,
}
impl<'i, T: 'i + Serialize + Clone, I: 'i + Hash, CachedNode: 'i> Node<'i, I> for MonitorNode<T, CachedNode>
where
CachedNode: for<'any_input> Node<'any_input, I, Output = T>,
{
type Output = T;
fn eval(&'i self, input: I) -> Self::Output {
let output = self.node.eval(input);
*self.output.lock().unwrap() = Some(output.clone());
output
}
fn serialize(&self) -> Option<String> {
let output = self.output.lock().unwrap();
(&*output).as_ref().map(|output| serde_json::to_string(output).ok()).flatten()
}
}
impl<T, CachedNode> std::marker::Unpin for MonitorNode<T, CachedNode> {}
impl<T, CachedNode> MonitorNode<T, CachedNode> {
pub const fn new(node: CachedNode) -> MonitorNode<T, CachedNode> {
MonitorNode { output: Mutex::new(None), node }
}
}
/// Caches the output of a given Node and acts as a proxy
/// It provides two modes of operation, it can either be set
/// when calling the node with a `Some<T>` variant or the last

View file

@ -153,6 +153,7 @@ fn node_registry() -> HashMap<NodeIdentifier, HashMap<NodeIOTypes, NodeConstruct
register_node!(graphene_std::raster::MaskImageNode<_, _, _>, input: ImageFrame<Color>, params: [ImageFrame<Color>]),
register_node!(graphene_std::raster::MaskImageNode<_, _, _>, input: ImageFrame<Color>, params: [ImageFrame<Luma>]),
register_node!(graphene_std::raster::EmptyImageNode<_, _>, input: DAffine2, params: [Color]),
register_node!(graphene_std::memo::MonitorNode<_, _>, input: (), params: [ImageFrame<Color>]),
#[cfg(feature = "gpu")]
register_node!(graphene_std::executor::MapGpuSingleImageNode<_>, input: Image<Color>, params: [String]),
vec![(