cu29_derive/
lib.rs

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