Skip to main content

async_flow/model/
system_builder.rs

1// This is free and unencumbered software released into the public domain.
2
3use super::{
4    BlockDefinition, InputPortId, Inputs, OutputPortId, Outputs, PortId, SystemDefinition,
5};
6use alloc::{collections::BTreeSet, rc::Rc, vec::Vec};
7use core::fmt::Debug;
8use thiserror::Error;
9
10#[derive(Clone, Debug, Error)]
11pub enum SystemBuildError {
12    #[error("unregistered input port ID: {0}")]
13    UnregisteredInput(InputPortId),
14
15    #[error("unregistered output port ID: {0}")]
16    UnregisteredOutput(OutputPortId),
17
18    #[error("already connected output port ID: {0}")]
19    AlreadyConnectedOutput(OutputPortId),
20}
21
22/// A builder for system definitions.
23///
24/// # Examples
25///
26/// ```
27/// use async_flow::model::SystemBuilder;
28///
29/// let mut builder = SystemBuilder::new();
30/// //let block = builder.register(MyBlock::new());
31/// let system = builder.build();
32/// ```
33#[derive(Clone, Default)]
34pub struct SystemBuilder {
35    system: SystemDefinition,
36    registered_inputs: BTreeSet<InputPortId>,
37    registered_outputs: BTreeSet<OutputPortId>,
38    connected_outputs: BTreeSet<OutputPortId>,
39}
40
41impl SystemBuilder {
42    /// Creates a new system builder.
43    pub fn new() -> Self {
44        Self::default()
45    }
46
47    /// Registers an instantiated block with the system under construction.
48    pub fn register<T: BlockDefinition + 'static>(&mut self, block: T) -> Rc<T> {
49        let block: Rc<T> = Rc::new(block);
50        self.system.push_block(&block);
51
52        for input in block.inputs() {
53            self.register_input(input);
54        }
55        for output in block.outputs() {
56            self.register_output(output);
57        }
58
59        block
60    }
61
62    /// Registers an input or output port with the system under construction.
63    pub fn register_port(&mut self, input: impl Into<PortId>) {
64        match input.into() {
65            PortId::Input(input) => self.register_input(input),
66            PortId::Output(output) => self.register_output(output),
67        }
68    }
69
70    /// Registers an input port with the system under construction.
71    pub fn register_input(&mut self, input: impl Into<InputPortId>) {
72        let input = input.into();
73        self.registered_inputs.insert(input);
74    }
75
76    /// Registers an output port with the system under construction.
77    pub fn register_output(&mut self, output: impl Into<OutputPortId>) {
78        let output = output.into();
79        self.registered_outputs.insert(output);
80    }
81
82    /// Exports an input or output port registered with the system under
83    /// construction.
84    pub fn export(&mut self, input: impl Into<PortId>) -> Result<PortId, SystemBuildError> {
85        self.export_port(input)
86    }
87
88    /// Exports an input or output port registered with the system under
89    /// construction.
90    pub fn export_port(&mut self, input: impl Into<PortId>) -> Result<PortId, SystemBuildError> {
91        let input = input.into();
92        match input.into() {
93            PortId::Input(input) => self.export_input(input).map(|_| ()),
94            PortId::Output(output) => self.export_output(output).map(|_| ()),
95        }?;
96        Ok(input)
97    }
98
99    /// Exports an input port registered with the system under construction.
100    pub fn export_input(
101        &mut self,
102        input: impl Into<InputPortId>,
103    ) -> Result<InputPortId, SystemBuildError> {
104        let input = input.into();
105        if !self.registered_inputs.contains(&input) {
106            return Err(SystemBuildError::UnregisteredInput(input));
107        }
108        self.system.inputs.insert(input);
109        Ok(input)
110    }
111
112    /// Exports an output port registered with the system under construction.
113    pub fn export_output(
114        &mut self,
115        output: impl Into<OutputPortId>,
116    ) -> Result<OutputPortId, SystemBuildError> {
117        let output = output.into();
118        if !self.registered_outputs.contains(&output) {
119            return Err(SystemBuildError::UnregisteredOutput(output));
120        }
121        self.system.outputs.insert(output);
122        Ok(output)
123    }
124
125    /// Connects an output port to an input port of the same type.
126    ///
127    /// Returns a boolean indicating whether the connection was newly
128    /// inserted or already existed.
129    pub fn connect<T>(
130        &mut self,
131        output: &Outputs<T>,
132        input: &Inputs<T>,
133    ) -> Result<bool, SystemBuildError> {
134        self.connect_ids(output.id(), input.id())
135    }
136
137    /// Connects an output port ID to an input port ID.
138    /// This isn't public because it doesn't enforce type safety.
139    ///
140    /// Returns a boolean indicating whether the connection was newly
141    /// inserted or already existed.
142    pub(crate) fn connect_ids(
143        &mut self,
144        output: impl Into<OutputPortId>,
145        input: impl Into<InputPortId>,
146    ) -> Result<bool, SystemBuildError> {
147        let output = output.into();
148        let input = input.into();
149        if !self.registered_inputs.contains(&input) {
150            return Err(SystemBuildError::UnregisteredInput(input));
151        }
152        if !self.registered_outputs.contains(&output) {
153            return Err(SystemBuildError::UnregisteredOutput(output));
154        }
155        if self.connected_outputs.contains(&output) {
156            return Err(SystemBuildError::AlreadyConnectedOutput(output));
157        }
158        let result = self.system.connections.insert((output, input));
159        if result {
160            // Output ports can only be connected once:
161            self.connected_outputs.insert(output);
162        }
163        Ok(result)
164    }
165
166    /// Builds the system under construction.
167    pub fn build(self) -> SystemDefinition {
168        self.system
169    }
170}
171
172impl Debug for SystemBuilder {
173    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
174        f.debug_struct("SystemBuilder")
175            .field(
176                "registered_inputs",
177                &self
178                    .registered_inputs
179                    .iter()
180                    .map(|id| id.0)
181                    .collect::<Vec<_>>(),
182            )
183            .field(
184                "registered_outputs",
185                &self
186                    .registered_outputs
187                    .iter()
188                    .map(|id| id.0)
189                    .collect::<Vec<_>>(),
190            )
191            .field(
192                "connected_outputs",
193                &self
194                    .connected_outputs
195                    .iter()
196                    .map(|id| id.0)
197                    .collect::<Vec<_>>(),
198            )
199            .field("system", &self.system)
200            .finish()
201    }
202}