flow-graph 0.23.0

A representation of a graph data structure for flow-based programming.
Documentation
mod connection;
mod node;
mod port;

pub use connection::*;
pub use node::*;
pub use port::*;

use crate::node::NodePort;
use crate::port::PortDirection;
use crate::{Connection, ConnectionIndex, PortReference, Schematic};

#[derive(Debug, Clone)]
#[non_exhaustive]
pub enum SchematicHop<'graph, DATA>
where
  DATA: Clone,
{
  Node(NodeHop<'graph, DATA>),
  Port(Port<'graph, DATA>),
  Ports(Ports<'graph, DATA>),
  Connections(Connections<'graph, DATA>),
  Connection(ConnectionRef<'graph, DATA>),
}

impl<'graph, DATA> std::fmt::Display for SchematicHop<'graph, DATA>
where
  DATA: Clone,
{
  fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
    write!(
      f,
      "{}",
      match self {
        SchematicHop::Node(v) => format!("Node:{}", v.name()),
        SchematicHop::Port(v) => format!("Port:{}", v),
        SchematicHop::Ports(v) => format!("Ports:{}", v),
        SchematicHop::Connections(v) => format!("Connections:{}", v),
        SchematicHop::Connection(v) => format!("Connection:{}", v),
      }
    )
  }
}

impl<'graph, DATA> From<NodeHop<'graph, DATA>> for SchematicHop<'graph, DATA>
where
  DATA: Clone,
{
  fn from(node: NodeHop<'graph, DATA>) -> Self {
    Self::Node(node)
  }
}

#[derive(Debug, Clone, Copy)]
#[must_use]
#[non_exhaustive]
pub enum WalkDirection {
  Up,
  Down,
}

#[derive(Debug)]
#[must_use]
pub struct SchematicWalker<'graph, DATA>
where
  DATA: Clone,
{
  schematic: &'graph Schematic<DATA>,
  last_hop: Option<SchematicHop<'graph, DATA>>,
  hop_queue: Vec<SchematicHop<'graph, DATA>>,
  direction: WalkDirection,
}

impl<'graph, DATA> SchematicWalker<'graph, DATA>
where
  DATA: Clone,
{
  pub fn new_from_input(schematic: &'graph Schematic<DATA>) -> Self {
    let inputs = NodeHop::new(schematic, schematic.input).into_inputs();
    let hop_queue = vec![SchematicHop::Ports(inputs)];
    Self {
      schematic,
      last_hop: None,
      hop_queue,
      direction: WalkDirection::Down,
    }
  }

  pub fn new_from_output(schematic: &'graph Schematic<DATA>) -> Self {
    let hop_queue = vec![SchematicHop::Node(NodeHop::new(schematic, schematic.output))];
    Self {
      schematic,
      last_hop: None,
      hop_queue,
      direction: WalkDirection::Up,
    }
  }

  pub fn from_port(schematic: &'graph Schematic<DATA>, port: PortReference, direction: WalkDirection) -> Self {
    let port = Port::new(schematic, port);
    let hop_queue = vec![SchematicHop::Port(port)];
    Self {
      schematic,
      last_hop: None,
      hop_queue,
      direction,
    }
  }
}

impl<'graph, DATA> Iterator for SchematicWalker<'graph, DATA>
where
  DATA: Clone,
{
  type Item = SchematicHop<'graph, DATA>;

  fn next(&mut self) -> Option<SchematicHop<'graph, DATA>> {
    let last_hop = self.last_hop.take();
    let (mut next_hop, branch) = walk(self.schematic, last_hop, self.direction);

    if let Some(branch) = branch {
      self.hop_queue.push(branch);
    }
    if next_hop.is_none() {
      next_hop = self.hop_queue.pop();
    }
    self.last_hop = next_hop.clone();
    next_hop
  }
}

#[allow(clippy::too_many_lines)]
fn walk<'graph, DATA>(
  schematic: &'graph Schematic<DATA>,
  hop: Option<SchematicHop<'graph, DATA>>,
  direction: WalkDirection,
) -> (Option<SchematicHop<'graph, DATA>>, Option<SchematicHop<'graph, DATA>>)
where
  DATA: Clone,
{
  match hop {
    Some(hop) => match hop {
      SchematicHop::Node(v) => {
        let ports = match direction {
          WalkDirection::Up => v.into_inputs(),
          WalkDirection::Down => v.into_outputs(),
        };
        if ports.is_empty() {
          (None, None)
        } else {
          (Some(SchematicHop::Ports(ports)), None)
        }
      }
      SchematicHop::Port(v) => match direction {
        WalkDirection::Down => match v.port.direction {
          PortDirection::In => {
            let node = NodeHop::new(schematic, v.port.node_index);
            (Some(node.into()), None)
          }
          PortDirection::Out => {
            let connections = schematic
              .get(v.port.node_index)
              .and_then(|node| node.output_connections(v.port.port_index))
              .map(|indices| Connections::new(schematic, indices.clone()))
              .unwrap();

            if connections.is_empty() {
              (None, None)
            } else {
              (Some(SchematicHop::Connections(connections)), None)
            }
          }
        },
        WalkDirection::Up => match v.port.direction {
          PortDirection::In => {
            let connections = schematic
              .get(v.port.node_index)
              .and_then(|node| node.input_connections(v.port.port_index))
              .map(|indices| Connections::new(schematic, indices.clone()))
              .unwrap();

            if connections.is_empty() {
              (None, None)
            } else {
              (Some(SchematicHop::Connections(connections)), None)
            }
          }
          PortDirection::Out => {
            let node = NodeHop::new(schematic, v.port.node_index);
            (Some(node.into()), None)
          }
        },
      },
      SchematicHop::Ports(mut v) => {
        if v.direction.is_none() {
          return (None, None);
        }
        let port_direction = v.direction.unwrap();
        match direction {
          WalkDirection::Up => match port_direction {
            PortDirection::In => {
              let result = v.next();
              let rest = if result.is_none() {
                None
              } else {
                Some(SchematicHop::Ports(v))
              };
              (result.map(SchematicHop::Port), rest)
            }
            PortDirection::Out => {
              let node = NodeHop::new(schematic, v.node_index);
              (Some(node.into()), None)
            }
          },
          WalkDirection::Down => match port_direction {
            PortDirection::In => {
              let node = NodeHop::new(schematic, v.node_index);
              (Some(node.into()), None)
            }
            PortDirection::Out => {
              let result = v.next();
              let rest = if result.is_none() {
                None
              } else {
                Some(SchematicHop::Ports(v))
              };
              (result.map(SchematicHop::Port), rest)
            }
          },
        }
      }
      SchematicHop::Connection(v) => {
        let connection = &schematic.connections[v.index];
        match direction {
          WalkDirection::Up => {
            let port = Port::new(schematic, connection.from);
            (Some(SchematicHop::Port(port)), None)
          }
          WalkDirection::Down => {
            let port = Port::new(schematic, connection.to);
            (Some(SchematicHop::Port(port)), None)
          }
        }
      }
      SchematicHop::Connections(mut v) => {
        let result = v.next();
        let rest = if result.is_none() {
          None
        } else {
          Some(SchematicHop::Connections(v))
        };

        (result.map(SchematicHop::Connection), rest)
      }
    },
    None => (None, None),
  }
}

fn get_ports_node<'graph, DATA>(schematic: &'graph Schematic<DATA>, port: &PortReference) -> &'graph NodePort
where
  DATA: Clone,
{
  match port.direction {
    PortDirection::In => &schematic.nodes[port.node_index].inputs()[port.port_index],
    PortDirection::Out => &schematic.nodes[port.node_index].outputs()[port.port_index],
  }
}

fn get_port_name<'graph, DATA>(schematic: &'graph Schematic<DATA>, port: &PortReference) -> &'graph str
where
  DATA: Clone,
{
  match port.direction {
    PortDirection::In => schematic.nodes[port.node_index].inputs()[port.port_index].name(),
    PortDirection::Out => schematic.nodes[port.node_index].outputs()[port.port_index].name(),
  }
}

fn get_port_connections<'graph, DATA>(
  schematic: &'graph Schematic<DATA>,
  port: &PortReference,
) -> Connections<'graph, DATA>
where
  DATA: Clone,
{
  let direction = port.direction;
  schematic
    .get(port.node_index)
    .and_then(|node| match direction {
      PortDirection::In => node.input_connections(port.port_index),
      PortDirection::Out => node.output_connections(port.port_index),
    })
    .map(|indices| Connections::new(schematic, indices.clone()))
    .unwrap()
}

fn get_connection<DATA>(schematic: &Schematic<DATA>, index: ConnectionIndex) -> &Connection<DATA> {
  &schematic.connections[index]
}