cu29_derive/
lib.rs

1use proc_macro::TokenStream;
2use quote::{format_ident, quote};
3use std::fs::read_to_string;
4use syn::meta::parser;
5use syn::Fields::{Named, Unnamed};
6use syn::{
7    parse_macro_input, parse_quote, parse_str, Field, Fields, ItemImpl, ItemStruct, LitStr, Type,
8    TypeTuple,
9};
10
11#[cfg(feature = "macro_debug")]
12use crate::format::rustfmt_generated_code;
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
22mod format;
23mod utils;
24
25// TODO: this needs to be determined when the runtime is sizing itself.
26const DEFAULT_CLNB: usize = 10;
27
28#[inline]
29fn int2sliceindex(i: u32) -> syn::Index {
30    syn::Index::from(i as usize)
31}
32
33#[inline(always)]
34fn return_error(msg: String) -> TokenStream {
35    syn::Error::new(Span::call_site(), msg)
36        .to_compile_error()
37        .into()
38}
39
40/// Generates the CopperList content type from a config.
41/// gen_cumsgs!("path/to/config.toml")
42/// It will create a new type called CuStampedDataSet you can pass to the log reader for decoding:
43#[proc_macro]
44pub fn gen_cumsgs(config_path_lit: TokenStream) -> TokenStream {
45    #[cfg(feature = "std")]
46    let std = true;
47
48    #[cfg(not(feature = "std"))]
49    let std = false;
50
51    let config = parse_macro_input!(config_path_lit as LitStr).value();
52    if !std::path::Path::new(&config_full_path(&config)).exists() {
53        return return_error(format!(
54            "The configuration file `{config}` does not exist. Please provide a valid path."
55        ));
56    }
57    #[cfg(feature = "macro_debug")]
58    eprintln!("[gen culist support with {config:?}]");
59    let cuconfig = match read_config(&config) {
60        Ok(cuconfig) => cuconfig,
61        Err(e) => return return_error(e.to_string()),
62    };
63    let graph = cuconfig
64        .get_graph(None) // FIXME(gbin): Multimission
65        .expect("Could not find the specified mission for gen_cumsgs");
66    let runtime_plan: CuExecutionLoop = match compute_runtime_plan(graph) {
67        Ok(plan) => plan,
68        Err(e) => return return_error(format!("Could not compute runtime plan: {e}")),
69    };
70
71    // Give a name compatible with a struct to match the task ids to their output in the CuStampedDataSet tuple.
72    let all_tasks_member_ids: Vec<String> = graph
73        .get_all_nodes()
74        .iter()
75        .map(|(_, node)| utils::config_id_to_struct_member(node.get_id().as_str()))
76        .collect();
77
78    // All accesses are linear on the culist but the id of the tasks is random (determined by the Ron declaration order).
79    // This records the task ids in call order.
80    let taskid_order: Vec<usize> = runtime_plan
81        .steps
82        .iter()
83        .filter_map(|unit| match unit {
84            CuExecutionUnit::Step(step) => Some(step.node_id as usize),
85            _ => None,
86        })
87        .collect();
88
89    #[cfg(feature = "macro_debug")]
90    eprintln!(
91        "[The CuStampedDataSet matching tasks ids are {:?}]",
92        taskid_order
93            .iter()
94            .map(|i| all_tasks_member_ids[*i].clone())
95            .collect::<Vec<_>>()
96    );
97
98    let support = gen_culist_support(&runtime_plan, &taskid_order, &all_tasks_member_ids);
99
100    let extra_imports = if !std {
101        quote! {
102            use core::fmt::Debug;
103            use core::fmt::Formatter;
104            use core::fmt::Result as FmtResult;
105            use alloc::vec;
106        }
107    } else {
108        quote! {
109            use std::fmt::Debug;
110            use std::fmt::Formatter;
111            use std::fmt::Result as FmtResult;
112        }
113    };
114
115    let with_uses = quote! {
116        mod cumsgs {
117            use cu29::bincode::Encode;
118            use cu29::bincode::enc::Encoder;
119            use cu29::bincode::error::EncodeError;
120            use cu29::bincode::Decode;
121            use cu29::bincode::de::Decoder;
122            use cu29::bincode::error::DecodeError;
123            use cu29::copperlist::CopperList;
124            use cu29::prelude::CuStampedData;
125            use cu29::prelude::ErasedCuStampedData;
126            use cu29::prelude::ErasedCuStampedDataSet;
127            use cu29::prelude::MatchingTasks;
128            use cu29::prelude::Serialize;
129            use cu29::prelude::CuMsg;
130            use cu29::prelude::CuMsgMetadata;
131            use cu29::prelude::CuListZeroedInit;
132            use cu29::prelude::CuCompactString;
133            #extra_imports
134            #support
135        }
136        use cumsgs::CuStampedDataSet;
137        type CuMsgs=CuStampedDataSet;
138    };
139    with_uses.into()
140}
141
142/// Build the inner support of the copper list.
143fn gen_culist_support(
144    runtime_plan: &CuExecutionLoop,
145    taskid_call_order: &[usize],
146    all_tasks_as_struct_member_name: &Vec<String>,
147) -> proc_macro2::TokenStream {
148    #[cfg(feature = "macro_debug")]
149    eprintln!("[Extract msgs types]");
150    let all_msgs_types_in_culist_order = extract_msg_types(runtime_plan);
151
152    let culist_size = all_msgs_types_in_culist_order.len();
153    let task_indices: Vec<_> = taskid_call_order
154        .iter()
155        .map(|i| syn::Index::from(*i))
156        .collect();
157
158    #[cfg(feature = "macro_debug")]
159    eprintln!("[build the copperlist struct]");
160    let msgs_types_tuple: TypeTuple = build_culist_tuple(&all_msgs_types_in_culist_order);
161
162    #[cfg(feature = "macro_debug")]
163    eprintln!("[build the copperlist tuple bincode support]");
164    let msgs_types_tuple_encode = build_culist_tuple_encode(&all_msgs_types_in_culist_order);
165    let msgs_types_tuple_decode = build_culist_tuple_decode(&all_msgs_types_in_culist_order);
166
167    #[cfg(feature = "macro_debug")]
168    eprintln!("[build the copperlist tuple debug support]");
169    let msgs_types_tuple_debug = build_culist_tuple_debug(&all_msgs_types_in_culist_order);
170
171    #[cfg(feature = "macro_debug")]
172    eprintln!("[build the copperlist tuple serialize support]");
173    let msgs_types_tuple_serialize = build_culist_tuple_serialize(&all_msgs_types_in_culist_order);
174
175    #[cfg(feature = "macro_debug")]
176    eprintln!("[build the default tuple support]");
177    let msgs_types_tuple_default = build_culist_tuple_default(&all_msgs_types_in_culist_order);
178
179    #[cfg(feature = "macro_debug")]
180    eprintln!("[build erasedcumsgs]");
181
182    let erasedmsg_trait_impl = build_culist_erasedcumsgs(&all_msgs_types_in_culist_order);
183
184    let collect_metadata_function = quote! {
185        pub fn collect_metadata<'a>(culist: &'a CuList) -> [&'a CuMsgMetadata; #culist_size] {
186            [#( &culist.msgs.0.#task_indices.metadata, )*]
187        }
188    };
189
190    let methods = all_tasks_as_struct_member_name
191        .iter()
192        .enumerate()
193        .map(|(task_id, name)| {
194            let output_position = taskid_call_order
195                .iter()
196                .position(|&id| id == task_id)
197                .unwrap_or_else(|| {
198                    panic!("Task {name} (id: {task_id}) not found in execution order")
199                });
200
201            let fn_name = format_ident!("get_{}_output", name);
202            let payload_type = all_msgs_types_in_culist_order[output_position].clone();
203            let index = syn::Index::from(output_position);
204            quote! {
205                #[allow(dead_code)]
206                pub fn #fn_name(&self) -> &CuMsg<#payload_type> {
207                    &self.0.#index
208                }
209            }
210        });
211
212    // This generates a way to get the metadata of every single message of a culist at low cost
213    quote! {
214        #collect_metadata_function
215
216        pub struct CuStampedDataSet(pub #msgs_types_tuple);
217
218        pub type CuList = CopperList<CuStampedDataSet>;
219
220        impl CuStampedDataSet {
221            #(#methods)*
222
223            #[allow(dead_code)]
224            fn get_tuple(&self) -> &#msgs_types_tuple {
225                &self.0
226            }
227
228            #[allow(dead_code)]
229            fn get_tuple_mut(&mut self) -> &mut #msgs_types_tuple {
230                &mut self.0
231            }
232        }
233
234        impl MatchingTasks for CuStampedDataSet {
235            #[allow(dead_code)]
236            fn get_all_task_ids() -> &'static [&'static str] {
237                &[#(#all_tasks_as_struct_member_name),*]
238            }
239        }
240
241        // Adds the bincode support for the copper list tuple
242        #msgs_types_tuple_encode
243        #msgs_types_tuple_decode
244
245        // Adds the debug support
246        #msgs_types_tuple_debug
247
248        // Adds the serialization support
249        #msgs_types_tuple_serialize
250
251        // Adds the default support
252        #msgs_types_tuple_default
253
254        // Adds the type erased CuStampedDataSet support (to help generic serialized conversions)
255        #erasedmsg_trait_impl
256
257        impl CuListZeroedInit for CuStampedDataSet {
258            fn init_zeroed(&mut self) {
259                #(self.0.#task_indices.metadata.status_txt = CuCompactString::default();)*
260            }
261        }
262    }
263}
264
265fn gen_sim_support(runtime_plan: &CuExecutionLoop) -> proc_macro2::TokenStream {
266    #[cfg(feature = "macro_debug")]
267    eprintln!("[Sim: Build SimEnum]");
268    let plan_enum: Vec<proc_macro2::TokenStream> = runtime_plan
269        .steps
270        .iter()
271        .map(|unit| match unit {
272            CuExecutionUnit::Step(step) => {
273                let enum_entry_name = config_id_to_enum(step.node.get_id().as_str());
274                let enum_ident = Ident::new(&enum_entry_name, Span::call_site());
275                let inputs: Vec<Type> = step
276                    .input_msg_indices_types
277                    .iter()
278                    .map(|(_, t)| parse_str::<Type>(format!("CuMsg<{t}>").as_str()).unwrap())
279                    .collect();
280                let output: Option<Type> = step
281                    .output_msg_index_type
282                    .as_ref()
283                    .map(|(_, t)| parse_str::<Type>(format!("CuMsg<{t}>").as_str()).unwrap());
284                let no_output = parse_str::<Type>("CuMsg<()>").unwrap();
285                let output = output.as_ref().unwrap_or(&no_output);
286
287                let inputs_type = if inputs.is_empty() {
288                    quote! { () }
289                } else if inputs.len() == 1 {
290                    let input = inputs.first().unwrap();
291                    quote! { &'a #input }
292                } else {
293                    quote! { &'a (#(&'a #inputs),*) }
294                };
295
296                quote! {
297                    #enum_ident(CuTaskCallbackState<#inputs_type, &'a mut #output>)
298                }
299            }
300            CuExecutionUnit::Loop(_) => {
301                todo!("Needs to be implemented")
302            }
303        })
304        .collect();
305    quote! {
306        // not used if sim is not generated but this is ok.
307        #[allow(dead_code)]
308        pub enum SimStep<'a> {
309            #(#plan_enum),*
310        }
311    }
312}
313
314/// Adds #[copper_runtime(config = "path", sim_mode = false/true)] to your application struct to generate the runtime.
315/// if sim_mode is omitted, it is set to false.
316/// This will add a "runtime" field to your struct and implement the "new" and "run" methods.
317#[proc_macro_attribute]
318pub fn copper_runtime(args: TokenStream, input: TokenStream) -> TokenStream {
319    #[cfg(feature = "macro_debug")]
320    eprintln!("[entry]");
321    let mut application_struct = parse_macro_input!(input as ItemStruct);
322
323    let application_name = &application_struct.ident;
324    let builder_name = format_ident!("{}Builder", application_name);
325
326    let mut config_file: Option<LitStr> = None;
327    let mut sim_mode = false;
328
329    #[cfg(feature = "std")]
330    let std = true;
331
332    #[cfg(not(feature = "std"))]
333    let std = false;
334
335    // Custom parser for the attribute arguments
336    let attribute_config_parser = parser(|meta| {
337        if meta.path.is_ident("config") {
338            config_file = Some(meta.value()?.parse()?);
339            Ok(())
340        } else if meta.path.is_ident("sim_mode") {
341            // Check if `sim_mode` has an explicit value (true/false)
342            if meta.input.peek(syn::Token![=]) {
343                meta.input.parse::<syn::Token![=]>()?;
344                let value: syn::LitBool = meta.input.parse()?;
345                sim_mode = value.value();
346                Ok(())
347            } else {
348                // If no value is provided, default to true
349                sim_mode = true;
350                Ok(())
351            }
352        } else {
353            Err(meta.error("unsupported property"))
354        }
355    });
356
357    #[cfg(feature = "macro_debug")]
358    eprintln!("[parse]");
359    // Parse the provided args with the custom parser
360    parse_macro_input!(args with attribute_config_parser);
361
362    // Adds the generic parameter for the UnifiedLogger if this is a real application (not sim)
363    // This allows to adapt either to the no-std (custom impl) and std (default file based one)
364    // if !sim_mode {
365    //     application_struct
366    //         .generics
367    //         .params
368    //         .push(syn::parse_quote!(L: UnifiedLogWrite + 'static));
369    // }
370
371    // Check if the config file was provided
372    let config_file = match config_file {
373        Some(file) => file.value(),
374        None => {
375            return return_error(
376                "Expected config file attribute like #[CopperRuntime(config = \"path\")]"
377                    .to_string(),
378            )
379        }
380    };
381
382    if !std::path::Path::new(&config_full_path(&config_file)).exists() {
383        return return_error(format!(
384            "The configuration file `{config_file}` does not exist. Please provide a valid path."
385        ));
386    }
387
388    let copper_config = match read_config(&config_file) {
389        Ok(cuconfig) => cuconfig,
390        Err(e) => return return_error(e.to_string()),
391    };
392    let copper_config_content = match read_to_string(config_full_path(config_file.as_str())) {
393        Ok(ok) => ok,
394        Err(e) => return return_error(format!("Could not read the config file (should not happen because we just succeeded just before). {e}"))
395    };
396
397    #[cfg(feature = "macro_debug")]
398    eprintln!("[build monitor type]");
399    let monitor_type = if let Some(monitor_config) = copper_config.get_monitor_config() {
400        let monitor_type = parse_str::<Type>(monitor_config.get_type())
401            .expect("Could not transform the monitor type name into a Rust type.");
402        quote! { #monitor_type }
403    } else {
404        quote! { NoMonitor }
405    };
406
407    // This is common for all the mission as it will be inserted in the respective modules with their local CuTasks, CuStampedDataSet etc...
408    #[cfg(feature = "macro_debug")]
409    eprintln!("[build runtime field]");
410    // add that to a new field
411    let runtime_field: Field = if sim_mode {
412        parse_quote! {
413            copper_runtime: cu29::curuntime::CuRuntime<CuSimTasks, CuStampedDataSet, #monitor_type, #DEFAULT_CLNB>
414        }
415    } else {
416        parse_quote! {
417            copper_runtime: cu29::curuntime::CuRuntime<CuTasks, CuStampedDataSet, #monitor_type, #DEFAULT_CLNB>
418        }
419    };
420
421    #[cfg(feature = "macro_debug")]
422    eprintln!("[match struct anonymity]");
423    match &mut application_struct.fields {
424        Named(fields_named) => {
425            fields_named.named.push(runtime_field);
426        }
427        Unnamed(fields_unnamed) => {
428            fields_unnamed.unnamed.push(runtime_field);
429        }
430        Fields::Unit => {
431            panic!("This struct is a unit struct, it should have named or unnamed fields. use struct Something {{}} and not struct Something;")
432        }
433    };
434
435    let all_missions = copper_config.graphs.get_all_missions_graphs();
436    let mut all_missions_tokens = Vec::<proc_macro2::TokenStream>::new();
437    for (mission, graph) in &all_missions {
438        let mission_mod = parse_str::<Ident>(mission.as_str())
439            .expect("Could not make an identifier of the mission name");
440
441        #[cfg(feature = "macro_debug")]
442        eprintln!("[extract tasks ids & types]");
443        let task_specs = CuTaskSpecSet::from_graph(graph);
444
445        #[cfg(feature = "macro_debug")]
446        eprintln!("[runtime plan for mission {mission}]");
447        let runtime_plan: CuExecutionLoop = match compute_runtime_plan(graph) {
448            Ok(plan) => plan,
449            Err(e) => return return_error(format!("Could not compute runtime plan: {e}")),
450        };
451
452        #[cfg(feature = "macro_debug")]
453        eprintln!("{runtime_plan:?}");
454
455        let all_sim_tasks_types: Vec<Type> = task_specs.ids
456            .iter()
457            .zip(&task_specs.cutypes)
458            .zip(&task_specs.sim_task_types)
459            .zip(&task_specs.background_flags)
460            .zip(&task_specs.run_in_sim_flags)
461            .map(|((((task_id, task_type), sim_type), background), run_in_sim)| {
462                match task_type {
463                    CuTaskType::Source => {
464                        if *background {
465                            panic!("CuSrcTask {task_id} cannot be a background task, it should be a regular task.");
466                        }
467                        if *run_in_sim {
468                            sim_type.clone()
469                        } else {
470                            let msg_type = graph
471                                .get_node_output_msg_type(task_id.as_str())
472                                .unwrap_or_else(|| panic!("CuSrcTask {task_id} should have an outgoing connection with a valid output msg type"));
473                            let sim_task_name = format!("CuSimSrcTask<{msg_type}>");
474                            parse_str(sim_task_name.as_str()).unwrap_or_else(|_| panic!("Could not build the placeholder for simulation: {sim_task_name}"))
475                        }
476                    }
477                    CuTaskType::Regular => {
478                        // TODO: wrap that correctly in a background task if background is true.
479                        // run_in_sim has no effect for normal tasks, they are always run in sim as is.
480                        sim_type.clone()
481                    },
482                    CuTaskType::Sink => {
483                        if *background {
484                            panic!("CuSinkTask {task_id} cannot be a background task, it should be a regular task.");
485                        }
486
487                        if *run_in_sim {
488                            // Use the real task in sim if asked to.
489                            sim_type.clone()
490                        }
491                        else {
492                            // Use the placeholder sim task.
493                            let msg_type = graph
494                                .get_node_input_msg_type(task_id.as_str())
495                                .unwrap_or_else(|| panic!("CuSinkTask {task_id} should have an incoming connection with a valid input msg type"));
496                            let sim_task_name = format!("CuSimSinkTask<{msg_type}>");
497                            parse_str(sim_task_name.as_str()).unwrap_or_else(|_| panic!("Could not build the placeholder for simulation: {sim_task_name}"))
498                        }
499                    }
500                }
501    })
502    .collect();
503
504        #[cfg(feature = "macro_debug")]
505        eprintln!("[build task tuples]");
506
507        let task_types = &task_specs.task_types;
508        // Build the tuple of all those types
509        // note the extraneous, at the end is to make the tuple work even if this is only one element
510        let task_types_tuple: TypeTuple = parse_quote! {
511            (#(#task_types),*,)
512        };
513
514        let task_types_tuple_sim: TypeTuple = parse_quote! {
515            (#(#all_sim_tasks_types),*,)
516        };
517
518        #[cfg(feature = "macro_debug")]
519        eprintln!("[gen instances]");
520        // FIXME: implement here the threadpool emulation.
521        let task_sim_instances_init_code = all_sim_tasks_types.iter().enumerate().map(|(index, ty)| {
522            let additional_error_info = format!(
523                "Failed to get create instance for {}, instance index {}.",
524                task_specs.type_names[index], index
525            );
526
527            quote! {
528                <#ty>::new(all_instances_configs[#index]).map_err(|e| e.add_cause(#additional_error_info))?
529            }
530        }).collect::<Vec<_>>();
531
532        let task_instances_init_code = task_specs.instantiation_types.iter().zip(&task_specs.background_flags).enumerate().map(|(index, (task_type, background))| {
533            let additional_error_info = format!(
534                "Failed to get create instance for {}, instance index {}.",
535                task_specs.type_names[index], index
536            );
537            if *background {
538                quote! {
539                    #task_type::new(all_instances_configs[#index], threadpool.clone()).map_err(|e| e.add_cause(#additional_error_info))?
540                }
541            } else {
542                quote! {
543                    #task_type::new(all_instances_configs[#index]).map_err(|e| e.add_cause(#additional_error_info))?
544                }
545            }
546        }).collect::<Vec<_>>();
547
548        // Generate the code to create instances of the nodes
549        // It maps the types to their index
550        let (
551            task_restore_code,
552            start_calls,
553            stop_calls,
554            preprocess_calls,
555            postprocess_calls,
556            ): (Vec<_>, Vec<_>, Vec<_>, Vec<_>, Vec<_>) = itertools::multiunzip(
557            (0..task_specs.task_types.len())
558            .map(|index| {
559                let task_index = int2sliceindex(index as u32);
560                let task_tuple_index = syn::Index::from(index);
561                let task_enum_name = config_id_to_enum(&task_specs.ids[index]);
562                let enum_name = Ident::new(&task_enum_name, Span::call_site());
563                (
564                    // Tasks keyframe restore code
565                    quote! {
566                        tasks.#task_tuple_index.thaw(&mut decoder).map_err(|e| CuError::new_with_cause("Failed to thaw", e))?
567                    },
568                    {  // Start calls
569                        let monitoring_action = quote! {
570                            let decision = self.copper_runtime.monitor.process_error(#index, CuTaskState::Start, &error);
571                            match decision {
572                                Decision::Abort => {
573                                    debug!("Start: ABORT decision from monitoring. Task '{}' errored out \
574                                during start. Aborting all the other starts.", #mission_mod::TASKS_IDS[#index]);
575                                    return Ok(());
576
577                                }
578                                Decision::Ignore => {
579                                    debug!("Start: IGNORE decision from monitoring. Task '{}' errored out \
580                                during start. The runtime will continue.", #mission_mod::TASKS_IDS[#index]);
581                                }
582                                Decision::Shutdown => {
583                                    debug!("Start: SHUTDOWN decision from monitoring. Task '{}' errored out \
584                                during start. The runtime cannot continue.", #mission_mod::TASKS_IDS[#index]);
585                                    return Err(CuError::new_with_cause("Task errored out during start.", error));
586                                }
587                            }
588                        };
589
590                        let call_sim_callback = if sim_mode {
591                            quote! {
592                                // Ask the sim if this task should be executed or overridden by the sim.
593                                let ovr = sim_callback(SimStep::#enum_name(CuTaskCallbackState::Start));
594
595                                let doit = if let SimOverride::Errored(reason) = ovr  {
596                                    let error: CuError = reason.into();
597                                    #monitoring_action
598                                    false
599                               }
600                               else {
601                                    ovr == SimOverride::ExecuteByRuntime
602                               };
603                            }
604                        } else {
605                            quote! {
606                                let doit = true;  // in normal mode always execute the steps in the runtime.
607                            }
608                        };
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.start(&self.copper_runtime.clock) {
616                                    #monitoring_action
617                                }
618                            }
619                        }
620                    },
621                    {  // Stop calls
622                        let monitoring_action = quote! {
623                                    let decision = self.copper_runtime.monitor.process_error(#index, CuTaskState::Stop, &error);
624                                    match decision {
625                                        Decision::Abort => {
626                                            debug!("Stop: ABORT decision from monitoring. Task '{}' errored out \
627                                    during stop. Aborting all the other starts.", #mission_mod::TASKS_IDS[#index]);
628                                            return Ok(());
629
630                                        }
631                                        Decision::Ignore => {
632                                            debug!("Stop: IGNORE decision from monitoring. Task '{}' errored out \
633                                    during stop. The runtime will continue.", #mission_mod::TASKS_IDS[#index]);
634                                        }
635                                        Decision::Shutdown => {
636                                            debug!("Stop: SHUTDOWN decision from monitoring. Task '{}' errored out \
637                                    during stop. The runtime cannot continue.", #mission_mod::TASKS_IDS[#index]);
638                                            return Err(CuError::new_with_cause("Task errored out during stop.", error));
639                                        }
640                                    }
641                            };
642                        let call_sim_callback = if sim_mode {
643                            quote! {
644                                // Ask the sim if this task should be executed or overridden by the sim.
645                                let ovr = sim_callback(SimStep::#enum_name(CuTaskCallbackState::Stop));
646
647                                let doit = if let SimOverride::Errored(reason) = ovr  {
648                                    let error: CuError = reason.into();
649                                    #monitoring_action
650                                    false
651                               }
652                               else {
653                                    ovr == SimOverride::ExecuteByRuntime
654                               };
655                            }
656                        } else {
657                            quote! {
658                                let doit = true;  // in normal mode always execute the steps in the runtime.
659                            }
660                        };
661                        quote! {
662                            #call_sim_callback
663                            if doit {
664                                let task = &mut self.copper_runtime.tasks.#task_index;
665                                if let Err(error) = task.stop(&self.copper_runtime.clock) {
666                                    #monitoring_action
667                                }
668                            }
669                        }
670                    },
671                    {  // Preprocess calls
672                        let monitoring_action = quote! {
673                            let decision = monitor.process_error(#index, CuTaskState::Preprocess, &error);
674                            match decision {
675                                Decision::Abort => {
676                                    debug!("Preprocess: ABORT decision from monitoring. Task '{}' errored out \
677                                during preprocess. Aborting all the other starts.", #mission_mod::TASKS_IDS[#index]);
678                                    return Ok(());
679
680                                }
681                                Decision::Ignore => {
682                                    debug!("Preprocess: IGNORE decision from monitoring. Task '{}' errored out \
683                                during preprocess. The runtime will continue.", #mission_mod::TASKS_IDS[#index]);
684                                }
685                                Decision::Shutdown => {
686                                    debug!("Preprocess: SHUTDOWN decision from monitoring. Task '{}' errored out \
687                                during preprocess. The runtime cannot continue.", #mission_mod::TASKS_IDS[#index]);
688                                    return Err(CuError::new_with_cause("Task errored out during preprocess.", error));
689                                }
690                            }
691                        };
692                        let call_sim_callback = if sim_mode {
693                            quote! {
694                                // Ask the sim if this task should be executed or overridden by the sim.
695                                let ovr = sim_callback(SimStep::#enum_name(CuTaskCallbackState::Preprocess));
696
697                                let doit = if let SimOverride::Errored(reason) = ovr  {
698                                    let error: CuError = reason.into();
699                                    #monitoring_action
700                                    false
701                                } else {
702                                    ovr == SimOverride::ExecuteByRuntime
703                                };
704                            }
705                        } else {
706                            quote! {
707                                let doit = true;  // in normal mode always execute the steps in the runtime.
708                            }
709                        };
710                        quote! {
711                            #call_sim_callback
712                            if doit {
713                                if let Err(error) = tasks.#task_index.preprocess(clock) {
714                                    #monitoring_action
715                                }
716                            }
717                        }
718                    },
719                    {  // Postprocess calls
720                        let monitoring_action = quote! {
721                            let decision = monitor.process_error(#index, CuTaskState::Postprocess, &error);
722                            match decision {
723                                Decision::Abort => {
724                                    debug!("Postprocess: ABORT decision from monitoring. Task '{}' errored out \
725                                during postprocess. Aborting all the other starts.", #mission_mod::TASKS_IDS[#index]);
726                                    return Ok(());
727
728                                }
729                                Decision::Ignore => {
730                                    debug!("Postprocess: IGNORE decision from monitoring. Task '{}' errored out \
731                                during postprocess. The runtime will continue.", #mission_mod::TASKS_IDS[#index]);
732                                }
733                                Decision::Shutdown => {
734                                    debug!("Postprocess: SHUTDOWN decision from monitoring. Task '{}' errored out \
735                                during postprocess. The runtime cannot continue.", #mission_mod::TASKS_IDS[#index]);
736                                    return Err(CuError::new_with_cause("Task errored out during postprocess.", error));
737                                }
738                            }
739                        };
740                        let call_sim_callback = if sim_mode {
741                            quote! {
742                                // Ask the sim if this task should be executed or overridden by the sim.
743                                let ovr = sim_callback(SimStep::#enum_name(CuTaskCallbackState::Postprocess));
744
745                                let doit = if let SimOverride::Errored(reason) = ovr  {
746                                    let error: CuError = reason.into();
747                                    #monitoring_action
748                                    false
749                                } else {
750                                    ovr == SimOverride::ExecuteByRuntime
751                                };
752                            }
753                        } else {
754                            quote! {
755                                let doit = true;  // in normal mode always execute the steps in the runtime.
756                            }
757                        };
758                        quote! {
759                            #call_sim_callback
760                            if doit {
761                                if let Err(error) = tasks.#task_index.postprocess(clock) {
762                                    #monitoring_action
763                                }
764                            }
765                        }
766                    }
767                )
768            })
769        );
770
771        // All accesses are linear on the culist but the id of the tasks is random (determined by the Ron declaration order).
772        // This records the task ids in call order.
773        let mut taskid_call_order: Vec<usize> = Vec::new();
774
775        let runtime_plan_code_and_logging: Vec<(proc_macro2::TokenStream, proc_macro2::TokenStream)> = runtime_plan.steps
776            .iter()
777            .map(|unit| {
778                match unit {
779                    CuExecutionUnit::Step(step) => {
780                        #[cfg(feature = "macro_debug")]
781                        eprintln!(
782                            "{} -> {} as {:?}. task_id: {} Input={:?}, Output={:?}",
783                            step.node.get_id(),
784                            step.node.get_type(),
785                            step.task_type,
786                            step.node_id,
787                            step.input_msg_indices_types,
788                            step.output_msg_index_type
789                        );
790
791                        let node_index = int2sliceindex(step.node_id);
792                        let task_instance = quote! { tasks.#node_index };
793                        let comment_str = format!(
794                            "DEBUG ->> {} ({:?}) Id:{} I:{:?} O:{:?}",
795                            step.node.get_id(),
796                            step.task_type,
797                            step.node_id,
798                            step.input_msg_indices_types,
799                            step.output_msg_index_type
800                        );
801                        let comment_tokens = quote! {
802                            {
803                                let _ = stringify!(#comment_str);
804                            }
805                        };
806                        // let comment_tokens: proc_macro2::TokenStream = parse_str(&comment_str).unwrap();
807                        let tid = step.node_id as usize;
808                        taskid_call_order.push(tid);
809
810                        let task_enum_name = config_id_to_enum(&task_specs.ids[tid]);
811                        let enum_name = Ident::new(&task_enum_name, proc_macro2::Span::call_site());
812
813                        let (process_call, preprocess_logging) = match step.task_type {
814                            CuTaskType::Source => {
815                                if let Some((output_index, _)) = &step.output_msg_index_type {
816                                    let output_culist_index = int2sliceindex(*output_index);
817
818                                    let monitoring_action = quote! {
819                                        debug!("Task {}: Error during process: {}", #mission_mod::TASKS_IDS[#tid], &error);
820                                        let decision = monitor.process_error(#tid, CuTaskState::Process, &error);
821                                        match decision {
822                                            Decision::Abort => {
823                                                debug!("Process: ABORT decision from monitoring. Task '{}' errored out \
824                                            during process. Skipping the processing of CL {}.", #mission_mod::TASKS_IDS[#tid], clid);
825                                                monitor.process_copperlist(&#mission_mod::collect_metadata(&culist))?;
826                                                cl_manager.end_of_processing(clid)?;
827                                                return Ok(()); // this returns early from the one iteration call.
828
829                                            }
830                                            Decision::Ignore => {
831                                                debug!("Process: IGNORE decision from monitoring. Task '{}' errored out \
832                                            during process. The runtime will continue with a forced empty message.", #mission_mod::TASKS_IDS[#tid]);
833                                                let cumsg_output = &mut msgs.#output_culist_index;
834                                                cumsg_output.clear_payload();
835                                            }
836                                            Decision::Shutdown => {
837                                                debug!("Process: SHUTDOWN decision from monitoring. Task '{}' errored out \
838                                            during process. The runtime cannot continue.", #mission_mod::TASKS_IDS[#tid]);
839                                                return Err(CuError::new_with_cause("Task errored out during process.", error));
840                                            }
841                                        }
842                                    };
843                                    let call_sim_callback = if sim_mode {
844                                        quote! {
845                                            let doit = {
846                                                let cumsg_output = &mut msgs.#output_culist_index;
847                                                let state = CuTaskCallbackState::Process((), cumsg_output);
848                                                let ovr = sim_callback(SimStep::#enum_name(state));
849                                                if let SimOverride::Errored(reason) = ovr  {
850                                                    let error: CuError = reason.into();
851                                                    #monitoring_action
852                                                    false
853                                                } else {
854                                                    ovr == SimOverride::ExecuteByRuntime
855                                                }
856                                            };
857                                         }
858                                    } else {
859                                        quote! {
860                                            let  doit = true;  // in normal mode always execute the steps in the runtime.
861                                       }
862                                    };
863
864                                    (quote! {  // process call
865                                        {
866                                            #comment_tokens
867                                            {
868                                                // Maybe freeze the task if this is a "key frame"
869                                                kf_manager.freeze_task(clid, &#task_instance)?;
870                                                #call_sim_callback
871                                                let cumsg_output = &mut msgs.#output_culist_index;
872                                                cumsg_output.metadata.process_time.start = clock.now().into();
873                                                let maybe_error = if doit {
874                                                    #task_instance.process(clock, cumsg_output)
875                                                } else {
876                                                    Ok(())
877                                                };
878                                                cumsg_output.metadata.process_time.end = clock.now().into();
879                                                if let Err(error) = maybe_error {
880                                                    #monitoring_action
881                                                }
882                                            }
883                                        }
884                                    }, {  // logging preprocess
885                                        if !task_specs.logging_enabled[step.node_id as usize] {
886
887                                            #[cfg(feature = "macro_debug")]
888                                            eprintln!(
889                                                "{} -> Logging Disabled",
890                                                step.node.get_id(),
891                                            );
892
893
894                                            let output_culist_index = int2sliceindex(*output_index);
895                                            quote! {
896                                                let mut cumsg_output = &mut culist.msgs.0.#output_culist_index;
897                                                cumsg_output.clear_payload();
898                                            }
899                                        } else {
900                                            #[cfg(feature = "macro_debug")]
901                                            eprintln!(
902                                                "{} -> Logging Enabled",
903                                                step.node.get_id(),
904                                            );
905                                            quote!() // do nothing
906                                        }
907                                    }
908                                    )
909                                } else {
910                                    panic!("Source task should have an output message index.");
911                                }
912                            }
913                            CuTaskType::Sink => {
914                                // collect the indices
915                                let indices = step.input_msg_indices_types.iter().map(|(index, _)| int2sliceindex(*index));
916                                if let Some((output_index, _)) = &step.output_msg_index_type {
917                                    let output_culist_index = int2sliceindex(*output_index);
918
919                                    let monitoring_action = quote! {
920                                        debug!("Task {}: Error during process: {}", #mission_mod::TASKS_IDS[#tid], &error);
921                                        let decision = monitor.process_error(#tid, CuTaskState::Process, &error);
922                                        match decision {
923                                            Decision::Abort => {
924                                                debug!("Process: ABORT decision from monitoring. Task '{}' errored out \
925                                            during process. Skipping the processing of CL {}.", #mission_mod::TASKS_IDS[#tid], clid);
926                                                monitor.process_copperlist(&#mission_mod::collect_metadata(&culist))?;
927                                                cl_manager.end_of_processing(clid)?;
928                                                return Ok(()); // this returns early from the one iteration call.
929
930                                            }
931                                            Decision::Ignore => {
932                                                debug!("Process: IGNORE decision from monitoring. Task '{}' errored out \
933                                            during process. The runtime will continue with a forced empty message.", #mission_mod::TASKS_IDS[#tid]);
934                                                let cumsg_output = &mut msgs.#output_culist_index;
935                                                cumsg_output.clear_payload();
936                                            }
937                                            Decision::Shutdown => {
938                                                debug!("Process: SHUTDOWN decision from monitoring. Task '{}' errored out \
939                                            during process. The runtime cannot continue.", #mission_mod::TASKS_IDS[#tid]);
940                                                return Err(CuError::new_with_cause("Task errored out during process.", error));
941                                            }
942                                        }
943                                    };
944
945                                    let call_sim_callback = if sim_mode {
946                                        let inputs_type = if indices.len() == 1 {
947                                            // Not a tuple for a single input
948                                            quote! { #(msgs.#indices)* }
949                                        } else {
950                                            // A tuple for multiple inputs
951                                            quote! { (#(&msgs.#indices),*) }
952                                        };
953
954                                        quote! {
955                                            let doit = {
956                                                let cumsg_input = &#inputs_type;
957                                                // This is the virtual output for the sink
958                                                let cumsg_output = &mut msgs.#output_culist_index;
959                                                let state = CuTaskCallbackState::Process(cumsg_input, cumsg_output);
960                                                let ovr = sim_callback(SimStep::#enum_name(state));
961
962                                                if let SimOverride::Errored(reason) = ovr  {
963                                                    let error: CuError = reason.into();
964                                                    #monitoring_action
965                                                    false
966                                                } else {
967                                                    ovr == SimOverride::ExecuteByRuntime
968                                                }
969                                            };
970                                         }
971                                    } else {
972                                        quote! {
973                                            let doit = true;  // in normal mode always execute the steps in the runtime.
974                                       }
975                                    };
976
977                                    let indices = step.input_msg_indices_types.iter().map(|(index, _)| int2sliceindex(*index));
978
979                                    let inputs_type = if indices.len() == 1 {
980                                        // Not a tuple for a single input
981                                        quote! { #(msgs.#indices)* }
982                                    } else {
983                                        // A tuple for multiple inputs
984                                        quote! { (#(&msgs.#indices),*) }
985                                    };
986
987                                    (quote! {
988                                        {
989                                            #comment_tokens
990                                            // Maybe freeze the task if this is a "key frame"
991                                            kf_manager.freeze_task(clid, &#task_instance)?;
992                                            #call_sim_callback
993                                            let cumsg_input = &#inputs_type;
994                                            // This is the virtual output for the sink
995                                            let cumsg_output = &mut msgs.#output_culist_index;
996                                            cumsg_output.metadata.process_time.start = clock.now().into();
997                                            let maybe_error = if doit {#task_instance.process(clock, cumsg_input)} else {Ok(())};
998                                            cumsg_output.metadata.process_time.end = clock.now().into();
999                                            if let Err(error) = maybe_error {
1000                                                #monitoring_action
1001                                            }
1002                                        }
1003                                    }, {
1004                                        quote!() // do nothing for logging
1005                                    })
1006                                } else {
1007                                    panic!("Sink tasks should have a virtual output message index.");
1008                                }
1009                            }
1010                            CuTaskType::Regular => {
1011                                let indices = step.input_msg_indices_types.iter().map(|(index, _)| int2sliceindex(*index));
1012                                if let Some((output_index, _)) = &step.output_msg_index_type {
1013                                    let output_culist_index = int2sliceindex(*output_index);
1014
1015                                    let monitoring_action = quote! {
1016                                        debug!("Task {}: Error during process: {}", #mission_mod::TASKS_IDS[#tid], &error);
1017                                        let decision = monitor.process_error(#tid, CuTaskState::Process, &error);
1018                                        match decision {
1019                                            Decision::Abort => {
1020                                                debug!("Process: ABORT decision from monitoring. Task '{}' errored out \
1021                                            during process. Skipping the processing of CL {}.", #mission_mod::TASKS_IDS[#tid], clid);
1022                                                monitor.process_copperlist(&#mission_mod::collect_metadata(&culist))?;
1023                                                cl_manager.end_of_processing(clid)?;
1024                                                return Ok(()); // this returns early from the one iteration call.
1025
1026                                            }
1027                                            Decision::Ignore => {
1028                                                debug!("Process: IGNORE decision from monitoring. Task '{}' errored out \
1029                                            during process. The runtime will continue with a forced empty message.", #mission_mod::TASKS_IDS[#tid]);
1030                                                let cumsg_output = &mut msgs.#output_culist_index;
1031                                                cumsg_output.clear_payload();
1032                                            }
1033                                            Decision::Shutdown => {
1034                                                debug!("Process: SHUTDOWN decision from monitoring. Task '{}' errored out \
1035                                            during process. The runtime cannot continue.", #mission_mod::TASKS_IDS[#tid]);
1036                                                return Err(CuError::new_with_cause("Task errored out during process.", error));
1037                                            }
1038                                        }
1039                                    };
1040
1041                                    let call_sim_callback = if sim_mode {
1042                                        let inputs_type = if indices.len() == 1 {
1043                                            // Not a tuple for a single input
1044                                            quote! { #(msgs.#indices)* }
1045                                        } else {
1046                                            // A tuple for multiple inputs
1047                                            quote! { (#(&msgs.#indices),*) }
1048                                        };
1049
1050                                        quote! {
1051                                            let doit = {
1052                                                let cumsg_input = &#inputs_type;
1053                                                let cumsg_output = &mut msgs.#output_culist_index;
1054                                                let state = CuTaskCallbackState::Process(cumsg_input, cumsg_output);
1055                                                let ovr = sim_callback(SimStep::#enum_name(state));
1056
1057                                                if let SimOverride::Errored(reason) = ovr  {
1058                                                    let error: CuError = reason.into();
1059                                                    #monitoring_action
1060                                                    false
1061                                                }
1062                                                else {
1063                                                    ovr == SimOverride::ExecuteByRuntime
1064                                                }
1065                                            };
1066                                         }
1067                                    } else {
1068                                        quote! {
1069                                            let doit = true;  // in normal mode always execute the steps in the runtime.
1070                                       }
1071                                    };
1072
1073                                    let indices = step.input_msg_indices_types.iter().map(|(index, _)| int2sliceindex(*index));
1074                                    let inputs_type = if indices.len() == 1 {
1075                                        // Not a tuple for a single input
1076                                        quote! { #(msgs.#indices)* }
1077                                    } else {
1078                                        // A tuple for multiple inputs
1079                                        quote! { (#(&msgs.#indices),*) }
1080                                    };
1081
1082                                    (quote! {
1083                                        {
1084                                            #comment_tokens
1085                                            // Maybe freeze the task if this is a "key frame"
1086                                            kf_manager.freeze_task(clid, &#task_instance)?;
1087                                            #call_sim_callback
1088                                            let cumsg_input = &#inputs_type;
1089                                            let cumsg_output = &mut msgs.#output_culist_index;
1090                                            cumsg_output.metadata.process_time.start = clock.now().into();
1091                                            let maybe_error = if doit {#task_instance.process(clock, cumsg_input, cumsg_output)} else {Ok(())};
1092                                            cumsg_output.metadata.process_time.end = clock.now().into();
1093                                            if let Err(error) = maybe_error {
1094                                                #monitoring_action
1095                                            }
1096                                        }
1097                                    }, {
1098
1099                                    if !task_specs.logging_enabled[step.node_id as usize] {
1100                                        #[cfg(feature = "macro_debug")]
1101                                        eprintln!(
1102                                            "{} -> Logging Disabled",
1103                                            step.node.get_id(),
1104                                        );
1105                                        let output_culist_index = int2sliceindex(*output_index);
1106                                        quote! {
1107                                                let mut cumsg_output = &mut culist.msgs.0.#output_culist_index;
1108                                                cumsg_output.clear_payload();
1109                                            }
1110                                   } else {
1111                                        #[cfg(feature = "macro_debug")]
1112                                        eprintln!(
1113                                            "{} -> Logging Enabled",
1114                                            step.node.get_id(),
1115                                        );
1116                                         quote!() // do nothing logging is enabled
1117                                   }
1118                                })
1119                                } else {
1120                                    panic!("Regular task should have an output message index.");
1121                                }
1122                            }
1123                        };
1124
1125                        (process_call, preprocess_logging)
1126                    }
1127                    CuExecutionUnit::Loop(_) => todo!("Needs to be implemented"),
1128                }
1129            }).collect();
1130        #[cfg(feature = "macro_debug")]
1131        eprintln!("[Culist access order:  {taskid_call_order:?}]");
1132
1133        // Give a name compatible with a struct to match the task ids to their output in the CuStampedDataSet tuple.
1134        let all_tasks_member_ids: Vec<String> = task_specs
1135            .ids
1136            .iter()
1137            .map(|name| utils::config_id_to_struct_member(name.as_str()))
1138            .collect();
1139
1140        #[cfg(feature = "macro_debug")]
1141        eprintln!("[build the copperlist support]");
1142        let culist_support: proc_macro2::TokenStream =
1143            gen_culist_support(&runtime_plan, &taskid_call_order, &all_tasks_member_ids);
1144
1145        #[cfg(feature = "macro_debug")]
1146        eprintln!("[build the sim support]");
1147        let sim_support = if sim_mode {
1148            Some(gen_sim_support(&runtime_plan))
1149        } else {
1150            None
1151        };
1152
1153        let (new, run_one_iteration, start_all_tasks, stop_all_tasks, run) = if sim_mode {
1154            (
1155                quote! {
1156                    fn new(clock:RobotClock, unified_logger: Arc<Mutex<L>>, config_override: Option<CuConfig>, sim_callback: &mut impl FnMut(SimStep) -> SimOverride) -> CuResult<Self>
1157                },
1158                quote! {
1159                    fn run_one_iteration(&mut self, sim_callback: &mut impl FnMut(SimStep) -> SimOverride) -> CuResult<()>
1160                },
1161                quote! {
1162                    fn start_all_tasks(&mut self, sim_callback: &mut impl FnMut(SimStep) -> SimOverride) -> CuResult<()>
1163                },
1164                quote! {
1165                    fn stop_all_tasks(&mut self, sim_callback: &mut impl FnMut(SimStep) -> SimOverride) -> CuResult<()>
1166                },
1167                quote! {
1168                    fn run(&mut self, sim_callback: &mut impl FnMut(SimStep) -> SimOverride) -> CuResult<()>
1169                },
1170            )
1171        } else {
1172            (
1173                if std {
1174                    quote! {
1175                        fn new(clock:RobotClock, unified_logger: Arc<Mutex<L>>, config_override: Option<CuConfig>) -> CuResult<Self>
1176                    }
1177                } else {
1178                    quote! {
1179                        // no config override is possible in no-std
1180                        fn new(clock:RobotClock, unified_logger: Arc<Mutex<L>>) -> CuResult<Self>
1181                    }
1182                },
1183                quote! {
1184                    fn run_one_iteration(&mut self) -> CuResult<()>
1185                },
1186                quote! {
1187                    fn start_all_tasks(&mut self) -> CuResult<()>
1188                },
1189                quote! {
1190                    fn stop_all_tasks(&mut self) -> CuResult<()>
1191                },
1192                quote! {
1193                    fn run(&mut self) -> CuResult<()>
1194                },
1195            )
1196        };
1197
1198        let sim_callback_arg = if sim_mode {
1199            Some(quote!(sim_callback))
1200        } else {
1201            None
1202        };
1203
1204        let app_trait = if sim_mode {
1205            quote!(CuSimApplication)
1206        } else {
1207            quote!(CuApplication)
1208        };
1209
1210        let sim_callback_on_new_calls = task_specs.ids.iter().enumerate().map(|(i, id)| {
1211            let enum_name = config_id_to_enum(id);
1212            let enum_ident = Ident::new(&enum_name, Span::call_site());
1213            quote! {
1214                // the answer is ignored, we have to instantiate the tasks anyway.
1215                sim_callback(SimStep::#enum_ident(CuTaskCallbackState::New(all_instances_configs[#i].cloned())));
1216            }
1217        });
1218
1219        let sim_callback_on_new = if sim_mode {
1220            Some(quote! {
1221                let graph = config.get_graph(Some(#mission)).expect("Could not find the mission #mission");
1222                let all_instances_configs: Vec<Option<&ComponentConfig>> = graph
1223                    .get_all_nodes()
1224                    .iter()
1225                    .map(|(_, node)| node.get_instance_config())
1226                    .collect();
1227                #(#sim_callback_on_new_calls)*
1228            })
1229        } else {
1230            None
1231        };
1232
1233        let (runtime_plan_code, preprocess_logging_calls): (Vec<_>, Vec<_>) =
1234            itertools::multiunzip(runtime_plan_code_and_logging);
1235
1236        let config_load_stmt = if std {
1237            quote! {
1238                let config = if let Some(overridden_config) = config_override {
1239                    debug!("CuConfig: Overridden programmatically: {}", overridden_config.serialize_ron());
1240                    overridden_config
1241                } else if ::std::path::Path::new(config_filename).exists() {
1242                    debug!("CuConfig: Reading configuration from file: {}", config_filename);
1243                    cu29::config::read_configuration(config_filename)?
1244                } else {
1245                    let original_config = <Self as #app_trait<S, L>>::get_original_config();
1246                    debug!("CuConfig: Using the original configuration the project was compiled with: {}", &original_config);
1247                    cu29::config::read_configuration_str(original_config, None)?
1248                };
1249            }
1250        } else {
1251            quote! {
1252                // Only the original config is available in no-std
1253                let original_config = <Self as #app_trait<S, L>>::get_original_config();
1254                debug!("CuConfig: Using the original configuration the project was compiled with: {}", &original_config);
1255                let config = cu29::config::read_configuration_str(original_config, None)?;
1256            }
1257        };
1258
1259        let kill_handler = if std {
1260            Some(quote! {
1261                ctrlc::set_handler(move || {
1262                    STOP_FLAG.store(true, Ordering::SeqCst);
1263                }).expect("Error setting Ctrl-C handler");
1264            })
1265        } else {
1266            None
1267        };
1268
1269        let run_loop = if std {
1270            quote! {
1271                loop  {
1272                    let iter_start = self.copper_runtime.clock.now();
1273                    let result = <Self as #app_trait<S, L>>::run_one_iteration(self, #sim_callback_arg);
1274
1275                    if let Some(rate) = self.copper_runtime.runtime_config.rate_target_hz {
1276                        let period: CuDuration = (1_000_000_000u64 / rate).into();
1277                        let elapsed = self.copper_runtime.clock.now() - iter_start;
1278                        if elapsed < period {
1279                            std::thread::sleep(std::time::Duration::from_nanos(period.as_nanos() - elapsed.as_nanos()));
1280                        }
1281                    }
1282
1283                    if STOP_FLAG.load(Ordering::SeqCst) || result.is_err() {
1284                        break result;
1285                    }
1286                }
1287            }
1288        } else {
1289            quote! {
1290                loop  {
1291                    let iter_start = self.copper_runtime.clock.now();
1292                    let result = <Self as #app_trait<S, L>>::run_one_iteration(self, #sim_callback_arg);
1293                    if let Some(rate) = self.copper_runtime.runtime_config.rate_target_hz {
1294                        let period: CuDuration = (1_000_000_000u64 / rate).into();
1295                        let elapsed = self.copper_runtime.clock.now() - iter_start;
1296                        if elapsed < period {
1297                            busy_wait_for(period - elapsed);
1298                        }
1299                    }
1300
1301                    if STOP_FLAG.load(Ordering::SeqCst) || result.is_err() {
1302                        break result;
1303                    }
1304                }
1305            }
1306        };
1307
1308        #[cfg(feature = "macro_debug")]
1309        eprintln!("[build the run methods]");
1310        let run_methods = quote! {
1311
1312            #run_one_iteration {
1313
1314                // Pre-explode the runtime to avoid complexity with partial borrowing in the generated code.
1315                let runtime = &mut self.copper_runtime;
1316                let clock = &runtime.clock;
1317                let monitor = &mut runtime.monitor;
1318                let tasks = &mut runtime.tasks;
1319                let cl_manager = &mut runtime.copperlists_manager;
1320                let kf_manager = &mut runtime.keyframes_manager;
1321
1322                // Preprocess calls can happen at any time, just packed them up front.
1323                #(#preprocess_calls)*
1324
1325                let culist = cl_manager.inner.create().expect("Ran out of space for copper lists"); // FIXME: error handling
1326                let clid = culist.id;
1327                kf_manager.reset(clid, clock); // beginning of processing, we empty the serialized frozen states of the tasks.
1328                culist.change_state(cu29::copperlist::CopperListState::Processing);
1329                culist.msgs.init_zeroed();
1330                {
1331                    let msgs = &mut culist.msgs.0;
1332                    #(#runtime_plan_code)*
1333                } // drop(msgs);
1334                monitor.process_copperlist(&#mission_mod::collect_metadata(&culist))?;
1335
1336                // here drop the payloads if we don't want them to be logged.
1337                #(#preprocess_logging_calls)*
1338
1339                cl_manager.end_of_processing(clid)?;
1340                kf_manager.end_of_processing(clid)?;
1341
1342                // Postprocess calls can happen at any time, just packed them up at the end.
1343                #(#postprocess_calls)*
1344                Ok(())
1345            }
1346
1347            fn restore_keyframe(&mut self, keyframe: &KeyFrame) -> CuResult<()> {
1348                let runtime = &mut self.copper_runtime;
1349                let clock = &runtime.clock;
1350                let tasks = &mut runtime.tasks;
1351                let config = cu29::bincode::config::standard();
1352                let reader = cu29::bincode::de::read::SliceReader::new(&keyframe.serialized_tasks);
1353                let mut decoder = DecoderImpl::new(reader, config, ());
1354                #(#task_restore_code);*;
1355                Ok(())
1356            }
1357
1358            #start_all_tasks {
1359                #(#start_calls)*
1360                self.copper_runtime.monitor.start(&self.copper_runtime.clock)?;
1361                Ok(())
1362            }
1363
1364            #stop_all_tasks {
1365                #(#stop_calls)*
1366                self.copper_runtime.monitor.stop(&self.copper_runtime.clock)?;
1367                Ok(())
1368            }
1369
1370            #run {
1371                static STOP_FLAG: AtomicBool = AtomicBool::new(false);
1372
1373                #kill_handler
1374
1375                <Self as #app_trait<S, L>>::start_all_tasks(self, #sim_callback_arg)?;
1376                let result = #run_loop;
1377
1378                if result.is_err() {
1379                    error!("A task errored out: {}", &result);
1380                }
1381                <Self as #app_trait<S, L>>::stop_all_tasks(self, #sim_callback_arg)?;
1382                result
1383            }
1384        };
1385
1386        let tasks_type = if sim_mode {
1387            quote!(CuSimTasks)
1388        } else {
1389            quote!(CuTasks)
1390        };
1391
1392        let tasks_instanciator_fn = if sim_mode {
1393            quote!(tasks_instanciator_sim)
1394        } else {
1395            quote!(tasks_instanciator)
1396        };
1397
1398        let app_impl_decl = if sim_mode {
1399            quote!(impl<S: SectionStorage + 'static, L: UnifiedLogWrite<S> + 'static> CuSimApplication<S, L> for #application_name)
1400        } else {
1401            quote!(impl<S: SectionStorage + 'static, L: UnifiedLogWrite<S> + 'static> CuApplication<S, L> for #application_name)
1402        };
1403
1404        let simstep_type_decl = if sim_mode {
1405            quote!(
1406                type Step<'z> = SimStep<'z>;
1407            )
1408        } else {
1409            quote!()
1410        };
1411
1412        #[cfg(feature = "std")]
1413        #[cfg(feature = "macro_debug")]
1414        eprintln!("[build result]");
1415        let application_impl = quote! {
1416            #app_impl_decl {
1417                #simstep_type_decl
1418
1419                #new {
1420                    let config_filename = #config_file;
1421
1422                    #config_load_stmt
1423
1424                    // For simple cases we can say the section is just a bunch of Copper Lists.
1425                    // But we can now have allocations outside of it so we can override it from the config.
1426                    let mut default_section_size = size_of::<super::#mission_mod::CuList>() * 64;
1427                    // Check if there is a logging configuration with section_size_mib
1428                    if let Some(section_size_mib) = config.logging.as_ref().and_then(|l| l.section_size_mib) {
1429                        // Convert MiB to bytes
1430                        default_section_size = section_size_mib as usize * 1024usize * 1024usize;
1431                    }
1432                    let copperlist_stream = stream_write::<#mission_mod::CuList, S>(
1433                        unified_logger.clone(),
1434                        UnifiedLogType::CopperList,
1435                        default_section_size,
1436                        // the 2 sizes are not directly related as we encode the CuList but we can
1437                        // assume the encoded size is close or lower than the non encoded one
1438                        // This is to be sure we have the size of at least a Culist and some.
1439                    )?;
1440
1441                    let keyframes_stream = stream_write::<KeyFrame, S>(
1442                        unified_logger.clone(),
1443                        UnifiedLogType::FrozenTasks,
1444                        1024 * 1024 * 10, // 10 MiB
1445                    )?;
1446
1447
1448                    let application = Ok(#application_name {
1449                        copper_runtime: CuRuntime::<#mission_mod::#tasks_type, #mission_mod::CuStampedDataSet, #monitor_type, #DEFAULT_CLNB>::new(
1450                            clock,
1451                            &config,
1452                            Some(#mission),
1453                            #mission_mod::#tasks_instanciator_fn,
1454                            #mission_mod::monitor_instanciator,
1455                            copperlist_stream,
1456                            keyframes_stream)?, // FIXME: gbin
1457                    });
1458
1459                    #sim_callback_on_new
1460
1461                    application
1462                }
1463
1464                fn get_original_config() -> String {
1465                    #copper_config_content.to_string()
1466                }
1467
1468                #run_methods
1469            }
1470        };
1471
1472        let (
1473            builder_struct,
1474            builder_new,
1475            builder_impl,
1476            builder_sim_callback_method,
1477            builder_build_sim_callback_arg,
1478        ) = if sim_mode {
1479            (
1480                quote! {
1481                    #[allow(dead_code)]
1482                    pub struct #builder_name <'a, F> {
1483                        clock: Option<RobotClock>,
1484                        unified_logger: Option<Arc<Mutex<UnifiedLoggerWrite>>>,
1485                        config_override: Option<CuConfig>,
1486                        sim_callback: Option<&'a mut F>
1487                    }
1488                },
1489                quote! {
1490                    #[allow(dead_code)]
1491                    pub fn new() -> Self {
1492                        Self {
1493                            clock: None,
1494                            unified_logger: None,
1495                            config_override: None,
1496                            sim_callback: None,
1497                        }
1498                    }
1499                },
1500                quote! {
1501                    impl<'a, F> #builder_name <'a, F>
1502                    where
1503                        F: FnMut(SimStep) -> SimOverride,
1504                },
1505                Some(quote! {
1506                    pub fn with_sim_callback(mut self, sim_callback: &'a mut F) -> Self
1507                    {
1508                        self.sim_callback = Some(sim_callback);
1509                        self
1510                    }
1511                }),
1512                Some(quote! {
1513                    self.sim_callback
1514                        .ok_or(CuError::from("Sim callback missing from builder"))?,
1515                }),
1516            )
1517        } else {
1518            (
1519                quote! {
1520                    #[allow(dead_code)]
1521                    pub struct #builder_name {
1522                        clock: Option<RobotClock>,
1523                        unified_logger: Option<Arc<Mutex<UnifiedLoggerWrite>>>,
1524                        config_override: Option<CuConfig>,
1525                    }
1526                },
1527                quote! {
1528                    #[allow(dead_code)]
1529                    pub fn new() -> Self {
1530                        Self {
1531                            clock: None,
1532                            unified_logger: None,
1533                            config_override: None,
1534                        }
1535                    }
1536                },
1537                quote! {
1538                    impl #builder_name
1539                },
1540                None,
1541                None,
1542            )
1543        };
1544
1545        // backward compat on std non-parameterized impl.
1546        let std_application_impl = if sim_mode {
1547            // sim mode
1548            Some(quote! {
1549                        impl #application_name {
1550                            pub fn start_all_tasks(&mut self, sim_callback: &mut impl FnMut(SimStep) -> SimOverride) -> CuResult<()> {
1551                                <Self as #app_trait<MmapSectionStorage, UnifiedLoggerWrite>>::start_all_tasks(self, sim_callback)
1552                            }
1553                            pub fn run_one_iteration(&mut self, sim_callback: &mut impl FnMut(SimStep) -> SimOverride) -> CuResult<()> {
1554                                <Self as #app_trait<MmapSectionStorage, UnifiedLoggerWrite>>::run_one_iteration(self, sim_callback)
1555                            }
1556                            pub fn run(&mut self, sim_callback: &mut impl FnMut(SimStep) -> SimOverride) -> CuResult<()> {
1557                                <Self as #app_trait<MmapSectionStorage, UnifiedLoggerWrite>>::run(self, sim_callback)
1558                            }
1559                            pub fn stop_all_tasks(&mut self, sim_callback: &mut impl FnMut(SimStep) -> SimOverride) -> CuResult<()> {
1560                                <Self as #app_trait<MmapSectionStorage, UnifiedLoggerWrite>>::stop_all_tasks(self, sim_callback)
1561                            }
1562                        }
1563            })
1564        } else if std {
1565            // std and normal mode, we use the memory mapped starage for those
1566            Some(quote! {
1567                        impl #application_name {
1568                            pub fn start_all_tasks(&mut self) -> CuResult<()> {
1569                                <Self as #app_trait<MmapSectionStorage, UnifiedLoggerWrite>>::start_all_tasks(self)
1570                            }
1571                            pub fn run_one_iteration(&mut self) -> CuResult<()> {
1572                                <Self as #app_trait<MmapSectionStorage, UnifiedLoggerWrite>>::run_one_iteration(self)
1573                            }
1574                            pub fn run(&mut self) -> CuResult<()> {
1575                                <Self as #app_trait<MmapSectionStorage, UnifiedLoggerWrite>>::run(self)
1576                            }
1577                            pub fn stop_all_tasks(&mut self) -> CuResult<()> {
1578                                <Self as #app_trait<MmapSectionStorage, UnifiedLoggerWrite>>::stop_all_tasks(self)
1579                            }
1580                        }
1581            })
1582        } else {
1583            None // if no-std, let the user figure our the correct logger type they need to provide anyway.
1584        };
1585
1586        let application_builder = if std {
1587            Some(quote! {
1588                #builder_struct
1589
1590                #builder_impl
1591                {
1592                    #builder_new
1593
1594                    #[allow(dead_code)]
1595                    pub fn with_clock(mut self, clock: RobotClock) -> Self {
1596                        self.clock = Some(clock);
1597                        self
1598                    }
1599
1600                    #[allow(dead_code)]
1601                    pub fn with_unified_logger(mut self, unified_logger: Arc<Mutex<UnifiedLoggerWrite>>) -> Self {
1602                        self.unified_logger = Some(unified_logger);
1603                        self
1604                    }
1605
1606                    #[allow(dead_code)]
1607                    pub fn with_context(mut self, copper_ctx: &CopperContext) -> Self {
1608                        self.clock = Some(copper_ctx.clock.clone());
1609                        self.unified_logger = Some(copper_ctx.unified_logger.clone());
1610                        self
1611                    }
1612
1613                    #[allow(dead_code)]
1614                    pub fn with_config(mut self, config_override: CuConfig) -> Self {
1615                            self.config_override = Some(config_override);
1616                            self
1617                    }
1618
1619                    #builder_sim_callback_method
1620
1621                    #[allow(dead_code)]
1622                    pub fn build(self) -> CuResult<#application_name> {
1623                        #application_name::new(
1624                            self.clock
1625                                .ok_or(CuError::from("Clock missing from builder"))?,
1626                            self.unified_logger
1627                                .ok_or(CuError::from("Unified logger missing from builder"))?,
1628                            self.config_override,
1629                            #builder_build_sim_callback_arg
1630                        )
1631                    }
1632                }
1633            })
1634        } else {
1635            // in no-std the user has to construct that manually anyway so don't make any helper here.
1636            None
1637        };
1638
1639        let ids = task_specs.ids;
1640
1641        let sim_imports = if sim_mode {
1642            Some(quote! {
1643                use cu29::simulation::SimOverride;
1644                use cu29::simulation::CuTaskCallbackState;
1645                use cu29::simulation::CuSimSrcTask;
1646                use cu29::simulation::CuSimSinkTask;
1647                use cu29::prelude::app::CuSimApplication;
1648            })
1649        } else {
1650            None
1651        };
1652
1653        let sim_tasks = if sim_mode {
1654            Some(quote! {
1655                // This is the variation with stubs for the sources and sinks in simulation mode.
1656                // Not used if the used doesn't generate Sim.
1657                pub type CuSimTasks = #task_types_tuple_sim;
1658            })
1659        } else {
1660            None
1661        };
1662
1663        let sim_tasks_instanciator = if sim_mode {
1664            Some(quote! {
1665                pub fn tasks_instanciator_sim(all_instances_configs: Vec<Option<&ComponentConfig>>, _threadpool: Arc<ThreadPool>) -> CuResult<CuSimTasks> {
1666                    Ok(( #(#task_sim_instances_init_code),*, ))
1667            }})
1668        } else {
1669            None
1670        };
1671
1672        let tasks_instanciator = if std {
1673            quote! {
1674                pub fn tasks_instanciator<'c>(all_instances_configs: Vec<Option<&'c ComponentConfig>>, threadpool: Arc<ThreadPool>) -> CuResult<CuTasks> {
1675                    Ok(( #(#task_instances_init_code),*, ))
1676                }
1677            }
1678        } else {
1679            // no thread pool in the no-std impl
1680            quote! {
1681                pub fn tasks_instanciator<'c>(all_instances_configs: Vec<Option<&'c ComponentConfig>>) -> CuResult<CuTasks> {
1682                    Ok(( #(#task_instances_init_code),*, ))
1683                }
1684            }
1685        };
1686
1687        let imports = if std {
1688            quote! {
1689                use cu29::rayon::ThreadPool;
1690                use cu29::cuasynctask::CuAsyncTask;
1691                use cu29::curuntime::CopperContext;
1692                use cu29::prelude::UnifiedLoggerWrite;
1693                use cu29::prelude::memmap::MmapSectionStorage;
1694                use std::fmt::{Debug, Formatter};
1695                use std::fmt::Result as FmtResult;
1696                use std::mem::size_of;
1697                use std::sync::Arc;
1698                use std::sync::atomic::{AtomicBool, Ordering};
1699                use std::sync::Mutex;
1700            }
1701        } else {
1702            quote! {
1703                use alloc::sync::Arc;
1704                use alloc::string::String;
1705                use alloc::string::ToString;
1706                use core::sync::atomic::{AtomicBool, Ordering};
1707                use core::fmt::{Debug, Formatter};
1708                use core::fmt::Result as FmtResult;
1709                use core::mem::size_of;
1710                use spin::Mutex;
1711            }
1712        };
1713
1714        // Convert the modified struct back into a TokenStream
1715        let mission_mod_tokens = quote! {
1716            mod #mission_mod {
1717                use super::*;  // import the modules the main app did.
1718
1719                use cu29::bincode::Encode;
1720                use cu29::bincode::enc::Encoder;
1721                use cu29::bincode::error::EncodeError;
1722                use cu29::bincode::Decode;
1723                use cu29::bincode::de::Decoder;
1724                use cu29::bincode::de::DecoderImpl;
1725                use cu29::bincode::error::DecodeError;
1726                use cu29::clock::RobotClock;
1727                use cu29::config::CuConfig;
1728                use cu29::config::ComponentConfig;
1729                use cu29::curuntime::CuRuntime;
1730                use cu29::curuntime::KeyFrame;
1731                use cu29::CuResult;
1732                use cu29::CuError;
1733                use cu29::cutask::CuSrcTask;
1734                use cu29::cutask::CuSinkTask;
1735                use cu29::cutask::CuTask;
1736                use cu29::cutask::CuMsg;
1737                use cu29::cutask::CuMsgMetadata;
1738                use cu29::copperlist::CopperList;
1739                use cu29::monitoring::CuMonitor; // Trait import.
1740                use cu29::monitoring::CuTaskState;
1741                use cu29::monitoring::Decision;
1742                use cu29::prelude::app::CuApplication;
1743                use cu29::prelude::debug;
1744                use cu29::prelude::stream_write;
1745                use cu29::prelude::UnifiedLogType;
1746                use cu29::prelude::UnifiedLogWrite;
1747
1748                #imports
1749
1750                #sim_imports
1751
1752                // Not used if a monitor is present
1753                #[allow(unused_imports)]
1754                use cu29::monitoring::NoMonitor;
1755
1756                // This is the heart of everything.
1757                // CuTasks is the list of all the tasks types.
1758                // CuList is a CopperList with the list of all the messages types as msgs.
1759                pub type CuTasks = #task_types_tuple;
1760
1761                #sim_tasks
1762                #sim_support
1763                #sim_tasks_instanciator
1764
1765                pub const TASKS_IDS: &'static [&'static str] = &[#( #ids ),*];
1766
1767                #culist_support
1768                #tasks_instanciator
1769
1770                pub fn monitor_instanciator(config: &CuConfig) -> #monitor_type {
1771                    #monitor_type::new(config, #mission_mod::TASKS_IDS).expect("Failed to create the given monitor.")
1772                }
1773
1774                // The application for this mission
1775                pub #application_struct
1776
1777                #application_impl
1778
1779                #std_application_impl
1780
1781                #application_builder
1782            }
1783
1784        };
1785        all_missions_tokens.push(mission_mod_tokens);
1786    }
1787
1788    let default_application_tokens = if all_missions.contains_key("default") {
1789        let default_builder = if std {
1790            Some(quote! {
1791                // you can bypass the builder and not use it
1792                #[allow(unused_imports)]
1793                use default::#builder_name;
1794            })
1795        } else {
1796            None
1797        };
1798        quote! {
1799            #default_builder
1800
1801            #[allow(unused_imports)]
1802            use default::#application_name;
1803        }
1804    } else {
1805        quote!() // do nothing
1806    };
1807
1808    let result: proc_macro2::TokenStream = quote! {
1809        #(#all_missions_tokens)*
1810        #default_application_tokens
1811    };
1812
1813    // Print and format the generated code using rustfmt
1814    #[cfg(feature = "macro_debug")]
1815    {
1816        let formatted_code = rustfmt_generated_code(result.to_string());
1817        eprintln!("\n     ===    Gen. Runtime ===\n");
1818        eprintln!("{formatted_code}");
1819        // if you need colors back: eprintln!("{}", highlight_rust_code(formatted_code)); was disabled for cubuild.
1820        // or simply use cargo expand
1821        eprintln!("\n     === === === === === ===\n");
1822    }
1823    result.into()
1824}
1825
1826fn read_config(config_file: &str) -> CuResult<CuConfig> {
1827    let filename = config_full_path(config_file);
1828
1829    read_configuration(filename.as_str())
1830}
1831
1832fn config_full_path(config_file: &str) -> String {
1833    let mut config_full_path = utils::caller_crate_root();
1834    config_full_path.push(config_file);
1835    let filename = config_full_path
1836        .as_os_str()
1837        .to_str()
1838        .expect("Could not interpret the config file name");
1839    filename.to_string()
1840}
1841
1842fn extract_tasks_output_types(graph: &CuGraph) -> Vec<Option<Type>> {
1843    let result = graph
1844        .get_all_nodes()
1845        .iter()
1846        .map(|(_, node)| {
1847            let id = node.get_id();
1848            let type_str = graph.get_node_output_msg_type(id.as_str());
1849            let result = type_str.map(|type_str| {
1850                let result = parse_str::<Type>(type_str.as_str())
1851                    .expect("Could not parse output message type.");
1852                result
1853            });
1854            result
1855        })
1856        .collect();
1857    result
1858}
1859
1860struct CuTaskSpecSet {
1861    pub ids: Vec<String>,
1862    pub cutypes: Vec<CuTaskType>,
1863    pub background_flags: Vec<bool>,
1864    pub logging_enabled: Vec<bool>,
1865    pub type_names: Vec<String>,
1866    pub task_types: Vec<Type>,
1867    pub instantiation_types: Vec<Type>,
1868    pub sim_task_types: Vec<Type>,
1869    pub run_in_sim_flags: Vec<bool>,
1870    #[allow(dead_code)]
1871    pub output_types: Vec<Option<Type>>,
1872}
1873
1874impl CuTaskSpecSet {
1875    pub fn from_graph(graph: &CuGraph) -> Self {
1876        let all_id_nodes = graph.get_all_nodes();
1877
1878        let ids = all_id_nodes
1879            .iter()
1880            .map(|(_, node)| node.get_id().to_string())
1881            .collect();
1882
1883        let cutypes = all_id_nodes
1884            .iter()
1885            .map(|(id, _)| find_task_type_for_id(graph, *id))
1886            .collect();
1887
1888        let background_flags: Vec<bool> = all_id_nodes
1889            .iter()
1890            .map(|(_, node)| node.is_background())
1891            .collect();
1892
1893        let logging_enabled: Vec<bool> = all_id_nodes
1894            .iter()
1895            .map(|(_, node)| node.is_logging_enabled())
1896            .collect();
1897
1898        let type_names: Vec<String> = all_id_nodes
1899            .iter()
1900            .map(|(_, node)| node.get_type().to_string())
1901            .collect();
1902
1903        let output_types = extract_tasks_output_types(graph);
1904
1905        let task_types = type_names
1906            .iter()
1907            .zip(background_flags.iter())
1908            .zip(output_types.iter())
1909            .map(|((name, &background), output_type)| {
1910                let name_type = parse_str::<Type>(name).unwrap_or_else(|error| {
1911                    panic!("Could not transform {name} into a Task Rust type: {error}");
1912                });
1913                if background {
1914                    if let Some(output_type) = output_type {
1915                        parse_quote!(CuAsyncTask<#name_type, #output_type>)
1916                    } else {
1917                        panic!("{name}: If a task is background, it has to have an output");
1918                    }
1919                } else {
1920                    name_type
1921                }
1922            })
1923            .collect();
1924
1925        let instantiation_types = type_names
1926            .iter()
1927            .zip(background_flags.iter())
1928            .zip(output_types.iter())
1929            .map(|((name, &background), output_type)| {
1930                let name_type = parse_str::<Type>(name).unwrap_or_else(|error| {
1931                    panic!("Could not transform {name} into a Task Rust type: {error}");
1932                });
1933                if background {
1934                    if let Some(output_type) = output_type {
1935                        parse_quote!(CuAsyncTask::<#name_type, #output_type>)
1936                    } else {
1937                        panic!("{name}: If a task is background, it has to have an output");
1938                    }
1939                } else {
1940                    name_type
1941                }
1942            })
1943            .collect();
1944
1945        let sim_task_types = type_names
1946            .iter()
1947            .map(|name| {
1948                parse_str::<Type>(name).unwrap_or_else(|err| {
1949                    eprintln!("Could not transform {name} into a Task Rust type.");
1950                    panic!("{err}")
1951                })
1952            })
1953            .collect();
1954
1955        let run_in_sim_flags = all_id_nodes
1956            .iter()
1957            .map(|(_, node)| node.is_run_in_sim())
1958            .collect();
1959
1960        Self {
1961            ids,
1962            cutypes,
1963            background_flags,
1964            logging_enabled,
1965            type_names,
1966            task_types,
1967            instantiation_types,
1968            sim_task_types,
1969            run_in_sim_flags,
1970            output_types,
1971        }
1972    }
1973}
1974
1975fn extract_msg_types(runtime_plan: &CuExecutionLoop) -> Vec<Type> {
1976    runtime_plan
1977        .steps
1978        .iter()
1979        .filter_map(|unit| match unit {
1980            CuExecutionUnit::Step(step) => {
1981                if let Some((_, output_msg_type)) = &step.output_msg_index_type {
1982                    Some(
1983                        parse_str::<Type>(output_msg_type.as_str()).unwrap_or_else(|_| {
1984                            panic!(
1985                                "Could not transform {output_msg_type} into a message Rust type."
1986                            )
1987                        }),
1988                    )
1989                } else {
1990                    None
1991                }
1992            }
1993            CuExecutionUnit::Loop(_) => todo!("Needs to be implemented"),
1994        })
1995        .collect()
1996}
1997
1998/// Builds the tuple of the CuList as a tuple off all the messages types.
1999fn build_culist_tuple(all_msgs_types_in_culist_order: &[Type]) -> TypeTuple {
2000    if all_msgs_types_in_culist_order.is_empty() {
2001        parse_quote! { () }
2002    } else {
2003        parse_quote! {
2004            ( #( CuMsg<#all_msgs_types_in_culist_order> ),* )
2005        }
2006    }
2007}
2008
2009/// This is the bincode encoding part of the CuStampedDataSet
2010fn build_culist_tuple_encode(all_msgs_types_in_culist_order: &[Type]) -> ItemImpl {
2011    let indices: Vec<usize> = (0..all_msgs_types_in_culist_order.len()).collect();
2012
2013    // Generate the `self.#i.encode(encoder)?` for each tuple index, including `()` types
2014    let encode_fields: Vec<_> = indices
2015        .iter()
2016        .map(|i| {
2017            let idx = syn::Index::from(*i);
2018            quote! { self.0.#idx.encode(encoder)?; }
2019        })
2020        .collect();
2021
2022    parse_quote! {
2023        impl Encode for CuStampedDataSet {
2024            fn encode<E: Encoder>(&self, encoder: &mut E) -> Result<(), EncodeError> {
2025                #(#encode_fields)*
2026                Ok(())
2027            }
2028        }
2029    }
2030}
2031
2032/// This is the bincode decoding part of the CuStampedDataSet
2033fn build_culist_tuple_decode(all_msgs_types_in_culist_order: &[Type]) -> ItemImpl {
2034    let indices: Vec<usize> = (0..all_msgs_types_in_culist_order.len()).collect();
2035
2036    // Generate the `CuStampedData::<T>::decode(decoder)?` for each tuple index
2037    let decode_fields: Vec<_> = indices
2038        .iter()
2039        .map(|i| {
2040            let t = &all_msgs_types_in_culist_order[*i];
2041            quote! { CuMsg::<#t>::decode(decoder)? }
2042        })
2043        .collect();
2044
2045    parse_quote! {
2046        impl Decode<()> for CuStampedDataSet {
2047            fn decode<D: Decoder<Context=()>>(decoder: &mut D) -> Result<Self, DecodeError> {
2048                Ok(CuStampedDataSet ((
2049                    #(#decode_fields),*
2050                )))
2051            }
2052        }
2053    }
2054}
2055
2056fn build_culist_erasedcumsgs(all_msgs_types_in_culist_order: &[Type]) -> ItemImpl {
2057    let indices: Vec<usize> = (0..all_msgs_types_in_culist_order.len()).collect();
2058    let casted_fields: Vec<_> = indices
2059        .iter()
2060        .map(|i| {
2061            let idx = syn::Index::from(*i);
2062            quote! { &self.0.#idx as &dyn ErasedCuStampedData }
2063        })
2064        .collect();
2065    parse_quote! {
2066        impl ErasedCuStampedDataSet for CuStampedDataSet {
2067            fn cumsgs(&self) -> Vec<&dyn ErasedCuStampedData> {
2068                vec![
2069                    #(#casted_fields),*
2070                ]
2071            }
2072        }
2073    }
2074}
2075
2076fn build_culist_tuple_debug(all_msgs_types_in_culist_order: &[Type]) -> ItemImpl {
2077    let indices: Vec<usize> = (0..all_msgs_types_in_culist_order.len()).collect();
2078
2079    let debug_fields: Vec<_> = indices
2080        .iter()
2081        .map(|i| {
2082            let idx = syn::Index::from(*i);
2083            quote! { .field(&self.0.#idx) }
2084        })
2085        .collect();
2086
2087    parse_quote! {
2088        impl Debug for CuStampedDataSet {
2089            fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
2090                f.debug_tuple("CuStampedDataSet")
2091                    #(#debug_fields)*
2092                    .finish()
2093            }
2094        }
2095    }
2096}
2097
2098/// This is the serde serialization part of the CuStampedDataSet
2099fn build_culist_tuple_serialize(all_msgs_types_in_culist_order: &[Type]) -> ItemImpl {
2100    let indices: Vec<usize> = (0..all_msgs_types_in_culist_order.len()).collect();
2101    let tuple_len = all_msgs_types_in_culist_order.len();
2102
2103    // Generate the serialization for each tuple field
2104    let serialize_fields: Vec<_> = indices
2105        .iter()
2106        .map(|i| {
2107            let idx = syn::Index::from(*i);
2108            quote! { &self.0.#idx }
2109        })
2110        .collect();
2111
2112    parse_quote! {
2113        impl Serialize for CuStampedDataSet {
2114            fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
2115            where
2116                S: serde::Serializer,
2117            {
2118                use serde::ser::SerializeTuple;
2119                let mut tuple = serializer.serialize_tuple(#tuple_len)?;
2120                #(tuple.serialize_element(#serialize_fields)?;)*
2121                tuple.end()
2122            }
2123        }
2124    }
2125}
2126
2127/// This is the default implementation for CuStampedDataSet
2128fn build_culist_tuple_default(all_msgs_types_in_culist_order: &[Type]) -> ItemImpl {
2129    // Generate the serialization for each tuple field
2130    let default_fields: Vec<_> = all_msgs_types_in_culist_order
2131        .iter()
2132        .map(|msg_type| quote! { CuStampedData::<#msg_type, CuMsgMetadata>::default() })
2133        .collect();
2134
2135    parse_quote! {
2136        impl Default for CuStampedDataSet {
2137            fn default() -> CuStampedDataSet
2138            {
2139                CuStampedDataSet((
2140                    #(#default_fields),*
2141                ))
2142            }
2143        }
2144    }
2145}
2146
2147#[cfg(test)]
2148mod tests {
2149    // See tests/compile_file directory for more information
2150    #[test]
2151    fn test_compile_fail() {
2152        let t = trybuild::TestCases::new();
2153        t.compile_fail("tests/compile_fail/*/*.rs");
2154    }
2155}