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