flow-graph 0.23.0

A representation of a graph data structure for flow-based programming.
Documentation
use std::collections::HashMap;

use crate::error::Error;
use crate::port::{PortDirection, PortReference};
use crate::schematic::{ConnectionIndex, NodeIndex, PortIndex};

#[derive(Debug, Clone, PartialEq, Eq)]
#[must_use]
#[allow(clippy::exhaustive_enums)]
pub enum NodeKind {
  Input(NodeReference),
  Output(NodeReference),
  Inherent(NodeReference),
  External(NodeReference),
}

impl NodeKind {
  pub fn input() -> Self {
    NodeKind::Input(NodeReference {
      name: crate::SCHEMATIC_INPUT.to_owned(),
      component_id: crate::NS_SCHEMATIC.to_owned(),
    })
  }
  pub fn output() -> Self {
    NodeKind::Output(NodeReference {
      name: crate::SCHEMATIC_OUTPUT.to_owned(),
      component_id: crate::NS_SCHEMATIC.to_owned(),
    })
  }
  pub const fn cref(&self) -> &NodeReference {
    match self {
      NodeKind::Input(c) => c,
      NodeKind::Output(c) => c,
      NodeKind::Inherent(c) => c,
      NodeKind::External(c) => c,
    }
  }
}

#[derive(Debug, Clone, PartialEq, Eq)]
#[must_use]
pub struct NodeReference {
  name: String,
  component_id: String,
}

impl NodeReference {
  pub fn new<T: Into<String>, U: Into<String>>(component_id: T, name: U) -> Self {
    Self {
      name: name.into(),
      component_id: component_id.into(),
    }
  }

  #[must_use]
  pub fn component_id(&self) -> &str {
    &self.component_id
  }

  #[must_use]
  pub fn name(&self) -> &str {
    &self.name
  }
}

impl std::fmt::Display for NodeReference {
  fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
    write!(f, "{}::{}", self.component_id, self.name)
  }
}

impl std::fmt::Display for NodeKind {
  fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
    write!(
      f,
      "{}",
      match self {
        NodeKind::Input(v) => format!("Input({})", v),
        NodeKind::Output(v) => format!("Output({})", v),
        NodeKind::Inherent(v) => format!("Inherent({})", v),
        NodeKind::External(v) => format!("External({})", v),
      }
    )
  }
}

#[derive(Debug, Clone, Eq)]
#[must_use]
pub struct Node<DATA> {
  pub name: String,
  kind: NodeKind,
  index: NodeIndex,
  data: DATA,
  inputs: PortList,
  outputs: PortList,
}

impl<DATA> PartialEq for Node<DATA> {
  fn eq(&self, other: &Self) -> bool {
    self.index == other.index
  }
}

impl<DATA> Node<DATA>
where
  DATA: Clone,
{
  pub(crate) fn new<T: Into<String>>(name: T, index: NodeIndex, kind: NodeKind, data: DATA) -> Self {
    Self {
      name: name.into(),
      kind,
      index,
      data,
      inputs: PortList::new(PortDirection::In),
      outputs: PortList::new(PortDirection::Out),
    }
  }

  pub const fn kind(&self) -> &NodeKind {
    &self.kind
  }

  pub const fn cref(&self) -> &NodeReference {
    self.kind.cref()
  }

  #[must_use]
  pub const fn index(&self) -> NodeIndex {
    self.index
  }

  #[must_use]
  pub fn id(&self) -> &str {
    &self.name
  }

  #[must_use]
  pub const fn data(&self) -> &DATA {
    &self.data
  }

  pub fn inputs(&self) -> &[NodePort] {
    self.inputs.inner()
  }

  #[must_use]
  pub fn input_refs(&self) -> Vec<PortReference> {
    self.inputs.inner().iter().map(|c| c.port).collect()
  }

  #[must_use]
  pub fn find_input(&self, name: &str) -> Option<&NodePort> {
    self.inputs.find(name)
  }

  #[must_use]
  pub fn get_input(&self, index: PortIndex) -> Option<&NodePort> {
    self.inputs.get(index)
  }

  pub fn add_input<T: Into<String>>(&mut self, port: T) -> PortReference {
    match self.kind {
      NodeKind::Output(_) => {
        let port = port.into();
        self.outputs.add(port.clone(), self.index);
        self.inputs.add(port, self.index)
      }
      NodeKind::Input(_) | NodeKind::Inherent(_) => {
        // Input/Output nodes have the same ports in & out.
        panic!("You can not manually add inputs to {} nodes", self.kind);
      }
      NodeKind::External(_) => self.inputs.add(port, self.index),
    }
  }

  pub(crate) fn connect_input(&mut self, port: PortIndex, connection: ConnectionIndex) -> Result<(), Error> {
    self.inputs.add_connection(port, connection)
  }

  pub(crate) fn input_connections(&self, port: PortIndex) -> Option<&Vec<ConnectionIndex>> {
    self.inputs.port_connections(port)
  }

  pub(crate) fn all_upstreams(&self) -> Vec<ConnectionIndex> {
    self.inputs.all_connections()
  }

  pub fn outputs(&self) -> &[NodePort] {
    self.outputs.inner()
  }

  #[must_use]
  pub fn output_refs(&self) -> Vec<PortReference> {
    self.outputs.inner().iter().map(|c| c.port).collect()
  }

  #[must_use]
  pub fn find_output(&self, name: &str) -> Option<&NodePort> {
    self.outputs.find(name)
  }

  #[must_use]
  pub fn get_output(&self, index: PortIndex) -> Option<&NodePort> {
    self.outputs.get(index)
  }

  pub fn add_output<T: Into<String>>(&mut self, port: T) -> PortReference {
    match self.kind {
      NodeKind::Input(_) | NodeKind::Inherent(_) => {
        let port = port.into();
        self.inputs.add(port.clone(), self.index);
        self.outputs.add(port, self.index)
      }
      NodeKind::Output(_) => {
        // Input/Output nodes have the same ports in & out.
        panic!("You can not manually add outputs to {} nodes", self.kind);
      }
      NodeKind::External(_) => self.outputs.add(port, self.index),
    }
  }

  pub(crate) fn connect_output(&mut self, port: PortIndex, connection: ConnectionIndex) -> Result<(), Error> {
    self.outputs.add_connection(port, connection)
  }

  pub(crate) fn output_connections(&self, port: PortIndex) -> Option<&Vec<ConnectionIndex>> {
    self.outputs.port_connections(port)
  }

  pub(crate) fn all_downstreams(&self) -> Vec<ConnectionIndex> {
    self.outputs.all_connections()
  }
}

