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