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::from("Failed to thaw").add_cause(&e.to_string()))?
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 cnx in graph.edges() {
2508        if let Some(channel) = &cnx.src_channel {
2509            let key = BridgeChannelKey {
2510                bridge_id: cnx.src.clone(),
2511                channel_id: channel.clone(),
2512                direction: BridgeChannelDirection::Rx,
2513            };
2514            usage
2515                .entry(key)
2516                .and_modify(|msg| {
2517                    if msg != &cnx.msg {
2518                        panic!(
2519                            "Bridge '{}' channel '{}' is used with incompatible message types: {} vs {}",
2520                            cnx.src, channel, msg, cnx.msg
2521                        );
2522                    }
2523                })
2524                .or_insert(cnx.msg.clone());
2525        }
2526        if let Some(channel) = &cnx.dst_channel {
2527            let key = BridgeChannelKey {
2528                bridge_id: cnx.dst.clone(),
2529                channel_id: channel.clone(),
2530                direction: BridgeChannelDirection::Tx,
2531            };
2532            usage
2533                .entry(key)
2534                .and_modify(|msg| {
2535                    if msg != &cnx.msg {
2536                        panic!(
2537                            "Bridge '{}' channel '{}' is used with incompatible message types: {} vs {}",
2538                            cnx.dst, channel, msg, cnx.msg
2539                        );
2540                    }
2541                })
2542                .or_insert(cnx.msg.clone());
2543        }
2544    }
2545    usage
2546}
2547
2548fn build_bridge_specs(
2549    config: &CuConfig,
2550    graph: &CuGraph,
2551    channel_usage: &HashMap<BridgeChannelKey, String>,
2552) -> Vec<BridgeSpec> {
2553    let mut specs = Vec::new();
2554    for (bridge_index, bridge_cfg) in config.bridges.iter().enumerate() {
2555        if graph.get_node_id_by_name(bridge_cfg.id.as_str()).is_none() {
2556            continue;
2557        }
2558
2559        let type_path = parse_str::<Type>(bridge_cfg.type_.as_str()).unwrap_or_else(|err| {
2560            panic!(
2561                "Could not parse bridge type '{}' for '{}': {err}",
2562                bridge_cfg.type_, bridge_cfg.id
2563            )
2564        });
2565
2566        let mut rx_channels = Vec::new();
2567        let mut tx_channels = Vec::new();
2568
2569        for (channel_index, channel) in bridge_cfg.channels.iter().enumerate() {
2570            match channel {
2571                BridgeChannelConfigRepresentation::Rx { id, .. } => {
2572                    let key = BridgeChannelKey {
2573                        bridge_id: bridge_cfg.id.clone(),
2574                        channel_id: id.clone(),
2575                        direction: BridgeChannelDirection::Rx,
2576                    };
2577                    if let Some(msg_type) = channel_usage.get(&key) {
2578                        let msg_type = parse_str::<Type>(msg_type).unwrap_or_else(|err| {
2579                            panic!(
2580                                "Could not parse message type '{msg_type}' for bridge '{}' channel '{}': {err}",
2581                                bridge_cfg.id, id
2582                            )
2583                        });
2584                        let const_ident =
2585                            Ident::new(&config_id_to_bridge_const(id.as_str()), Span::call_site());
2586                        rx_channels.push(BridgeChannelSpec {
2587                            id: id.clone(),
2588                            const_ident,
2589                            msg_type,
2590                            config_index: channel_index,
2591                            plan_node_id: None,
2592                            culist_index: None,
2593                            monitor_index: None,
2594                        });
2595                    }
2596                }
2597                BridgeChannelConfigRepresentation::Tx { id, .. } => {
2598                    let key = BridgeChannelKey {
2599                        bridge_id: bridge_cfg.id.clone(),
2600                        channel_id: id.clone(),
2601                        direction: BridgeChannelDirection::Tx,
2602                    };
2603                    if let Some(msg_type) = channel_usage.get(&key) {
2604                        let msg_type = parse_str::<Type>(msg_type).unwrap_or_else(|err| {
2605                            panic!(
2606                                "Could not parse message type '{msg_type}' for bridge '{}' channel '{}': {err}",
2607                                bridge_cfg.id, id
2608                            )
2609                        });
2610                        let const_ident =
2611                            Ident::new(&config_id_to_bridge_const(id.as_str()), Span::call_site());
2612                        tx_channels.push(BridgeChannelSpec {
2613                            id: id.clone(),
2614                            const_ident,
2615                            msg_type,
2616                            config_index: channel_index,
2617                            plan_node_id: None,
2618                            culist_index: None,
2619                            monitor_index: None,
2620                        });
2621                    }
2622                }
2623            }
2624        }
2625
2626        if rx_channels.is_empty() && tx_channels.is_empty() {
2627            continue;
2628        }
2629
2630        specs.push(BridgeSpec {
2631            id: bridge_cfg.id.clone(),
2632            type_path,
2633            config_index: bridge_index,
2634            tuple_index: 0,
2635            monitor_index: None,
2636            rx_channels,
2637            tx_channels,
2638        });
2639    }
2640
2641    for (tuple_index, spec) in specs.iter_mut().enumerate() {
2642        spec.tuple_index = tuple_index;
2643    }
2644
2645    specs
2646}
2647
2648fn collect_task_member_names(graph: &CuGraph) -> Vec<(NodeId, String)> {
2649    graph
2650        .get_all_nodes()
2651        .iter()
2652        .filter(|(_, node)| node.get_flavor() == Flavor::Task)
2653        .map(|(node_id, node)| (*node_id, config_id_to_struct_member(node.get_id().as_str())))
2654        .collect()
2655}
2656
2657#[derive(Clone, Copy)]
2658enum ResourceOwner {
2659    Task(usize),
2660    Bridge(usize),
2661}
2662
2663#[derive(Clone)]
2664struct ResourceKeySpec {
2665    bundle_index: usize,
2666    provider_path: syn::Path,
2667    resource_name: String,
2668    binding_name: String,
2669    owner: ResourceOwner,
2670}
2671
2672fn parse_resource_path(path: &str) -> CuResult<(String, String)> {
2673    let (bundle_id, name) = path.split_once('.').ok_or_else(|| {
2674        CuError::from(format!(
2675            "Resource '{path}' is missing a bundle prefix (expected bundle.resource)"
2676        ))
2677    })?;
2678
2679    if bundle_id.is_empty() || name.is_empty() {
2680        return Err(CuError::from(format!(
2681            "Resource '{path}' must use the 'bundle.resource' format"
2682        )));
2683    }
2684
2685    Ok((bundle_id.to_string(), name.to_string()))
2686}
2687
2688fn collect_resource_specs(
2689    graph: &CuGraph,
2690    task_specs: &CuTaskSpecSet,
2691    bridge_specs: &[BridgeSpec],
2692    bundle_specs: &[BundleSpec],
2693) -> CuResult<Vec<ResourceKeySpec>> {
2694    let mut bridge_lookup: BTreeMap<String, usize> = BTreeMap::new();
2695    for (idx, spec) in bridge_specs.iter().enumerate() {
2696        bridge_lookup.insert(spec.id.clone(), idx);
2697    }
2698
2699    let mut bundle_lookup: HashMap<String, (usize, syn::Path)> = HashMap::new();
2700    for (index, bundle) in bundle_specs.iter().enumerate() {
2701        bundle_lookup.insert(bundle.id.clone(), (index, bundle.provider_path.clone()));
2702    }
2703
2704    let mut specs = Vec::new();
2705
2706    for (node_id, node) in graph.get_all_nodes() {
2707        let resources = node.get_resources();
2708        if let Some(resources) = resources {
2709            let task_index = task_specs.node_id_to_task_index[node_id as usize];
2710            let owner = if let Some(task_index) = task_index {
2711                ResourceOwner::Task(task_index)
2712            } else if node.get_flavor() == Flavor::Bridge {
2713                let bridge_index = bridge_lookup.get(&node.get_id()).ok_or_else(|| {
2714                    CuError::from(format!(
2715                        "Resource mapping attached to unknown bridge node '{}'",
2716                        node.get_id()
2717                    ))
2718                })?;
2719                ResourceOwner::Bridge(*bridge_index)
2720            } else {
2721                return Err(CuError::from(format!(
2722                    "Resource mapping attached to non-task node '{}'",
2723                    node.get_id()
2724                )));
2725            };
2726
2727            for (binding_name, path) in resources {
2728                let (bundle_id, resource_name) = parse_resource_path(path)?;
2729                let (bundle_index, provider_path) =
2730                    bundle_lookup.get(&bundle_id).ok_or_else(|| {
2731                        CuError::from(format!(
2732                            "Resource '{}' references unknown bundle '{}'",
2733                            path, bundle_id
2734                        ))
2735                    })?;
2736                specs.push(ResourceKeySpec {
2737                    bundle_index: *bundle_index,
2738                    provider_path: provider_path.clone(),
2739                    resource_name,
2740                    binding_name: binding_name.clone(),
2741                    owner,
2742                });
2743            }
2744        }
2745    }
2746
2747    Ok(specs)
2748}
2749
2750fn build_bundle_list<'a>(config: &'a CuConfig, mission: &str) -> Vec<&'a ResourceBundleConfig> {
2751    config
2752        .resources
2753        .iter()
2754        .filter(|bundle| {
2755            bundle
2756                .missions
2757                .as_ref()
2758                .is_none_or(|missions| missions.iter().any(|m| m == mission))
2759        })
2760        .collect()
2761}
2762
2763struct BundleSpec {
2764    id: String,
2765    provider_path: syn::Path,
2766}
2767
2768fn build_bundle_specs(config: &CuConfig, mission: &str) -> CuResult<Vec<BundleSpec>> {
2769    build_bundle_list(config, mission)
2770        .into_iter()
2771        .map(|bundle| {
2772            let provider_path: syn::Path =
2773                syn::parse_str(bundle.provider.as_str()).map_err(|err| {
2774                    CuError::from(format!(
2775                        "Failed to parse provider path '{}' for bundle '{}': {err}",
2776                        bundle.provider, bundle.id
2777                    ))
2778                })?;
2779            Ok(BundleSpec {
2780                id: bundle.id.clone(),
2781                provider_path,
2782            })
2783        })
2784        .collect()
2785}
2786
2787fn build_resources_module(
2788    bundle_specs: &[BundleSpec],
2789) -> CuResult<(proc_macro2::TokenStream, proc_macro2::TokenStream)> {
2790    let bundle_consts = bundle_specs.iter().enumerate().map(|(index, bundle)| {
2791        let const_ident = Ident::new(
2792            &config_id_to_bridge_const(bundle.id.as_str()),
2793            Span::call_site(),
2794        );
2795        quote! { pub const #const_ident: BundleIndex = BundleIndex::new(#index); }
2796    });
2797
2798    let resources_module = quote! {
2799        pub mod resources {
2800            #![allow(dead_code)]
2801            use cu29::resource::BundleIndex;
2802
2803            pub mod bundles {
2804                use super::BundleIndex;
2805                #(#bundle_consts)*
2806            }
2807        }
2808    };
2809
2810    let bundle_counts = bundle_specs.iter().map(|bundle| {
2811        let provider_path = &bundle.provider_path;
2812        quote! { <#provider_path as cu29::resource::ResourceBundleDecl>::Id::COUNT }
2813    });
2814
2815    let bundle_inits = bundle_specs
2816        .iter()
2817        .enumerate()
2818        .map(|(index, bundle)| {
2819            let bundle_id = LitStr::new(bundle.id.as_str(), Span::call_site());
2820            let provider_path = &bundle.provider_path;
2821            quote! {
2822                let bundle_cfg = config
2823                    .resources
2824                    .iter()
2825                    .find(|b| b.id == #bundle_id)
2826                    .unwrap_or_else(|| panic!("Resource bundle '{}' missing from configuration", #bundle_id));
2827                let bundle_ctx = cu29::resource::BundleContext::<#provider_path>::new(
2828                    cu29::resource::BundleIndex::new(#index),
2829                    #bundle_id,
2830                );
2831                <#provider_path as cu29::resource::ResourceBundle>::build(
2832                    bundle_ctx,
2833                    bundle_cfg.config.as_ref(),
2834                    &mut manager,
2835                )?;
2836            }
2837        })
2838        .collect::<Vec<_>>();
2839
2840    let resources_instanciator = quote! {
2841        pub fn resources_instanciator(config: &CuConfig) -> CuResult<cu29::resource::ResourceManager> {
2842            let bundle_counts: &[usize] = &[ #(#bundle_counts),* ];
2843            let mut manager = cu29::resource::ResourceManager::new(bundle_counts);
2844            #(#bundle_inits)*
2845            Ok(manager)
2846        }
2847    };
2848
2849    Ok((resources_module, resources_instanciator))
2850}
2851
2852struct ResourceMappingTokens {
2853    defs: proc_macro2::TokenStream,
2854    refs: Vec<proc_macro2::TokenStream>,
2855}
2856
2857fn build_task_resource_mappings(
2858    resource_specs: &[ResourceKeySpec],
2859    task_specs: &CuTaskSpecSet,
2860) -> CuResult<ResourceMappingTokens> {
2861    let mut per_task: Vec<Vec<&ResourceKeySpec>> = vec![Vec::new(); task_specs.ids.len()];
2862
2863    for spec in resource_specs {
2864        let ResourceOwner::Task(task_index) = spec.owner else {
2865            continue;
2866        };
2867        per_task
2868            .get_mut(task_index)
2869            .ok_or_else(|| {
2870                CuError::from(format!(
2871                    "Resource '{}' mapped to invalid task index {}",
2872                    spec.binding_name, task_index
2873                ))
2874            })?
2875            .push(spec);
2876    }
2877
2878    let mut mapping_defs = Vec::new();
2879    let mut mapping_refs = Vec::new();
2880
2881    for (idx, entries) in per_task.iter().enumerate() {
2882        if entries.is_empty() {
2883            mapping_refs.push(quote! { None });
2884            continue;
2885        }
2886
2887        let binding_task_type = if task_specs.background_flags[idx] {
2888            &task_specs.sim_task_types[idx]
2889        } else {
2890            &task_specs.task_types[idx]
2891        };
2892
2893        let binding_trait = match task_specs.cutypes[idx] {
2894            CuTaskType::Source => quote! { CuSrcTask },
2895            CuTaskType::Regular => quote! { CuTask },
2896            CuTaskType::Sink => quote! { CuSinkTask },
2897        };
2898
2899        let entries_ident = format_ident!("TASK{}_RES_ENTRIES", idx);
2900        let map_ident = format_ident!("TASK{}_RES_MAPPING", idx);
2901        let binding_type = quote! {
2902            <<#binding_task_type as #binding_trait>::Resources<'_> as ResourceBindings>::Binding
2903        };
2904        let entry_tokens = entries.iter().map(|spec| {
2905            let binding_ident =
2906                Ident::new(&config_id_to_enum(spec.binding_name.as_str()), Span::call_site());
2907            let resource_ident =
2908                Ident::new(&config_id_to_enum(spec.resource_name.as_str()), Span::call_site());
2909            let bundle_index = spec.bundle_index;
2910            let provider_path = &spec.provider_path;
2911            quote! {
2912                (#binding_type::#binding_ident, cu29::resource::ResourceKey::new(
2913                    cu29::resource::BundleIndex::new(#bundle_index),
2914                    <#provider_path as cu29::resource::ResourceBundleDecl>::Id::#resource_ident as usize,
2915                ))
2916            }
2917        });
2918
2919        mapping_defs.push(quote! {
2920            const #entries_ident: &[(#binding_type, cu29::resource::ResourceKey)] = &[ #(#entry_tokens),* ];
2921            const #map_ident: cu29::resource::ResourceBindingMap<#binding_type> =
2922                cu29::resource::ResourceBindingMap::new(#entries_ident);
2923        });
2924        mapping_refs.push(quote! { Some(&#map_ident) });
2925    }
2926
2927    Ok(ResourceMappingTokens {
2928        defs: quote! { #(#mapping_defs)* },
2929        refs: mapping_refs,
2930    })
2931}
2932
2933fn build_bridge_resource_mappings(
2934    resource_specs: &[ResourceKeySpec],
2935    bridge_specs: &[BridgeSpec],
2936) -> ResourceMappingTokens {
2937    let mut per_bridge: Vec<Vec<&ResourceKeySpec>> = vec![Vec::new(); bridge_specs.len()];
2938
2939    for spec in resource_specs {
2940        let ResourceOwner::Bridge(bridge_index) = spec.owner else {
2941            continue;
2942        };
2943        per_bridge[bridge_index].push(spec);
2944    }
2945
2946    let mut mapping_defs = Vec::new();
2947    let mut mapping_refs = Vec::new();
2948
2949    for (idx, entries) in per_bridge.iter().enumerate() {
2950        if entries.is_empty() {
2951            mapping_refs.push(quote! { None });
2952            continue;
2953        }
2954
2955        let bridge_type = &bridge_specs[idx].type_path;
2956        let binding_type = quote! {
2957            <<#bridge_type as cu29::cubridge::CuBridge>::Resources<'_> as ResourceBindings>::Binding
2958        };
2959        let entries_ident = format_ident!("BRIDGE{}_RES_ENTRIES", idx);
2960        let map_ident = format_ident!("BRIDGE{}_RES_MAPPING", idx);
2961        let entry_tokens = entries.iter().map(|spec| {
2962            let binding_ident =
2963                Ident::new(&config_id_to_enum(spec.binding_name.as_str()), Span::call_site());
2964            let resource_ident =
2965                Ident::new(&config_id_to_enum(spec.resource_name.as_str()), Span::call_site());
2966            let bundle_index = spec.bundle_index;
2967            let provider_path = &spec.provider_path;
2968            quote! {
2969                (#binding_type::#binding_ident, cu29::resource::ResourceKey::new(
2970                    cu29::resource::BundleIndex::new(#bundle_index),
2971                    <#provider_path as cu29::resource::ResourceBundleDecl>::Id::#resource_ident as usize,
2972                ))
2973            }
2974        });
2975
2976        mapping_defs.push(quote! {
2977            const #entries_ident: &[(#binding_type, cu29::resource::ResourceKey)] = &[ #(#entry_tokens),* ];
2978            const #map_ident: cu29::resource::ResourceBindingMap<#binding_type> =
2979                cu29::resource::ResourceBindingMap::new(#entries_ident);
2980        });
2981        mapping_refs.push(quote! { Some(&#map_ident) });
2982    }
2983
2984    ResourceMappingTokens {
2985        defs: quote! { #(#mapping_defs)* },
2986        refs: mapping_refs,
2987    }
2988}
2989
2990fn build_execution_plan(
2991    graph: &CuGraph,
2992    task_specs: &CuTaskSpecSet,
2993    bridge_specs: &mut [BridgeSpec],
2994) -> CuResult<(
2995    CuExecutionLoop,
2996    Vec<ExecutionEntity>,
2997    HashMap<NodeId, NodeId>,
2998)> {
2999    let mut plan_graph = CuGraph::default();
3000    let mut exec_entities = Vec::new();
3001    let mut original_to_plan = HashMap::new();
3002    let mut plan_to_original = HashMap::new();
3003    let mut name_to_original = HashMap::new();
3004    let mut channel_nodes = HashMap::new();
3005
3006    for (node_id, node) in graph.get_all_nodes() {
3007        name_to_original.insert(node.get_id(), node_id);
3008        if node.get_flavor() != Flavor::Task {
3009            continue;
3010        }
3011        let plan_node_id = plan_graph.add_node(node.clone())?;
3012        let task_index = task_specs.node_id_to_task_index[node_id as usize]
3013            .expect("Task missing from specifications");
3014        plan_to_original.insert(plan_node_id, node_id);
3015        original_to_plan.insert(node_id, plan_node_id);
3016        if plan_node_id as usize != exec_entities.len() {
3017            panic!("Unexpected node ordering while mirroring tasks in plan graph");
3018        }
3019        exec_entities.push(ExecutionEntity {
3020            kind: ExecutionEntityKind::Task { task_index },
3021        });
3022    }
3023
3024    for (bridge_index, spec) in bridge_specs.iter_mut().enumerate() {
3025        for (channel_index, channel_spec) in spec.rx_channels.iter_mut().enumerate() {
3026            let mut node = Node::new(
3027                format!("{}::rx::{}", spec.id, channel_spec.id).as_str(),
3028                "__CuBridgeRxChannel",
3029            );
3030            node.set_flavor(Flavor::Bridge);
3031            let plan_node_id = plan_graph.add_node(node)?;
3032            if plan_node_id as usize != exec_entities.len() {
3033                panic!("Unexpected node ordering while inserting bridge rx channel");
3034            }
3035            channel_spec.plan_node_id = Some(plan_node_id);
3036            exec_entities.push(ExecutionEntity {
3037                kind: ExecutionEntityKind::BridgeRx {
3038                    bridge_index,
3039                    channel_index,
3040                },
3041            });
3042            channel_nodes.insert(
3043                BridgeChannelKey {
3044                    bridge_id: spec.id.clone(),
3045                    channel_id: channel_spec.id.clone(),
3046                    direction: BridgeChannelDirection::Rx,
3047                },
3048                plan_node_id,
3049            );
3050        }
3051
3052        for (channel_index, channel_spec) in spec.tx_channels.iter_mut().enumerate() {
3053            let mut node = Node::new(
3054                format!("{}::tx::{}", spec.id, channel_spec.id).as_str(),
3055                "__CuBridgeTxChannel",
3056            );
3057            node.set_flavor(Flavor::Bridge);
3058            let plan_node_id = plan_graph.add_node(node)?;
3059            if plan_node_id as usize != exec_entities.len() {
3060                panic!("Unexpected node ordering while inserting bridge tx channel");
3061            }
3062            channel_spec.plan_node_id = Some(plan_node_id);
3063            exec_entities.push(ExecutionEntity {
3064                kind: ExecutionEntityKind::BridgeTx {
3065                    bridge_index,
3066                    channel_index,
3067                },
3068            });
3069            channel_nodes.insert(
3070                BridgeChannelKey {
3071                    bridge_id: spec.id.clone(),
3072                    channel_id: channel_spec.id.clone(),
3073                    direction: BridgeChannelDirection::Tx,
3074                },
3075                plan_node_id,
3076            );
3077        }
3078    }
3079
3080    for cnx in graph.edges() {
3081        let src_plan = if let Some(channel) = &cnx.src_channel {
3082            let key = BridgeChannelKey {
3083                bridge_id: cnx.src.clone(),
3084                channel_id: channel.clone(),
3085                direction: BridgeChannelDirection::Rx,
3086            };
3087            *channel_nodes
3088                .get(&key)
3089                .unwrap_or_else(|| panic!("Bridge source {:?} missing from plan graph", key))
3090        } else {
3091            let node_id = name_to_original
3092                .get(&cnx.src)
3093                .copied()
3094                .unwrap_or_else(|| panic!("Unknown source node '{}'", cnx.src));
3095            *original_to_plan
3096                .get(&node_id)
3097                .unwrap_or_else(|| panic!("Source node '{}' missing from plan", cnx.src))
3098        };
3099
3100        let dst_plan = if let Some(channel) = &cnx.dst_channel {
3101            let key = BridgeChannelKey {
3102                bridge_id: cnx.dst.clone(),
3103                channel_id: channel.clone(),
3104                direction: BridgeChannelDirection::Tx,
3105            };
3106            *channel_nodes
3107                .get(&key)
3108                .unwrap_or_else(|| panic!("Bridge destination {:?} missing from plan graph", key))
3109        } else {
3110            let node_id = name_to_original
3111                .get(&cnx.dst)
3112                .copied()
3113                .unwrap_or_else(|| panic!("Unknown destination node '{}'", cnx.dst));
3114            *original_to_plan
3115                .get(&node_id)
3116                .unwrap_or_else(|| panic!("Destination node '{}' missing from plan", cnx.dst))
3117        };
3118
3119        plan_graph
3120            .connect_ext(
3121                src_plan,
3122                dst_plan,
3123                &cnx.msg,
3124                cnx.missions.clone(),
3125                None,
3126                None,
3127            )
3128            .map_err(|e| CuError::from(e.to_string()))?;
3129    }
3130
3131    let runtime_plan = compute_runtime_plan(&plan_graph)?;
3132    Ok((runtime_plan, exec_entities, plan_to_original))
3133}
3134
3135fn collect_culist_metadata(
3136    runtime_plan: &CuExecutionLoop,
3137    exec_entities: &[ExecutionEntity],
3138    bridge_specs: &mut [BridgeSpec],
3139    plan_to_original: &HashMap<NodeId, NodeId>,
3140) -> (Vec<usize>, HashMap<NodeId, usize>) {
3141    let mut culist_order = Vec::new();
3142    let mut node_output_positions = HashMap::new();
3143
3144    for unit in &runtime_plan.steps {
3145        if let CuExecutionUnit::Step(step) = unit
3146            && let Some((output_idx, _)) = &step.output_msg_index_type
3147        {
3148            culist_order.push(*output_idx as usize);
3149            match &exec_entities[step.node_id as usize].kind {
3150                ExecutionEntityKind::Task { .. } => {
3151                    if let Some(original_node_id) = plan_to_original.get(&step.node_id) {
3152                        node_output_positions.insert(*original_node_id, *output_idx as usize);
3153                    }
3154                }
3155                ExecutionEntityKind::BridgeRx {
3156                    bridge_index,
3157                    channel_index,
3158                } => {
3159                    bridge_specs[*bridge_index].rx_channels[*channel_index].culist_index =
3160                        Some(*output_idx as usize);
3161                }
3162                ExecutionEntityKind::BridgeTx { .. } => {}
3163            }
3164        }
3165    }
3166
3167    (culist_order, node_output_positions)
3168}
3169
3170#[allow(dead_code)]
3171fn build_monitored_ids(task_ids: &[String], bridge_specs: &mut [BridgeSpec]) -> Vec<String> {
3172    let mut names = task_ids.to_vec();
3173    for spec in bridge_specs.iter_mut() {
3174        spec.monitor_index = Some(names.len());
3175        names.push(format!("bridge::{}", spec.id));
3176        for channel in spec.rx_channels.iter_mut() {
3177            channel.monitor_index = Some(names.len());
3178            names.push(format!("bridge::{}::rx::{}", spec.id, channel.id));
3179        }
3180        for channel in spec.tx_channels.iter_mut() {
3181            channel.monitor_index = Some(names.len());
3182            names.push(format!("bridge::{}::tx::{}", spec.id, channel.id));
3183        }
3184    }
3185    names
3186}
3187
3188fn generate_task_execution_tokens(
3189    step: &CuExecutionStep,
3190    task_index: usize,
3191    task_specs: &CuTaskSpecSet,
3192    sim_mode: bool,
3193    mission_mod: &Ident,
3194) -> (proc_macro2::TokenStream, proc_macro2::TokenStream) {
3195    let node_index = int2sliceindex(task_index as u32);
3196    let task_instance = quote! { tasks.#node_index };
3197    let comment_str = format!(
3198        "DEBUG ->> {} ({:?}) Id:{} I:{:?} O:{:?}",
3199        step.node.get_id(),
3200        step.task_type,
3201        step.node_id,
3202        step.input_msg_indices_types,
3203        step.output_msg_index_type
3204    );
3205    let comment_tokens = quote! {{
3206        let _ = stringify!(#comment_str);
3207    }};
3208    let tid = task_index;
3209    let task_enum_name = config_id_to_enum(&task_specs.ids[tid]);
3210    let enum_name = Ident::new(&task_enum_name, Span::call_site());
3211
3212    match step.task_type {
3213        CuTaskType::Source => {
3214            if let Some((output_index, _)) = &step.output_msg_index_type {
3215                let output_culist_index = int2sliceindex(*output_index);
3216
3217                let monitoring_action = quote! {
3218                    debug!("Task {}: Error during process: {}", #mission_mod::TASKS_IDS[#tid], &error);
3219                    let decision = monitor.process_error(#tid, CuTaskState::Process, &error);
3220                    match decision {
3221                        Decision::Abort => {
3222                            debug!("Process: ABORT decision from monitoring. Task '{}' errored out \
3223                                    during process. Skipping the processing of CL {}.", #mission_mod::TASKS_IDS[#tid], clid);
3224                            monitor.process_copperlist(&#mission_mod::collect_metadata(&culist))?;
3225                            cl_manager.end_of_processing(clid)?;
3226                            return Ok(());
3227                        }
3228                        Decision::Ignore => {
3229                            debug!("Process: IGNORE decision from monitoring. Task '{}' errored out \
3230                                    during process. The runtime will continue with a forced empty message.", #mission_mod::TASKS_IDS[#tid]);
3231                            let cumsg_output = &mut msgs.#output_culist_index;
3232                            cumsg_output.clear_payload();
3233                        }
3234                        Decision::Shutdown => {
3235                            debug!("Process: SHUTDOWN decision from monitoring. Task '{}' errored out \
3236                                    during process. The runtime cannot continue.", #mission_mod::TASKS_IDS[#tid]);
3237                            return Err(CuError::new_with_cause("Task errored out during process.", error));
3238                        }
3239                    }
3240                };
3241
3242                let call_sim_callback = if sim_mode {
3243                    quote! {
3244                        let doit = {
3245                            let cumsg_output = &mut msgs.#output_culist_index;
3246                            let state = CuTaskCallbackState::Process((), cumsg_output);
3247                            let ovr = sim_callback(SimStep::#enum_name(state));
3248
3249                            if let SimOverride::Errored(reason) = ovr  {
3250                                let error: CuError = reason.into();
3251                                #monitoring_action
3252                                false
3253                            } else {
3254                                ovr == SimOverride::ExecuteByRuntime
3255                            }
3256                        };
3257                    }
3258                } else {
3259                    quote! { let doit = true; }
3260                };
3261
3262                let logging_tokens = if !task_specs.logging_enabled[tid] {
3263                    let output_culist_index = int2sliceindex(*output_index);
3264                    quote! {
3265                        let mut cumsg_output = &mut culist.msgs.0.#output_culist_index;
3266                        cumsg_output.clear_payload();
3267                    }
3268                } else {
3269                    quote!()
3270                };
3271
3272                (
3273                    quote! {
3274                        {
3275                            #comment_tokens
3276                            kf_manager.freeze_task(clid, &#task_instance)?;
3277                            #call_sim_callback
3278                            let cumsg_output = &mut msgs.#output_culist_index;
3279                            cumsg_output.metadata.process_time.start = clock.now().into();
3280                            let maybe_error = if doit { #task_instance.process(clock, cumsg_output) } else { Ok(()) };
3281                            cumsg_output.metadata.process_time.end = clock.now().into();
3282                            if let Err(error) = maybe_error {
3283                                #monitoring_action
3284                            }
3285                        }
3286                    },
3287                    logging_tokens,
3288                )
3289            } else {
3290                panic!("Source task should have an output message index.");
3291            }
3292        }
3293        CuTaskType::Sink => {
3294            if let Some((output_index, _)) = &step.output_msg_index_type {
3295                let output_culist_index = int2sliceindex(*output_index);
3296                let indices = step
3297                    .input_msg_indices_types
3298                    .iter()
3299                    .map(|(index, _)| int2sliceindex(*index));
3300
3301                let monitoring_action = quote! {
3302                    debug!("Task {}: Error during process: {}", #mission_mod::TASKS_IDS[#tid], &error);
3303                    let decision = monitor.process_error(#tid, CuTaskState::Process, &error);
3304                    match decision {
3305                        Decision::Abort => {
3306                            debug!("Process: ABORT decision from monitoring. Task '{}' errored out \
3307                                    during process. Skipping the processing of CL {}.", #mission_mod::TASKS_IDS[#tid], clid);
3308                            monitor.process_copperlist(&#mission_mod::collect_metadata(&culist))?;
3309                            cl_manager.end_of_processing(clid)?;
3310                            return Ok(());
3311                        }
3312                        Decision::Ignore => {
3313                            debug!("Process: IGNORE decision from monitoring. Task '{}' errored out \
3314                                    during process. The runtime will continue with a forced empty message.", #mission_mod::TASKS_IDS[#tid]);
3315                            let cumsg_output = &mut msgs.#output_culist_index;
3316                            cumsg_output.clear_payload();
3317                        }
3318                        Decision::Shutdown => {
3319                            debug!("Process: SHUTDOWN decision from monitoring. Task '{}' errored out \
3320                                    during process. The runtime cannot continue.", #mission_mod::TASKS_IDS[#tid]);
3321                            return Err(CuError::new_with_cause("Task errored out during process.", error));
3322                        }
3323                    }
3324                };
3325
3326                let inputs_type = if indices.len() == 1 {
3327                    quote! { #(msgs.#indices)* }
3328                } else {
3329                    quote! { (#(&msgs.#indices),*) }
3330                };
3331
3332                let call_sim_callback = if sim_mode {
3333                    quote! {
3334                        let doit = {
3335                            let cumsg_input = &#inputs_type;
3336                            let cumsg_output = &mut msgs.#output_culist_index;
3337                            let state = CuTaskCallbackState::Process(cumsg_input, cumsg_output);
3338                            let ovr = sim_callback(SimStep::#enum_name(state));
3339
3340                            if let SimOverride::Errored(reason) = ovr  {
3341                                let error: CuError = reason.into();
3342                                #monitoring_action
3343                                false
3344                            } else {
3345                                ovr == SimOverride::ExecuteByRuntime
3346                            }
3347                        };
3348                    }
3349                } else {
3350                    quote! { let doit = true; }
3351                };
3352
3353                (
3354                    quote! {
3355                        {
3356                            #comment_tokens
3357                            kf_manager.freeze_task(clid, &#task_instance)?;
3358                            #call_sim_callback
3359                            let cumsg_input = &#inputs_type;
3360                            let cumsg_output = &mut msgs.#output_culist_index;
3361                            cumsg_output.metadata.process_time.start = clock.now().into();
3362                            let maybe_error = if doit { #task_instance.process(clock, cumsg_input) } else { Ok(()) };
3363                            cumsg_output.metadata.process_time.end = clock.now().into();
3364                            if let Err(error) = maybe_error {
3365                                #monitoring_action
3366                            }
3367                        }
3368                    },
3369                    quote! {},
3370                )
3371            } else {
3372                panic!("Sink tasks should have a virtual output message index.");
3373            }
3374        }
3375        CuTaskType::Regular => {
3376            if let Some((output_index, _)) = &step.output_msg_index_type {
3377                let output_culist_index = int2sliceindex(*output_index);
3378                let indices = step
3379                    .input_msg_indices_types
3380                    .iter()
3381                    .map(|(index, _)| int2sliceindex(*index));
3382
3383                let monitoring_action = quote! {
3384                    debug!("Task {}: Error during process: {}", #mission_mod::TASKS_IDS[#tid], &error);
3385                    let decision = monitor.process_error(#tid, CuTaskState::Process, &error);
3386                    match decision {
3387                        Decision::Abort => {
3388                            debug!("Process: ABORT decision from monitoring. Task '{}' errored out \
3389                                    during process. Skipping the processing of CL {}.", #mission_mod::TASKS_IDS[#tid], clid);
3390                            monitor.process_copperlist(&#mission_mod::collect_metadata(&culist))?;
3391                            cl_manager.end_of_processing(clid)?;
3392                            return Ok(());
3393                        }
3394                        Decision::Ignore => {
3395                            debug!("Process: IGNORE decision from monitoring. Task '{}' errored out \
3396                                    during process. The runtime will continue with a forced empty message.", #mission_mod::TASKS_IDS[#tid]);
3397                            let cumsg_output = &mut msgs.#output_culist_index;
3398                            cumsg_output.clear_payload();
3399                        }
3400                        Decision::Shutdown => {
3401                            debug!("Process: SHUTDOWN decision from monitoring. Task '{}' errored out \
3402                                    during process. The runtime cannot continue.", #mission_mod::TASKS_IDS[#tid]);
3403                            return Err(CuError::new_with_cause("Task errored out during process.", error));
3404                        }
3405                    }
3406                };
3407
3408                let inputs_type = if indices.len() == 1 {
3409                    quote! { #(msgs.#indices)* }
3410                } else {
3411                    quote! { (#(&msgs.#indices),*) }
3412                };
3413
3414                let call_sim_callback = if sim_mode {
3415                    quote! {
3416                        let doit = {
3417                            let cumsg_input = &#inputs_type;
3418                            let cumsg_output = &mut msgs.#output_culist_index;
3419                            let state = CuTaskCallbackState::Process(cumsg_input, cumsg_output);
3420                            let ovr = sim_callback(SimStep::#enum_name(state));
3421
3422                            if let SimOverride::Errored(reason) = ovr  {
3423                                let error: CuError = reason.into();
3424                                #monitoring_action
3425                                false
3426                            }
3427                            else {
3428                                ovr == SimOverride::ExecuteByRuntime
3429                            }
3430                        };
3431                    }
3432                } else {
3433                    quote! { let doit = true; }
3434                };
3435
3436                let logging_tokens = if !task_specs.logging_enabled[tid] {
3437                    let output_culist_index = int2sliceindex(*output_index);
3438                    quote! {
3439                        let mut cumsg_output = &mut culist.msgs.0.#output_culist_index;
3440                        cumsg_output.clear_payload();
3441                    }
3442                } else {
3443                    quote!()
3444                };
3445
3446                (
3447                    quote! {
3448                        {
3449                            #comment_tokens
3450                            kf_manager.freeze_task(clid, &#task_instance)?;
3451                            #call_sim_callback
3452                            let cumsg_input = &#inputs_type;
3453                            let cumsg_output = &mut msgs.#output_culist_index;
3454                            cumsg_output.metadata.process_time.start = clock.now().into();
3455                            let maybe_error = if doit { #task_instance.process(clock, cumsg_input, cumsg_output) } else { Ok(()) };
3456                            cumsg_output.metadata.process_time.end = clock.now().into();
3457                            if let Err(error) = maybe_error {
3458                                #monitoring_action
3459                            }
3460                        }
3461                    },
3462                    logging_tokens,
3463                )
3464            } else {
3465                panic!("Regular task should have an output message index.");
3466            }
3467        }
3468    }
3469}
3470
3471fn generate_bridge_rx_execution_tokens(
3472    bridge_spec: &BridgeSpec,
3473    channel_index: usize,
3474    mission_mod: &Ident,
3475) -> (proc_macro2::TokenStream, proc_macro2::TokenStream) {
3476    let bridge_tuple_index = int2sliceindex(bridge_spec.tuple_index as u32);
3477    let channel = &bridge_spec.rx_channels[channel_index];
3478    let culist_index = channel
3479        .culist_index
3480        .unwrap_or_else(|| panic!("Bridge Rx channel missing output index"));
3481    let culist_index_ts = int2sliceindex(culist_index as u32);
3482    let monitor_index = syn::Index::from(
3483        channel
3484            .monitor_index
3485            .expect("Bridge Rx channel missing monitor index"),
3486    );
3487    let bridge_type = &bridge_spec.type_path;
3488    let const_ident = &channel.const_ident;
3489    (
3490        quote! {
3491            {
3492                let bridge = &mut bridges.#bridge_tuple_index;
3493                let cumsg_output = &mut msgs.#culist_index_ts;
3494                cumsg_output.metadata.process_time.start = clock.now().into();
3495                let maybe_error = bridge.receive(
3496                    clock,
3497                    &<#bridge_type as cu29::cubridge::CuBridge>::Rx::#const_ident,
3498                    cumsg_output,
3499                );
3500                cumsg_output.metadata.process_time.end = clock.now().into();
3501                if let Err(error) = maybe_error {
3502                    let decision = monitor.process_error(#monitor_index, CuTaskState::Process, &error);
3503                    match decision {
3504                        Decision::Abort => {
3505                            debug!("Process: ABORT decision from monitoring. Task '{}' errored out during process. Skipping the processing of CL {}.", #mission_mod::TASKS_IDS[#monitor_index], clid);
3506                            monitor.process_copperlist(&#mission_mod::collect_metadata(&culist))?;
3507                            cl_manager.end_of_processing(clid)?;
3508                            return Ok(());
3509                        }
3510                        Decision::Ignore => {
3511                            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]);
3512                            let cumsg_output = &mut msgs.#culist_index_ts;
3513                            cumsg_output.clear_payload();
3514                        }
3515                        Decision::Shutdown => {
3516                            debug!("Process: SHUTDOWN decision from monitoring. Task '{}' errored out during process. The runtime cannot continue.", #mission_mod::TASKS_IDS[#monitor_index]);
3517                            return Err(CuError::new_with_cause("Task errored out during process.", error));
3518                        }
3519                    }
3520                }
3521            }
3522        },
3523        quote! {},
3524    )
3525}
3526
3527fn generate_bridge_tx_execution_tokens(
3528    step: &CuExecutionStep,
3529    bridge_spec: &BridgeSpec,
3530    channel_index: usize,
3531    mission_mod: &Ident,
3532) -> (proc_macro2::TokenStream, proc_macro2::TokenStream) {
3533    let channel = &bridge_spec.tx_channels[channel_index];
3534    let monitor_index = syn::Index::from(
3535        channel
3536            .monitor_index
3537            .expect("Bridge Tx channel missing monitor index"),
3538    );
3539    let input_index = step
3540        .input_msg_indices_types
3541        .first()
3542        .map(|(idx, _)| int2sliceindex(*idx))
3543        .expect("Bridge Tx channel should have exactly one input");
3544    let bridge_tuple_index = int2sliceindex(bridge_spec.tuple_index as u32);
3545    let bridge_type = &bridge_spec.type_path;
3546    let const_ident = &channel.const_ident;
3547    (
3548        quote! {
3549            {
3550                let bridge = &mut bridges.#bridge_tuple_index;
3551                let cumsg_input = &mut msgs.#input_index;
3552                // Stamp timing so monitors see consistent ranges for bridge Tx as well.
3553                cumsg_input.metadata.process_time.start = clock.now().into();
3554                if let Err(error) = bridge.send(
3555                    clock,
3556                    &<#bridge_type as cu29::cubridge::CuBridge>::Tx::#const_ident,
3557                    &*cumsg_input,
3558                ) {
3559                    let decision = monitor.process_error(#monitor_index, CuTaskState::Process, &error);
3560                    match decision {
3561                        Decision::Abort => {
3562                            debug!("Process: ABORT decision from monitoring. Task '{}' errored out during process. Skipping the processing of CL {}.", #mission_mod::TASKS_IDS[#monitor_index], clid);
3563                            monitor.process_copperlist(&#mission_mod::collect_metadata(&culist))?;
3564                            cl_manager.end_of_processing(clid)?;
3565                            return Ok(());
3566                        }
3567                        Decision::Ignore => {
3568                            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]);
3569                        }
3570                        Decision::Shutdown => {
3571                            debug!("Process: SHUTDOWN decision from monitoring. Task '{}' errored out during process. The runtime cannot continue.", #mission_mod::TASKS_IDS[#monitor_index]);
3572                            return Err(CuError::new_with_cause("Task errored out during process.", error));
3573                        }
3574                    }
3575                }
3576                cumsg_input.metadata.process_time.end = clock.now().into();
3577            }
3578        },
3579        quote! {},
3580    )
3581}
3582
3583#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
3584enum BridgeChannelDirection {
3585    Rx,
3586    Tx,
3587}
3588
3589#[derive(Clone, Debug, PartialEq, Eq, Hash)]
3590struct BridgeChannelKey {
3591    bridge_id: String,
3592    channel_id: String,
3593    direction: BridgeChannelDirection,
3594}
3595
3596#[derive(Clone)]
3597struct BridgeChannelSpec {
3598    id: String,
3599    const_ident: Ident,
3600    #[allow(dead_code)]
3601    msg_type: Type,
3602    config_index: usize,
3603    plan_node_id: Option<NodeId>,
3604    culist_index: Option<usize>,
3605    monitor_index: Option<usize>,
3606}
3607
3608#[derive(Clone)]
3609struct BridgeSpec {
3610    id: String,
3611    type_path: Type,
3612    config_index: usize,
3613    tuple_index: usize,
3614    monitor_index: Option<usize>,
3615    rx_channels: Vec<BridgeChannelSpec>,
3616    tx_channels: Vec<BridgeChannelSpec>,
3617}
3618
3619#[derive(Clone)]
3620struct ExecutionEntity {
3621    kind: ExecutionEntityKind,
3622}
3623
3624#[derive(Clone)]
3625enum ExecutionEntityKind {
3626    Task {
3627        task_index: usize,
3628    },
3629    BridgeRx {
3630        bridge_index: usize,
3631        channel_index: usize,
3632    },
3633    BridgeTx {
3634        bridge_index: usize,
3635        channel_index: usize,
3636    },
3637}
3638
3639#[cfg(test)]
3640mod tests {
3641    // See tests/compile_file directory for more information
3642    #[test]
3643    fn test_compile_fail() {
3644        use rustc_version::{Channel, version_meta};
3645        use std::{fs, path::Path};
3646
3647        let dir = Path::new("tests/compile_fail");
3648        for entry in fs::read_dir(dir).unwrap() {
3649            let entry = entry.unwrap();
3650            if !entry.file_type().unwrap().is_dir() {
3651                continue;
3652            }
3653            for file in fs::read_dir(entry.path()).unwrap() {
3654                let file = file.unwrap();
3655                let p = file.path();
3656                if p.extension().and_then(|x| x.to_str()) != Some("rs") {
3657                    continue;
3658                }
3659
3660                let base = p.with_extension("stderr"); // the file trybuild reads
3661                let src = match version_meta().unwrap().channel {
3662                    Channel::Beta => Path::new(&format!("{}.beta", base.display())).to_path_buf(),
3663                    _ => Path::new(&format!("{}.stable", base.display())).to_path_buf(),
3664                };
3665
3666                if src.exists() {
3667                    fs::copy(src, &base).unwrap();
3668                }
3669            }
3670        }
3671
3672        let t = trybuild::TestCases::new();
3673        t.compile_fail("tests/compile_fail/*/*.rs");
3674    }
3675
3676    #[test]
3677    fn bridge_resources_are_collected() {
3678        use super::*;
3679        use cu29::config::{CuGraph, Flavor, Node};
3680        use std::collections::HashMap;
3681        use syn::parse_str;
3682
3683        let mut graph = CuGraph::default();
3684        let mut node = Node::new_with_flavor("radio", "bridge::Dummy", Flavor::Bridge);
3685        let mut res = HashMap::new();
3686        res.insert("serial".to_string(), "fc.serial0".to_string());
3687        node.set_resources(Some(res));
3688        graph.add_node(node).expect("bridge node");
3689
3690        let task_specs = CuTaskSpecSet::from_graph(&graph);
3691        let bridge_spec = BridgeSpec {
3692            id: "radio".to_string(),
3693            type_path: parse_str("bridge::Dummy").unwrap(),
3694            config_index: 0,
3695            tuple_index: 0,
3696            monitor_index: None,
3697            rx_channels: Vec::new(),
3698            tx_channels: Vec::new(),
3699        };
3700
3701        let mut config = cu29::config::CuConfig::default();
3702        config.resources.push(ResourceBundleConfig {
3703            id: "fc".to_string(),
3704            provider: "board::Bundle".to_string(),
3705            config: None,
3706            missions: None,
3707        });
3708        let bundle_specs = build_bundle_specs(&config, "default").expect("bundle specs");
3709        let specs = collect_resource_specs(&graph, &task_specs, &[bridge_spec], &bundle_specs)
3710            .expect("collect specs");
3711        assert_eq!(specs.len(), 1);
3712        assert!(matches!(specs[0].owner, ResourceOwner::Bridge(0)));
3713        assert_eq!(specs[0].binding_name, "serial");
3714        assert_eq!(specs[0].bundle_index, 0);
3715        assert_eq!(specs[0].resource_name, "serial0");
3716    }
3717}