|
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"); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
} |
|
|