logic_mesh/base/block/
connect.rs

1// Copyright (c) 2022-2023, Radu Racariu.
2
3//!
4//! Defines the block connection trait
5//!
6
7use uuid::Uuid;
8
9use super::desc::BlockStaticDesc;
10use super::{Block, BlockProps};
11use crate::base::input::InputProps;
12use crate::base::link::{BaseLink, Link, LinkState};
13use crate::base::output::Output;
14
15/// Block connection functions
16pub trait BlockConnect: BlockStaticDesc {
17    /// Connect a block output to the given input
18    ///
19    /// # Arguments
20    /// - output_name: The name of the output to be connected
21    /// - input: The block input to be connected
22    ///
23    fn connect_output(
24        &mut self,
25        output_name: &str,
26        target_input: &mut dyn InputProps<Reader = Self::Reader, Writer = Self::Writer>,
27    ) -> Result<Uuid, &'static str>;
28
29    /// Connect a block input to another's block input
30    ///
31    /// # Arguments
32    /// - input_name: The name of the output to be connected
33    /// - input: The block input to be connected
34    ///
35    fn connect_input(
36        &mut self,
37        input_name: &str,
38        target_input: &mut dyn InputProps<Reader = Self::Reader, Writer = Self::Writer>,
39    ) -> Result<Uuid, &'static str>;
40
41    /// Disconnect a block output from the given input
42    /// # Arguments
43    /// - source_output_name: The name of the source output to be disconnected
44    /// - target_input: The target input to be disconnected
45    ///
46    fn disconnect_output(
47        &mut self,
48        source_output_name: &str,
49        target_input: &mut dyn InputProps<Reader = Self::Reader, Writer = Self::Writer>,
50    ) -> Result<(), &'static str>;
51
52    /// Disconnect a block input from the given output
53    /// # Arguments
54    /// - source_input_name: The name of the source input to be disconnected
55    /// - target_input: The target input to be disconnected
56    fn disconnect_input(
57        &mut self,
58        source_input_name: &str,
59        target_input: &mut dyn InputProps<Reader = Self::Reader, Writer = Self::Writer>,
60    ) -> Result<(), &'static str>;
61}
62
63///
64/// Implements the `BlockConnect` trait for all types
65/// that are `Block`s
66///
67impl<T: Block + ?Sized> BlockConnect for T {
68    fn connect_output(
69        &mut self,
70        source_output_name: &str,
71        target_input: &mut dyn InputProps<Reader = Self::Reader, Writer = Self::Writer>,
72    ) -> Result<Uuid, &'static str> {
73        let mut outputs = self.outputs_mut();
74        let source_output = if let Some(out) = outputs
75            .iter_mut()
76            .find(|output| output.desc().name == source_output_name)
77        {
78            out
79        } else {
80            return Err("Output not found");
81        };
82
83        connect_output(*source_output, target_input)
84    }
85
86    fn connect_input(
87        &mut self,
88        source_input_name: &str,
89        target_input: &mut dyn InputProps<Reader = Self::Reader, Writer = Self::Writer>,
90    ) -> Result<Uuid, &'static str> {
91        let mut inputs = self.inputs_mut();
92        let source_input = if let Some(input) = inputs
93            .iter_mut()
94            .find(|input| input.name() == source_input_name)
95        {
96            *input as &mut dyn InputProps<Reader = Self::Reader, Writer = Self::Writer>
97        } else {
98            return Err("Input not found");
99        };
100        connect_input(source_input, target_input)
101    }
102
103    fn disconnect_output(
104        &mut self,
105        source_output_name: &str,
106        target_input: &mut dyn InputProps<Reader = Self::Reader, Writer = Self::Writer>,
107    ) -> Result<(), &'static str> {
108        let mut outputs = self.outputs_mut();
109        let source_output = if let Some(out) = outputs
110            .iter_mut()
111            .find(|output| output.desc().name == source_output_name)
112        {
113            out
114        } else {
115            return Err("Output not found");
116        };
117
118        disconnect_output(*source_output, target_input)
119    }
120
121    fn disconnect_input(
122        &mut self,
123        source_input_name: &str,
124        target_input: &mut dyn InputProps<Reader = Self::Reader, Writer = Self::Writer>,
125    ) -> Result<(), &'static str> {
126        let mut inputs = self.inputs_mut();
127        let source_input = if let Some(in_) = inputs
128            .iter_mut()
129            .find(|input| input.name() == source_input_name)
130        {
131            in_
132        } else {
133            return Err("Input not found");
134        };
135
136        let link_id = link_id_for_input(source_input.links(), target_input);
137
138        if let Some(id) = link_id {
139            source_input.remove_link_by_id(&id);
140            target_input.decrement_conn();
141            Ok(())
142        } else {
143            Err("No connection found")
144        }
145    }
146}
147
148/// Connect a block output to the given input
149/// # Arguments
150/// - source_output: The output to be connected
151/// - target_input: The block input to be connected
152pub fn connect_output<Reader, Writer: Clone>(
153    source_output: &mut dyn Output<Writer = Writer>,
154    target_input: &mut dyn InputProps<Reader = Reader, Writer = Writer>,
155) -> Result<Uuid, &'static str> {
156    // Connections should be unique.
157    if source_output.links().iter().any(|link| {
158        link.target_block_id() == target_input.block_id()
159            && link.target_input() == target_input.name()
160    }) {
161        return Err("Already connected");
162    }
163
164    let mut link = BaseLink::new(*target_input.block_id(), target_input.name().to_string());
165    let id = link.id;
166
167    link.tx = Some(target_input.writer().clone());
168
169    link.state = LinkState::Connected;
170
171    source_output.add_link(link);
172    target_input.increment_conn();
173
174    Ok(id)
175}
176
177/// Disconnect a block output from the given input
178/// # Arguments
179/// - source_output: The output to be disconnected
180/// - target_input: The block input to be disconnected
181///
182/// # Returns
183/// - `Ok(())` if the disconnection was successful, `Err` otherwise
184pub fn disconnect_output<Reader, Writer: Clone>(
185    source_output: &mut dyn Output<Writer = Writer>,
186    target_input: &mut dyn InputProps<Reader = Reader, Writer = Writer>,
187) -> Result<(), &'static str> {
188    let link_id = link_id_for_input(source_output.links(), target_input);
189
190    if let Some(id) = link_id {
191        source_output.remove_link_by_id(&id);
192        target_input.decrement_conn();
193        Ok(())
194    } else {
195        Err("No connection found")
196    }
197}
198
199/// Connect a block input to another's block input
200/// # Arguments
201/// - source_input: The input to be connected to
202/// - target_input: The block input to be connected
203pub fn connect_input<Reader, Writer: Clone>(
204    source_input: &mut dyn InputProps<Reader = Reader, Writer = Writer>,
205    target_input: &mut dyn InputProps<Reader = Reader, Writer = Writer>,
206) -> Result<Uuid, &'static str> {
207    if source_input.block_id() == target_input.block_id() {
208        return Err("Cannot connect to the same block");
209    }
210
211    if source_input.links().iter().any(|link| {
212        link.target_block_id() == target_input.block_id()
213            && link.target_input() == target_input.name()
214    }) {
215        return Err("Already connected");
216    }
217
218    let mut link = BaseLink::new(*target_input.block_id(), target_input.name().to_string());
219    let id = link.id;
220
221    link.tx = Some(target_input.writer().clone());
222
223    link.state = LinkState::Connected;
224
225    source_input.add_link(link);
226    target_input.increment_conn();
227
228    Ok(id)
229}
230
231/// Disconnect a block input from another's block input
232/// # Arguments
233/// - source_input: The input to be disconnected from
234/// - target_input: The block input to be disconnected
235/// # Returns
236/// - `Ok(())` if the disconnection was successful, `Err` otherwise
237pub fn disconnect_input<I: InputProps + ?Sized>(
238    source_input: &mut I,
239    target_input: &mut I,
240) -> Result<(), &'static str> {
241    let link_id = link_id_for_input(source_input.links(), target_input);
242
243    if let Some(id) = link_id {
244        source_input.remove_link_by_id(&id);
245        target_input.decrement_conn();
246        Ok(())
247    } else {
248        Err("No connection found")
249    }
250}
251
252/// Disconnect all the inputs and outputs of a block
253/// # Arguments
254/// - block: The block to be disconnected
255/// - decrement_target_input: A function that decrements the number of connections of a block input
256pub fn disconnect_block<B, F>(block: &mut B, mut decrement_target_input: F)
257where
258    B: BlockProps + ?Sized,
259    F: FnMut(&Uuid, &str) -> Option<usize>,
260{
261    block
262        .outputs_mut()
263        .iter()
264        .filter(|output| output.is_connected())
265        .for_each(|out| {
266            out.links().iter_mut().for_each(|link| {
267                decrement_target_input(link.target_block_id(), link.target_input());
268            });
269        });
270
271    block
272        .inputs_mut()
273        .iter()
274        .filter(|input| input.has_output())
275        .for_each(|src_input| {
276            src_input.links().iter_mut().for_each(|link| {
277                decrement_target_input(link.target_block_id(), link.target_input());
278            });
279        });
280
281    block.remove_all_links();
282}
283
284/// Disconnect a link from a block
285/// # Arguments
286/// - block: The block to be disconnected
287/// - link_id: The id of the link to be disconnected
288/// - decrement_target_input: A function that decrements the number of connections of a block input
289pub fn disconnect_link<B, F>(block: &mut B, link_id: &Uuid, mut decrement_target_input: F) -> bool
290where
291    B: BlockProps + ?Sized,
292    F: FnMut(&Uuid, &str) -> Option<usize>,
293{
294    if !block
295        .outputs_mut()
296        .iter()
297        .filter(|output| output.is_connected())
298        .any(|out| {
299            out.links().iter_mut().any(|link| {
300                if link.id() == link_id {
301                    decrement_target_input(link.target_block_id(), link.target_input());
302                    true
303                } else {
304                    false
305                }
306            })
307        })
308        && !block
309            .inputs_mut()
310            .iter()
311            .filter(|input| input.has_output())
312            .any(|src_input| {
313                src_input.links().iter_mut().any(|link| {
314                    if link.id() == link_id {
315                        decrement_target_input(link.target_block_id(), link.target_input());
316                        true
317                    } else {
318                        false
319                    }
320                })
321            })
322    {
323        return false;
324    }
325
326    block.remove_link_by_id(link_id);
327    true
328}
329
330fn link_id_for_input<I: InputProps + ?Sized>(
331    links: Vec<&dyn Link>,
332    target_input: &I,
333) -> Option<Uuid> {
334    let link_id = links
335        .iter()
336        .find(|link| {
337            link.target_input() == target_input.name()
338                && link.target_block_id() == target_input.block_id()
339        })
340        .map(|link| *link.id());
341    link_id
342}
343
344#[cfg(test)]
345mod test {
346
347    use uuid::Uuid;
348
349    use crate::base::{
350        block::{connect::disconnect_block, Block, BlockDesc, BlockProps, BlockState},
351        input::{Input, InputProps},
352        output::Output,
353    };
354
355    use super::BlockConnect;
356
357    use crate::base::block::test_utils::mock::{InputImpl, OutputImpl};
358
359    use libhaystack::val::kind::HaystackKind;
360
361    #[block]
362    #[derive(BlockProps, Debug)]
363    #[category = "test"]
364    struct Block1 {
365        #[input(kind = "Number")]
366        input1: InputImpl,
367        #[output(kind = "Number")]
368        out: OutputImpl,
369    }
370    impl Block for Block1 {
371        async fn execute(&mut self) {
372            todo!()
373        }
374    }
375
376    #[block]
377    #[derive(BlockProps, Debug)]
378    #[category = "test"]
379    struct Block2 {
380        #[input(kind = "Number")]
381        input1: InputImpl,
382        #[output(kind = "Number")]
383        out: OutputImpl,
384    }
385    impl Block for Block2 {
386        async fn execute(&mut self) {
387            todo!()
388        }
389    }
390
391    #[test]
392    fn test_block_out_links() {
393        let mut block1 = Block1::new();
394        let mut block2 = Block2::new();
395
396        assert_eq!(block1.name(), "Block1");
397        assert_eq!(block2.name(), "Block2");
398
399        let input = &mut block2.inputs_mut()[0];
400        block1
401            .connect_output("out", *input)
402            .expect("Could not connect");
403
404        assert!(input.is_connected());
405        assert_eq!(block1.outputs()[0].links().len(), 1);
406
407        assert!(
408            block1.connect_output("out", *input).is_err(),
409            "Should not be able to connect twice"
410        );
411
412        assert!(
413            block1.connect_output("invalid out", *input).is_err(),
414            "Should not be able to connect to invalid output"
415        );
416
417        block1
418            .disconnect_output("out", *input)
419            .expect("Could not disconnect");
420
421        assert!(!input.is_connected());
422        assert_eq!(block1.outputs()[0].links().len(), 0);
423    }
424
425    #[test]
426    fn test_block_input_links() {
427        let mut block1 = Block1::new();
428        let mut block2 = Block2::new();
429
430        let input2 = &mut block2.inputs_mut()[0];
431
432        block1
433            .connect_input("input1", *input2)
434            .expect("Could not connect");
435
436        assert!(
437            block1.connect_input("input1", *input2).is_err(),
438            "Should not be able to connect twice"
439        );
440        assert!(
441            block1.connect_input("invalid input", *input2).is_err(),
442            "Should not be able to connect to invalid input"
443        );
444
445        assert!(block1.input1.has_output());
446        assert!(input2.is_connected());
447        assert_eq!(block1.input1.links().len(), 1);
448        assert_eq!(input2.links().len(), 0);
449
450        block1
451            .disconnect_input("input1", *input2)
452            .expect("Could not disconnect");
453
454        assert!(!block1.input1.is_connected());
455        assert_eq!(block1.outputs()[0].links().len(), 0);
456    }
457
458    #[test]
459    fn test_block_disconnect() {
460        let mut block1 = Block1::new();
461        let mut block2 = Block2::new();
462        {
463            let input2 = &mut block2.inputs_mut()[0];
464
465            block1
466                .connect_input("input1", *input2)
467                .expect("Could not connect");
468
469            assert!(
470                block1.connect_input("input1", *input2).is_err(),
471                "Should not be able to connect twice"
472            );
473            assert!(
474                block1.connect_input("invalid input", *input2).is_err(),
475                "Should not be able to connect to invalid input"
476            );
477
478            assert!(input2.is_connected());
479            assert!(block1.input1.links().len() == 1);
480        }
481
482        let gid = block2.id().clone();
483        let input1 = &mut block2.input1;
484
485        disconnect_block(&mut block1, |id, name| {
486            assert!(*id == gid);
487            assert!(name == "input1");
488            Some(input1.decrement_conn())
489        });
490
491        assert!(!input1.is_connected());
492        assert!(block1.input1.links().len() == 0);
493    }
494}