Consolidate and rename Graphene data types, and add many comments (#1620)

* Document ProtoNodeInput

* More comments

* Comment improvements

* Comment and split ProtoNodeInput::NodeLambda from ProtoNodeInput::Node

* Combine NodeImplementation into DocumentNodeImplementation

---------

Co-authored-by: Dennis Kobert <dennis@kobert.dev>
This commit is contained in:
Keavon Chambers 2024-02-20 17:53:04 -08:00 committed by GitHub
parent dc7de4d973
commit a02b162e30
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
20 changed files with 468 additions and 467 deletions

View file

@ -259,7 +259,7 @@ impl DocumentNode {
fn resolve_proto_node(mut self) -> ProtoNode {
assert!(!self.inputs.is_empty() || self.manual_composition.is_some(), "Resolving document node {self:#?} with no inputs");
let DocumentNodeImplementation::Unresolved(fqn) = self.implementation else {
let DocumentNodeImplementation::ProtoNode(fqn) = self.implementation else {
unreachable!("tried to resolve not flattened node on resolved node {self:?}");
};
let (input, mut args) = if let Some(ty) = self.manual_composition {
@ -273,7 +273,8 @@ impl DocumentNode {
}
NodeInput::Node { node_id, output_index, lambda } => {
assert_eq!(output_index, 0, "Outputs should be flattened before converting to protonode. {:#?}", self.name);
(ProtoNodeInput::Node(node_id, lambda), ConstructionArgs::Nodes(vec![]))
let node = if lambda { ProtoNodeInput::NodeLambda(node_id) } else { ProtoNodeInput::Node(node_id) };
(node, ConstructionArgs::Nodes(vec![]))
}
NodeInput::Network(ty) => (ProtoNodeInput::ManualComposition(ty), ConstructionArgs::Nodes(vec![])),
NodeInput::Inline(inline) => (ProtoNodeInput::None, ConstructionArgs::Inline(inline)),
@ -438,20 +439,41 @@ impl NodeInput {
#[derive(Clone, Debug, PartialEq, Hash, DynAny)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
/// Represents the implementation of a node, which can be a nested [`NodeNetwork`], a proto [`ProtoNodeIdentifier`], or extract.
/// Represents the implementation of a node, which can be a nested [`NodeNetwork`], a proto [`ProtoNodeIdentifier`], or `Extract`.
pub enum DocumentNodeImplementation {
/// This describes a (document) node built out of a subgraph of other (document) nodes.
///
/// A nested [`NodeNetwork`] that is flattened by the [`NodeNetwork::flatten`] function.
Network(NodeNetwork),
/// This describes a (document) node implemented as a protonode.
///
/// A protonode identifier which can be found in `node_registry.rs`.
Unresolved(ProtoNodeIdentifier),
/// `DocumentNode`s with a `DocumentNodeImplementation::Extract` are converted into a `ClonedNode` that returns the `DocumentNode` specified by the single `NodeInput::Node`.
/// The referenced node (specified by the single `NodeInput::Node`) is removed from the network, and any `NodeInput::Node`s used by the referenced node are replaced with a generically typed network input.
ProtoNode(ProtoNodeIdentifier),
/// The Extract variant is a tag which tells the compilation process to do something special. It invokes language-level functionality built for use by the ExtractNode to enable metaprogramming.
/// When the ExtractNode is compiled, it gets replaced by a value node containing a representation of the source code for the function/lambda of the document node that's fed into the ExtractNode
/// (but only that one document node, not upstream nodes).
///
/// This is explained in more detail here: <https://www.youtube.com/watch?v=72KJa3jQClo>
///
/// Currently we use it for GPU execution, where a node has to get "extracted" to its source code representation and stored as a value that can be given to the GpuCompiler node at runtime
/// (to become a compute shader). Future use could involve the addition of an InjectNode to convert the source code form back into an executable node, enabling metaprogramming in the node graph.
/// We would use an assortment of nodes that operate on Graphene source code (just data, no different from any other data flowing through the graph) to make graph transformations.
///
/// We use this for dealing with macros in a syntactic way of modifying the node graph from within the graph itself. Just like we often deal with lambdas to represent a whole group of
/// operations/code/logic, this allows us to basically deal with a lambda at a meta/source-code level, because we need to pass the GPU SPIR-V compiler the source code for a lambda,
/// not the executable logic of a lambda.
///
/// This is analogous to how Rust macros operate at the level of source code, not executable code. When we speak of source code, that represents Graphene's source code in the form of a
/// DocumentNode network, not the text form of Rust's source code. (Analogous to the token stream/AST of a Rust macro.)
///
/// `DocumentNode`s with a `DocumentNodeImplementation::Extract` are converted into a `ClonedNode` that returns the `DocumentNode` specified by the single `NodeInput::Node`. The referenced node
/// (specified by the single `NodeInput::Node`) is removed from the network, and any `NodeInput::Node`s used by the referenced node are replaced with a generically typed network input.
Extract,
}
impl Default for DocumentNodeImplementation {
fn default() -> Self {
Self::Unresolved(ProtoNodeIdentifier::new("graphene_core::ops::IdentityNode"))
Self::ProtoNode(ProtoNodeIdentifier::new("graphene_core::ops::IdentityNode"))
}
}
@ -471,7 +493,7 @@ impl DocumentNodeImplementation {
}
pub const fn proto(name: &'static str) -> Self {
Self::Unresolved(ProtoNodeIdentifier::new(name))
Self::ProtoNode(ProtoNodeIdentifier::new(name))
}
}
@ -558,7 +580,7 @@ impl NodeNetwork {
DocumentNode {
name: "Input Frame".into(),
manual_composition: Some(concrete!(u32)),
implementation: DocumentNodeImplementation::Unresolved("graphene_core::ops::IdentityNode".into()),
implementation: DocumentNodeImplementation::ProtoNode("graphene_core::ops::IdentityNode".into()),
metadata: DocumentNodeMetadata { position: (8, 4).into() },
..Default::default()
},
@ -596,7 +618,7 @@ impl NodeNetwork {
let node = DocumentNode {
name: "Output".into(),
inputs: vec![],
implementation: DocumentNodeImplementation::Unresolved("graphene_core::ops::IdentityNode".into()),
implementation: DocumentNodeImplementation::ProtoNode("graphene_core::ops::IdentityNode".into()),
..Default::default()
};
self.push_node(node)
@ -617,7 +639,7 @@ impl NodeNetwork {
name: "MemoNode".to_string(),
manual_composition: Some(concrete!(())),
inputs: vec![NodeInput::Network(ty)],
implementation: DocumentNodeImplementation::Unresolved(ProtoNodeIdentifier::new("graphene_core::memo::MemoNode")),
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::memo::MemoNode")),
..Default::default()
},
),
@ -626,7 +648,7 @@ impl NodeNetwork {
DocumentNode {
name: "CloneNode".to_string(),
inputs: vec![NodeInput::node(NodeId(0), 0)],
implementation: DocumentNodeImplementation::Unresolved(ProtoNodeIdentifier::new("graphene_core::ops::CloneNode<_>")),
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::ops::CloneNode<_>")),
..Default::default()
},
),
@ -906,8 +928,8 @@ impl NodeNetwork {
return;
};
if node.implementation != DocumentNodeImplementation::Unresolved("graphene_core::ops::IdentityNode".into()) && self.disabled.contains(&id) {
node.implementation = DocumentNodeImplementation::Unresolved("graphene_core::ops::IdentityNode".into());
if node.implementation != DocumentNodeImplementation::ProtoNode("graphene_core::ops::IdentityNode".into()) && self.disabled.contains(&id) {
node.implementation = DocumentNodeImplementation::ProtoNode("graphene_core::ops::IdentityNode".into());
if node.is_layer() {
// Connect layer node to the graphic group below
node.inputs.drain(..1);
@ -921,7 +943,7 @@ impl NodeNetwork {
// replace value inputs with value nodes
for input in node.inputs.iter_mut() {
// Skip inputs that are already value nodes
if node.implementation == DocumentNodeImplementation::Unresolved("graphene_core::value::ClonedNode".into()) {
if node.implementation == DocumentNodeImplementation::ProtoNode("graphene_core::value::ClonedNode".into()) {
break;
}
@ -938,7 +960,7 @@ impl NodeNetwork {
DocumentNode {
name: "Value".into(),
inputs: vec![NodeInput::Value { tagged_value, exposed }],
implementation: DocumentNodeImplementation::Unresolved("graphene_core::value::ClonedNode".into()),
implementation: DocumentNodeImplementation::ProtoNode("graphene_core::value::ClonedNode".into()),
original_location,
..Default::default()
},
@ -1027,7 +1049,7 @@ impl NodeNetwork {
fn remove_id_node(&mut self, id: NodeId) -> Result<(), String> {
let node = self.nodes.get(&id).ok_or_else(|| format!("Node with id {id} does not exist"))?.clone();
if let DocumentNodeImplementation::Unresolved(ident) = &node.implementation {
if let DocumentNodeImplementation::ProtoNode(ident) = &node.implementation {
if ident.name == "graphene_core::ops::IdentityNode" {
assert_eq!(node.inputs.len(), 1, "Id node has more than one input");
if let NodeInput::Node { node_id, output_index, .. } = node.inputs[0] {
@ -1081,7 +1103,7 @@ impl NodeNetwork {
.nodes
.iter()
.filter(|(_, node)| {
matches!(&node.implementation, DocumentNodeImplementation::Unresolved(ident) if ident == &ProtoNodeIdentifier::new("graphene_core::ops::IdentityNode"))
matches!(&node.implementation, DocumentNodeImplementation::ProtoNode(ident) if ident == &ProtoNodeIdentifier::new("graphene_core::ops::IdentityNode"))
&& node.inputs.len() == 1
&& matches!(node.inputs[0], NodeInput::Node { .. })
})
@ -1114,7 +1136,7 @@ impl NodeNetwork {
assert_eq!(output_index, 0);
// TODO: check if we can read lambda checking?
let mut input_node = self.nodes.remove(&node_id).unwrap();
node.implementation = DocumentNodeImplementation::Unresolved("graphene_core::value::ClonedNode".into());
node.implementation = DocumentNodeImplementation::ProtoNode("graphene_core::value::ClonedNode".into());
if let Some(input) = input_node.inputs.get_mut(0) {
*input = match &input {
NodeInput::Node { .. } => NodeInput::Network(generic!(T)),
@ -1230,7 +1252,7 @@ mod test {
DocumentNode {
name: "Cons".into(),
inputs: vec![NodeInput::Network(concrete!(u32)), NodeInput::Network(concrete!(u32))],
implementation: DocumentNodeImplementation::Unresolved("graphene_core::structural::ConsNode".into()),
implementation: DocumentNodeImplementation::ProtoNode("graphene_core::structural::ConsNode".into()),
..Default::default()
},
),
@ -1239,7 +1261,7 @@ mod test {
DocumentNode {
name: "Add".into(),
inputs: vec![NodeInput::node(NodeId(0), 0)],
implementation: DocumentNodeImplementation::Unresolved("graphene_core::ops::AddPairNode".into()),
implementation: DocumentNodeImplementation::ProtoNode("graphene_core::ops::AddPairNode".into()),
..Default::default()
},
),
@ -1263,7 +1285,7 @@ mod test {
DocumentNode {
name: "Cons".into(),
inputs: vec![NodeInput::Network(concrete!(u32)), NodeInput::Network(concrete!(u32))],
implementation: DocumentNodeImplementation::Unresolved("graphene_core::structural::ConsNode".into()),
implementation: DocumentNodeImplementation::ProtoNode("graphene_core::structural::ConsNode".into()),
..Default::default()
},
),
@ -1272,7 +1294,7 @@ mod test {
DocumentNode {
name: "Add".into(),
inputs: vec![NodeInput::node(NodeId(1), 0)],
implementation: DocumentNodeImplementation::Unresolved("graphene_core::ops::AddPairNode".into()),
implementation: DocumentNodeImplementation::ProtoNode("graphene_core::ops::AddPairNode".into()),
..Default::default()
},
),
@ -1289,7 +1311,7 @@ mod test {
let id_node = DocumentNode {
name: "Id".into(),
inputs: vec![],
implementation: DocumentNodeImplementation::Unresolved("graphene_core::ops::IdentityNode".into()),
implementation: DocumentNodeImplementation::ProtoNode("graphene_core::ops::IdentityNode".into()),
..Default::default()
};
// TODO: Extend test cases to test nested network
@ -1356,7 +1378,7 @@ mod test {
let document_node = DocumentNode {
name: "Cons".into(),
inputs: vec![NodeInput::Network(concrete!(u32)), NodeInput::node(NodeId(0), 0)],
implementation: DocumentNodeImplementation::Unresolved("graphene_core::structural::ConsNode".into()),
implementation: DocumentNodeImplementation::ProtoNode("graphene_core::structural::ConsNode".into()),
..Default::default()
};
@ -1397,7 +1419,7 @@ mod test {
NodeId(11),
ProtoNode {
identifier: "graphene_core::ops::AddPairNode".into(),
input: ProtoNodeInput::Node(NodeId(10), false),
input: ProtoNodeInput::Node(NodeId(10)),
construction_args: ConstructionArgs::Nodes(vec![]),
original_location: OriginalLocation {
path: Some(vec![NodeId(1), NodeId(1)]),
@ -1432,7 +1454,7 @@ mod test {
DocumentNode {
name: "Cons".into(),
inputs: vec![NodeInput::Network(concrete!(u32)), NodeInput::node(NodeId(14), 0)],
implementation: DocumentNodeImplementation::Unresolved("graphene_core::structural::ConsNode".into()),
implementation: DocumentNodeImplementation::ProtoNode("graphene_core::structural::ConsNode".into()),
original_location: OriginalLocation {
path: Some(vec![NodeId(1), NodeId(0)]),
inputs_source: [(Source { node: vec![NodeId(1)], index: 0 }, 1)].into(),
@ -1451,7 +1473,7 @@ mod test {
tagged_value: TaggedValue::U32(2),
exposed: false,
}],
implementation: DocumentNodeImplementation::Unresolved("graphene_core::value::ClonedNode".into()),
implementation: DocumentNodeImplementation::ProtoNode("graphene_core::value::ClonedNode".into()),
original_location: OriginalLocation {
path: Some(vec![NodeId(1), NodeId(4)]),
inputs_source: HashMap::new(),
@ -1467,7 +1489,7 @@ mod test {
DocumentNode {
name: "Add".into(),
inputs: vec![NodeInput::node(NodeId(10), 0)],
implementation: DocumentNodeImplementation::Unresolved("graphene_core::ops::AddPairNode".into()),
implementation: DocumentNodeImplementation::ProtoNode("graphene_core::ops::AddPairNode".into()),
original_location: OriginalLocation {
path: Some(vec![NodeId(1), NodeId(1)]),
inputs_source: HashMap::new(),
@ -1495,7 +1517,7 @@ mod test {
DocumentNode {
name: "Identity 1".into(),
inputs: vec![NodeInput::Network(concrete!(u32))],
implementation: DocumentNodeImplementation::Unresolved(ProtoNodeIdentifier::new("graphene_core::ops::IdentityNode")),
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::ops::IdentityNode")),
..Default::default()
},
),
@ -1504,7 +1526,7 @@ mod test {
DocumentNode {
name: "Identity 2".into(),
inputs: vec![NodeInput::Network(concrete!(u32))],
implementation: DocumentNodeImplementation::Unresolved(ProtoNodeIdentifier::new("graphene_core::ops::IdentityNode")),
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::ops::IdentityNode")),
..Default::default()
},
),
@ -1534,7 +1556,7 @@ mod test {
DocumentNode {
name: "Result".into(),
inputs: vec![result_node_input],
implementation: DocumentNodeImplementation::Unresolved(ProtoNodeIdentifier::new("graphene_core::ops::IdentityNode")),
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::ops::IdentityNode")),
..Default::default()
},
),

View file

@ -26,7 +26,7 @@ pub type TypeErasedPinned<'n> = Pin<Box<TypeErasedNode<'n>>>;
pub type SharedNodeContainer = std::rc::Rc<NodeContainer>;
pub type NodeConstructor = for<'a> fn(Vec<SharedNodeContainer>) -> DynFuture<'static, TypeErasedBox<'static>>;
pub type NodeConstructor = fn(Vec<SharedNodeContainer>) -> DynFuture<'static, TypeErasedBox<'static>>;
#[derive(Clone)]
pub struct NodeContainer {
@ -109,7 +109,8 @@ impl core::fmt::Display for ProtoNetwork {
match &node.input {
ProtoNodeInput::None => f.write_str("None")?,
ProtoNodeInput::ManualComposition(ty) => f.write_fmt(format_args!("Manual Composition (type = {ty:?})"))?,
ProtoNodeInput::Node(_, _) => f.write_str("Node")?,
ProtoNodeInput::Node(_) => f.write_str("Node")?,
ProtoNodeInput::NodeLambda(_) => f.write_str("Lambda Node")?,
}
f.write_str("\n")?;
@ -232,28 +233,31 @@ impl Default for ProtoNode {
pub enum ProtoNodeInput {
/// [`ProtoNode`]s do not require any input, e.g. the value node just takes in [`ConstructionArgs`].
None,
/// A ManualComposition input represents an input that opts out of being resolved through the default `ComposeNode`, which first runs the previous (upstream) node, then passes that evaluated result to this node
/// Instead, ManualComposition lets this node actually consume the provided input instead of passing it to its predecessor.
/// A ManualComposition input represents an input that opts out of being resolved through the default `ComposeNode`, which first runs the previous (upstream) node, then passes that evaluated
/// result to this node. Instead, ManualComposition lets this node actually consume the provided input instead of passing it to its predecessor.
///
/// Say we have the network `a -> b -> c` where `c` is the output node and `a` is the input node.
/// We would expect `a` to get input from the network, `b` to get input from `a`, and `c` to get input from `b`.
/// This could be represented as `f(x) = c(b(a(x)))`. `a` is run with input `x` from the network. `b` is run with input from `a`. `c` is run with input from `b`.
///
/// However if `b`'s input is using manual composition, this means it would instead be `f(x) = c(b(x))`. This means that `b` actually gets input from the network, and `a` is not automatically executed as it would be using the default ComposeNode flow.
/// However if `b`'s input is using manual composition, this means it would instead be `f(x) = c(b(x))`. This means that `b` actually gets input from the network, and `a` is not automatically
/// executed as it would be using the default ComposeNode flow. Now `b` can use its own logic to decide when or if it wants to run `a` and how to use its output. For example, the CacheNode can
/// look up `x` in its cache and return the result, or otherwise call `a`, cache the result, and return it.
ManualComposition(Type),
/// the bool indicates whether to treat the node as lambda node.
/// When treating it as a lambda, only the node that is connected itself is fed as input.
/// Otherwise, the the entire network of which the node is the output is fed as input.
Node(NodeId, bool),
}
impl ProtoNodeInput {
pub fn unwrap_node(self) -> NodeId {
match self {
ProtoNodeInput::Node(id, _) => id,
_ => panic!("tried to unwrap id from non node input \n node: {self:#?}"),
}
}
/// The previous node where automatic (not manual) composition occurs when compiled. The entire network, of which the node is the output, is fed as input.
///
/// Grayscale example:
///
/// We're interested in receiving an input of the desaturated image data which has been fed through a grayscale filter.
/// (If we were interested in the grayscale filter itself, we would use the `NodeLambda` variant.)
Node(NodeId),
/// Unlike the `Node` variant, with `NodeLambda` we treat the connected node singularly as a lambda node while ignoring all nodes which feed into it from upstream.
///
/// Grayscale example:
///
/// We're interested in receiving an input of a particular image filter, such as a grayscale filter in the form of a grayscale node lambda.
/// (If we were interested in some image data that had been fed through a grayscale filter, we would use the `Node` variant.)
NodeLambda(NodeId),
}
impl ProtoNode {
@ -275,7 +279,8 @@ impl ProtoNode {
ProtoNodeInput::ManualComposition(ref ty) => {
ty.hash(&mut hasher);
}
ProtoNodeInput::Node(id, lambda) => (id, lambda).hash(&mut hasher),
ProtoNodeInput::Node(id) => (id, false).hash(&mut hasher),
ProtoNodeInput::NodeLambda(id) => (id, true).hash(&mut hasher),
};
Some(NodeId(hasher.finish()))
}
@ -303,11 +308,16 @@ impl ProtoNode {
/// Converts all references to other node IDs into new IDs by running the specified function on them.
/// This can be used when changing the IDs of the nodes, for example in the case of generating stable IDs.
pub fn map_ids(&mut self, f: impl Fn(NodeId) -> NodeId, skip_lambdas: bool) {
if let ProtoNodeInput::Node(id, lambda) = self.input {
if !(skip_lambdas && lambda) {
self.input = ProtoNodeInput::Node(f(id), lambda)
match self.input {
ProtoNodeInput::Node(id) => self.input = ProtoNodeInput::Node(f(id)),
ProtoNodeInput::NodeLambda(id) => {
if !skip_lambdas {
self.input = ProtoNodeInput::NodeLambda(f(id))
}
}
_ => (),
}
if let ConstructionArgs::Nodes(ids) = &mut self.construction_args {
ids.iter_mut().filter(|(_, lambda)| !(skip_lambdas && *lambda)).for_each(|(id, _)| *id = f(*id));
}
@ -333,10 +343,14 @@ impl ProtoNetwork {
pub fn collect_outwards_edges(&self) -> HashMap<NodeId, Vec<NodeId>> {
let mut edges: HashMap<NodeId, Vec<NodeId>> = HashMap::new();
for (id, node) in &self.nodes {
if let ProtoNodeInput::Node(ref_id, _) = &node.input {
self.check_ref(ref_id, id);
edges.entry(*ref_id).or_default().push(*id)
match &node.input {
ProtoNodeInput::Node(ref_id) | ProtoNodeInput::NodeLambda(ref_id) => {
self.check_ref(ref_id, id);
edges.entry(*ref_id).or_default().push(*id)
}
_ => (),
}
if let ConstructionArgs::Nodes(ref_nodes) = &node.construction_args {
for (ref_id, _) in ref_nodes {
self.check_ref(ref_id, id);
@ -366,10 +380,14 @@ impl ProtoNetwork {
pub fn collect_inwards_edges(&self) -> HashMap<NodeId, Vec<NodeId>> {
let mut edges: HashMap<NodeId, Vec<NodeId>> = HashMap::new();
for (id, node) in &self.nodes {
if let ProtoNodeInput::Node(ref_id, _) = &node.input {
self.check_ref(ref_id, id);
edges.entry(*id).or_default().push(*ref_id)
match &node.input {
ProtoNodeInput::Node(ref_id) | ProtoNodeInput::NodeLambda(ref_id) => {
self.check_ref(ref_id, id);
edges.entry(*id).or_default().push(*ref_id)
}
_ => (),
}
if let ConstructionArgs::Nodes(ref_nodes) = &node.construction_args {
for (ref_id, _) in ref_nodes {
self.check_ref(ref_id, id);
@ -396,7 +414,7 @@ impl ProtoNetwork {
let (_, node) = &mut self.nodes[node_id.0 as usize];
if let ProtoNodeInput::Node(input_node_id, false) = node.input {
if let ProtoNodeInput::Node(input_node_id) = node.input {
// Create a new node that composes the current node and its input node
let compose_node_id = NodeId(self.nodes.len() as u64);
@ -701,11 +719,11 @@ impl TypingContext {
ConstructionArgs::Inline(ref inline) => vec![inline.ty.clone()],
};
// Get the node input type from the proto node declaration
// Get the node input type from the protonode declaration
let input = match node.input {
ProtoNodeInput::None => concrete!(()),
ProtoNodeInput::ManualComposition(ref ty) => ty.clone(),
ProtoNodeInput::Node(id, _) => {
ProtoNodeInput::Node(id) | ProtoNodeInput::NodeLambda(id) => {
let input = self.inferred.get(&id).ok_or_else(|| vec![GraphError::new(node, GraphErrorType::InputNodeNotFound(id))])?;
input.output.clone()
}
@ -919,7 +937,7 @@ mod test {
NodeId(7),
ProtoNode {
identifier: "id".into(),
input: ProtoNodeInput::Node(NodeId(11), false),
input: ProtoNodeInput::Node(NodeId(11)),
construction_args: ConstructionArgs::Nodes(vec![]),
..Default::default()
},
@ -928,7 +946,7 @@ mod test {
NodeId(1),
ProtoNode {
identifier: "id".into(),
input: ProtoNodeInput::Node(NodeId(11), false),
input: ProtoNodeInput::Node(NodeId(11)),
construction_args: ConstructionArgs::Nodes(vec![]),
..Default::default()
},
@ -946,7 +964,7 @@ mod test {
NodeId(11),
ProtoNode {
identifier: "add".into(),
input: ProtoNodeInput::Node(NodeId(10), false),
input: ProtoNodeInput::Node(NodeId(10)),
construction_args: ConstructionArgs::Nodes(vec![]),
..Default::default()
},
@ -975,7 +993,7 @@ mod test {
NodeId(1),
ProtoNode {
identifier: "id".into(),
input: ProtoNodeInput::Node(NodeId(2), false),
input: ProtoNodeInput::Node(NodeId(2)),
construction_args: ConstructionArgs::Nodes(vec![]),
..Default::default()
},
@ -984,7 +1002,7 @@ mod test {
NodeId(2),
ProtoNode {
identifier: "id".into(),
input: ProtoNodeInput::Node(NodeId(1), false),
input: ProtoNodeInput::Node(NodeId(1)),
construction_args: ConstructionArgs::Nodes(vec![]),
..Default::default()
},