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