cu29_derive/
lib.rs

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