Skip to main content

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