use std::collections::{BTreeSet, VecDeque};
use std::fmt;
use std::marker::PhantomData;
use std::sync::Arc;
use crate::exec::Pipeline;
use crate::node::{Node, NodeSpec};
use crate::schema::{InputSpec, OutputSpec, Props, SchemaId};
use crate::signal::{LogicBlock, MAX_CHANNELS};
use crate::time::TimeBase;
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub struct SourceId(pub(crate) usize);
impl SourceId {
pub const fn from_index(index: usize) -> Self {
Self(index)
}
pub const fn index(self) -> usize {
self.0
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub struct NodeId(pub(crate) usize);
impl NodeId {
pub const fn from_index(index: usize) -> Self {
Self(index)
}
pub const fn index(self) -> usize {
self.0
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub(crate) enum Owner {
Source(usize),
Node(usize),
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub(crate) struct PortKey {
pub(crate) owner: Owner,
pub(crate) index: usize,
}
pub struct OutputRef<T> {
key: PortKey,
_payload: PhantomData<fn() -> T>,
}
impl<T> OutputRef<T> {
pub fn of_node(node: NodeId, index: usize) -> Self {
Self {
key: PortKey {
owner: Owner::Node(node.0),
index,
},
_payload: PhantomData,
}
}
}
impl<T> Clone for OutputRef<T> {
fn clone(&self) -> Self {
*self
}
}
impl<T> Copy for OutputRef<T> {}
impl<T> fmt::Debug for OutputRef<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("OutputRef").field("key", &self.key).finish()
}
}
pub struct InputRef<T> {
node: usize,
index: usize,
_payload: PhantomData<fn() -> T>,
}
impl<T> InputRef<T> {
pub fn of_node(node: NodeId, index: usize) -> Self {
Self {
node: node.0,
index,
_payload: PhantomData,
}
}
}
impl<T> Clone for InputRef<T> {
fn clone(&self) -> Self {
*self
}
}
impl<T> Copy for InputRef<T> {}
impl<T> fmt::Debug for InputRef<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("InputRef")
.field("node", &self.node)
.field("index", &self.index)
.finish()
}
}
pub struct ExposedRef<T> {
pub(crate) index: usize,
_payload: PhantomData<fn() -> T>,
}
impl<T> Clone for ExposedRef<T> {
fn clone(&self) -> Self {
*self
}
}
impl<T> Copy for ExposedRef<T> {}
impl<T> fmt::Debug for ExposedRef<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("ExposedRef")
.field("index", &self.index)
.finish()
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub struct DynOutput {
key: PortKey,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub struct DynInput {
node: usize,
index: usize,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub struct DynExposedRef {
pub(crate) index: usize,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct SourceSpec {
pub name: String,
pub channels: u8,
pub time_base: TimeBase,
pub capacity: usize,
}
impl SourceSpec {
pub fn new(name: impl Into<String>, channels: u8, time_base: TimeBase) -> Self {
Self {
name: name.into(),
channels,
time_base,
capacity: 4,
}
}
pub fn with_capacity(mut self, capacity: usize) -> Self {
self.capacity = capacity.max(1);
self
}
pub(crate) fn output(&self) -> OutputSpec {
OutputSpec::new::<Arc<LogicBlock>>(
"samples",
Props::new()
.with("channels", i64::from(self.channels))
.with(
"rateHz",
i64::try_from(self.time_base.rate_hz).unwrap_or(i64::MAX),
),
)
}
}
#[derive(Clone, Copy, Debug)]
pub struct SourceHandle {
pub id: SourceId,
pub samples: OutputRef<Arc<LogicBlock>>,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct GraphConfig {
pub queue_capacity: usize,
pub budget: u32,
}
impl Default for GraphConfig {
fn default() -> Self {
Self {
queue_capacity: 256,
budget: 1024,
}
}
}
#[derive(Clone, Debug, PartialEq, Eq, thiserror::Error)]
pub enum GraphError {
#[error("no node {0} in the graph")]
UnknownNode(usize),
#[error("{owner} declares no {direction} port {index}")]
UnknownPort {
owner: String,
direction: &'static str,
index: usize,
},
#[error("source {name} has {channels} channels; at most {MAX_CHANNELS} are supported")]
TooManyChannels {
name: String,
channels: u8,
},
#[error("{from} carries {output} but {to} reads {input}")]
SchemaMismatch {
from: String,
to: String,
output: SchemaId,
input: SchemaId,
},
#[error("{to} requires {key} {requirement} of {from}, which declares {found}")]
Requirement {
from: String,
to: String,
key: &'static str,
requirement: String,
found: String,
},
#[error("{to} is already connected")]
AlreadyConnected {
to: String,
},
#[error("{to} is not connected")]
Unconnected {
to: String,
},
#[error("{node} is part of a cycle")]
Cycle {
node: String,
},
#[error("{node} reads from more than one time domain ({first} and {second})")]
TimeDomain {
node: String,
first: String,
second: String,
},
#[error("an output named {0} is already exposed")]
DuplicateExposure(String),
#[error("{owner} has no {direction} port named {name}")]
UnknownPortName {
owner: String,
direction: &'static str,
name: String,
},
}
struct NodeEntry {
node: Box<dyn Node>,
spec: NodeSpec,
inputs: Vec<Option<PortKey>>,
}
pub(crate) struct Exposure {
pub(crate) name: String,
pub(crate) from: PortKey,
}
pub struct Graph {
config: GraphConfig,
sources: Vec<SourceSpec>,
nodes: Vec<NodeEntry>,
exposed: Vec<Exposure>,
}
impl fmt::Debug for Graph {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Graph")
.field("config", &self.config)
.field("sources", &self.sources.len())
.field("nodes", &self.nodes.len())
.field("exposed", &self.exposed.len())
.finish()
}
}
impl Default for Graph {
fn default() -> Self {
Self::new()
}
}
impl Graph {
pub fn new() -> Self {
Self::with_config(GraphConfig::default())
}
pub fn with_config(config: GraphConfig) -> Self {
Self {
config,
sources: Vec::new(),
nodes: Vec::new(),
exposed: Vec::new(),
}
}
pub fn config(&self) -> GraphConfig {
self.config
}
pub fn source(&mut self, spec: SourceSpec) -> Result<SourceHandle, GraphError> {
if spec.channels > MAX_CHANNELS {
return Err(GraphError::TooManyChannels {
name: spec.name,
channels: spec.channels,
});
}
let id = SourceId(self.sources.len());
self.sources.push(spec);
Ok(SourceHandle {
id,
samples: OutputRef {
key: PortKey {
owner: Owner::Source(id.0),
index: 0,
},
_payload: PhantomData,
},
})
}
pub fn add(&mut self, node: impl Node) -> NodeId {
let spec = node.spec();
let inputs = vec![None; spec.inputs.len()];
self.nodes.push(NodeEntry {
node: Box::new(node),
spec,
inputs,
});
NodeId(self.nodes.len() - 1)
}
fn owner_name(&self, owner: Owner) -> String {
match owner {
Owner::Source(index) => {
format!(
"{}#{index}",
self.sources.get(index).map_or("?", |s| s.name.as_str())
)
}
Owner::Node(index) => {
format!(
"{}#{index}",
self.nodes.get(index).map_or("?", |n| n.spec.name.as_str())
)
}
}
}
fn output_spec(&self, key: PortKey) -> Result<OutputSpec, GraphError> {
let unknown = || GraphError::UnknownPort {
owner: self.owner_name(key.owner),
direction: "output",
index: key.index,
};
match key.owner {
Owner::Source(index) => {
let source = self.sources.get(index).ok_or_else(unknown)?;
if key.index != 0 {
return Err(unknown());
}
Ok(source.output())
}
Owner::Node(index) => {
let node = self
.nodes
.get(index)
.ok_or(GraphError::UnknownNode(index))?;
node.spec
.outputs
.get(key.index)
.cloned()
.ok_or_else(unknown)
}
}
}
fn input_spec(&self, node: usize, index: usize) -> Result<&InputSpec, GraphError> {
let entry = self.nodes.get(node).ok_or(GraphError::UnknownNode(node))?;
entry
.spec
.inputs
.get(index)
.ok_or_else(|| GraphError::UnknownPort {
owner: self.owner_name(Owner::Node(node)),
direction: "input",
index,
})
}
fn port_name(&self, owner: Owner, name: &str) -> String {
format!("{}.{name}", self.owner_name(owner))
}
pub fn connect<T>(&mut self, from: OutputRef<T>, to: InputRef<T>) -> Result<(), GraphError> {
self.connect_keys(from.key, to.node, to.index)
}
pub fn expose<T>(
&mut self,
name: impl Into<String>,
from: OutputRef<T>,
) -> Result<ExposedRef<T>, GraphError> {
let index = self.expose_key(name.into(), from.key)?;
Ok(ExposedRef {
index,
_payload: PhantomData,
})
}
pub fn source_output(&self, source: SourceId) -> Result<DynOutput, GraphError> {
if source.0 >= self.sources.len() {
return Err(GraphError::UnknownPort {
owner: self.owner_name(Owner::Source(source.0)),
direction: "output",
index: 0,
});
}
Ok(DynOutput {
key: PortKey {
owner: Owner::Source(source.0),
index: 0,
},
})
}
pub fn output(&self, node: NodeId, name: &str) -> Result<DynOutput, GraphError> {
let entry = self
.nodes
.get(node.0)
.ok_or(GraphError::UnknownNode(node.0))?;
let index = entry
.spec
.outputs
.iter()
.position(|output| output.name == name)
.ok_or_else(|| GraphError::UnknownPortName {
owner: self.owner_name(Owner::Node(node.0)),
direction: "output",
name: name.to_owned(),
})?;
Ok(DynOutput {
key: PortKey {
owner: Owner::Node(node.0),
index,
},
})
}
pub fn input(&self, node: NodeId, name: &str) -> Result<DynInput, GraphError> {
let entry = self
.nodes
.get(node.0)
.ok_or(GraphError::UnknownNode(node.0))?;
let index = entry
.spec
.inputs
.iter()
.position(|input| input.name == name)
.ok_or_else(|| GraphError::UnknownPortName {
owner: self.owner_name(Owner::Node(node.0)),
direction: "input",
name: name.to_owned(),
})?;
Ok(DynInput {
node: node.0,
index,
})
}
pub fn connect_dyn(&mut self, from: DynOutput, to: DynInput) -> Result<(), GraphError> {
self.connect_keys(from.key, to.node, to.index)
}
pub fn expose_dyn(
&mut self,
name: impl Into<String>,
from: DynOutput,
) -> Result<DynExposedRef, GraphError> {
Ok(DynExposedRef {
index: self.expose_key(name.into(), from.key)?,
})
}
pub fn schema_of(&self, output: DynOutput) -> Result<SchemaId, GraphError> {
self.output_spec(output.key).map(|spec| spec.schema)
}
pub fn props_of(&self, output: DynOutput) -> Result<Props, GraphError> {
self.output_spec(output.key).map(|spec| spec.props)
}
fn connect_keys(&mut self, from: PortKey, node: usize, index: usize) -> Result<(), GraphError> {
let output = self.output_spec(from)?;
let input = self.input_spec(node, index)?.clone();
let from_name = self.port_name(from.owner, output.name);
let to_name = self.port_name(Owner::Node(node), input.name);
if output.schema != input.schema {
return Err(GraphError::SchemaMismatch {
from: from_name,
to: to_name,
output: output.schema,
input: input.schema,
});
}
if let Some((key, requirement)) = input.requires.unmet(&output.props) {
let found = output
.props
.get(key)
.map_or_else(|| "nothing".to_owned(), |v| v.to_string());
let requirement = requirement.to_string();
return Err(GraphError::Requirement {
from: from_name,
to: to_name,
key,
requirement,
found,
});
}
let slot = &mut self.nodes[node].inputs[index];
if slot.is_some() {
return Err(GraphError::AlreadyConnected { to: to_name });
}
*slot = Some(from);
Ok(())
}
fn expose_key(&mut self, name: String, from: PortKey) -> Result<usize, GraphError> {
self.output_spec(from)?;
if self.exposed.iter().any(|exposure| exposure.name == name) {
return Err(GraphError::DuplicateExposure(name));
}
self.exposed.push(Exposure { name, from });
Ok(self.exposed.len() - 1)
}
fn order(&self) -> Result<Vec<usize>, GraphError> {
let mut pending: Vec<usize> = self
.nodes
.iter()
.map(|entry| {
entry
.inputs
.iter()
.filter(|input| {
matches!(
input,
Some(PortKey {
owner: Owner::Node(_),
..
})
)
})
.count()
})
.collect();
let mut ready: VecDeque<usize> = (0..self.nodes.len())
.filter(|&node| pending[node] == 0)
.collect();
let mut order = Vec::with_capacity(self.nodes.len());
while let Some(node) = ready.pop_front() {
order.push(node);
for (next, entry) in self.nodes.iter().enumerate() {
let reads = entry
.inputs
.iter()
.filter(|input| matches!(input, Some(PortKey { owner: Owner::Node(n), .. }) if *n == node))
.count();
if reads > 0 {
pending[next] -= reads;
if pending[next] == 0 {
ready.push_back(next);
}
}
}
}
if order.len() < self.nodes.len() {
let stuck = (0..self.nodes.len())
.find(|node| !order.contains(node))
.unwrap_or(0);
return Err(GraphError::Cycle {
node: self.owner_name(Owner::Node(stuck)),
});
}
Ok(order)
}
fn domains(&self, order: &[usize]) -> Result<Vec<Option<usize>>, GraphError> {
let mut domains: Vec<Option<usize>> = vec![None; self.nodes.len()];
for &node in order {
let mut domain: Option<usize> = None;
for input in self.nodes[node].inputs.iter().flatten() {
let read = match input.owner {
Owner::Source(source) => Some(source),
Owner::Node(upstream) => domains[upstream],
};
match (domain, read) {
(Some(first), Some(second)) if first != second => {
return Err(GraphError::TimeDomain {
node: self.owner_name(Owner::Node(node)),
first: self.owner_name(Owner::Source(first)),
second: self.owner_name(Owner::Source(second)),
});
}
(None, Some(read)) => domain = Some(read),
_ => {}
}
}
domains[node] = domain;
}
Ok(domains)
}
pub fn build(self) -> Result<Pipeline, GraphError> {
for (index, entry) in self.nodes.iter().enumerate() {
for (port, input) in entry.inputs.iter().enumerate() {
if input.is_none() {
return Err(GraphError::Unconnected {
to: self.port_name(Owner::Node(index), entry.spec.inputs[port].name),
});
}
}
}
let order = self.order()?;
let domains = self.domains(&order)?;
let input_props: Vec<Vec<Props>> = self
.nodes
.iter()
.map(|entry| {
entry
.inputs
.iter()
.flatten()
.map(|key| self.output_spec(*key).map(|spec| spec.props))
.collect::<Result<Vec<_>, _>>()
})
.collect::<Result<_, _>>()?;
let mut exposed_names = BTreeSet::new();
for exposure in &self.exposed {
if !exposed_names.insert(exposure.name.as_str()) {
return Err(GraphError::DuplicateExposure(exposure.name.clone()));
}
}
let Graph {
config,
sources,
nodes,
exposed,
} = self;
let (nodes, specs, inputs): (Vec<_>, Vec<_>, Vec<_>) = nodes
.into_iter()
.map(|entry| (entry.node, entry.spec, entry.inputs))
.fold(
(Vec::new(), Vec::new(), Vec::new()),
|mut acc, (node, spec, input)| {
acc.0.push(node);
acc.1.push(spec);
acc.2.push(input);
acc
},
);
Pipeline::start(
config,
sources,
nodes,
specs,
inputs,
order,
domains,
input_props,
exposed,
)
}
}