logic_mesh/blocks/
registry.rs

1// Copyright (c) 2022-2024, Radu Racariu.
2
3//!
4//! Defines the block registry.
5//!
6
7use crate::base::block::{BlockDesc, BlockProps, BlockStaticDesc};
8use crate::base::input::input_reader::InputReader;
9use crate::base::input::InputProps;
10use libhaystack::val::Value;
11
12use crate::base::engine::Engine;
13use crate::blocks::bitwise::{BitwiseAnd, BitwiseNot, BitwiseOr, BitwiseXor};
14use crate::blocks::collections::{Dict, GetElement, Keys, Length, List, Values};
15use crate::blocks::control::{Pid, PriorityArray};
16use crate::blocks::logic::{
17    And, Equal, GreaterThan, GreaterThanEq, Latch, LessThan, LessThanEq, Not, NotEqual, Or, Xor,
18};
19use crate::blocks::math::{Abs, Add, ArcTan, Cos, Sub};
20use crate::blocks::math::{
21    ArcCos, ArcSin, Average, Div, Even, Exp, Log10, Logn, Max, Median, Min, Mod, Mul, Neg, Odd,
22    Pow, Sin, Sqrt,
23};
24use crate::blocks::misc::{HasValue, ParseBool, ParseNumber, Random, SineWave};
25use crate::blocks::string::{Concat, Replace, StrLen};
26use crate::blocks::time::Now;
27
28use anyhow::{anyhow, Result};
29use lazy_static::lazy_static;
30use std::collections::BTreeMap;
31use std::sync::Mutex;
32
33use super::{BlockImpl, InputImpl};
34
35pub(crate) type DynBlockProps = dyn BlockProps<
36    Reader = <InputImpl as InputProps>::Reader,
37    Writer = <InputImpl as InputProps>::Writer,
38>;
39type MapType = BTreeMap<String, BTreeMap<String, BlockEntry>>;
40type BlockRegistry = Mutex<MapType>;
41
42/// Register a block in the registry
43#[derive(Debug, Clone)]
44pub struct BlockEntry {
45    pub desc: BlockDesc,
46    pub make: Option<fn() -> Box<DynBlockProps>>,
47}
48
49/// Macro for statically registering all the blocks that are
50/// available in the system.
51#[macro_export]
52macro_rules! register_blocks{
53    ( $( $block_name:ty ),* ) => {
54		lazy_static! {
55			/// The block registry
56			/// This is a static variable that is initialized once and then
57			/// used throughout the lifetime of the program.
58			static ref BLOCKS: BlockRegistry = {
59				let mut reg = BTreeMap::new();
60
61				$(
62					register_impl::<$block_name>(&mut reg);
63				)*
64
65				reg.into()
66			};
67		}
68
69		/// Schedule a block by name.
70		/// If the block name is valid, it will be scheduled on the engine.
71		/// The engine will execute the block if the engine is running.
72		/// This requires that the block is statically registered.
73		///
74		/// # Arguments
75		/// - name: The name of the block to schedule
76		/// - eng: The engine to schedule the block on
77		/// # Returns
78		/// A result indicating success or failure
79		pub fn schedule_block<E>(name: &str, eng: &mut E) -> Result<uuid::Uuid>
80		where E : Engine<Reader = <InputImpl as InputProps>::Reader, Writer = <InputImpl as InputProps>::Writer> {
81
82			match name {
83				$(
84					stringify!($block_name) => {
85						let block = <$block_name>::new();
86						let uuid = *block.id();
87						eng.schedule(block);
88						Ok(uuid)
89					}
90				)*
91				_ => {
92					return Err(anyhow!("Block not found"));
93				}
94			}
95
96		}
97
98		/// Schedule a block by name and UUID.
99		/// See [`schedule_block`] for more details.
100		pub fn schedule_block_with_uuid<E>(name: &str, uuid: uuid::Uuid, eng: &mut E) -> Result<uuid::Uuid>
101		where E : Engine<Reader = <InputImpl as InputProps>::Reader, Writer = <InputImpl as InputProps>::Writer> {
102
103			match name {
104				$(
105					stringify!($block_name) => {
106						let block = <$block_name>::new_uuid(uuid);
107						eng.schedule(block);
108						Ok(uuid)
109					}
110				)*
111				_ => {
112					return Err(anyhow!("Block not found"));
113				}
114			}
115
116		}
117
118		/// Evaluate a static registered block by name.
119		/// This will create a block instance and execute it.
120		///
121		/// # Arguments
122		/// - name: The name of the block to evaluate
123		/// - inputs: The input values to the block
124		///
125		/// # Returns
126		/// A list of values representing the outputs of the block
127		pub async fn eval_static_block(name: &str, inputs: Vec<Value>) -> Result<Vec<Value>> {
128			match name {
129				$(
130					stringify!($block_name) => {
131						let mut block = <$block_name>::new();
132						eval_block_impl(&mut block, inputs).await
133					}
134				)*
135				_ => {
136					return Err(anyhow!("Block not found"));
137				}
138			}
139		}
140    };
141}
142
143register_blocks!(
144    // Logic blocks
145    And,
146    Or,
147    Not,
148    Equal,
149    NotEqual,
150    Xor,
151    GreaterThan,
152    GreaterThanEq,
153    Latch,
154    LessThan,
155    LessThanEq,
156    // Math blocks
157    Abs,
158    Add,
159    ArcCos,
160    ArcTan,
161    Average,
162    Median,
163    Even,
164    Odd,
165    Sub,
166    Mul,
167    Div,
168    Exp,
169    Cos,
170    ArcSin,
171    Sin,
172    Log10,
173    Logn,
174    Sqrt,
175    Pow,
176    Mod,
177    Min,
178    Max,
179    Neg,
180    // Bitwise blocks
181    BitwiseAnd,
182    BitwiseNot,
183    BitwiseOr,
184    BitwiseXor,
185    // Control blocks
186    Pid,
187    PriorityArray,
188    // String blocks
189    Concat,
190    Replace,
191    StrLen,
192    // Collections blocks
193    GetElement,
194    Length,
195    Keys,
196    Values,
197    List,
198    Dict,
199    // Time blocks
200    Now,
201    // Misc blocks
202    Random,
203    SineWave,
204    ParseBool,
205    ParseNumber,
206    HasValue
207);
208
209/// Construct a block properties from the registry
210/// # Arguments
211/// - name: The name of the block to get
212/// # Returns
213/// A boxed block
214pub fn make(name: &str, lib: Option<String>) -> Option<Box<DynBlockProps>> {
215    let entry = get_block(name, lib)?;
216    entry.make.map(|make| make())
217}
218
219/// Get a block entry from the registry
220pub fn get_block(name: &str, lib: Option<String>) -> Option<BlockEntry> {
221    let reg = BLOCKS.lock().expect("Block registry is locked");
222    let lib = lib.unwrap_or_else(|| "core".to_string());
223
224    let reg = reg.get(&lib)?;
225    reg.get(name).cloned()
226}
227
228/// Get a core block
229pub fn get_core_block(name: &str) -> Option<BlockEntry> {
230    get_block(name, Some("core".to_string()))
231}
232
233/// Get all block descriptions from the registry
234pub fn list_registered_blocks() -> Vec<BlockDesc> {
235    let reg = BLOCKS.lock().expect("Block registry is locked");
236
237    let mut blocks = Vec::new();
238    for (_, lib) in reg.iter() {
239        for (_, block) in lib.iter() {
240            blocks.push(block.desc.clone());
241        }
242    }
243
244    blocks
245}
246
247/// Register a block with the registry
248pub fn register_block_desc(desc: &BlockDesc) -> Result<()> {
249    let mut reg = BLOCKS.lock().expect("Block registry is locked");
250
251    let lib = desc.library.clone();
252    let reg = reg.entry(lib).or_default();
253
254    let name = desc.name.clone();
255    if reg.contains_key(&name) {
256        return Err(anyhow!("Block already registered"));
257    }
258
259    reg.insert(
260        name.to_string(),
261        BlockEntry {
262            desc: desc.clone(),
263            make: None,
264        },
265    );
266
267    Ok(())
268}
269
270/// Register a block with the registry
271/// # Arguments
272/// - B: The block type to register
273/// # Panics
274/// Panics if the block registry is already locked
275pub fn register<B: BlockImpl>() {
276    let mut reg = BLOCKS.lock().expect("Block registry is locked");
277
278    register_impl::<B>(&mut reg);
279}
280
281/// Evaluate a block directly
282///
283/// # Arguments
284/// - block: The block to evaluate
285/// - inputs: The input values to the block
286/// # Returns
287/// A list of values representing the outputs of the block
288pub async fn eval_block_impl<B: BlockImpl>(
289    block: &mut B,
290    inputs: Vec<Value>,
291) -> Result<Vec<Value>> {
292    for (i, input) in inputs.iter().enumerate() {
293        let mut input_pins = block.inputs_mut();
294
295        if i >= input_pins.len() {
296            return Err(anyhow!("Too many inputs"));
297        }
298
299        input_pins[i].increment_conn();
300        if input_pins[i].writer().try_send(input.clone()).is_ok() && i < inputs.len() - 1 {
301            block.read_inputs().await;
302        }
303    }
304
305    block.execute().await;
306    Ok(block.outputs().iter().map(|o| o.value().clone()).collect())
307}
308
309fn register_impl<B: BlockImpl>(reg: &mut MapType) {
310    let desc = <B as BlockStaticDesc>::desc();
311    let lib = desc.library.clone();
312
313    reg.entry(lib).or_default().insert(desc.name.clone(), {
314        let make = || -> Box<DynBlockProps> {
315            let block = B::default();
316            Box::new(block)
317        };
318
319        BlockEntry {
320            desc: desc.clone(),
321            make: Some(make),
322        }
323    });
324}
325
326#[cfg(test)]
327mod test {
328
329    use crate::base::block::connect::connect_output;
330
331    use super::*;
332
333    #[test]
334    fn test_registry() {
335        let add = get_core_block("Add").expect("Add block not found");
336        let random = get_core_block("Random").expect("Random block not found");
337        let sine = get_core_block("SineWave").expect("SineWave block not found");
338
339        assert_eq!(add.desc.name, "Add");
340        assert_eq!(random.desc.name, "Random");
341        assert_eq!(sine.desc.name, "SineWave");
342
343        let mut random = random.make.unwrap()();
344        let mut outs = random.outputs_mut();
345
346        let mut add = add.make.unwrap()();
347        let mut ins = add.inputs_mut();
348
349        let out = outs.first_mut().unwrap();
350        let input = ins.first_mut().unwrap();
351
352        connect_output(*out, *input).unwrap();
353
354        let mut eng = crate::single_threaded::SingleThreadedEngine::new();
355
356        schedule_block("Add", &mut eng).expect("Block");
357
358        assert!(eng.blocks().iter().any(|b| b.desc().name == "Add"));
359    }
360
361    #[tokio::test]
362    async fn test_block_eval() {
363        let result = eval_static_block("Add", vec![Value::from(1), Value::from(2)]).await;
364
365        assert_eq!(result.unwrap(), vec![Value::from(3)]);
366    }
367}