logic_mesh/base/program/
mod.rs

1// Copyright (c) 2022-2023, Radu Racariu.
2
3//! Module for defining the program
4//! that would be executed by the engine.
5
6pub mod data;
7
8use anyhow::Result;
9use serde::{Deserialize, Serialize};
10
11use crate::blocks::InputImpl;
12
13use self::data::{BlockData, LinkData, ProgramMeta};
14
15use super::{engine::Engine, input::InputProps};
16
17type Reader = <InputImpl as InputProps>::Reader;
18type Writer = <InputImpl as InputProps>::Writer;
19
20pub trait EngineType = Engine<Reader = Reader, Writer = Writer> + Default;
21
22#[derive(Debug, Default, Clone, Serialize, Deserialize)]
23pub struct Program<E: EngineType> {
24    pub meta: ProgramMeta,
25    pub blocks: Vec<BlockData>,
26    pub links: Vec<LinkData>,
27    pub engine: E,
28}
29
30impl<E: EngineType> Program<E> {
31    pub fn new(name: &str, blocks: Vec<BlockData>, links: Vec<LinkData>) -> Self {
32        Self {
33            meta: ProgramMeta {
34                name: name.to_string(),
35                ..Default::default()
36            },
37            blocks,
38            links,
39            engine: E::default(),
40        }
41    }
42
43    pub fn load(&mut self) -> Result<()> {
44        self.engine.load_blocks_and_links(&self.blocks, &self.links)
45    }
46
47    pub async fn run(&mut self) {
48        self.engine.run().await
49    }
50}
51
52#[cfg(test)]
53mod test {
54    use crate::single_threaded::SingleThreadedEngine;
55
56    use super::{
57        data::{BlockData, LinkData},
58        Program,
59    };
60
61    #[test]
62    fn test_program_load() {
63        let mut program = Program::<SingleThreadedEngine>::new(
64            "test",
65            vec![
66                BlockData {
67                    id: "00000000-0000-0000-0000-000000000000".to_string(),
68                    name: "Add".to_string(),
69                    dis: "Add".to_string(),
70                    lib: "core".to_string(),
71                    category: "maths".to_string(),
72                    ver: "0.1.0".to_string(),
73                },
74                BlockData {
75                    id: "00000000-0000-0000-0000-000000000001".to_string(),
76                    name: "Add".to_string(),
77                    dis: "Add".to_string(),
78                    lib: "core".to_string(),
79                    category: "maths".to_string(),
80                    ver: "0.1.0".to_string(),
81                },
82            ],
83            vec![LinkData {
84                id: None,
85                source_block_uuid: "00000000-0000-0000-0000-000000000000".to_string(),
86                target_block_uuid: "00000000-0000-0000-0000-000000000001".to_string(),
87                source_block_pin_name: "out".to_string(),
88                target_block_pin_name: "in1".to_string(),
89            }],
90        );
91
92        assert!(program.load().is_ok());
93
94        assert!(program.engine.blocks().iter().all(|b| b.name() == "Add"));
95
96        assert!(program.engine.blocks()[0]
97            .get_output("out")
98            .unwrap()
99            .is_connected());
100
101        assert!(
102            program.engine.blocks()[0]
103                .get_output("out")
104                .unwrap()
105                .links()
106                .len()
107                == 1
108        );
109
110        assert!(program.engine.blocks()[0]
111            .get_output("out")
112            .unwrap()
113            .links()
114            .iter()
115            .any(
116                |l| l.target_block_id().to_string() == "00000000-0000-0000-0000-000000000001"
117                    && l.target_input() == "in1"
118            ));
119    }
120
121    #[test]
122    fn test_program_load_invalid_block() {
123        let mut program = Program::<SingleThreadedEngine>::new(
124            "test",
125            vec![BlockData {
126                id: "00000000-0000-0000-0000-000000000000".to_string(),
127                name: "Missing".to_string(),
128                dis: "Missing".to_string(),
129                lib: "test".to_string(),
130                category: "maths".to_string(),
131                ver: "0.1.0".to_string(),
132            }],
133            vec![],
134        );
135
136        assert!(program.load().is_err());
137    }
138
139    #[test]
140    fn test_program_load_invalid_link() {
141        let blocks = vec![
142            BlockData {
143                id: "00000000-0000-0000-0000-000000000000".to_string(),
144                name: "Add".to_string(),
145                dis: "Add".to_string(),
146                lib: "test".to_string(),
147                category: "maths".to_string(),
148                ver: "0.1.0".to_string(),
149            },
150            BlockData {
151                id: "00000000-0000-0000-0000-000000000001".to_string(),
152                name: "Add".to_string(),
153                dis: "Add".to_string(),
154                lib: "test".to_string(),
155                category: "maths".to_string(),
156                ver: "0.1.0".to_string(),
157            },
158        ];
159
160        // Invalid source block
161        let mut program = Program::<SingleThreadedEngine>::new(
162            "test",
163            blocks.clone(),
164            vec![LinkData {
165                id: None,
166                source_block_uuid: "00000000-0000-0000-0000-000000000009".to_string(),
167                target_block_uuid: "00000000-0000-0000-0000-000000000001".to_string(),
168                source_block_pin_name: "out".to_string(),
169                target_block_pin_name: "in1".to_string(),
170            }],
171        );
172
173        assert!(program.load().is_err());
174
175        // Invalid source block pin
176        let mut program = Program::<SingleThreadedEngine>::new(
177            "test",
178            blocks.clone(),
179            vec![LinkData {
180                id: None,
181                source_block_uuid: "00000000-0000-0000-0000-000000000000".to_string(),
182                target_block_uuid: "00000000-0000-0000-0000-000000000001".to_string(),
183                source_block_pin_name: "missing".to_string(),
184                target_block_pin_name: "in1".to_string(),
185            }],
186        );
187
188        assert!(program.load().is_err());
189
190        // Invalid target input
191        let mut program = Program::<SingleThreadedEngine>::new(
192            "test",
193            blocks.clone(),
194            vec![LinkData {
195                id: None,
196                source_block_uuid: "00000000-0000-0000-0000-000000000000".to_string(),
197                target_block_uuid: "00000000-0000-0000-0000-000000000001".to_string(),
198                source_block_pin_name: "out".to_string(),
199                target_block_pin_name: "missing".to_string(),
200            }],
201        );
202
203        assert!(program.load().is_err());
204    }
205}