use super::{
BlockDefinition, InputPortId, Inputs, OutputPortId, Outputs, PortId, SystemDefinition,
};
use alloc::{collections::BTreeSet, rc::Rc, vec::Vec};
use core::fmt::Debug;
use thiserror::Error;
#[derive(Clone, Debug, Error)]
pub enum SystemBuildError {
#[error("unregistered input port ID: {0}")]
UnregisteredInput(InputPortId),
#[error("unregistered output port ID: {0}")]
UnregisteredOutput(OutputPortId),
#[error("already connected output port ID: {0}")]
AlreadyConnectedOutput(OutputPortId),
}
#[derive(Clone, Default)]
pub struct SystemBuilder {
system: SystemDefinition,
registered_inputs: BTreeSet<InputPortId>,
registered_outputs: BTreeSet<OutputPortId>,
connected_outputs: BTreeSet<OutputPortId>,
}
impl SystemBuilder {
pub fn new() -> Self {
Self::default()
}
pub fn register<T: BlockDefinition + 'static>(&mut self, block: T) -> Rc<T> {
let block: Rc<T> = Rc::new(block);
self.system.push_block(&block);
for input in block.inputs() {
self.register_input(input);
}
for output in block.outputs() {
self.register_output(output);
}
block
}
pub fn register_port(&mut self, input: impl Into<PortId>) {
match input.into() {
PortId::Input(input) => self.register_input(input),
PortId::Output(output) => self.register_output(output),
}
}
pub fn register_input(&mut self, input: impl Into<InputPortId>) {
let input = input.into();
self.registered_inputs.insert(input);
}
pub fn register_output(&mut self, output: impl Into<OutputPortId>) {
let output = output.into();
self.registered_outputs.insert(output);
}
pub fn export(&mut self, input: impl Into<PortId>) -> Result<PortId, SystemBuildError> {
self.export_port(input)
}
pub fn export_port(&mut self, input: impl Into<PortId>) -> Result<PortId, SystemBuildError> {
let input = input.into();
match input.into() {
PortId::Input(input) => self.export_input(input).map(|_| ()),
PortId::Output(output) => self.export_output(output).map(|_| ()),
}?;
Ok(input)
}
pub fn export_input(
&mut self,
input: impl Into<InputPortId>,
) -> Result<InputPortId, SystemBuildError> {
let input = input.into();
if !self.registered_inputs.contains(&input) {
return Err(SystemBuildError::UnregisteredInput(input));
}
self.system.inputs.insert(input);
Ok(input)
}
pub fn export_output(
&mut self,
output: impl Into<OutputPortId>,
) -> Result<OutputPortId, SystemBuildError> {
let output = output.into();
if !self.registered_outputs.contains(&output) {
return Err(SystemBuildError::UnregisteredOutput(output));
}
self.system.outputs.insert(output);
Ok(output)
}
pub fn connect<T>(
&mut self,
output: &Outputs<T>,
input: &Inputs<T>,
) -> Result<bool, SystemBuildError> {
self.connect_ids(output.id(), input.id())
}
pub(crate) fn connect_ids(
&mut self,
output: impl Into<OutputPortId>,
input: impl Into<InputPortId>,
) -> Result<bool, SystemBuildError> {
let output = output.into();
let input = input.into();
if !self.registered_inputs.contains(&input) {
return Err(SystemBuildError::UnregisteredInput(input));
}
if !self.registered_outputs.contains(&output) {
return Err(SystemBuildError::UnregisteredOutput(output));
}
if self.connected_outputs.contains(&output) {
return Err(SystemBuildError::AlreadyConnectedOutput(output));
}
let result = self.system.connections.insert((output, input));
if result {
self.connected_outputs.insert(output);
}
Ok(result)
}
pub fn build(self) -> SystemDefinition {
self.system
}
}
impl Debug for SystemBuilder {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("SystemBuilder")
.field(
"registered_inputs",
&self
.registered_inputs
.iter()
.map(|id| id.0)
.collect::<Vec<_>>(),
)
.field(
"registered_outputs",
&self
.registered_outputs
.iter()
.map(|id| id.0)
.collect::<Vec<_>>(),
)
.field(
"connected_outputs",
&self
.connected_outputs
.iter()
.map(|id| id.0)
.collect::<Vec<_>>(),
)
.field("system", &self.system)
.finish()
}
}