forester_rs/runtime/
builder.rs

1pub mod builtin;
2pub mod custom_builder;
3pub mod file_builder;
4pub mod text_builder;
5pub mod ros_nav;
6pub mod ros_core;
7
8use crate::get_pb;
9
10use crate::runtime::action::keeper::{ActionImpl, ActionKeeper};
11use crate::runtime::action::{Action, ActionName, Impl, ImplAsync, ImplRemote};
12use crate::runtime::blackboard::BlackBoard;
13use crate::runtime::builder::custom_builder::CustomForesterBuilder;
14use crate::runtime::builder::file_builder::FileForesterBuilder;
15use crate::runtime::builder::text_builder::TextForesterBuilder;
16use crate::runtime::env::RtEnv;
17
18use crate::runtime::forester::{serv, Forester};
19use crate::runtime::rtree::builder::RtNodeBuilder;
20use crate::runtime::rtree::rnode::RNodeId;
21use crate::runtime::rtree::{RuntimeTree, RuntimeTreeStarter};
22use crate::runtime::{RtOk, RtResult, RuntimeError};
23use crate::tracer::Tracer;
24use crate::tree::project::{FileName, TreeName};
25
26use std::collections::HashMap;
27use std::path::PathBuf;
28use std::sync::{Arc, Mutex};
29use crate::runtime::env::daemon::{DaemonName, Daemon};
30use crate::runtime::env::daemon::context::DaemonContext;
31
32/// The builder to create a Forester instance
33///
34///# Example
35///```
36/// use std::path::PathBuf;
37/// use forester_rs::tracer::Tracer;
38/// use forester_rs::runtime::builder::ForesterBuilder;
39/// use forester_rs::runtime::action::Action;
40/// use forester_rs::runtime::action::builtin::data::StoreData;
41/// use forester_rs::runtime::rtree::builder::RtNodeBuilder;
42/// use forester_rs::*;
43/// use forester_rs::runtime::rtree::rnode::{DecoratorType, FlowType, RNodeName};
44/// use forester_rs::runtime::args::RtArgs;
45/// fn from_file(root:PathBuf){
46///     let mut fb = ForesterBuilder::from_fs();
47///     fb.main_file("main.tree".to_string());
48///     fb.root(root);
49///     fb.register_sync_action("store", StoreData);
50///     
51///     fb.tracer(Tracer::default());
52///     fb.bb_load("db/db.json".to_string());
53///     let forester = fb.build().unwrap();
54/// }
55/// fn from_text(){
56///
57///     let mut fb = ForesterBuilder::from_text();
58///            
59///     fb.text(r#"root main store("hello", "world") "#.to_string());    
60///
61///     fb.register_sync_action("store", StoreData);
62///     
63///     let forester = fb.build().unwrap();
64/// }
65/// fn from_code(){
66///
67///     let mut fb = ForesterBuilder::from_code();
68///            
69///     fb.add_rt_node(
70///          flow!(fallback node_name!(), args!();
71///                         action!(),
72///                         action!(),
73///                         action!(),
74///                         action!()
75///                     )
76///      );
77///
78///     fb.register_sync_action("store", StoreData);
79///     
80///     let forester = fb.build().unwrap();
81/// }
82///
83/// ```
84pub enum ForesterBuilder {
85    Files {
86        cfb: CommonForesterBuilder,
87        delegate: FileForesterBuilder,
88        error: Option<String>,
89    },
90    Text {
91        cfb: CommonForesterBuilder,
92        delegate: TextForesterBuilder,
93        error: Option<String>,
94    },
95    Code {
96        cfb: CommonForesterBuilder,
97        delegate: CustomForesterBuilder,
98        error: Option<String>,
99    },
100}
101
102impl ForesterBuilder {
103    pub fn from_fs() -> ForesterBuilder {
104        ForesterBuilder::Files {
105            cfb: CommonForesterBuilder::new(),
106            delegate: FileForesterBuilder::new(),
107            error: None,
108        }
109    }
110    pub fn from_text() -> ForesterBuilder {
111        ForesterBuilder::Text {
112            cfb: CommonForesterBuilder::new(),
113            delegate: TextForesterBuilder::new(),
114            error: None,
115        }
116    }
117    pub fn from_code() -> ForesterBuilder {
118        ForesterBuilder::Code {
119            cfb: CommonForesterBuilder::new(),
120            delegate: CustomForesterBuilder::new(),
121            error: None,
122        }
123    }
124
125    /// Root folder.
126    pub fn root(&mut self, root: PathBuf) {
127        match self {
128            ForesterBuilder::Files { delegate, .. } => {
129                delegate.root(root);
130            }
131            ForesterBuilder::Text { error, .. } | ForesterBuilder::Code { error, .. } => {
132                let _ = error.insert("This type of builder does not accept root folder. Only `from_file_system` builder accept it.".to_string());
133            }
134        }
135    }
136    /// A file that has a main root definition
137    pub fn main_file(&mut self, main_file: FileName) {
138        match self {
139            ForesterBuilder::Files { delegate, .. } => {
140                delegate.main_file(main_file);
141            }
142            ForesterBuilder::Text { error, .. } | ForesterBuilder::Code { error, .. } => {
143                let _ = error.insert("This type of builder does not accept main_file. Only `from_file_system` builder accept it.".to_string());
144            }
145        }
146    }
147    /// A name of the main root definition
148    pub fn main_tree(&mut self, main_tree: TreeName) {
149        match self {
150            ForesterBuilder::Files { delegate, .. } => {
151                delegate.main_tree(main_tree);
152            }
153            ForesterBuilder::Text { error, .. } | ForesterBuilder::Code { error, .. } => {
154                let _ = error.insert("This type of builder does not accept main_tree. Only `from_file_system` builder accept it.".to_string());
155            }
156        }
157    }
158
159    /// add script on the fly.
160    /// In that scenario, there is no need in the other attributes like files or root.
161    ///
162    /// Precautions:
163    /// Imports and other files still work only with absolute paths.
164    pub fn text(&mut self, txt: String) {
165        match self {
166            ForesterBuilder::Text { delegate, .. } => {
167                delegate.text(txt);
168            }
169            ForesterBuilder::Files { error, .. } | ForesterBuilder::Code { error, .. } => {
170                let _ = error.insert("This type of builder does not accept code as text. Only `from_text` builder accept it.".to_string());
171            }
172        }
173    }
174    /// add runtime node.
175    pub fn add_rt_node(&mut self, node_b: RtNodeBuilder) -> RNodeId {
176        match self {
177            ForesterBuilder::Code { delegate, .. } => delegate.add_rt_node(node_b),
178            ForesterBuilder::Files { error, .. } | ForesterBuilder::Text { error, .. } => {
179                let _ = error.insert("This type of builder does not accept code as text. Only `from_text` builder accept it.".to_string());
180                0
181            }
182        }
183    }
184
185
186    /// Add a daemon
187    pub fn register_daemon(&mut self, daemon: Daemon) {
188        self.cfb().register_daemon(daemon);
189    }
190
191    /// Add a daemon with a name
192    /// The name is used to stop the daemon
193    pub fn register_named_daemon(&mut self, name: DaemonName, daemon: Daemon) {
194        self.cfb().register_named_daemon(name, daemon);
195    }
196
197
198    /// Add a sync action according to the name.
199    pub fn register_sync_action<A>(&mut self, name: &str, action: A)
200        where
201            A: Impl + 'static,
202    {
203        self.cfb().register_sync_action(name, action);
204    }
205    /// Add an async action according to the name.
206    pub fn register_async_action<A>(&mut self, name: &str, action: A)
207        where
208            A: ImplAsync + 'static,
209    {
210        self.cfb().register_async_action(name, action);
211    }
212
213    /// Add an action according to the name but with a promise the action remote.
214    pub fn register_remote_action<A>(&mut self, name: &str, action: A)
215        where
216            A: ImplRemote + 'static,
217    {
218        self.cfb().register_remote_action(name, action);
219    }
220    /// setup the port for the server
221    pub fn http_serv(&mut self, port: u16) {
222        self.cfb().http_serv(port)
223    }
224
225    /// Tracer that will be used to save the tracing information.
226    pub fn tracer(&mut self, tr: Tracer) {
227        self.cfb().tracer(tr);
228    }
229    /// A file that has a snapshot of the bb in json format.
230    pub fn bb_load(&mut self, bb: String) {
231        self.cfb().bb_load(bb);
232    }
233
234    /// Mix the runtime async env(tokio env)
235    /// By default, creates the default tokio Runtime multi thread
236    pub fn rt_env(&mut self, env: RtEnv) {
237        self.cfb().rt_env(env);
238    }
239
240    /// The method to build forester
241    pub fn build(self) -> RtResult<Forester> {
242        self.build_with(|| ActionImpl::Absent)
243    }
244
245    /// The method to build forester and provide the implementation for the absent actions
246    pub fn build_with<T>(self, default_action: T) -> RtResult<Forester>
247        where
248            T: Fn() -> ActionImpl,
249    {
250        self.error()?;
251
252        let (
253            tree,
254            actions,
255            action_names,
256            daemons,
257            tr,
258            env,
259            bb_load,
260            root,
261            port
262        ) = match self {
263            ForesterBuilder::Files { delegate, cfb, .. } => {
264                let root = delegate.root.clone();
265                let project = delegate.build()?;
266                let RuntimeTreeStarter {
267                    tree,
268                    std_actions,
269                    actions,
270                } = RuntimeTree::build(project)?;
271                let mut impl_actions = cfb.actions;
272
273                for (action_name, file_name) in std_actions.iter() {
274                    impl_actions.insert(
275                        action_name.clone(),
276                        builtin::pick_action(action_name, file_name)?,
277                    );
278                }
279                (
280                    tree,
281                    impl_actions,
282                    actions,
283                    cfb.daemons,
284                    cfb.tracer,
285                    cfb.env,
286                    cfb.bb_load,
287                    root,
288                    cfb.port,
289                )
290            }
291            ForesterBuilder::Text { delegate, cfb, .. } => {
292                let project = delegate.build()?;
293                let RuntimeTreeStarter {
294                    tree,
295                    std_actions,
296                    actions,
297                } = RuntimeTree::build(project)?;
298                let mut impl_actions = cfb.actions;
299                for (action_name, file_name) in std_actions.iter() {
300                    let action = builtin::pick_action(action_name, file_name)?;
301                    impl_actions.insert(action_name.clone(), action);
302                }
303                (
304                    tree,
305                    impl_actions,
306                    actions,
307                    cfb.daemons,
308                    cfb.tracer,
309                    cfb.env,
310                    cfb.bb_load,
311                    None,
312                    cfb.port,
313                )
314            }
315            ForesterBuilder::Code { delegate, cfb, .. } => {
316                let (tree, actions) = delegate.build()?;
317                (
318                    tree,
319                    cfb.actions,
320                    actions,
321                    cfb.daemons,
322                    cfb.tracer,
323                    cfb.env,
324                    cfb.bb_load,
325                    None,
326                    cfb.port,
327                )
328            }
329        };
330
331        let bb =
332            if let Some(bb_load_dump) = bb_load {
333                BlackBoard::load(&get_pb(&PathBuf::from(bb_load_dump), &root)?)?
334            } else { BlackBoard::default() };
335
336        let mut env = if let Some(e) = env {
337            e
338        } else {
339            RtEnv::try_new()?
340        };
341
342
343        let bb = Arc::new(Mutex::new(bb));
344        let tracer = Arc::new(Mutex::new(tr));
345
346        let context = DaemonContext::new(bb.clone(), tracer.clone());
347        for daemon_cfg in daemons.into_iter() {
348            match daemon_cfg {
349                DaemonTaskCfg::Unnamed(d) => {
350                    env.start_daemon(d, context.clone())?;
351                }
352                DaemonTaskCfg::Named(name, d) => {
353                    env.start_named_daemon(name, d, context.clone())?;
354                }
355            }
356        }
357
358        let env = Arc::new(Mutex::new(env));
359        let serv = if port.is_some() {
360            Some(serv::start(env.clone(), port, bb.clone(), tracer.clone())?)
361        } else {
362            None
363        };
364
365        let keeper = ActionKeeper::new_with(actions, action_names, default_action)?;
366
367        Forester::new(tree, bb, tracer, keeper, env, serv)
368    }
369
370    fn cfb(&mut self) -> &mut CommonForesterBuilder {
371        match self {
372            ForesterBuilder::Files { cfb, .. }
373            | ForesterBuilder::Text { cfb, .. }
374            | ForesterBuilder::Code { cfb, .. } => cfb,
375        }
376    }
377    fn error(&self) -> RtOk {
378        match &self {
379            ForesterBuilder::Files { error: Some(v), .. }
380            | ForesterBuilder::Text { error: Some(v), .. }
381            | ForesterBuilder::Code { error: Some(v), .. } => {
382                Err(RuntimeError::Unexpected(v.to_string()))
383            }
384            _ => Ok(()),
385        }
386    }
387}
388
389pub struct CommonForesterBuilder {
390    env: Option<RtEnv>,
391    tracer: Tracer,
392    bb_load: Option<String>,
393    actions: HashMap<ActionName, Action>,
394    daemons: Vec<DaemonTaskCfg>,
395    port: ServerPort,
396}
397
398impl CommonForesterBuilder {
399    pub fn new() -> Self {
400        Self {
401            env: None,
402            tracer: Tracer::noop(),
403            bb_load: None,
404            actions: HashMap::new(),
405            daemons: Vec::new(),
406            port: ServerPort::None,
407        }
408    }
409
410    /// Add a daemon
411    pub fn register_daemon(&mut self, daemon: Daemon) {
412        self.daemons.push(DaemonTaskCfg::Unnamed(daemon));
413    }
414
415    /// Add a daemon with a name
416    /// The name is used to stop the daemon
417    pub fn register_named_daemon(&mut self, name: DaemonName, daemon: Daemon)
418    {
419        self.daemons.push(DaemonTaskCfg::Named(name, daemon));
420    }
421
422
423    /// Add an sync action according to the name.
424    pub fn register_sync_action<A>(&mut self, name: &str, action: A)
425        where
426            A: Impl + 'static,
427    {
428        self.actions
429            .insert(name.to_string(), Action::Sync(Box::new(action)));
430    }
431    /// Add an sync action according to the name.
432    pub fn register_async_action<A>(&mut self, name: &str, action: A)
433        where
434            A: ImplAsync + 'static,
435    {
436        self.actions
437            .insert(name.to_string(), Action::Async(Arc::new(action)));
438    }
439    /// Add an action according to the name but with a promise the action remote.
440    pub fn register_remote_action<A>(&mut self, name: &str, action: A)
441        where
442            A: ImplRemote + 'static,
443    {
444        self.actions
445            .insert(name.to_string(), Action::Remote(Box::new(action)));
446    }
447    /// setup the port for the server
448    pub fn http_serv(&mut self, port: u16) {
449        self.port = ServerPort::Static(port)
450    }
451
452    /// Tracer that will be used to save the tracing information.
453    pub fn tracer(&mut self, tr: Tracer) {
454        self.tracer = tr;
455    }
456    /// A file that has a snapshot of the bb in json format.
457    pub fn bb_load(&mut self, bb: String) {
458        self.bb_load = Some(bb);
459    }
460
461    /// Mix the runtime async env(tokio env)
462    /// By default, creates the default tokio Runtime multi thread
463    pub fn rt_env(&mut self, env: RtEnv) {
464        self.env = Some(env);
465    }
466}
467
468/// The struct defines the information of the server.
469#[derive(Debug, Clone)]
470pub enum ServerPort {
471    None,
472    Static(u16),
473}
474
475impl Default for ServerPort {
476    fn default() -> Self {
477        ServerPort::None
478    }
479}
480
481impl ServerPort {
482    fn is_some(&self) -> bool {
483        match self {
484            ServerPort::None => false,
485            _ => true,
486        }
487    }
488    fn get(&self) -> u16 {
489        match self {
490            ServerPort::None => 0,
491            ServerPort::Static(v) => *v,
492        }
493    }
494}
495
496pub enum DaemonTaskCfg {
497    Unnamed(Daemon),
498    Named(DaemonName, Daemon),
499}