Introduce the NetworkView read cursor with typed errors and convert the network queries onto it (#4376)

* Introduce a NetworkView cursor with typed errors and convert the primitive queries to it

* Convert the composite name, selection, and eligibility queries onto NetworkView
This commit is contained in:
Keavon Chambers
2026-07-25 19:15:15 -07:00
committed by GitHub
parent 7af4fe4bad
commit 85d6eda68e
4 changed files with 523 additions and 383 deletions
@@ -13,8 +13,10 @@ mod structure;
mod types; mod types;
#[cfg(test)] #[cfg(test)]
mod validation; mod validation;
mod view;
pub use types::*; pub use types::*;
pub use view::{NetworkError, NetworkView};
use super::document_metadata::{DocumentMetadata, LayerNodeIdentifier, NodeRelations}; use super::document_metadata::{DocumentMetadata, LayerNodeIdentifier, NodeRelations};
use super::misc::PTZ; use super::misc::PTZ;
@@ -110,15 +110,7 @@ impl NodeNetworkInterface {
} }
pub fn is_eligible_to_be_layer(&self, node_id: &NodeId, network_path: &[NodeId]) -> bool { pub fn is_eligible_to_be_layer(&self, node_id: &NodeId, network_path: &[NodeId]) -> bool {
let Some(node) = self.document_node(node_id, network_path) else { self.query(network_path, "is_eligible_to_be_layer", |view| view.is_eligible_to_be_layer(node_id)).unwrap_or_default()
log::error!("Could not get node {node_id} in is_eligible_to_be_layer");
return false;
};
let input_count = node.inputs.iter().take(2).filter(|input| input.is_exposed()).count();
let parameters_hidden = node.inputs.iter().skip(2).all(|input| !input.is_exposed());
let output_count = self.number_of_outputs(node_id, network_path);
!self.hidden_primary_output(node_id, network_path) && output_count == 1 && (input_count <= 2) && parameters_hidden
} }
pub fn node_graph_ptz(&self, network_path: &[NodeId]) -> Option<&PTZ> { pub fn node_graph_ptz(&self, network_path: &[NodeId]) -> Option<&PTZ> {
@@ -1,5 +1,18 @@
use super::*; use super::*;
impl NodeNetworkInterface {
/// Runs a query against a resolved [`NetworkView`], logging any error at this message-boundary wrapper and mapping it to None.
pub(crate) fn query<'a, 'p, T>(&'a self, network_path: &'p [NodeId], caller: &str, query: impl FnOnce(NetworkView<'a, 'p>) -> Result<T, NetworkError>) -> Option<T> {
match self.view(network_path).and_then(query) {
Ok(value) => Some(value),
Err(error) => {
log::error!("{error} in {caller}");
None
}
}
}
}
// Public immutable getters for the network interface // Public immutable getters for the network interface
impl NodeNetworkInterface { impl NodeNetworkInterface {
/// Gets the network of the root document /// Gets the network of the root document
@@ -25,21 +38,11 @@ impl NodeNetworkInterface {
/// Get the specified document node in the nested network based on node_id and network_path /// Get the specified document node in the nested network based on node_id and network_path
pub fn document_node(&self, node_id: &NodeId, network_path: &[NodeId]) -> Option<&DocumentNode> { pub fn document_node(&self, node_id: &NodeId, network_path: &[NodeId]) -> Option<&DocumentNode> {
let network = self.nested_network(network_path)?; self.query(network_path, "document_node", |view| view.node(node_id))
let Some(node_metadata) = network.nodes.get(node_id) else {
log::error!("Could not get document node with id {node_id} in network {network_path:?}");
return None;
};
Some(node_metadata)
} }
pub fn node_metadata(&self, node_id: &NodeId, network_path: &[NodeId]) -> Option<&DocumentNodeMetadata> { pub fn node_metadata(&self, node_id: &NodeId, network_path: &[NodeId]) -> Option<&DocumentNodeMetadata> {
let network_metadata = self.network_metadata(network_path)?; self.query(network_path, "node_metadata", |view| view.node_metadata(node_id))
let Some(node_metadata) = network_metadata.persistent_metadata.node_metadata.get(node_id) else {
log::error!("Could not get node metadata for node {node_id} in network {network_path:?}");
return None;
};
Some(node_metadata)
} }
pub fn document_network_metadata(&self) -> &NodeNetworkMetadata { pub fn document_network_metadata(&self) -> &NodeNetworkMetadata {
@@ -69,57 +72,34 @@ impl NodeNetworkInterface {
/// Get the selected nodes for the network at the network_path /// Get the selected nodes for the network at the network_path
pub fn selected_nodes_in_nested_network(&self, network_path: &[NodeId]) -> Option<SelectedNodes> { pub fn selected_nodes_in_nested_network(&self, network_path: &[NodeId]) -> Option<SelectedNodes> {
let Some(network_metadata) = self.network_metadata(network_path) else { self.query(network_path, "selected_nodes_in_nested_network", |view| Ok(view.selected_nodes()))
log::error!("Could not get nested network_metadata in selected_nodes"); }
return None;
};
Some( /// Runs an encapsulating-node query, staying silent for the document network which has no encapsulating node.
network_metadata fn query_encapsulating<'a, 'p, T>(&'a self, network_path: &'p [NodeId], caller: &str, query: impl FnOnce(NetworkView<'a, 'p>) -> Result<T, NetworkError>) -> Option<T> {
.persistent_metadata match self.view(network_path).and_then(query) {
.selection_undo_history Ok(value) => Some(value),
.back() Err(NetworkError::NoEncapsulatingNode) => None,
.cloned() Err(error) => {
.unwrap_or_default() log::error!("{error} in {caller}");
.filtered_selected_nodes(|node_id| network_metadata.persistent_metadata.node_metadata.contains_key(node_id)), None
) }
}
} }
/// Get the network which the encapsulating node of the currently viewed network is part of. Will always be None in the document network. /// Get the network which the encapsulating node of the currently viewed network is part of. Will always be None in the document network.
pub fn encapsulating_network_metadata(&self, network_path: &[NodeId]) -> Option<&NodeNetworkMetadata> { pub fn encapsulating_network_metadata(&self, network_path: &[NodeId]) -> Option<&NodeNetworkMetadata> {
let mut encapsulating_path = network_path.to_vec(); self.query_encapsulating(network_path, "encapsulating_network_metadata", |view| view.encapsulating().map(|parent| parent.network_metadata()))
encapsulating_path.pop()?;
let Some(parent_metadata) = self.network_metadata(&encapsulating_path) else {
log::error!("Could not get parent network in encapsulating_node_metadata");
return None;
};
Some(parent_metadata)
} }
/// Get the node which encapsulates the currently viewed network. Will always be None in the document network. /// Get the node which encapsulates the currently viewed network. Will always be None in the document network.
pub fn encapsulating_node(&self, network_path: &[NodeId]) -> Option<&DocumentNode> { pub fn encapsulating_node(&self, network_path: &[NodeId]) -> Option<&DocumentNode> {
let mut encapsulating_path = network_path.to_vec(); self.query_encapsulating(network_path, "encapsulating_node", |view| view.encapsulating_node())
let encapsulating_node_id = encapsulating_path.pop()?;
let Some(encapsulating_node) = self.document_node(&encapsulating_node_id, &encapsulating_path) else {
log::error!("Could not get encapsulating node in encapsulating_node");
return None;
};
Some(encapsulating_node)
} }
/// Get the node metadata for the node which encapsulates the currently viewed network. Will always be None in the document network. /// Get the node metadata for the node which encapsulates the currently viewed network. Will always be None in the document network.
pub fn encapsulating_node_metadata(&self, network_path: &[NodeId]) -> Option<&DocumentNodeMetadata> { pub fn encapsulating_node_metadata(&self, network_path: &[NodeId]) -> Option<&DocumentNodeMetadata> {
let mut encapsulating_path = network_path.to_vec(); self.query_encapsulating(network_path, "encapsulating_node_metadata", |view| view.encapsulating_node_metadata())
let encapsulating_node_id = encapsulating_path.pop()?;
let Some(parent_metadata) = self.network_metadata(&encapsulating_path) else {
log::error!("Could not get parent network in encapsulating_node_metadata");
return None;
};
let Some(encapsulating_node_metadata) = parent_metadata.persistent_metadata.node_metadata.get(&encapsulating_node_id) else {
log::error!("Could not get encapsulating node metadata in encapsulating_node_metadata");
return None;
};
Some(encapsulating_node_metadata)
} }
/// Returns the first downstream layer(inclusive) from a node. If the node is a layer, it will return itself. /// Returns the first downstream layer(inclusive) from a node. If the node is a layer, it will return itself.
@@ -150,128 +130,39 @@ impl NodeNetworkInterface {
} }
pub fn chain_width(&self, node_id: &NodeId, network_path: &[NodeId]) -> u32 { pub fn chain_width(&self, node_id: &NodeId, network_path: &[NodeId]) -> u32 {
if self.number_of_displayed_inputs(node_id, network_path) > 1 { self.query(network_path, "chain_width", |view| Ok(view.chain_width(node_id))).unwrap_or_default()
let mut last_chain_node_distance = 0_u32;
// Iterate upstream from the layer, and get the number of nodes distance to the last node with Position::Chain
for (index, node_id) in self
.upstream_flow_back_from_nodes(vec![*node_id], network_path, FlowType::HorizontalPrimaryOutputFlow)
.skip(1)
.enumerate()
.collect::<Vec<_>>()
{
// Check if the node is positioned as a chain
if self.is_chain(&node_id, network_path) {
last_chain_node_distance = (index as u32) + 1;
} else {
return last_chain_node_distance * NODE_CHAIN_WIDTH as u32 + 1;
}
}
last_chain_node_distance * NODE_CHAIN_WIDTH as u32 + 1
} else {
// Layer with no inputs has no chain
0
}
} }
/// Check if the specified node id is connected to the output /// Check if the specified node id is connected to the output
pub fn connected_to_output(&self, target_node_id: &NodeId, network_path: &[NodeId]) -> bool { pub fn connected_to_output(&self, target_node_id: &NodeId, network_path: &[NodeId]) -> bool {
let Some(network) = self.nested_network(network_path) else { self.query(network_path, "connected_to_output", |view| Ok(view.connected_to_output(target_node_id))).unwrap_or_default()
log::error!("Could not get network in connected_to_output");
return false;
};
// If the node is the output then return true
if network
.exports
.iter()
.any(|export| if let NodeInput::Node { node_id, .. } = export { node_id == target_node_id } else { false })
{
return true;
}
// Get the outputs
let mut stack = network
.exports
.iter()
.filter_map(|output| if let NodeInput::Node { node_id, .. } = output { network.nodes.get(node_id) } else { None })
.collect::<Vec<_>>();
let mut already_visited = HashSet::new();
already_visited.extend(
network
.exports
.iter()
.filter_map(|output| if let NodeInput::Node { node_id, .. } = output { Some(node_id) } else { None }),
);
while let Some(node) = stack.pop() {
for input in &node.inputs {
if let &NodeInput::Node { node_id: ref_id, .. } = input {
// Skip if already viewed
if already_visited.contains(&ref_id) {
continue;
}
// If the target node is used as input then return true
if ref_id == *target_node_id {
return true;
}
// Add the referenced node to the stack
let Some(ref_node) = network.nodes.get(&ref_id) else {
continue;
};
already_visited.insert(ref_id);
stack.push(ref_node);
}
}
}
false
} }
pub fn number_of_imports(&self, network_path: &[NodeId]) -> usize { pub fn number_of_imports(&self, network_path: &[NodeId]) -> usize {
// TODO: Use network.import_types.len() self.view(network_path).map(|view| view.number_of_imports()).unwrap_or_default()
if let Some(encapsulating_node) = self.encapsulating_node(network_path) {
encapsulating_node.inputs.len()
} else {
0
}
} }
pub fn number_of_exports(&self, network_path: &[NodeId]) -> usize { pub fn number_of_exports(&self, network_path: &[NodeId]) -> usize {
if let Some(network) = self.nested_network(network_path) { network.exports.len() } else { 0 } self.view(network_path).map(|view| view.number_of_exports()).unwrap_or_default()
} }
pub(crate) fn number_of_displayed_inputs(&self, node_id: &NodeId, network_path: &[NodeId]) -> usize { pub(crate) fn number_of_displayed_inputs(&self, node_id: &NodeId, network_path: &[NodeId]) -> usize {
let Some(node) = self.document_node(node_id, network_path) else { self.query(network_path, "number_of_displayed_inputs", |view| view.number_of_displayed_inputs(node_id))
log::error!("Could not get node {node_id} in number_of_displayed_inputs"); .unwrap_or_default()
return 0;
};
node.inputs.iter().filter(|input| input.is_exposed()).count()
} }
pub fn number_of_inputs(&self, node_id: &NodeId, network_path: &[NodeId]) -> usize { pub fn number_of_inputs(&self, node_id: &NodeId, network_path: &[NodeId]) -> usize {
let Some(node) = self.document_node(node_id, network_path) else { self.query(network_path, "number_of_inputs", |view| view.number_of_inputs(node_id)).unwrap_or_default()
log::error!("Could not get node {node_id} in number_of_inputs");
return 0;
};
node.inputs.len()
} }
/// Whether the node has an exposed input at index 0 to accept the horizontal flow from upstream. /// Whether the node has an exposed input at index 0 to accept the horizontal flow from upstream.
/// A node without one (e.g. a generator) can only be the most-upstream node in a chain. /// A node without one (e.g. a generator) can only be the most-upstream node in a chain.
pub fn has_primary_input(&self, node_id: &NodeId, network_path: &[NodeId]) -> bool { pub fn has_primary_input(&self, node_id: &NodeId, network_path: &[NodeId]) -> bool {
self.input_from_connector(&InputConnector::node(*node_id, 0), network_path).is_some_and(|input| input.is_exposed()) self.view(network_path).and_then(|view| view.has_primary_input(node_id)).unwrap_or_default()
} }
pub fn number_of_outputs(&self, node_id: &NodeId, network_path: &[NodeId]) -> usize { pub fn number_of_outputs(&self, node_id: &NodeId, network_path: &[NodeId]) -> usize {
let Some(implementation) = self.implementation(node_id, network_path) else { self.query(network_path, "number_of_outputs", |view| view.number_of_outputs(node_id)).unwrap_or_default()
log::error!("Could not get node {node_id} in number_of_outputs");
return 0;
};
match &implementation {
DocumentNodeImplementation::ProtoNode(_) => 1,
DocumentNodeImplementation::Network(nested_network) => nested_network.exports.len(),
DocumentNodeImplementation::Extract => 1,
}
} }
/// Creates a copy for each node by disconnecting nodes which are not connected to other copied nodes. /// Creates a copy for each node by disconnecting nodes which are not connected to other copied nodes.
@@ -350,19 +241,7 @@ impl NodeNetworkInterface {
/// Create a node template from an existing node. /// Create a node template from an existing node.
pub fn create_node_template(&self, node_id: &NodeId, network_path: &[NodeId]) -> Option<NodeTemplate> { pub fn create_node_template(&self, node_id: &NodeId, network_path: &[NodeId]) -> Option<NodeTemplate> {
let Some(node) = self.document_node(node_id, network_path) else { self.query(network_path, "create_node_template", |view| view.create_node_template(node_id))
log::error!("Could not get node {node_id} in create_node_template");
return None;
};
let Some(node_metadata) = self.node_metadata(node_id, network_path).cloned() else {
log::error!("Could not get node_metadata in create_node_template");
return None;
};
Some(NodeTemplate {
persistent_node_metadata: node_metadata.persistent_metadata,
document_node: node.clone(),
})
} }
/// Converts all node id inputs to a new id based on a HashMap. /// Converts all node id inputs to a new id based on a HashMap.
@@ -388,19 +267,14 @@ impl NodeNetworkInterface {
} }
pub fn input_from_connector(&self, input_connector: &InputConnector, network_path: &[NodeId]) -> Option<&NodeInput> { pub fn input_from_connector(&self, input_connector: &InputConnector, network_path: &[NodeId]) -> Option<&NodeInput> {
let Some(network) = self.nested_network(network_path) else { match self.view(network_path).and_then(|view| view.input(input_connector)) {
log::error!("Could not get network in input_from_connector"); Ok(input) => Some(input),
return None; // An out-of-range input index is an expected lookup miss, not an error worth logging
}; Err(NetworkError::InputNotFound { .. }) => None,
match input_connector { Err(error) => {
InputConnector::Node { node_id, input_index } => { log::error!("{error} in input_from_connector");
let Some(node) = network.nodes.get(node_id) else { None
log::error!("Could not get node {node_id} in input_from_connector");
return None;
};
node.inputs.get(*input_index)
} }
InputConnector::Export(export_index) => network.exports.get(*export_index),
} }
} }
@@ -806,86 +680,40 @@ impl NodeNetworkInterface {
} }
pub fn previewing(&self, network_path: &[NodeId]) -> Previewing { pub fn previewing(&self, network_path: &[NodeId]) -> Previewing {
let Some(network_metadata) = self.network_metadata(network_path) else { self.query(network_path, "previewing", |view| Ok(view.previewing())).unwrap_or(Previewing::No)
log::error!("Could not get nested network_metadata in previewing");
return Previewing::No;
};
network_metadata.persistent_metadata.previewing
} }
/// Returns the root node (the node that the solid line is connect to), or None if no nodes are connected to the output /// Returns the root node (the node that the solid line is connect to), or None if no nodes are connected to the output
pub fn root_node(&self, network_path: &[NodeId]) -> Option<RootNode> { pub fn root_node(&self, network_path: &[NodeId]) -> Option<RootNode> {
let Some(network) = self.nested_network(network_path) else { self.query(network_path, "root_node", |view| Ok(view.root_node())).flatten()
log::error!("Could not get network in root_node");
return None;
};
let Some(network_metadata) = self.network_metadata(network_path) else {
log::error!("Could not get nested network_metadata in root_node");
return None;
};
match &network_metadata.persistent_metadata.previewing {
Previewing::Yes { root_node_to_restore } => *root_node_to_restore,
Previewing::No => network.exports.first().and_then(|export| {
if let NodeInput::Node { node_id, output_index, .. } = export {
Some(RootNode {
node_id: *node_id,
output_index: *output_index,
})
} else {
None
}
}),
}
} }
pub fn reference(&self, node_id: &NodeId, network_path: &[NodeId]) -> Option<DefinitionIdentifier> { pub fn reference(&self, node_id: &NodeId, network_path: &[NodeId]) -> Option<DefinitionIdentifier> {
let Some(document_node) = self.document_node(node_id, network_path) else { self.query(network_path, "reference", |view| view.reference(node_id)).flatten()
log::error!("Could not get document_node for node in reference: {node_id:?}");
return None;
};
match &document_node.implementation {
DocumentNodeImplementation::Network(_) => {
let Some(node_metadata) = self.node_metadata(node_id, network_path) else {
log::error!("Could not get reference for node in reference: {node_id:?}");
return None;
};
let Some(network_metadata) = node_metadata.persistent_metadata.network_metadata.as_ref() else {
log::error!("Network metadata must exist for network node in reference: {node_id:?}");
return None;
};
network_metadata.persistent_metadata.reference.clone().map(DefinitionIdentifier::Network)
}
DocumentNodeImplementation::ProtoNode(protonode_id) => Some(DefinitionIdentifier::ProtoNode(protonode_id.clone())),
_ => None,
}
} }
pub fn implementation(&self, node_id: &NodeId, network_path: &[NodeId]) -> Option<&DocumentNodeImplementation> { pub fn implementation(&self, node_id: &NodeId, network_path: &[NodeId]) -> Option<&DocumentNodeImplementation> {
let Some(node) = self.document_node(node_id, network_path) else { self.query(network_path, "implementation", |view| view.implementation(node_id))
log::error!("Could not get implementation");
return None;
};
Some(&node.implementation)
} }
pub fn input_data(&self, node_id: &NodeId, index: usize, key: &str, network_path: &[NodeId]) -> Option<&Value> { pub fn input_data(&self, node_id: &NodeId, index: usize, key: &str, network_path: &[NodeId]) -> Option<&Value> {
let metadata = self match self.view(network_path).and_then(|view| view.input_data(node_id, index, key)) {
.node_metadata(node_id, network_path) Ok(value) => value,
.and_then(|node_metadata| node_metadata.persistent_metadata.input_metadata.get(index))?; // A missing input slot is the caller probing optional data, but any other failure is a corrupt network worth logging
metadata.persistent_metadata.input_data.get(key) Err(NetworkError::InputNotFound { .. }) => None,
Err(error) => {
log::error!("{error} in input_data");
None
}
}
} }
pub fn persistent_input_metadata(&self, node_id: &NodeId, index: usize, network_path: &[NodeId]) -> Option<&InputPersistentMetadata> { pub fn persistent_input_metadata(&self, node_id: &NodeId, index: usize, network_path: &[NodeId]) -> Option<&InputPersistentMetadata> {
let metadata = self self.view(network_path).ok().and_then(|view| view.persistent_input_metadata(node_id, index).ok())
.node_metadata(node_id, network_path)
.and_then(|node_metadata| node_metadata.persistent_metadata.input_metadata.get(index))?;
Some(&metadata.persistent_metadata)
} }
pub(crate) fn transient_input_metadata(&self, node_id: &NodeId, index: usize, network_path: &[NodeId]) -> Option<&InputTransientMetadata> { pub(crate) fn transient_input_metadata(&self, node_id: &NodeId, index: usize, network_path: &[NodeId]) -> Option<&InputTransientMetadata> {
let metadata = self self.view(network_path).ok().and_then(|view| view.transient_input_metadata(node_id, index).ok())
.node_metadata(node_id, network_path)
.and_then(|node_metadata| node_metadata.persistent_metadata.input_metadata.get(index))?;
Some(&metadata.transient_metadata)
} }
pub fn set_input_override(&mut self, node_id: &NodeId, index: usize, widget_override: Option<String>, network_path: &[NodeId]) { pub fn set_input_override(&mut self, node_id: &NodeId, index: usize, widget_override: Option<String>, network_path: &[NodeId]) {
@@ -916,93 +744,35 @@ impl NodeNetworkInterface {
/// Returns the display name of the node. If the display name is empty, it will return "Untitled Node" or "Untitled Layer" depending on the node type. /// Returns the display name of the node. If the display name is empty, it will return "Untitled Node" or "Untitled Layer" depending on the node type.
pub fn display_name(&self, node_id: &NodeId, network_path: &[NodeId]) -> String { pub fn display_name(&self, node_id: &NodeId, network_path: &[NodeId]) -> String {
let is_layer = self.is_layer(node_id, network_path); self.query(network_path, "display_name", |view| Ok(view.display_name(node_id)))
.unwrap_or_else(|| "Custom Node".to_string())
let display_name = if let Some(node_metadata) = self.node_metadata(node_id, network_path) {
node_metadata.persistent_metadata.display_name.clone()
} else {
log::error!("Could not get node_metadata in display_name");
String::new()
};
if display_name.is_empty() {
if is_layer {
"Untitled Layer".to_string()
} else {
// TODO: Have this displayed in italics in the UI
self.implementation_name(node_id, network_path)
}
} else {
display_name
}
} }
/// The uneditable name in the Properties panel which represents the function name of the node implementation. /// The uneditable name in the Properties panel which represents the function name of the node implementation.
pub fn implementation_name(&self, node_id: &NodeId, network_path: &[NodeId]) -> String { pub fn implementation_name(&self, node_id: &NodeId, network_path: &[NodeId]) -> String {
self.reference(node_id, network_path) self.query(network_path, "implementation_name", |view| Ok(view.implementation_name(node_id)))
.map(|identifier| identifier.implementation_name_from_identifier()) .unwrap_or_else(|| "Custom Node".to_string())
.unwrap_or("Custom Node".to_string())
} }
pub fn is_locked(&self, node_id: &NodeId, network_path: &[NodeId]) -> bool { pub fn is_locked(&self, node_id: &NodeId, network_path: &[NodeId]) -> bool {
let Some(node_metadata) = self.node_metadata(node_id, network_path) else { self.query(network_path, "is_locked", |view| view.is_locked(node_id)).unwrap_or_default()
log::error!("Could not get persistent node metadata in is_locked for node {node_id}");
return false;
};
node_metadata.persistent_metadata.locked
} }
pub fn is_pinned(&self, node_id: &NodeId, network_path: &[NodeId]) -> bool { pub fn is_pinned(&self, node_id: &NodeId, network_path: &[NodeId]) -> bool {
let Some(node_metadata) = self.node_metadata(node_id, network_path) else { self.query(network_path, "is_pinned", |view| view.is_pinned(node_id)).unwrap_or_default()
log::error!("Could not get persistent node metadata in is_pinned for node {node_id}");
return false;
};
node_metadata.persistent_metadata.pinned
} }
/// The given network's pinned nodes in display order: pinning appends, dragging rearranges, and any not yet recorded go last. /// The given network's pinned nodes in display order: pinning appends, dragging rearranges, and any not yet recorded go last.
pub fn ordered_pinned_nodes(&self, network_path: &[NodeId]) -> Vec<NodeId> { pub fn ordered_pinned_nodes(&self, network_path: &[NodeId]) -> Vec<NodeId> {
let Some(network) = self.nested_network(network_path) else { return Vec::new() }; self.view(network_path).map(|view| view.ordered_pinned_nodes()).unwrap_or_default()
let order = self
.network_metadata(network_path)
.map(|network_metadata| network_metadata.persistent_metadata.pinned_node_order.clone())
.unwrap_or_default();
// Follow the saved order, keeping only nodes that still exist and are still pinned
let mut pinned_nodes = order
.iter()
.copied()
.filter(|node_id| network.nodes.contains_key(node_id) && self.is_pinned(node_id, network_path))
.collect::<Vec<_>>();
// Append any pinned nodes missing from the saved order at the end
let mut unordered = network
.nodes
.keys()
.copied()
.filter(|node_id| self.is_pinned(node_id, network_path) && !order.contains(node_id))
.collect::<Vec<_>>();
unordered.sort();
pinned_nodes.extend(unordered);
pinned_nodes
} }
pub fn is_visible(&self, node_id: &NodeId, network_path: &[NodeId]) -> bool { pub fn is_visible(&self, node_id: &NodeId, network_path: &[NodeId]) -> bool {
let Some(node) = self.document_node(node_id, network_path) else { self.query(network_path, "is_visible", |view| view.is_visible(node_id)).unwrap_or_default()
log::error!("Could not get node in is_visible");
return false;
};
node.visible
} }
pub fn is_layer(&self, node_id: &NodeId, network_path: &[NodeId]) -> bool { pub fn is_layer(&self, node_id: &NodeId, network_path: &[NodeId]) -> bool {
let Some(node_metadata) = self.node_metadata(node_id, network_path) else { self.query(network_path, "is_layer", |view| view.is_layer(node_id)).unwrap_or_default()
log::error!("Could not get nested node_metadata in is_layer");
return false;
};
node_metadata.persistent_metadata.is_layer()
} }
pub fn primary_output_connected_to_layer(&mut self, node_id: &NodeId, network_path: &[NodeId]) -> bool { pub fn primary_output_connected_to_layer(&mut self, node_id: &NodeId, network_path: &[NodeId]) -> bool {
@@ -1022,73 +792,39 @@ impl NodeNetworkInterface {
downstream_nodes.iter().any(|node_id| self.is_layer(node_id, network_path)) downstream_nodes.iter().any(|node_id| self.is_layer(node_id, network_path))
} }
pub fn primary_input_connected_to_layer(&mut self, node_id: &NodeId, network_path: &[NodeId]) -> bool { pub fn primary_input_connected_to_layer(&self, node_id: &NodeId, network_path: &[NodeId]) -> bool {
self.input_from_connector(&InputConnector::node(*node_id, 0), network_path) self.query(network_path, "primary_input_connected_to_layer", |view| Ok(view.primary_input_connected_to_layer(node_id)))
.and_then(|input| input.as_node()) .unwrap_or_default()
.is_some_and(|node_id| self.is_layer(&node_id, network_path))
} }
pub fn hidden_primary_export(&self, network_path: &[NodeId]) -> bool { pub fn hidden_primary_export(&self, network_path: &[NodeId]) -> bool {
let Some((node_id, network_path)) = network_path.split_last() else { self.view(network_path).map(|view| view.hidden_primary_export()).unwrap_or_default()
// The document network does not have a hidden primary export
return false;
};
self.hidden_primary_output(node_id, network_path)
} }
pub fn hidden_primary_output(&self, node_id: &NodeId, network_path: &[NodeId]) -> bool { pub fn hidden_primary_output(&self, node_id: &NodeId, network_path: &[NodeId]) -> bool {
match self.implementation(node_id, network_path) { self.view(network_path).and_then(|view| view.hidden_primary_output(node_id)).unwrap_or_default()
Some(DocumentNodeImplementation::Network(network)) => network.exports.first().is_none_or(|input| !input.is_exposed()),
_ => false,
}
} }
pub fn hidden_primary_import(&self, network_path: &[NodeId]) -> bool { pub fn hidden_primary_import(&self, network_path: &[NodeId]) -> bool {
let Some((encapsulating_node_id, encapsulating_path)) = network_path.split_last() else { self.view(network_path).map(|view| view.hidden_primary_import()).unwrap_or_default()
return false;
};
self.input_from_connector(&InputConnector::node(*encapsulating_node_id, 0), encapsulating_path)
.is_some_and(|input| !input.is_exposed())
} }
pub fn is_absolute(&self, node_id: &NodeId, network_path: &[NodeId]) -> bool { pub fn is_absolute(&self, node_id: &NodeId, network_path: &[NodeId]) -> bool {
let Some(node_metadata) = self.node_metadata(node_id, network_path) else { self.query(network_path, "is_absolute", |view| view.is_absolute(node_id)).unwrap_or_default()
log::error!("Could not get node_metadata in is_absolute");
return false;
};
match &node_metadata.persistent_metadata.node_type_metadata {
NodeTypePersistentMetadata::Layer(layer_metadata) => matches!(layer_metadata.position, LayerPosition::Absolute(_)),
NodeTypePersistentMetadata::Node(node_metadata) => matches!(node_metadata.position, NodePosition::Absolute(_)),
}
} }
pub fn is_chain(&self, node_id: &NodeId, network_path: &[NodeId]) -> bool { pub fn is_chain(&self, node_id: &NodeId, network_path: &[NodeId]) -> bool {
let Some(node_metadata) = self.node_metadata(node_id, network_path) else { self.query(network_path, "is_chain", |view| view.is_chain(node_id)).unwrap_or_default()
log::error!("Could not get node_metadata in is_chain");
return false;
};
match &node_metadata.persistent_metadata.node_type_metadata {
NodeTypePersistentMetadata::Node(node_metadata) => matches!(node_metadata.position, NodePosition::Chain),
_ => false,
}
} }
pub fn is_stack(&self, node_id: &NodeId, network_path: &[NodeId]) -> bool { pub fn is_stack(&self, node_id: &NodeId, network_path: &[NodeId]) -> bool {
let Some(node_metadata) = self.node_metadata(node_id, network_path) else { self.query(network_path, "is_stack", |view| view.is_stack(node_id)).unwrap_or_default()
log::error!("Could not get node_metadata in is_stack");
return false;
};
match &node_metadata.persistent_metadata.node_type_metadata {
NodeTypePersistentMetadata::Layer(layer_metadata) => matches!(layer_metadata.position, LayerPosition::Stack(_)),
_ => false,
}
} }
/// Whether the node is an Artboard node by identity, regardless of whether it currently participates in the scene. /// Whether the node is an Artboard node by identity, regardless of whether it currently participates in the scene.
/// Callers that care about scene membership should source their layers from the document structure or check connectivity separately. /// Callers that care about scene membership should source their layers from the document structure or check connectivity separately.
pub fn is_artboard(&self, node_id: &NodeId, network_path: &[NodeId]) -> bool { pub fn is_artboard(&self, node_id: &NodeId, network_path: &[NodeId]) -> bool {
self.reference(node_id, network_path) self.view(network_path).map(|view| view.is_artboard(node_id)).unwrap_or_default()
.is_some_and(|reference| reference == DefinitionIdentifier::Network("Artboard".into()))
} }
/// All artboard layers that participate in the scene, excluding disconnected Artboard nodes. /// All artboard layers that participate in the scene, excluding disconnected Artboard nodes.
@@ -1290,34 +1026,18 @@ impl NodeNetworkInterface {
} }
/// Gives an iterator to all nodes connected to the given nodes by all inputs (primary or primary + secondary depending on `only_follow_primary` choice), traversing backwards upstream starting from the given node's inputs. /// Gives an iterator to all nodes connected to the given nodes by all inputs (primary or primary + secondary depending on `only_follow_primary` choice), traversing backwards upstream starting from the given node's inputs.
pub fn upstream_flow_back_from_nodes<'a>(&'a self, mut node_ids: Vec<NodeId>, network_path: &'a [NodeId], mut flow_type: FlowType) -> impl Iterator<Item = NodeId> + 'a { pub fn upstream_flow_back_from_nodes<'a>(&'a self, node_ids: Vec<NodeId>, network_path: &'a [NodeId], flow_type: FlowType) -> impl Iterator<Item = NodeId> + 'a {
let (Some(network), Some(network_metadata)) = (self.nested_network(network_path), self.network_metadata(network_path)) else { match self.view(network_path) {
log::error!("Could not get network or network_metadata in upstream_flow_back_from_nodes"); Ok(view) => view.upstream_flow(node_ids, flow_type),
return FlowIter { Err(error) => {
stack: Vec::new(), log::error!("{error} in upstream_flow_back_from_nodes");
network: self.document_network(), FlowIter {
network_metadata: &self.network_metadata, stack: Vec::new(),
flow_type: FlowType::UpstreamFlow, network: self.document_network(),
}; network_metadata: &self.network_metadata,
}; flow_type: FlowType::UpstreamFlow,
if matches!(flow_type, FlowType::LayerChildrenUpstreamFlow) { }
node_ids = node_ids }
.iter()
.filter_map(move |node_id| {
if self.is_layer(node_id, network_path) {
network.nodes.get(node_id).and_then(|node| node.inputs.get(1)).and_then(|input| input.as_node())
} else {
Some(*node_id)
}
})
.collect::<Vec<_>>();
flow_type = FlowType::UpstreamFlow;
};
FlowIter {
stack: node_ids,
network,
network_metadata,
flow_type,
} }
} }
@@ -0,0 +1,426 @@
use super::*;
/// A failure to resolve part of the network or its metadata, returned by [`NetworkView`] accessors.
#[derive(Debug, Clone, PartialEq, thiserror::Error)]
pub enum NetworkError {
#[error("Network not found at path {path:?}")]
NetworkNotFound { path: Vec<NodeId> },
#[error("Node {node_id} not found")]
NodeNotFound { node_id: NodeId },
#[error("Node metadata for {node_id} not found")]
NodeMetadataNotFound { node_id: NodeId },
#[error("Nested network metadata for node {node_id} not found")]
NestedMetadataNotFound { node_id: NodeId },
#[error("Input {connector:?} not found")]
InputNotFound { connector: InputConnector },
#[error("The document network has no encapsulating node")]
NoEncapsulatingNode,
}
/// A read cursor over one network nesting level, resolving the network and its parallel metadata once instead of per query.
/// Obtained from [`NodeNetworkInterface::view`]; accessors return typed errors that callers at the message boundary may log.
#[derive(Clone, Copy)]
pub struct NetworkView<'a, 'p> {
pub(crate) interface: &'a NodeNetworkInterface,
pub(crate) path: &'p [NodeId],
pub(crate) network: &'a NodeNetwork,
pub(crate) metadata: &'a NodeNetworkMetadata,
}
impl NodeNetworkInterface {
/// Resolves a read cursor for the network at `network_path`, walking the nested network and metadata trees once.
pub fn view<'a, 'p>(&'a self, network_path: &'p [NodeId]) -> Result<NetworkView<'a, 'p>, NetworkError> {
let network = self
.document_network()
.nested_network(network_path)
.ok_or_else(|| NetworkError::NetworkNotFound { path: network_path.to_vec() })?;
let metadata = self
.document_network_metadata()
.nested_metadata(network_path)
.ok_or_else(|| NetworkError::NetworkNotFound { path: network_path.to_vec() })?;
Ok(NetworkView {
interface: self,
path: network_path,
network,
metadata,
})
}
}
impl<'a, 'p> NetworkView<'a, 'p> {
pub fn network(&self) -> &'a NodeNetwork {
self.network
}
pub fn network_metadata(&self) -> &'a NodeNetworkMetadata {
self.metadata
}
pub fn node(&self, node_id: &NodeId) -> Result<&'a DocumentNode, NetworkError> {
self.network.nodes.get(node_id).ok_or(NetworkError::NodeNotFound { node_id: *node_id })
}
pub fn node_metadata(&self, node_id: &NodeId) -> Result<&'a DocumentNodeMetadata, NetworkError> {
self.metadata
.persistent_metadata
.node_metadata
.get(node_id)
.ok_or(NetworkError::NodeMetadataNotFound { node_id: *node_id })
}
pub fn input(&self, input_connector: &InputConnector) -> Result<&'a NodeInput, NetworkError> {
let input = match input_connector {
InputConnector::Node { node_id, input_index } => self.node(node_id)?.inputs.get(*input_index),
InputConnector::Export(export_index) => self.network.exports.get(*export_index),
};
input.ok_or(NetworkError::InputNotFound { connector: *input_connector })
}
/// The view over the network which contains this network's encapsulating node, or an error in the document network.
pub fn encapsulating(&self) -> Result<NetworkView<'a, 'p>, NetworkError> {
let (_, encapsulating_path) = self.path.split_last().ok_or(NetworkError::NoEncapsulatingNode)?;
self.interface.view(encapsulating_path)
}
/// The node which encapsulates this network, or an error in the document network.
pub fn encapsulating_node(&self) -> Result<&'a DocumentNode, NetworkError> {
let (node_id, _) = self.path.split_last().ok_or(NetworkError::NoEncapsulatingNode)?;
self.encapsulating()?.node(node_id)
}
/// The metadata of the node which encapsulates this network, or an error in the document network.
pub fn encapsulating_node_metadata(&self) -> Result<&'a DocumentNodeMetadata, NetworkError> {
let (node_id, _) = self.path.split_last().ok_or(NetworkError::NoEncapsulatingNode)?;
self.encapsulating()?.node_metadata(node_id)
}
pub fn implementation(&self, node_id: &NodeId) -> Result<&'a DocumentNodeImplementation, NetworkError> {
Ok(&self.node(node_id)?.implementation)
}
pub fn reference(&self, node_id: &NodeId) -> Result<Option<DefinitionIdentifier>, NetworkError> {
match self.implementation(node_id)? {
DocumentNodeImplementation::Network(_) => {
let node_metadata = self.node_metadata(node_id)?;
let network_metadata = node_metadata
.persistent_metadata
.network_metadata
.as_ref()
.ok_or(NetworkError::NestedMetadataNotFound { node_id: *node_id })?;
Ok(network_metadata.persistent_metadata.reference.clone().map(DefinitionIdentifier::Network))
}
DocumentNodeImplementation::ProtoNode(protonode_id) => Ok(Some(DefinitionIdentifier::ProtoNode(protonode_id.clone()))),
_ => Ok(None),
}
}
pub fn is_layer(&self, node_id: &NodeId) -> Result<bool, NetworkError> {
Ok(self.node_metadata(node_id)?.persistent_metadata.is_layer())
}
/// Whether the node is an Artboard node by identity, regardless of whether it currently participates in the scene.
pub fn is_artboard(&self, node_id: &NodeId) -> bool {
self.reference(node_id)
.ok()
.flatten()
.is_some_and(|reference| reference == DefinitionIdentifier::Network("Artboard".into()))
}
/// The uneditable name in the Properties panel which represents the function name of the node implementation.
pub fn implementation_name(&self, node_id: &NodeId) -> String {
self.reference(node_id)
.ok()
.flatten()
.map(|identifier| identifier.implementation_name_from_identifier())
.unwrap_or("Custom Node".to_string())
}
/// The display name of the node, falling back to "Untitled Layer" or the implementation name when unnamed.
pub fn display_name(&self, node_id: &NodeId) -> String {
let display_name = self.node_metadata(node_id).map(|metadata| metadata.persistent_metadata.display_name.clone()).unwrap_or_default();
if display_name.is_empty() {
if self.is_layer(node_id).unwrap_or_default() {
"Untitled Layer".to_string()
} else {
// TODO: Have this displayed in italics in the UI
self.implementation_name(node_id)
}
} else {
display_name
}
}
/// The current selection of this network, filtered to nodes that still exist.
pub fn selected_nodes(&self) -> SelectedNodes {
self.metadata
.persistent_metadata
.selection_undo_history
.back()
.cloned()
.unwrap_or_default()
.filtered_selected_nodes(|node_id| self.metadata.persistent_metadata.node_metadata.contains_key(node_id))
}
pub fn primary_input_connected_to_layer(&self, node_id: &NodeId) -> bool {
self.input(&InputConnector::node(*node_id, 0))
.ok()
.and_then(|input| input.as_node())
.is_some_and(|upstream_id| self.is_layer(&upstream_id).unwrap_or_default())
}
/// Whether the node's signature fits the layer form: one output, at most two exposed leading inputs, and no exposed parameters.
pub fn is_eligible_to_be_layer(&self, node_id: &NodeId) -> Result<bool, NetworkError> {
let node = self.node(node_id)?;
let input_count = node.inputs.iter().take(2).filter(|input| input.is_exposed()).count();
let parameters_hidden = node.inputs.iter().skip(2).all(|input| !input.is_exposed());
let output_count = self.number_of_outputs(node_id)?;
Ok(!self.hidden_primary_output(node_id)? && output_count == 1 && (input_count <= 2) && parameters_hidden)
}
pub fn is_locked(&self, node_id: &NodeId) -> Result<bool, NetworkError> {
Ok(self.node_metadata(node_id)?.persistent_metadata.locked)
}
pub fn is_pinned(&self, node_id: &NodeId) -> Result<bool, NetworkError> {
Ok(self.node_metadata(node_id)?.persistent_metadata.pinned)
}
pub fn is_visible(&self, node_id: &NodeId) -> Result<bool, NetworkError> {
Ok(self.node(node_id)?.visible)
}
pub fn is_absolute(&self, node_id: &NodeId) -> Result<bool, NetworkError> {
Ok(match &self.node_metadata(node_id)?.persistent_metadata.node_type_metadata {
NodeTypePersistentMetadata::Layer(layer_metadata) => matches!(layer_metadata.position, LayerPosition::Absolute(_)),
NodeTypePersistentMetadata::Node(node_metadata) => matches!(node_metadata.position, NodePosition::Absolute(_)),
})
}
pub fn is_chain(&self, node_id: &NodeId) -> Result<bool, NetworkError> {
Ok(match &self.node_metadata(node_id)?.persistent_metadata.node_type_metadata {
NodeTypePersistentMetadata::Node(node_metadata) => matches!(node_metadata.position, NodePosition::Chain),
_ => false,
})
}
pub fn is_stack(&self, node_id: &NodeId) -> Result<bool, NetworkError> {
Ok(match &self.node_metadata(node_id)?.persistent_metadata.node_type_metadata {
NodeTypePersistentMetadata::Layer(layer_metadata) => matches!(layer_metadata.position, LayerPosition::Stack(_)),
_ => false,
})
}
pub fn number_of_inputs(&self, node_id: &NodeId) -> Result<usize, NetworkError> {
Ok(self.node(node_id)?.inputs.len())
}
pub fn number_of_displayed_inputs(&self, node_id: &NodeId) -> Result<usize, NetworkError> {
Ok(self.node(node_id)?.inputs.iter().filter(|input| input.is_exposed()).count())
}
pub fn number_of_outputs(&self, node_id: &NodeId) -> Result<usize, NetworkError> {
Ok(match self.implementation(node_id)? {
DocumentNodeImplementation::Network(nested_network) => nested_network.exports.len(),
_ => 1,
})
}
/// The number of imports as defined by the encapsulating node's input count, or zero for the document network.
pub fn number_of_imports(&self) -> usize {
self.encapsulating_node().map_or(0, |node| node.inputs.len())
}
pub fn number_of_exports(&self) -> usize {
self.network.exports.len()
}
pub fn has_primary_input(&self, node_id: &NodeId) -> Result<bool, NetworkError> {
Ok(self.input(&InputConnector::node(*node_id, 0)).is_ok_and(|input| input.is_exposed()))
}
pub fn hidden_primary_output(&self, node_id: &NodeId) -> Result<bool, NetworkError> {
Ok(match self.implementation(node_id)? {
DocumentNodeImplementation::Network(network) => network.exports.first().is_none_or(|input| !input.is_exposed()),
_ => false,
})
}
pub fn hidden_primary_export(&self) -> bool {
let Some((node_id, _)) = self.path.split_last() else { return false };
self.encapsulating().and_then(|parent| parent.hidden_primary_output(node_id)).unwrap_or(false)
}
pub fn hidden_primary_import(&self) -> bool {
self.encapsulating_node().is_ok_and(|node| node.inputs.first().is_some_and(|input| !input.is_exposed()))
}
pub fn previewing(&self) -> Previewing {
self.metadata.persistent_metadata.previewing
}
/// The root node (the node that the solid line is connected to), or None if no nodes are connected to the output.
pub fn root_node(&self) -> Option<RootNode> {
match &self.metadata.persistent_metadata.previewing {
Previewing::Yes { root_node_to_restore } => *root_node_to_restore,
Previewing::No => self.network.exports.first().and_then(|export| {
if let NodeInput::Node { node_id, output_index, .. } = export {
Some(RootNode {
node_id: *node_id,
output_index: *output_index,
})
} else {
None
}
}),
}
}
pub fn input_data(&self, node_id: &NodeId, index: usize, key: &str) -> Result<Option<&'a Value>, NetworkError> {
Ok(self.persistent_input_metadata(node_id, index)?.input_data.get(key))
}
pub fn persistent_input_metadata(&self, node_id: &NodeId, index: usize) -> Result<&'a InputPersistentMetadata, NetworkError> {
let metadata = self.node_metadata(node_id)?;
let input_metadata = metadata.persistent_metadata.input_metadata.get(index).ok_or(NetworkError::InputNotFound {
connector: InputConnector::node(*node_id, index),
})?;
Ok(&input_metadata.persistent_metadata)
}
pub(crate) fn transient_input_metadata(&self, node_id: &NodeId, index: usize) -> Result<&'a InputTransientMetadata, NetworkError> {
let metadata = self.node_metadata(node_id)?;
let input_metadata = metadata.persistent_metadata.input_metadata.get(index).ok_or(NetworkError::InputNotFound {
connector: InputConnector::node(*node_id, index),
})?;
Ok(&input_metadata.transient_metadata)
}
pub fn upstream_output_connector(&self, input_connector: &InputConnector) -> Result<Option<OutputConnector>, NetworkError> {
Ok(match self.input(input_connector)? {
NodeInput::Node { node_id, output_index, .. } => Some(OutputConnector::node(*node_id, *output_index)),
NodeInput::Import { import_index, .. } => Some(OutputConnector::Import(*import_index)),
_ => None,
})
}
/// Whether the node reaches the exports by following wires downstream.
// O(nodes + wires) reachability walk
pub fn connected_to_output(&self, target_node_id: &NodeId) -> bool {
if self
.network
.exports
.iter()
.any(|export| if let NodeInput::Node { node_id, .. } = export { node_id == target_node_id } else { false })
{
return true;
}
let mut stack = self
.network
.exports
.iter()
.filter_map(|output| if let NodeInput::Node { node_id, .. } = output { self.network.nodes.get(node_id) } else { None })
.collect::<Vec<_>>();
let mut already_visited = HashSet::new();
already_visited.extend(
self.network
.exports
.iter()
.filter_map(|output| if let NodeInput::Node { node_id, .. } = output { Some(node_id) } else { None }),
);
while let Some(node) = stack.pop() {
for input in &node.inputs {
if let &NodeInput::Node { node_id: ref_id, .. } = input {
if already_visited.contains(&ref_id) {
continue;
}
if ref_id == *target_node_id {
return true;
}
let Some(ref_node) = self.network.nodes.get(&ref_id) else { continue };
already_visited.insert(ref_id);
stack.push(ref_node);
}
}
}
false
}
/// An iterator of all nodes connected to the given nodes by the flow type, traversing backwards upstream from the given nodes' inputs.
pub(crate) fn upstream_flow(&self, mut node_ids: Vec<NodeId>, mut flow_type: FlowType) -> FlowIter<'a> {
if matches!(flow_type, FlowType::LayerChildrenUpstreamFlow) {
node_ids = node_ids
.iter()
.filter_map(|node_id| {
if self.is_layer(node_id).unwrap_or_default() {
self.network.nodes.get(node_id).and_then(|node| node.inputs.get(1)).and_then(|input| input.as_node())
} else {
Some(*node_id)
}
})
.collect::<Vec<_>>();
flow_type = FlowType::UpstreamFlow;
};
FlowIter {
stack: node_ids,
network: self.network,
network_metadata: self.metadata,
flow_type,
}
}
/// The distance in grid cells from the layer to the end of its chain, or zero for a layer with no chain.
pub fn chain_width(&self, node_id: &NodeId) -> u32 {
if self.number_of_displayed_inputs(node_id).unwrap_or_default() > 1 {
let mut last_chain_node_distance = 0_u32;
for (index, node_id) in self.upstream_flow(vec![*node_id], FlowType::HorizontalPrimaryOutputFlow).skip(1).enumerate() {
if self.is_chain(&node_id).unwrap_or_default() {
last_chain_node_distance = (index as u32) + 1;
} else {
return last_chain_node_distance * NODE_CHAIN_WIDTH as u32 + 1;
}
}
last_chain_node_distance * NODE_CHAIN_WIDTH as u32 + 1
} else {
// A layer with no secondary input has no chain
0
}
}
/// The given network's pinned nodes in display order: pinning appends, dragging rearranges, and any not yet recorded go last.
pub fn ordered_pinned_nodes(&self) -> Vec<NodeId> {
let order = &self.metadata.persistent_metadata.pinned_node_order;
let is_pinned = |node_id: &NodeId| self.is_pinned(node_id).unwrap_or_default();
// Follow the saved order, keeping only nodes that still exist and are still pinned
let mut pinned_nodes = order
.iter()
.copied()
.filter(|node_id| self.network.nodes.contains_key(node_id) && is_pinned(node_id))
.collect::<Vec<_>>();
// Append any pinned nodes missing from the saved order at the end
let mut unordered = self.network.nodes.keys().copied().filter(|node_id| is_pinned(node_id) && !order.contains(node_id)).collect::<Vec<_>>();
unordered.sort();
pinned_nodes.extend(unordered);
pinned_nodes
}
/// Create a node template from an existing node.
pub fn create_node_template(&self, node_id: &NodeId) -> Result<NodeTemplate, NetworkError> {
let node = self.node(node_id)?;
let node_metadata = self.node_metadata(node_id)?;
Ok(NodeTemplate {
persistent_node_metadata: node_metadata.persistent_metadata.clone(),
document_node: node.clone(),
})
}
}