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
820                                        let inputs_type = if indices.len() == 1 {
821                                            // Not a tuple for a single input
822                                            quote! { #(&msgs.#indices)* }
823                                        } else {
824                                            // A tuple for multiple inputs
825                                            quote! { (#(&msgs.#indices),*) }
826                                        };
827
828                                        quote! {
829                                            let doit = {
830                                                let cumsg_input = #inputs_type;
831                                                // This is the virtual output for the sink
832                                                let cumsg_output = &mut msgs.#output_culist_index;
833                                                let state = cu29::simulation::CuTaskCallbackState::Process(cumsg_input, cumsg_output);
834                                                let ovr = sim_callback(SimStep::#enum_name(state));
835
836                                                if let cu29::simulation::SimOverride::Errored(reason) = ovr  {
837                                                    let error: CuError = reason.into();
838                                                    #monitoring_action
839                                                    false
840                                                } else {
841                                                    ovr == cu29::simulation::SimOverride::ExecuteByRuntime
842                                                }
843                                            };
844                                         }
845                                    } else {
846                                        quote! {
847                                            let doit = true;  // in normal mode always execute the steps in the runtime.
848                                       }
849                                    };
850
851                                    let indices = step.input_msg_indices_types.iter().map(|(index, _)| int2sliceindex(*index));
852
853                                    let inputs_type = if indices.len() == 1 {
854                                        // Not a tuple for a single input
855                                        quote! { #(&msgs.#indices)* }
856                                    } else {
857                                        // A tuple for multiple inputs
858                                        quote! { (#(&msgs.#indices),*) }
859                                    };
860
861                                    quote! {
862                                        {
863                                            #comment_tokens
864                                            // Maybe freeze the task if this is a "key frame"
865                                            kf_manager.freeze_task(clid, &#task_instance)?;
866                                            #call_sim_callback
867                                            let cumsg_input = #inputs_type;
868                                            // This is the virtual output for the sink
869                                            let cumsg_output = &mut msgs.#output_culist_index;
870                                            cumsg_output.metadata.process_time.start = clock.now().into();
871                                            let maybe_error = if doit {#task_instance.process(clock, cumsg_input)} else {Ok(())};
872                                            cumsg_output.metadata.process_time.end = clock.now().into();
873                                            if let Err(error) = maybe_error {
874                                                #monitoring_action
875                                            }
876                                        }
877                                    }
878                                } else {
879                                    panic!("Sink tasks should have a virtual output message index.");
880                                }
881                            }
882                            CuTaskType::Regular => {
883                                let indices = step.input_msg_indices_types.iter().map(|(index, _)| int2sliceindex(*index));
884                                if let Some((output_index, _)) = &step.output_msg_index_type {
885                                    let output_culist_index = int2sliceindex(*output_index);
886
887                                    let monitoring_action = quote! {
888                                        debug!("Task {}: Error during process: {}", #mission_mod::TASKS_IDS[#tid], &error);
889                                        let decision = monitor.process_error(#tid, CuTaskState::Process, &error);
890                                        match decision {
891                                            Decision::Abort => {
892                                                debug!("Process: ABORT decision from monitoring. Task '{}' errored out \
893                                            during process. Skipping the processing of CL {}.", #mission_mod::TASKS_IDS[#tid], clid);
894                                                monitor.process_copperlist(&#mission_mod::collect_metadata(&culist))?;
895                                                cl_manager.end_of_processing(clid);
896                                                return Ok(()); // this returns early from the one iteration call.
897
898                                            }
899                                            Decision::Ignore => {
900                                                debug!("Process: IGNORE decision from monitoring. Task '{}' errored out \
901                                            during process. The runtime will continue with a forced empty message.", #mission_mod::TASKS_IDS[#tid]);
902                                                let cumsg_output = &mut msgs.#output_culist_index;
903                                                cumsg_output.clear_payload();
904                                            }
905                                            Decision::Shutdown => {
906                                                debug!("Process: SHUTDOWN decision from monitoring. Task '{}' errored out \
907                                            during process. The runtime cannot continue.", #mission_mod::TASKS_IDS[#tid]);
908                                                return Err(CuError::new_with_cause("Task errored out during process.", error));
909                                            }
910                                        }
911                                    };
912
913                                    let call_sim_callback = if sim_mode {
914                                        let inputs_type = if indices.len() == 1 {
915                                            // Not a tuple for a single input
916                                            quote! { #(&msgs.#indices)* }
917                                        } else {
918                                            // A tuple for multiple inputs
919                                            quote! { (#(&msgs.#indices),*) }
920                                        };
921
922                                        quote! {
923                                            let doit = {
924                                                let cumsg_input = #inputs_type;
925                                                let cumsg_output = &mut msgs.#output_culist_index;
926                                                let state = cu29::simulation::CuTaskCallbackState::Process(cumsg_input, cumsg_output);
927                                                let ovr = sim_callback(SimStep::#enum_name(state));
928
929                                                if let cu29::simulation::SimOverride::Errored(reason) = ovr  {
930                                                    let error: CuError = reason.into();
931                                                    #monitoring_action
932                                                    false
933                                                }
934                                                else {
935                                                    ovr == cu29::simulation::SimOverride::ExecuteByRuntime
936                                                }
937                                            };
938                                         }
939                                    } else {
940                                        quote! {
941                                            let doit = true;  // in normal mode always execute the steps in the runtime.
942                                       }
943                                    };
944
945                                    let indices = step.input_msg_indices_types.iter().map(|(index, _)| int2sliceindex(*index));
946                                    let inputs_type = if indices.len() == 1 {
947                                        // Not a tuple for a single input
948                                        quote! { #(&msgs.#indices)* }
949                                    } else {
950                                        // A tuple for multiple inputs
951                                        quote! { (#(&msgs.#indices),*) }
952                                    };
953                                    quote! {
954                                        {
955                                            #comment_tokens
956                                            // Maybe freeze the task if this is a "key frame"
957                                            kf_manager.freeze_task(clid, &#task_instance)?;
958                                            #call_sim_callback
959                                            let cumsg_input = #inputs_type;
960                                            let cumsg_output = &mut msgs.#output_culist_index;
961                                            cumsg_output.metadata.process_time.start = clock.now().into();
962                                            let maybe_error = if doit {#task_instance.process(clock, cumsg_input, cumsg_output)} else {Ok(())};
963                                            cumsg_output.metadata.process_time.end = clock.now().into();
964                                            if let Err(error) = maybe_error {
965                                                #monitoring_action
966                                            }
967                                        }
968                                    }
969                                } else {
970                                    panic!("Regular task should have an output message index.");
971                                }
972                            }
973                        };
974
975                        process_call
976                    }
977                    CuExecutionUnit::Loop(_) => todo!("Needs to be implemented"),
978                }
979            }).collect();
980        #[cfg(feature = "macro_debug")]
981        eprintln!("[Culist access order:  {taskid_call_order:?}]");
982
983        // Give a name compatible with a struct to match the task ids to their output in the CuMsgs tuple.
984        let all_tasks_member_ids: Vec<String> = all_tasks_ids
985            .iter()
986            .map(|name| utils::config_id_to_struct_member(name.as_str()))
987            .collect();
988
989        #[cfg(feature = "macro_debug")]
990        eprintln!("[build the copperlist support]");
991        let culist_support: proc_macro2::TokenStream =
992            gen_culist_support(&runtime_plan, &taskid_call_order, &all_tasks_member_ids);
993
994        #[cfg(feature = "macro_debug")]
995        eprintln!("[build the sim support]");
996        let sim_support: proc_macro2::TokenStream = gen_sim_support(&runtime_plan);
997
998        let (new, run_one_iteration, start_all_tasks, stop_all_tasks, run) = if sim_mode {
999            (
1000                quote! {
1001                    fn new(clock:RobotClock, unified_logger: Arc<Mutex<UnifiedLoggerWrite>>, config_override: Option<CuConfig>, sim_callback: &mut impl FnMut(SimStep) -> cu29::simulation::SimOverride) -> CuResult<Self>
1002                },
1003                quote! {
1004                    fn run_one_iteration(&mut self, sim_callback: &mut impl FnMut(SimStep) -> cu29::simulation::SimOverride) -> CuResult<()>
1005                },
1006                quote! {
1007                    fn start_all_tasks(&mut self, sim_callback: &mut impl FnMut(SimStep) -> cu29::simulation::SimOverride) -> CuResult<()>
1008                },
1009                quote! {
1010                    fn stop_all_tasks(&mut self, sim_callback: &mut impl FnMut(SimStep) -> cu29::simulation::SimOverride) -> CuResult<()>
1011                },
1012                quote! {
1013                    fn run(&mut self, sim_callback: &mut impl FnMut(SimStep) -> cu29::simulation::SimOverride) -> CuResult<()>
1014                },
1015            )
1016        } else {
1017            (
1018                quote! {
1019                    fn new(clock:RobotClock, unified_logger: Arc<Mutex<UnifiedLoggerWrite>>, config_override: Option<CuConfig>) -> CuResult<Self>
1020                },
1021                quote! {
1022                    fn run_one_iteration(&mut self) -> CuResult<()>
1023                },
1024                quote! {
1025                    fn start_all_tasks(&mut self) -> CuResult<()>
1026                },
1027                quote! {
1028                    fn stop_all_tasks(&mut self) -> CuResult<()>
1029                },
1030                quote! {
1031                    fn run(&mut self) -> CuResult<()>
1032                },
1033            )
1034        };
1035
1036        let sim_callback_arg = if sim_mode {
1037            Some(quote!(sim_callback))
1038        } else {
1039            None
1040        };
1041
1042        let sim_callback_on_new_calls = all_tasks_ids.iter().enumerate().map(|(i, id)| {
1043            let enum_name = config_id_to_enum(id);
1044            let enum_ident = Ident::new(&enum_name, Span::call_site());
1045            quote! {
1046                // the answer is ignored, we have to instantiate the tasks anyway.
1047                sim_callback(SimStep::#enum_ident(cu29::simulation::CuTaskCallbackState::New(all_instances_configs[#i].cloned())));
1048            }
1049        });
1050
1051        let sim_callback_on_new = if sim_mode {
1052            Some(quote! {
1053                let graph = config.get_graph(Some(#mission)).expect("Could not find the mission #mission");
1054                let all_instances_configs: Vec<Option<&ComponentConfig>> = graph
1055                    .get_all_nodes()
1056                    .iter()
1057                    .map(|(_, node)| node.get_instance_config())
1058                    .collect();
1059                #(#sim_callback_on_new_calls)*
1060            })
1061        } else {
1062            None
1063        };
1064
1065        #[cfg(feature = "macro_debug")]
1066        eprintln!("[build the run methods]");
1067        let run_methods = quote! {
1068
1069            #run_one_iteration {
1070
1071                // Pre-explode the runtime to avoid complexity with partial borrowing in the generated code.
1072                let runtime = &mut self.copper_runtime;
1073                let clock = &runtime.clock;
1074                let monitor = &mut runtime.monitor;
1075                let tasks = &mut runtime.tasks;
1076                let cl_manager = &mut runtime.copperlists_manager;
1077                let kf_manager = &mut runtime.keyframes_manager;
1078
1079                // Preprocess calls can happen at any time, just packed them up front.
1080                #(#preprocess_calls)*
1081
1082                let culist = cl_manager.inner.create().expect("Ran out of space for copper lists"); // FIXME: error handling
1083                let clid = culist.id;
1084                kf_manager.reset(clid, clock); // beginning of processing, we empty the serialized frozen states of the tasks.
1085                culist.change_state(cu29::copperlist::CopperListState::Processing);
1086                {
1087                    let msgs = &mut culist.msgs.0;
1088                    #(#runtime_plan_code)*
1089                } // drop(msgs);
1090                monitor.process_copperlist(&#mission_mod::collect_metadata(&culist))?;
1091                cl_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                self.start_all_tasks(#sim_callback_arg)?;
1123                let error = loop {
1124                    let error = self.run_one_iteration(#sim_callback_arg);
1125                    if error.is_err() {
1126                        break error;
1127                    }
1128                };
1129                debug!("A task errored out: {}", &error);
1130                self.stop_all_tasks(#sim_callback_arg)?;
1131                error
1132            }
1133        };
1134
1135        let tasks_type = if sim_mode {
1136            quote!(CuSimTasks)
1137        } else {
1138            quote!(CuTasks)
1139        };
1140
1141        let tasks_instanciator = if sim_mode {
1142            quote!(tasks_instanciator_sim)
1143        } else {
1144            quote!(tasks_instanciator)
1145        };
1146
1147        let app_impl_decl = if sim_mode {
1148            quote!(impl CuSimApplication for #application_name)
1149        } else {
1150            quote!(impl CuApplication for #application_name)
1151        };
1152        let simstep_type_decl = if sim_mode {
1153            quote!(
1154                type Step<'z> = SimStep<'z>;
1155            )
1156        } else {
1157            quote!()
1158        };
1159
1160        #[cfg(feature = "macro_debug")]
1161        eprintln!("[build result]");
1162        let application_impl = quote! {
1163            #app_impl_decl {
1164                #simstep_type_decl
1165
1166                #new {
1167                    let config_filename = #config_file;
1168                    let config = if config_override.is_some() {
1169                        let overridden_config = config_override.unwrap();
1170                        debug!("CuConfig: Overridden programmatically: {}", &overridden_config.serialize_ron());
1171                        overridden_config
1172                    } else if std::path::Path::new(config_filename).exists() {
1173                        debug!("CuConfig: Reading configuration from file: {}", config_filename);
1174                        cu29::config::read_configuration(config_filename)?
1175                    } else {
1176                        let original_config = Self::get_original_config();
1177                        debug!("CuConfig: Using the original configuration the project was compiled with: {}", &original_config);
1178                        cu29::config::read_configuration_str(original_config, None)?
1179                    };
1180
1181                    // For simple cases we can say the section is just a bunch of Copper Lists.
1182                    // But we can now have allocations outside of it so we can override it from the config.
1183                    let mut default_section_size = std::mem::size_of::<super::#mission_mod::CuList>() * 64;
1184                    // Check if there is a logging configuration with section_size_mib
1185                    if let Some(section_size_mib) = config.logging.as_ref().and_then(|l| l.section_size_mib) {
1186                        // Convert MiB to bytes
1187                        default_section_size = section_size_mib as usize * 1024usize * 1024usize;
1188                    }
1189                    let copperlist_stream = stream_write::<#mission_mod::CuList>(
1190                        unified_logger.clone(),
1191                        UnifiedLogType::CopperList,
1192                        default_section_size,
1193                        // the 2 sizes are not directly related as we encode the CuList but we can
1194                        // assume the encoded size is close or lower than the non encoded one
1195                        // This is to be sure we have the size of at least a Culist and some.
1196                    );
1197
1198                    let keyframes_stream = stream_write::<KeyFrame>(
1199                        unified_logger.clone(),
1200                        UnifiedLogType::FrozenTasks,
1201                        1024 * 1024 * 10, // 10 MiB
1202                    );
1203
1204                    let application = Ok(#application_name {
1205                        copper_runtime: CuRuntime::<#mission_mod::#tasks_type, #mission_mod::CuMsgs, #monitor_type, #DEFAULT_CLNB>::new(
1206                            clock,
1207                            &config,
1208                            Some(#mission),
1209                            #mission_mod::#tasks_instanciator,
1210                            #mission_mod::monitor_instanciator,
1211                            copperlist_stream,
1212                            keyframes_stream)?, // FIXME: gbin
1213                    });
1214
1215                    #sim_callback_on_new
1216
1217                    application
1218                }
1219
1220                fn get_original_config() -> String {
1221                    #copper_config_content.to_string()
1222                }
1223
1224                #run_methods
1225            }
1226        };
1227
1228        let (
1229            builder_struct,
1230            builder_new,
1231            builder_impl,
1232            builder_sim_callback_method,
1233            builder_build_sim_callback_arg,
1234        ) = if sim_mode {
1235            (
1236                quote! {
1237                    #[allow(dead_code)]
1238                    pub struct #builder_name <'a, F> {
1239                        clock: Option<RobotClock>,
1240                        unified_logger: Option<Arc<Mutex<UnifiedLoggerWrite>>>,
1241                        config_override: Option<CuConfig>,
1242                        sim_callback: Option<&'a mut F>
1243                    }
1244                },
1245                quote! {
1246                    #[allow(dead_code)]
1247                    pub fn new() -> Self {
1248                        Self {
1249                            clock: None,
1250                            unified_logger: None,
1251                            config_override: None,
1252                            sim_callback: None,
1253                        }
1254                    }
1255                },
1256                quote! {
1257                    impl<'a, F> #builder_name <'a, F>
1258                    where
1259                        F: FnMut(SimStep) -> cu29::simulation::SimOverride,
1260                },
1261                Some(quote! {
1262                    pub fn with_sim_callback(mut self, sim_callback: &'a mut F) -> Self
1263                    {
1264                        self.sim_callback = Some(sim_callback);
1265                        self
1266                    }
1267                }),
1268                Some(quote! {
1269                    self.sim_callback
1270                        .ok_or(CuError::from("Sim callback missing from builder"))?,
1271                }),
1272            )
1273        } else {
1274            (
1275                quote! {
1276                    #[allow(dead_code)]
1277                    pub struct #builder_name {
1278                        clock: Option<RobotClock>,
1279                        unified_logger: Option<Arc<Mutex<UnifiedLoggerWrite>>>,
1280                        config_override: Option<CuConfig>,
1281                    }
1282                },
1283                quote! {
1284                    #[allow(dead_code)]
1285                    pub fn new() -> Self {
1286                        Self {
1287                            clock: None,
1288                            unified_logger: None,
1289                            config_override: None,
1290                        }
1291                    }
1292                },
1293                quote! {
1294                    impl #builder_name
1295                },
1296                None,
1297                None,
1298            )
1299        };
1300
1301        let application_builder = quote! {
1302            #builder_struct
1303
1304            #builder_impl
1305            {
1306                #builder_new
1307
1308                #[allow(dead_code)]
1309                pub fn with_clock(mut self, clock: RobotClock) -> Self {
1310                    self.clock = Some(clock);
1311                    self
1312                }
1313
1314                #[allow(dead_code)]
1315                pub fn with_unified_logger(mut self, unified_logger: Arc<Mutex<UnifiedLoggerWrite>>) -> Self {
1316                    self.unified_logger = Some(unified_logger);
1317                    self
1318                }
1319
1320                #[allow(dead_code)]
1321                pub fn with_context(mut self, copper_ctx: &CopperContext) -> Self {
1322                    self.clock = Some(copper_ctx.clock.clone());
1323                    self.unified_logger = Some(copper_ctx.unified_logger.clone());
1324                    self
1325                }
1326
1327                #[allow(dead_code)]
1328                pub fn with_config(mut self, config_override: CuConfig) -> Self {
1329                        self.config_override = Some(config_override);
1330                        self
1331                }
1332
1333                #builder_sim_callback_method
1334
1335                #[allow(dead_code)]
1336                pub fn build(self) -> CuResult<#application_name> {
1337                    #application_name::new(
1338                        self.clock
1339                            .ok_or(CuError::from("Clock missing from builder"))?,
1340                        self.unified_logger
1341                            .ok_or(CuError::from("Unified logger missing from builder"))?,
1342                        self.config_override,
1343                        #builder_build_sim_callback_arg
1344                    )
1345                }
1346            }
1347        };
1348
1349        // Convert the modified struct back into a TokenStream
1350        let mission_mod_tokens = quote! {
1351            mod #mission_mod {
1352                use super::*;  // import the modules the main app did.
1353
1354                use cu29::bincode::Encode;
1355                use cu29::bincode::enc::Encoder;
1356                use cu29::bincode::error::EncodeError;
1357                use cu29::bincode::Decode;
1358                use cu29::bincode::de::Decoder;
1359                use cu29::bincode::de::DecoderImpl;
1360                use cu29::bincode::error::DecodeError;
1361                use cu29::clock::RobotClock;
1362                use cu29::config::CuConfig;
1363                use cu29::config::ComponentConfig;
1364                use cu29::curuntime::CuRuntime;
1365                use cu29::curuntime::KeyFrame;
1366                use cu29::curuntime::CopperContext;
1367                use cu29::CuResult;
1368                use cu29::CuError;
1369                use cu29::cutask::CuSrcTask;
1370                use cu29::cutask::CuSinkTask;
1371                use cu29::cutask::CuTask;
1372                use cu29::cutask::CuMsg;
1373                use cu29::cutask::CuMsgMetadata;
1374                use cu29::copperlist::CopperList;
1375                use cu29::monitoring::CuMonitor; // Trait import.
1376                use cu29::monitoring::CuTaskState;
1377                use cu29::monitoring::Decision;
1378                use cu29::prelude::app::CuApplication;
1379                use cu29::prelude::debug;
1380                use cu29::prelude::stream_write;
1381                use cu29::prelude::UnifiedLoggerWrite;
1382                use cu29::prelude::UnifiedLogType;
1383                use std::sync::Arc;
1384                use std::sync::Mutex;
1385
1386                // Not used if the used doesn't generate Sim.
1387                #[allow(unused_imports)]
1388                use cu29::prelude::app::CuSimApplication;
1389
1390                // Not used if a monitor is present
1391                #[allow(unused_imports)]
1392                use cu29::monitoring::NoMonitor;
1393
1394                // This is the heart of everything.
1395                // CuTasks is the list of all the tasks types.
1396                // CuList is a CopperList with the list of all the messages types as msgs.
1397                pub type CuTasks = #task_types_tuple;
1398
1399                // This is the variation with stubs for the sources and sinks in simulation mode.
1400                // Not used if the used doesn't generate Sim.
1401                #[allow(dead_code)]
1402                pub type CuSimTasks = #task_types_tuple_sim;
1403
1404                pub const TASKS_IDS: &'static [&'static str] = &[#( #all_tasks_ids ),*];
1405
1406                #culist_support
1407
1408                #sim_support
1409
1410                pub fn tasks_instanciator(all_instances_configs: Vec<Option<&ComponentConfig>>) -> CuResult<CuTasks> {
1411                    Ok(( #(#task_instances_init_code),*, ))
1412                }
1413
1414                #[allow(dead_code)]
1415                pub fn tasks_instanciator_sim(all_instances_configs: Vec<Option<&ComponentConfig>>) -> CuResult<CuSimTasks> {
1416                    Ok(( #(#task_sim_instances_init_code),*, ))
1417                }
1418
1419                pub fn monitor_instanciator(config: &CuConfig) -> #monitor_type {
1420                    #monitor_type::new(config, #mission_mod::TASKS_IDS).expect("Failed to create the given monitor.")
1421                }
1422
1423                // The application for this mission
1424                pub #application_struct
1425
1426                #application_impl
1427
1428                #application_builder
1429            }
1430
1431        };
1432        all_missions_tokens.push(mission_mod_tokens);
1433    }
1434
1435    let default_application_tokens = if all_missions.contains_key("default") {
1436        quote! {
1437        // you can bypass the builder and not use it
1438        #[allow(unused_imports)]
1439        use default::#builder_name;
1440
1441        #[allow(unused_imports)]
1442        use default::#application_name;
1443        }
1444    } else {
1445        quote! {}
1446    };
1447
1448    let result: proc_macro2::TokenStream = quote! {
1449        #(#all_missions_tokens)*
1450        #default_application_tokens
1451    };
1452
1453    // Print and format the generated code using rustfmt
1454    #[cfg(feature = "macro_debug")]
1455    {
1456        let formatted_code = rustfmt_generated_code(result.to_string());
1457        eprintln!("\n     ===    Gen. Runtime ===\n");
1458        eprintln!("{formatted_code}");
1459        // if you need colors back: eprintln!("{}", highlight_rust_code(formatted_code)); was disabled for cubuild.
1460        // or simply use cargo expand
1461        eprintln!("\n     === === === === === ===\n");
1462    }
1463    result.into()
1464}
1465
1466fn read_config(config_file: &str) -> CuResult<CuConfig> {
1467    let filename = config_full_path(config_file);
1468
1469    read_configuration(filename.as_str())
1470}
1471
1472fn config_full_path(config_file: &str) -> String {
1473    let mut config_full_path = utils::caller_crate_root();
1474    config_full_path.push(config_file);
1475    let filename = config_full_path
1476        .as_os_str()
1477        .to_str()
1478        .expect("Could not interpret the config file name");
1479    filename.to_string()
1480}
1481
1482/// Extract all the tasks types in their index order and their ids.
1483fn extract_tasks_types(graph: &CuGraph) -> (Vec<String>, Vec<CuTaskType>, Vec<String>, Vec<Type>) {
1484    let all_id_nodes = graph.get_all_nodes();
1485
1486    // Get all the tasks Ids
1487    let all_tasks_ids: Vec<String> = all_id_nodes
1488        .iter()
1489        .map(|(_, node)| node.get_id().to_string())
1490        .collect();
1491
1492    let all_task_cutype: Vec<CuTaskType> = all_id_nodes
1493        .iter()
1494        .map(|(id, _)| find_task_type_for_id(graph, *id))
1495        .collect();
1496
1497    // Collect all the type names used by our configs.
1498    let all_types_names: Vec<String> = all_id_nodes
1499        .iter()
1500        .map(|(_, node)| node.get_type().to_string())
1501        .collect();
1502
1503    // Transform them as Rust types
1504    let all_types: Vec<Type> = all_types_names
1505        .iter()
1506        .map(|name| {
1507            parse_str(name)
1508                .unwrap_or_else(|_| panic!("Could not transform {name} into a Task Rust type."))
1509        })
1510        .collect();
1511    (all_tasks_ids, all_task_cutype, all_types_names, all_types)
1512}
1513
1514fn extract_msg_types(runtime_plan: &CuExecutionLoop) -> Vec<Type> {
1515    runtime_plan
1516        .steps
1517        .iter()
1518        .filter_map(|unit| match unit {
1519            CuExecutionUnit::Step(step) => {
1520                if let Some((_, output_msg_type)) = &step.output_msg_index_type {
1521                    Some(
1522                        parse_str::<Type>(output_msg_type.as_str()).unwrap_or_else(|_| {
1523                            panic!(
1524                                "Could not transform {output_msg_type} into a message Rust type."
1525                            )
1526                        }),
1527                    )
1528                } else {
1529                    None
1530                }
1531            }
1532            CuExecutionUnit::Loop(_) => todo!("Needs to be implemented"),
1533        })
1534        .collect()
1535}
1536
1537/// Builds the tuple of the CuList as a tuple off all the messages types.
1538fn build_culist_tuple(all_msgs_types_in_culist_order: &[Type]) -> TypeTuple {
1539    if all_msgs_types_in_culist_order.is_empty() {
1540        parse_quote! { () }
1541    } else {
1542        parse_quote! {
1543            ( #( CuMsg<#all_msgs_types_in_culist_order> ),* )
1544        }
1545    }
1546}
1547
1548/// This is the bincode encoding part of the CuMsgs
1549fn build_culist_tuple_encode(all_msgs_types_in_culist_order: &[Type]) -> ItemImpl {
1550    let indices: Vec<usize> = (0..all_msgs_types_in_culist_order.len()).collect();
1551
1552    // Generate the `self.#i.encode(encoder)?` for each tuple index, including `()` types
1553    let encode_fields: Vec<_> = indices
1554        .iter()
1555        .map(|i| {
1556            let idx = syn::Index::from(*i);
1557            quote! { self.0.#idx.encode(encoder)?; }
1558        })
1559        .collect();
1560
1561    parse_quote! {
1562        impl Encode for CuMsgs {
1563            fn encode<E: Encoder>(&self, encoder: &mut E) -> Result<(), EncodeError> {
1564                #(#encode_fields)*
1565                Ok(())
1566            }
1567        }
1568    }
1569}
1570
1571/// This is the bincode decoding part of the CuMsgs
1572fn build_culist_tuple_decode(all_msgs_types_in_culist_order: &[Type]) -> ItemImpl {
1573    let indices: Vec<usize> = (0..all_msgs_types_in_culist_order.len()).collect();
1574
1575    // Generate the `CuMsg::<T>::decode(decoder)?` for each tuple index
1576    let decode_fields: Vec<_> = indices
1577        .iter()
1578        .map(|i| {
1579            let t = &all_msgs_types_in_culist_order[*i];
1580            quote! { CuMsg::<#t>::decode(decoder)? }
1581        })
1582        .collect();
1583
1584    parse_quote! {
1585        impl Decode<()> for CuMsgs {
1586            fn decode<D: Decoder<Context=()>>(decoder: &mut D) -> Result<Self, DecodeError> {
1587                Ok(CuMsgs ((
1588                    #(#decode_fields),*
1589                )))
1590            }
1591        }
1592    }
1593}
1594
1595fn build_culist_erasedcumsgs(all_msgs_types_in_culist_order: &[Type]) -> ItemImpl {
1596    let indices: Vec<usize> = (0..all_msgs_types_in_culist_order.len()).collect();
1597    let casted_fields: Vec<_> = indices
1598        .iter()
1599        .map(|i| {
1600            let idx = syn::Index::from(*i);
1601            quote! { &self.0.#idx as &dyn ErasedCuMsg }
1602        })
1603        .collect();
1604    parse_quote! {
1605        impl ErasedCuMsgs for CuMsgs {
1606            fn erased_cumsgs(&self) -> Vec<&dyn ErasedCuMsg> {
1607                vec![
1608                    #(#casted_fields),*
1609                ]
1610            }
1611        }
1612    }
1613}
1614
1615fn build_culist_tuple_debug(all_msgs_types_in_culist_order: &[Type]) -> ItemImpl {
1616    let indices: Vec<usize> = (0..all_msgs_types_in_culist_order.len()).collect();
1617
1618    let debug_fields: Vec<_> = indices
1619        .iter()
1620        .map(|i| {
1621            let idx = syn::Index::from(*i);
1622            quote! { .field(&self.0.#idx) }
1623        })
1624        .collect();
1625
1626    parse_quote! {
1627        impl std::fmt::Debug for CuMsgs {
1628            fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1629                f.debug_tuple("CuMsgs")
1630                    #(#debug_fields)*
1631                    .finish()
1632            }
1633        }
1634    }
1635}
1636
1637/// This is the serde serialization part of the CuMsgs
1638fn build_culist_tuple_serialize(all_msgs_types_in_culist_order: &[Type]) -> ItemImpl {
1639    let indices: Vec<usize> = (0..all_msgs_types_in_culist_order.len()).collect();
1640    let tuple_len = all_msgs_types_in_culist_order.len();
1641
1642    // Generate the serialization for each tuple field
1643    let serialize_fields: Vec<_> = indices
1644        .iter()
1645        .map(|i| {
1646            let idx = syn::Index::from(*i);
1647            quote! { &self.0.#idx }
1648        })
1649        .collect();
1650
1651    parse_quote! {
1652        impl Serialize for CuMsgs {
1653            fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
1654            where
1655                S: serde::Serializer,
1656            {
1657                use serde::ser::SerializeTuple;
1658                let mut tuple = serializer.serialize_tuple(#tuple_len)?;
1659                #(tuple.serialize_element(#serialize_fields)?;)*
1660                tuple.end()
1661            }
1662        }
1663    }
1664}
1665
1666#[cfg(test)]
1667mod tests {
1668    // See tests/compile_file directory for more information
1669    #[test]
1670    fn test_compile_fail() {
1671        let t = trybuild::TestCases::new();
1672        t.compile_fail("tests/compile_fail/*/*.rs");
1673    }
1674}