| pub mod value; |
|
|
| use crate::document::value::TaggedValue; |
| use crate::proto::{ConstructionArgs, ProtoNetwork, ProtoNode, ProtoNodeInput}; |
| use dyn_any::DynAny; |
| use glam::IVec2; |
| use graphene_core::memo::MemoHashGuard; |
| pub use graphene_core::uuid::NodeId; |
| pub use graphene_core::uuid::generate_uuid; |
| use graphene_core::{Cow, MemoHash, ProtoNodeIdentifier, Type}; |
| use log::Metadata; |
| use rustc_hash::FxHashMap; |
| use std::collections::HashMap; |
| use std::collections::hash_map::DefaultHasher; |
| use std::hash::{Hash, Hasher}; |
|
|
| |
| |
| fn merge_ids(a: NodeId, b: NodeId) -> NodeId { |
| let mut hasher = DefaultHasher::new(); |
| a.hash(&mut hasher); |
| b.hash(&mut hasher); |
| NodeId(hasher.finish()) |
| } |
|
|
| |
| #[inline(always)] |
| fn return_true() -> bool { |
| true |
| } |
|
|
| |
| |
| |
| #[derive(Clone, Debug, PartialEq, Hash, DynAny, serde::Serialize, serde::Deserialize)] |
| pub struct DocumentNode { |
| |
| |
| |
| |
| |
| |
| |
| |
| #[cfg_attr(target_arch = "wasm32", serde(alias = "outputs"))] |
| pub inputs: Vec<NodeInput>, |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| pub manual_composition: Option<Type>, |
| |
| pub implementation: DocumentNodeImplementation, |
| |
| #[serde(default = "return_true")] |
| pub visible: bool, |
| |
| |
| |
| #[serde(default)] |
| pub skip_deduplication: bool, |
| |
| #[serde(skip)] |
| pub original_location: OriginalLocation, |
| } |
|
|
| |
| #[derive(Clone, Debug, PartialEq, Eq, Hash, DynAny, serde::Serialize, serde::Deserialize)] |
| pub struct Source { |
| pub node: Vec<NodeId>, |
| pub index: usize, |
| } |
|
|
| |
| #[derive(Clone, Debug, PartialEq, Eq, DynAny, Default, serde::Serialize, serde::Deserialize)] |
| #[non_exhaustive] |
| pub struct OriginalLocation { |
| |
| pub path: Option<Vec<NodeId>>, |
| |
| pub inputs_source: HashMap<Source, usize>, |
| |
| pub dependants: Vec<Vec<NodeId>>, |
| |
| pub inputs_exposed: Vec<bool>, |
| |
| pub skip_inputs: usize, |
| } |
|
|
| impl Default for DocumentNode { |
| fn default() -> Self { |
| Self { |
| inputs: Default::default(), |
| manual_composition: Default::default(), |
| implementation: Default::default(), |
| visible: true, |
| skip_deduplication: Default::default(), |
| original_location: OriginalLocation::default(), |
| } |
| } |
| } |
|
|
| impl Hash for OriginalLocation { |
| fn hash<H: Hasher>(&self, state: &mut H) { |
| self.path.hash(state); |
| self.inputs_source.iter().for_each(|val| val.hash(state)); |
| self.inputs_exposed.hash(state); |
| self.skip_inputs.hash(state); |
| } |
| } |
| impl OriginalLocation { |
| pub fn inputs(&self, index: usize) -> impl Iterator<Item = Source> + '_ { |
| [(index >= self.skip_inputs).then(|| Source { |
| node: self.path.clone().unwrap_or_default(), |
| index: self.inputs_exposed.iter().take(index - self.skip_inputs).filter(|&&exposed| exposed).count(), |
| })] |
| .into_iter() |
| .flatten() |
| .chain(self.inputs_source.iter().filter(move |x| *x.1 == index).map(|(source, _)| source.clone())) |
| } |
| } |
| impl DocumentNode { |
| |
| pub fn populate_first_network_input(&mut self, node_id: NodeId, output_index: usize, offset: usize, lambda: bool, source: impl Iterator<Item = Source>, skip: usize) { |
| let (index, _) = self |
| .inputs |
| .iter() |
| .enumerate() |
| .nth(offset) |
| .unwrap_or_else(|| panic!("no network input found for {self:#?} and offset: {offset}")); |
|
|
| self.inputs[index] = NodeInput::Node { node_id, output_index, lambda }; |
| let input_source = &mut self.original_location.inputs_source; |
| for source in source { |
| input_source.insert(source, (index + self.original_location.skip_inputs).saturating_sub(skip)); |
| } |
| } |
|
|
| 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::ProtoNode(identifier) = 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 { |
| (ProtoNodeInput::ManualComposition(ty), ConstructionArgs::Nodes(vec![])) |
| } else { |
| let first = self.inputs.remove(0); |
| match first { |
| NodeInput::Value { tagged_value, .. } => { |
| assert_eq!(self.inputs.len(), 0, "A value node cannot have any inputs. Current inputs: {:?}", self.inputs); |
| (ProtoNodeInput::ManualComposition(concrete!(graphene_core::Context<'static>)), ConstructionArgs::Value(tagged_value)) |
| } |
| NodeInput::Node { node_id, output_index, lambda } => { |
| assert_eq!(output_index, 0, "Outputs should be flattened before converting to proto node"); |
| let node = if lambda { ProtoNodeInput::NodeLambda(node_id) } else { ProtoNodeInput::Node(node_id) }; |
| (node, ConstructionArgs::Nodes(vec![])) |
| } |
| NodeInput::Network { import_type, .. } => (ProtoNodeInput::ManualComposition(import_type), ConstructionArgs::Nodes(vec![])), |
| NodeInput::Inline(inline) => (ProtoNodeInput::None, ConstructionArgs::Inline(inline)), |
| NodeInput::Scope(_) => unreachable!("Scope input was not resolved"), |
| NodeInput::Reflection(_) => unreachable!("Reflection input was not resolved"), |
| } |
| }; |
| assert!(!self.inputs.iter().any(|input| matches!(input, NodeInput::Network { .. })), "received non-resolved input"); |
| assert!( |
| !self.inputs.iter().any(|input| matches!(input, NodeInput::Value { .. })), |
| "received value as input. inputs: {:#?}, construction_args: {:#?}", |
| self.inputs, |
| args |
| ); |
|
|
| |
| if let &[NodeInput::Inline(ref inline)] = self.inputs.as_slice() { |
| args = ConstructionArgs::Inline(inline.clone()); |
| } |
| if let ConstructionArgs::Nodes(nodes) = &mut args { |
| nodes.extend(self.inputs.iter().map(|input| match input { |
| NodeInput::Node { node_id, lambda, .. } => (*node_id, *lambda), |
| _ => unreachable!(), |
| })); |
| } |
| ProtoNode { |
| identifier, |
| input, |
| construction_args: args, |
| original_location: self.original_location, |
| skip_deduplication: self.skip_deduplication, |
| } |
| } |
| } |
|
|
| |
| #[derive(Debug, Clone, PartialEq, Hash, DynAny, serde::Serialize, serde::Deserialize)] |
| pub enum NodeInput { |
| |
| Node { node_id: NodeId, output_index: usize, lambda: bool }, |
|
|
| |
| Value { tagged_value: MemoHash<TaggedValue>, exposed: bool }, |
|
|
| |
| |
| Network { import_type: Type, import_index: usize }, |
|
|
| |
| Scope(Cow<'static, str>), |
|
|
| |
| Reflection(DocumentNodeMetadata), |
|
|
| |
| |
| Inline(InlineRust), |
| } |
|
|
| #[derive(Debug, Clone, PartialEq, Hash, DynAny, serde::Serialize, serde::Deserialize)] |
| pub struct InlineRust { |
| pub expr: String, |
| pub ty: Type, |
| } |
|
|
| impl InlineRust { |
| pub fn new(expr: String, ty: Type) -> Self { |
| Self { expr, ty } |
| } |
| } |
|
|
| #[derive(Debug, Clone, PartialEq, Hash, DynAny, serde::Serialize, serde::Deserialize)] |
| pub enum DocumentNodeMetadata { |
| DocumentNodePath, |
| } |
|
|
| impl NodeInput { |
| pub const fn node(node_id: NodeId, output_index: usize) -> Self { |
| Self::Node { node_id, output_index, lambda: false } |
| } |
|
|
| pub const fn lambda(node_id: NodeId, output_index: usize) -> Self { |
| Self::Node { node_id, output_index, lambda: true } |
| } |
|
|
| pub fn value(tagged_value: TaggedValue, exposed: bool) -> Self { |
| let tagged_value = tagged_value.into(); |
| Self::Value { tagged_value, exposed } |
| } |
|
|
| pub const fn network(import_type: Type, import_index: usize) -> Self { |
| Self::Network { import_type, import_index } |
| } |
|
|
| pub fn scope(key: impl Into<Cow<'static, str>>) -> Self { |
| Self::Scope(key.into()) |
| } |
|
|
| fn map_ids(&mut self, f: impl Fn(NodeId) -> NodeId) { |
| if let &mut NodeInput::Node { node_id, output_index, lambda } = self { |
| *self = NodeInput::Node { |
| node_id: f(node_id), |
| output_index, |
| lambda, |
| } |
| } |
| } |
|
|
| pub fn is_exposed(&self) -> bool { |
| match self { |
| NodeInput::Node { .. } => true, |
| NodeInput::Value { exposed, .. } => *exposed, |
| NodeInput::Network { .. } => true, |
| NodeInput::Inline(_) => false, |
| NodeInput::Scope(_) => false, |
| NodeInput::Reflection(_) => false, |
| } |
| } |
|
|
| pub fn ty(&self) -> Type { |
| match self { |
| NodeInput::Node { .. } => unreachable!("ty() called on NodeInput::Node"), |
| NodeInput::Value { tagged_value, .. } => tagged_value.ty(), |
| NodeInput::Network { import_type, .. } => import_type.clone(), |
| NodeInput::Inline(_) => panic!("ty() called on NodeInput::Inline"), |
| NodeInput::Scope(_) => unreachable!("ty() called on NodeInput::Scope"), |
| NodeInput::Reflection(_) => concrete!(Metadata), |
| } |
| } |
|
|
| pub fn as_value(&self) -> Option<&TaggedValue> { |
| if let NodeInput::Value { tagged_value, .. } = self { Some(tagged_value) } else { None } |
| } |
| pub fn as_value_mut(&mut self) -> Option<MemoHashGuard<'_, TaggedValue>> { |
| if let NodeInput::Value { tagged_value, .. } = self { Some(tagged_value.inner_mut()) } else { None } |
| } |
| pub fn as_non_exposed_value(&self) -> Option<&TaggedValue> { |
| if let NodeInput::Value { tagged_value, exposed: false } = self { Some(tagged_value) } else { None } |
| } |
|
|
| pub fn as_node(&self) -> Option<NodeId> { |
| if let NodeInput::Node { node_id, .. } = self { Some(*node_id) } else { None } |
| } |
| } |
|
|
| #[derive(Clone, Debug, DynAny, serde::Serialize, serde::Deserialize)] |
| |
| pub enum OldDocumentNodeImplementation { |
| |
| |
| |
| Network(OldNodeNetwork), |
| |
| |
| |
| #[serde(alias = "Unresolved")] |
| ProtoNode(ProtoNodeIdentifier), |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| Extract, |
| } |
|
|
| #[derive(Clone, Debug, PartialEq, Hash, DynAny, serde::Serialize, serde::Deserialize)] |
| |
| pub enum DocumentNodeImplementation { |
| |
| |
| |
| Network(NodeNetwork), |
| |
| |
| |
| #[serde(alias = "Unresolved")] |
| ProtoNode(ProtoNodeIdentifier), |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| Extract, |
| } |
|
|
| impl Default for DocumentNodeImplementation { |
| fn default() -> Self { |
| Self::ProtoNode(ProtoNodeIdentifier::new("graphene_core::ops::IdentityNode")) |
| } |
| } |
|
|
| impl DocumentNodeImplementation { |
| pub fn get_network(&self) -> Option<&NodeNetwork> { |
| match self { |
| DocumentNodeImplementation::Network(n) => Some(n), |
| _ => None, |
| } |
| } |
|
|
| pub fn get_network_mut(&mut self) -> Option<&mut NodeNetwork> { |
| match self { |
| DocumentNodeImplementation::Network(n) => Some(n), |
| _ => None, |
| } |
| } |
|
|
| pub fn get_proto_node(&self) -> Option<&ProtoNodeIdentifier> { |
| match self { |
| DocumentNodeImplementation::ProtoNode(p) => Some(p), |
| _ => None, |
| } |
| } |
|
|
| pub const fn proto(name: &'static str) -> Self { |
| Self::ProtoNode(ProtoNodeIdentifier::new(name)) |
| } |
|
|
| pub fn output_count(&self) -> usize { |
| match self { |
| DocumentNodeImplementation::Network(network) => network.exports.len(), |
| _ => 1, |
| } |
| } |
| } |
|
|
| |
| #[derive(Debug, serde::Deserialize)] |
| #[serde(untagged)] |
| pub enum NodeExportVersions { |
| OldNodeInput(NodeOutput), |
| NodeInput(NodeInput), |
| } |
|
|
| |
| #[derive(Debug, serde::Deserialize)] |
| pub struct NodeOutput { |
| pub node_id: NodeId, |
| pub node_output_index: usize, |
| } |
|
|
| |
| fn deserialize_exports<'de, D>(deserializer: D) -> Result<Vec<NodeInput>, D::Error> |
| where |
| D: serde::Deserializer<'de>, |
| { |
| use serde::Deserialize; |
| let node_input_versions = Vec::<NodeExportVersions>::deserialize(deserializer)?; |
|
|
| |
| let inputs = node_input_versions |
| .into_iter() |
| .map(|node_input_version| { |
| let node_output = match node_input_version { |
| NodeExportVersions::OldNodeInput(node_output) => node_output, |
| NodeExportVersions::NodeInput(node_input) => return node_input, |
| }; |
| NodeInput::node(node_output.node_id, node_output.node_output_index) |
| }) |
| .collect(); |
|
|
| Ok(inputs) |
| } |
|
|
| |
| |
| |
| #[derive(Clone, Debug, DynAny, serde::Serialize, serde::Deserialize)] |
| pub struct OldDocumentNode { |
| |
| |
| #[serde(default)] |
| pub alias: String, |
| |
| |
| #[serde(deserialize_with = "migrate_layer_to_merge")] |
| pub name: String, |
| |
| |
| |
| |
| |
| |
| |
| #[cfg_attr(target_arch = "wasm32", serde(alias = "outputs"))] |
| pub inputs: Vec<NodeInput>, |
| pub manual_composition: Option<Type>, |
| |
| |
| |
| #[serde(default = "return_true")] |
| pub has_primary_output: bool, |
| |
| pub implementation: OldDocumentNodeImplementation, |
| |
| #[serde(default)] |
| pub is_layer: bool, |
| |
| #[serde(default = "return_true")] |
| pub visible: bool, |
| |
| #[serde(default)] |
| pub locked: bool, |
| |
| pub metadata: OldDocumentNodeMetadata, |
| |
| |
| |
| #[serde(default)] |
| pub skip_deduplication: bool, |
| |
| #[serde(skip)] |
| pub original_location: OriginalLocation, |
| } |
|
|
| |
| #[derive(Clone, Debug, PartialEq, Default, specta::Type, Hash, DynAny, serde::Serialize, serde::Deserialize)] |
| |
| pub struct OldDocumentNodeMetadata { |
| pub position: IVec2, |
| } |
|
|
| |
| #[derive(Clone, Copy, Debug, PartialEq, Hash, serde::Serialize, serde::Deserialize)] |
| |
| pub struct OldRootNode { |
| pub id: NodeId, |
| pub output_index: usize, |
| } |
|
|
| |
| #[derive(PartialEq, Debug, Clone, Hash, Default, serde::Serialize, serde::Deserialize)] |
| pub enum OldPreviewing { |
| |
| |
| Yes { root_node_to_restore: Option<OldRootNode> }, |
| #[default] |
| No, |
| } |
|
|
| |
| #[derive(Clone, Debug, DynAny, serde::Serialize, serde::Deserialize)] |
| |
| pub struct OldNodeNetwork { |
| |
| |
| #[serde(alias = "outputs", deserialize_with = "deserialize_exports")] |
| pub exports: Vec<NodeInput>, |
| |
| |
| pub nodes: HashMap<NodeId, OldDocumentNode>, |
| |
| #[serde(default)] |
| pub previewing: OldPreviewing, |
| |
| #[serde(default = "default_import_metadata")] |
| pub imports_metadata: (NodeId, IVec2), |
| #[serde(default = "default_export_metadata")] |
| pub exports_metadata: (NodeId, IVec2), |
|
|
| |
| #[serde(default)] |
| |
| pub scope_injections: HashMap<String, (NodeId, Type)>, |
| } |
|
|
| |
| fn migrate_layer_to_merge<'de, D: serde::Deserializer<'de>>(deserializer: D) -> Result<String, D::Error> { |
| let mut s: String = serde::Deserialize::deserialize(deserializer)?; |
| if s == "Layer" { |
| s = "Merge".to_string(); |
| } |
| Ok(s) |
| } |
| |
| fn default_import_metadata() -> (NodeId, IVec2) { |
| (NodeId::new(), IVec2::new(-25, -4)) |
| } |
| |
| fn default_export_metadata() -> (NodeId, IVec2) { |
| (NodeId::new(), IVec2::new(8, -4)) |
| } |
|
|
| #[derive(Clone, Default, Debug, DynAny, serde::Serialize, serde::Deserialize)] |
| |
| pub struct NodeNetwork { |
| |
| |
| |
| #[cfg_attr(target_arch = "wasm32", serde(alias = "outputs", deserialize_with = "deserialize_exports"))] |
| pub exports: Vec<NodeInput>, |
| |
| |
| |
| #[serde(serialize_with = "graphene_core::vector::serialize_hashmap", deserialize_with = "graphene_core::vector::deserialize_hashmap")] |
| pub nodes: FxHashMap<NodeId, DocumentNode>, |
| |
| #[serde(default)] |
| #[serde(serialize_with = "graphene_core::vector::serialize_hashmap", deserialize_with = "graphene_core::vector::deserialize_hashmap")] |
| pub scope_injections: FxHashMap<String, (NodeId, Type)>, |
| #[serde(skip)] |
| pub generated: bool, |
| } |
|
|
| impl Hash for NodeNetwork { |
| fn hash<H: Hasher>(&self, state: &mut H) { |
| self.exports.hash(state); |
| let mut nodes: Vec<_> = self.nodes.iter().collect(); |
| nodes.sort_by_key(|(id, _)| *id); |
| for (id, node) in nodes { |
| id.hash(state); |
| node.hash(state); |
| } |
| } |
| } |
|
|
| impl PartialEq for NodeNetwork { |
| fn eq(&self, other: &Self) -> bool { |
| self.exports == other.exports |
| } |
| } |
|
|
| |
| impl NodeNetwork { |
| pub fn current_hash(&self) -> u64 { |
| let mut hasher = DefaultHasher::new(); |
| self.hash(&mut hasher); |
| hasher.finish() |
| } |
|
|
| pub fn value_network(node: DocumentNode) -> Self { |
| Self { |
| exports: vec![NodeInput::node(NodeId(0), 0)], |
| nodes: [(NodeId(0), node)].into_iter().collect(), |
| ..Default::default() |
| } |
| } |
|
|
| |
| pub fn nested_network(&self, nested_path: &[NodeId]) -> Option<&Self> { |
| let mut network = Some(self); |
|
|
| for segment in nested_path { |
| network = network.and_then(|network| network.nodes.get(segment)).and_then(|node| node.implementation.get_network()); |
| } |
| network |
| } |
|
|
| |
| pub fn nested_network_mut(&mut self, nested_path: &[NodeId]) -> Option<&mut Self> { |
| let mut network = Some(self); |
|
|
| for segment in nested_path { |
| network = network.and_then(|network| network.nodes.get_mut(segment)).and_then(|node| node.implementation.get_network_mut()); |
| } |
| network |
| } |
|
|
| |
| pub fn outputs_contain(&self, node_id_to_check: NodeId) -> bool { |
| self.exports |
| .iter() |
| .any(|output| if let NodeInput::Node { node_id, .. } = output { *node_id == node_id_to_check } else { false }) |
| } |
|
|
| |
| pub fn is_acyclic(&self) -> bool { |
| let mut dependencies: HashMap<NodeId, Vec<NodeId>> = HashMap::new(); |
| for (node_id, node) in &self.nodes { |
| dependencies.insert( |
| *node_id, |
| node.inputs |
| .iter() |
| .filter_map(|input| if let NodeInput::Node { node_id, .. } = input { Some(*node_id) } else { None }) |
| .collect(), |
| ); |
| } |
| while !dependencies.is_empty() { |
| let Some((&disconnected, _)) = dependencies.iter().find(|(_, l)| l.is_empty()) else { |
| error!("Dependencies {dependencies:?}"); |
| return false; |
| }; |
| dependencies.remove(&disconnected); |
| for connections in dependencies.values_mut() { |
| connections.retain(|&id| id != disconnected); |
| } |
| } |
| true |
| } |
| } |
|
|
| |
| impl NodeNetwork { |
| |
| pub fn map_ids(&mut self, f: impl Fn(NodeId) -> NodeId + Copy) { |
| self.exports.iter_mut().for_each(|output| { |
| if let NodeInput::Node { node_id, .. } = output { |
| *node_id = f(*node_id) |
| } |
| }); |
| self.scope_injections.values_mut().for_each(|(id, _ty)| *id = f(*id)); |
| let nodes = std::mem::take(&mut self.nodes); |
| self.nodes = nodes |
| .into_iter() |
| .map(|(id, mut node)| { |
| node.inputs.iter_mut().for_each(|input| input.map_ids(f)); |
| node.original_location.dependants.iter_mut().for_each(|deps| deps.iter_mut().for_each(|id| *id = f(*id))); |
| (f(id), node) |
| }) |
| .collect(); |
| } |
|
|
| |
| pub fn generate_node_paths(&mut self, prefix: &[NodeId]) { |
| for (node_id, node) in &mut self.nodes { |
| let mut new_path = prefix.to_vec(); |
| if !self.generated { |
| new_path.push(*node_id); |
| } |
| if let DocumentNodeImplementation::Network(network) = &mut node.implementation { |
| network.generate_node_paths(new_path.as_slice()); |
| } |
| if node.original_location.path.is_some() { |
| log::warn!("Attempting to overwrite node path"); |
| } else { |
| node.original_location = OriginalLocation { |
| path: Some(new_path), |
| inputs_exposed: node.inputs.iter().map(|input| input.is_exposed()).collect(), |
| skip_inputs: if node.manual_composition.is_some() { 1 } else { 0 }, |
| dependants: (0..node.implementation.output_count()).map(|_| Vec::new()).collect(), |
| ..Default::default() |
| }; |
| } |
| } |
| } |
|
|
| pub fn populate_dependants(&mut self) { |
| let mut dep_changes = Vec::new(); |
| for (node_id, node) in &mut self.nodes { |
| let len = node.original_location.dependants.len(); |
| node.original_location.dependants.extend(vec![vec![]; (node.implementation.output_count()).max(len) - len]); |
| for input in &node.inputs { |
| if let NodeInput::Node { node_id: dep_id, output_index, .. } = input { |
| dep_changes.push((*dep_id, *output_index, *node_id)); |
| } |
| } |
| } |
| |
| for (dep_id, output_index, node_id) in dep_changes { |
| let node = self.nodes.get_mut(&dep_id).expect("Encountered invalid node id"); |
| let len = node.original_location.dependants.len(); |
| |
| node.original_location.dependants.extend(vec![vec![]; (output_index + 1).max(len) - len]); |
| |
| node.original_location.dependants[output_index].push(node_id); |
| } |
| } |
|
|
| |
| fn replace_node_inputs(&mut self, node_id: NodeId, old_input: (NodeId, usize), new_input: (NodeId, usize)) { |
| let Some(node) = self.nodes.get_mut(&node_id) else { return }; |
| node.inputs.iter_mut().for_each(|input| { |
| if let NodeInput::Node { node_id: input_id, output_index, .. } = input { |
| if (*input_id, *output_index) == old_input { |
| (*input_id, *output_index) = new_input; |
| } |
| } |
| }); |
| } |
|
|
| |
| fn replace_network_outputs(&mut self, old_output: NodeInput, new_output: NodeInput) { |
| for output in self.exports.iter_mut() { |
| if *output == old_output { |
| *output = new_output.clone(); |
| } |
| } |
| } |
|
|
| |
| pub fn remove_dead_nodes(&mut self, number_of_inputs: usize) -> Vec<bool> { |
| |
| let mut old_nodes = std::mem::take(&mut self.nodes); |
|
|
| let mut stack = self |
| .exports |
| .iter() |
| .filter_map(|output| if let NodeInput::Node { node_id, .. } = output { Some(*node_id) } else { None }) |
| .collect::<Vec<_>>(); |
| while let Some(node_id) = stack.pop() { |
| let Some((node_id, mut document_node)) = old_nodes.remove_entry(&node_id) else { |
| continue; |
| }; |
| |
| if let DocumentNodeImplementation::Network(network) = &mut document_node.implementation { |
| |
| let mut retain_inputs = network.remove_dead_nodes(document_node.inputs.len()).into_iter(); |
| document_node.inputs.retain(|_| retain_inputs.next().unwrap_or(true)) |
| } |
| |
| stack.extend( |
| document_node |
| .inputs |
| .iter() |
| .filter_map(|input| if let NodeInput::Node { node_id, .. } = input { Some(node_id) } else { None }), |
| ); |
| |
| self.nodes.insert(node_id, document_node); |
| } |
|
|
| |
| let mut are_inputs_used = vec![false; number_of_inputs]; |
| for node in &self.nodes { |
| for node_input in &node.1.inputs { |
| if let NodeInput::Network { import_index, .. } = node_input { |
| if let Some(is_used) = are_inputs_used.get_mut(*import_index) { |
| *is_used = true; |
| } |
| } |
| } |
| } |
| are_inputs_used |
| } |
|
|
| pub fn resolve_scope_inputs(&mut self) { |
| for node in self.nodes.values_mut() { |
| for input in node.inputs.iter_mut() { |
| if let NodeInput::Scope(key) = input { |
| let (import_id, _ty) = self.scope_injections.get(key.as_ref()).expect("Tried to import a non existent key from scope"); |
| |
| *input = NodeInput::node(*import_id, 0); |
| } |
| } |
| } |
| } |
|
|
| |
| pub fn flatten(&mut self, node_id: NodeId) { |
| self.flatten_with_fns(node_id, merge_ids, NodeId::new) |
| } |
|
|
| |
| pub fn flatten_with_fns(&mut self, node_id: NodeId, map_ids: impl Fn(NodeId, NodeId) -> NodeId + Copy, gen_id: impl Fn() -> NodeId + Copy) { |
| let Some((id, mut node)) = self.nodes.remove_entry(&node_id) else { |
| warn!("The node which was supposed to be flattened does not exist in the network, id {node_id} network {self:#?}"); |
| return; |
| }; |
| |
| let identity_node = DocumentNodeImplementation::ProtoNode("graphene_core::ops::IdentityNode".into()); |
| if !node.visible && node.implementation != identity_node { |
| node.implementation = identity_node; |
|
|
| |
| node.inputs.drain(1..); |
| node.manual_composition = None; |
| self.nodes.insert(id, node); |
| return; |
| } |
|
|
| let path = node.original_location.path.clone().unwrap_or_default(); |
|
|
| |
| if node.implementation != DocumentNodeImplementation::ProtoNode("graphene_core::value::ClonedNode".into()) { |
| Self::replace_value_inputs_with_nodes(&mut node.inputs, &mut self.nodes, &path, gen_id, map_ids, id); |
| } |
|
|
| let DocumentNodeImplementation::Network(mut inner_network) = node.implementation else { |
| |
| assert!(!self.nodes.contains_key(&id), "Trying to insert a node into the network caused an id conflict"); |
|
|
| self.nodes.insert(id, node); |
| return; |
| }; |
|
|
| |
| Self::replace_value_inputs_with_nodes( |
| &mut inner_network.exports, |
| &mut inner_network.nodes, |
| node.original_location.path.as_ref().unwrap_or(&vec![]), |
| gen_id, |
| map_ids, |
| id, |
| ); |
|
|
| |
| inner_network.map_ids(|inner_id| map_ids(id, inner_id)); |
| inner_network.populate_dependants(); |
| let new_nodes = inner_network.nodes.keys().cloned().collect::<Vec<_>>(); |
|
|
| for (key, value) in inner_network.scope_injections.into_iter() { |
| match self.scope_injections.entry(key) { |
| std::collections::hash_map::Entry::Occupied(o) => { |
| log::warn!("Found duplicate scope injection for key {}, ignoring", o.key()); |
| } |
| std::collections::hash_map::Entry::Vacant(v) => { |
| v.insert(value); |
| } |
| } |
| } |
|
|
| |
| for (nested_node_id, mut nested_node) in inner_network.nodes.into_iter() { |
| for (nested_input_index, nested_input) in nested_node.clone().inputs.iter().enumerate() { |
| if let NodeInput::Network { import_index, .. } = nested_input { |
| let parent_input = node.inputs.get(*import_index).unwrap_or_else(|| panic!("Import index {} should always exist", import_index)); |
| match *parent_input { |
| |
| NodeInput::Node { node_id, output_index, lambda } => { |
| let skip = node.original_location.skip_inputs; |
| nested_node.populate_first_network_input(node_id, output_index, nested_input_index, lambda, node.original_location.inputs(*import_index), skip); |
| let input_node = self.nodes.get_mut(&node_id).unwrap_or_else(|| panic!("unable find input node {node_id:?}")); |
| input_node.original_location.dependants[output_index].push(nested_node_id); |
| } |
| NodeInput::Network { import_index, .. } => { |
| let parent_input_index = import_index; |
| let Some(NodeInput::Network { import_index, .. }) = nested_node.inputs.get_mut(nested_input_index) else { |
| log::error!("Nested node should have a network input"); |
| continue; |
| }; |
| *import_index = parent_input_index; |
| } |
| NodeInput::Value { .. } => unreachable!("Value inputs should have been replaced with value nodes"), |
| NodeInput::Inline(_) => (), |
| NodeInput::Scope(ref key) => { |
| let (import_id, _ty) = self.scope_injections.get(key.as_ref()).expect("Tried to import a non existent key from scope"); |
| |
| nested_node.inputs[nested_input_index] = NodeInput::node(*import_id, 0); |
| } |
| NodeInput::Reflection(_) => unreachable!("Reflection inputs should have been replaced with value nodes"), |
| } |
| } |
| } |
| self.nodes.insert(nested_node_id, nested_node); |
| } |
| |
|
|
| |
| for (i, export) in inner_network.exports.into_iter().enumerate() { |
| if let NodeInput::Node { node_id, output_index, .. } = &export { |
| for deps in &node.original_location.dependants { |
| for dep in deps { |
| self.replace_node_inputs(*dep, (id, i), (*node_id, *output_index)); |
| } |
| } |
|
|
| if let Some(new_output_node) = self.nodes.get_mut(node_id) { |
| for dep in &node.original_location.dependants[i] { |
| new_output_node.original_location.dependants[*output_index].push(*dep); |
| } |
| } |
| } |
|
|
| self.replace_network_outputs(NodeInput::node(id, i), export); |
| } |
|
|
| for node_id in new_nodes { |
| self.flatten_with_fns(node_id, map_ids, gen_id); |
| } |
| } |
|
|
| #[inline(never)] |
| fn replace_value_inputs_with_nodes( |
| inputs: &mut [NodeInput], |
| collection: &mut FxHashMap<NodeId, DocumentNode>, |
| path: &[NodeId], |
| gen_id: impl Fn() -> NodeId + Copy, |
| map_ids: impl Fn(NodeId, NodeId) -> NodeId + Copy, |
| id: NodeId, |
| ) { |
| |
| for export in inputs { |
| let export: &mut NodeInput = export; |
| let previous_export = std::mem::replace(export, NodeInput::network(concrete!(()), 0)); |
|
|
| let (tagged_value, exposed) = match previous_export { |
| NodeInput::Value { tagged_value, exposed } => (tagged_value, exposed), |
| NodeInput::Reflection(reflect) => match reflect { |
| DocumentNodeMetadata::DocumentNodePath => (TaggedValue::NodePath(path.to_vec()).into(), false), |
| }, |
| previous_export => { |
| *export = previous_export; |
| continue; |
| } |
| }; |
| let value_node_id = gen_id(); |
| let merged_node_id = map_ids(id, value_node_id); |
| let mut original_location = OriginalLocation { |
| path: Some(path.to_vec()), |
| dependants: vec![vec![id]], |
| ..Default::default() |
| }; |
|
|
| if let Some(path) = &mut original_location.path { |
| path.push(value_node_id); |
| } |
| collection.insert( |
| merged_node_id, |
| DocumentNode { |
| inputs: vec![NodeInput::Value { tagged_value, exposed }], |
| implementation: DocumentNodeImplementation::ProtoNode("graphene_core::value::ClonedNode".into()), |
| original_location, |
| ..Default::default() |
| }, |
| ); |
| *export = NodeInput::Node { |
| node_id: merged_node_id, |
| output_index: 0, |
| lambda: false, |
| }; |
| } |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
|
|
| 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::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] { |
| let node_input_output_index = output_index; |
| |
| if let Some(input_node) = self.nodes.get_mut(&node_id) { |
| for &dep in &node.original_location.dependants[0] { |
| input_node.original_location.dependants[output_index].push(dep); |
| } |
| } |
|
|
| let input_node_id = node_id; |
| for output in self.nodes.values_mut() { |
| for (index, input) in output.inputs.iter_mut().enumerate() { |
| if let NodeInput::Node { |
| node_id: output_node_id, |
| output_index: output_output_index, |
| .. |
| } = input |
| { |
| if *output_node_id == id { |
| *output_node_id = input_node_id; |
| *output_output_index = node_input_output_index; |
|
|
| let input_source = &mut output.original_location.inputs_source; |
| for source in node.original_location.inputs(index) { |
| input_source.insert(source, index + output.original_location.skip_inputs - node.original_location.skip_inputs); |
| } |
| } |
| } |
| } |
| for node_input in self.exports.iter_mut() { |
| if let NodeInput::Node { node_id, output_index, .. } = node_input { |
| if *node_id == id { |
| *node_id = input_node_id; |
| *output_index = node_input_output_index; |
| } |
| } |
| } |
| } |
| } |
| self.nodes.remove(&id); |
| } |
| } |
| Ok(()) |
| } |
|
|
| |
| pub fn remove_redundant_id_nodes(&mut self) { |
| let id_nodes = self |
| .nodes |
| .iter() |
| .filter(|(_, node)| { |
| matches!(&node.implementation, DocumentNodeImplementation::ProtoNode(ident) if ident == &ProtoNodeIdentifier::new("graphene_core::ops::IdentityNode")) |
| && node.inputs.len() == 1 |
| && matches!(node.inputs[0], NodeInput::Node { .. }) |
| }) |
| .map(|(id, _)| *id) |
| .collect::<Vec<_>>(); |
| for id in id_nodes { |
| if let Err(e) = self.remove_id_node(id) { |
| log::warn!("{e}") |
| } |
| } |
| } |
|
|
| |
| |
| |
| pub fn resolve_extract_nodes(&mut self) { |
| let mut extraction_nodes = self |
| .nodes |
| .iter() |
| .filter(|(_, node)| matches!(node.implementation, DocumentNodeImplementation::Extract)) |
| .map(|(id, node)| (*id, node.clone())) |
| .collect::<Vec<_>>(); |
| self.nodes.retain(|_, node| !matches!(node.implementation, DocumentNodeImplementation::Extract)); |
|
|
| for (_, node) in &mut extraction_nodes { |
| assert_eq!(node.inputs.len(), 1); |
| let NodeInput::Node { node_id, output_index, .. } = node.inputs.pop().unwrap() else { |
| panic!("Extract node has no input, inputs: {:?}", node.inputs); |
| }; |
| assert_eq!(output_index, 0); |
| |
| let mut input_node = self.nodes.remove(&node_id).unwrap(); |
| 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), 0), |
| ni => NodeInput::network(ni.ty(), 0), |
| }; |
| } |
|
|
| for input in input_node.inputs.iter_mut() { |
| if let NodeInput::Node { .. } = input { |
| *input = NodeInput::network(generic!(T), 0) |
| } |
| } |
| node.inputs = vec![NodeInput::value(TaggedValue::DocumentNode(input_node), false)]; |
| } |
| self.nodes.extend(extraction_nodes); |
| } |
|
|
| |
| pub fn into_proto_networks(self) -> impl Iterator<Item = ProtoNetwork> { |
| let nodes: Vec<_> = self.nodes.into_iter().map(|(id, node)| (id, node.resolve_proto_node())).collect(); |
|
|
| |
| if self.exports.len() == 1 { |
| if let NodeInput::Node { node_id, .. } = self.exports[0] { |
| return vec![ProtoNetwork { |
| inputs: Vec::new(), |
| output: node_id, |
| nodes, |
| }] |
| .into_iter(); |
| } |
| } |
|
|
| |
| let networks: Vec<_> = self |
| .exports |
| .into_iter() |
| .filter_map(move |output| { |
| if let NodeInput::Node { node_id, .. } = output { |
| Some(ProtoNetwork { |
| inputs: Vec::new(), |
| |
| |
| output: node_id, |
| nodes: nodes.clone(), |
| }) |
| } else { |
| None |
| } |
| }) |
| .collect(); |
| networks.into_iter() |
| } |
|
|
| |
| pub fn recursive_nodes(&self) -> RecursiveNodeIter<'_> { |
| let nodes = self.nodes.iter().map(|(id, node)| (id, node, Vec::new())).collect(); |
| RecursiveNodeIter { nodes } |
| } |
| } |
|
|
| |
| pub struct RecursiveNodeIter<'a> { |
| nodes: Vec<(&'a NodeId, &'a DocumentNode, Vec<NodeId>)>, |
| } |
|
|
| impl<'a> Iterator for RecursiveNodeIter<'a> { |
| type Item = (&'a NodeId, &'a DocumentNode, Vec<NodeId>); |
| fn next(&mut self) -> Option<Self::Item> { |
| let (current_id, node, path) = self.nodes.pop()?; |
| if let DocumentNodeImplementation::Network(network) = &node.implementation { |
| self.nodes.extend(network.nodes.iter().map(|(id, node)| { |
| let mut nested_path = path.clone(); |
| nested_path.push(*current_id); |
| (id, node, nested_path) |
| })); |
| } |
| Some((current_id, node, path)) |
| } |
| } |
|
|
| #[cfg(test)] |
| mod test { |
| use super::*; |
| use crate::proto::{ConstructionArgs, ProtoNetwork, ProtoNode, ProtoNodeInput}; |
| use graphene_core::ProtoNodeIdentifier; |
| use std::sync::atomic::AtomicU64; |
|
|
| fn gen_node_id() -> NodeId { |
| static NODE_ID: AtomicU64 = AtomicU64::new(4); |
| NodeId(NODE_ID.fetch_add(1, std::sync::atomic::Ordering::SeqCst)) |
| } |
|
|
| fn add_network() -> NodeNetwork { |
| NodeNetwork { |
| exports: vec![NodeInput::node(NodeId(1), 0)], |
| nodes: [ |
| ( |
| NodeId(0), |
| DocumentNode { |
| inputs: vec![NodeInput::network(concrete!(u32), 0), NodeInput::network(concrete!(u32), 1)], |
| implementation: DocumentNodeImplementation::ProtoNode("graphene_core::structural::ConsNode".into()), |
| ..Default::default() |
| }, |
| ), |
| ( |
| NodeId(1), |
| DocumentNode { |
| inputs: vec![NodeInput::node(NodeId(0), 0)], |
| implementation: DocumentNodeImplementation::ProtoNode("graphene_core::ops::AddPairNode".into()), |
| ..Default::default() |
| }, |
| ), |
| ] |
| .into_iter() |
| .collect(), |
| ..Default::default() |
| } |
| } |
|
|
| #[test] |
| fn map_ids() { |
| let mut network = add_network(); |
| network.map_ids(|id| NodeId(id.0 + 1)); |
| let mapped_add = NodeNetwork { |
| exports: vec![NodeInput::node(NodeId(2), 0)], |
| nodes: [ |
| ( |
| NodeId(1), |
| DocumentNode { |
| inputs: vec![NodeInput::network(concrete!(u32), 0), NodeInput::network(concrete!(u32), 1)], |
| implementation: DocumentNodeImplementation::ProtoNode("graphene_core::structural::ConsNode".into()), |
| ..Default::default() |
| }, |
| ), |
| ( |
| NodeId(2), |
| DocumentNode { |
| inputs: vec![NodeInput::node(NodeId(1), 0)], |
| implementation: DocumentNodeImplementation::ProtoNode("graphene_core::ops::AddPairNode".into()), |
| ..Default::default() |
| }, |
| ), |
| ] |
| .into_iter() |
| .collect(), |
| ..Default::default() |
| }; |
| assert_eq!(network, mapped_add); |
| } |
|
|
| #[test] |
| fn extract_node() { |
| let id_node = DocumentNode { |
| inputs: vec![], |
| implementation: DocumentNodeImplementation::ProtoNode("graphene_core::ops::IdentityNode".into()), |
| ..Default::default() |
| }; |
| |
| let mut extraction_network = NodeNetwork { |
| exports: vec![NodeInput::node(NodeId(1), 0)], |
| nodes: [ |
| id_node.clone(), |
| DocumentNode { |
| inputs: vec![NodeInput::lambda(NodeId(0), 0)], |
| implementation: DocumentNodeImplementation::Extract, |
| ..Default::default() |
| }, |
| ] |
| .into_iter() |
| .enumerate() |
| .map(|(id, node)| (NodeId(id as u64), node)) |
| .collect(), |
| ..Default::default() |
| }; |
| extraction_network.resolve_extract_nodes(); |
| assert_eq!(extraction_network.nodes.len(), 1); |
| let inputs = extraction_network.nodes.get(&NodeId(1)).unwrap().inputs.clone(); |
| assert_eq!(inputs.len(), 1); |
| assert!(matches!(&inputs[0].as_value(), &Some(TaggedValue::DocumentNode(network), ..) if network == &id_node)); |
| } |
|
|
| #[test] |
| fn flatten_add() { |
| let mut network = NodeNetwork { |
| exports: vec![NodeInput::node(NodeId(1), 0)], |
| nodes: [( |
| NodeId(1), |
| DocumentNode { |
| inputs: vec![NodeInput::network(concrete!(u32), 0), NodeInput::value(TaggedValue::U32(2), false)], |
| implementation: DocumentNodeImplementation::Network(add_network()), |
| ..Default::default() |
| }, |
| )] |
| .into_iter() |
| .collect(), |
| ..Default::default() |
| }; |
| network.populate_dependants(); |
| network.flatten_with_fns(NodeId(1), |self_id, inner_id| NodeId(self_id.0 * 10 + inner_id.0), gen_node_id); |
| let flat_network = flat_network(); |
| println!("{flat_network:#?}"); |
| println!("{network:#?}"); |
|
|
| assert_eq!(flat_network, network); |
| } |
|
|
| #[test] |
| fn resolve_proto_node_add() { |
| let document_node = DocumentNode { |
| inputs: vec![NodeInput::network(concrete!(u32), 0), NodeInput::node(NodeId(0), 0)], |
| implementation: DocumentNodeImplementation::ProtoNode("graphene_core::structural::ConsNode".into()), |
| ..Default::default() |
| }; |
|
|
| let proto_node = document_node.resolve_proto_node(); |
| let reference = ProtoNode { |
| identifier: "graphene_core::structural::ConsNode".into(), |
| input: ProtoNodeInput::ManualComposition(concrete!(u32)), |
| construction_args: ConstructionArgs::Nodes(vec![(NodeId(0), false)]), |
| ..Default::default() |
| }; |
| assert_eq!(proto_node, reference); |
| } |
|
|
| #[test] |
| fn resolve_flatten_add_as_proto_network() { |
| let construction_network = ProtoNetwork { |
| inputs: Vec::new(), |
| output: NodeId(11), |
| nodes: [ |
| ( |
| NodeId(10), |
| ProtoNode { |
| identifier: "graphene_core::structural::ConsNode".into(), |
| input: ProtoNodeInput::ManualComposition(concrete!(u32)), |
| construction_args: ConstructionArgs::Nodes(vec![(NodeId(14), false)]), |
| original_location: OriginalLocation { |
| path: Some(vec![NodeId(1), NodeId(0)]), |
| inputs_source: [(Source { node: vec![NodeId(1)], index: 1 }, 1)].into(), |
| inputs_exposed: vec![true, true], |
| skip_inputs: 0, |
| ..Default::default() |
| }, |
|
|
| ..Default::default() |
| }, |
| ), |
| ( |
| NodeId(11), |
| ProtoNode { |
| identifier: "graphene_core::ops::AddPairNode".into(), |
| input: ProtoNodeInput::Node(NodeId(10)), |
| construction_args: ConstructionArgs::Nodes(vec![]), |
| original_location: OriginalLocation { |
| path: Some(vec![NodeId(1), NodeId(1)]), |
| inputs_source: HashMap::new(), |
| inputs_exposed: vec![true], |
| skip_inputs: 0, |
| ..Default::default() |
| }, |
| ..Default::default() |
| }, |
| ), |
| ( |
| NodeId(14), |
| ProtoNode { |
| identifier: "graphene_core::value::ClonedNode".into(), |
| input: ProtoNodeInput::ManualComposition(concrete!(graphene_core::Context)), |
| construction_args: ConstructionArgs::Value(TaggedValue::U32(2).into()), |
| original_location: OriginalLocation { |
| path: Some(vec![NodeId(1), NodeId(4)]), |
| inputs_source: HashMap::new(), |
| inputs_exposed: vec![true, false], |
| skip_inputs: 0, |
| ..Default::default() |
| }, |
| ..Default::default() |
| }, |
| ), |
| ] |
| .into_iter() |
| .collect(), |
| }; |
| let network = flat_network(); |
| let mut resolved_network = network.into_proto_networks().collect::<Vec<_>>(); |
| resolved_network[0].nodes.sort_unstable_by_key(|(id, _)| *id); |
|
|
| println!("{:#?}", resolved_network[0]); |
| println!("{construction_network:#?}"); |
| pretty_assertions::assert_eq!(resolved_network[0], construction_network); |
| } |
|
|
| fn flat_network() -> NodeNetwork { |
| NodeNetwork { |
| exports: vec![NodeInput::node(NodeId(11), 0)], |
| nodes: [ |
| ( |
| NodeId(10), |
| DocumentNode { |
| inputs: vec![NodeInput::network(concrete!(u32), 0), NodeInput::node(NodeId(14), 0)], |
| 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: 1 }, 1)].into(), |
| inputs_exposed: vec![true, true], |
| skip_inputs: 0, |
| ..Default::default() |
| }, |
| ..Default::default() |
| }, |
| ), |
| ( |
| NodeId(14), |
| DocumentNode { |
| inputs: vec![NodeInput::value(TaggedValue::U32(2), false)], |
| implementation: DocumentNodeImplementation::ProtoNode("graphene_core::value::ClonedNode".into()), |
| original_location: OriginalLocation { |
| path: Some(vec![NodeId(1), NodeId(4)]), |
| inputs_source: HashMap::new(), |
| inputs_exposed: vec![true, false], |
| skip_inputs: 0, |
| ..Default::default() |
| }, |
| ..Default::default() |
| }, |
| ), |
| ( |
| NodeId(11), |
| DocumentNode { |
| inputs: vec![NodeInput::node(NodeId(10), 0)], |
| implementation: DocumentNodeImplementation::ProtoNode("graphene_core::ops::AddPairNode".into()), |
| original_location: OriginalLocation { |
| path: Some(vec![NodeId(1), NodeId(1)]), |
| inputs_source: HashMap::new(), |
| inputs_exposed: vec![true], |
| skip_inputs: 0, |
| ..Default::default() |
| }, |
| ..Default::default() |
| }, |
| ), |
| ] |
| .into_iter() |
| .collect(), |
| ..Default::default() |
| } |
| } |
|
|
| fn two_node_identity() -> NodeNetwork { |
| NodeNetwork { |
| exports: vec![NodeInput::node(NodeId(1), 0), NodeInput::node(NodeId(2), 0)], |
| nodes: [ |
| ( |
| NodeId(1), |
| DocumentNode { |
| inputs: vec![NodeInput::network(concrete!(u32), 0)], |
| implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::ops::IdentityNode")), |
| ..Default::default() |
| }, |
| ), |
| ( |
| NodeId(2), |
| DocumentNode { |
| inputs: vec![NodeInput::network(concrete!(u32), 1)], |
| implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::ops::IdentityNode")), |
| ..Default::default() |
| }, |
| ), |
| ] |
| .into_iter() |
| .collect(), |
| ..Default::default() |
| } |
| } |
|
|
| fn output_duplicate(network_outputs: Vec<NodeInput>, result_node_input: NodeInput) -> NodeNetwork { |
| let mut network = NodeNetwork { |
| exports: network_outputs, |
| nodes: [ |
| ( |
| NodeId(1), |
| DocumentNode { |
| inputs: vec![NodeInput::value(TaggedValue::F64(1.), false), NodeInput::value(TaggedValue::F64(2.), false)], |
| implementation: DocumentNodeImplementation::Network(two_node_identity()), |
| ..Default::default() |
| }, |
| ), |
| ( |
| NodeId(2), |
| DocumentNode { |
| inputs: vec![result_node_input], |
| implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::ops::IdentityNode")), |
| ..Default::default() |
| }, |
| ), |
| ] |
| .into_iter() |
| .collect(), |
| ..Default::default() |
| }; |
| let _new_ids = 101..; |
| network.populate_dependants(); |
| network.flatten_with_fns(NodeId(1), |self_id, inner_id| NodeId(self_id.0 * 10 + inner_id.0), || NodeId(10000)); |
| network.flatten_with_fns(NodeId(2), |self_id, inner_id| NodeId(self_id.0 * 10 + inner_id.0), || NodeId(10001)); |
| network.remove_dead_nodes(0); |
| network |
| } |
|
|
| #[test] |
| fn simple_duplicate() { |
| let result = output_duplicate(vec![NodeInput::node(NodeId(1), 0)], NodeInput::node(NodeId(1), 0)); |
| println!("{result:#?}"); |
| assert_eq!(result.exports.len(), 1, "The number of outputs should remain as 1"); |
| assert_eq!(result.exports[0], NodeInput::node(NodeId(11), 0), "The outer network output should be from a duplicated inner network"); |
| let mut ids = result.nodes.keys().copied().collect::<Vec<_>>(); |
| ids.sort(); |
| assert_eq!(ids, vec![NodeId(11), NodeId(10010)], "Should only contain identity and values"); |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| } |
|
|