cu29_derive/
lib.rs

1extern crate proc_macro;
2
3use proc_macro::TokenStream;
4use quote::{format_ident, quote};
5use std::fs::read_to_string;
6use syn::meta::parser;
7use syn::Fields::{Named, Unnamed};
8use syn::{
9    parse_macro_input, parse_quote, parse_str, Field, Fields, ItemImpl, ItemStruct, LitStr, Type,
10    TypeTuple,
11};
12
13use crate::utils::config_id_to_enum;
14use cu29_runtime::config::read_configuration;
15use cu29_runtime::config::CuConfig;
16use cu29_runtime::curuntime::{
17    compute_runtime_plan, find_task_type_for_id, CuExecutionLoop, CuExecutionUnit, CuTaskType,
18};
19use cu29_traits::CuResult;
20
21#[cfg(feature = "macro_debug")]
22use format::{highlight_rust_code, rustfmt_generated_code};
23use proc_macro2::{Ident, Span};
24
25mod format;
26mod utils;
27
28// TODO: this needs to be determined when the runtime is sizing itself.
29const DEFAULT_CLNB: usize = 10;
30
31#[inline]
32fn int2sliceindex(i: u32) -> syn::Index {
33    syn::Index::from(i as usize)
34}
35
36#[inline(always)]
37fn return_error(msg: String) -> TokenStream {
38    syn::Error::new(Span::call_site(), msg)
39        .to_compile_error()
40        .into()
41}
42
43/// Generates the CopperList content type from a config.
44/// gen_cumsgs!("path/to/config.toml")
45/// It will create a new type called CuMsgs you can pass to the log reader for decoding:
46#[proc_macro]
47pub fn gen_cumsgs(config_path_lit: TokenStream) -> TokenStream {
48    let config = parse_macro_input!(config_path_lit as LitStr).value();
49    if !std::path::Path::new(&config_full_path(&config)).exists() {
50        return return_error(format!(
51            "The configuration file `{}` does not exist. Please provide a valid path.",
52            config
53        ));
54    }
55    #[cfg(feature = "macro_debug")]
56    eprintln!("[gen culist support with {:?}]", config);
57    let cuconfig = match read_config(&config) {
58        Ok(cuconfig) => cuconfig,
59        Err(e) => return return_error(e.to_string()),
60    };
61    let runtime_plan: CuExecutionLoop = match compute_runtime_plan(&cuconfig) {
62        Ok(plan) => plan,
63        Err(e) => return return_error(format!("Could not compute runtime plan: {}", e)),
64    };
65
66    // Give a name compatible with a struct to match the task ids to their output in the CuMsgs tuple.
67    let all_tasks_member_ids: Vec<String> = cuconfig
68        .get_all_nodes(None) // FIXME(gbin): Multimission
69        .iter()
70        .map(|(_, node)| utils::config_id_to_struct_member(node.get_id().as_str()))
71        .collect();
72
73    // All accesses are linear on the culist but the id of the tasks is random (determined by the Ron declaration order).
74    // This records the task ids in call order.
75    let taskid_order: Vec<usize> = runtime_plan
76        .steps
77        .iter()
78        .filter_map(|unit| match unit {
79            CuExecutionUnit::Step(step) => Some(step.node_id as usize),
80            _ => None,
81        })
82        .collect();
83
84    #[cfg(feature = "macro_debug")]
85    eprintln!(
86        "[The CuMsgs matching tasks ids are {:?}]",
87        taskid_order
88            .iter()
89            .map(|i| all_tasks_member_ids[*i].clone())
90            .collect::<Vec<_>>()
91    );
92
93    let support = gen_culist_support(&runtime_plan, &taskid_order, &all_tasks_member_ids);
94
95    let with_uses = quote! {
96        mod cumsgs {
97            use cu29::bincode::Encode;
98            use cu29::bincode::enc::Encoder;
99            use cu29::bincode::error::EncodeError;
100            use cu29::bincode::Decode;
101            use cu29::bincode::de::Decoder;
102            use cu29::bincode::error::DecodeError;
103            use cu29::copperlist::CopperList;
104            use cu29::cutask::CuMsgMetadata;
105            use cu29::cutask::CuMsg;
106            #support
107        }
108        use cumsgs::CuMsgs;
109    };
110    with_uses.into()
111}
112
113/// Build the inner support of the copper list.
114fn gen_culist_support(
115    runtime_plan: &CuExecutionLoop,
116    taskid_call_order: &[usize],
117    all_tasks_as_struct_member_name: &Vec<String>,
118) -> proc_macro2::TokenStream {
119    #[cfg(feature = "macro_debug")]
120    eprintln!("[Extract msgs types]");
121    let all_msgs_types_in_culist_order = extract_msg_types(runtime_plan);
122
123    let culist_size = all_msgs_types_in_culist_order.len();
124    let task_indices: Vec<_> = taskid_call_order
125        .iter()
126        .map(|i| syn::Index::from(*i))
127        .collect();
128
129    #[cfg(feature = "macro_debug")]
130    eprintln!("[build the copperlist struct]");
131    let msgs_types_tuple: TypeTuple = build_culist_tuple(&all_msgs_types_in_culist_order);
132
133    #[cfg(feature = "macro_debug")]
134    eprintln!("[build the copperlist tuple bincode support]");
135    let msgs_types_tuple_encode = build_culist_tuple_encode(&all_msgs_types_in_culist_order);
136    let msgs_types_tuple_decode = build_culist_tuple_decode(&all_msgs_types_in_culist_order);
137
138    #[cfg(feature = "macro_debug")]
139    eprintln!("[build the copperlist tuple debug support]");
140    let msgs_types_tuple_debug = build_culist_tuple_debug(&all_msgs_types_in_culist_order);
141
142    let collect_metadata_function = quote! {
143        pub fn collect_metadata<'a>(culist: &'a CuList) -> [&'a CuMsgMetadata; #culist_size] {
144            [#( &culist.msgs.0.#task_indices.metadata, )*]
145        }
146    };
147
148    let methods = itertools::multizip((all_tasks_as_struct_member_name, taskid_call_order)).map(
149        |(name, output_position)| {
150            let fn_name = format_ident!("get_{}_output", name);
151            let payload_type = all_msgs_types_in_culist_order[*output_position].clone();
152            let index = syn::Index::from(*output_position);
153            quote! {
154                pub fn #fn_name(&self) -> &CuMsg<#payload_type> {
155                    &self.0.#index
156                }
157            }
158        },
159    );
160
161    // This generates a way to get the metadata of every single message of a culist at low cost
162    quote! {
163        #collect_metadata_function
164
165        pub struct CuMsgs(pub #msgs_types_tuple);
166
167        pub type CuList = CopperList<CuMsgs>;
168
169        impl CuMsgs {
170            #(#methods)*
171
172            fn get_tuple(&self) -> &#msgs_types_tuple {
173                &self.0
174            }
175
176            fn get_tuple_mut(&mut self) -> &mut #msgs_types_tuple {
177                &mut self.0
178            }
179        }
180
181        // Adds the bincode support for the copper list tuple
182        #msgs_types_tuple_encode
183        #msgs_types_tuple_decode
184
185        // Adds the debug support
186        #msgs_types_tuple_debug
187    }
188}
189
190fn gen_sim_support(runtime_plan: &CuExecutionLoop) -> proc_macro2::TokenStream {
191    #[cfg(feature = "macro_debug")]
192    eprintln!("[Sim: Build SimEnum]");
193    let plan_enum: Vec<proc_macro2::TokenStream> = runtime_plan
194        .steps
195        .iter()
196        .map(|unit| match unit {
197            CuExecutionUnit::Step(step) => {
198                let enum_entry_name = config_id_to_enum(step.node.get_id().as_str());
199                let enum_ident = Ident::new(&enum_entry_name, proc_macro2::Span::call_site());
200                let inputs: Vec<Type> = step
201                    .input_msg_indices_types
202                    .iter()
203                    .map(|(_, t)| parse_str::<Type>(format!("CuMsg<{t}>").as_str()).unwrap())
204                    .collect();
205                let output: Option<Type> = step
206                    .output_msg_index_type
207                    .as_ref()
208                    .map(|(_, t)| parse_str::<Type>(format!("CuMsg<{t}>").as_str()).unwrap());
209                let no_output = parse_str::<Type>("CuMsg<()>").unwrap();
210                let output = output.as_ref().unwrap_or(&no_output);
211                quote! {
212                    #enum_ident(cu29::simulation::CuTaskCallbackState<'cl, (#(&'cl #inputs),*), &'cl mut #output>)
213                }
214            }
215            CuExecutionUnit::Loop(_) => {
216                todo!("Needs to be implemented")
217            }
218        })
219        .collect();
220    quote! {
221        pub enum SimStep<'cl> {
222            #(#plan_enum),*
223        }
224    }
225}
226
227/// Adds #[copper_runtime(config = "path", sim_mode = false/true)] to your application struct to generate the runtime.
228/// if sim_mode is omitted, it is set to false.
229/// This will add a "runtime" field to your struct and implement the "new" and "run" methods.
230#[proc_macro_attribute]
231pub fn copper_runtime(args: TokenStream, input: TokenStream) -> TokenStream {
232    #[cfg(feature = "macro_debug")]
233    eprintln!("[entry]");
234    let mut application_struct = parse_macro_input!(input as ItemStruct);
235    let mut config_file: Option<LitStr> = None;
236    let mut sim_mode = false;
237
238    // Custom parser for the attribute arguments
239    let attribute_config_parser = parser(|meta| {
240        if meta.path.is_ident("config") {
241            config_file = Some(meta.value()?.parse()?);
242            Ok(())
243        } else if meta.path.is_ident("sim_mode") {
244            // Check if `sim_mode` has an explicit value (true/false)
245            if meta.input.peek(syn::Token![=]) {
246                meta.input.parse::<syn::Token![=]>()?;
247                let value: syn::LitBool = meta.input.parse()?;
248                sim_mode = value.value();
249                Ok(())
250            } else {
251                // If no value is provided, default to true
252                sim_mode = true;
253                Ok(())
254            }
255        } else {
256            Err(meta.error("unsupported property"))
257        }
258    });
259
260    #[cfg(feature = "macro_debug")]
261    eprintln!("[parse]");
262    // Parse the provided args with the custom parser
263    parse_macro_input!(args with attribute_config_parser);
264
265    // Check if the config file was provided
266    let config_file = match config_file {
267        Some(file) => file.value(),
268        None => {
269            return return_error(
270                "Expected config file attribute like #[CopperRuntime(config = \"path\")]"
271                    .to_string(),
272            )
273        }
274    };
275
276    if !std::path::Path::new(&config_full_path(&config_file)).exists() {
277        return return_error(format!(
278            "The configuration file `{}` does not exist. Please provide a valid path.",
279            config_file
280        ));
281    }
282
283    let copper_config = match read_config(&config_file) {
284        Ok(cuconfig) => cuconfig,
285        Err(e) => return return_error(e.to_string()),
286    };
287    let copper_config_content = match read_to_string(config_full_path(config_file.as_str())) {
288        Ok(ok) => ok,
289        Err(e) => return return_error(format!("Could not read the config file (should not happen because we just succeeded just before). {}", e))
290    };
291
292    let mission = "default"; // FIXME(gbin) generate all the missions from the config.
293    let mission_mod =
294        parse_str::<Ident>(mission).expect("Could not make an identifier of the mission name");
295
296    #[cfg(feature = "macro_debug")]
297    eprintln!("[runtime plan for mission {}]", mission);
298    let runtime_plan: CuExecutionLoop = match compute_runtime_plan(&copper_config) {
299        Ok(plan) => plan,
300        Err(e) => return return_error(format!("Could not compute runtime plan: {}", e)),
301    };
302    #[cfg(feature = "macro_debug")]
303    eprintln!("{:?}", runtime_plan);
304
305    #[cfg(feature = "macro_debug")]
306    eprintln!("[extract tasks ids & types]");
307    let (all_tasks_ids, all_tasks_cutype, all_tasks_types_names, all_tasks_types) =
308        extract_tasks_types(&copper_config);
309
310    let all_sim_tasks_types: Vec<Type> = all_tasks_ids
311        .iter()
312        .zip(&all_tasks_cutype)
313        .zip(&all_tasks_types)
314        .map(|((task_id, cutype), stype)| match cutype {
315            CuTaskType::Source => {
316                let msg_type = copper_config
317                    .get_node_output_msg_type(task_id.as_str(), None) // FIXME(gbin): Multimission
318                    .unwrap_or_else(|| panic!("CuSrcTask {task_id} should have an outgoing connection with a valid output msg type"));
319                let sim_task_name = format!("cu29::simulation::CuSimSrcTask<{msg_type}>");
320                parse_str(sim_task_name.as_str()).unwrap_or_else(|_| panic!("Could not build the placeholder for simulation: {sim_task_name}"))
321            }
322            CuTaskType::Regular => stype.clone(),
323            CuTaskType::Sink => {
324                let msg_type = copper_config
325                    .get_node_input_msg_type(task_id.as_str(), None) // FIXME(gbin): Multimission
326                    .unwrap_or_else(|| panic!("CuSinkTask {task_id} should have an incoming connection with a valid input msg type"));
327                let sim_task_name = format!("cu29::simulation::CuSimSinkTask<{msg_type}>");
328                parse_str(sim_task_name.as_str()).unwrap_or_else(|_| panic!("Could not build the placeholder for simulation: {sim_task_name}"))
329            }
330        })
331        .collect();
332
333    #[cfg(feature = "macro_debug")]
334    eprintln!("[build task tuples]");
335    // Build the tuple of all those types
336    // note the extraneous, at the end is to make the tuple work even if this is only one element
337    let task_types_tuple: TypeTuple = parse_quote! {
338        (#(#all_tasks_types),*,)
339    };
340
341    let task_types_tuple_sim: TypeTuple = parse_quote! {
342        (#(#all_sim_tasks_types),*,)
343    };
344
345    #[cfg(feature = "macro_debug")]
346    eprintln!("[build monitor type]");
347    let monitor_type = if let Some(monitor_config) = copper_config.get_monitor_config() {
348        let monitor_type = parse_str::<Type>(monitor_config.get_type())
349            .expect("Could not transform the monitor type name into a Rust type.");
350        quote! { #monitor_type }
351    } else {
352        quote! { NoMonitor }
353    };
354
355    // FIXME(gbin): generate that per mission
356    #[cfg(feature = "macro_debug")]
357    eprintln!("[build runtime field]");
358    // add that to a new field
359    let runtime_field: Field = if sim_mode {
360        parse_quote! {
361            copper_runtime: cu29::curuntime::CuRuntime<#mission_mod::CuSimTasks, #mission_mod::CuMsgs, #monitor_type, #DEFAULT_CLNB>
362        }
363    } else {
364        parse_quote! {
365            copper_runtime: cu29::curuntime::CuRuntime<#mission_mod::CuTasks, #mission_mod::CuMsgs, #monitor_type, #DEFAULT_CLNB>
366        }
367    };
368
369    let name = &application_struct.ident;
370
371    #[cfg(feature = "macro_debug")]
372    eprintln!("[match struct anonymity]");
373    match &mut application_struct.fields {
374        Named(fields_named) => {
375            fields_named.named.push(runtime_field);
376        }
377        Unnamed(fields_unnamed) => {
378            fields_unnamed.unnamed.push(runtime_field);
379        }
380        Fields::Unit => {
381            panic!("This struct is a unit struct, it should have named or unnamed fields. use struct Something {{}} and not struct Something;")
382        }
383    };
384
385    #[cfg(feature = "macro_debug")]
386    eprintln!("[gen instances]");
387
388    let task_sim_instances_init_code = all_sim_tasks_types.iter().enumerate().map(|(index, ty)| {
389        let additional_error_info = format!(
390            "Failed to get create instance for {}, instance index {}.",
391            all_tasks_types_names[index], index
392        );
393
394        quote! {
395        <#ty>::new(all_instances_configs[#index]).map_err(|e| e.add_cause(#additional_error_info))?
396        }
397    }).collect::<Vec<_>>();
398
399    // Generate the code to create instances of the nodes
400    // It maps the types to their index
401    let (task_instances_init_code,
402        start_calls,
403        stop_calls,
404        preprocess_calls,
405        postprocess_calls): (Vec<_>, Vec<_>, Vec<_>, Vec<_>, Vec<_>) = itertools::multiunzip(all_tasks_types
406        .iter()
407        .enumerate()
408        .map(|(index, ty)| {
409            let task_index = int2sliceindex(index as u32);
410            let task_enum_name = config_id_to_enum(&all_tasks_ids[index]);
411            let enum_name = Ident::new(&task_enum_name, proc_macro2::Span::call_site());
412            let additional_error_info = format!(
413                "Failed to get create instance for {}, instance index {}.",
414                all_tasks_types_names[index], index
415            );
416            (
417                quote! {
418                    #ty::new(all_instances_configs[#index]).map_err(|e| e.add_cause(#additional_error_info))?
419                },
420                {
421                    let monitoring_action = quote! {
422                        let decision = self.copper_runtime.monitor.process_error(#index, CuTaskState::Start, &error);
423                        match decision {
424                            Decision::Abort => {
425                                debug!("Start: ABORT decision from monitoring. Task '{}' errored out \
426                                during start. Aborting all the other starts.", #mission_mod::TASKS_IDS[#index]);
427                                return Ok(());
428
429                            }
430                            Decision::Ignore => {
431                                debug!("Start: IGNORE decision from monitoring. Task '{}' errored out \
432                                during start. The runtime will continue.", #mission_mod::TASKS_IDS[#index]);
433                            }
434                            Decision::Shutdown => {
435                                debug!("Start: SHUTDOWN decision from monitoring. Task '{}' errored out \
436                                during start. The runtime cannot continue.", #mission_mod::TASKS_IDS[#index]);
437                                return Err(CuError::new_with_cause("Task errored out during start.", error));
438                            }
439                        }
440                    };
441
442                    let call_sim_callback = if sim_mode {
443                        quote! {
444                            // Ask the sim if this task should be executed or overridden by the sim.
445                            let ovr = sim_callback(SimStep::#enum_name(cu29::simulation::CuTaskCallbackState::Start));
446
447                            let doit = if let cu29::simulation::SimOverride::Errored(reason) = ovr  {
448                                let error: CuError = reason.into();
449                                #monitoring_action
450                                false
451                           }
452                           else {
453                                ovr == cu29::simulation::SimOverride::ExecuteByRuntime
454                           };
455                        }
456                    } else {
457                        quote! {
458                            let doit = true;  // in normal mode always execute the steps in the runtime.
459                        }
460                    };
461
462
463                    quote! {
464                        #call_sim_callback
465                        if doit {
466                            let task = &mut self.copper_runtime.tasks.#task_index;
467                            if let Err(error) = task.start(&self.copper_runtime.clock) {
468                                #monitoring_action
469                            }
470                        }
471                    }
472                },
473                {
474                    let monitoring_action = quote! {
475                                let decision = self.copper_runtime.monitor.process_error(#index, CuTaskState::Stop, &error);
476                                match decision {
477                                    Decision::Abort => {
478                                        debug!("Stop: ABORT decision from monitoring. Task '{}' errored out \
479                                    during stop. Aborting all the other starts.", #mission_mod::TASKS_IDS[#index]);
480                                        return Ok(());
481
482                                    }
483                                    Decision::Ignore => {
484                                        debug!("Stop: IGNORE decision from monitoring. Task '{}' errored out \
485                                    during stop. The runtime will continue.", #mission_mod::TASKS_IDS[#index]);
486                                    }
487                                    Decision::Shutdown => {
488                                        debug!("Stop: SHUTDOWN decision from monitoring. Task '{}' errored out \
489                                    during stop. The runtime cannot continue.", #mission_mod::TASKS_IDS[#index]);
490                                        return Err(CuError::new_with_cause("Task errored out during stop.", error));
491                                    }
492                                }
493                        };
494                    let call_sim_callback = if sim_mode {
495                        quote! {
496                            // Ask the sim if this task should be executed or overridden by the sim.
497                            let ovr = sim_callback(SimStep::#enum_name(cu29::simulation::CuTaskCallbackState::Stop));
498
499                            let doit = if let cu29::simulation::SimOverride::Errored(reason) = ovr  {
500                                let error: CuError = reason.into();
501                                #monitoring_action
502                                false
503                           }
504                           else {
505                                ovr == cu29::simulation::SimOverride::ExecuteByRuntime
506                           };
507                        }
508                    } else {
509                        quote! {
510                            let doit = true;  // in normal mode always execute the steps in the runtime.
511                        }
512                    };
513                    quote! {
514                        #call_sim_callback
515                        if doit {
516                            let task = &mut self.copper_runtime.tasks.#task_index;
517                            if let Err(error) = task.stop(&self.copper_runtime.clock) {
518                                #monitoring_action
519                            }
520                        }
521                    }
522                },
523                {
524                    let monitoring_action = quote! {
525                        let decision = self.copper_runtime.monitor.process_error(#index, CuTaskState::Preprocess, &error);
526                        match decision {
527                            Decision::Abort => {
528                                debug!("Preprocess: ABORT decision from monitoring. Task '{}' errored out \
529                                during preprocess. Aborting all the other starts.", #mission_mod::TASKS_IDS[#index]);
530                                return Ok(());
531
532                            }
533                            Decision::Ignore => {
534                                debug!("Preprocess: IGNORE decision from monitoring. Task '{}' errored out \
535                                during preprocess. The runtime will continue.", #mission_mod::TASKS_IDS[#index]);
536                            }
537                            Decision::Shutdown => {
538                                debug!("Preprocess: SHUTDOWN decision from monitoring. Task '{}' errored out \
539                                during preprocess. The runtime cannot continue.", #mission_mod::TASKS_IDS[#index]);
540                                return Err(CuError::new_with_cause("Task errored out during preprocess.", error));
541                            }
542                        }
543                    };
544                    let call_sim_callback = if sim_mode {
545                        quote! {
546                            // Ask the sim if this task should be executed or overridden by the sim.
547                            let ovr = sim_callback(SimStep::#enum_name(cu29::simulation::CuTaskCallbackState::Preprocess));
548
549                            let doit = if let cu29::simulation::SimOverride::Errored(reason) = ovr  {
550                                let error: CuError = reason.into();
551                                #monitoring_action
552                                false
553                            } else {
554                                ovr == cu29::simulation::SimOverride::ExecuteByRuntime
555                            };
556                        }
557                    } else {
558                        quote! {
559                            let doit = true;  // in normal mode always execute the steps in the runtime.
560                        }
561                    };
562                    quote! {
563                        #call_sim_callback
564                        if doit {
565                            let task = &mut self.copper_runtime.tasks.#task_index;
566                            if let Err(error) = task.preprocess(&self.copper_runtime.clock) {
567                                #monitoring_action
568                            }
569                        }
570                    }
571                },
572                {
573                    let monitoring_action = quote! {
574                        let decision = self.copper_runtime.monitor.process_error(#index, CuTaskState::Postprocess, &error);
575                        match decision {
576                            Decision::Abort => {
577                                debug!("Postprocess: ABORT decision from monitoring. Task '{}' errored out \
578                                during postprocess. Aborting all the other starts.", #mission_mod::TASKS_IDS[#index]);
579                                return Ok(());
580
581                            }
582                            Decision::Ignore => {
583                                debug!("Postprocess: IGNORE decision from monitoring. Task '{}' errored out \
584                                during postprocess. The runtime will continue.", #mission_mod::TASKS_IDS[#index]);
585                            }
586                            Decision::Shutdown => {
587                                debug!("Postprocess: SHUTDOWN decision from monitoring. Task '{}' errored out \
588                                during postprocess. The runtime cannot continue.", #mission_mod::TASKS_IDS[#index]);
589                                return Err(CuError::new_with_cause("Task errored out during postprocess.", error));
590                            }
591                        }
592                    };
593                    let call_sim_callback = if sim_mode {
594                        quote! {
595                            // Ask the sim if this task should be executed or overridden by the sim.
596                            let ovr = sim_callback(SimStep::#enum_name(cu29::simulation::CuTaskCallbackState::Postprocess));
597
598                            let doit = if let cu29::simulation::SimOverride::Errored(reason) = ovr  {
599                                let error: CuError = reason.into();
600                                #monitoring_action
601                                false
602                            } else {
603                                ovr == cu29::simulation::SimOverride::ExecuteByRuntime
604                            };
605                        }
606                    } else {
607                        quote! {
608                            let doit = true;  // in normal mode always execute the steps in the runtime.
609                        }
610                    };
611                    quote! {
612                        #call_sim_callback
613                        if doit {
614                            let task = &mut self.copper_runtime.tasks.#task_index;
615                            if let Err(error) = task.postprocess(&self.copper_runtime.clock) {
616                                #monitoring_action
617                            }
618                        }
619                    }
620                }
621            )
622        })
623    );
624
625    // All accesses are linear on the culist but the id of the tasks is random (determined by the Ron declaration order).
626    // This records the task ids in call order.
627    let mut taskid_call_order: Vec<usize> = Vec::new();
628
629    let runtime_plan_code: Vec<proc_macro2::TokenStream> = runtime_plan.steps
630        .iter()
631        .map(|unit| {
632            match unit {
633                CuExecutionUnit::Step(step) => {
634                    #[cfg(feature = "macro_debug")]
635                    eprintln!(
636                        "{} -> {} as {:?}. task_id: {} Input={:?}, Output={:?}",
637                        step.node.get_id(),
638                        step.node.get_type(),
639                        step.task_type,
640                        step.node_id,
641                        step.input_msg_indices_types,
642                        step.output_msg_index_type
643                    );
644
645                    let node_index = int2sliceindex(step.node_id);
646                    let task_instance = quote! { self.copper_runtime.tasks.#node_index };
647                    let comment_str = format!(
648                        "/// {} ({:?}) Id:{} I:{:?} O:{:?}",
649                        step.node.get_id(),
650                        step.task_type,
651                        step.node_id,
652                        step.input_msg_indices_types,
653                        step.output_msg_index_type
654                    );
655                    let comment_tokens: proc_macro2::TokenStream = parse_str(&comment_str).unwrap();
656                    let tid = step.node_id as usize;
657                    taskid_call_order.push(tid);
658
659                    let task_enum_name = config_id_to_enum(&all_tasks_ids[tid]);
660                    let enum_name = Ident::new(&task_enum_name, proc_macro2::Span::call_site());
661
662                    let process_call = match step.task_type {
663                        CuTaskType::Source => {
664                            if let Some((index, _)) = &step.output_msg_index_type {
665                                let output_culist_index = int2sliceindex(*index);
666
667                                let monitoring_action = quote! {
668                                    debug!("Task {}: Error during process: {}", #mission_mod::TASKS_IDS[#tid], &error);
669                                    let decision = self.copper_runtime.monitor.process_error(#tid, CuTaskState::Process, &error);
670                                    match decision {
671                                        Decision::Abort => {
672                                            debug!("Process: ABORT decision from monitoring. Task '{}' errored out \
673                                            during process. Skipping the processing of CL {}.", #mission_mod::TASKS_IDS[#tid], id);
674                                            self.copper_runtime.monitor.process_copperlist(&#mission_mod::collect_metadata(&culist))?;
675                                            self.copper_runtime.end_of_processing(id);
676                                            return Ok(()); // this returns early from the one iteration call.
677
678                                        }
679                                        Decision::Ignore => {
680                                            debug!("Process: IGNORE decision from monitoring. Task '{}' errored out \
681                                            during process. The runtime will continue with a forced empty message.", #mission_mod::TASKS_IDS[#tid]);
682                                            cumsg_output.clear_payload();
683                                        }
684                                        Decision::Shutdown => {
685                                            debug!("Process: SHUTDOWN decision from monitoring. Task '{}' errored out \
686                                            during process. The runtime cannot continue.", #mission_mod::TASKS_IDS[#tid]);
687                                            return Err(CuError::new_with_cause("Task errored out during process.", error));
688                                        }
689                                    }
690                                };
691                                let call_sim_callback = if sim_mode {
692                                    quote! {
693                                        let doit = {
694                                            let ovr = sim_callback(SimStep::#enum_name(cu29::simulation::CuTaskCallbackState::Process((), cumsg_output)));
695                                            if let cu29::simulation::SimOverride::Errored(reason) = ovr  {
696                                                let error: CuError = reason.into();
697                                                #monitoring_action
698                                                false
699                                            } else {
700                                                ovr == cu29::simulation::SimOverride::ExecuteByRuntime
701                                            }
702                                        };
703                                     }
704                                } else {
705                                    quote! {
706                                        let  doit = true;  // in normal mode always execute the steps in the runtime.
707                                   }
708                                };
709
710                                quote! {
711                                    {
712                                        #comment_tokens
713                                        {
714                                            let cumsg_output = &mut msgs.#output_culist_index;
715                                            #call_sim_callback
716                                            cumsg_output.metadata.process_time.start = self.copper_runtime.clock.now().into();
717                                            let maybe_error = if doit {
718                                                #task_instance.process(&self.copper_runtime.clock, cumsg_output)
719                                            } else {
720                                                Ok(())
721                                            };
722                                            cumsg_output.metadata.process_time.end = self.copper_runtime.clock.now().into();
723                                            if let Err(error) = maybe_error {
724                                                #monitoring_action
725                                            }
726                                        }
727                                    }
728                                }
729                            } else {
730                                panic!("Source task should have an output message index.");
731                            }
732                        }
733                        CuTaskType::Sink => {
734                            // collect the indices
735                            let indices = step.input_msg_indices_types.iter().map(|(index, _)| int2sliceindex(*index));
736                            if let Some((output_index, _)) = &step.output_msg_index_type {
737                                let output_culist_index = int2sliceindex(*output_index);
738
739                                let monitoring_action = quote! {
740                                    debug!("Task {}: Error during process: {}", #mission_mod::TASKS_IDS[#tid], &error);
741                                    let decision = self.copper_runtime.monitor.process_error(#tid, CuTaskState::Process, &error);
742                                    match decision {
743                                        Decision::Abort => {
744                                            debug!("Process: ABORT decision from monitoring. Task '{}' errored out \
745                                            during process. Skipping the processing of CL {}.", #mission_mod::TASKS_IDS[#tid], id);
746                                            self.copper_runtime.monitor.process_copperlist(&#mission_mod::collect_metadata(&culist))?;
747                                            self.copper_runtime.end_of_processing(id);
748                                            return Ok(()); // this returns early from the one iteration call.
749
750                                        }
751                                        Decision::Ignore => {
752                                            debug!("Process: IGNORE decision from monitoring. Task '{}' errored out \
753                                            during process. The runtime will continue with a forced empty message.", #mission_mod::TASKS_IDS[#tid]);
754                                            cumsg_output.clear_payload();
755                                        }
756                                        Decision::Shutdown => {
757                                            debug!("Process: SHUTDOWN decision from monitoring. Task '{}' errored out \
758                                            during process. The runtime cannot continue.", #mission_mod::TASKS_IDS[#tid]);
759                                            return Err(CuError::new_with_cause("Task errored out during process.", error));
760                                        }
761                                    }
762                                };
763
764                                let call_sim_callback = if sim_mode {
765                                    quote! {
766                                        let doit = {
767                                            let ovr = sim_callback(SimStep::#enum_name(cu29::simulation::CuTaskCallbackState::Process(cumsg_input, cumsg_output)));
768
769                                            if let cu29::simulation::SimOverride::Errored(reason) = ovr  {
770                                                let error: CuError = reason.into();
771                                                #monitoring_action
772                                                false
773                                            } else {
774                                                ovr == cu29::simulation::SimOverride::ExecuteByRuntime
775                                            }
776                                        };
777                                     }
778                                } else {
779                                    quote! {
780                                        let doit = true;  // in normal mode always execute the steps in the runtime.
781                                   }
782                                };
783                                quote! {
784                                    {
785                                        #comment_tokens
786                                        let cumsg_input = (#(&msgs.#indices),*);
787                                        // This is the virtual output for the sink
788                                        let cumsg_output = &mut msgs.#output_culist_index;
789                                        #call_sim_callback
790                                        cumsg_output.metadata.process_time.start = self.copper_runtime.clock.now().into();
791                                        let maybe_error = if doit {#task_instance.process(&self.copper_runtime.clock, cumsg_input)} else {Ok(())};
792                                        cumsg_output.metadata.process_time.end = self.copper_runtime.clock.now().into();
793                                        if let Err(error) = maybe_error {
794                                            #monitoring_action
795                                        }
796                                    }
797                                }
798                            } else {
799                                panic!("Sink tasks should have a virtual output message index.");
800                            }
801                        }
802                        CuTaskType::Regular => {
803                            let indices = step.input_msg_indices_types.iter().map(|(index, _)| int2sliceindex(*index));
804                            if let Some((output_index, _)) = &step.output_msg_index_type {
805                                let output_culist_index = int2sliceindex(*output_index);
806
807                                let monitoring_action = quote! {
808                                    debug!("Task {}: Error during process: {}", #mission_mod::TASKS_IDS[#tid], &error);
809                                    let decision = self.copper_runtime.monitor.process_error(#tid, CuTaskState::Process, &error);
810                                    match decision {
811                                        Decision::Abort => {
812                                            debug!("Process: ABORT decision from monitoring. Task '{}' errored out \
813                                            during process. Skipping the processing of CL {}.", #mission_mod::TASKS_IDS[#tid], id);
814                                            self.copper_runtime.monitor.process_copperlist(&#mission_mod::collect_metadata(&culist))?;
815                                            self.copper_runtime.end_of_processing(id);
816                                            return Ok(()); // this returns early from the one iteration call.
817
818                                        }
819                                        Decision::Ignore => {
820                                            debug!("Process: IGNORE decision from monitoring. Task '{}' errored out \
821                                            during process. The runtime will continue with a forced empty message.", #mission_mod::TASKS_IDS[#tid]);
822                                            cumsg_output.clear_payload();
823                                        }
824                                        Decision::Shutdown => {
825                                            debug!("Process: SHUTDOWN decision from monitoring. Task '{}' errored out \
826                                            during process. The runtime cannot continue.", #mission_mod::TASKS_IDS[#tid]);
827                                            return Err(CuError::new_with_cause("Task errored out during process.", error));
828                                        }
829                                    }
830                                };
831
832                                let call_sim_callback = if sim_mode {
833                                    quote! {
834                                        let doit = {
835                                            let ovr = sim_callback(SimStep::#enum_name(cu29::simulation::CuTaskCallbackState::Process(cumsg_input, cumsg_output)));
836
837                                            if let cu29::simulation::SimOverride::Errored(reason) = ovr  {
838                                                let error: CuError = reason.into();
839                                                #monitoring_action
840                                                false
841                                            }
842                                            else {
843                                                ovr == cu29::simulation::SimOverride::ExecuteByRuntime
844                                            }
845                                        };
846                                     }
847                                } else {
848                                    quote! {
849                                        let doit = true;  // in normal mode always execute the steps in the runtime.
850                                   }
851                                };
852                                quote! {
853                                    {
854                                        #comment_tokens
855                                        let cumsg_input = (#(&msgs.#indices),*);
856                                        let cumsg_output = &mut msgs.#output_culist_index;
857                                        #call_sim_callback
858                                        cumsg_output.metadata.process_time.start = self.copper_runtime.clock.now().into();
859                                        let maybe_error = if doit {#task_instance.process(&self.copper_runtime.clock, cumsg_input, cumsg_output)} else {Ok(())};
860                                        cumsg_output.metadata.process_time.end = self.copper_runtime.clock.now().into();
861                                        if let Err(error) = maybe_error {
862                                            #monitoring_action
863                                        }
864                                    }
865                                }
866                            } else {
867                                panic!("Regular task should have an output message index.");
868                            }
869                        }
870                    };
871
872                    process_call
873                }
874                CuExecutionUnit::Loop(_) => todo!("Needs to be implemented"),
875            }
876        }).collect();
877    #[cfg(feature = "macro_debug")]
878    eprintln!("[Culist access order:  {:?}]", taskid_call_order);
879
880    // Give a name compatible with a struct to match the task ids to their output in the CuMsgs tuple.
881    let all_tasks_member_ids: Vec<String> = all_tasks_ids
882        .iter()
883        .map(|name| utils::config_id_to_struct_member(name.as_str()))
884        .collect();
885
886    #[cfg(feature = "macro_debug")]
887    eprintln!("[build the copperlist support]");
888    let culist_support: proc_macro2::TokenStream =
889        gen_culist_support(&runtime_plan, &taskid_call_order, &all_tasks_member_ids);
890
891    #[cfg(feature = "macro_debug")]
892    eprintln!("[build the sim support]");
893    let sim_support: proc_macro2::TokenStream = gen_sim_support(&runtime_plan);
894
895    let (new, run_one_iteration, start_all_tasks, stop_all_tasks, run) = if sim_mode {
896        (
897            quote! {
898                pub fn new<F>(clock:RobotClock, unified_logger: Arc<Mutex<UnifiedLoggerWrite>>, config_override: Option<CuConfig>, sim_callback: &mut F) -> CuResult<Self>
899                where F: FnMut(SimStep) -> cu29::simulation::SimOverride,
900            },
901            quote! {
902                pub fn run_one_iteration<F>(&mut self, sim_callback: &mut F) -> CuResult<()>
903                where F: FnMut(SimStep) -> cu29::simulation::SimOverride,
904            },
905            quote! {
906                pub fn start_all_tasks<F>(&mut self, sim_callback: &mut F) -> CuResult<()>
907                where F: FnMut(SimStep) -> cu29::simulation::SimOverride,
908            },
909            quote! {
910                pub fn stop_all_tasks<F>(&mut self, sim_callback: &mut F) -> CuResult<()>
911                where F: FnMut(SimStep) -> cu29::simulation::SimOverride,
912            },
913            quote! {
914                pub fn run<F>(&mut self, sim_callback: &mut F) -> CuResult<()>
915                where F: FnMut(SimStep) -> cu29::simulation::SimOverride,
916            },
917        )
918    } else {
919        (
920            quote! {
921                pub fn new(clock:RobotClock, unified_logger: Arc<Mutex<UnifiedLoggerWrite>>, config_override: Option<CuConfig>) -> CuResult<Self>
922            },
923            quote! {
924                pub fn run_one_iteration(&mut self) -> CuResult<()>
925            },
926            quote! {
927                pub fn start_all_tasks(&mut self) -> CuResult<()>
928            },
929            quote! {
930                pub fn stop_all_tasks(&mut self) -> CuResult<()>
931            },
932            quote! {
933                pub fn run(&mut self) -> CuResult<()>
934            },
935        )
936    };
937
938    let sim_callback_arg = if sim_mode {
939        Some(quote!(sim_callback))
940    } else {
941        None
942    };
943
944    let sim_callback_on_new_calls = all_tasks_ids.iter().enumerate().map(|(i, id)| {
945        let enum_name = config_id_to_enum(id);
946        let enum_ident = Ident::new(&enum_name, Span::call_site());
947        quote! {
948            // the answer is ignored, we have to instantiate the tasks anyway.
949            sim_callback(SimStep::#enum_ident(cu29::simulation::CuTaskCallbackState::New(all_instances_configs[#i].cloned())));
950        }
951    });
952
953    let sim_callback_on_new = if sim_mode {
954        Some(quote! {
955            let all_instances_configs: Vec<Option<&ComponentConfig>> = config
956                .get_all_nodes(None) // FIXME(gbin): Multimission
957                .iter()
958                .map(|(_, node)| node.get_instance_config())
959                .collect();
960            #(#sim_callback_on_new_calls)*
961        })
962    } else {
963        None
964    };
965
966    #[cfg(feature = "macro_debug")]
967    eprintln!("[build the run methods]");
968    let run_methods = quote! {
969
970        #run_one_iteration {
971            #(#preprocess_calls)*
972            {
973                let mut culist: &mut _ = &mut self.copper_runtime.copper_lists_manager.create().expect("Ran out of space for copper lists"); // FIXME: error handling.
974                let id = culist.id;
975                culist.change_state(cu29::copperlist::CopperListState::Processing);
976                {
977                    let msgs = &mut culist.msgs.0;
978                    #(#runtime_plan_code)*
979                } // drop(msgs);
980
981                {
982                    // End of CL monitoring
983                    let md = #mission_mod::collect_metadata(&culist);
984                    let e2e = md.last().unwrap().process_time.end.unwrap() - md.first().unwrap().process_time.start.unwrap();
985                    let e2en: u64 = e2e.into();
986                } // drop(md);
987
988                self.copper_runtime.monitor.process_copperlist(&#mission_mod::collect_metadata(&culist))?;
989                self.copper_runtime.end_of_processing(id);
990
991           }// drop(culist); avoids a double mutable borrow
992           #(#postprocess_calls)*
993           Ok(())
994        }
995
996        #start_all_tasks {
997            #(#start_calls)*
998            self.copper_runtime.monitor.start(&self.copper_runtime.clock)?;
999            Ok(())
1000        }
1001
1002        #stop_all_tasks {
1003            #(#stop_calls)*
1004            self.copper_runtime.monitor.stop(&self.copper_runtime.clock)?;
1005            Ok(())
1006        }
1007
1008        #run {
1009            self.start_all_tasks(#sim_callback_arg)?;
1010            let error = loop {
1011                let error = self.run_one_iteration(#sim_callback_arg);
1012                if error.is_err() {
1013                    break error;
1014                }
1015            };
1016            debug!("A task errored out: {}", &error);
1017            self.stop_all_tasks(#sim_callback_arg)?;
1018            error
1019        }
1020    };
1021
1022    let tasks_type = if sim_mode {
1023        quote!(CuSimTasks)
1024    } else {
1025        quote!(CuTasks)
1026    };
1027
1028    let tasks_instanciator = if sim_mode {
1029        quote!(tasks_instanciator_sim)
1030    } else {
1031        quote!(tasks_instanciator)
1032    };
1033
1034    let application_impl = quote! {
1035        impl #name {
1036
1037            #new {
1038                let config_filename = #config_file;
1039                let config = if config_override.is_some() {
1040                    let overridden_config = config_override.unwrap();
1041                    debug!("CuConfig: Overridden programmatically: {}", &overridden_config.serialize_ron());
1042                    overridden_config
1043                } else if std::path::Path::new(config_filename).exists() {
1044                    debug!("CuConfig: Reading configuration from file: {}", config_filename);
1045                    cu29::config::read_configuration(config_filename)?
1046                } else {
1047                    let original_config = Self::get_original_config();
1048                    debug!("CuConfig: Using the original configuration the project was compiled with: {}", &original_config);
1049                    cu29::config::read_configuration_str(original_config)?
1050                };
1051
1052                // For simple cases we can say the section is just a bunch of Copper Lists.
1053                // But we can now have allocations outside of it so we can override it from the config.
1054                let mut default_section_size = std::mem::size_of::<super::#mission_mod::CuList>() * 64;
1055                // Check if there is a logging configuration with section_size_mib
1056                if let Some(section_size_mib) = config.logging.as_ref().and_then(|l| l.section_size_mib) {
1057                    // Convert MiB to bytes
1058                    default_section_size = section_size_mib as usize * 1024usize * 1024usize;
1059                }
1060                let copperlist_stream = stream_write::<#mission_mod::CuList>(
1061                    unified_logger.clone(),
1062                    UnifiedLogType::CopperList,
1063                    default_section_size,
1064                    // the 2 sizes are not directly related as we encode the CuList but we can
1065                    // assume the encoded size is close or lower than the non encoded one
1066                    // This is to be sure we have the size of at least a Culist and some.
1067                );
1068
1069                // FIXME(gbin): mission support
1070
1071                let application = Ok(#name {
1072                    copper_runtime: CuRuntime::<#mission_mod::#tasks_type, #mission_mod::CuMsgs, #monitor_type, #DEFAULT_CLNB>::new(
1073                        clock,
1074                        &config,
1075                        #mission_mod::#tasks_instanciator,
1076                        #mission_mod::monitor_instanciator,
1077                        copperlist_stream)?,
1078                });
1079
1080                #sim_callback_on_new
1081
1082                application
1083            }
1084
1085            pub fn get_original_config() -> String {
1086                #copper_config_content.to_string()
1087            }
1088
1089            #run_methods
1090        }
1091    };
1092
1093    let builder_name = format_ident!("{}Builder", name);
1094    let (
1095        builder_struct,
1096        builder_new,
1097        builder_impl,
1098        builder_sim_callback_method,
1099        builder_build_sim_callback_arg,
1100    ) = if sim_mode {
1101        (
1102            quote! {
1103                pub struct #builder_name <'a, F> {
1104                    clock: Option<RobotClock>,
1105                    unified_logger: Option<Arc<Mutex<UnifiedLoggerWrite>>>,
1106                    config_override: Option<CuConfig>,
1107                    sim_callback: Option<&'a mut F>
1108                }
1109            },
1110            quote! {
1111                pub fn new() -> Self {
1112                    Self {
1113                        clock: None,
1114                        unified_logger: None,
1115                        config_override: None,
1116                        sim_callback: None,
1117                    }
1118                }
1119            },
1120            quote! {
1121                impl<'a, F> #builder_name <'a, F>
1122                where
1123                    F: FnMut(SimStep) -> cu29::simulation::SimOverride,
1124            },
1125            Some(quote! {
1126                pub fn with_sim_callback(mut self, sim_callback: &'a mut F) -> Self
1127                {
1128                    self.sim_callback = Some(sim_callback);
1129                    self
1130                }
1131            }),
1132            Some(quote! {
1133                self.sim_callback
1134                    .ok_or(CuError::from("Sim callback missing from builder"))?,
1135            }),
1136        )
1137    } else {
1138        (
1139            quote! {
1140                pub struct #builder_name {
1141                    clock: Option<RobotClock>,
1142                    unified_logger: Option<Arc<Mutex<UnifiedLoggerWrite>>>,
1143                    config_override: Option<CuConfig>,
1144                }
1145            },
1146            quote! {
1147                pub fn new() -> Self {
1148                    Self {
1149                        clock: None,
1150                        unified_logger: None,
1151                        config_override: None,
1152                    }
1153                }
1154            },
1155            quote! {
1156                impl #builder_name
1157            },
1158            None,
1159            None,
1160        )
1161    };
1162
1163    let application_builder = quote! {
1164        #builder_struct
1165
1166        #builder_impl
1167        {
1168            #builder_new
1169
1170            pub fn with_clock(mut self, clock: RobotClock) -> Self {
1171                self.clock = Some(clock);
1172                self
1173            }
1174
1175            pub fn with_unified_logger(mut self, unified_logger: Arc<Mutex<UnifiedLoggerWrite>>) -> Self {
1176                self.unified_logger = Some(unified_logger);
1177                self
1178            }
1179
1180            pub fn with_context(mut self, copper_ctx: &CopperContext) -> Self {
1181                self.clock = Some(copper_ctx.clock.clone());
1182                self.unified_logger = Some(copper_ctx.unified_logger.clone());
1183                self
1184            }
1185
1186            pub fn with_config(mut self, config_override: CuConfig) -> Self {
1187                    self.config_override = Some(config_override);
1188                    self
1189            }
1190
1191            #builder_sim_callback_method
1192
1193            pub fn build(self) -> CuResult<#name> {
1194                #name::new(
1195                    self.clock
1196                        .ok_or(CuError::from("Clock missing from builder"))?,
1197                    self.unified_logger
1198                        .ok_or(CuError::from("Unified logger missing from builder"))?,
1199                    self.config_override,
1200                    #builder_build_sim_callback_arg
1201                )
1202            }
1203        }
1204    };
1205
1206    #[cfg(feature = "macro_debug")]
1207    eprintln!("[build result]");
1208    // Convert the modified struct back into a TokenStream
1209    let result = quote! {
1210        mod #mission_mod {
1211            use super::*;  // import the modules the main app did.
1212
1213            use cu29::bincode::Encode;
1214            use cu29::bincode::enc::Encoder;
1215            use cu29::bincode::error::EncodeError;
1216            use cu29::bincode::Decode;
1217            use cu29::bincode::de::Decoder;
1218            use cu29::bincode::error::DecodeError;
1219            use cu29::clock::RobotClock;
1220            use cu29::clock::OptionCuTime;
1221            use cu29::clock::ClockProvider;
1222            use cu29::config::CuConfig;
1223            use cu29::config::ComponentConfig;
1224            use cu29::config::MonitorConfig;
1225            use cu29::config::read_configuration;
1226            use cu29::config::read_configuration_str;
1227            use cu29::curuntime::CuRuntime;
1228            use cu29::curuntime::CopperContext;
1229            use cu29::CuResult;
1230            use cu29::CuError;
1231            use cu29::cutask::CuSrcTask;
1232            use cu29::cutask::CuSinkTask;
1233            use cu29::cutask::CuTask;
1234            use cu29::cutask::CuMsg;
1235            use cu29::cutask::CuMsgMetadata;
1236            use cu29::copperlist::CopperList;
1237            use cu29::prelude::debug;
1238            use cu29::monitoring::CuMonitor; // Trait import.
1239            use cu29::monitoring::NoMonitor;
1240            use cu29::monitoring::CuTaskState;
1241            use cu29::monitoring::Decision;
1242            use cu29::prelude::stream_write;
1243            use cu29::prelude::UnifiedLoggerWrite;
1244            use cu29::prelude::UnifiedLogType;
1245            use std::sync::Arc;
1246            use std::sync::Mutex;
1247
1248            // This is the heart of everything.
1249            // CuTasks is the list of all the tasks types.
1250            // CuList is a CopperList with the list of all the messages types as msgs.
1251            pub type CuTasks = #task_types_tuple;
1252
1253            // This is the variation with stubs for the sources and sinks in simulation mode.
1254            pub type CuSimTasks = #task_types_tuple_sim;
1255
1256            pub const TASKS_IDS: &'static [&'static str] = &[#( #all_tasks_ids ),*];
1257
1258            #culist_support
1259
1260            #sim_support
1261
1262            pub fn tasks_instanciator(all_instances_configs: Vec<Option<&ComponentConfig>>) -> CuResult<CuTasks> {
1263                Ok(( #(#task_instances_init_code),*, ))
1264            }
1265
1266            pub fn tasks_instanciator_sim(all_instances_configs: Vec<Option<&ComponentConfig>>) -> CuResult<CuSimTasks> {
1267                Ok(( #(#task_sim_instances_init_code),*, ))
1268            }
1269
1270            pub fn monitor_instanciator(config: &CuConfig) -> #monitor_type {
1271                #monitor_type::new(config, #mission_mod::TASKS_IDS).expect("Failed to create the given monitor.")
1272            }
1273        }
1274
1275        mod app {
1276            use super::*;  // import the modules the main app did.
1277            use std::sync::Arc;
1278            use std::sync::Mutex;
1279            use cu29::clock::RobotClock;
1280            use cu29::curuntime::CuRuntime;
1281            use cu29::curuntime::CopperContext;
1282            use cu29::prelude::debug;
1283            use cu29::prelude::UnifiedLoggerWrite;
1284            use cu29::prelude::stream_write;
1285            use cu29::prelude::UnifiedLogType;
1286            use cu29::config::CuConfig;
1287            use cu29::CuResult;
1288            use cu29::CuError;
1289            use cu29::monitoring::CuTaskState;
1290            use cu29::monitoring::Decision;
1291            use cu29::monitoring::NoMonitor;
1292            use cu29::config::ComponentConfig;
1293
1294            // FIXME(gbin): this is not good.
1295            use default::SimStep;
1296
1297            pub #application_struct
1298
1299            #application_impl
1300
1301            #application_builder
1302        }
1303
1304        use app::#builder_name;
1305        use app::#name;
1306    };
1307    let tokens: TokenStream = result.into();
1308
1309    // Print and format the generated code using rustfmt
1310    #[cfg(feature = "macro_debug")]
1311    {
1312        let formatted_code = rustfmt_generated_code(tokens.to_string());
1313        eprintln!("\n     ===    Gen. Runtime ===\n");
1314        eprintln!("{}", highlight_rust_code(formatted_code));
1315        eprintln!("\n     === === === === === ===\n");
1316    }
1317
1318    tokens
1319}
1320
1321fn read_config(config_file: &str) -> CuResult<CuConfig> {
1322    let filename = config_full_path(config_file);
1323
1324    read_configuration(filename.as_str())
1325}
1326
1327fn config_full_path(config_file: &str) -> String {
1328    let mut config_full_path = utils::caller_crate_root();
1329    config_full_path.push(config_file);
1330    let filename = config_full_path
1331        .as_os_str()
1332        .to_str()
1333        .expect("Could not interpret the config file name");
1334    filename.to_string()
1335}
1336
1337/// Extract all the tasks types in their index order and their ids.
1338fn extract_tasks_types(
1339    copper_config: &CuConfig,
1340) -> (Vec<String>, Vec<CuTaskType>, Vec<String>, Vec<Type>) {
1341    let all_id_nodes = copper_config.get_all_nodes(None); // FIXME(gbin): Multimission
1342
1343    // Get all the tasks Ids
1344    let all_tasks_ids: Vec<String> = all_id_nodes
1345        .iter()
1346        .map(|(_, node)| node.get_id().to_string())
1347        .collect();
1348
1349    let all_task_cutype: Vec<CuTaskType> = all_id_nodes
1350        .iter()
1351        .map(|(id, _)| {
1352            find_task_type_for_id(
1353                copper_config
1354                    .get_graph(None)
1355                    .expect("Only implemented for Simple"),
1356                *id,
1357            )
1358        }) // FIXME(gbin): Multimission
1359        .collect();
1360
1361    // Collect all the type names used by our configs.
1362    let all_types_names: Vec<String> = all_id_nodes
1363        .iter()
1364        .map(|(_, node)| node.get_type().to_string())
1365        .collect();
1366
1367    // Transform them as Rust types
1368    let all_types: Vec<Type> = all_types_names
1369        .iter()
1370        .map(|name| {
1371            parse_str(name)
1372                .unwrap_or_else(|_| panic!("Could not transform {name} into a Task Rust type."))
1373        })
1374        .collect();
1375    (all_tasks_ids, all_task_cutype, all_types_names, all_types)
1376}
1377
1378fn extract_msg_types(runtime_plan: &CuExecutionLoop) -> Vec<Type> {
1379    runtime_plan
1380        .steps
1381        .iter()
1382        .filter_map(|unit| match unit {
1383            CuExecutionUnit::Step(step) => {
1384                if let Some((_, output_msg_type)) = &step.output_msg_index_type {
1385                    Some(
1386                        parse_str::<Type>(output_msg_type.as_str()).unwrap_or_else(|_| {
1387                            panic!(
1388                                "Could not transform {output_msg_type} into a message Rust type."
1389                            )
1390                        }),
1391                    )
1392                } else {
1393                    None
1394                }
1395            }
1396            CuExecutionUnit::Loop(_) => todo!("Needs to be implemented"),
1397        })
1398        .collect()
1399}
1400
1401/// Builds the tuple of the CuList as a tuple off all the messages types.
1402fn build_culist_tuple(all_msgs_types_in_culist_order: &[Type]) -> TypeTuple {
1403    if all_msgs_types_in_culist_order.is_empty() {
1404        parse_quote! { () }
1405    } else {
1406        parse_quote! {
1407            ( #( CuMsg<#all_msgs_types_in_culist_order> ),* )
1408        }
1409    }
1410}
1411
1412/// This is the bincode encoding part of the CuMsgs
1413fn build_culist_tuple_encode(all_msgs_types_in_culist_order: &[Type]) -> ItemImpl {
1414    let indices: Vec<usize> = (0..all_msgs_types_in_culist_order.len()).collect();
1415
1416    // Generate the `self.#i.encode(encoder)?` for each tuple index, including `()` types
1417    let encode_fields: Vec<_> = indices
1418        .iter()
1419        .map(|i| {
1420            let idx = syn::Index::from(*i);
1421            quote! { self.0.#idx.encode(encoder)?; }
1422        })
1423        .collect();
1424
1425    parse_quote! {
1426        impl Encode for CuMsgs {
1427            fn encode<E: Encoder>(&self, encoder: &mut E) -> Result<(), EncodeError> {
1428                #(#encode_fields)*
1429                Ok(())
1430            }
1431        }
1432    }
1433}
1434
1435/// This is the bincode decoding part of the CuMsgs
1436fn build_culist_tuple_decode(all_msgs_types_in_culist_order: &[Type]) -> ItemImpl {
1437    let indices: Vec<usize> = (0..all_msgs_types_in_culist_order.len()).collect();
1438
1439    // Generate the `CuMsg::<T>::decode(decoder)?` for each tuple index
1440    let decode_fields: Vec<_> = indices
1441        .iter()
1442        .map(|i| {
1443            let t = &all_msgs_types_in_culist_order[*i];
1444            quote! { CuMsg::<#t>::decode(decoder)? }
1445        })
1446        .collect();
1447
1448    parse_quote! {
1449        impl Decode<()> for CuMsgs {
1450            fn decode<D: Decoder<Context=()>>(decoder: &mut D) -> Result<Self, DecodeError> {
1451                Ok(CuMsgs ((
1452                    #(#decode_fields),*
1453                )))
1454            }
1455        }
1456    }
1457}
1458
1459fn build_culist_tuple_debug(all_msgs_types_in_culist_order: &[Type]) -> ItemImpl {
1460    let indices: Vec<usize> = (0..all_msgs_types_in_culist_order.len()).collect();
1461
1462    let debug_fields: Vec<_> = indices
1463        .iter()
1464        .map(|i| {
1465            let idx = syn::Index::from(*i);
1466            quote! { .field(&self.0.#idx) }
1467        })
1468        .collect();
1469
1470    parse_quote! {
1471        impl std::fmt::Debug for CuMsgs {
1472            fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1473                f.debug_tuple("CuMsgs")
1474                    #(#debug_fields)*
1475                    .finish()
1476            }
1477        }
1478    }
1479}
1480
1481#[cfg(test)]
1482mod tests {
1483    // See tests/compile_file directory for more information
1484    #[test]
1485    fn test_compile_fail() {
1486        let t = trybuild::TestCases::new();
1487        t.compile_fail("tests/compile_fail/*/*.rs");
1488    }
1489}