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                                    let decision = self.copper_runtime.monitor.process_error(#tid, _CuTaskState::Process, &error);
663                                    match decision {
664                                        _Decision::Abort => {
665                                            debug!("Process: ABORT decision from monitoring. Task '{}' errored out \
666                                            during process. Skipping the processing of CL {}.", TASKS_IDS[#tid], id);
667                                            self.copper_runtime.monitor.process_copperlist(&collect_metadata(&culist))?;
668                                            self.copper_runtime.end_of_processing(id);
669                                            return Ok(()); // this returns early from the one iteration call.
670
671                                        }
672                                        _Decision::Ignore => {
673                                            debug!("Process: IGNORE decision from monitoring. Task '{}' errored out \
674                                            during process. The runtime will continue with a forced empty message.", TASKS_IDS[#tid]);
675                                            cumsg_output.clear_payload();
676                                        }
677                                        _Decision::Shutdown => {
678                                            debug!("Process: SHUTDOWN decision from monitoring. Task '{}' errored out \
679                                            during process. The runtime cannot continue.", TASKS_IDS[#tid]);
680                                            return Err(_CuError::new_with_cause("Task errored out during process.", error));
681                                        }
682                                    }
683                                };
684                                let call_sim_callback = if sim_mode {
685                                    quote! {
686                                        let doit = {
687                                            let ovr = sim_callback(SimStep::#enum_name(cu29::simulation::CuTaskCallbackState::Process((), cumsg_output)));
688                                            if let cu29::simulation::SimOverride::Errored(reason) = ovr  {
689                                                let error: _CuError = reason.into();
690                                                #monitoring_action
691                                                false
692                                            } else {
693                                                ovr == cu29::simulation::SimOverride::ExecuteByRuntime
694                                            }
695                                        };
696                                     }
697                                } else {
698                                    quote! {
699                                        let  doit = true;  // in normal mode always execute the steps in the runtime.
700                                   }
701                                };
702
703                                quote! {
704                                    {
705                                        #comment_tokens
706                                        {
707                                            let cumsg_output = &mut msgs.#output_culist_index;
708                                            #call_sim_callback
709                                            cumsg_output.metadata.process_time.start = self.copper_runtime.clock.now().into();
710                                            let maybe_error = if doit {
711                                                #task_instance.process(&self.copper_runtime.clock, cumsg_output)
712                                            } else {
713                                                Ok(())
714                                            };
715                                            cumsg_output.metadata.process_time.end = self.copper_runtime.clock.now().into();
716                                            if let Err(error) = maybe_error {
717                                                #monitoring_action
718                                            }
719                                        }
720                                    }
721                                }
722                            } else {
723                                panic!("Source task should have an output message index.");
724                            }
725                        }
726                        CuTaskType::Sink => {
727                            // collect the indices
728                            let indices = step.input_msg_indices_types.iter().map(|(index, _)| int2sliceindex(*index));
729                            if let Some((output_index, _)) = &step.output_msg_index_type {
730                                let output_culist_index = int2sliceindex(*output_index);
731
732                                let monitoring_action = quote! {
733                                    let decision = self.copper_runtime.monitor.process_error(#tid, _CuTaskState::Process, &error);
734                                    match decision {
735                                        _Decision::Abort => {
736                                            debug!("Process: ABORT decision from monitoring. Task '{}' errored out \
737                                            during process. Skipping the processing of CL {}.", TASKS_IDS[#tid], id);
738                                            self.copper_runtime.monitor.process_copperlist(&collect_metadata(&culist))?;
739                                            self.copper_runtime.end_of_processing(id);
740                                            return Ok(()); // this returns early from the one iteration call.
741
742                                        }
743                                        _Decision::Ignore => {
744                                            debug!("Process: IGNORE decision from monitoring. Task '{}' errored out \
745                                            during process. The runtime will continue with a forced empty message.", TASKS_IDS[#tid]);
746                                            cumsg_output.clear_payload();
747                                        }
748                                        _Decision::Shutdown => {
749                                            debug!("Process: SHUTDOWN decision from monitoring. Task '{}' errored out \
750                                            during process. The runtime cannot continue.", TASKS_IDS[#tid]);
751                                            return Err(_CuError::new_with_cause("Task errored out during process.", error));
752                                        }
753                                    }
754                                };
755
756                                let call_sim_callback = if sim_mode {
757                                    quote! {
758                                        let doit = {
759                                            let ovr = sim_callback(SimStep::#enum_name(cu29::simulation::CuTaskCallbackState::Process(cumsg_input, cumsg_output)));
760
761                                            if let cu29::simulation::SimOverride::Errored(reason) = ovr  {
762                                                let error: _CuError = reason.into();
763                                                #monitoring_action
764                                                false
765                                            } else {
766                                                ovr == cu29::simulation::SimOverride::ExecuteByRuntime
767                                            }
768                                        };
769                                     }
770                                } else {
771                                    quote! {
772                                        let doit = true;  // in normal mode always execute the steps in the runtime.
773                                   }
774                                };
775                                quote! {
776                                    {
777                                        #comment_tokens
778                                        let cumsg_input = (#(&msgs.#indices),*);
779                                        // This is the virtual output for the sink
780                                        let cumsg_output = &mut msgs.#output_culist_index;
781                                        #call_sim_callback
782                                        cumsg_output.metadata.process_time.start = self.copper_runtime.clock.now().into();
783                                        let maybe_error = if doit {#task_instance.process(&self.copper_runtime.clock, cumsg_input)} else {Ok(())};
784                                        cumsg_output.metadata.process_time.end = self.copper_runtime.clock.now().into();
785                                        if let Err(error) = maybe_error {
786                                            #monitoring_action
787                                        }
788                                    }
789                                }
790                            } else {
791                                panic!("Sink tasks should have a virtual output message index.");
792                            }
793                        }
794                        CuTaskType::Regular => {
795                            let indices = step.input_msg_indices_types.iter().map(|(index, _)| int2sliceindex(*index));
796                            if let Some((output_index, _)) = &step.output_msg_index_type {
797                                let output_culist_index = int2sliceindex(*output_index);
798
799                                let monitoring_action = quote! {
800                                    let decision = self.copper_runtime.monitor.process_error(#tid, _CuTaskState::Process, &error);
801                                    match decision {
802                                        _Decision::Abort => {
803                                            debug!("Process: ABORT decision from monitoring. Task '{}' errored out \
804                                            during process. Skipping the processing of CL {}.", TASKS_IDS[#tid], id);
805                                            self.copper_runtime.monitor.process_copperlist(&collect_metadata(&culist))?;
806                                            self.copper_runtime.end_of_processing(id);
807                                            return Ok(()); // this returns early from the one iteration call.
808
809                                        }
810                                        _Decision::Ignore => {
811                                            debug!("Process: IGNORE decision from monitoring. Task '{}' errored out \
812                                            during process. The runtime will continue with a forced empty message.", TASKS_IDS[#tid]);
813                                            cumsg_output.clear_payload();
814                                        }
815                                        _Decision::Shutdown => {
816                                            debug!("Process: SHUTDOWN decision from monitoring. Task '{}' errored out \
817                                            during process. The runtime cannot continue.", TASKS_IDS[#tid]);
818                                            return Err(_CuError::new_with_cause("Task errored out during process.", error));
819                                        }
820                                    }
821                                };
822
823                                let call_sim_callback = if sim_mode {
824                                    quote! {
825                                        let doit = {
826                                            let ovr = sim_callback(SimStep::#enum_name(cu29::simulation::CuTaskCallbackState::Process(cumsg_input, cumsg_output)));
827
828                                            if let cu29::simulation::SimOverride::Errored(reason) = ovr  {
829                                                let error: _CuError = reason.into();
830                                                #monitoring_action
831                                                false
832                                            }
833                                            else {
834                                                ovr == cu29::simulation::SimOverride::ExecuteByRuntime
835                                            }
836                                        };
837                                     }
838                                } else {
839                                    quote! {
840                                        let doit = true;  // in normal mode always execute the steps in the runtime.
841                                   }
842                                };
843                                quote! {
844                                    {
845                                        #comment_tokens
846                                        let cumsg_input = (#(&msgs.#indices),*);
847                                        let cumsg_output = &mut msgs.#output_culist_index;
848                                        #call_sim_callback
849                                        cumsg_output.metadata.process_time.start = self.copper_runtime.clock.now().into();
850                                        let maybe_error = if doit {#task_instance.process(&self.copper_runtime.clock, cumsg_input, cumsg_output)} else {Ok(())};
851                                        cumsg_output.metadata.process_time.end = self.copper_runtime.clock.now().into();
852                                        if let Err(error) = maybe_error {
853                                            #monitoring_action
854                                        }
855                                    }
856                                }
857                            } else {
858                                panic!("Regular task should have an output message index.");
859                            }
860                        }
861                    };
862
863                    process_call
864                }
865                CuExecutionUnit::Loop(_) => todo!("Needs to be implemented"),
866            }
867        }).collect();
868    #[cfg(feature = "macro_debug")]
869    eprintln!("[Culist access order:  {:?}]", taskid_call_order);
870
871    // Give a name compatible with a struct to match the task ids to their output in the CuMsgs tuple.
872    let all_tasks_member_ids: Vec<String> = all_tasks_ids
873        .iter()
874        .map(|name| utils::config_id_to_struct_member(name.as_str()))
875        .collect();
876
877    #[cfg(feature = "macro_debug")]
878    eprintln!("[build the copperlist support]");
879    let culist_support: proc_macro2::TokenStream =
880        gen_culist_support(&runtime_plan, &taskid_call_order, &all_tasks_member_ids);
881
882    #[cfg(feature = "macro_debug")]
883    eprintln!("[build the sim support]");
884    let sim_support: proc_macro2::TokenStream = gen_sim_support(&runtime_plan);
885
886    let (new, run_one_iteration, start_all_tasks, stop_all_tasks, run) = if sim_mode {
887        (
888            quote! {
889                pub fn new<F>(clock:_RobotClock, unified_logger: _Arc<_Mutex<_UnifiedLoggerWrite>>, config_override: Option<_CuConfig>, sim_callback: &mut F) -> _CuResult<Self>
890                where F: FnMut(SimStep) -> cu29::simulation::SimOverride,
891            },
892            quote! {
893                pub fn run_one_iteration<F>(&mut self, sim_callback: &mut F) -> _CuResult<()>
894                where F: FnMut(SimStep) -> cu29::simulation::SimOverride,
895            },
896            quote! {
897                pub fn start_all_tasks<F>(&mut self, sim_callback: &mut F) -> _CuResult<()>
898                where F: FnMut(SimStep) -> cu29::simulation::SimOverride,
899            },
900            quote! {
901                pub fn stop_all_tasks<F>(&mut self, sim_callback: &mut F) -> _CuResult<()>
902                where F: FnMut(SimStep) -> cu29::simulation::SimOverride,
903            },
904            quote! {
905                pub fn run<F>(&mut self, sim_callback: &mut F) -> _CuResult<()>
906                where F: FnMut(SimStep) -> cu29::simulation::SimOverride,
907            },
908        )
909    } else {
910        (
911            quote! {
912                pub fn new(clock:_RobotClock, unified_logger: _Arc<_Mutex<_UnifiedLoggerWrite>>, config_override: Option<_CuConfig>) -> _CuResult<Self>
913            },
914            quote! {
915                pub fn run_one_iteration(&mut self) -> _CuResult<()>
916            },
917            quote! {
918                pub fn start_all_tasks(&mut self) -> _CuResult<()>
919            },
920            quote! {
921                pub fn stop_all_tasks(&mut self) -> _CuResult<()>
922            },
923            quote! {
924                pub fn run(&mut self) -> _CuResult<()>
925            },
926        )
927    };
928
929    let sim_callback_arg = if sim_mode {
930        Some(quote!(sim_callback))
931    } else {
932        None
933    };
934
935    let sim_callback_on_new_calls = all_tasks_ids.iter().enumerate().map(|(i, id)| {
936        let enum_name = config_id_to_enum(id);
937        let enum_ident = Ident::new(&enum_name, proc_macro2::Span::call_site());
938        quote! {
939            // the answer is ignored, we have to instantiate the tasks anyway.
940            sim_callback(SimStep::#enum_ident(cu29::simulation::CuTaskCallbackState::New(all_instances_configs[#i].cloned())));
941        }
942    });
943
944    let sim_callback_on_new = if sim_mode {
945        Some(quote! {
946            let all_instances_configs: Vec<Option<&_ComponentConfig>> = config
947                .get_all_nodes()
948                .iter()
949                .map(|(_, node)| node.get_instance_config())
950                .collect();
951            #(#sim_callback_on_new_calls)*
952        })
953    } else {
954        None
955    };
956
957    #[cfg(feature = "macro_debug")]
958    eprintln!("[build the run method]");
959    let run_method = quote! {
960
961        #run_one_iteration {
962            #(#preprocess_calls)*
963            {
964                let mut culist: &mut _ = &mut self.copper_runtime.copper_lists_manager.create().expect("Ran out of space for copper lists"); // FIXME: error handling.
965                let id = culist.id;
966                culist.change_state(cu29::copperlist::CopperListState::Processing);
967                {
968                    let msgs = &mut culist.msgs.0;
969                    #(#runtime_plan_code)*
970                } // drop(msgs);
971
972                {
973                    // End of CL monitoring
974                    let md = collect_metadata(&culist);
975                    let e2e = md.last().unwrap().process_time.end.unwrap() - md.first().unwrap().process_time.start.unwrap();
976                    let e2en: u64 = e2e.into();
977                } // drop(md);
978
979                self.copper_runtime.monitor.process_copperlist(&collect_metadata(&culist))?;
980                self.copper_runtime.end_of_processing(id);
981
982           }// drop(culist); avoids a double mutable borrow
983           #(#postprocess_calls)*
984           Ok(())
985        }
986
987        #start_all_tasks {
988            #(#start_calls)*
989            self.copper_runtime.monitor.start(&self.copper_runtime.clock)?;
990            Ok(())
991        }
992
993        #stop_all_tasks {
994            #(#stop_calls)*
995            self.copper_runtime.monitor.stop(&self.copper_runtime.clock)?;
996            Ok(())
997        }
998
999        #run {
1000            self.start_all_tasks(#sim_callback_arg)?;
1001            let error = loop {
1002                let error = self.run_one_iteration(#sim_callback_arg);
1003                if error.is_err() {
1004                    break error;
1005                }
1006            };
1007            debug!("A task errored out: {}", &error);
1008            self.stop_all_tasks(#sim_callback_arg)?;
1009            error
1010        }
1011    };
1012
1013    let tasks_type = if sim_mode {
1014        quote!(CuSimTasks)
1015    } else {
1016        quote!(CuTasks)
1017    };
1018
1019    let tasks_instanciator = if sim_mode {
1020        quote!(tasks_instanciator_sim)
1021    } else {
1022        quote!(tasks_instanciator)
1023    };
1024
1025    let runtime_impl = quote! {
1026        impl #name {
1027
1028            #new {
1029                let config_filename = #config_file;
1030                let config = if config_override.is_some() {
1031                    let overridden_config = config_override.unwrap();
1032                    debug!("CuConfig: Overridden programmatically: {}", &overridden_config.serialize_ron());
1033                    overridden_config
1034                } else if std::path::Path::new(config_filename).exists() {
1035                    debug!("CuConfig: Reading configuration from file: {}", config_filename);
1036                    _read_configuration(config_filename)?
1037                } else {
1038                    let original_config = Self::get_original_config();
1039                    debug!("CuConfig: Using the original configuration the project was compiled with: {}", &original_config);
1040                    _read_configuration_str(original_config)?
1041                };
1042
1043                // For simple cases we can say the section is just a bunch of Copper Lists.
1044                // But we can now have allocations outside of it so we can override it from the config.
1045                let mut default_section_size = std::mem::size_of::<CuList>() * 64;
1046                // Check if there is a logging configuration with section_size_mib
1047                if let Some(section_size_mib) = config.logging.as_ref().and_then(|l| l.section_size_mib) {
1048                    // Convert MiB to bytes
1049                    default_section_size = section_size_mib as usize * 1024usize * 1024usize;
1050                }
1051                let copperlist_stream = _stream_write::<CuList>(
1052                    unified_logger.clone(),
1053                    _UnifiedLogType::CopperList,
1054                    default_section_size,
1055                    // the 2 sizes are not directly related as we encode the CuList but we can
1056                    // assume the encoded size is close or lower than the non encoded one
1057                    // This is to be sure we have the size of at least a Culist and some.
1058                );
1059
1060                let runtime = Ok(#name {
1061                    copper_runtime: _CuRuntime::<#tasks_type, CuMsgs, #monitor_type, #DEFAULT_CLNB>::new(
1062                        clock,
1063                        &config,
1064                        #tasks_instanciator,
1065                        monitor_instanciator,
1066                        copperlist_stream)?,
1067                });
1068
1069                #sim_callback_on_new
1070
1071                runtime
1072            }
1073
1074            pub fn get_original_config() -> String {
1075                #copper_config_content.to_string()
1076            }
1077
1078            #run_method
1079        }
1080    };
1081
1082    let builder_name = format_ident!("{}Builder", name);
1083    let (
1084        builder_struct,
1085        builder_new,
1086        builder_impl,
1087        builder_sim_callback_method,
1088        builder_build_sim_callback_arg,
1089    ) = if sim_mode {
1090        (
1091            quote! {
1092                pub struct #builder_name <'a, F> {
1093                    clock: Option<_RobotClock>,
1094                    unified_logger: Option<_Arc<_Mutex<_UnifiedLoggerWrite>>>,
1095                    config_override: Option<_CuConfig>,
1096                    sim_callback: Option<&'a mut F>
1097                }
1098            },
1099            quote! {
1100                pub fn new() -> Self {
1101                    Self {
1102                        clock: None,
1103                        unified_logger: None,
1104                        config_override: None,
1105                        sim_callback: None,
1106                    }
1107                }
1108            },
1109            quote! {
1110                impl<'a, F> #builder_name <'a, F>
1111                where
1112                    F: FnMut(SimStep) -> cu29::simulation::SimOverride,
1113            },
1114            Some(quote! {
1115                fn with_sim_callback(mut self, sim_callback: &'a mut F) -> Self
1116                {
1117                    self.sim_callback = Some(sim_callback);
1118                    self
1119                }
1120            }),
1121            Some(quote! {
1122                self.sim_callback
1123                    .ok_or(_CuError::from("Sim callback missing from builder"))?,
1124            }),
1125        )
1126    } else {
1127        (
1128            quote! {
1129                pub struct #builder_name {
1130                    clock: Option<_RobotClock>,
1131                    unified_logger: Option<_Arc<_Mutex<_UnifiedLoggerWrite>>>,
1132                    config_override: Option<_CuConfig>,
1133                }
1134            },
1135            quote! {
1136                pub fn new() -> Self {
1137                    Self {
1138                        clock: None,
1139                        unified_logger: None,
1140                        config_override: None,
1141                    }
1142                }
1143            },
1144            quote! {
1145                impl #builder_name
1146            },
1147            None,
1148            None,
1149        )
1150    };
1151
1152    let builder = quote! {
1153        #builder_struct
1154
1155        #builder_impl
1156        {
1157            #builder_new
1158
1159            pub fn with_clock(mut self, clock: _RobotClock) -> Self {
1160                self.clock = Some(clock);
1161                self
1162            }
1163
1164            pub fn with_unified_logger(mut self, unified_logger: _Arc<_Mutex<_UnifiedLoggerWrite>>) -> Self {
1165                self.unified_logger = Some(unified_logger);
1166                self
1167            }
1168
1169            pub fn with_context(mut self, copper_ctx: &_CopperContext) -> Self {
1170                self.clock = Some(copper_ctx.clock.clone());
1171                self.unified_logger = Some(copper_ctx.unified_logger.clone());
1172                self
1173            }
1174
1175            pub fn with_config(mut self, config_override: _CuConfig) -> Self {
1176                    self.config_override = Some(config_override);
1177                    self
1178            }
1179
1180            #builder_sim_callback_method
1181
1182            pub fn build(self) -> _CuResult<#name> {
1183                #name::new(
1184                    self.clock
1185                        .ok_or(_CuError::from("Clock missing from builder"))?,
1186                    self.unified_logger
1187                        .ok_or(_CuError::from("Unified logger missing from builder"))?,
1188                    self.config_override,
1189                    #builder_build_sim_callback_arg
1190                )
1191            }
1192        }
1193    };
1194
1195    #[cfg(feature = "macro_debug")]
1196    eprintln!("[build result]");
1197    // Convert the modified struct back into a TokenStream
1198    let result = quote! {
1199        // import everything with an _ to avoid clashes with the user's code
1200        use cu29::bincode::Encode as _Encode;
1201        use cu29::bincode::enc::Encoder as _Encoder;
1202        use cu29::bincode::error::EncodeError as _EncodeError;
1203        use cu29::bincode::Decode as _Decode;
1204        use cu29::bincode::de::Decoder as _Decoder;
1205        use cu29::bincode::error::DecodeError as _DecodeError;
1206        use cu29::clock::RobotClock as _RobotClock;
1207        use cu29::clock::OptionCuTime as _OptionCuTime;
1208        use cu29::clock::ClockProvider as _ClockProvider;
1209        use cu29::config::CuConfig as _CuConfig;
1210        use cu29::config::ComponentConfig as _ComponentConfig;
1211        use cu29::config::MonitorConfig as _MonitorConfig;
1212        use cu29::config::read_configuration as _read_configuration;
1213        use cu29::config::read_configuration_str as _read_configuration_str;
1214        use cu29::curuntime::CuRuntime as _CuRuntime;
1215        use cu29::curuntime::CopperContext as _CopperContext;
1216        use cu29::CuResult as _CuResult;
1217        use cu29::CuError as _CuError;
1218        use cu29::cutask::CuSrcTask as _CuSrcTask;
1219        use cu29::cutask::CuSinkTask as _CuSinkTask;
1220        use cu29::cutask::CuTask as _CuTask;
1221        use cu29::cutask::CuMsg as _CuMsg;
1222        use cu29::cutask::CuMsgMetadata as _CuMsgMetadata;
1223        use cu29::copperlist::CopperList as _CopperList;
1224        use cu29::monitoring::CuMonitor as _CuMonitor; // Trait import.
1225        use cu29::monitoring::NoMonitor as _NoMonitor;
1226        use cu29::monitoring::CuTaskState as _CuTaskState;
1227        use cu29::monitoring::Decision as _Decision;
1228        use cu29::prelude::stream_write as _stream_write;
1229        use cu29::prelude::UnifiedLoggerWrite as _UnifiedLoggerWrite;
1230        use cu29::prelude::UnifiedLogType as _UnifiedLogType;
1231        use std::sync::Arc as _Arc;
1232        use std::sync::Mutex as _Mutex;
1233
1234        // This is the heart of everything.
1235        // CuTasks is the list of all the tasks types.
1236        // CuList is a CopperList with the list of all the messages types as msgs.
1237        pub type CuTasks = #task_types_tuple;
1238
1239        // This is the variation with stubs for the sources and sinks in simulation mode.
1240        pub type CuSimTasks = #task_types_tuple_sim;
1241
1242        const TASKS_IDS: &'static [&'static str] = &[#( #all_tasks_ids ),*];
1243
1244        #culist_support
1245
1246        #sim_support
1247
1248        fn tasks_instanciator(all_instances_configs: Vec<Option<&_ComponentConfig>>) -> _CuResult<CuTasks> {
1249            Ok(( #(#task_instances_init_code),*, ))
1250        }
1251
1252        fn tasks_instanciator_sim(all_instances_configs: Vec<Option<&_ComponentConfig>>) -> _CuResult<CuSimTasks> {
1253            Ok(( #(#task_sim_instances_init_code),*, ))
1254        }
1255
1256        fn monitor_instanciator(config: &_CuConfig) -> #monitor_type {
1257            #monitor_type::new(config, TASKS_IDS).expect("Failed to create the given monitor.")
1258        }
1259
1260        pub #item_struct
1261
1262        #runtime_impl
1263
1264        #builder
1265    };
1266    let tokens: TokenStream = result.into();
1267
1268    // Print and format the generated code using rustfmt
1269    #[cfg(feature = "macro_debug")]
1270    {
1271        let formatted_code = rustfmt_generated_code(tokens.to_string());
1272        eprintln!("\n     ===    Gen. Runtime ===\n");
1273        eprintln!("{}", highlight_rust_code(formatted_code));
1274        eprintln!("\n     === === === === === ===\n");
1275    }
1276
1277    tokens
1278}
1279
1280fn read_config(config_file: &str) -> CuResult<CuConfig> {
1281    let filename = config_full_path(config_file);
1282
1283    read_configuration(filename.as_str())
1284}
1285
1286fn config_full_path(config_file: &str) -> String {
1287    let mut config_full_path = utils::caller_crate_root();
1288    config_full_path.push(config_file);
1289    let filename = config_full_path
1290        .as_os_str()
1291        .to_str()
1292        .expect("Could not interpret the config file name");
1293    filename.to_string()
1294}
1295
1296/// Extract all the tasks types in their index order and their ids.
1297fn extract_tasks_types(
1298    copper_config: &CuConfig,
1299) -> (Vec<String>, Vec<CuTaskType>, Vec<String>, Vec<Type>) {
1300    let all_id_nodes = copper_config.get_all_nodes();
1301
1302    // Get all the tasks Ids
1303    let all_tasks_ids: Vec<String> = all_id_nodes
1304        .iter()
1305        .map(|(_, node)| node.get_id().to_string())
1306        .collect();
1307
1308    let all_task_cutype: Vec<CuTaskType> = all_id_nodes
1309        .iter()
1310        .map(|(id, _)| find_task_type_for_id(&copper_config.graph, *id))
1311        .collect();
1312
1313    // Collect all the type names used by our configs.
1314    let all_types_names: Vec<String> = all_id_nodes
1315        .iter()
1316        .map(|(_, node)| node.get_type().to_string())
1317        .collect();
1318
1319    // Transform them as Rust types
1320    let all_types: Vec<Type> = all_types_names
1321        .iter()
1322        .map(|name| {
1323            parse_str(name)
1324                .unwrap_or_else(|_| panic!("Could not transform {name} into a Task Rust type."))
1325        })
1326        .collect();
1327    (all_tasks_ids, all_task_cutype, all_types_names, all_types)
1328}
1329
1330fn extract_msg_types(runtime_plan: &CuExecutionLoop) -> Vec<Type> {
1331    runtime_plan
1332        .steps
1333        .iter()
1334        .filter_map(|unit| match unit {
1335            CuExecutionUnit::Step(step) => {
1336                if let Some((_, output_msg_type)) = &step.output_msg_index_type {
1337                    Some(
1338                        parse_str::<Type>(output_msg_type.as_str()).unwrap_or_else(|_| {
1339                            panic!(
1340                                "Could not transform {output_msg_type} into a message Rust type."
1341                            )
1342                        }),
1343                    )
1344                } else {
1345                    None
1346                }
1347            }
1348            CuExecutionUnit::Loop(_) => todo!("Needs to be implemented"),
1349        })
1350        .collect()
1351}
1352
1353/// Builds the tuple of the CuList as a tuple off all the messages types.
1354fn build_culist_tuple(all_msgs_types_in_culist_order: &[Type]) -> TypeTuple {
1355    if all_msgs_types_in_culist_order.is_empty() {
1356        parse_quote! {()}
1357    } else {
1358        parse_quote! { (#(_CuMsg<#all_msgs_types_in_culist_order>),*,)}
1359    }
1360}
1361
1362/// This is the bincode encoding part of the CuMsgs
1363fn build_culist_tuple_encode(all_msgs_types_in_culist_order: &[Type]) -> ItemImpl {
1364    let indices: Vec<usize> = (0..all_msgs_types_in_culist_order.len()).collect();
1365
1366    // Generate the `self.#i.encode(encoder)?` for each tuple index, including `()` types
1367    let encode_fields: Vec<_> = indices
1368        .iter()
1369        .map(|i| {
1370            let idx = syn::Index::from(*i);
1371            quote! { self.0.#idx.encode(encoder)?; }
1372        })
1373        .collect();
1374
1375    parse_quote! {
1376        impl _Encode for CuMsgs {
1377            fn encode<E: _Encoder>(&self, encoder: &mut E) -> Result<(), _EncodeError> {
1378                #(#encode_fields)*
1379                Ok(())
1380            }
1381        }
1382    }
1383}
1384
1385/// This is the bincode decoding part of the CuMsgs
1386fn build_culist_tuple_decode(all_msgs_types_in_culist_order: &[Type]) -> ItemImpl {
1387    let indices: Vec<usize> = (0..all_msgs_types_in_culist_order.len()).collect();
1388
1389    // Generate the `_CuMsg::<T>::decode(decoder)?` for each tuple index
1390    let decode_fields: Vec<_> = indices
1391        .iter()
1392        .map(|i| {
1393            let t = &all_msgs_types_in_culist_order[*i];
1394            quote! { _CuMsg::<#t>::decode(decoder)? }
1395        })
1396        .collect();
1397
1398    parse_quote! {
1399        impl _Decode for CuMsgs {
1400            fn decode<D: _Decoder>(decoder: &mut D) -> Result<Self, _DecodeError> {
1401                Ok(CuMsgs ((
1402                    #(#decode_fields),*
1403                )))
1404            }
1405        }
1406    }
1407}
1408
1409fn build_culist_tuple_debug(all_msgs_types_in_culist_order: &[Type]) -> ItemImpl {
1410    let indices: Vec<usize> = (0..all_msgs_types_in_culist_order.len()).collect();
1411
1412    let debug_fields: Vec<_> = indices
1413        .iter()
1414        .map(|i| {
1415            let idx = syn::Index::from(*i);
1416            quote! { .field(&self.0.#idx) }
1417        })
1418        .collect();
1419
1420    parse_quote! {
1421        impl std::fmt::Debug for CuMsgs {
1422            fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1423                f.debug_tuple("CuMsgs")
1424                    #(#debug_fields)*
1425                    .finish()
1426            }
1427        }
1428    }
1429}
1430
1431#[cfg(test)]
1432mod tests {
1433    // See tests/compile_file directory for more information
1434    #[test]
1435    fn test_compile_fail() {
1436        let t = trybuild::TestCases::new();
1437        t.compile_fail("tests/compile_fail/*/*.rs");
1438    }
1439}