#[derive(Debug, Clone, PartialEq, Eq)]
struct PortList {
  direction: PortDirection,
  map: HashMap<String, PortIndex>,
  list: Vec<NodePort>,
}

impl PortList {
  fn new(direction: PortDirection) -> Self {
    Self {
      direction,
      map: Default::default(),
      list: Default::default(),
    }
  }
  fn find(&self, name: &str) -> Option<&NodePort> {
    self.map.get(name).map(|index| &self.list[*index])
  }

  fn get(&self, index: PortIndex) -> Option<&NodePort> {
    self.list.get(index)
  }

  fn inner(&self) -> &[NodePort] {
    &self.list
  }

  fn add<T: Into<String>>(&mut self, port_name: T, node_index: NodeIndex) -> PortReference {
    let name = port_name.into();
    let existing_index = self.map.get(&name);
    match existing_index {
      Some(index) => self.list[*index].port,
      None => {
        let index = self.list.len();
        let port_ref = PortReference::new(node_index, index, self.direction);
        self.map.insert(name.clone(), index);
        let port = NodePort::new(name, port_ref);
        self.list.push(port);
        port_ref
      }
    }
  }

  fn add_connection(&mut self, port: PortIndex, connection: ConnectionIndex) -> Result<(), Error> {
    let node_port = self.list.get_mut(port).ok_or(Error::InvalidPortIndex(port))?;

    if node_port.direction() == &PortDirection::In && !node_port.connections.is_empty() {
      return Err(Error::MultipleInputConnections(node_port.to_string()));
    }
    node_port.connections.push(connection);
    Ok(())
  }

  fn port_connections(&self, port: PortIndex) -> Option<&Vec<ConnectionIndex>> {
    self.list.get(port).map(|node| &node.connections)
  }

  fn all_connections(&self) -> Vec<ConnectionIndex> {
    self.list.iter().cloned().flat_map(|port| port.connections).collect()
  }
}

impl<DATA> std::fmt::Display for Node<DATA> {
  fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
    write!(f, "{}", self.name)
  }
}

#[derive(Debug, Clone, PartialEq, Eq)]
#[must_use]
pub struct NodePort {
  name: String,
  port: PortReference,
  connections: Vec<ConnectionIndex>,
}

impl NodePort {
  pub(crate) fn new<T: Into<String>>(name: T, port: PortReference) -> Self {
    Self {
      name: name.into(),
      port,
      connections: Default::default(),
    }
  }

  #[must_use]
  pub const fn is_graph_output(&self) -> bool {
    self.port.node_index == crate::schematic::SCHEMATIC_OUTPUT_INDEX
  }

  #[must_use]
  pub const fn is_graph_input(&self) -> bool {
    self.port.node_index == crate::schematic::SCHEMATIC_INPUT_INDEX
  }

  #[must_use]
  pub fn connections(&self) -> &[ConnectionIndex] {
    &self.connections
  }

  #[must_use]
  pub fn name(&self) -> &str {
    &self.name
  }

  #[must_use]
  pub const fn detached(&self) -> PortReference {
    self.port
  }

  pub const fn direction(&self) -> &PortDirection {
    self.port.direction()
  }
}

impl From<&NodePort> for PortReference {
  fn from(port: &NodePort) -> Self {
    port.port
  }
}

impl AsRef<PortReference> for NodePort {
  fn as_ref(&self) -> &PortReference {
    &self.port
  }
}

impl std::fmt::Display for NodePort {
  fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
    write!(f, "{}", self.name)
  }
}