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