cu29_derive/
lib.rs

1use proc_macro::TokenStream;
2use quote::{format_ident, quote};
3use std::collections::{BTreeMap, BTreeSet, HashMap};
4use std::fs::read_to_string;
5use syn::Fields::{Named, Unnamed};
6use syn::meta::parser;
7use syn::{
8    Field, Fields, ItemImpl, ItemStruct, LitStr, Type, TypeTuple, parse_macro_input, parse_quote,
9    parse_str,
10};
11
12#[cfg(feature = "macro_debug")]
13use crate::format::rustfmt_generated_code;
14use crate::utils::{config_id_to_bridge_const, config_id_to_enum, config_id_to_struct_member};
15use cu29_runtime::config::CuConfig;
16use cu29_runtime::config::{
17    BridgeChannelConfigRepresentation, CuGraph, Flavor, Node, NodeId, ResourceBundleConfig,
18    read_configuration,
19};
20use cu29_runtime::curuntime::{
21    CuExecutionLoop, CuExecutionStep, CuExecutionUnit, CuTaskType, compute_runtime_plan,
22    find_task_type_for_id,
23};
24use cu29_traits::{CuError, CuResult};
25use proc_macro2::{Ident, Span};
26
27mod format;
28mod resources;
29mod utils;
30
31// TODO: this needs to be determined when the runtime is sizing itself.
32const DEFAULT_CLNB: usize = 10;
33
34#[inline]
35fn int2sliceindex(i: u32) -> syn::Index {
36    syn::Index::from(i as usize)
37}
38
39#[inline(always)]
40fn return_error(msg: String) -> TokenStream {
41    syn::Error::new(Span::call_site(), msg)
42        .to_compile_error()
43        .into()
44}
45
46#[proc_macro]
47pub fn resources(input: TokenStream) -> TokenStream {
48    resources::resources(input)
49}
50
51/// Generates the CopperList content type from a config.
52/// gen_cumsgs!("path/to/config.toml")
53/// It will create a new type called CuStampedDataSet you can pass to the log reader for decoding:
54#[proc_macro]
55pub fn gen_cumsgs(config_path_lit: TokenStream) -> TokenStream {
56    #[cfg(feature = "std")]
57    let std = true;
58
59    #[cfg(not(feature = "std"))]
60    let std = false;
61
62    let config = parse_macro_input!(config_path_lit as LitStr).value();
63    if !std::path::Path::new(&config_full_path(&config)).exists() {
64        return return_error(format!(
65            "The configuration file `{config}` does not exist. Please provide a valid path."
66        ));
67    }
68    #[cfg(feature = "macro_debug")]
69    eprintln!("[gen culist support with {config:?}]");
70    let cuconfig = match read_config(&config) {
71        Ok(cuconfig) => cuconfig,
72        Err(e) => return return_error(e.to_string()),
73    };
74    let graph = cuconfig
75        .get_graph(None) // FIXME(gbin): Multimission
76        .expect("Could not find the specified mission for gen_cumsgs");
77    let task_specs = CuTaskSpecSet::from_graph(graph);
78    let channel_usage = collect_bridge_channel_usage(graph);
79    let mut bridge_specs = build_bridge_specs(&cuconfig, graph, &channel_usage);
80    let (culist_plan, exec_entities, plan_to_original) =
81        match build_execution_plan(graph, &task_specs, &mut bridge_specs) {
82            Ok(plan) => plan,
83            Err(e) => return return_error(format!("Could not compute copperlist plan: {e}")),
84        };
85    let task_member_names = collect_task_member_names(graph);
86    let (culist_order, node_output_positions) = collect_culist_metadata(
87        &culist_plan,
88        &exec_entities,
89        &mut bridge_specs,
90        &plan_to_original,
91    );
92
93    #[cfg(feature = "macro_debug")]
94    eprintln!(
95        "[The CuStampedDataSet matching tasks ids are {:?}]",
96        culist_order
97    );
98
99    let support = gen_culist_support(
100        &culist_plan,
101        &culist_order,
102        &node_output_positions,
103        &task_member_names,
104    );
105
106    let extra_imports = if !std {
107        quote! {
108            use core::fmt::Debug;
109            use core::fmt::Formatter;
110            use core::fmt::Result as FmtResult;
111            use alloc::vec;
112        }
113    } else {
114        quote! {
115            use std::fmt::Debug;
116            use std::fmt::Formatter;
117            use std::fmt::Result as FmtResult;
118        }
119    };
120
121    let with_uses = quote! {
122        mod cumsgs {
123            use cu29::bincode::Encode;
124            use cu29::bincode::enc::Encoder;
125            use cu29::bincode::error::EncodeError;
126            use cu29::bincode::Decode;
127            use cu29::bincode::de::Decoder;
128            use cu29::bincode::error::DecodeError;
129            use cu29::copperlist::CopperList;
130            use cu29::prelude::CuStampedData;
131            use cu29::prelude::ErasedCuStampedData;
132            use cu29::prelude::ErasedCuStampedDataSet;
133            use cu29::prelude::MatchingTasks;
134            use cu29::prelude::Serialize;
135            use cu29::prelude::CuMsg;
136            use cu29::prelude::CuMsgMetadata;
137            use cu29::prelude::CuListZeroedInit;
138            use cu29::prelude::CuCompactString;
139            #extra_imports
140            #support
141        }
142        use cumsgs::CuStampedDataSet;
143        type CuMsgs=CuStampedDataSet;
144    };
145    with_uses.into()
146}
147
148/// Build the inner support of the copper list.
149fn gen_culist_support(
150    runtime_plan: &CuExecutionLoop,
151    culist_indices_in_plan_order: &[usize],
152    node_output_positions: &HashMap<NodeId, usize>,
153    task_member_names: &[(NodeId, String)],
154) -> proc_macro2::TokenStream {
155    #[cfg(feature = "macro_debug")]
156    eprintln!("[Extract msgs types]");
157    let all_msgs_types_in_culist_order = extract_msg_types(runtime_plan);
158
159    let culist_size = all_msgs_types_in_culist_order.len();
160    let task_indices: Vec<_> = culist_indices_in_plan_order
161        .iter()
162        .map(|i| syn::Index::from(*i))
163        .collect();
164
165    #[cfg(feature = "macro_debug")]
166    eprintln!("[build the copperlist struct]");
167    let msgs_types_tuple: TypeTuple = build_culist_tuple(&all_msgs_types_in_culist_order);
168
169    #[cfg(feature = "macro_debug")]
170    eprintln!("[build the copperlist tuple bincode support]");
171    let msgs_types_tuple_encode = build_culist_tuple_encode(&all_msgs_types_in_culist_order);
172    let msgs_types_tuple_decode = build_culist_tuple_decode(&all_msgs_types_in_culist_order);
173
174    #[cfg(feature = "macro_debug")]
175    eprintln!("[build the copperlist tuple debug support]");
176    let msgs_types_tuple_debug = build_culist_tuple_debug(&all_msgs_types_in_culist_order);
177
178    #[cfg(feature = "macro_debug")]
179    eprintln!("[build the copperlist tuple serialize support]");
180    let msgs_types_tuple_serialize = build_culist_tuple_serialize(&all_msgs_types_in_culist_order);
181
182    #[cfg(feature = "macro_debug")]
183    eprintln!("[build the default tuple support]");
184    let msgs_types_tuple_default = build_culist_tuple_default(&all_msgs_types_in_culist_order);
185
186    #[cfg(feature = "macro_debug")]
187    eprintln!("[build erasedcumsgs]");
188
189    let erasedmsg_trait_impl = build_culist_erasedcumsgs(&all_msgs_types_in_culist_order);
190
191    let collect_metadata_function = quote! {
192        pub fn collect_metadata<'a>(culist: &'a CuList) -> [&'a CuMsgMetadata; #culist_size] {
193            [#( &culist.msgs.0.#task_indices.metadata, )*]
194        }
195    };
196
197    let task_name_literals: Vec<String> = task_member_names
198        .iter()
199        .map(|(_, name)| name.clone())
200        .collect();
201
202    let methods = task_member_names.iter().map(|(node_id, name)| {
203        let output_position = node_output_positions
204            .get(node_id)
205            .unwrap_or_else(|| panic!("Task {name} (id: {node_id}) not found in execution order"));
206
207        let fn_name = format_ident!("get_{}_output", name);
208        let payload_type = all_msgs_types_in_culist_order[*output_position].clone();
209        let index = syn::Index::from(*output_position);
210        quote! {
211            #[allow(dead_code)]
212            pub fn #fn_name(&self) -> &CuMsg<#payload_type> {
213                &self.0.#index
214            }
215        }
216    });
217
218    // This generates a way to get the metadata of every single message of a culist at low cost
219    quote! {
220        #collect_metadata_function
221
222        pub struct CuStampedDataSet(pub #msgs_types_tuple);
223
224        pub type CuList = CopperList<CuStampedDataSet>;
225
226        impl CuStampedDataSet {
227            #(#methods)*
228
229            #[allow(dead_code)]
230            fn get_tuple(&self) -> &#msgs_types_tuple {
231                &self.0
232            }
233
234            #[allow(dead_code)]
235            fn get_tuple_mut(&mut self) -> &mut #msgs_types_tuple {
236                &mut self.0
237            }
238        }
239
240        impl MatchingTasks for CuStampedDataSet {
241            #[allow(dead_code)]
242            fn get_all_task_ids() -> &'static [&'static str] {
243                &[#(#task_name_literals),*]
244            }
245        }
246
247        // Adds the bincode support for the copper list tuple
248        #msgs_types_tuple_encode
249        #msgs_types_tuple_decode
250
251        // Adds the debug support
252        #msgs_types_tuple_debug
253
254        // Adds the serialization support
255        #msgs_types_tuple_serialize
256
257        // Adds the default support
258        #msgs_types_tuple_default
259
260        // Adds the type erased CuStampedDataSet support (to help generic serialized conversions)
261        #erasedmsg_trait_impl
262
263        impl CuListZeroedInit for CuStampedDataSet {
264            fn init_zeroed(&mut self) {
265                #(self.0.#task_indices.metadata.status_txt = CuCompactString::default();)*
266            }
267        }
268    }
269}
270
271fn gen_sim_support(
272    runtime_plan: &CuExecutionLoop,
273    exec_entities: &[ExecutionEntity],
274) -> proc_macro2::TokenStream {
275    #[cfg(feature = "macro_debug")]
276    eprintln!("[Sim: Build SimEnum]");
277    let plan_enum: Vec<proc_macro2::TokenStream> = runtime_plan
278        .steps
279        .iter()
280        .filter_map(|unit| match unit {
281            CuExecutionUnit::Step(step) => {
282                if !matches!(
283                    exec_entities[step.node_id as usize].kind,
284                    ExecutionEntityKind::Task { .. }
285                ) {
286                    return None;
287                }
288                let enum_entry_name = config_id_to_enum(step.node.get_id().as_str());
289                let enum_ident = Ident::new(&enum_entry_name, Span::call_site());
290                let inputs: Vec<Type> = step
291                    .input_msg_indices_types
292                    .iter()
293                    .map(|(_, t)| parse_str::<Type>(format!("CuMsg<{t}>").as_str()).unwrap())
294                    .collect();
295                let output: Option<Type> = step
296                    .output_msg_index_type
297                    .as_ref()
298                    .map(|(_, t)| parse_str::<Type>(format!("CuMsg<{t}>").as_str()).unwrap());
299                let no_output = parse_str::<Type>("CuMsg<()>").unwrap();
300                let output = output.as_ref().unwrap_or(&no_output);
301
302                let inputs_type = if inputs.is_empty() {
303                    quote! { () }
304                } else if inputs.len() == 1 {
305                    let input = inputs.first().unwrap();
306                    quote! { &'a #input }
307                } else {
308                    quote! { &'a (#(&'a #inputs),*) }
309                };
310
311                Some(quote! {
312                    #enum_ident(CuTaskCallbackState<#inputs_type, &'a mut #output>)
313                })
314            }
315            CuExecutionUnit::Loop(_) => {
316                todo!("Needs to be implemented")
317            }
318        })
319        .collect();
320    let mut variants = plan_enum;
321    variants.push(quote! { __Phantom(core::marker::PhantomData<&'a ()>) });
322    quote! {
323        // not used if sim is not generated but this is ok.
324        #[allow(dead_code, unused_lifetimes)]
325        pub enum SimStep<'a> {
326            #(#variants),*
327        }
328    }
329}
330
331/// Adds #[copper_runtime(config = "path", sim_mode = false/true)] to your application struct to generate the runtime.
332/// if sim_mode is omitted, it is set to false.
333/// This will add a "runtime" field to your struct and implement the "new" and "run" methods.
334#[proc_macro_attribute]
335pub fn copper_runtime(args: TokenStream, input: TokenStream) -> TokenStream {
336    #[cfg(feature = "macro_debug")]
337    eprintln!("[entry]");
338    let mut application_struct = parse_macro_input!(input as ItemStruct);
339
340    let application_name = &application_struct.ident;
341    let builder_name = format_ident!("{}Builder", application_name);
342
343    let mut config_file: Option<LitStr> = None;
344    let mut sim_mode = false;
345
346    #[cfg(feature = "std")]
347    let std = true;
348
349    #[cfg(not(feature = "std"))]
350    let std = false;
351
352    // Custom parser for the attribute arguments
353    let attribute_config_parser = parser(|meta| {
354        if meta.path.is_ident("config") {
355            config_file = Some(meta.value()?.parse()?);
356            Ok(())
357        } else if meta.path.is_ident("sim_mode") {
358            // Check if `sim_mode` has an explicit value (true/false)
359            if meta.input.peek(syn::Token![=]) {
360                meta.input.parse::<syn::Token![=]>()?;
361                let value: syn::LitBool = meta.input.parse()?;
362                sim_mode = value.value();
363                Ok(())
364            } else {
365                // If no value is provided, default to true
366                sim_mode = true;
367                Ok(())
368            }
369        } else {
370            Err(meta.error("unsupported property"))
371        }
372    });
373
374    #[cfg(feature = "macro_debug")]
375    eprintln!("[parse]");
376    // Parse the provided args with the custom parser
377    parse_macro_input!(args with attribute_config_parser);
378
379    // Adds the generic parameter for the UnifiedLogger if this is a real application (not sim)
380    // This allows to adapt either to the no-std (custom impl) and std (default file based one)
381    // if !sim_mode {
382    //     application_struct
383    //         .generics
384    //         .params
385    //         .push(syn::parse_quote!(L: UnifiedLogWrite + 'static));
386    // }
387
388    // Check if the config file was provided
389    let config_file = match config_file {
390        Some(file) => file.value(),
391        None => {
392            return return_error(
393                "Expected config file attribute like #[CopperRuntime(config = \"path\")]"
394                    .to_string(),
395            );
396        }
397    };
398
399    if !std::path::Path::new(&config_full_path(&config_file)).exists() {
400        return return_error(format!(
401            "The configuration file `{config_file}` does not exist. Please provide a valid path."
402        ));
403    }
404
405    let copper_config = match read_config(&config_file) {
406        Ok(cuconfig) => cuconfig,
407        Err(e) => return return_error(e.to_string()),
408    };
409    let copper_config_content = match read_to_string(config_full_path(config_file.as_str())) {
410        Ok(ok) => ok,
411        Err(e) => {
412            return return_error(format!(
413                "Could not read the config file (should not happen because we just succeeded just before). {e}"
414            ));
415        }
416    };
417
418    #[cfg(feature = "macro_debug")]
419    eprintln!("[build monitor type]");
420    let monitor_type = if let Some(monitor_config) = copper_config.get_monitor_config() {
421        let monitor_type = parse_str::<Type>(monitor_config.get_type())
422            .expect("Could not transform the monitor type name into a Rust type.");
423        quote! { #monitor_type }
424    } else {
425        quote! { NoMonitor }
426    };
427
428    // This is common for all the mission as it will be inserted in the respective modules with their local CuTasks, CuStampedDataSet etc...
429    #[cfg(feature = "macro_debug")]
430    eprintln!("[build runtime field]");
431    // add that to a new field
432    let runtime_field: Field = if sim_mode {
433        parse_quote! {
434            copper_runtime: cu29::curuntime::CuRuntime<CuSimTasks, CuBridges, CuStampedDataSet, #monitor_type, #DEFAULT_CLNB>
435        }
436    } else {
437        parse_quote! {
438            copper_runtime: cu29::curuntime::CuRuntime<CuTasks, CuBridges, CuStampedDataSet, #monitor_type, #DEFAULT_CLNB>
439        }
440    };
441
442    #[cfg(feature = "macro_debug")]
443    eprintln!("[match struct anonymity]");
444    match &mut application_struct.fields {
445        Named(fields_named) => {
446            fields_named.named.push(runtime_field);
447        }
448        Unnamed(fields_unnamed) => {
449            fields_unnamed.unnamed.push(runtime_field);
450        }
451        Fields::Unit => {
452            panic!(
453                "This struct is a unit struct, it should have named or unnamed fields. use struct Something {{}} and not struct Something;"
454            )
455        }
456    };
457
458    let all_missions = copper_config.graphs.get_all_missions_graphs();
459    let mut all_missions_tokens = Vec::<proc_macro2::TokenStream>::new();
460    for (mission, graph) in &all_missions {
461        let mission_mod = parse_str::<Ident>(mission.as_str())
462            .expect("Could not make an identifier of the mission name");
463
464        #[cfg(feature = "macro_debug")]
465        eprintln!("[extract tasks ids & types]");
466        let task_specs = CuTaskSpecSet::from_graph(graph);
467
468        let culist_channel_usage = collect_bridge_channel_usage(graph);
469        let mut culist_bridge_specs =
470            build_bridge_specs(&copper_config, graph, &culist_channel_usage);
471        let (culist_plan, culist_exec_entities, culist_plan_to_original) =
472            match build_execution_plan(graph, &task_specs, &mut culist_bridge_specs) {
473                Ok(plan) => plan,
474                Err(e) => return return_error(format!("Could not compute copperlist plan: {e}")),
475            };
476        let task_member_names = collect_task_member_names(graph);
477        let (culist_call_order, node_output_positions) = collect_culist_metadata(
478            &culist_plan,
479            &culist_exec_entities,
480            &mut culist_bridge_specs,
481            &culist_plan_to_original,
482        );
483
484        #[cfg(feature = "macro_debug")]
485        {
486            eprintln!("[runtime plan for mission {mission}]");
487            eprintln!("{culist_plan:?}");
488        }
489
490        let culist_support: proc_macro2::TokenStream = gen_culist_support(
491            &culist_plan,
492            &culist_call_order,
493            &node_output_positions,
494            &task_member_names,
495        );
496
497        let resource_specs = match collect_resource_specs(graph, &task_specs, &culist_bridge_specs)
498        {
499            Ok(specs) => specs,
500            Err(e) => return return_error(e.to_string()),
501        };
502
503        let (resources_module, resources_instanciator_fn) =
504            match build_resources_module(mission.as_str(), &copper_config, &resource_specs) {
505                Ok(tokens) => tokens,
506                Err(e) => return return_error(e.to_string()),
507            };
508        let task_resource_mappings_tokens =
509            match build_task_resource_mappings(&resource_specs, &task_specs) {
510                Ok(tokens) => tokens,
511                Err(e) => return return_error(e.to_string()),
512            };
513        let bridge_resource_mappings_tokens =
514            build_bridge_resource_mappings(&resource_specs, &culist_bridge_specs);
515
516        let ids = build_monitored_ids(&task_specs.ids, &mut culist_bridge_specs);
517
518        let bridge_types: Vec<Type> = culist_bridge_specs
519            .iter()
520            .map(|spec| spec.type_path.clone())
521            .collect();
522        let bridges_type_tokens: proc_macro2::TokenStream = if bridge_types.is_empty() {
523            quote! { () }
524        } else {
525            let tuple: TypeTuple = parse_quote! { (#(#bridge_types),*,) };
526            quote! { #tuple }
527        };
528
529        let bridge_binding_idents: Vec<Ident> = culist_bridge_specs
530            .iter()
531            .enumerate()
532            .map(|(idx, _)| format_ident!("bridge_{idx}"))
533            .collect();
534
535        let bridge_init_statements: Vec<proc_macro2::TokenStream> = culist_bridge_specs
536            .iter()
537            .enumerate()
538            .map(|(idx, spec)| {
539                let binding_ident = &bridge_binding_idents[idx];
540                let bridge_type = &spec.type_path;
541                let bridge_name = spec.id.clone();
542                let config_index = syn::Index::from(spec.config_index);
543                let binding_error = LitStr::new(
544                    &format!("Failed to bind resources for bridge '{}'", bridge_name),
545                    Span::call_site(),
546                );
547                let tx_configs: Vec<proc_macro2::TokenStream> = spec
548                    .tx_channels
549                    .iter()
550                    .map(|channel| {
551                        let const_ident = &channel.const_ident;
552                        let channel_name = channel.id.clone();
553                        let channel_config_index = syn::Index::from(channel.config_index);
554                        quote! {
555                            {
556                        let (channel_route, channel_config) = match &bridge_cfg.channels[#channel_config_index] {
557                            cu29::config::BridgeChannelConfigRepresentation::Tx { route, config, .. } => {
558                                (route.clone(), config.clone())
559                                    }
560                                    _ => panic!(
561                                        "Bridge '{}' channel '{}' expected to be Tx",
562                                        #bridge_name,
563                                        #channel_name
564                                    ),
565                                };
566                                cu29::cubridge::BridgeChannelConfig::from_static(
567                                    &<#bridge_type as cu29::cubridge::CuBridge>::Tx::#const_ident,
568                                    channel_route,
569                                    channel_config,
570                                )
571                            }
572                        }
573                    })
574                    .collect();
575                let rx_configs: Vec<proc_macro2::TokenStream> = spec
576                    .rx_channels
577                    .iter()
578                    .map(|channel| {
579                        let const_ident = &channel.const_ident;
580                        let channel_name = channel.id.clone();
581                        let channel_config_index = syn::Index::from(channel.config_index);
582                        quote! {
583                            {
584                                let (channel_route, channel_config) = match &bridge_cfg.channels[#channel_config_index] {
585                                    cu29::config::BridgeChannelConfigRepresentation::Rx { route, config, .. } => {
586                                        (route.clone(), config.clone())
587                                    }
588                                    _ => panic!(
589                                        "Bridge '{}' channel '{}' expected to be Rx",
590                                        #bridge_name,
591                                        #channel_name
592                                    ),
593                                };
594                                cu29::cubridge::BridgeChannelConfig::from_static(
595                                    &<#bridge_type as cu29::cubridge::CuBridge>::Rx::#const_ident,
596                                    channel_route,
597                                    channel_config,
598                                )
599                            }
600                        }
601                    })
602                    .collect();
603                quote! {
604                    let #binding_ident = {
605                        let bridge_cfg = config
606                            .bridges
607                            .get(#config_index)
608                            .unwrap_or_else(|| panic!("Bridge '{}' missing from configuration", #bridge_name));
609                        let bridge_resources = <<#bridge_type as cu29::cubridge::CuBridge>::Resources<'_> as ResourceBindings>::from_bindings(
610                            resources,
611                            BRIDGE_RESOURCE_MAPPINGS[#idx],
612                        )
613                        .map_err(|e| cu29::CuError::new_with_cause(#binding_error, e))?;
614                        let tx_channels: &[cu29::cubridge::BridgeChannelConfig<
615                            <<#bridge_type as cu29::cubridge::CuBridge>::Tx as cu29::cubridge::BridgeChannelSet>::Id,
616                        >] = &[#(#tx_configs),*];
617                        let rx_channels: &[cu29::cubridge::BridgeChannelConfig<
618                            <<#bridge_type as cu29::cubridge::CuBridge>::Rx as cu29::cubridge::BridgeChannelSet>::Id,
619                        >] = &[#(#rx_configs),*];
620                        <#bridge_type as cu29::cubridge::CuBridge>::new_with(
621                            bridge_cfg.config.as_ref(),
622                            tx_channels,
623                            rx_channels,
624                            bridge_resources,
625                        )?
626                    };
627                }
628            })
629            .collect();
630
631        let bridges_instanciator = if culist_bridge_specs.is_empty() {
632            quote! {
633                pub fn bridges_instanciator(_config: &CuConfig, resources: &mut ResourceManager) -> CuResult<CuBridges> {
634                    let _ = resources;
635                    Ok(())
636                }
637            }
638        } else {
639            let bridge_bindings = bridge_binding_idents.clone();
640            quote! {
641                pub fn bridges_instanciator(config: &CuConfig, resources: &mut ResourceManager) -> CuResult<CuBridges> {
642                    #(#bridge_init_statements)*
643                    Ok((#(#bridge_bindings),*,))
644                }
645            }
646        };
647
648        let all_sim_tasks_types: Vec<Type> = task_specs
649            .ids
650            .iter()
651            .zip(&task_specs.cutypes)
652            .zip(&task_specs.sim_task_types)
653            .zip(&task_specs.background_flags)
654            .zip(&task_specs.run_in_sim_flags)
655            .zip(task_specs.output_types.iter())
656            .map(|(((((task_id, task_type), sim_type), background), run_in_sim), output_type)| {
657                match task_type {
658                    CuTaskType::Source => {
659                        if *background {
660                            panic!("CuSrcTask {task_id} cannot be a background task, it should be a regular task.");
661                        }
662                        if *run_in_sim {
663                            sim_type.clone()
664                        } else {
665                            let msg_type = graph
666                                .get_node_output_msg_type(task_id.as_str())
667                                .unwrap_or_else(|| panic!("CuSrcTask {task_id} should have an outgoing connection with a valid output msg type"));
668                            let sim_task_name = format!("CuSimSrcTask<{msg_type}>");
669                            parse_str(sim_task_name.as_str()).unwrap_or_else(|_| panic!("Could not build the placeholder for simulation: {sim_task_name}"))
670                        }
671                    }
672                    CuTaskType::Regular => {
673                        if *background {
674                            if let Some(out_ty) = output_type {
675                                parse_quote!(CuAsyncTask<#sim_type, #out_ty>)
676                            } else {
677                                panic!("{task_id}: If a task is background, it has to have an output");
678                            }
679                        } else {
680                            // run_in_sim has no effect for normal tasks, they are always run in sim as is.
681                            sim_type.clone()
682                        }
683                    },
684                    CuTaskType::Sink => {
685                        if *background {
686                            panic!("CuSinkTask {task_id} cannot be a background task, it should be a regular task.");
687                        }
688
689                        if *run_in_sim {
690                            // Use the real task in sim if asked to.
691                            sim_type.clone()
692                        }
693                        else {
694                            // Use the placeholder sim task.
695                            let msg_types = graph
696                                .get_node_input_msg_types(task_id.as_str())
697                                .unwrap_or_else(|| panic!("CuSinkTask {task_id} should have an incoming connection with a valid input msg type"));
698                            let msg_type = if msg_types.len() == 1 {
699                                format!("({},)", msg_types[0])
700                            } else {
701                                format!("({})", msg_types.join(", "))
702                            };
703                            let sim_task_name = format!("CuSimSinkTask<{msg_type}>");
704                            parse_str(sim_task_name.as_str()).unwrap_or_else(|_| panic!("Could not build the placeholder for simulation: {sim_task_name}"))
705                        }
706                    }
707                }
708            })
709            .collect();
710
711        #[cfg(feature = "macro_debug")]
712        eprintln!("[build task tuples]");
713
714        let task_types = &task_specs.task_types;
715        // Build the tuple of all those types
716        // note the extraneous, at the end is to make the tuple work even if this is only one element
717        let task_types_tuple: TypeTuple = if task_types.is_empty() {
718            parse_quote! { () }
719        } else {
720            parse_quote! { (#(#task_types),*,) }
721        };
722
723        let task_types_tuple_sim: TypeTuple = if all_sim_tasks_types.is_empty() {
724            parse_quote! { () }
725        } else {
726            parse_quote! { (#(#all_sim_tasks_types),*,) }
727        };
728
729        #[cfg(feature = "macro_debug")]
730        eprintln!("[gen instances]");
731        let task_sim_instances_init_code = all_sim_tasks_types
732            .iter()
733            .enumerate()
734            .map(|(index, ty)| {
735                let additional_error_info = format!(
736                    "Failed to get create instance for {}, instance index {}.",
737                    task_specs.type_names[index], index
738                );
739                match task_specs.cutypes[index] {
740                    CuTaskType::Source => quote! {
741                        {
742                            let resources = <<#ty as CuSrcTask>::Resources<'_> as ResourceBindings>::from_bindings(
743                                resources,
744                                TASK_RESOURCE_MAPPINGS[#index],
745                            ).map_err(|e| e.add_cause(#additional_error_info))?;
746                            <#ty>::new_with(all_instances_configs[#index], resources).map_err(|e| e.add_cause(#additional_error_info))?
747                        }
748                    },
749                    CuTaskType::Regular => quote! {
750                        {
751                            let resources = <<#ty as CuTask>::Resources<'_> as ResourceBindings>::from_bindings(
752                                resources,
753                                TASK_RESOURCE_MAPPINGS[#index],
754                            ).map_err(|e| e.add_cause(#additional_error_info))?;
755                            <#ty>::new_with(all_instances_configs[#index], resources).map_err(|e| e.add_cause(#additional_error_info))?
756                        }
757                    },
758                    CuTaskType::Sink => quote! {
759                        {
760                            let resources = <<#ty as CuSinkTask>::Resources<'_> as ResourceBindings>::from_bindings(
761                                resources,
762                                TASK_RESOURCE_MAPPINGS[#index],
763                            ).map_err(|e| e.add_cause(#additional_error_info))?;
764                            <#ty>::new_with(all_instances_configs[#index], resources).map_err(|e| e.add_cause(#additional_error_info))?
765                        }
766                    },
767                }
768            })
769            .collect::<Vec<_>>();
770
771        let task_instances_init_code = task_specs
772            .instantiation_types
773            .iter()
774            .zip(&task_specs.background_flags)
775            .enumerate()
776            .map(|(index, (task_type, _background))| {
777                let additional_error_info = format!(
778                    "Failed to get create instance for {}, instance index {}.",
779                    task_specs.type_names[index], index
780                );
781                match task_specs.cutypes[index] {
782                    CuTaskType::Source => quote! {
783                        {
784                            let resources = <<#task_type as CuSrcTask>::Resources<'_> as ResourceBindings>::from_bindings(
785                                resources,
786                                TASK_RESOURCE_MAPPINGS[#index],
787                            ).map_err(|e| e.add_cause(#additional_error_info))?;
788                            #task_type::new_with(all_instances_configs[#index], resources).map_err(|e| e.add_cause(#additional_error_info))?
789                        }
790                    },
791                    CuTaskType::Regular => quote! {
792                        {
793                            let resources = <<#task_type as CuTask>::Resources<'_> as ResourceBindings>::from_bindings(
794                                resources,
795                                TASK_RESOURCE_MAPPINGS[#index],
796                            ).map_err(|e| e.add_cause(#additional_error_info))?;
797                            #task_type::new_with(all_instances_configs[#index], resources).map_err(|e| e.add_cause(#additional_error_info))?
798                        }
799                    },
800                    CuTaskType::Sink => quote! {
801                        {
802                            let resources = <<#task_type as CuSinkTask>::Resources<'_> as ResourceBindings>::from_bindings(
803                                resources,
804                                TASK_RESOURCE_MAPPINGS[#index],
805                            ).map_err(|e| e.add_cause(#additional_error_info))?;
806                            #task_type::new_with(all_instances_configs[#index], resources).map_err(|e| e.add_cause(#additional_error_info))?
807                        }
808                    },
809                }
810            })
811            .collect::<Vec<_>>();
812
813        // Generate the code to create instances of the nodes
814        // It maps the types to their index
815        let (
816            task_restore_code,
817            task_start_calls,
818            task_stop_calls,
819            task_preprocess_calls,
820            task_postprocess_calls,
821            ): (Vec<_>, Vec<_>, Vec<_>, Vec<_>, Vec<_>) = itertools::multiunzip(
822            (0..task_specs.task_types.len())
823            .map(|index| {
824                let task_index = int2sliceindex(index as u32);
825                let task_tuple_index = syn::Index::from(index);
826                let task_enum_name = config_id_to_enum(&task_specs.ids[index]);
827                let enum_name = Ident::new(&task_enum_name, Span::call_site());
828                (
829                    // Tasks keyframe restore code
830                    quote! {
831                        tasks.#task_tuple_index.thaw(&mut decoder).map_err(|e| CuError::new_with_cause("Failed to thaw", e))?
832                    },
833                    {  // Start calls
834                        let monitoring_action = quote! {
835                            let decision = self.copper_runtime.monitor.process_error(#index, CuTaskState::Start, &error);
836                            match decision {
837                                Decision::Abort => {
838                                    debug!("Start: ABORT decision from monitoring. Task '{}' errored out \
839                                during start. Aborting all the other starts.", #mission_mod::TASKS_IDS[#index]);
840                                    return Ok(());
841
842                                }
843                                Decision::Ignore => {
844                                    debug!("Start: IGNORE decision from monitoring. Task '{}' errored out \
845                                during start. The runtime will continue.", #mission_mod::TASKS_IDS[#index]);
846                                }
847                                Decision::Shutdown => {
848                                    debug!("Start: SHUTDOWN decision from monitoring. Task '{}' errored out \
849                                during start. The runtime cannot continue.", #mission_mod::TASKS_IDS[#index]);
850                                    return Err(CuError::new_with_cause("Task errored out during start.", error));
851                                }
852                            }
853                        };
854
855                        let call_sim_callback = if sim_mode {
856                            quote! {
857                                // Ask the sim if this task should be executed or overridden by the sim.
858                                let ovr = sim_callback(SimStep::#enum_name(CuTaskCallbackState::Start));
859
860                                let doit = if let SimOverride::Errored(reason) = ovr  {
861                                    let error: CuError = reason.into();
862                                    #monitoring_action
863                                    false
864                               }
865                               else {
866                                    ovr == SimOverride::ExecuteByRuntime
867                               };
868                            }
869                        } else {
870                            quote! {
871                                let doit = true;  // in normal mode always execute the steps in the runtime.
872                            }
873                        };
874
875
876                        quote! {
877                            #call_sim_callback
878                            if doit {
879                                let task = &mut self.copper_runtime.tasks.#task_index;
880                                if let Err(error) = task.start(&self.copper_runtime.clock) {
881                                    #monitoring_action
882                                }
883                            }
884                        }
885                    },
886                    {  // Stop calls
887                        let monitoring_action = quote! {
888                                    let decision = self.copper_runtime.monitor.process_error(#index, CuTaskState::Stop, &error);
889                                    match decision {
890                                        Decision::Abort => {
891                                            debug!("Stop: ABORT decision from monitoring. Task '{}' errored out \
892                                    during stop. Aborting all the other starts.", #mission_mod::TASKS_IDS[#index]);
893                                            return Ok(());
894
895                                        }
896                                        Decision::Ignore => {
897                                            debug!("Stop: IGNORE decision from monitoring. Task '{}' errored out \
898                                    during stop. The runtime will continue.", #mission_mod::TASKS_IDS[#index]);
899                                        }
900                                        Decision::Shutdown => {
901                                            debug!("Stop: SHUTDOWN decision from monitoring. Task '{}' errored out \
902                                    during stop. The runtime cannot continue.", #mission_mod::TASKS_IDS[#index]);
903                                            return Err(CuError::new_with_cause("Task errored out during stop.", error));
904                                        }
905                                    }
906                            };
907                        let call_sim_callback = if sim_mode {
908                            quote! {
909                                // Ask the sim if this task should be executed or overridden by the sim.
910                                let ovr = sim_callback(SimStep::#enum_name(CuTaskCallbackState::Stop));
911
912                                let doit = if let SimOverride::Errored(reason) = ovr  {
913                                    let error: CuError = reason.into();
914                                    #monitoring_action
915                                    false
916                               }
917                               else {
918                                    ovr == SimOverride::ExecuteByRuntime
919                               };
920                            }
921                        } else {
922                            quote! {
923                                let doit = true;  // in normal mode always execute the steps in the runtime.
924                            }
925                        };
926                        quote! {
927                            #call_sim_callback
928                            if doit {
929                                let task = &mut self.copper_runtime.tasks.#task_index;
930                                if let Err(error) = task.stop(&self.copper_runtime.clock) {
931                                    #monitoring_action
932                                }
933                            }
934                        }
935                    },
936                    {  // Preprocess calls
937                        let monitoring_action = quote! {
938                            let decision = monitor.process_error(#index, CuTaskState::Preprocess, &error);
939                            match decision {
940                                Decision::Abort => {
941                                    debug!("Preprocess: ABORT decision from monitoring. Task '{}' errored out \
942                                during preprocess. Aborting all the other starts.", #mission_mod::TASKS_IDS[#index]);
943                                    return Ok(());
944
945                                }
946                                Decision::Ignore => {
947                                    debug!("Preprocess: IGNORE decision from monitoring. Task '{}' errored out \
948                                during preprocess. The runtime will continue.", #mission_mod::TASKS_IDS[#index]);
949                                }
950                                Decision::Shutdown => {
951                                    debug!("Preprocess: SHUTDOWN decision from monitoring. Task '{}' errored out \
952                                during preprocess. The runtime cannot continue.", #mission_mod::TASKS_IDS[#index]);
953                                    return Err(CuError::new_with_cause("Task errored out during preprocess.", error));
954                                }
955                            }
956                        };
957                        let call_sim_callback = if sim_mode {
958                            quote! {
959                                // Ask the sim if this task should be executed or overridden by the sim.
960                                let ovr = sim_callback(SimStep::#enum_name(CuTaskCallbackState::Preprocess));
961
962                                let doit = if let SimOverride::Errored(reason) = ovr  {
963                                    let error: CuError = reason.into();
964                                    #monitoring_action
965                                    false
966                                } else {
967                                    ovr == SimOverride::ExecuteByRuntime
968                                };
969                            }
970                        } else {
971                            quote! {
972                                let doit = true;  // in normal mode always execute the steps in the runtime.
973                            }
974                        };
975                        quote! {
976                            #call_sim_callback
977                            if doit {
978                                if let Err(error) = tasks.#task_index.preprocess(clock) {
979                                    #monitoring_action
980                                }
981                            }
982                        }
983                    },
984                    {  // Postprocess calls
985                        let monitoring_action = quote! {
986                            let decision = monitor.process_error(#index, CuTaskState::Postprocess, &error);
987                            match decision {
988                                Decision::Abort => {
989                                    debug!("Postprocess: ABORT decision from monitoring. Task '{}' errored out \
990                                during postprocess. Aborting all the other starts.", #mission_mod::TASKS_IDS[#index]);
991                                    return Ok(());
992
993                                }
994                                Decision::Ignore => {
995                                    debug!("Postprocess: IGNORE decision from monitoring. Task '{}' errored out \
996                                during postprocess. The runtime will continue.", #mission_mod::TASKS_IDS[#index]);
997                                }
998                                Decision::Shutdown => {
999                                    debug!("Postprocess: SHUTDOWN decision from monitoring. Task '{}' errored out \
1000                                during postprocess. The runtime cannot continue.", #mission_mod::TASKS_IDS[#index]);
1001                                    return Err(CuError::new_with_cause("Task errored out during postprocess.", error));
1002                                }
1003                            }
1004                        };
1005                        let call_sim_callback = if sim_mode {
1006                            quote! {
1007                                // Ask the sim if this task should be executed or overridden by the sim.
1008                                let ovr = sim_callback(SimStep::#enum_name(CuTaskCallbackState::Postprocess));
1009
1010                                let doit = if let SimOverride::Errored(reason) = ovr  {
1011                                    let error: CuError = reason.into();
1012                                    #monitoring_action
1013                                    false
1014                                } else {
1015                                    ovr == SimOverride::ExecuteByRuntime
1016                                };
1017                            }
1018                        } else {
1019                            quote! {
1020                                let doit = true;  // in normal mode always execute the steps in the runtime.
1021                            }
1022                        };
1023                        quote! {
1024                            #call_sim_callback
1025                            if doit {
1026                                if let Err(error) = tasks.#task_index.postprocess(clock) {
1027                                    #monitoring_action
1028                                }
1029                            }
1030                        }
1031                    }
1032                )
1033            })
1034        );
1035
1036        let bridge_start_calls: Vec<proc_macro2::TokenStream> = culist_bridge_specs
1037            .iter()
1038            .map(|spec| {
1039                let bridge_index = int2sliceindex(spec.tuple_index as u32);
1040                let monitor_index = syn::Index::from(
1041                    spec.monitor_index
1042                        .expect("Bridge missing monitor index for start"),
1043                );
1044                quote! {
1045                    {
1046                        let bridge = &mut self.copper_runtime.bridges.#bridge_index;
1047                        if let Err(error) = bridge.start(&self.copper_runtime.clock) {
1048                            let decision = self.copper_runtime.monitor.process_error(#monitor_index, CuTaskState::Start, &error);
1049                            match decision {
1050                                Decision::Abort => {
1051                                    debug!("Start: ABORT decision from monitoring. Task '{}' errored out during start. Aborting all the other starts.", #mission_mod::TASKS_IDS[#monitor_index]);
1052                                    return Ok(());
1053                                }
1054                                Decision::Ignore => {
1055                                    debug!("Start: IGNORE decision from monitoring. Task '{}' errored out during start. The runtime will continue.", #mission_mod::TASKS_IDS[#monitor_index]);
1056                                }
1057                                Decision::Shutdown => {
1058                                    debug!("Start: SHUTDOWN decision from monitoring. Task '{}' errored out during start. The runtime cannot continue.", #mission_mod::TASKS_IDS[#monitor_index]);
1059                                    return Err(CuError::new_with_cause("Task errored out during start.", error));
1060                                }
1061                            }
1062                        }
1063                    }
1064                }
1065            })
1066            .collect();
1067
1068        let bridge_stop_calls: Vec<proc_macro2::TokenStream> = culist_bridge_specs
1069            .iter()
1070            .map(|spec| {
1071                let bridge_index = int2sliceindex(spec.tuple_index as u32);
1072                let monitor_index = syn::Index::from(
1073                    spec.monitor_index
1074                        .expect("Bridge missing monitor index for stop"),
1075                );
1076                quote! {
1077                    {
1078                        let bridge = &mut self.copper_runtime.bridges.#bridge_index;
1079                        if let Err(error) = bridge.stop(&self.copper_runtime.clock) {
1080                            let decision = self.copper_runtime.monitor.process_error(#monitor_index, CuTaskState::Stop, &error);
1081                            match decision {
1082                                Decision::Abort => {
1083                                    debug!("Stop: ABORT decision from monitoring. Task '{}' errored out during stop. Aborting all the other stops.", #mission_mod::TASKS_IDS[#monitor_index]);
1084                                    return Ok(());
1085                                }
1086                                Decision::Ignore => {
1087                                    debug!("Stop: IGNORE decision from monitoring. Task '{}' errored out during stop. The runtime will continue.", #mission_mod::TASKS_IDS[#monitor_index]);
1088                                }
1089                                Decision::Shutdown => {
1090                                    debug!("Stop: SHUTDOWN decision from monitoring. Task '{}' errored out during stop. The runtime cannot continue.", #mission_mod::TASKS_IDS[#monitor_index]);
1091                                    return Err(CuError::new_with_cause("Task errored out during stop.", error));
1092                                }
1093                            }
1094                        }
1095                    }
1096                }
1097            })
1098            .collect();
1099
1100        let bridge_preprocess_calls: Vec<proc_macro2::TokenStream> = culist_bridge_specs
1101            .iter()
1102            .map(|spec| {
1103                let bridge_index = int2sliceindex(spec.tuple_index as u32);
1104                let monitor_index = syn::Index::from(
1105                    spec.monitor_index
1106                        .expect("Bridge missing monitor index for preprocess"),
1107                );
1108                quote! {
1109                    {
1110                        let bridge = &mut bridges.#bridge_index;
1111                        if let Err(error) = bridge.preprocess(clock) {
1112                            let decision = monitor.process_error(#monitor_index, CuTaskState::Preprocess, &error);
1113                            match decision {
1114                                Decision::Abort => {
1115                                    debug!("Preprocess: ABORT decision from monitoring. Task '{}' errored out during preprocess. Aborting all the other starts.", #mission_mod::TASKS_IDS[#monitor_index]);
1116                                    return Ok(());
1117                                }
1118                                Decision::Ignore => {
1119                                    debug!("Preprocess: IGNORE decision from monitoring. Task '{}' errored out during preprocess. The runtime will continue.", #mission_mod::TASKS_IDS[#monitor_index]);
1120                                }
1121                                Decision::Shutdown => {
1122                                    debug!("Preprocess: SHUTDOWN decision from monitoring. Task '{}' errored out during preprocess. The runtime cannot continue.", #mission_mod::TASKS_IDS[#monitor_index]);
1123                                    return Err(CuError::new_with_cause("Task errored out during preprocess.", error));
1124                                }
1125                            }
1126                        }
1127                    }
1128                }
1129            })
1130            .collect();
1131
1132        let bridge_postprocess_calls: Vec<proc_macro2::TokenStream> = culist_bridge_specs
1133            .iter()
1134            .map(|spec| {
1135                let bridge_index = int2sliceindex(spec.tuple_index as u32);
1136                let monitor_index = syn::Index::from(
1137                    spec.monitor_index
1138                        .expect("Bridge missing monitor index for postprocess"),
1139                );
1140                quote! {
1141                    {
1142                        let bridge = &mut bridges.#bridge_index;
1143                        if let Err(error) = bridge.postprocess(clock) {
1144                            let decision = monitor.process_error(#monitor_index, CuTaskState::Postprocess, &error);
1145                            match decision {
1146                                Decision::Abort => {
1147                                    debug!("Postprocess: ABORT decision from monitoring. Task '{}' errored out during postprocess. Aborting all the other starts.", #mission_mod::TASKS_IDS[#monitor_index]);
1148                                    return Ok(());
1149                                }
1150                                Decision::Ignore => {
1151                                    debug!("Postprocess: IGNORE decision from monitoring. Task '{}' errored out during postprocess. The runtime will continue.", #mission_mod::TASKS_IDS[#monitor_index]);
1152                                }
1153                                Decision::Shutdown => {
1154                                    debug!("Postprocess: SHUTDOWN decision from monitoring. Task '{}' errored out during postprocess. The runtime cannot continue.", #mission_mod::TASKS_IDS[#monitor_index]);
1155                                    return Err(CuError::new_with_cause("Task errored out during postprocess.", error));
1156                                }
1157                            }
1158                        }
1159                    }
1160                }
1161            })
1162            .collect();
1163
1164        let mut start_calls = bridge_start_calls;
1165        start_calls.extend(task_start_calls);
1166        let mut stop_calls = task_stop_calls;
1167        stop_calls.extend(bridge_stop_calls);
1168        let mut preprocess_calls = bridge_preprocess_calls;
1169        preprocess_calls.extend(task_preprocess_calls);
1170        let mut postprocess_calls = task_postprocess_calls;
1171        postprocess_calls.extend(bridge_postprocess_calls);
1172
1173        let runtime_plan_code_and_logging: Vec<(
1174            proc_macro2::TokenStream,
1175            proc_macro2::TokenStream,
1176        )> = culist_plan
1177            .steps
1178            .iter()
1179            .map(|unit| match unit {
1180                CuExecutionUnit::Step(step) => {
1181                    #[cfg(feature = "macro_debug")]
1182                    eprintln!(
1183                        "{} -> {} as {:?}. task_id: {} Input={:?}, Output={:?}",
1184                        step.node.get_id(),
1185                        step.node.get_type(),
1186                        step.task_type,
1187                        step.node_id,
1188                        step.input_msg_indices_types,
1189                        step.output_msg_index_type
1190                    );
1191
1192                    match &culist_exec_entities[step.node_id as usize].kind {
1193                        ExecutionEntityKind::Task { task_index } => generate_task_execution_tokens(
1194                            step,
1195                            *task_index,
1196                            &task_specs,
1197                            sim_mode,
1198                            &mission_mod,
1199                        ),
1200                        ExecutionEntityKind::BridgeRx {
1201                            bridge_index,
1202                            channel_index,
1203                        } => {
1204                            let spec = &culist_bridge_specs[*bridge_index];
1205                            generate_bridge_rx_execution_tokens(spec, *channel_index, &mission_mod)
1206                        }
1207                        ExecutionEntityKind::BridgeTx {
1208                            bridge_index,
1209                            channel_index,
1210                        } => {
1211                            let spec = &culist_bridge_specs[*bridge_index];
1212                            generate_bridge_tx_execution_tokens(
1213                                step,
1214                                spec,
1215                                *channel_index,
1216                                &mission_mod,
1217                            )
1218                        }
1219                    }
1220                }
1221                CuExecutionUnit::Loop(_) => {
1222                    panic!("Execution loops are not supported in runtime generation");
1223                }
1224            })
1225            .collect();
1226
1227        let sim_support = if sim_mode {
1228            Some(gen_sim_support(&culist_plan, &culist_exec_entities))
1229        } else {
1230            None
1231        };
1232
1233        let (new, run_one_iteration, start_all_tasks, stop_all_tasks, run) = if sim_mode {
1234            (
1235                quote! {
1236                    fn new(clock:RobotClock, unified_logger: Arc<Mutex<L>>, config_override: Option<CuConfig>, sim_callback: &mut impl FnMut(SimStep) -> SimOverride) -> CuResult<Self>
1237                },
1238                quote! {
1239                    fn run_one_iteration(&mut self, sim_callback: &mut impl FnMut(SimStep) -> SimOverride) -> CuResult<()>
1240                },
1241                quote! {
1242                    fn start_all_tasks(&mut self, sim_callback: &mut impl FnMut(SimStep) -> SimOverride) -> CuResult<()>
1243                },
1244                quote! {
1245                    fn stop_all_tasks(&mut self, sim_callback: &mut impl FnMut(SimStep) -> SimOverride) -> CuResult<()>
1246                },
1247                quote! {
1248                    fn run(&mut self, sim_callback: &mut impl FnMut(SimStep) -> SimOverride) -> CuResult<()>
1249                },
1250            )
1251        } else {
1252            (
1253                if std {
1254                    quote! {
1255                        fn new(clock:RobotClock, unified_logger: Arc<Mutex<L>>, config_override: Option<CuConfig>) -> CuResult<Self>
1256                    }
1257                } else {
1258                    quote! {
1259                        // no config override is possible in no-std
1260                        fn new(clock:RobotClock, unified_logger: Arc<Mutex<L>>) -> CuResult<Self>
1261                    }
1262                },
1263                quote! {
1264                    fn run_one_iteration(&mut self) -> CuResult<()>
1265                },
1266                quote! {
1267                    fn start_all_tasks(&mut self) -> CuResult<()>
1268                },
1269                quote! {
1270                    fn stop_all_tasks(&mut self) -> CuResult<()>
1271                },
1272                quote! {
1273                    fn run(&mut self) -> CuResult<()>
1274                },
1275            )
1276        };
1277
1278        let sim_callback_arg = if sim_mode {
1279            Some(quote!(sim_callback))
1280        } else {
1281            None
1282        };
1283
1284        let app_trait = if sim_mode {
1285            quote!(CuSimApplication)
1286        } else {
1287            quote!(CuApplication)
1288        };
1289
1290        let sim_callback_on_new_calls = task_specs.ids.iter().enumerate().map(|(i, id)| {
1291            let enum_name = config_id_to_enum(id);
1292            let enum_ident = Ident::new(&enum_name, Span::call_site());
1293            quote! {
1294                // the answer is ignored, we have to instantiate the tasks anyway.
1295                sim_callback(SimStep::#enum_ident(CuTaskCallbackState::New(all_instances_configs[#i].cloned())));
1296            }
1297        });
1298
1299        let sim_callback_on_new = if sim_mode {
1300            Some(quote! {
1301                let graph = config.get_graph(Some(#mission)).expect("Could not find the mission #mission");
1302                let all_instances_configs: Vec<Option<&ComponentConfig>> = graph
1303                    .get_all_nodes()
1304                    .iter()
1305                    .map(|(_, node)| node.get_instance_config())
1306                    .collect();
1307                #(#sim_callback_on_new_calls)*
1308            })
1309        } else {
1310            None
1311        };
1312
1313        let (runtime_plan_code, preprocess_logging_calls): (Vec<_>, Vec<_>) =
1314            itertools::multiunzip(runtime_plan_code_and_logging);
1315
1316        let config_load_stmt = if std {
1317            quote! {
1318                let config = if let Some(overridden_config) = config_override {
1319                    debug!("CuConfig: Overridden programmatically: {}", overridden_config.serialize_ron());
1320                    overridden_config
1321                } else if ::std::path::Path::new(config_filename).exists() {
1322                    debug!("CuConfig: Reading configuration from file: {}", config_filename);
1323                    cu29::config::read_configuration(config_filename)?
1324                } else {
1325                    let original_config = <Self as #app_trait<S, L>>::get_original_config();
1326                    debug!("CuConfig: Using the original configuration the project was compiled with: {}", &original_config);
1327                    cu29::config::read_configuration_str(original_config, None)?
1328                };
1329            }
1330        } else {
1331            quote! {
1332                // Only the original config is available in no-std
1333                let original_config = <Self as #app_trait<S, L>>::get_original_config();
1334                debug!("CuConfig: Using the original configuration the project was compiled with: {}", &original_config);
1335                let config = cu29::config::read_configuration_str(original_config, None)?;
1336            }
1337        };
1338
1339        let kill_handler = if std {
1340            Some(quote! {
1341                ctrlc::set_handler(move || {
1342                    STOP_FLAG.store(true, Ordering::SeqCst);
1343                }).expect("Error setting Ctrl-C handler");
1344            })
1345        } else {
1346            None
1347        };
1348
1349        let run_loop = if std {
1350            quote! {
1351                loop  {
1352                    let iter_start = self.copper_runtime.clock.now();
1353                    let result = <Self as #app_trait<S, L>>::run_one_iteration(self, #sim_callback_arg);
1354
1355                    if let Some(rate) = self.copper_runtime.runtime_config.rate_target_hz {
1356                        let period: CuDuration = (1_000_000_000u64 / rate).into();
1357                        let elapsed = self.copper_runtime.clock.now() - iter_start;
1358                        if elapsed < period {
1359                            std::thread::sleep(std::time::Duration::from_nanos(period.as_nanos() - elapsed.as_nanos()));
1360                        }
1361                    }
1362
1363                    if STOP_FLAG.load(Ordering::SeqCst) || result.is_err() {
1364                        break result;
1365                    }
1366                }
1367            }
1368        } else {
1369            quote! {
1370                loop  {
1371                    let iter_start = self.copper_runtime.clock.now();
1372                    let result = <Self as #app_trait<S, L>>::run_one_iteration(self, #sim_callback_arg);
1373                    if let Some(rate) = self.copper_runtime.runtime_config.rate_target_hz {
1374                        let period: CuDuration = (1_000_000_000u64 / rate).into();
1375                        let elapsed = self.copper_runtime.clock.now() - iter_start;
1376                        if elapsed < period {
1377                            busy_wait_for(period - elapsed);
1378                        }
1379                    }
1380
1381                    if STOP_FLAG.load(Ordering::SeqCst) || result.is_err() {
1382                        break result;
1383                    }
1384                }
1385            }
1386        };
1387
1388        #[cfg(feature = "macro_debug")]
1389        eprintln!("[build the run methods]");
1390        let run_methods = quote! {
1391
1392            #run_one_iteration {
1393
1394                // Pre-explode the runtime to avoid complexity with partial borrowing in the generated code.
1395                let runtime = &mut self.copper_runtime;
1396                let clock = &runtime.clock;
1397                let monitor = &mut runtime.monitor;
1398                let tasks = &mut runtime.tasks;
1399                let bridges = &mut runtime.bridges;
1400                let cl_manager = &mut runtime.copperlists_manager;
1401                let kf_manager = &mut runtime.keyframes_manager;
1402
1403                // Preprocess calls can happen at any time, just packed them up front.
1404                #(#preprocess_calls)*
1405
1406                let culist = cl_manager.inner.create().expect("Ran out of space for copper lists"); // FIXME: error handling
1407                let clid = culist.id;
1408                kf_manager.reset(clid, clock); // beginning of processing, we empty the serialized frozen states of the tasks.
1409                culist.change_state(cu29::copperlist::CopperListState::Processing);
1410                culist.msgs.init_zeroed();
1411                {
1412                    let msgs = &mut culist.msgs.0;
1413                    #(#runtime_plan_code)*
1414                } // drop(msgs);
1415                monitor.process_copperlist(&#mission_mod::collect_metadata(&culist))?;
1416
1417                // here drop the payloads if we don't want them to be logged.
1418                #(#preprocess_logging_calls)*
1419
1420                cl_manager.end_of_processing(clid)?;
1421                kf_manager.end_of_processing(clid)?;
1422
1423                // Postprocess calls can happen at any time, just packed them up at the end.
1424                #(#postprocess_calls)*
1425                Ok(())
1426            }
1427
1428            fn restore_keyframe(&mut self, keyframe: &KeyFrame) -> CuResult<()> {
1429                let runtime = &mut self.copper_runtime;
1430                let clock = &runtime.clock;
1431                let tasks = &mut runtime.tasks;
1432                let config = cu29::bincode::config::standard();
1433                let reader = cu29::bincode::de::read::SliceReader::new(&keyframe.serialized_tasks);
1434                let mut decoder = DecoderImpl::new(reader, config, ());
1435                #(#task_restore_code);*;
1436                Ok(())
1437            }
1438
1439            #start_all_tasks {
1440                #(#start_calls)*
1441                self.copper_runtime.monitor.start(&self.copper_runtime.clock)?;
1442                Ok(())
1443            }
1444
1445            #stop_all_tasks {
1446                #(#stop_calls)*
1447                self.copper_runtime.monitor.stop(&self.copper_runtime.clock)?;
1448                Ok(())
1449            }
1450
1451            #run {
1452                static STOP_FLAG: AtomicBool = AtomicBool::new(false);
1453
1454                #kill_handler
1455
1456                <Self as #app_trait<S, L>>::start_all_tasks(self, #sim_callback_arg)?;
1457                let result = #run_loop;
1458
1459                if result.is_err() {
1460                    error!("A task errored out: {}", &result);
1461                }
1462                <Self as #app_trait<S, L>>::stop_all_tasks(self, #sim_callback_arg)?;
1463                result
1464            }
1465        };
1466
1467        let tasks_type = if sim_mode {
1468            quote!(CuSimTasks)
1469        } else {
1470            quote!(CuTasks)
1471        };
1472
1473        let tasks_instanciator_fn = if sim_mode {
1474            quote!(tasks_instanciator_sim)
1475        } else {
1476            quote!(tasks_instanciator)
1477        };
1478
1479        let app_impl_decl = if sim_mode {
1480            quote!(impl<S: SectionStorage + 'static, L: UnifiedLogWrite<S> + 'static> CuSimApplication<S, L> for #application_name)
1481        } else {
1482            quote!(impl<S: SectionStorage + 'static, L: UnifiedLogWrite<S> + 'static> CuApplication<S, L> for #application_name)
1483        };
1484
1485        let simstep_type_decl = if sim_mode {
1486            quote!(
1487                type Step<'z> = SimStep<'z>;
1488            )
1489        } else {
1490            quote!()
1491        };
1492
1493        #[cfg(feature = "std")]
1494        #[cfg(feature = "macro_debug")]
1495        eprintln!("[build result]");
1496        let application_impl = quote! {
1497            #app_impl_decl {
1498                #simstep_type_decl
1499
1500                #new {
1501                    let config_filename = #config_file;
1502
1503                    #config_load_stmt
1504
1505                    // For simple cases we can say the section is just a bunch of Copper Lists.
1506                    // But we can now have allocations outside of it so we can override it from the config.
1507                    let mut default_section_size = size_of::<super::#mission_mod::CuList>() * 64;
1508                    // Check if there is a logging configuration with section_size_mib
1509                    if let Some(section_size_mib) = config.logging.as_ref().and_then(|l| l.section_size_mib) {
1510                        // Convert MiB to bytes
1511                        default_section_size = section_size_mib as usize * 1024usize * 1024usize;
1512                    }
1513                    let copperlist_stream = stream_write::<#mission_mod::CuList, S>(
1514                        unified_logger.clone(),
1515                        UnifiedLogType::CopperList,
1516                        default_section_size,
1517                        // the 2 sizes are not directly related as we encode the CuList but we can
1518                        // assume the encoded size is close or lower than the non encoded one
1519                        // This is to be sure we have the size of at least a Culist and some.
1520                    )?;
1521
1522                    let keyframes_stream = stream_write::<KeyFrame, S>(
1523                        unified_logger.clone(),
1524                        UnifiedLogType::FrozenTasks,
1525                        1024 * 1024 * 10, // 10 MiB
1526                    )?;
1527
1528
1529                    let application = Ok(#application_name {
1530                        copper_runtime: CuRuntime::<#mission_mod::#tasks_type, #mission_mod::CuBridges, #mission_mod::CuStampedDataSet, #monitor_type, #DEFAULT_CLNB>::new(
1531                            clock,
1532                            &config,
1533                            Some(#mission),
1534                            #mission_mod::resources_instanciator,
1535                            #mission_mod::#tasks_instanciator_fn,
1536                            #mission_mod::monitor_instanciator,
1537                            #mission_mod::bridges_instanciator,
1538                            copperlist_stream,
1539                            keyframes_stream)?, // FIXME: gbin
1540                    });
1541
1542                    #sim_callback_on_new
1543
1544                    application
1545                }
1546
1547                fn get_original_config() -> String {
1548                    #copper_config_content.to_string()
1549                }
1550
1551                #run_methods
1552            }
1553        };
1554
1555        let (
1556            builder_struct,
1557            builder_new,
1558            builder_impl,
1559            builder_sim_callback_method,
1560            builder_build_sim_callback_arg,
1561        ) = if sim_mode {
1562            (
1563                quote! {
1564                    #[allow(dead_code)]
1565                    pub struct #builder_name <'a, F> {
1566                        clock: Option<RobotClock>,
1567                        unified_logger: Option<Arc<Mutex<UnifiedLoggerWrite>>>,
1568                        config_override: Option<CuConfig>,
1569                        sim_callback: Option<&'a mut F>
1570                    }
1571                },
1572                quote! {
1573                    #[allow(dead_code)]
1574                    pub fn new() -> Self {
1575                        Self {
1576                            clock: None,
1577                            unified_logger: None,
1578                            config_override: None,
1579                            sim_callback: None,
1580                        }
1581                    }
1582                },
1583                quote! {
1584                    impl<'a, F> #builder_name <'a, F>
1585                    where
1586                        F: FnMut(SimStep) -> SimOverride,
1587                },
1588                Some(quote! {
1589                    pub fn with_sim_callback(mut self, sim_callback: &'a mut F) -> Self
1590                    {
1591                        self.sim_callback = Some(sim_callback);
1592                        self
1593                    }
1594                }),
1595                Some(quote! {
1596                    self.sim_callback
1597                        .ok_or(CuError::from("Sim callback missing from builder"))?,
1598                }),
1599            )
1600        } else {
1601            (
1602                quote! {
1603                    #[allow(dead_code)]
1604                    pub struct #builder_name {
1605                        clock: Option<RobotClock>,
1606                        unified_logger: Option<Arc<Mutex<UnifiedLoggerWrite>>>,
1607                        config_override: Option<CuConfig>,
1608                    }
1609                },
1610                quote! {
1611                    #[allow(dead_code)]
1612                    pub fn new() -> Self {
1613                        Self {
1614                            clock: None,
1615                            unified_logger: None,
1616                            config_override: None,
1617                        }
1618                    }
1619                },
1620                quote! {
1621                    impl #builder_name
1622                },
1623                None,
1624                None,
1625            )
1626        };
1627
1628        // backward compat on std non-parameterized impl.
1629        let std_application_impl = if sim_mode {
1630            // sim mode
1631            Some(quote! {
1632                        impl #application_name {
1633                            pub fn start_all_tasks(&mut self, sim_callback: &mut impl FnMut(SimStep) -> SimOverride) -> CuResult<()> {
1634                                <Self as #app_trait<MmapSectionStorage, UnifiedLoggerWrite>>::start_all_tasks(self, sim_callback)
1635                            }
1636                            pub fn run_one_iteration(&mut self, sim_callback: &mut impl FnMut(SimStep) -> SimOverride) -> CuResult<()> {
1637                                <Self as #app_trait<MmapSectionStorage, UnifiedLoggerWrite>>::run_one_iteration(self, sim_callback)
1638                            }
1639                            pub fn run(&mut self, sim_callback: &mut impl FnMut(SimStep) -> SimOverride) -> CuResult<()> {
1640                                <Self as #app_trait<MmapSectionStorage, UnifiedLoggerWrite>>::run(self, sim_callback)
1641                            }
1642                            pub fn stop_all_tasks(&mut self, sim_callback: &mut impl FnMut(SimStep) -> SimOverride) -> CuResult<()> {
1643                                <Self as #app_trait<MmapSectionStorage, UnifiedLoggerWrite>>::stop_all_tasks(self, sim_callback)
1644                            }
1645                        }
1646            })
1647        } else if std {
1648            // std and normal mode, we use the memory mapped starage for those
1649            Some(quote! {
1650                        impl #application_name {
1651                            pub fn start_all_tasks(&mut self) -> CuResult<()> {
1652                                <Self as #app_trait<MmapSectionStorage, UnifiedLoggerWrite>>::start_all_tasks(self)
1653                            }
1654                            pub fn run_one_iteration(&mut self) -> CuResult<()> {
1655                                <Self as #app_trait<MmapSectionStorage, UnifiedLoggerWrite>>::run_one_iteration(self)
1656                            }
1657                            pub fn run(&mut self) -> CuResult<()> {
1658                                <Self as #app_trait<MmapSectionStorage, UnifiedLoggerWrite>>::run(self)
1659                            }
1660                            pub fn stop_all_tasks(&mut self) -> CuResult<()> {
1661                                <Self as #app_trait<MmapSectionStorage, UnifiedLoggerWrite>>::stop_all_tasks(self)
1662                            }
1663                        }
1664            })
1665        } else {
1666            None // if no-std, let the user figure our the correct logger type they need to provide anyway.
1667        };
1668
1669        let application_builder = if std {
1670            Some(quote! {
1671                #builder_struct
1672
1673                #builder_impl
1674                {
1675                    #builder_new
1676
1677                    #[allow(dead_code)]
1678                    pub fn with_clock(mut self, clock: RobotClock) -> Self {
1679                        self.clock = Some(clock);
1680                        self
1681                    }
1682
1683                    #[allow(dead_code)]
1684                    pub fn with_unified_logger(mut self, unified_logger: Arc<Mutex<UnifiedLoggerWrite>>) -> Self {
1685                        self.unified_logger = Some(unified_logger);
1686                        self
1687                    }
1688
1689                    #[allow(dead_code)]
1690                    pub fn with_context(mut self, copper_ctx: &CopperContext) -> Self {
1691                        self.clock = Some(copper_ctx.clock.clone());
1692                        self.unified_logger = Some(copper_ctx.unified_logger.clone());
1693                        self
1694                    }
1695
1696                    #[allow(dead_code)]
1697                    pub fn with_config(mut self, config_override: CuConfig) -> Self {
1698                            self.config_override = Some(config_override);
1699                            self
1700                    }
1701
1702                    #builder_sim_callback_method
1703
1704                    #[allow(dead_code)]
1705                    pub fn build(self) -> CuResult<#application_name> {
1706                        #application_name::new(
1707                            self.clock
1708                                .ok_or(CuError::from("Clock missing from builder"))?,
1709                            self.unified_logger
1710                                .ok_or(CuError::from("Unified logger missing from builder"))?,
1711                            self.config_override,
1712                            #builder_build_sim_callback_arg
1713                        )
1714                    }
1715                }
1716            })
1717        } else {
1718            // in no-std the user has to construct that manually anyway so don't make any helper here.
1719            None
1720        };
1721
1722        let sim_imports = if sim_mode {
1723            Some(quote! {
1724                use cu29::simulation::SimOverride;
1725                use cu29::simulation::CuTaskCallbackState;
1726                use cu29::simulation::CuSimSrcTask;
1727                use cu29::simulation::CuSimSinkTask;
1728                use cu29::prelude::app::CuSimApplication;
1729            })
1730        } else {
1731            None
1732        };
1733
1734        let sim_tasks = if sim_mode {
1735            Some(quote! {
1736                // This is the variation with stubs for the sources and sinks in simulation mode.
1737                // Not used if the used doesn't generate Sim.
1738                pub type CuSimTasks = #task_types_tuple_sim;
1739            })
1740        } else {
1741            None
1742        };
1743
1744        let sim_inst_body = if task_sim_instances_init_code.is_empty() {
1745            quote! {
1746                let _ = resources;
1747                Ok(())
1748            }
1749        } else {
1750            quote! { Ok(( #(#task_sim_instances_init_code),*, )) }
1751        };
1752
1753        let sim_tasks_instanciator = if sim_mode {
1754            Some(quote! {
1755                pub fn tasks_instanciator_sim(
1756                    all_instances_configs: Vec<Option<&ComponentConfig>>,
1757                    resources: &mut ResourceManager,
1758                ) -> CuResult<CuSimTasks> {
1759                    #sim_inst_body
1760            }})
1761        } else {
1762            None
1763        };
1764
1765        let tasks_inst_body_std = if task_instances_init_code.is_empty() {
1766            quote! {
1767                let _ = resources;
1768                Ok(())
1769            }
1770        } else {
1771            quote! { Ok(( #(#task_instances_init_code),*, )) }
1772        };
1773
1774        let tasks_inst_body_nostd = if task_instances_init_code.is_empty() {
1775            quote! {
1776                let _ = resources;
1777                Ok(())
1778            }
1779        } else {
1780            quote! { Ok(( #(#task_instances_init_code),*, )) }
1781        };
1782
1783        let tasks_instanciator = if std {
1784            quote! {
1785                pub fn tasks_instanciator<'c>(
1786                    all_instances_configs: Vec<Option<&'c ComponentConfig>>,
1787                    resources: &mut ResourceManager,
1788                ) -> CuResult<CuTasks> {
1789                    #tasks_inst_body_std
1790                }
1791            }
1792        } else {
1793            // no thread pool in the no-std impl
1794            quote! {
1795                pub fn tasks_instanciator<'c>(
1796                    all_instances_configs: Vec<Option<&'c ComponentConfig>>,
1797                    resources: &mut ResourceManager,
1798                ) -> CuResult<CuTasks> {
1799                    #tasks_inst_body_nostd
1800                }
1801            }
1802        };
1803
1804        let imports = if std {
1805            quote! {
1806                use cu29::rayon::ThreadPool;
1807                use cu29::cuasynctask::CuAsyncTask;
1808                use cu29::curuntime::CopperContext;
1809                use cu29::resource::ResourceMapping;
1810                use cu29::resource::{ResourceBindings, ResourceManager};
1811                use cu29::prelude::UnifiedLoggerWrite;
1812                use cu29::prelude::memmap::MmapSectionStorage;
1813                use std::fmt::{Debug, Formatter};
1814                use std::fmt::Result as FmtResult;
1815                use std::mem::size_of;
1816                use std::sync::Arc;
1817                use std::sync::atomic::{AtomicBool, Ordering};
1818                use std::sync::Mutex;
1819            }
1820        } else {
1821            quote! {
1822                use alloc::sync::Arc;
1823                use alloc::string::String;
1824                use alloc::string::ToString;
1825                use core::sync::atomic::{AtomicBool, Ordering};
1826                use core::fmt::{Debug, Formatter};
1827                use core::fmt::Result as FmtResult;
1828                use core::mem::size_of;
1829                use spin::Mutex;
1830                use cu29::resource::{ResourceBindings, ResourceManager};
1831                use cu29::resource::ResourceMapping;
1832            }
1833        };
1834
1835        // Convert the modified struct back into a TokenStream
1836        let mission_mod_tokens = quote! {
1837            mod #mission_mod {
1838                use super::*;  // import the modules the main app did.
1839
1840                use cu29::bincode::Encode;
1841                use cu29::bincode::enc::Encoder;
1842                use cu29::bincode::error::EncodeError;
1843                use cu29::bincode::Decode;
1844                use cu29::bincode::de::Decoder;
1845                use cu29::bincode::de::DecoderImpl;
1846                use cu29::bincode::error::DecodeError;
1847                use cu29::clock::RobotClock;
1848                use cu29::config::CuConfig;
1849                use cu29::config::ComponentConfig;
1850                use cu29::curuntime::CuRuntime;
1851                use cu29::curuntime::KeyFrame;
1852                use cu29::CuResult;
1853                use cu29::CuError;
1854                use cu29::cutask::CuSrcTask;
1855                use cu29::cutask::CuSinkTask;
1856                use cu29::cutask::CuTask;
1857                use cu29::cutask::CuMsg;
1858                use cu29::cutask::CuMsgMetadata;
1859                use cu29::copperlist::CopperList;
1860                use cu29::monitoring::CuMonitor; // Trait import.
1861                use cu29::monitoring::CuTaskState;
1862                use cu29::monitoring::Decision;
1863                use cu29::prelude::app::CuApplication;
1864                use cu29::prelude::debug;
1865                use cu29::prelude::stream_write;
1866                use cu29::prelude::UnifiedLogType;
1867                use cu29::prelude::UnifiedLogWrite;
1868
1869                #imports
1870
1871                #sim_imports
1872
1873                // Not used if a monitor is present
1874                #[allow(unused_imports)]
1875                use cu29::monitoring::NoMonitor;
1876
1877                // This is the heart of everything.
1878                // CuTasks is the list of all the tasks types.
1879                // CuList is a CopperList with the list of all the messages types as msgs.
1880                pub type CuTasks = #task_types_tuple;
1881                pub type CuBridges = #bridges_type_tokens;
1882                #resources_module
1883                #resources_instanciator_fn
1884                #task_resource_mappings_tokens
1885                #bridge_resource_mappings_tokens
1886
1887                #sim_tasks
1888                #sim_support
1889                #sim_tasks_instanciator
1890
1891                pub const TASKS_IDS: &'static [&'static str] = &[#( #ids ),*];
1892
1893                #culist_support
1894                #tasks_instanciator
1895                #bridges_instanciator
1896
1897                pub fn monitor_instanciator(config: &CuConfig) -> #monitor_type {
1898                    #monitor_type::new(config, #mission_mod::TASKS_IDS).expect("Failed to create the given monitor.")
1899                }
1900
1901                // The application for this mission
1902                pub #application_struct
1903
1904                #application_impl
1905
1906                #std_application_impl
1907
1908                #application_builder
1909            }
1910
1911        };
1912        all_missions_tokens.push(mission_mod_tokens);
1913    }
1914
1915    let default_application_tokens = if all_missions.contains_key("default") {
1916        let default_builder = if std {
1917            Some(quote! {
1918                // you can bypass the builder and not use it
1919                #[allow(unused_imports)]
1920                use default::#builder_name;
1921            })
1922        } else {
1923            None
1924        };
1925        quote! {
1926            #default_builder
1927
1928            #[allow(unused_imports)]
1929            use default::#application_name;
1930        }
1931    } else {
1932        quote!() // do nothing
1933    };
1934
1935    let result: proc_macro2::TokenStream = quote! {
1936        #(#all_missions_tokens)*
1937        #default_application_tokens
1938    };
1939
1940    // Print and format the generated code using rustfmt
1941    #[cfg(feature = "macro_debug")]
1942    {
1943        let formatted_code = rustfmt_generated_code(result.to_string());
1944        eprintln!("\n     ===    Gen. Runtime ===\n");
1945        eprintln!("{formatted_code}");
1946        // if you need colors back: eprintln!("{}", highlight_rust_code(formatted_code)); was disabled for cubuild.
1947        // or simply use cargo expand
1948        eprintln!("\n     === === === === === ===\n");
1949    }
1950    result.into()
1951}
1952
1953fn read_config(config_file: &str) -> CuResult<CuConfig> {
1954    let filename = config_full_path(config_file);
1955
1956    read_configuration(filename.as_str())
1957}
1958
1959fn config_full_path(config_file: &str) -> String {
1960    let mut config_full_path = utils::caller_crate_root();
1961    config_full_path.push(config_file);
1962    let filename = config_full_path
1963        .as_os_str()
1964        .to_str()
1965        .expect("Could not interpret the config file name");
1966    filename.to_string()
1967}
1968
1969fn extract_tasks_output_types(graph: &CuGraph) -> Vec<Option<Type>> {
1970    graph
1971        .get_all_nodes()
1972        .iter()
1973        .map(|(_, node)| {
1974            let id = node.get_id();
1975            let type_str = graph.get_node_output_msg_type(id.as_str());
1976            type_str.map(|type_str| {
1977                parse_str::<Type>(type_str.as_str()).expect("Could not parse output message type.")
1978            })
1979        })
1980        .collect()
1981}
1982
1983struct CuTaskSpecSet {
1984    pub ids: Vec<String>,
1985    pub cutypes: Vec<CuTaskType>,
1986    pub background_flags: Vec<bool>,
1987    pub logging_enabled: Vec<bool>,
1988    pub type_names: Vec<String>,
1989    pub task_types: Vec<Type>,
1990    pub instantiation_types: Vec<Type>,
1991    pub sim_task_types: Vec<Type>,
1992    pub run_in_sim_flags: Vec<bool>,
1993    #[allow(dead_code)]
1994    pub output_types: Vec<Option<Type>>,
1995    pub node_id_to_task_index: Vec<Option<usize>>,
1996}
1997
1998impl CuTaskSpecSet {
1999    pub fn from_graph(graph: &CuGraph) -> Self {
2000        let all_id_nodes: Vec<(NodeId, &Node)> = graph
2001            .get_all_nodes()
2002            .into_iter()
2003            .filter(|(_, node)| node.get_flavor() == Flavor::Task)
2004            .collect();
2005
2006        let ids = all_id_nodes
2007            .iter()
2008            .map(|(_, node)| node.get_id().to_string())
2009            .collect();
2010
2011        let cutypes = all_id_nodes
2012            .iter()
2013            .map(|(id, _)| find_task_type_for_id(graph, *id))
2014            .collect();
2015
2016        let background_flags: Vec<bool> = all_id_nodes
2017            .iter()
2018            .map(|(_, node)| node.is_background())
2019            .collect();
2020
2021        let logging_enabled: Vec<bool> = all_id_nodes
2022            .iter()
2023            .map(|(_, node)| node.is_logging_enabled())
2024            .collect();
2025
2026        let type_names: Vec<String> = all_id_nodes
2027            .iter()
2028            .map(|(_, node)| node.get_type().to_string())
2029            .collect();
2030
2031        let output_types = extract_tasks_output_types(graph);
2032
2033        let task_types = type_names
2034            .iter()
2035            .zip(background_flags.iter())
2036            .zip(output_types.iter())
2037            .map(|((name, &background), output_type)| {
2038                let name_type = parse_str::<Type>(name).unwrap_or_else(|error| {
2039                    panic!("Could not transform {name} into a Task Rust type: {error}");
2040                });
2041                if background {
2042                    if let Some(output_type) = output_type {
2043                        parse_quote!(CuAsyncTask<#name_type, #output_type>)
2044                    } else {
2045                        panic!("{name}: If a task is background, it has to have an output");
2046                    }
2047                } else {
2048                    name_type
2049                }
2050            })
2051            .collect();
2052
2053        let instantiation_types = type_names
2054            .iter()
2055            .zip(background_flags.iter())
2056            .zip(output_types.iter())
2057            .map(|((name, &background), output_type)| {
2058                let name_type = parse_str::<Type>(name).unwrap_or_else(|error| {
2059                    panic!("Could not transform {name} into a Task Rust type: {error}");
2060                });
2061                if background {
2062                    if let Some(output_type) = output_type {
2063                        parse_quote!(CuAsyncTask::<#name_type, #output_type>)
2064                    } else {
2065                        panic!("{name}: If a task is background, it has to have an output");
2066                    }
2067                } else {
2068                    name_type
2069                }
2070            })
2071            .collect();
2072
2073        let sim_task_types = type_names
2074            .iter()
2075            .map(|name| {
2076                parse_str::<Type>(name).unwrap_or_else(|err| {
2077                    eprintln!("Could not transform {name} into a Task Rust type.");
2078                    panic!("{err}")
2079                })
2080            })
2081            .collect();
2082
2083        let run_in_sim_flags = all_id_nodes
2084            .iter()
2085            .map(|(_, node)| node.is_run_in_sim())
2086            .collect();
2087
2088        let mut node_id_to_task_index = vec![None; graph.node_count()];
2089        for (index, (node_id, _)) in all_id_nodes.iter().enumerate() {
2090            node_id_to_task_index[*node_id as usize] = Some(index);
2091        }
2092
2093        Self {
2094            ids,
2095            cutypes,
2096            background_flags,
2097            logging_enabled,
2098            type_names,
2099            task_types,
2100            instantiation_types,
2101            sim_task_types,
2102            run_in_sim_flags,
2103            output_types,
2104            node_id_to_task_index,
2105        }
2106    }
2107}
2108
2109fn extract_msg_types(runtime_plan: &CuExecutionLoop) -> Vec<Type> {
2110    runtime_plan
2111        .steps
2112        .iter()
2113        .filter_map(|unit| match unit {
2114            CuExecutionUnit::Step(step) => {
2115                if let Some((_, output_msg_type)) = &step.output_msg_index_type {
2116                    Some(
2117                        parse_str::<Type>(output_msg_type.as_str()).unwrap_or_else(|_| {
2118                            panic!(
2119                                "Could not transform {output_msg_type} into a message Rust type."
2120                            )
2121                        }),
2122                    )
2123                } else {
2124                    None
2125                }
2126            }
2127            CuExecutionUnit::Loop(_) => todo!("Needs to be implemented"),
2128        })
2129        .collect()
2130}
2131
2132/// Builds the tuple of the CuList as a tuple off all the messages types.
2133fn build_culist_tuple(all_msgs_types_in_culist_order: &[Type]) -> TypeTuple {
2134    if all_msgs_types_in_culist_order.is_empty() {
2135        parse_quote! { () }
2136    } else {
2137        parse_quote! {
2138            ( #( CuMsg<#all_msgs_types_in_culist_order> ),* )
2139        }
2140    }
2141}
2142
2143/// This is the bincode encoding part of the CuStampedDataSet
2144fn build_culist_tuple_encode(all_msgs_types_in_culist_order: &[Type]) -> ItemImpl {
2145    let indices: Vec<usize> = (0..all_msgs_types_in_culist_order.len()).collect();
2146
2147    // Generate the `self.#i.encode(encoder)?` for each tuple index, including `()` types
2148    let encode_fields: Vec<_> = indices
2149        .iter()
2150        .map(|i| {
2151            let idx = syn::Index::from(*i);
2152            quote! { self.0.#idx.encode(encoder)?; }
2153        })
2154        .collect();
2155
2156    parse_quote! {
2157        impl Encode for CuStampedDataSet {
2158            fn encode<E: Encoder>(&self, encoder: &mut E) -> Result<(), EncodeError> {
2159                #(#encode_fields)*
2160                Ok(())
2161            }
2162        }
2163    }
2164}
2165
2166/// This is the bincode decoding part of the CuStampedDataSet
2167fn build_culist_tuple_decode(all_msgs_types_in_culist_order: &[Type]) -> ItemImpl {
2168    let indices: Vec<usize> = (0..all_msgs_types_in_culist_order.len()).collect();
2169
2170    // Generate the `CuStampedData::<T>::decode(decoder)?` for each tuple index
2171    let decode_fields: Vec<_> = indices
2172        .iter()
2173        .map(|i| {
2174            let t = &all_msgs_types_in_culist_order[*i];
2175            quote! { CuMsg::<#t>::decode(decoder)? }
2176        })
2177        .collect();
2178
2179    parse_quote! {
2180        impl Decode<()> for CuStampedDataSet {
2181            fn decode<D: Decoder<Context=()>>(decoder: &mut D) -> Result<Self, DecodeError> {
2182                Ok(CuStampedDataSet ((
2183                    #(#decode_fields),*
2184                )))
2185            }
2186        }
2187    }
2188}
2189
2190fn build_culist_erasedcumsgs(all_msgs_types_in_culist_order: &[Type]) -> ItemImpl {
2191    let indices: Vec<usize> = (0..all_msgs_types_in_culist_order.len()).collect();
2192    let casted_fields: Vec<_> = indices
2193        .iter()
2194        .map(|i| {
2195            let idx = syn::Index::from(*i);
2196            quote! { &self.0.#idx as &dyn ErasedCuStampedData }
2197        })
2198        .collect();
2199    parse_quote! {
2200        impl ErasedCuStampedDataSet for CuStampedDataSet {
2201            fn cumsgs(&self) -> Vec<&dyn ErasedCuStampedData> {
2202                vec![
2203                    #(#casted_fields),*
2204                ]
2205            }
2206        }
2207    }
2208}
2209
2210fn build_culist_tuple_debug(all_msgs_types_in_culist_order: &[Type]) -> ItemImpl {
2211    let indices: Vec<usize> = (0..all_msgs_types_in_culist_order.len()).collect();
2212
2213    let debug_fields: Vec<_> = indices
2214        .iter()
2215        .map(|i| {
2216            let idx = syn::Index::from(*i);
2217            quote! { .field(&self.0.#idx) }
2218        })
2219        .collect();
2220
2221    parse_quote! {
2222        impl Debug for CuStampedDataSet {
2223            fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
2224                f.debug_tuple("CuStampedDataSet")
2225                    #(#debug_fields)*
2226                    .finish()
2227            }
2228        }
2229    }
2230}
2231
2232/// This is the serde serialization part of the CuStampedDataSet
2233fn build_culist_tuple_serialize(all_msgs_types_in_culist_order: &[Type]) -> ItemImpl {
2234    let indices: Vec<usize> = (0..all_msgs_types_in_culist_order.len()).collect();
2235    let tuple_len = all_msgs_types_in_culist_order.len();
2236
2237    // Generate the serialization for each tuple field
2238    let serialize_fields: Vec<_> = indices
2239        .iter()
2240        .map(|i| {
2241            let idx = syn::Index::from(*i);
2242            quote! { &self.0.#idx }
2243        })
2244        .collect();
2245
2246    parse_quote! {
2247        impl Serialize for CuStampedDataSet {
2248            fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
2249            where
2250                S: serde::Serializer,
2251            {
2252                use serde::ser::SerializeTuple;
2253                let mut tuple = serializer.serialize_tuple(#tuple_len)?;
2254                #(tuple.serialize_element(#serialize_fields)?;)*
2255                tuple.end()
2256            }
2257        }
2258    }
2259}
2260
2261/// This is the default implementation for CuStampedDataSet
2262fn build_culist_tuple_default(all_msgs_types_in_culist_order: &[Type]) -> ItemImpl {
2263    // Generate the serialization for each tuple field
2264    let default_fields: Vec<_> = all_msgs_types_in_culist_order
2265        .iter()
2266        .map(|msg_type| quote! { CuStampedData::<#msg_type, CuMsgMetadata>::default() })
2267        .collect();
2268
2269    parse_quote! {
2270        impl Default for CuStampedDataSet {
2271            fn default() -> CuStampedDataSet
2272            {
2273                CuStampedDataSet((
2274                    #(#default_fields),*
2275                ))
2276            }
2277        }
2278    }
2279}
2280
2281fn collect_bridge_channel_usage(graph: &CuGraph) -> HashMap<BridgeChannelKey, String> {
2282    let mut usage = HashMap::new();
2283    for edge_idx in graph.0.edge_indices() {
2284        let cnx = graph
2285            .0
2286            .edge_weight(edge_idx)
2287            .expect("Edge should exist while collecting bridge usage")
2288            .clone();
2289        if let Some(channel) = &cnx.src_channel {
2290            let key = BridgeChannelKey {
2291                bridge_id: cnx.src.clone(),
2292                channel_id: channel.clone(),
2293                direction: BridgeChannelDirection::Rx,
2294            };
2295            usage
2296                .entry(key)
2297                .and_modify(|msg| {
2298                    if msg != &cnx.msg {
2299                        panic!(
2300                            "Bridge '{}' channel '{}' is used with incompatible message types: {} vs {}",
2301                            cnx.src, channel, msg, cnx.msg
2302                        );
2303                    }
2304                })
2305                .or_insert(cnx.msg.clone());
2306        }
2307        if let Some(channel) = &cnx.dst_channel {
2308            let key = BridgeChannelKey {
2309                bridge_id: cnx.dst.clone(),
2310                channel_id: channel.clone(),
2311                direction: BridgeChannelDirection::Tx,
2312            };
2313            usage
2314                .entry(key)
2315                .and_modify(|msg| {
2316                    if msg != &cnx.msg {
2317                        panic!(
2318                            "Bridge '{}' channel '{}' is used with incompatible message types: {} vs {}",
2319                            cnx.dst, channel, msg, cnx.msg
2320                        );
2321                    }
2322                })
2323                .or_insert(cnx.msg.clone());
2324        }
2325    }
2326    usage
2327}
2328
2329fn build_bridge_specs(
2330    config: &CuConfig,
2331    graph: &CuGraph,
2332    channel_usage: &HashMap<BridgeChannelKey, String>,
2333) -> Vec<BridgeSpec> {
2334    let mut specs = Vec::new();
2335    for (bridge_index, bridge_cfg) in config.bridges.iter().enumerate() {
2336        if graph.get_node_id_by_name(bridge_cfg.id.as_str()).is_none() {
2337            continue;
2338        }
2339
2340        let type_path = parse_str::<Type>(bridge_cfg.type_.as_str()).unwrap_or_else(|err| {
2341            panic!(
2342                "Could not parse bridge type '{}' for '{}': {err}",
2343                bridge_cfg.type_, bridge_cfg.id
2344            )
2345        });
2346
2347        let mut rx_channels = Vec::new();
2348        let mut tx_channels = Vec::new();
2349
2350        for (channel_index, channel) in bridge_cfg.channels.iter().enumerate() {
2351            match channel {
2352                BridgeChannelConfigRepresentation::Rx { id, .. } => {
2353                    let key = BridgeChannelKey {
2354                        bridge_id: bridge_cfg.id.clone(),
2355                        channel_id: id.clone(),
2356                        direction: BridgeChannelDirection::Rx,
2357                    };
2358                    if let Some(msg_type) = channel_usage.get(&key) {
2359                        let msg_type = parse_str::<Type>(msg_type).unwrap_or_else(|err| {
2360                            panic!(
2361                                "Could not parse message type '{msg_type}' for bridge '{}' channel '{}': {err}",
2362                                bridge_cfg.id, id
2363                            )
2364                        });
2365                        let const_ident =
2366                            Ident::new(&config_id_to_bridge_const(id.as_str()), Span::call_site());
2367                        rx_channels.push(BridgeChannelSpec {
2368                            id: id.clone(),
2369                            const_ident,
2370                            msg_type,
2371                            config_index: channel_index,
2372                            plan_node_id: None,
2373                            culist_index: None,
2374                            monitor_index: None,
2375                        });
2376                    }
2377                }
2378                BridgeChannelConfigRepresentation::Tx { id, .. } => {
2379                    let key = BridgeChannelKey {
2380                        bridge_id: bridge_cfg.id.clone(),
2381                        channel_id: id.clone(),
2382                        direction: BridgeChannelDirection::Tx,
2383                    };
2384                    if let Some(msg_type) = channel_usage.get(&key) {
2385                        let msg_type = parse_str::<Type>(msg_type).unwrap_or_else(|err| {
2386                            panic!(
2387                                "Could not parse message type '{msg_type}' for bridge '{}' channel '{}': {err}",
2388                                bridge_cfg.id, id
2389                            )
2390                        });
2391                        let const_ident =
2392                            Ident::new(&config_id_to_bridge_const(id.as_str()), Span::call_site());
2393                        tx_channels.push(BridgeChannelSpec {
2394                            id: id.clone(),
2395                            const_ident,
2396                            msg_type,
2397                            config_index: channel_index,
2398                            plan_node_id: None,
2399                            culist_index: None,
2400                            monitor_index: None,
2401                        });
2402                    }
2403                }
2404            }
2405        }
2406
2407        if rx_channels.is_empty() && tx_channels.is_empty() {
2408            continue;
2409        }
2410
2411        specs.push(BridgeSpec {
2412            id: bridge_cfg.id.clone(),
2413            type_path,
2414            config_index: bridge_index,
2415            tuple_index: 0,
2416            monitor_index: None,
2417            rx_channels,
2418            tx_channels,
2419        });
2420    }
2421
2422    for (tuple_index, spec) in specs.iter_mut().enumerate() {
2423        spec.tuple_index = tuple_index;
2424    }
2425
2426    specs
2427}
2428
2429fn collect_task_member_names(graph: &CuGraph) -> Vec<(NodeId, String)> {
2430    graph
2431        .get_all_nodes()
2432        .iter()
2433        .filter(|(_, node)| node.get_flavor() == Flavor::Task)
2434        .map(|(node_id, node)| (*node_id, config_id_to_struct_member(node.get_id().as_str())))
2435        .collect()
2436}
2437
2438#[derive(Clone, Copy)]
2439enum ResourceOwner {
2440    Task(usize),
2441    Bridge(usize),
2442}
2443
2444#[derive(Clone)]
2445struct ResourceKeySpec {
2446    path: String,
2447    bundle_id: String,
2448    enum_ident: Ident,
2449    const_ident: Ident,
2450    binding_name: String,
2451    owner: ResourceOwner,
2452}
2453
2454fn parse_resource_path(path: &str) -> CuResult<(String, String)> {
2455    let (bundle_id, name) = path.split_once('.').ok_or_else(|| {
2456        CuError::from(format!(
2457            "Resource '{path}' is missing a bundle prefix (expected bundle.resource)"
2458        ))
2459    })?;
2460
2461    if bundle_id.is_empty() || name.is_empty() {
2462        return Err(CuError::from(format!(
2463            "Resource '{path}' must use the 'bundle.resource' format"
2464        )));
2465    }
2466
2467    Ok((bundle_id.to_string(), name.to_string()))
2468}
2469
2470fn collect_resource_specs(
2471    graph: &CuGraph,
2472    task_specs: &CuTaskSpecSet,
2473    bridge_specs: &[BridgeSpec],
2474) -> CuResult<Vec<ResourceKeySpec>> {
2475    let mut path_registry: BTreeMap<String, (String, Ident, Ident)> = BTreeMap::new();
2476    let mut bridge_lookup: BTreeMap<String, usize> = BTreeMap::new();
2477    for (idx, spec) in bridge_specs.iter().enumerate() {
2478        bridge_lookup.insert(spec.id.clone(), idx);
2479    }
2480    let mut specs = Vec::new();
2481
2482    let mut push_spec =
2483        |owner: ResourceOwner, binding_name: String, path: String| -> CuResult<()> {
2484            let (bundle_id, name) = parse_resource_path(&path)?;
2485            let (bundle_id, enum_ident, const_ident) = path_registry
2486                .entry(path.clone())
2487                .or_insert_with(|| {
2488                    let enum_ident =
2489                        Ident::new(&config_id_to_enum(path.as_str()), Span::call_site());
2490                    let const_ident =
2491                        Ident::new(&config_id_to_bridge_const(name.as_str()), Span::call_site());
2492                    (bundle_id.clone(), enum_ident, const_ident)
2493                })
2494                .clone();
2495
2496            specs.push(ResourceKeySpec {
2497                path,
2498                bundle_id,
2499                enum_ident,
2500                const_ident,
2501                binding_name,
2502                owner,
2503            });
2504            Ok(())
2505        };
2506
2507    for (node_id, node) in graph.get_all_nodes() {
2508        let resources = node.get_resources();
2509        if let Some(resources) = resources {
2510            let task_index = task_specs.node_id_to_task_index[node_id as usize];
2511            let owner = if let Some(task_index) = task_index {
2512                ResourceOwner::Task(task_index)
2513            } else if node.get_flavor() == Flavor::Bridge {
2514                let bridge_index = bridge_lookup.get(&node.get_id()).ok_or_else(|| {
2515                    CuError::from(format!(
2516                        "Resource mapping attached to unknown bridge node '{}'",
2517                        node.get_id()
2518                    ))
2519                })?;
2520                ResourceOwner::Bridge(*bridge_index)
2521            } else {
2522                return Err(CuError::from(format!(
2523                    "Resource mapping attached to non-task node '{}'",
2524                    node.get_id()
2525                )));
2526            };
2527
2528            for (binding_name, path) in resources {
2529                push_spec(owner, binding_name.clone(), path.clone())?;
2530            }
2531        }
2532
2533        if let Some(task_index) = task_specs.node_id_to_task_index[node_id as usize]
2534            && task_specs.background_flags[task_index]
2535            && resources
2536                .map(|res| !res.contains_key("bg_threads"))
2537                .unwrap_or(true)
2538        {
2539            push_spec(
2540                ResourceOwner::Task(task_index),
2541                "bg_threads".to_string(),
2542                "threadpool.bg_threads".to_string(),
2543            )?;
2544        }
2545    }
2546
2547    Ok(specs)
2548}
2549
2550fn build_bundle_list<'a>(config: &'a CuConfig, mission: &str) -> Vec<&'a ResourceBundleConfig> {
2551    config
2552        .resources
2553        .iter()
2554        .filter(|bundle| {
2555            bundle
2556                .missions
2557                .as_ref()
2558                .is_none_or(|missions| missions.iter().any(|m| m == mission))
2559        })
2560        .collect()
2561}
2562
2563fn build_resources_module(
2564    mission: &str,
2565    config: &CuConfig,
2566    resource_specs: &[ResourceKeySpec],
2567) -> CuResult<(proc_macro2::TokenStream, proc_macro2::TokenStream)> {
2568    let mut unique_specs: Vec<&ResourceKeySpec> = Vec::new();
2569    let mut seen_paths = BTreeSet::new();
2570    for spec in resource_specs {
2571        if seen_paths.insert(spec.path.clone()) {
2572            unique_specs.push(spec);
2573        }
2574    }
2575
2576    let bundle_specs = build_bundle_list(config, mission);
2577
2578    for spec in &unique_specs {
2579        if !bundle_specs
2580            .iter()
2581            .any(|bundle| bundle.id == spec.bundle_id)
2582        {
2583            return Err(CuError::from(format!(
2584                "Resource '{}' references unknown bundle '{}' for mission '{}'",
2585                spec.path, spec.bundle_id, mission
2586            )));
2587        }
2588    }
2589
2590    let mut resources_by_bundle: BTreeMap<String, Vec<&ResourceKeySpec>> = BTreeMap::new();
2591    for spec in &unique_specs {
2592        resources_by_bundle
2593            .entry(spec.bundle_id.clone())
2594            .or_default()
2595            .push(spec);
2596    }
2597
2598    let res_id_variants = unique_specs.iter().map(|spec| {
2599        let ident = &spec.enum_ident;
2600        quote! { #ident }
2601    });
2602    let num_resources = unique_specs.len();
2603    let res_id_def = if num_resources == 0 {
2604        quote! {
2605            #[derive(Copy, Clone, Debug, Eq, PartialEq)]
2606            pub enum ResId {
2607                __Unused,
2608            }
2609        }
2610    } else {
2611        quote! {
2612            #[derive(Copy, Clone, Debug, Eq, PartialEq)]
2613            #[repr(usize)]
2614            pub enum ResId {
2615                #(#res_id_variants,)*
2616            }
2617        }
2618    };
2619
2620    let bundle_modules = resources_by_bundle.iter().map(|(bundle_id, specs)| {
2621        let module_ident = Ident::new(
2622            &config_id_to_struct_member(bundle_id.as_str()),
2623            Span::call_site(),
2624        );
2625        let mut seen_consts = BTreeSet::new();
2626        let consts = specs.iter().filter_map(|spec| {
2627            let const_ident = &spec.const_ident;
2628            let enum_ident = &spec.enum_ident;
2629            if !seen_consts.insert(const_ident.to_string()) {
2630                return None;
2631            }
2632            Some(quote! {
2633                pub const #const_ident: super::ResourceKey =
2634                    super::ResourceKey::new(super::ResId::#enum_ident as usize);
2635            })
2636        });
2637
2638        quote! {
2639            pub mod #module_ident {
2640                #![allow(dead_code)]
2641                #(#consts)*
2642            }
2643        }
2644    });
2645
2646    let all_decl_entries = unique_specs.iter().map(|spec| {
2647        let module_ident = Ident::new(
2648            &config_id_to_struct_member(spec.bundle_id.as_str()),
2649            Span::call_site(),
2650        );
2651        let const_ident = &spec.const_ident;
2652        let path = LitStr::new(&spec.path, Span::call_site());
2653        quote! { ResourceDecl::new(#module_ident::#const_ident, #path) }
2654    });
2655
2656    let bundle_arrays = bundle_specs.iter().map(|bundle| {
2657        let array_ident = Ident::new(
2658            &config_id_to_bridge_const(bundle.id.as_str()),
2659            Span::call_site(),
2660        );
2661        let module_ident = Ident::new(
2662            &config_id_to_struct_member(bundle.id.as_str()),
2663            Span::call_site(),
2664        );
2665        let mut seen_paths = BTreeSet::new();
2666        let entries = resources_by_bundle
2667            .get(&bundle.id)
2668            .cloned()
2669            .unwrap_or_default()
2670            .into_iter()
2671            .filter_map(|spec| {
2672                if !seen_paths.insert(spec.path.clone()) {
2673                    return None;
2674                }
2675                let const_ident = &spec.const_ident;
2676                let path = LitStr::new(&spec.path, Span::call_site());
2677                Some(quote! { ResourceDecl::new(#module_ident::#const_ident, #path) })
2678            });
2679        quote! {
2680            pub const #array_ident: &[ResourceDecl] = &[ #(#entries),* ];
2681        }
2682    });
2683
2684    let provider_entries = bundle_specs
2685        .iter()
2686        .filter(|bundle| resources_by_bundle.contains_key(&bundle.id))
2687        .map(|bundle| {
2688            let array_ident = Ident::new(
2689                &config_id_to_bridge_const(bundle.id.as_str()),
2690                Span::call_site(),
2691            );
2692            let bundle_id = LitStr::new(bundle.id.as_str(), Span::call_site());
2693            let provider = LitStr::new(bundle.provider.as_str(), Span::call_site());
2694            quote! { ResourceProvider::new(#bundle_id, #provider, #array_ident) }
2695        });
2696
2697    let resources_module = quote! {
2698        pub mod resources {
2699            #![allow(dead_code)]
2700            use cu29::resource::{ResourceDecl, ResourceKey, ResourceProvider};
2701
2702            #res_id_def
2703
2704            pub const NUM_RESOURCES: usize = #num_resources;
2705
2706            #(#bundle_modules)*
2707
2708            pub const RESOURCES: &[ResourceDecl] = &[ #(#all_decl_entries),* ];
2709            #(#bundle_arrays)*
2710            pub const PROVIDERS: &[ResourceProvider] = &[ #(#provider_entries),* ];
2711        }
2712    };
2713
2714    let bundle_inits = bundle_specs
2715        .iter()
2716        .filter(|bundle| resources_by_bundle.contains_key(&bundle.id))
2717        .map(|bundle| {
2718            let bundle_id = LitStr::new(bundle.id.as_str(), Span::call_site());
2719            let array_ident = Ident::new(
2720                &config_id_to_bridge_const(bundle.id.as_str()),
2721                Span::call_site(),
2722            );
2723            let provider_path: syn::Path =
2724                syn::parse_str(bundle.provider.as_str()).map_err(|err| {
2725                    CuError::from(format!(
2726                        "Failed to parse provider path '{}' for bundle '{}': {err}",
2727                        bundle.provider, bundle.id
2728                    ))
2729                })?;
2730
2731            Ok(quote! {
2732                let bundle_cfg = config
2733                    .resources
2734                    .iter()
2735                    .find(|b| b.id == #bundle_id)
2736                    .unwrap_or_else(|| panic!("Resource bundle '{}' missing from configuration", #bundle_id));
2737                <#provider_path as cu29::resource::ResourceBundle>::build(
2738                    #bundle_id,
2739                    bundle_cfg.config.as_ref(),
2740                    resources::#array_ident,
2741                    &mut manager,
2742                )?;
2743            })
2744        })
2745        .collect::<CuResult<Vec<_>>>()?;
2746
2747    let resources_instanciator = quote! {
2748        pub fn resources_instanciator(config: &CuConfig) -> CuResult<cu29::resource::ResourceManager> {
2749            let mut manager = cu29::resource::ResourceManager::new(resources::NUM_RESOURCES);
2750            #(#bundle_inits)*
2751            Ok(manager)
2752        }
2753    };
2754
2755    Ok((resources_module, resources_instanciator))
2756}
2757
2758fn build_task_resource_mappings(
2759    resource_specs: &[ResourceKeySpec],
2760    task_specs: &CuTaskSpecSet,
2761) -> CuResult<proc_macro2::TokenStream> {
2762    let mut per_task: Vec<Vec<(&str, Ident, Ident)>> = vec![Vec::new(); task_specs.ids.len()];
2763
2764    for spec in resource_specs {
2765        let ResourceOwner::Task(task_index) = spec.owner else {
2766            continue;
2767        };
2768        let module_ident = Ident::new(
2769            &config_id_to_struct_member(spec.bundle_id.as_str()),
2770            Span::call_site(),
2771        );
2772        per_task
2773            .get_mut(task_index)
2774            .ok_or_else(|| {
2775                CuError::from(format!(
2776                    "Resource '{}' mapped to invalid task index {}",
2777                    spec.path, task_index
2778                ))
2779            })?
2780            .push((
2781                spec.binding_name.as_str(),
2782                module_ident,
2783                spec.const_ident.clone(),
2784            ));
2785    }
2786
2787    let mut mapping_entries = Vec::new();
2788    let mut mapping_consts = Vec::new();
2789    let mut mapping_refs = Vec::new();
2790
2791    for (idx, entries) in per_task.iter().enumerate() {
2792        if entries.is_empty() {
2793            mapping_refs.push(quote! { None });
2794            continue;
2795        }
2796        let entries_ident = format_ident!("TASK{}_RES_ENTRIES", idx);
2797        let map_ident = format_ident!("TASK{}_RES_MAPPING", idx);
2798        let entry_tokens = entries.iter().map(|(name, module_ident, const_ident)| {
2799            let name_lit = LitStr::new(name, Span::call_site());
2800            quote! { (#name_lit, resources::#module_ident::#const_ident) }
2801        });
2802        mapping_entries.push(quote! {
2803            const #entries_ident: &[(&str, cu29::resource::ResourceKey)] = &[ #(#entry_tokens),* ];
2804        });
2805        mapping_consts.push(quote! {
2806            const #map_ident: cu29::resource::ResourceMapping = cu29::resource::ResourceMapping::new(#entries_ident);
2807        });
2808        mapping_refs.push(quote! { Some(&#map_ident) });
2809    }
2810
2811    let mappings_array = quote! {
2812        pub const TASK_RESOURCE_MAPPINGS: &[Option<&cu29::resource::ResourceMapping>] =
2813            &[ #(#mapping_refs),* ];
2814    };
2815
2816    Ok(quote! {
2817        #(#mapping_entries)*
2818        #(#mapping_consts)*
2819        #mappings_array
2820    })
2821}
2822
2823fn build_bridge_resource_mappings(
2824    resource_specs: &[ResourceKeySpec],
2825    bridge_specs: &[BridgeSpec],
2826) -> proc_macro2::TokenStream {
2827    let mut per_bridge: Vec<Vec<(&str, Ident, Ident)>> = vec![Vec::new(); bridge_specs.len()];
2828
2829    for spec in resource_specs {
2830        let ResourceOwner::Bridge(bridge_index) = spec.owner else {
2831            continue;
2832        };
2833        let module_ident = Ident::new(
2834            &config_id_to_struct_member(spec.bundle_id.as_str()),
2835            Span::call_site(),
2836        );
2837        per_bridge[bridge_index].push((
2838            spec.binding_name.as_str(),
2839            module_ident,
2840            spec.const_ident.clone(),
2841        ));
2842    }
2843
2844    let mut mapping_entries = Vec::new();
2845    let mut mapping_consts = Vec::new();
2846    let mut mapping_refs = Vec::new();
2847
2848    for (idx, entries) in per_bridge.iter().enumerate() {
2849        if entries.is_empty() {
2850            mapping_refs.push(quote! { None });
2851            continue;
2852        }
2853        let entries_ident = format_ident!("BRIDGE{}_RES_ENTRIES", idx);
2854        let map_ident = format_ident!("BRIDGE{}_RES_MAPPING", idx);
2855        let entry_tokens = entries.iter().map(|(name, module_ident, const_ident)| {
2856            let name_lit = LitStr::new(name, Span::call_site());
2857            quote! { (#name_lit, resources::#module_ident::#const_ident) }
2858        });
2859        mapping_entries.push(quote! {
2860            const #entries_ident: &[(&str, cu29::resource::ResourceKey)] = &[ #(#entry_tokens),* ];
2861        });
2862        mapping_consts.push(quote! {
2863            const #map_ident: cu29::resource::ResourceMapping = cu29::resource::ResourceMapping::new(#entries_ident);
2864        });
2865        mapping_refs.push(quote! { Some(&#map_ident) });
2866    }
2867
2868    let mappings_array = quote! {
2869        pub const BRIDGE_RESOURCE_MAPPINGS: &[Option<&cu29::resource::ResourceMapping>] =
2870            &[ #(#mapping_refs),* ];
2871    };
2872
2873    quote! {
2874        #(#mapping_entries)*
2875        #(#mapping_consts)*
2876        #mappings_array
2877    }
2878}
2879
2880fn build_execution_plan(
2881    graph: &CuGraph,
2882    task_specs: &CuTaskSpecSet,
2883    bridge_specs: &mut [BridgeSpec],
2884) -> CuResult<(
2885    CuExecutionLoop,
2886    Vec<ExecutionEntity>,
2887    HashMap<NodeId, NodeId>,
2888)> {
2889    let mut plan_graph = CuGraph::default();
2890    let mut exec_entities = Vec::new();
2891    let mut original_to_plan = HashMap::new();
2892    let mut plan_to_original = HashMap::new();
2893    let mut name_to_original = HashMap::new();
2894    let mut channel_nodes = HashMap::new();
2895
2896    for (node_id, node) in graph.get_all_nodes() {
2897        name_to_original.insert(node.get_id(), node_id);
2898        if node.get_flavor() != Flavor::Task {
2899            continue;
2900        }
2901        let plan_node_id = plan_graph.add_node(node.clone())?;
2902        let task_index = task_specs.node_id_to_task_index[node_id as usize]
2903            .expect("Task missing from specifications");
2904        plan_to_original.insert(plan_node_id, node_id);
2905        original_to_plan.insert(node_id, plan_node_id);
2906        if plan_node_id as usize != exec_entities.len() {
2907            panic!("Unexpected node ordering while mirroring tasks in plan graph");
2908        }
2909        exec_entities.push(ExecutionEntity {
2910            kind: ExecutionEntityKind::Task { task_index },
2911        });
2912    }
2913
2914    for (bridge_index, spec) in bridge_specs.iter_mut().enumerate() {
2915        for (channel_index, channel_spec) in spec.rx_channels.iter_mut().enumerate() {
2916            let mut node = Node::new(
2917                format!("{}::rx::{}", spec.id, channel_spec.id).as_str(),
2918                "__CuBridgeRxChannel",
2919            );
2920            node.set_flavor(Flavor::Bridge);
2921            let plan_node_id = plan_graph.add_node(node)?;
2922            if plan_node_id as usize != exec_entities.len() {
2923                panic!("Unexpected node ordering while inserting bridge rx channel");
2924            }
2925            channel_spec.plan_node_id = Some(plan_node_id);
2926            exec_entities.push(ExecutionEntity {
2927                kind: ExecutionEntityKind::BridgeRx {
2928                    bridge_index,
2929                    channel_index,
2930                },
2931            });
2932            channel_nodes.insert(
2933                BridgeChannelKey {
2934                    bridge_id: spec.id.clone(),
2935                    channel_id: channel_spec.id.clone(),
2936                    direction: BridgeChannelDirection::Rx,
2937                },
2938                plan_node_id,
2939            );
2940        }
2941
2942        for (channel_index, channel_spec) in spec.tx_channels.iter_mut().enumerate() {
2943            let mut node = Node::new(
2944                format!("{}::tx::{}", spec.id, channel_spec.id).as_str(),
2945                "__CuBridgeTxChannel",
2946            );
2947            node.set_flavor(Flavor::Bridge);
2948            let plan_node_id = plan_graph.add_node(node)?;
2949            if plan_node_id as usize != exec_entities.len() {
2950                panic!("Unexpected node ordering while inserting bridge tx channel");
2951            }
2952            channel_spec.plan_node_id = Some(plan_node_id);
2953            exec_entities.push(ExecutionEntity {
2954                kind: ExecutionEntityKind::BridgeTx {
2955                    bridge_index,
2956                    channel_index,
2957                },
2958            });
2959            channel_nodes.insert(
2960                BridgeChannelKey {
2961                    bridge_id: spec.id.clone(),
2962                    channel_id: channel_spec.id.clone(),
2963                    direction: BridgeChannelDirection::Tx,
2964                },
2965                plan_node_id,
2966            );
2967        }
2968    }
2969
2970    for edge_idx in graph.0.edge_indices() {
2971        let cnx = graph
2972            .0
2973            .edge_weight(edge_idx)
2974            .expect("Edge should exist while building plan")
2975            .clone();
2976
2977        let src_plan = if let Some(channel) = &cnx.src_channel {
2978            let key = BridgeChannelKey {
2979                bridge_id: cnx.src.clone(),
2980                channel_id: channel.clone(),
2981                direction: BridgeChannelDirection::Rx,
2982            };
2983            *channel_nodes
2984                .get(&key)
2985                .unwrap_or_else(|| panic!("Bridge source {:?} missing from plan graph", key))
2986        } else {
2987            let node_id = name_to_original
2988                .get(&cnx.src)
2989                .copied()
2990                .unwrap_or_else(|| panic!("Unknown source node '{}'", cnx.src));
2991            *original_to_plan
2992                .get(&node_id)
2993                .unwrap_or_else(|| panic!("Source node '{}' missing from plan", cnx.src))
2994        };
2995
2996        let dst_plan = if let Some(channel) = &cnx.dst_channel {
2997            let key = BridgeChannelKey {
2998                bridge_id: cnx.dst.clone(),
2999                channel_id: channel.clone(),
3000                direction: BridgeChannelDirection::Tx,
3001            };
3002            *channel_nodes
3003                .get(&key)
3004                .unwrap_or_else(|| panic!("Bridge destination {:?} missing from plan graph", key))
3005        } else {
3006            let node_id = name_to_original
3007                .get(&cnx.dst)
3008                .copied()
3009                .unwrap_or_else(|| panic!("Unknown destination node '{}'", cnx.dst));
3010            *original_to_plan
3011                .get(&node_id)
3012                .unwrap_or_else(|| panic!("Destination node '{}' missing from plan", cnx.dst))
3013        };
3014
3015        plan_graph
3016            .connect_ext(
3017                src_plan,
3018                dst_plan,
3019                &cnx.msg,
3020                cnx.missions.clone(),
3021                None,
3022                None,
3023            )
3024            .map_err(|e| CuError::from(e.to_string()))?;
3025    }
3026
3027    let runtime_plan = compute_runtime_plan(&plan_graph)?;
3028    Ok((runtime_plan, exec_entities, plan_to_original))
3029}
3030
3031fn collect_culist_metadata(
3032    runtime_plan: &CuExecutionLoop,
3033    exec_entities: &[ExecutionEntity],
3034    bridge_specs: &mut [BridgeSpec],
3035    plan_to_original: &HashMap<NodeId, NodeId>,
3036) -> (Vec<usize>, HashMap<NodeId, usize>) {
3037    let mut culist_order = Vec::new();
3038    let mut node_output_positions = HashMap::new();
3039
3040    for unit in &runtime_plan.steps {
3041        if let CuExecutionUnit::Step(step) = unit
3042            && let Some((output_idx, _)) = &step.output_msg_index_type
3043        {
3044            culist_order.push(*output_idx as usize);
3045            match &exec_entities[step.node_id as usize].kind {
3046                ExecutionEntityKind::Task { .. } => {
3047                    if let Some(original_node_id) = plan_to_original.get(&step.node_id) {
3048                        node_output_positions.insert(*original_node_id, *output_idx as usize);
3049                    }
3050                }
3051                ExecutionEntityKind::BridgeRx {
3052                    bridge_index,
3053                    channel_index,
3054                } => {
3055                    bridge_specs[*bridge_index].rx_channels[*channel_index].culist_index =
3056                        Some(*output_idx as usize);
3057                }
3058                ExecutionEntityKind::BridgeTx { .. } => {}
3059            }
3060        }
3061    }
3062
3063    (culist_order, node_output_positions)
3064}
3065
3066#[allow(dead_code)]
3067fn build_monitored_ids(task_ids: &[String], bridge_specs: &mut [BridgeSpec]) -> Vec<String> {
3068    let mut names = task_ids.to_vec();
3069    for spec in bridge_specs.iter_mut() {
3070        spec.monitor_index = Some(names.len());
3071        names.push(format!("bridge::{}", spec.id));
3072        for channel in spec.rx_channels.iter_mut() {
3073            channel.monitor_index = Some(names.len());
3074            names.push(format!("bridge::{}::rx::{}", spec.id, channel.id));
3075        }
3076        for channel in spec.tx_channels.iter_mut() {
3077            channel.monitor_index = Some(names.len());
3078            names.push(format!("bridge::{}::tx::{}", spec.id, channel.id));
3079        }
3080    }
3081    names
3082}
3083
3084fn generate_task_execution_tokens(
3085    step: &CuExecutionStep,
3086    task_index: usize,
3087    task_specs: &CuTaskSpecSet,
3088    sim_mode: bool,
3089    mission_mod: &Ident,
3090) -> (proc_macro2::TokenStream, proc_macro2::TokenStream) {
3091    let node_index = int2sliceindex(task_index as u32);
3092    let task_instance = quote! { tasks.#node_index };
3093    let comment_str = format!(
3094        "DEBUG ->> {} ({:?}) Id:{} I:{:?} O:{:?}",
3095        step.node.get_id(),
3096        step.task_type,
3097        step.node_id,
3098        step.input_msg_indices_types,
3099        step.output_msg_index_type
3100    );
3101    let comment_tokens = quote! {{
3102        let _ = stringify!(#comment_str);
3103    }};
3104    let tid = task_index;
3105    let task_enum_name = config_id_to_enum(&task_specs.ids[tid]);
3106    let enum_name = Ident::new(&task_enum_name, Span::call_site());
3107
3108    match step.task_type {
3109        CuTaskType::Source => {
3110            if let Some((output_index, _)) = &step.output_msg_index_type {
3111                let output_culist_index = int2sliceindex(*output_index);
3112
3113                let monitoring_action = quote! {
3114                    debug!("Task {}: Error during process: {}", #mission_mod::TASKS_IDS[#tid], &error);
3115                    let decision = monitor.process_error(#tid, CuTaskState::Process, &error);
3116                    match decision {
3117                        Decision::Abort => {
3118                            debug!("Process: ABORT decision from monitoring. Task '{}' errored out \
3119                                    during process. Skipping the processing of CL {}.", #mission_mod::TASKS_IDS[#tid], clid);
3120                            monitor.process_copperlist(&#mission_mod::collect_metadata(&culist))?;
3121                            cl_manager.end_of_processing(clid)?;
3122                            return Ok(());
3123                        }
3124                        Decision::Ignore => {
3125                            debug!("Process: IGNORE decision from monitoring. Task '{}' errored out \
3126                                    during process. The runtime will continue with a forced empty message.", #mission_mod::TASKS_IDS[#tid]);
3127                            let cumsg_output = &mut msgs.#output_culist_index;
3128                            cumsg_output.clear_payload();
3129                        }
3130                        Decision::Shutdown => {
3131                            debug!("Process: SHUTDOWN decision from monitoring. Task '{}' errored out \
3132                                    during process. The runtime cannot continue.", #mission_mod::TASKS_IDS[#tid]);
3133                            return Err(CuError::new_with_cause("Task errored out during process.", error));
3134                        }
3135                    }
3136                };
3137
3138                let call_sim_callback = if sim_mode {
3139                    quote! {
3140                        let doit = {
3141                            let cumsg_output = &mut msgs.#output_culist_index;
3142                            let state = CuTaskCallbackState::Process((), cumsg_output);
3143                            let ovr = sim_callback(SimStep::#enum_name(state));
3144
3145                            if let SimOverride::Errored(reason) = ovr  {
3146                                let error: CuError = reason.into();
3147                                #monitoring_action
3148                                false
3149                            } else {
3150                                ovr == SimOverride::ExecuteByRuntime
3151                            }
3152                        };
3153                    }
3154                } else {
3155                    quote! { let doit = true; }
3156                };
3157
3158                let logging_tokens = if !task_specs.logging_enabled[tid] {
3159                    let output_culist_index = int2sliceindex(*output_index);
3160                    quote! {
3161                        let mut cumsg_output = &mut culist.msgs.0.#output_culist_index;
3162                        cumsg_output.clear_payload();
3163                    }
3164                } else {
3165                    quote!()
3166                };
3167
3168                (
3169                    quote! {
3170                        {
3171                            #comment_tokens
3172                            kf_manager.freeze_task(clid, &#task_instance)?;
3173                            #call_sim_callback
3174                            let cumsg_output = &mut msgs.#output_culist_index;
3175                            cumsg_output.metadata.process_time.start = clock.now().into();
3176                            let maybe_error = if doit { #task_instance.process(clock, cumsg_output) } else { Ok(()) };
3177                            cumsg_output.metadata.process_time.end = clock.now().into();
3178                            if let Err(error) = maybe_error {
3179                                #monitoring_action
3180                            }
3181                        }
3182                    },
3183                    logging_tokens,
3184                )
3185            } else {
3186                panic!("Source task should have an output message index.");
3187            }
3188        }
3189        CuTaskType::Sink => {
3190            if let Some((output_index, _)) = &step.output_msg_index_type {
3191                let output_culist_index = int2sliceindex(*output_index);
3192                let indices = step
3193                    .input_msg_indices_types
3194                    .iter()
3195                    .map(|(index, _)| int2sliceindex(*index));
3196
3197                let monitoring_action = quote! {
3198                    debug!("Task {}: Error during process: {}", #mission_mod::TASKS_IDS[#tid], &error);
3199                    let decision = monitor.process_error(#tid, CuTaskState::Process, &error);
3200                    match decision {
3201                        Decision::Abort => {
3202                            debug!("Process: ABORT decision from monitoring. Task '{}' errored out \
3203                                    during process. Skipping the processing of CL {}.", #mission_mod::TASKS_IDS[#tid], clid);
3204                            monitor.process_copperlist(&#mission_mod::collect_metadata(&culist))?;
3205                            cl_manager.end_of_processing(clid)?;
3206                            return Ok(());
3207                        }
3208                        Decision::Ignore => {
3209                            debug!("Process: IGNORE decision from monitoring. Task '{}' errored out \
3210                                    during process. The runtime will continue with a forced empty message.", #mission_mod::TASKS_IDS[#tid]);
3211                            let cumsg_output = &mut msgs.#output_culist_index;
3212                            cumsg_output.clear_payload();
3213                        }
3214                        Decision::Shutdown => {
3215                            debug!("Process: SHUTDOWN decision from monitoring. Task '{}' errored out \
3216                                    during process. The runtime cannot continue.", #mission_mod::TASKS_IDS[#tid]);
3217                            return Err(CuError::new_with_cause("Task errored out during process.", error));
3218                        }
3219                    }
3220                };
3221
3222                let inputs_type = if indices.len() == 1 {
3223                    quote! { #(msgs.#indices)* }
3224                } else {
3225                    quote! { (#(&msgs.#indices),*) }
3226                };
3227
3228                let call_sim_callback = if sim_mode {
3229                    quote! {
3230                        let doit = {
3231                            let cumsg_input = &#inputs_type;
3232                            let cumsg_output = &mut msgs.#output_culist_index;
3233                            let state = CuTaskCallbackState::Process(cumsg_input, cumsg_output);
3234                            let ovr = sim_callback(SimStep::#enum_name(state));
3235
3236                            if let SimOverride::Errored(reason) = ovr  {
3237                                let error: CuError = reason.into();
3238                                #monitoring_action
3239                                false
3240                            } else {
3241                                ovr == SimOverride::ExecuteByRuntime
3242                            }
3243                        };
3244                    }
3245                } else {
3246                    quote! { let doit = true; }
3247                };
3248
3249                (
3250                    quote! {
3251                        {
3252                            #comment_tokens
3253                            kf_manager.freeze_task(clid, &#task_instance)?;
3254                            #call_sim_callback
3255                            let cumsg_input = &#inputs_type;
3256                            let cumsg_output = &mut msgs.#output_culist_index;
3257                            cumsg_output.metadata.process_time.start = clock.now().into();
3258                            let maybe_error = if doit { #task_instance.process(clock, cumsg_input) } else { Ok(()) };
3259                            cumsg_output.metadata.process_time.end = clock.now().into();
3260                            if let Err(error) = maybe_error {
3261                                #monitoring_action
3262                            }
3263                        }
3264                    },
3265                    quote! {},
3266                )
3267            } else {
3268                panic!("Sink tasks should have a virtual output message index.");
3269            }
3270        }
3271        CuTaskType::Regular => {
3272            if let Some((output_index, _)) = &step.output_msg_index_type {
3273                let output_culist_index = int2sliceindex(*output_index);
3274                let indices = step
3275                    .input_msg_indices_types
3276                    .iter()
3277                    .map(|(index, _)| int2sliceindex(*index));
3278
3279                let monitoring_action = quote! {
3280                    debug!("Task {}: Error during process: {}", #mission_mod::TASKS_IDS[#tid], &error);
3281                    let decision = monitor.process_error(#tid, CuTaskState::Process, &error);
3282                    match decision {
3283                        Decision::Abort => {
3284                            debug!("Process: ABORT decision from monitoring. Task '{}' errored out \
3285                                    during process. Skipping the processing of CL {}.", #mission_mod::TASKS_IDS[#tid], clid);
3286                            monitor.process_copperlist(&#mission_mod::collect_metadata(&culist))?;
3287                            cl_manager.end_of_processing(clid)?;
3288                            return Ok(());
3289                        }
3290                        Decision::Ignore => {
3291                            debug!("Process: IGNORE decision from monitoring. Task '{}' errored out \
3292                                    during process. The runtime will continue with a forced empty message.", #mission_mod::TASKS_IDS[#tid]);
3293                            let cumsg_output = &mut msgs.#output_culist_index;
3294                            cumsg_output.clear_payload();
3295                        }
3296                        Decision::Shutdown => {
3297                            debug!("Process: SHUTDOWN decision from monitoring. Task '{}' errored out \
3298                                    during process. The runtime cannot continue.", #mission_mod::TASKS_IDS[#tid]);
3299                            return Err(CuError::new_with_cause("Task errored out during process.", error));
3300                        }
3301                    }
3302                };
3303
3304                let inputs_type = if indices.len() == 1 {
3305                    quote! { #(msgs.#indices)* }
3306                } else {
3307                    quote! { (#(&msgs.#indices),*) }
3308                };
3309
3310                let call_sim_callback = if sim_mode {
3311                    quote! {
3312                        let doit = {
3313                            let cumsg_input = &#inputs_type;
3314                            let cumsg_output = &mut msgs.#output_culist_index;
3315                            let state = CuTaskCallbackState::Process(cumsg_input, cumsg_output);
3316                            let ovr = sim_callback(SimStep::#enum_name(state));
3317
3318                            if let SimOverride::Errored(reason) = ovr  {
3319                                let error: CuError = reason.into();
3320                                #monitoring_action
3321                                false
3322                            }
3323                            else {
3324                                ovr == SimOverride::ExecuteByRuntime
3325                            }
3326                        };
3327                    }
3328                } else {
3329                    quote! { let doit = true; }
3330                };
3331
3332                let logging_tokens = if !task_specs.logging_enabled[tid] {
3333                    let output_culist_index = int2sliceindex(*output_index);
3334                    quote! {
3335                        let mut cumsg_output = &mut culist.msgs.0.#output_culist_index;
3336                        cumsg_output.clear_payload();
3337                    }
3338                } else {
3339                    quote!()
3340                };
3341
3342                (
3343                    quote! {
3344                        {
3345                            #comment_tokens
3346                            kf_manager.freeze_task(clid, &#task_instance)?;
3347                            #call_sim_callback
3348                            let cumsg_input = &#inputs_type;
3349                            let cumsg_output = &mut msgs.#output_culist_index;
3350                            cumsg_output.metadata.process_time.start = clock.now().into();
3351                            let maybe_error = if doit { #task_instance.process(clock, cumsg_input, cumsg_output) } else { Ok(()) };
3352                            cumsg_output.metadata.process_time.end = clock.now().into();
3353                            if let Err(error) = maybe_error {
3354                                #monitoring_action
3355                            }
3356                        }
3357                    },
3358                    logging_tokens,
3359                )
3360            } else {
3361                panic!("Regular task should have an output message index.");
3362            }
3363        }
3364    }
3365}
3366
3367fn generate_bridge_rx_execution_tokens(
3368    bridge_spec: &BridgeSpec,
3369    channel_index: usize,
3370    mission_mod: &Ident,
3371) -> (proc_macro2::TokenStream, proc_macro2::TokenStream) {
3372    let bridge_tuple_index = int2sliceindex(bridge_spec.tuple_index as u32);
3373    let channel = &bridge_spec.rx_channels[channel_index];
3374    let culist_index = channel
3375        .culist_index
3376        .unwrap_or_else(|| panic!("Bridge Rx channel missing output index"));
3377    let culist_index_ts = int2sliceindex(culist_index as u32);
3378    let monitor_index = syn::Index::from(
3379        channel
3380            .monitor_index
3381            .expect("Bridge Rx channel missing monitor index"),
3382    );
3383    let bridge_type = &bridge_spec.type_path;
3384    let const_ident = &channel.const_ident;
3385    (
3386        quote! {
3387            {
3388                let bridge = &mut bridges.#bridge_tuple_index;
3389                let cumsg_output = &mut msgs.#culist_index_ts;
3390                cumsg_output.metadata.process_time.start = clock.now().into();
3391                let maybe_error = bridge.receive(
3392                    clock,
3393                    &<#bridge_type as cu29::cubridge::CuBridge>::Rx::#const_ident,
3394                    cumsg_output,
3395                );
3396                cumsg_output.metadata.process_time.end = clock.now().into();
3397                if let Err(error) = maybe_error {
3398                    let decision = monitor.process_error(#monitor_index, CuTaskState::Process, &error);
3399                    match decision {
3400                        Decision::Abort => {
3401                            debug!("Process: ABORT decision from monitoring. Task '{}' errored out during process. Skipping the processing of CL {}.", #mission_mod::TASKS_IDS[#monitor_index], clid);
3402                            monitor.process_copperlist(&#mission_mod::collect_metadata(&culist))?;
3403                            cl_manager.end_of_processing(clid)?;
3404                            return Ok(());
3405                        }
3406                        Decision::Ignore => {
3407                            debug!("Process: IGNORE decision from monitoring. Task '{}' errored out during process. The runtime will continue with a forced empty message.", #mission_mod::TASKS_IDS[#monitor_index]);
3408                            let cumsg_output = &mut msgs.#culist_index_ts;
3409                            cumsg_output.clear_payload();
3410                        }
3411                        Decision::Shutdown => {
3412                            debug!("Process: SHUTDOWN decision from monitoring. Task '{}' errored out during process. The runtime cannot continue.", #mission_mod::TASKS_IDS[#monitor_index]);
3413                            return Err(CuError::new_with_cause("Task errored out during process.", error));
3414                        }
3415                    }
3416                }
3417            }
3418        },
3419        quote! {},
3420    )
3421}
3422
3423fn generate_bridge_tx_execution_tokens(
3424    step: &CuExecutionStep,
3425    bridge_spec: &BridgeSpec,
3426    channel_index: usize,
3427    mission_mod: &Ident,
3428) -> (proc_macro2::TokenStream, proc_macro2::TokenStream) {
3429    let channel = &bridge_spec.tx_channels[channel_index];
3430    let monitor_index = syn::Index::from(
3431        channel
3432            .monitor_index
3433            .expect("Bridge Tx channel missing monitor index"),
3434    );
3435    let input_index = step
3436        .input_msg_indices_types
3437        .first()
3438        .map(|(idx, _)| int2sliceindex(*idx))
3439        .expect("Bridge Tx channel should have exactly one input");
3440    let bridge_tuple_index = int2sliceindex(bridge_spec.tuple_index as u32);
3441    let bridge_type = &bridge_spec.type_path;
3442    let const_ident = &channel.const_ident;
3443    (
3444        quote! {
3445            {
3446                let bridge = &mut bridges.#bridge_tuple_index;
3447                let cumsg_input = &mut msgs.#input_index;
3448                // Stamp timing so monitors see consistent ranges for bridge Tx as well.
3449                cumsg_input.metadata.process_time.start = clock.now().into();
3450                if let Err(error) = bridge.send(
3451                    clock,
3452                    &<#bridge_type as cu29::cubridge::CuBridge>::Tx::#const_ident,
3453                    &*cumsg_input,
3454                ) {
3455                    let decision = monitor.process_error(#monitor_index, CuTaskState::Process, &error);
3456                    match decision {
3457                        Decision::Abort => {
3458                            debug!("Process: ABORT decision from monitoring. Task '{}' errored out during process. Skipping the processing of CL {}.", #mission_mod::TASKS_IDS[#monitor_index], clid);
3459                            monitor.process_copperlist(&#mission_mod::collect_metadata(&culist))?;
3460                            cl_manager.end_of_processing(clid)?;
3461                            return Ok(());
3462                        }
3463                        Decision::Ignore => {
3464                            debug!("Process: IGNORE decision from monitoring. Task '{}' errored out during process. The runtime will continue with a forced empty message.", #mission_mod::TASKS_IDS[#monitor_index]);
3465                        }
3466                        Decision::Shutdown => {
3467                            debug!("Process: SHUTDOWN decision from monitoring. Task '{}' errored out during process. The runtime cannot continue.", #mission_mod::TASKS_IDS[#monitor_index]);
3468                            return Err(CuError::new_with_cause("Task errored out during process.", error));
3469                        }
3470                    }
3471                }
3472                cumsg_input.metadata.process_time.end = clock.now().into();
3473            }
3474        },
3475        quote! {},
3476    )
3477}
3478
3479#[cfg(test)]
3480mod tests {
3481    // See tests/compile_file directory for more information
3482    #[test]
3483    fn test_compile_fail() {
3484        let t = trybuild::TestCases::new();
3485        t.compile_fail("tests/compile_fail/*/*.rs");
3486    }
3487
3488    #[test]
3489    fn bridge_resources_are_collected() {
3490        use super::*;
3491        use cu29::config::{CuGraph, Flavor, Node};
3492        use std::collections::HashMap;
3493        use syn::parse_str;
3494
3495        let mut graph = CuGraph::default();
3496        let mut node = Node::new_with_flavor("radio", "bridge::Dummy", Flavor::Bridge);
3497        let mut res = HashMap::new();
3498        res.insert("serial".to_string(), "fc.serial0".to_string());
3499        node.set_resources(Some(res));
3500        graph.add_node(node).expect("bridge node");
3501
3502        let task_specs = CuTaskSpecSet::from_graph(&graph);
3503        let bridge_spec = BridgeSpec {
3504            id: "radio".to_string(),
3505            type_path: parse_str("bridge::Dummy").unwrap(),
3506            config_index: 0,
3507            tuple_index: 0,
3508            monitor_index: None,
3509            rx_channels: Vec::new(),
3510            tx_channels: Vec::new(),
3511        };
3512
3513        let specs =
3514            collect_resource_specs(&graph, &task_specs, &[bridge_spec]).expect("collect specs");
3515        assert_eq!(specs.len(), 1);
3516        assert!(matches!(specs[0].owner, ResourceOwner::Bridge(0)));
3517        assert_eq!(specs[0].binding_name, "serial");
3518        assert_eq!(specs[0].path, "fc.serial0");
3519    }
3520}
3521#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
3522enum BridgeChannelDirection {
3523    Rx,
3524    Tx,
3525}
3526
3527#[derive(Clone, Debug, PartialEq, Eq, Hash)]
3528struct BridgeChannelKey {
3529    bridge_id: String,
3530    channel_id: String,
3531    direction: BridgeChannelDirection,
3532}
3533
3534#[derive(Clone)]
3535struct BridgeChannelSpec {
3536    id: String,
3537    const_ident: Ident,
3538    #[allow(dead_code)]
3539    msg_type: Type,
3540    config_index: usize,
3541    plan_node_id: Option<NodeId>,
3542    culist_index: Option<usize>,
3543    monitor_index: Option<usize>,
3544}
3545
3546#[derive(Clone)]
3547struct BridgeSpec {
3548    id: String,
3549    type_path: Type,
3550    config_index: usize,
3551    tuple_index: usize,
3552    monitor_index: Option<usize>,
3553    rx_channels: Vec<BridgeChannelSpec>,
3554    tx_channels: Vec<BridgeChannelSpec>,
3555}
3556
3557#[derive(Clone)]
3558struct ExecutionEntity {
3559    kind: ExecutionEntityKind,
3560}
3561
3562#[derive(Clone)]
3563enum ExecutionEntityKind {
3564    Task {
3565        task_index: usize,
3566    },
3567    BridgeRx {
3568        bridge_index: usize,
3569        channel_index: usize,
3570    },
3571    BridgeTx {
3572        bridge_index: usize,
3573        channel_index: usize,
3574    },
3575}