Skip to main content

cu29_derive/
lib.rs

1use proc_macro::TokenStream;
2use quote::{ToTokens, format_ident, quote};
3use std::collections::{BTreeMap, HashMap};
4use std::fs::read_to_string;
5use std::path::Path;
6use std::process::Command;
7use syn::Fields::{Named, Unnamed};
8use syn::meta::parser;
9use syn::parse::Parser;
10use syn::{
11    Field, Fields, ItemImpl, ItemStruct, LitStr, Type, TypeTuple, parse_macro_input, parse_quote,
12    parse_str,
13};
14
15use crate::utils::{config_id_to_bridge_const, config_id_to_enum, config_id_to_struct_member};
16use cu29_runtime::config::CuConfig;
17use cu29_runtime::config::{
18    BridgeChannelConfigRepresentation, ConfigGraphs, CuGraph, Flavor, Node, NodeId,
19    ResourceBundleConfig, read_configuration,
20};
21use cu29_runtime::curuntime::{
22    CuExecutionLoop, CuExecutionStep, CuExecutionUnit, CuTaskType, compute_runtime_plan,
23    find_task_type_for_id,
24};
25use cu29_traits::{CuError, CuResult};
26use proc_macro2::{Ident, Span};
27
28mod bundle_resources;
29mod resources;
30mod utils;
31
32const DEFAULT_CLNB: usize = 2; // We can double buffer for now until we add the parallel copperlist execution support.
33
34#[inline]
35fn int2sliceindex(i: u32) -> syn::Index {
36    syn::Index::from(i as usize)
37}
38
39#[inline(always)]
40fn return_error(msg: String) -> TokenStream {
41    syn::Error::new(Span::call_site(), msg)
42        .to_compile_error()
43        .into()
44}
45
46fn rtsan_guard_tokens() -> proc_macro2::TokenStream {
47    if cfg!(feature = "rtsan") {
48        quote! {
49            let _rt_guard = ::cu29::rtsan::ScopedSanitizeRealtime::default();
50        }
51    } else {
52        quote! {}
53    }
54}
55
56fn git_output_trimmed(repo_root: &Path, args: &[&str]) -> Option<String> {
57    let output = Command::new("git")
58        .arg("-C")
59        .arg(repo_root)
60        .args(args)
61        .output()
62        .ok()?;
63    if !output.status.success() {
64        return None;
65    }
66    let stdout = String::from_utf8(output.stdout).ok()?;
67    Some(stdout.trim().to_string())
68}
69
70fn detect_git_info(repo_root: &Path) -> (Option<String>, Option<bool>) {
71    let in_repo = git_output_trimmed(repo_root, &["rev-parse", "--is-inside-work-tree"])
72        .is_some_and(|value| value == "true");
73    if !in_repo {
74        return (None, None);
75    }
76
77    let commit = git_output_trimmed(repo_root, &["rev-parse", "HEAD"]).filter(|s| !s.is_empty());
78    // Porcelain output is empty when tree is clean.
79    let dirty = git_output_trimmed(repo_root, &["status", "--porcelain"]).map(|s| !s.is_empty());
80    (commit, dirty)
81}
82
83#[derive(Debug, Clone)]
84struct CopperRuntimeArgs {
85    config_path: String,
86    subsystem_id: Option<String>,
87    sim_mode: bool,
88    ignore_resources: bool,
89}
90
91impl CopperRuntimeArgs {
92    fn parse_tokens(args: proc_macro2::TokenStream) -> Result<Self, syn::Error> {
93        let mut config_file: Option<LitStr> = None;
94        let mut subsystem_id: Option<LitStr> = None;
95        let mut sim_mode = false;
96        let mut ignore_resources = false;
97
98        let parser = parser(|meta| {
99            if meta.path.is_ident("config") {
100                config_file = Some(meta.value()?.parse()?);
101                Ok(())
102            } else if meta.path.is_ident("subsystem") {
103                subsystem_id = Some(meta.value()?.parse()?);
104                Ok(())
105            } else if meta.path.is_ident("sim_mode") {
106                if meta.input.peek(syn::Token![=]) {
107                    meta.input.parse::<syn::Token![=]>()?;
108                    let value: syn::LitBool = meta.input.parse()?;
109                    sim_mode = value.value();
110                } else {
111                    sim_mode = true;
112                }
113                Ok(())
114            } else if meta.path.is_ident("ignore_resources") {
115                if meta.input.peek(syn::Token![=]) {
116                    meta.input.parse::<syn::Token![=]>()?;
117                    let value: syn::LitBool = meta.input.parse()?;
118                    ignore_resources = value.value();
119                } else {
120                    ignore_resources = true;
121                }
122                Ok(())
123            } else {
124                Err(meta.error("unsupported property"))
125            }
126        });
127
128        parser.parse2(args)?;
129
130        let config_path = config_file
131            .ok_or_else(|| {
132                syn::Error::new(
133                    Span::call_site(),
134                    "Expected config file attribute like #[copper_runtime(config = \"path\")]",
135                )
136            })?
137            .value();
138
139        Ok(Self {
140            config_path,
141            subsystem_id: subsystem_id.map(|value| value.value()),
142            sim_mode,
143            ignore_resources,
144        })
145    }
146}
147
148#[derive(Debug)]
149struct ResolvedRuntimeConfig {
150    local_config: CuConfig,
151    bundled_local_config_content: String,
152    subsystem_id: Option<String>,
153    subsystem_code: u16,
154}
155
156#[proc_macro]
157pub fn resources(input: TokenStream) -> TokenStream {
158    resources::resources(input)
159}
160
161#[proc_macro]
162pub fn bundle_resources(input: TokenStream) -> TokenStream {
163    bundle_resources::bundle_resources(input)
164}
165
166/// Generates the CopperList content type from a config.
167/// gen_cumsgs!("path/to/config.toml")
168/// It will create a new type called CuStampedDataSet you can pass to the log reader for decoding:
169#[proc_macro]
170pub fn gen_cumsgs(config_path_lit: TokenStream) -> TokenStream {
171    #[cfg(feature = "std")]
172    let std = true;
173
174    #[cfg(not(feature = "std"))]
175    let std = false;
176    let config = parse_macro_input!(config_path_lit as LitStr).value();
177    if !std::path::Path::new(&config_full_path(&config)).exists() {
178        return return_error(format!(
179            "The configuration file `{config}` does not exist. Please provide a valid path."
180        ));
181    }
182    #[cfg(feature = "macro_debug")]
183    eprintln!("[gen culist support with {config:?}]");
184    let cuconfig = match read_config(&config) {
185        Ok(cuconfig) => cuconfig,
186        Err(e) => return return_error(e.to_string()),
187    };
188
189    let extra_imports = if !std {
190        quote! {
191            use core::fmt::Debug;
192            use core::fmt::Formatter;
193            use core::fmt::Result as FmtResult;
194            use alloc::vec;
195            use alloc::vec::Vec;
196        }
197    } else {
198        quote! {
199            use std::fmt::Debug;
200            use std::fmt::Formatter;
201            use std::fmt::Result as FmtResult;
202        }
203    };
204
205    let common_imports = quote! {
206        use cu29::bincode::Encode;
207        use cu29::bincode::enc::Encoder;
208        use cu29::bincode::error::EncodeError;
209        use cu29::bincode::Decode;
210        use cu29::bincode::de::Decoder;
211        use cu29::bincode::error::DecodeError;
212        use cu29::copperlist::CopperList;
213        use cu29::prelude::ErasedCuStampedData;
214        use cu29::prelude::ErasedCuStampedDataSet;
215        use cu29::prelude::MatchingTasks;
216        use cu29::prelude::Serialize;
217        use cu29::prelude::CuMsg;
218        use cu29::prelude::CuMsgMetadata;
219        use cu29::prelude::CuListZeroedInit;
220        use cu29::prelude::CuCompactString;
221        #extra_imports
222    };
223
224    let with_uses = match &cuconfig.graphs {
225        ConfigGraphs::Simple(graph) => {
226            let support = match build_gen_cumsgs_support(&cuconfig, graph, None) {
227                Ok(support) => support,
228                Err(e) => return return_error(e.to_string()),
229            };
230
231            quote! {
232                mod cumsgs {
233                    #common_imports
234                    #support
235                }
236                use cumsgs::CuStampedDataSet;
237                type CuMsgs=CuStampedDataSet;
238            }
239        }
240        ConfigGraphs::Missions(graphs) => {
241            let mut missions: Vec<_> = graphs.iter().collect();
242            missions.sort_by(|a, b| a.0.cmp(b.0));
243
244            let mut mission_modules = Vec::<proc_macro2::TokenStream>::new();
245            for (mission, graph) in missions {
246                let mission_mod = match parse_str::<Ident>(mission.as_str()) {
247                    Ok(id) => id,
248                    Err(_) => {
249                        return return_error(format!(
250                            "Mission '{mission}' is not a valid Rust identifier for gen_cumsgs output."
251                        ));
252                    }
253                };
254
255                let support = match build_gen_cumsgs_support(&cuconfig, graph, Some(mission)) {
256                    Ok(support) => support,
257                    Err(e) => return return_error(e.to_string()),
258                };
259
260                mission_modules.push(quote! {
261                    pub mod #mission_mod {
262                        #common_imports
263                        #support
264                    }
265                });
266            }
267
268            let default_exports = if graphs.contains_key("default") {
269                quote! {
270                    use cumsgs::default::CuStampedDataSet;
271                    type CuMsgs=CuStampedDataSet;
272                }
273            } else {
274                quote! {}
275            };
276
277            quote! {
278                mod cumsgs {
279                    #(#mission_modules)*
280                }
281                #default_exports
282            }
283        }
284    };
285    with_uses.into()
286}
287
288fn build_gen_cumsgs_support(
289    cuconfig: &CuConfig,
290    graph: &CuGraph,
291    mission_label: Option<&str>,
292) -> CuResult<proc_macro2::TokenStream> {
293    let task_specs = CuTaskSpecSet::from_graph(graph);
294    let channel_usage = collect_bridge_channel_usage(graph);
295    let mut bridge_specs = build_bridge_specs(cuconfig, graph, &channel_usage);
296    let (culist_plan, exec_entities, plan_to_original) =
297        build_execution_plan(graph, &task_specs, &mut bridge_specs).map_err(|e| {
298            if let Some(mission) = mission_label {
299                CuError::from(format!(
300                    "Could not compute copperlist plan for mission '{mission}': {e}"
301                ))
302            } else {
303                CuError::from(format!("Could not compute copperlist plan: {e}"))
304            }
305        })?;
306    let task_names = collect_task_names(graph);
307    let (culist_order, node_output_positions) = collect_culist_metadata(
308        &culist_plan,
309        &exec_entities,
310        &mut bridge_specs,
311        &plan_to_original,
312    );
313
314    #[cfg(feature = "macro_debug")]
315    if let Some(mission) = mission_label {
316        eprintln!(
317            "[The CuStampedDataSet matching tasks ids for mission '{mission}' are {:?}]",
318            culist_order
319        );
320    } else {
321        eprintln!(
322            "[The CuStampedDataSet matching tasks ids are {:?}]",
323            culist_order
324        );
325    }
326
327    Ok(gen_culist_support(
328        cuconfig,
329        mission_label,
330        &culist_plan,
331        &culist_order,
332        &node_output_positions,
333        &task_names,
334        &bridge_specs,
335    ))
336}
337
338/// Build the inner support of the copper list.
339fn gen_culist_support(
340    cuconfig: &CuConfig,
341    mission_label: Option<&str>,
342    runtime_plan: &CuExecutionLoop,
343    culist_indices_in_plan_order: &[usize],
344    node_output_positions: &HashMap<NodeId, usize>,
345    task_names: &[(NodeId, String, String)],
346    bridge_specs: &[BridgeSpec],
347) -> proc_macro2::TokenStream {
348    #[cfg(feature = "macro_debug")]
349    eprintln!("[Extract msgs types]");
350    let output_packs = extract_output_packs(runtime_plan);
351    let slot_types: Vec<Type> = output_packs.iter().map(|pack| pack.slot_type()).collect();
352
353    let culist_size = output_packs.len();
354
355    #[cfg(feature = "macro_debug")]
356    eprintln!("[build the copperlist struct]");
357    let msgs_types_tuple: TypeTuple = build_culist_tuple(&slot_types);
358    let cumsg_count: usize = output_packs.iter().map(|pack| pack.msg_types.len()).sum();
359    let flat_codec_bindings = build_flat_slot_codec_bindings(
360        cuconfig,
361        mission_label,
362        &output_packs,
363        node_output_positions,
364        task_names,
365    )
366    .unwrap_or_else(|err| panic!("Could not resolve log codec bindings: {err}"));
367    let default_config_ron_ident = format_ident!("__CU_LOGCODEC_DEFAULT_CONFIG_RON");
368    let default_config_ron = cuconfig
369        .serialize_ron()
370        .unwrap_or_else(|_| "<failed to serialize config>".to_string());
371    let default_config_ron_lit = LitStr::new(&default_config_ron, Span::call_site());
372    let (codec_helper_fns, encode_helper_names, decode_helper_names) = build_culist_codec_helpers(
373        &flat_codec_bindings,
374        &default_config_ron_ident,
375        mission_label,
376    );
377    let default_config_ron_const = if flat_codec_bindings.iter().any(Option::is_some) {
378        quote! {
379            const #default_config_ron_ident: &str = #default_config_ron_lit;
380        }
381    } else {
382        quote! {}
383    };
384
385    #[cfg(feature = "macro_debug")]
386    eprintln!("[build the copperlist tuple bincode support]");
387    let msgs_types_tuple_encode = build_culist_tuple_encode(&output_packs, &encode_helper_names);
388    let msgs_types_tuple_decode = build_culist_tuple_decode(
389        &output_packs,
390        &slot_types,
391        cumsg_count,
392        &decode_helper_names,
393    );
394
395    #[cfg(feature = "macro_debug")]
396    eprintln!("[build the copperlist tuple debug support]");
397    let msgs_types_tuple_debug = build_culist_tuple_debug(&slot_types);
398
399    #[cfg(feature = "macro_debug")]
400    eprintln!("[build the copperlist tuple serialize support]");
401    let msgs_types_tuple_serialize = build_culist_tuple_serialize(&slot_types);
402
403    #[cfg(feature = "macro_debug")]
404    eprintln!("[build the default tuple support]");
405    let msgs_types_tuple_default = build_culist_tuple_default(&slot_types, cumsg_count);
406
407    #[cfg(feature = "macro_debug")]
408    eprintln!("[build erasedcumsgs]");
409
410    let erasedmsg_trait_impl = build_culist_erasedcumsgs(&output_packs);
411
412    let metadata_accessors: Vec<proc_macro2::TokenStream> = culist_indices_in_plan_order
413        .iter()
414        .map(|idx| {
415            let slot_index = syn::Index::from(*idx);
416            let pack = output_packs
417                .get(*idx)
418                .unwrap_or_else(|| panic!("Missing output pack for index {idx}"));
419            if pack.is_multi() {
420                quote! { &culist.msgs.0.#slot_index.0.metadata }
421            } else {
422                quote! { &culist.msgs.0.#slot_index.metadata }
423            }
424        })
425        .collect();
426    let mut zeroed_init_tokens: Vec<proc_macro2::TokenStream> = Vec::new();
427    for idx in culist_indices_in_plan_order {
428        let slot_index = syn::Index::from(*idx);
429        let pack = output_packs
430            .get(*idx)
431            .unwrap_or_else(|| panic!("Missing output pack for index {idx}"));
432        if pack.is_multi() {
433            for port_idx in 0..pack.msg_types.len() {
434                let port_index = syn::Index::from(port_idx);
435                zeroed_init_tokens.push(quote! {
436                    self.0.#slot_index.#port_index.metadata.status_txt = CuCompactString::default();
437                    self.0.#slot_index.#port_index.metadata.process_time.start =
438                        cu29::clock::OptionCuTime::none();
439                    self.0.#slot_index.#port_index.metadata.process_time.end =
440                        cu29::clock::OptionCuTime::none();
441                    self.0.#slot_index.#port_index.metadata.origin = None;
442                });
443            }
444        } else {
445            zeroed_init_tokens.push(quote! {
446                self.0.#slot_index.metadata.status_txt = CuCompactString::default();
447                self.0.#slot_index.metadata.process_time.start = cu29::clock::OptionCuTime::none();
448                self.0.#slot_index.metadata.process_time.end = cu29::clock::OptionCuTime::none();
449                self.0.#slot_index.metadata.origin = None;
450            });
451        }
452    }
453    let collect_metadata_function = quote! {
454        pub fn collect_metadata<'a>(culist: &'a CuList) -> [&'a CuMsgMetadata; #culist_size] {
455            [#( #metadata_accessors, )*]
456        }
457    };
458
459    let payload_bytes_accumulators: Vec<proc_macro2::TokenStream> = culist_indices_in_plan_order
460        .iter()
461        .scan(0usize, |flat_idx, idx| {
462            let slot_index = syn::Index::from(*idx);
463            let pack = output_packs
464                .get(*idx)
465                .unwrap_or_else(|| panic!("Missing output pack for index {idx}"));
466            if pack.is_multi() {
467                let iter = (0..pack.msg_types.len()).map(|port_idx| {
468                    let port_index = syn::Index::from(port_idx);
469                    let cache_index = syn::Index::from(*flat_idx);
470                    *flat_idx += 1;
471                    quote! {
472                        if let Some(payload) = culist.msgs.0.#slot_index.#port_index.payload() {
473                            let cached = culist.msgs.1.get(#cache_index);
474                            let io = if cached.present {
475                                cu29::monitoring::PayloadIoStats {
476                                    resident_bytes: cached.resident_bytes as usize,
477                                    encoded_bytes: cached.encoded_bytes as usize,
478                                    handle_bytes: cached.handle_bytes as usize,
479                                }
480                            } else {
481                                cu29::monitoring::payload_io_stats(payload)?
482                            };
483                            raw += io.resident_bytes;
484                            handles += io.handle_bytes;
485                        }
486                    }
487                });
488                Some(quote! { #(#iter)* })
489            } else {
490                let cache_index = syn::Index::from(*flat_idx);
491                *flat_idx += 1;
492                Some(quote! {
493                    if let Some(payload) = culist.msgs.0.#slot_index.payload() {
494                        let cached = culist.msgs.1.get(#cache_index);
495                        let io = if cached.present {
496                            cu29::monitoring::PayloadIoStats {
497                                resident_bytes: cached.resident_bytes as usize,
498                                encoded_bytes: cached.encoded_bytes as usize,
499                                handle_bytes: cached.handle_bytes as usize,
500                            }
501                        } else {
502                            cu29::monitoring::payload_io_stats(payload)?
503                        };
504                        raw += io.resident_bytes;
505                        handles += io.handle_bytes;
506                    }
507                })
508            }
509        })
510        .collect();
511
512    let payload_raw_bytes_accumulators: Vec<proc_macro2::TokenStream> = output_packs
513        .iter()
514        .enumerate()
515        .scan(0usize, |flat_idx, (slot_idx, pack)| {
516            let slot_index = syn::Index::from(slot_idx);
517            if pack.is_multi() {
518                let iter = (0..pack.msg_types.len()).map(|port_idx| {
519                    let port_index = syn::Index::from(port_idx);
520                    let cache_index = syn::Index::from(*flat_idx);
521                    *flat_idx += 1;
522                    quote! {
523                        if let Some(payload) = self.0.#slot_index.#port_index.payload() {
524                            let cached = self.1.get(#cache_index);
525                            bytes.push(if cached.present {
526                                Some(cached.resident_bytes)
527                            } else {
528                                cu29::monitoring::payload_io_stats(payload)
529                                    .ok()
530                                    .map(|io| io.resident_bytes as u64)
531                            });
532                        } else {
533                            bytes.push(None);
534                        }
535                    }
536                });
537                Some(quote! { #(#iter)* })
538            } else {
539                let cache_index = syn::Index::from(*flat_idx);
540                *flat_idx += 1;
541                Some(quote! {
542                    if let Some(payload) = self.0.#slot_index.payload() {
543                        let cached = self.1.get(#cache_index);
544                        bytes.push(if cached.present {
545                            Some(cached.resident_bytes)
546                        } else {
547                            cu29::monitoring::payload_io_stats(payload)
548                                .ok()
549                                .map(|io| io.resident_bytes as u64)
550                        });
551                    } else {
552                        bytes.push(None);
553                    }
554                })
555            }
556        })
557        .collect();
558
559    let compute_payload_bytes_fn = quote! {
560        pub fn compute_payload_bytes(culist: &CuList) -> cu29::prelude::CuResult<(u64, u64)> {
561            let mut raw: usize = 0;
562            let mut handles: usize = 0;
563            #(#payload_bytes_accumulators)*
564            Ok((raw as u64, handles as u64))
565        }
566    };
567
568    let payload_raw_bytes_impl = quote! {
569        impl ::cu29::CuPayloadRawBytes for CuStampedDataSet {
570            fn payload_raw_bytes(&self) -> Vec<Option<u64>> {
571                let mut bytes: Vec<Option<u64>> = Vec::with_capacity(#cumsg_count);
572                #(#payload_raw_bytes_accumulators)*
573                bytes
574            }
575        }
576    };
577
578    let mut slot_origin_ids: Vec<Option<String>> = vec![None; output_packs.len()];
579    let mut slot_task_names: Vec<Option<String>> = vec![None; output_packs.len()];
580
581    let mut methods = Vec::new();
582    for (node_id, task_id, member_name) in task_names {
583        let output_position = node_output_positions.get(node_id).unwrap_or_else(|| {
584            panic!("Task {task_id} (node id: {node_id}) not found in execution order")
585        });
586        let pack = output_packs
587            .get(*output_position)
588            .unwrap_or_else(|| panic!("Missing output pack for task {task_id}"));
589        let slot_index = syn::Index::from(*output_position);
590        slot_origin_ids[*output_position] = Some(task_id.clone());
591        slot_task_names[*output_position] = Some(member_name.clone());
592
593        if pack.msg_types.len() == 1 {
594            let fn_name = format_ident!("get_{}_output", member_name);
595            let payload_type = pack.msg_types.first().unwrap();
596            methods.push(quote! {
597                #[allow(dead_code)]
598                pub fn #fn_name(&self) -> &CuMsg<#payload_type> {
599                    &self.0.#slot_index
600                }
601            });
602        } else {
603            let outputs_fn = format_ident!("get_{}_outputs", member_name);
604            let slot_type = pack.slot_type();
605            for (port_idx, payload_type) in pack.msg_types.iter().enumerate() {
606                let fn_name = format_ident!("get_{}_output_{}", member_name, port_idx);
607                let port_index = syn::Index::from(port_idx);
608                methods.push(quote! {
609                    #[allow(dead_code)]
610                    pub fn #fn_name(&self) -> &CuMsg<#payload_type> {
611                        &self.0.#slot_index.#port_index
612                    }
613                });
614            }
615            methods.push(quote! {
616                #[allow(dead_code)]
617                pub fn #outputs_fn(&self) -> &#slot_type {
618                    &self.0.#slot_index
619                }
620            });
621        }
622    }
623
624    for spec in bridge_specs {
625        for channel in &spec.rx_channels {
626            if let Some(culist_index) = channel.culist_index {
627                let origin_id = format!("bridge::{}::rx::{}", spec.id, channel.id);
628                let Some(existing_slot) = slot_origin_ids.get_mut(culist_index) else {
629                    panic!(
630                        "Bridge origin '{origin_id}' points to out-of-range copperlist slot {culist_index}"
631                    );
632                };
633                if let Some(existing) = existing_slot.as_ref() {
634                    panic!(
635                        "Duplicate slot origin assignment for slot {culist_index}: '{existing}' and '{origin_id}'"
636                    );
637                }
638                *existing_slot = Some(origin_id.clone());
639                let Some(slot_name) = slot_task_names.get_mut(culist_index) else {
640                    panic!(
641                        "Bridge origin '{origin_id}' points to out-of-range name slot {culist_index}"
642                    );
643                };
644                *slot_name = Some(origin_id);
645            }
646        }
647        for channel in &spec.tx_channels {
648            if let Some(culist_index) = channel.culist_index {
649                let origin_id = format!("bridge::{}::tx::{}", spec.id, channel.id);
650                let Some(existing_slot) = slot_origin_ids.get_mut(culist_index) else {
651                    panic!(
652                        "Bridge origin '{origin_id}' points to out-of-range copperlist slot {culist_index}"
653                    );
654                };
655                if let Some(existing) = existing_slot.as_ref() {
656                    panic!(
657                        "Duplicate slot origin assignment for slot {culist_index}: '{existing}' and '{origin_id}'"
658                    );
659                }
660                *existing_slot = Some(origin_id.clone());
661                let Some(slot_name) = slot_task_names.get_mut(culist_index) else {
662                    panic!(
663                        "Bridge origin '{origin_id}' points to out-of-range name slot {culist_index}"
664                    );
665                };
666                *slot_name = Some(origin_id);
667            }
668        }
669    }
670
671    let task_name_literals = flatten_slot_origin_ids(&output_packs, &slot_origin_ids);
672    let task_output_specs = flatten_task_output_specs(&output_packs, &slot_origin_ids);
673    let task_output_spec_literals: Vec<proc_macro2::TokenStream> = task_output_specs
674        .iter()
675        .map(|(task_id, msg_type, payload_type_path)| {
676            let task_id = LitStr::new(task_id, Span::call_site());
677            let msg_type = LitStr::new(msg_type, Span::call_site());
678            let payload_type_path = LitStr::new(payload_type_path, Span::call_site());
679            quote! {
680                cu29::TaskOutputSpec {
681                    task_id: #task_id,
682                    msg_type: #msg_type,
683                    payload_type_path: #payload_type_path,
684                }
685            }
686        })
687        .collect();
688
689    let mut logviz_blocks = Vec::new();
690    for (slot_idx, pack) in output_packs.iter().enumerate() {
691        if pack.msg_types.is_empty() {
692            continue;
693        }
694        let slot_index = syn::Index::from(slot_idx);
695        let slot_name = slot_task_names.get(slot_idx).and_then(|name| name.as_ref());
696
697        if pack.is_multi() {
698            for (port_idx, _) in pack.msg_types.iter().enumerate() {
699                let port_index = syn::Index::from(port_idx);
700                let path_expr = if let Some(name) = slot_name {
701                    let lit = LitStr::new(name, Span::call_site());
702                    quote! { format!("{}/{}", #lit, #port_idx) }
703                } else {
704                    quote! { format!("slot_{}/{}", #slot_idx, #port_idx) }
705                };
706                logviz_blocks.push(quote! {
707                    {
708                        let msg = &self.0.#slot_index.#port_index;
709                        if let Some(payload) = msg.payload() {
710                            ::cu29_logviz::apply_tov(rec, &msg.tov);
711                            let path = #path_expr;
712                            ::cu29_logviz::log_payload_auto(rec, &path, payload)?;
713                        }
714                    }
715                });
716            }
717        } else {
718            let path_expr = if let Some(name) = slot_name {
719                let lit = LitStr::new(name, Span::call_site());
720                quote! { #lit.to_string() }
721            } else {
722                quote! { format!("slot_{}", #slot_idx) }
723            };
724            logviz_blocks.push(quote! {
725                {
726                    let msg = &self.0.#slot_index;
727                    if let Some(payload) = msg.payload() {
728                        ::cu29_logviz::apply_tov(rec, &msg.tov);
729                        let path = #path_expr;
730                        ::cu29_logviz::log_payload_auto(rec, &path, payload)?;
731                    }
732                }
733            });
734        }
735    }
736
737    let logviz_impl = if cfg!(feature = "logviz") {
738        quote! {
739            impl ::cu29_logviz::LogvizDataSet for CuStampedDataSet {
740                fn logviz_emit(
741                    &self,
742                    rec: &::cu29_logviz::RecordingStream,
743                ) -> ::cu29::prelude::CuResult<()> {
744                    #(#logviz_blocks)*
745                    Ok(())
746                }
747            }
748        }
749    } else {
750        quote! {}
751    };
752    // Generate bridge channel getter methods
753    for spec in bridge_specs {
754        for channel in &spec.rx_channels {
755            if let Some(culist_index) = channel.culist_index {
756                let slot_index = syn::Index::from(culist_index);
757                let bridge_name = config_id_to_struct_member(spec.id.as_str());
758                let channel_name = config_id_to_struct_member(channel.id.as_str());
759                let fn_name = format_ident!("get_{}_rx_{}", bridge_name, channel_name);
760                let msg_type = &channel.msg_type;
761
762                methods.push(quote! {
763                    #[allow(dead_code)]
764                    pub fn #fn_name(&self) -> &CuMsg<#msg_type> {
765                        &self.0.#slot_index
766                    }
767                });
768            }
769        }
770    }
771
772    // This generates a way to get the metadata of every single message of a culist at low cost
773    quote! {
774        #collect_metadata_function
775        #compute_payload_bytes_fn
776        #default_config_ron_const
777        #(#codec_helper_fns)*
778
779        pub struct CuStampedDataSet(pub #msgs_types_tuple, cu29::monitoring::CuMsgIoCache<#cumsg_count>);
780
781        pub type CuList = CopperList<CuStampedDataSet>;
782
783        impl CuStampedDataSet {
784            #(#methods)*
785
786            #[allow(dead_code)]
787            fn get_tuple(&self) -> &#msgs_types_tuple {
788                &self.0
789            }
790
791            #[allow(dead_code)]
792            fn get_tuple_mut(&mut self) -> &mut #msgs_types_tuple {
793                &mut self.0
794            }
795        }
796
797        #payload_raw_bytes_impl
798        #logviz_impl
799
800        impl MatchingTasks for CuStampedDataSet {
801            #[allow(dead_code)]
802            fn get_all_task_ids() -> &'static [&'static str] {
803                &[#(#task_name_literals),*]
804            }
805
806            #[allow(dead_code)]
807            fn get_output_specs() -> &'static [cu29::TaskOutputSpec] {
808                &[#(#task_output_spec_literals),*]
809            }
810        }
811
812        // Note: PayloadSchemas is NOT implemented here.
813        // Users who want MCAP export with schemas should implement it manually
814        // using cu29_export::trace_type_to_jsonschema.
815
816        // Adds the bincode support for the copper list tuple
817        #msgs_types_tuple_encode
818        #msgs_types_tuple_decode
819
820        // Adds the debug support
821        #msgs_types_tuple_debug
822
823        // Adds the serialization support
824        #msgs_types_tuple_serialize
825
826        // Adds the default support
827        #msgs_types_tuple_default
828
829        // Adds the type erased CuStampedDataSet support (to help generic serialized conversions)
830        #erasedmsg_trait_impl
831
832        impl CuListZeroedInit for CuStampedDataSet {
833            fn init_zeroed(&mut self) {
834                self.1.clear();
835                #(#zeroed_init_tokens)*
836            }
837        }
838    }
839}
840
841fn gen_sim_support(
842    runtime_plan: &CuExecutionLoop,
843    exec_entities: &[ExecutionEntity],
844    bridge_specs: &[BridgeSpec],
845) -> proc_macro2::TokenStream {
846    #[cfg(feature = "macro_debug")]
847    eprintln!("[Sim: Build SimEnum]");
848    let plan_enum: Vec<proc_macro2::TokenStream> = runtime_plan
849        .steps
850        .iter()
851        .map(|unit| match unit {
852            CuExecutionUnit::Step(step) => match &exec_entities[step.node_id as usize].kind {
853                ExecutionEntityKind::Task { .. } => {
854                    let enum_entry_name = config_id_to_enum(step.node.get_id().as_str());
855                    let enum_ident = Ident::new(&enum_entry_name, Span::call_site());
856                    let inputs: Vec<Type> = step
857                        .input_msg_indices_types
858                        .iter()
859                        .map(|input| {
860                            parse_str::<Type>(format!("CuMsg<{}>", input.msg_type).as_str()).unwrap()
861                        })
862                        .collect();
863                    let output: Option<Type> = step.output_msg_pack.as_ref().map(|pack| {
864                        let msg_types: Vec<Type> = pack
865                            .msg_types
866                            .iter()
867                            .map(|msg_type| {
868                                parse_str::<Type>(msg_type.as_str()).unwrap_or_else(|_| {
869                                    panic!("Could not transform {msg_type} into a message Rust type.")
870                                })
871                            })
872                            .collect();
873                        build_output_slot_type(&msg_types)
874                    });
875                    let no_output = parse_str::<Type>("CuMsg<()>").unwrap();
876                    let output = output.as_ref().unwrap_or(&no_output);
877
878                    let inputs_type = if inputs.is_empty() {
879                        quote! { () }
880                    } else if inputs.len() == 1 {
881                        let input = inputs.first().unwrap();
882                        quote! { &'a #input }
883                    } else {
884                        quote! { &'a (#(&'a #inputs),*) }
885                    };
886
887                    quote! {
888                        #enum_ident(CuTaskCallbackState<#inputs_type, &'a mut #output>)
889                    }
890                }
891                ExecutionEntityKind::BridgeRx { bridge_index, channel_index } => {
892                    let bridge_spec = &bridge_specs[*bridge_index];
893                    let channel = &bridge_spec.rx_channels[*channel_index];
894                    let enum_entry_name = config_id_to_enum(&format!("{}_rx_{}", bridge_spec.id, channel.id));
895                    let enum_ident = Ident::new(&enum_entry_name, Span::call_site());
896                    let channel_type: Type = parse_str::<Type>(channel.msg_type_name.as_str()).unwrap();
897                    let bridge_type = runtime_bridge_type_for_spec(bridge_spec, true);
898                    let _const_ident = &channel.const_ident;
899                    quote! {
900                        #enum_ident {
901                            channel: &'static cu29::cubridge::BridgeChannel<< <#bridge_type as cu29::cubridge::CuBridge>::Rx as cu29::cubridge::BridgeChannelSet >::Id, #channel_type>,
902                            msg: &'a mut CuMsg<#channel_type>,
903                        }
904                    }
905                }
906                ExecutionEntityKind::BridgeTx { bridge_index, channel_index } => {
907                    let bridge_spec = &bridge_specs[*bridge_index];
908                    let channel = &bridge_spec.tx_channels[*channel_index];
909                    let enum_entry_name = config_id_to_enum(&format!("{}_tx_{}", bridge_spec.id, channel.id));
910                    let enum_ident = Ident::new(&enum_entry_name, Span::call_site());
911                    let channel_type: Type = parse_str::<Type>(channel.msg_type_name.as_str()).unwrap();
912                    let output_pack = step
913                        .output_msg_pack
914                        .as_ref()
915                        .expect("Bridge Tx channel missing output pack for sim support");
916                    let output_types: Vec<Type> = output_pack
917                        .msg_types
918                        .iter()
919                        .map(|msg_type| {
920                            parse_str::<Type>(msg_type.as_str()).unwrap_or_else(|_| {
921                                panic!("Could not transform {msg_type} into a message Rust type.")
922                            })
923                        })
924                        .collect();
925                    let output_type = build_output_slot_type(&output_types);
926                    let bridge_type = runtime_bridge_type_for_spec(bridge_spec, true);
927                    let _const_ident = &channel.const_ident;
928                    quote! {
929                        #enum_ident {
930                            channel: &'static cu29::cubridge::BridgeChannel<< <#bridge_type as cu29::cubridge::CuBridge>::Tx as cu29::cubridge::BridgeChannelSet >::Id, #channel_type>,
931                            msg: &'a CuMsg<#channel_type>,
932                            output: &'a mut #output_type,
933                        }
934                    }
935                }
936            },
937            CuExecutionUnit::Loop(_) => {
938                todo!("Needs to be implemented")
939            }
940        })
941        .collect();
942
943    // bridge lifecycle variants (one per bridge)
944    let mut variants = plan_enum;
945
946    // add bridge lifecycle variants
947    for bridge_spec in bridge_specs {
948        let enum_entry_name = config_id_to_enum(&format!("{}_bridge", bridge_spec.id));
949        let enum_ident = Ident::new(&enum_entry_name, Span::call_site());
950        variants.push(quote! {
951            #enum_ident(cu29::simulation::CuBridgeLifecycleState)
952        });
953    }
954
955    variants.push(quote! { __Phantom(core::marker::PhantomData<&'a ()>) });
956    quote! {
957        // not used if sim is not generated but this is ok.
958        #[allow(dead_code, unused_lifetimes)]
959        pub enum SimStep<'a> {
960            #(#variants),*
961        }
962    }
963}
964
965fn gen_recorded_replay_support(
966    runtime_plan: &CuExecutionLoop,
967    exec_entities: &[ExecutionEntity],
968    bridge_specs: &[BridgeSpec],
969) -> proc_macro2::TokenStream {
970    let replay_arms: Vec<proc_macro2::TokenStream> = runtime_plan
971        .steps
972        .iter()
973        .filter_map(|unit| match unit {
974            CuExecutionUnit::Step(step) => match &exec_entities[step.node_id as usize].kind {
975                ExecutionEntityKind::Task { .. } => {
976                    let enum_entry_name = config_id_to_enum(step.node.get_id().as_str());
977                    let enum_ident = Ident::new(&enum_entry_name, Span::call_site());
978                    let output_pack = step
979                        .output_msg_pack
980                        .as_ref()
981                        .expect("Task step missing output pack for recorded replay");
982                    let culist_index = int2sliceindex(output_pack.culist_index);
983                    Some(quote! {
984                        SimStep::#enum_ident(CuTaskCallbackState::Process(_, output)) => {
985                            *output = recorded.msgs.0.#culist_index.clone();
986                            SimOverride::ExecutedBySim
987                        }
988                    })
989                }
990                ExecutionEntityKind::BridgeRx {
991                    bridge_index,
992                    channel_index,
993                } => {
994                    let bridge_spec = &bridge_specs[*bridge_index];
995                    let channel = &bridge_spec.rx_channels[*channel_index];
996                    let enum_entry_name =
997                        config_id_to_enum(&format!("{}_rx_{}", bridge_spec.id, channel.id));
998                    let enum_ident = Ident::new(&enum_entry_name, Span::call_site());
999                    let output_pack = step
1000                        .output_msg_pack
1001                        .as_ref()
1002                        .expect("Bridge Rx channel missing output pack for recorded replay");
1003                    let port_index = output_pack
1004                        .msg_types
1005                        .iter()
1006                        .position(|msg| msg == &channel.msg_type_name)
1007                        .unwrap_or_else(|| {
1008                            panic!(
1009                                "Bridge Rx channel '{}' missing output port for '{}'",
1010                                channel.id, channel.msg_type_name
1011                            )
1012                        });
1013                    let culist_index = int2sliceindex(output_pack.culist_index);
1014                    let recorded_slot = if output_pack.msg_types.len() == 1 {
1015                        quote! { recorded.msgs.0.#culist_index.clone() }
1016                    } else {
1017                        let port_index = syn::Index::from(port_index);
1018                        quote! { recorded.msgs.0.#culist_index.#port_index.clone() }
1019                    };
1020                    Some(quote! {
1021                        SimStep::#enum_ident { msg, .. } => {
1022                            *msg = #recorded_slot;
1023                            SimOverride::ExecutedBySim
1024                        }
1025                    })
1026                }
1027                ExecutionEntityKind::BridgeTx {
1028                    bridge_index,
1029                    channel_index,
1030                } => {
1031                    let bridge_spec = &bridge_specs[*bridge_index];
1032                    let channel = &bridge_spec.tx_channels[*channel_index];
1033                    let enum_entry_name =
1034                        config_id_to_enum(&format!("{}_tx_{}", bridge_spec.id, channel.id));
1035                    let enum_ident = Ident::new(&enum_entry_name, Span::call_site());
1036                    let output_pack = step
1037                        .output_msg_pack
1038                        .as_ref()
1039                        .expect("Bridge Tx channel missing output pack for recorded replay");
1040                    let culist_index = int2sliceindex(output_pack.culist_index);
1041                    Some(quote! {
1042                        SimStep::#enum_ident { output, .. } => {
1043                            *output = recorded.msgs.0.#culist_index.clone();
1044                            SimOverride::ExecutedBySim
1045                        }
1046                    })
1047                }
1048            },
1049            CuExecutionUnit::Loop(_) => None,
1050        })
1051        .collect();
1052
1053    quote! {
1054        #[allow(dead_code)]
1055        pub fn recorded_replay_step<'a>(
1056            step: SimStep<'a>,
1057            recorded: &CopperList<CuStampedDataSet>,
1058        ) -> SimOverride {
1059            match step {
1060                #(#replay_arms),*,
1061                _ => SimOverride::ExecuteByRuntime,
1062            }
1063        }
1064    }
1065}
1066
1067/// Adds `#[copper_runtime(config = "path", subsystem = "id", sim_mode = false/true, ignore_resources = false/true)]`
1068/// to your application struct to generate the runtime.
1069/// if sim_mode is omitted, it is set to false.
1070/// if ignore_resources is omitted, it is set to false.
1071/// if `subsystem` is provided, `config` must point to a strict multi-Copper config and the
1072/// selected subsystem local config will be embedded into the generated runtime.
1073/// This will add a "runtime" field to your struct and implement the "new" and "run" methods.
1074#[proc_macro_attribute]
1075pub fn copper_runtime(args: TokenStream, input: TokenStream) -> TokenStream {
1076    #[cfg(feature = "macro_debug")]
1077    eprintln!("[entry]");
1078    let mut application_struct = parse_macro_input!(input as ItemStruct);
1079
1080    let application_name = &application_struct.ident;
1081    let builder_name = format_ident!("{}Builder", application_name);
1082    let runtime_args = match CopperRuntimeArgs::parse_tokens(args.into()) {
1083        Ok(runtime_args) => runtime_args,
1084        Err(err) => return err.to_compile_error().into(),
1085    };
1086    let config_file = runtime_args.config_path.clone();
1087    let sim_mode = runtime_args.sim_mode;
1088    let ignore_resources = runtime_args.ignore_resources;
1089
1090    #[cfg(feature = "std")]
1091    let std = true;
1092
1093    #[cfg(not(feature = "std"))]
1094    let std = false;
1095    let signal_handler = cfg!(feature = "signal-handler");
1096    let parallel_rt_enabled = cfg!(feature = "parallel-rt");
1097    let rt_guard = rtsan_guard_tokens();
1098
1099    if ignore_resources && !sim_mode {
1100        return return_error(
1101            "`ignore_resources` is only supported when `sim_mode` is enabled".to_string(),
1102        );
1103    }
1104
1105    // Adds the generic parameter for the UnifiedLogger if this is a real application (not sim)
1106    // This allows to adapt either to the no-std (custom impl) and std (default file based one)
1107    // if !sim_mode {
1108    //     application_struct
1109    //         .generics
1110    //         .params
1111    //         .push(syn::parse_quote!(L: UnifiedLogWrite + 'static));
1112    // }
1113
1114    let resolved_runtime_config = match resolve_runtime_config(&runtime_args) {
1115        Ok(resolved_runtime_config) => resolved_runtime_config,
1116        Err(e) => return return_error(e.to_string()),
1117    };
1118    let subsystem_code = resolved_runtime_config.subsystem_code;
1119    let subsystem_id = resolved_runtime_config.subsystem_id.clone();
1120    let copper_config_content = resolved_runtime_config.bundled_local_config_content.clone();
1121    let copper_config = resolved_runtime_config.local_config;
1122    let copperlist_count = copper_config
1123        .logging
1124        .as_ref()
1125        .and_then(|logging| logging.copperlist_count)
1126        .unwrap_or(DEFAULT_CLNB);
1127    let copperlist_count_tokens = proc_macro2::Literal::usize_unsuffixed(copperlist_count);
1128    let caller_root = utils::caller_crate_root();
1129    let (git_commit, git_dirty) = detect_git_info(&caller_root);
1130    let git_commit_tokens = if let Some(commit) = git_commit {
1131        quote! { Some(#commit.to_string()) }
1132    } else {
1133        quote! { None }
1134    };
1135    let git_dirty_tokens = if let Some(dirty) = git_dirty {
1136        quote! { Some(#dirty) }
1137    } else {
1138        quote! { None }
1139    };
1140    let subsystem_code_literal = proc_macro2::Literal::u16_unsuffixed(subsystem_code);
1141    let subsystem_id_tokens = if let Some(subsystem_id) = subsystem_id.as_deref() {
1142        quote! { Some(#subsystem_id) }
1143    } else {
1144        quote! { None }
1145    };
1146
1147    #[cfg(feature = "macro_debug")]
1148    eprintln!("[build monitor type]");
1149    let monitor_configs = copper_config.get_monitor_configs();
1150    let (monitor_type, monitor_instanciator_body) = if monitor_configs.is_empty() {
1151        (
1152            quote! { NoMonitor },
1153            quote! {
1154                let monitor_metadata = metadata.with_subsystem_id(#subsystem_id_tokens);
1155                let monitor = NoMonitor::new(monitor_metadata, runtime)
1156                    .expect("Failed to create NoMonitor.");
1157                monitor
1158            },
1159        )
1160    } else if monitor_configs.len() == 1 {
1161        let only_monitor_type = parse_str::<Type>(monitor_configs[0].get_type())
1162            .expect("Could not transform the monitor type name into a Rust type.");
1163        (
1164            quote! { #only_monitor_type },
1165            quote! {
1166                let monitor_metadata = metadata.with_monitor_config(
1167                    config
1168                        .get_monitor_configs()
1169                        .first()
1170                        .and_then(|entry| entry.get_config().cloned())
1171                )
1172                .with_subsystem_id(#subsystem_id_tokens);
1173                let monitor = #only_monitor_type::new(monitor_metadata, runtime)
1174                    .expect("Failed to create the given monitor.");
1175                monitor
1176            },
1177        )
1178    } else {
1179        let monitor_types: Vec<Type> = monitor_configs
1180            .iter()
1181            .map(|monitor_config| {
1182                parse_str::<Type>(monitor_config.get_type())
1183                    .expect("Could not transform the monitor type name into a Rust type.")
1184            })
1185            .collect();
1186        let monitor_bindings: Vec<Ident> = (0..monitor_types.len())
1187            .map(|idx| format_ident!("__cu_monitor_{idx}"))
1188            .collect();
1189        let monitor_indices: Vec<syn::Index> =
1190            (0..monitor_types.len()).map(syn::Index::from).collect();
1191
1192        let monitor_builders: Vec<proc_macro2::TokenStream> = monitor_types
1193            .iter()
1194            .zip(monitor_bindings.iter())
1195            .zip(monitor_indices.iter())
1196            .map(|((monitor_ty, monitor_binding), monitor_idx)| {
1197                quote! {
1198                    let __cu_monitor_cfg_entry = config
1199                        .get_monitor_configs()
1200                        .get(#monitor_idx)
1201                        .and_then(|entry| entry.get_config().cloned());
1202                    let __cu_monitor_metadata = metadata
1203                        .clone()
1204                        .with_monitor_config(__cu_monitor_cfg_entry)
1205                        .with_subsystem_id(#subsystem_id_tokens);
1206                    let #monitor_binding = #monitor_ty::new(__cu_monitor_metadata, runtime.clone())
1207                    .expect("Failed to create one of the configured monitors.");
1208                }
1209            })
1210            .collect();
1211        let tuple_type: TypeTuple = parse_quote! { (#(#monitor_types),*,) };
1212        (
1213            quote! { #tuple_type },
1214            quote! {
1215                #(#monitor_builders)*
1216                let monitor: #tuple_type = (#(#monitor_bindings),*,);
1217                monitor
1218            },
1219        )
1220    };
1221
1222    // This is common for all the mission as it will be inserted in the respective modules with their local CuTasks, CuStampedDataSet etc...
1223    #[cfg(feature = "macro_debug")]
1224    eprintln!("[build runtime field]");
1225    // add that to a new field
1226    let runtime_field: Field = if sim_mode {
1227        parse_quote! {
1228            copper_runtime: cu29::curuntime::CuRuntime<CuSimTasks, CuBridges, CuStampedDataSet, #monitor_type, #copperlist_count_tokens>
1229        }
1230    } else {
1231        parse_quote! {
1232            copper_runtime: cu29::curuntime::CuRuntime<CuTasks, CuBridges, CuStampedDataSet, #monitor_type, #copperlist_count_tokens>
1233        }
1234    };
1235    let lifecycle_stream_field: Field = parse_quote! {
1236        runtime_lifecycle_stream: Option<Box<dyn WriteStream<RuntimeLifecycleRecord>>>
1237    };
1238    let logger_runtime_field: Field = parse_quote! {
1239        logger_runtime: cu29::prelude::LoggerRuntime
1240    };
1241
1242    #[cfg(feature = "macro_debug")]
1243    eprintln!("[match struct anonymity]");
1244    match &mut application_struct.fields {
1245        Named(fields_named) => {
1246            fields_named.named.push(runtime_field);
1247            fields_named.named.push(lifecycle_stream_field);
1248            fields_named.named.push(logger_runtime_field);
1249        }
1250        Unnamed(fields_unnamed) => {
1251            fields_unnamed.unnamed.push(runtime_field);
1252            fields_unnamed.unnamed.push(lifecycle_stream_field);
1253            fields_unnamed.unnamed.push(logger_runtime_field);
1254        }
1255        Fields::Unit => {
1256            panic!(
1257                "This struct is a unit struct, it should have named or unnamed fields. use struct Something {{}} and not struct Something;"
1258            )
1259        }
1260    };
1261
1262    let all_missions = copper_config.graphs.get_all_missions_graphs();
1263    let mut all_missions_tokens = Vec::<proc_macro2::TokenStream>::new();
1264    for (mission, graph) in &all_missions {
1265        let git_commit_tokens = git_commit_tokens.clone();
1266        let git_dirty_tokens = git_dirty_tokens.clone();
1267        let mission_mod = parse_str::<Ident>(mission.as_str())
1268            .expect("Could not make an identifier of the mission name");
1269
1270        #[cfg(feature = "macro_debug")]
1271        eprintln!("[extract tasks ids & types]");
1272        let task_specs = CuTaskSpecSet::from_graph(graph);
1273
1274        let culist_channel_usage = collect_bridge_channel_usage(graph);
1275        let mut culist_bridge_specs =
1276            build_bridge_specs(&copper_config, graph, &culist_channel_usage);
1277        let (culist_plan, culist_exec_entities, culist_plan_to_original) =
1278            match build_execution_plan(graph, &task_specs, &mut culist_bridge_specs) {
1279                Ok(plan) => plan,
1280                Err(e) => return return_error(format!("Could not compute copperlist plan: {e}")),
1281            };
1282        let task_names = collect_task_names(graph);
1283        let (culist_call_order, node_output_positions) = collect_culist_metadata(
1284            &culist_plan,
1285            &culist_exec_entities,
1286            &mut culist_bridge_specs,
1287            &culist_plan_to_original,
1288        );
1289
1290        #[cfg(feature = "macro_debug")]
1291        {
1292            eprintln!("[runtime plan for mission {mission}]");
1293            eprintln!("{culist_plan:?}");
1294        }
1295
1296        let culist_support: proc_macro2::TokenStream = gen_culist_support(
1297            &copper_config,
1298            Some(mission.as_str()),
1299            &culist_plan,
1300            &culist_call_order,
1301            &node_output_positions,
1302            &task_names,
1303            &culist_bridge_specs,
1304        );
1305
1306        let (
1307            threadpool_bundle_index,
1308            resources_module,
1309            resources_instanciator_fn,
1310            task_resource_mappings,
1311            bridge_resource_mappings,
1312        ) = if ignore_resources {
1313            if task_specs.background_flags.iter().any(|&flag| flag) {
1314                return return_error(
1315                    "`ignore_resources` cannot be used with background tasks because they require the threadpool resource bundle"
1316                        .to_string(),
1317                );
1318            }
1319
1320            let bundle_specs: Vec<BundleSpec> = Vec::new();
1321            let resource_specs: Vec<ResourceKeySpec> = Vec::new();
1322            let (resources_module, resources_instanciator_fn) =
1323                match build_resources_module(&bundle_specs) {
1324                    Ok(tokens) => tokens,
1325                    Err(e) => return return_error(e.to_string()),
1326                };
1327            let task_resource_mappings =
1328                match build_task_resource_mappings(&resource_specs, &task_specs) {
1329                    Ok(tokens) => tokens,
1330                    Err(e) => return return_error(e.to_string()),
1331                };
1332            let bridge_resource_mappings =
1333                build_bridge_resource_mappings(&resource_specs, &culist_bridge_specs, sim_mode);
1334            (
1335                None,
1336                resources_module,
1337                resources_instanciator_fn,
1338                task_resource_mappings,
1339                bridge_resource_mappings,
1340            )
1341        } else {
1342            let bundle_specs = match build_bundle_specs(&copper_config, mission.as_str()) {
1343                Ok(specs) => specs,
1344                Err(e) => return return_error(e.to_string()),
1345            };
1346            let threadpool_bundle_index = if task_specs.background_flags.iter().any(|&flag| flag) {
1347                match bundle_specs
1348                    .iter()
1349                    .position(|bundle| bundle.id == "threadpool")
1350                {
1351                    Some(index) => Some(index),
1352                    None => {
1353                        return return_error(
1354                            "Background tasks require the threadpool bundle to be configured"
1355                                .to_string(),
1356                        );
1357                    }
1358                }
1359            } else {
1360                None
1361            };
1362
1363            let resource_specs = match collect_resource_specs(
1364                graph,
1365                &task_specs,
1366                &culist_bridge_specs,
1367                &bundle_specs,
1368            ) {
1369                Ok(specs) => specs,
1370                Err(e) => return return_error(e.to_string()),
1371            };
1372
1373            let (resources_module, resources_instanciator_fn) =
1374                match build_resources_module(&bundle_specs) {
1375                    Ok(tokens) => tokens,
1376                    Err(e) => return return_error(e.to_string()),
1377                };
1378            let task_resource_mappings =
1379                match build_task_resource_mappings(&resource_specs, &task_specs) {
1380                    Ok(tokens) => tokens,
1381                    Err(e) => return return_error(e.to_string()),
1382                };
1383            let bridge_resource_mappings =
1384                build_bridge_resource_mappings(&resource_specs, &culist_bridge_specs, sim_mode);
1385            (
1386                threadpool_bundle_index,
1387                resources_module,
1388                resources_instanciator_fn,
1389                task_resource_mappings,
1390                bridge_resource_mappings,
1391            )
1392        };
1393
1394        let task_ids = task_specs.ids.clone();
1395        let ids = build_monitored_ids(&task_ids, &mut culist_bridge_specs);
1396        let parallel_rt_stage_entries = match build_parallel_rt_stage_entries(
1397            &culist_plan,
1398            &culist_exec_entities,
1399            &task_specs,
1400            &culist_bridge_specs,
1401        ) {
1402            Ok(entries) => entries,
1403            Err(e) => return return_error(e.to_string()),
1404        };
1405        let parallel_rt_metadata_defs = if std && parallel_rt_enabled {
1406            Some(quote! {
1407                pub const PARALLEL_RT_STAGES: &'static [cu29::parallel_rt::ParallelRtStageMetadata] =
1408                    &[#( #parallel_rt_stage_entries ),*];
1409                pub const PARALLEL_RT_METADATA: cu29::parallel_rt::ParallelRtMetadata =
1410                    cu29::parallel_rt::ParallelRtMetadata::new(PARALLEL_RT_STAGES);
1411            })
1412        } else {
1413            None
1414        };
1415        let monitored_component_entries: Vec<proc_macro2::TokenStream> = ids
1416            .iter()
1417            .enumerate()
1418            .map(|(idx, id)| {
1419                let id_lit = LitStr::new(id, Span::call_site());
1420                if idx < task_specs.task_types.len() {
1421                    let task_ty = &task_specs.task_types[idx];
1422                    let component_type = match task_specs.cutypes[idx] {
1423                        CuTaskType::Source => quote! { cu29::monitoring::ComponentType::Source },
1424                        CuTaskType::Regular => quote! { cu29::monitoring::ComponentType::Task },
1425                        CuTaskType::Sink => quote! { cu29::monitoring::ComponentType::Sink },
1426                    };
1427                    quote! {
1428                        cu29::monitoring::MonitorComponentMetadata::new(
1429                            #id_lit,
1430                            #component_type,
1431                            Some(stringify!(#task_ty)),
1432                        )
1433                    }
1434                } else {
1435                    quote! {
1436                        cu29::monitoring::MonitorComponentMetadata::new(
1437                            #id_lit,
1438                            cu29::monitoring::ComponentType::Bridge,
1439                            None,
1440                        )
1441                    }
1442                }
1443            })
1444            .collect();
1445        let culist_component_mapping = match build_monitor_culist_component_mapping(
1446            &culist_plan,
1447            &culist_exec_entities,
1448            &culist_bridge_specs,
1449        ) {
1450            Ok(mapping) => mapping,
1451            Err(e) => return return_error(e),
1452        };
1453
1454        let task_reflect_read_arms: Vec<proc_macro2::TokenStream> = task_specs
1455            .ids
1456            .iter()
1457            .enumerate()
1458            .map(|(index, task_id)| {
1459                let task_index = syn::Index::from(index);
1460                let task_id_lit = LitStr::new(task_id, Span::call_site());
1461                quote! {
1462                    #task_id_lit => Some(&self.copper_runtime.tasks.#task_index as &dyn cu29::reflect::Reflect),
1463                }
1464            })
1465            .collect();
1466
1467        let task_reflect_write_arms: Vec<proc_macro2::TokenStream> = task_specs
1468            .ids
1469            .iter()
1470            .enumerate()
1471            .map(|(index, task_id)| {
1472                let task_index = syn::Index::from(index);
1473                let task_id_lit = LitStr::new(task_id, Span::call_site());
1474                quote! {
1475                    #task_id_lit => Some(&mut self.copper_runtime.tasks.#task_index as &mut dyn cu29::reflect::Reflect),
1476                }
1477            })
1478            .collect();
1479
1480        let mut reflect_registry_types: BTreeMap<String, Type> = BTreeMap::new();
1481        let mut add_reflect_type = |ty: Type| {
1482            let key = quote! { #ty }.to_string();
1483            reflect_registry_types.entry(key).or_insert(ty);
1484        };
1485
1486        for task_type in &task_specs.task_types {
1487            add_reflect_type(task_type.clone());
1488        }
1489
1490        let mut sim_bridge_channel_decls = Vec::<proc_macro2::TokenStream>::new();
1491        let bridge_runtime_types: Vec<Type> = culist_bridge_specs
1492            .iter()
1493            .map(|spec| {
1494                if sim_mode && !spec.run_in_sim {
1495                    let (tx_set_ident, tx_id_ident, rx_set_ident, rx_id_ident) =
1496                        sim_bridge_channel_set_idents(spec.tuple_index);
1497
1498                    if !spec.tx_channels.is_empty() {
1499                        let tx_entries = spec.tx_channels.iter().map(|channel| {
1500                            let entry_ident = Ident::new(
1501                                &channel.const_ident.to_string().to_lowercase(),
1502                                Span::call_site(),
1503                            );
1504                            let msg_type = &channel.msg_type;
1505                            quote! { #entry_ident => #msg_type, }
1506                        });
1507                        sim_bridge_channel_decls.push(quote! {
1508                            cu29::tx_channels! {
1509                                pub struct #tx_set_ident : #tx_id_ident {
1510                                    #(#tx_entries)*
1511                                }
1512                            }
1513                        });
1514                    }
1515
1516                    if !spec.rx_channels.is_empty() {
1517                        let rx_entries = spec.rx_channels.iter().map(|channel| {
1518                            let entry_ident = Ident::new(
1519                                &channel.const_ident.to_string().to_lowercase(),
1520                                Span::call_site(),
1521                            );
1522                            let msg_type = &channel.msg_type;
1523                            quote! { #entry_ident => #msg_type, }
1524                        });
1525                        sim_bridge_channel_decls.push(quote! {
1526                            cu29::rx_channels! {
1527                                pub struct #rx_set_ident : #rx_id_ident {
1528                                    #(#rx_entries)*
1529                                }
1530                            }
1531                        });
1532                    }
1533                }
1534                runtime_bridge_type_for_spec(spec, sim_mode)
1535            })
1536            .collect();
1537        let sim_bridge_channel_defs = quote! { #(#sim_bridge_channel_decls)* };
1538
1539        for (bridge_index, bridge_spec) in culist_bridge_specs.iter().enumerate() {
1540            add_reflect_type(bridge_runtime_types[bridge_index].clone());
1541            for channel in bridge_spec
1542                .rx_channels
1543                .iter()
1544                .chain(bridge_spec.tx_channels.iter())
1545            {
1546                add_reflect_type(channel.msg_type.clone());
1547            }
1548        }
1549
1550        for output_pack in extract_output_packs(&culist_plan) {
1551            for msg_type in output_pack.msg_types {
1552                add_reflect_type(msg_type);
1553            }
1554        }
1555
1556        let reflect_type_registration_calls: Vec<proc_macro2::TokenStream> = reflect_registry_types
1557            .values()
1558            .map(|ty| {
1559                quote! {
1560                    registry.register::<#ty>();
1561                }
1562            })
1563            .collect();
1564
1565        let bridges_type_tokens: proc_macro2::TokenStream = if bridge_runtime_types.is_empty() {
1566            quote! { () }
1567        } else {
1568            let bridge_types_for_tuple = bridge_runtime_types.clone();
1569            let tuple: TypeTuple = parse_quote! { (#(#bridge_types_for_tuple),*,) };
1570            quote! { #tuple }
1571        };
1572
1573        let bridge_binding_idents: Vec<Ident> = culist_bridge_specs
1574            .iter()
1575            .enumerate()
1576            .map(|(idx, _)| format_ident!("bridge_{idx}"))
1577            .collect();
1578
1579        let bridge_init_statements: Vec<proc_macro2::TokenStream> = culist_bridge_specs
1580            .iter()
1581            .enumerate()
1582            .map(|(idx, spec)| {
1583                let binding_ident = &bridge_binding_idents[idx];
1584                let bridge_mapping_ref = bridge_resource_mappings.refs[idx].clone();
1585                let bridge_type = &bridge_runtime_types[idx];
1586                let bridge_name = spec.id.clone();
1587                let config_index = syn::Index::from(spec.config_index);
1588                let binding_error = LitStr::new(
1589                    &format!("Failed to bind resources for bridge '{}'", bridge_name),
1590                    Span::call_site(),
1591                );
1592                let tx_configs: Vec<proc_macro2::TokenStream> = spec
1593                    .tx_channels
1594                    .iter()
1595                    .map(|channel| {
1596                        let const_ident = &channel.const_ident;
1597                        let channel_name = channel.id.clone();
1598                        let channel_config_index = syn::Index::from(channel.config_index);
1599                        quote! {
1600                            {
1601                        let (channel_route, channel_config) = match &bridge_cfg.channels[#channel_config_index] {
1602                            cu29::config::BridgeChannelConfigRepresentation::Tx { route, config, .. } => {
1603                                (route.clone(), config.clone())
1604                                    }
1605                                    _ => panic!(
1606                                        "Bridge '{}' channel '{}' expected to be Tx",
1607                                        #bridge_name,
1608                                        #channel_name
1609                                    ),
1610                                };
1611                                cu29::cubridge::BridgeChannelConfig::from_static(
1612                                    &<#bridge_type as cu29::cubridge::CuBridge>::Tx::#const_ident,
1613                                    channel_route,
1614                                    channel_config,
1615                                )
1616                            }
1617                        }
1618                    })
1619                    .collect();
1620                let rx_configs: Vec<proc_macro2::TokenStream> = spec
1621                    .rx_channels
1622                    .iter()
1623                    .map(|channel| {
1624                        let const_ident = &channel.const_ident;
1625                        let channel_name = channel.id.clone();
1626                        let channel_config_index = syn::Index::from(channel.config_index);
1627                        quote! {
1628                            {
1629                                let (channel_route, channel_config) = match &bridge_cfg.channels[#channel_config_index] {
1630                                    cu29::config::BridgeChannelConfigRepresentation::Rx { route, config, .. } => {
1631                                        (route.clone(), config.clone())
1632                                    }
1633                                    _ => panic!(
1634                                        "Bridge '{}' channel '{}' expected to be Rx",
1635                                        #bridge_name,
1636                                        #channel_name
1637                                    ),
1638                                };
1639                                cu29::cubridge::BridgeChannelConfig::from_static(
1640                                    &<#bridge_type as cu29::cubridge::CuBridge>::Rx::#const_ident,
1641                                    channel_route,
1642                                    channel_config,
1643                                )
1644                            }
1645                        }
1646                    })
1647                    .collect();
1648                quote! {
1649                    let #binding_ident = {
1650                        let bridge_cfg = config
1651                            .bridges
1652                            .get(#config_index)
1653                            .unwrap_or_else(|| panic!("Bridge '{}' missing from configuration", #bridge_name));
1654                        let bridge_mapping = #bridge_mapping_ref;
1655                        let bridge_resources = <<#bridge_type as cu29::cubridge::CuBridge>::Resources<'_> as ResourceBindings>::from_bindings(
1656                            resources,
1657                            bridge_mapping,
1658                        )
1659                        .map_err(|e| cu29::CuError::new_with_cause(#binding_error, e))?;
1660                        let tx_channels: &[cu29::cubridge::BridgeChannelConfig<
1661                            <<#bridge_type as cu29::cubridge::CuBridge>::Tx as cu29::cubridge::BridgeChannelSet>::Id,
1662                        >] = &[#(#tx_configs),*];
1663                        let rx_channels: &[cu29::cubridge::BridgeChannelConfig<
1664                            <<#bridge_type as cu29::cubridge::CuBridge>::Rx as cu29::cubridge::BridgeChannelSet>::Id,
1665                        >] = &[#(#rx_configs),*];
1666                        <#bridge_type as cu29::cubridge::CuBridge>::new(
1667                            bridge_cfg.config.as_ref(),
1668                            tx_channels,
1669                            rx_channels,
1670                            bridge_resources,
1671                        )?
1672                    };
1673                }
1674            })
1675            .collect();
1676
1677        let bridges_instanciator = if culist_bridge_specs.is_empty() {
1678            quote! {
1679                pub fn bridges_instanciator(_config: &CuConfig, resources: &mut ResourceManager) -> CuResult<CuBridges> {
1680                    let _ = resources;
1681                    Ok(())
1682                }
1683            }
1684        } else {
1685            let bridge_bindings = bridge_binding_idents.clone();
1686            quote! {
1687                pub fn bridges_instanciator(config: &CuConfig, resources: &mut ResourceManager) -> CuResult<CuBridges> {
1688                    #(#bridge_init_statements)*
1689                    Ok((#(#bridge_bindings),*,))
1690                }
1691            }
1692        };
1693
1694        let all_sim_tasks_types: Vec<Type> = task_specs
1695            .ids
1696            .iter()
1697            .zip(&task_specs.cutypes)
1698            .zip(&task_specs.sim_task_types)
1699            .zip(&task_specs.background_flags)
1700            .zip(&task_specs.run_in_sim_flags)
1701            .zip(task_specs.output_types.iter())
1702            .map(|(((((task_id, task_type), sim_type), background), run_in_sim), output_type)| {
1703                match task_type {
1704                    CuTaskType::Source => {
1705                        if *background {
1706                            if let Some(out_ty) = output_type {
1707                                parse_quote!(CuAsyncSrcTask<#sim_type, #out_ty>)
1708                            } else {
1709                                panic!("{task_id}: If a source is background, it has to have an output");
1710                            }
1711                        } else if *run_in_sim {
1712                            sim_type.clone()
1713                        } else {
1714                            let msg_type = graph
1715                                .get_node_output_msg_type(task_id.as_str())
1716                                .unwrap_or_else(|| panic!("CuSrcTask {task_id} should have an outgoing connection with a valid output msg type"));
1717                            let sim_task_name = format!("CuSimSrcTask<{msg_type}>");
1718                            parse_str(sim_task_name.as_str()).unwrap_or_else(|_| panic!("Could not build the placeholder for simulation: {sim_task_name}"))
1719                        }
1720                    }
1721                    CuTaskType::Regular => {
1722                        if *background {
1723                            if let Some(out_ty) = output_type {
1724                                parse_quote!(CuAsyncTask<#sim_type, #out_ty>)
1725                            } else {
1726                                panic!("{task_id}: If a task is background, it has to have an output");
1727                            }
1728                        } else {
1729                            // run_in_sim has no effect for normal tasks, they are always run in sim as is.
1730                            sim_type.clone()
1731                        }
1732                    },
1733                    CuTaskType::Sink => {
1734                        if *background {
1735                            panic!("CuSinkTask {task_id} cannot be a background task, it should be a regular task.");
1736                        }
1737
1738                        if *run_in_sim {
1739                            // Use the real task in sim if asked to.
1740                            sim_type.clone()
1741                        }
1742                        else {
1743                            // Use the placeholder sim task.
1744                            let msg_types = graph
1745                                .get_node_input_msg_types(task_id.as_str())
1746                                .unwrap_or_else(|| panic!("CuSinkTask {task_id} should have an incoming connection with a valid input msg type"));
1747                            let msg_type = if msg_types.len() == 1 {
1748                                format!("({},)", msg_types[0])
1749                            } else {
1750                                format!("({})", msg_types.join(", "))
1751                            };
1752                            let sim_task_name = format!("CuSimSinkTask<{msg_type}>");
1753                            parse_str(sim_task_name.as_str()).unwrap_or_else(|_| panic!("Could not build the placeholder for simulation: {sim_task_name}"))
1754                        }
1755                    }
1756                }
1757            })
1758            .collect();
1759
1760        #[cfg(feature = "macro_debug")]
1761        eprintln!("[build task tuples]");
1762
1763        let task_types = &task_specs.task_types;
1764        // Build the tuple of all those types
1765        // note the extraneous, at the end is to make the tuple work even if this is only one element
1766        let task_types_tuple: TypeTuple = if task_types.is_empty() {
1767            parse_quote! { () }
1768        } else {
1769            parse_quote! { (#(#task_types),*,) }
1770        };
1771
1772        let task_types_tuple_sim: TypeTuple = if all_sim_tasks_types.is_empty() {
1773            parse_quote! { () }
1774        } else {
1775            parse_quote! { (#(#all_sim_tasks_types),*,) }
1776        };
1777
1778        #[cfg(feature = "macro_debug")]
1779        eprintln!("[gen instances]");
1780        let task_sim_instances_init_code = all_sim_tasks_types
1781            .iter()
1782            .enumerate()
1783            .map(|(index, ty)| {
1784                let additional_error_info = format!(
1785                    "Failed to get create instance for {}, instance index {}.",
1786                    task_specs.type_names[index], index
1787                );
1788                let mapping_ref = task_resource_mappings.refs[index].clone();
1789                let background = task_specs.background_flags[index];
1790                let inner_task_type = &task_specs.sim_task_types[index];
1791                match task_specs.cutypes[index] {
1792                    CuTaskType::Source => {
1793                        if background {
1794                            let threadpool_bundle_index = threadpool_bundle_index
1795                                .expect("threadpool bundle missing for background tasks");
1796                            quote! {
1797                                {
1798                                    let inner_resources = <<#inner_task_type as CuSrcTask>::Resources<'_> as ResourceBindings>::from_bindings(
1799                                        resources,
1800                                        #mapping_ref,
1801                                    ).map_err(|e| e.add_cause(#additional_error_info))?;
1802                                    let threadpool_key = cu29::resource::ResourceKey::new(
1803                                        cu29::resource::BundleIndex::new(#threadpool_bundle_index),
1804                                        <cu29::resource::ThreadPoolBundle as cu29::resource::ResourceBundleDecl>::Id::BgThreads as usize,
1805                                    );
1806                                    let threadpool = resources.borrow_shared_arc(threadpool_key)?;
1807                                    let resources = cu29::cuasynctask::CuAsyncSrcTaskResources {
1808                                        inner: inner_resources,
1809                                        threadpool,
1810                                    };
1811                                    <#ty as CuSrcTask>::new(all_instances_configs[#index], resources)
1812                                        .map_err(|e| e.add_cause(#additional_error_info))?
1813                                }
1814                            }
1815                        } else {
1816                            quote! {
1817                                {
1818                                    let resources = <<#ty as CuSrcTask>::Resources<'_> as ResourceBindings>::from_bindings(
1819                                        resources,
1820                                        #mapping_ref,
1821                                    ).map_err(|e| e.add_cause(#additional_error_info))?;
1822                                    <#ty as CuSrcTask>::new(all_instances_configs[#index], resources)
1823                                        .map_err(|e| e.add_cause(#additional_error_info))?
1824                                }
1825                            }
1826                        }
1827                    }
1828                    CuTaskType::Regular => {
1829                        if background {
1830                            let threadpool_bundle_index = threadpool_bundle_index
1831                                .expect("threadpool bundle missing for background tasks");
1832                            quote! {
1833                                {
1834                                    let inner_resources = <<#inner_task_type as CuTask>::Resources<'_> as ResourceBindings>::from_bindings(
1835                                        resources,
1836                                        #mapping_ref,
1837                                    ).map_err(|e| e.add_cause(#additional_error_info))?;
1838                                    let threadpool_key = cu29::resource::ResourceKey::new(
1839                                        cu29::resource::BundleIndex::new(#threadpool_bundle_index),
1840                                        <cu29::resource::ThreadPoolBundle as cu29::resource::ResourceBundleDecl>::Id::BgThreads as usize,
1841                                    );
1842                                    let threadpool = resources.borrow_shared_arc(threadpool_key)?;
1843                                    let resources = cu29::cuasynctask::CuAsyncTaskResources {
1844                                        inner: inner_resources,
1845                                        threadpool,
1846                                    };
1847                                    <#ty as CuTask>::new(all_instances_configs[#index], resources)
1848                                        .map_err(|e| e.add_cause(#additional_error_info))?
1849                                }
1850                            }
1851                        } else {
1852                            quote! {
1853                                {
1854                                    let resources = <<#ty as CuTask>::Resources<'_> as ResourceBindings>::from_bindings(
1855                                        resources,
1856                                        #mapping_ref,
1857                                    ).map_err(|e| e.add_cause(#additional_error_info))?;
1858                                    <#ty as CuTask>::new(all_instances_configs[#index], resources)
1859                                        .map_err(|e| e.add_cause(#additional_error_info))?
1860                                }
1861                            }
1862                        }
1863                    }
1864                    CuTaskType::Sink => quote! {
1865                        {
1866                            let resources = <<#ty as CuSinkTask>::Resources<'_> as ResourceBindings>::from_bindings(
1867                                resources,
1868                                #mapping_ref,
1869                            ).map_err(|e| e.add_cause(#additional_error_info))?;
1870                            <#ty as CuSinkTask>::new(all_instances_configs[#index], resources)
1871                                .map_err(|e| e.add_cause(#additional_error_info))?
1872                        }
1873                    },
1874                }
1875            })
1876            .collect::<Vec<_>>();
1877
1878        let task_instances_init_code = task_specs
1879            .instantiation_types
1880            .iter()
1881            .zip(&task_specs.background_flags)
1882            .enumerate()
1883            .map(|(index, (task_type, background))| {
1884                let additional_error_info = format!(
1885                    "Failed to get create instance for {}, instance index {}.",
1886                    task_specs.type_names[index], index
1887                );
1888                let mapping_ref = task_resource_mappings.refs[index].clone();
1889                let inner_task_type = &task_specs.sim_task_types[index];
1890                match task_specs.cutypes[index] {
1891                    CuTaskType::Source => {
1892                        if *background {
1893                            let threadpool_bundle_index = threadpool_bundle_index
1894                                .expect("threadpool bundle missing for background tasks");
1895                            quote! {
1896                                {
1897                                    let inner_resources = <<#inner_task_type as CuSrcTask>::Resources<'_> as ResourceBindings>::from_bindings(
1898                                        resources,
1899                                        #mapping_ref,
1900                                    ).map_err(|e| e.add_cause(#additional_error_info))?;
1901                                    let threadpool_key = cu29::resource::ResourceKey::new(
1902                                        cu29::resource::BundleIndex::new(#threadpool_bundle_index),
1903                                        <cu29::resource::ThreadPoolBundle as cu29::resource::ResourceBundleDecl>::Id::BgThreads as usize,
1904                                    );
1905                                    let threadpool = resources.borrow_shared_arc(threadpool_key)?;
1906                                    let resources = cu29::cuasynctask::CuAsyncSrcTaskResources {
1907                                        inner: inner_resources,
1908                                        threadpool,
1909                                    };
1910                                    <#task_type as CuSrcTask>::new(all_instances_configs[#index], resources)
1911                                        .map_err(|e| e.add_cause(#additional_error_info))?
1912                                }
1913                            }
1914                        } else {
1915                            quote! {
1916                                {
1917                                    let resources = <<#task_type as CuSrcTask>::Resources<'_> as ResourceBindings>::from_bindings(
1918                                        resources,
1919                                        #mapping_ref,
1920                                    ).map_err(|e| e.add_cause(#additional_error_info))?;
1921                                    <#task_type as CuSrcTask>::new(all_instances_configs[#index], resources)
1922                                        .map_err(|e| e.add_cause(#additional_error_info))?
1923                                }
1924                            }
1925                        }
1926                    }
1927                    CuTaskType::Regular => {
1928                        if *background {
1929                            let threadpool_bundle_index = threadpool_bundle_index
1930                                .expect("threadpool bundle missing for background tasks");
1931                            quote! {
1932                                {
1933                                    let inner_resources = <<#inner_task_type as CuTask>::Resources<'_> as ResourceBindings>::from_bindings(
1934                                        resources,
1935                                        #mapping_ref,
1936                                    ).map_err(|e| e.add_cause(#additional_error_info))?;
1937                                    let threadpool_key = cu29::resource::ResourceKey::new(
1938                                        cu29::resource::BundleIndex::new(#threadpool_bundle_index),
1939                                        <cu29::resource::ThreadPoolBundle as cu29::resource::ResourceBundleDecl>::Id::BgThreads as usize,
1940                                    );
1941                                    let threadpool = resources.borrow_shared_arc(threadpool_key)?;
1942                                    let resources = cu29::cuasynctask::CuAsyncTaskResources {
1943                                        inner: inner_resources,
1944                                        threadpool,
1945                                    };
1946                                    <#task_type as CuTask>::new(all_instances_configs[#index], resources)
1947                                        .map_err(|e| e.add_cause(#additional_error_info))?
1948                                }
1949                            }
1950                        } else {
1951                            quote! {
1952                                {
1953                                    let resources = <<#task_type as CuTask>::Resources<'_> as ResourceBindings>::from_bindings(
1954                                        resources,
1955                                        #mapping_ref,
1956                                    ).map_err(|e| e.add_cause(#additional_error_info))?;
1957                                    <#task_type as CuTask>::new(all_instances_configs[#index], resources)
1958                                        .map_err(|e| e.add_cause(#additional_error_info))?
1959                                }
1960                            }
1961                        }
1962                    }
1963                    CuTaskType::Sink => quote! {
1964                        {
1965                            let resources = <<#task_type as CuSinkTask>::Resources<'_> as ResourceBindings>::from_bindings(
1966                                resources,
1967                                #mapping_ref,
1968                            ).map_err(|e| e.add_cause(#additional_error_info))?;
1969                            <#task_type as CuSinkTask>::new(all_instances_configs[#index], resources)
1970                                .map_err(|e| e.add_cause(#additional_error_info))?
1971                        }
1972                    },
1973                }
1974            })
1975            .collect::<Vec<_>>();
1976
1977        let mut keyframe_task_restore_order = Vec::new();
1978        for unit in &culist_plan.steps {
1979            let CuExecutionUnit::Step(step) = unit else {
1980                panic!("Execution loops are not supported in runtime generation");
1981            };
1982            let ExecutionEntityKind::Task { task_index } =
1983                &culist_exec_entities[step.node_id as usize].kind
1984            else {
1985                continue;
1986            };
1987            if !keyframe_task_restore_order.contains(task_index) {
1988                keyframe_task_restore_order.push(*task_index);
1989            }
1990        }
1991        if keyframe_task_restore_order.len() != task_specs.task_types.len() {
1992            return return_error(format!(
1993                "Keyframe restore order covers {} task steps but mission declares {} tasks",
1994                keyframe_task_restore_order.len(),
1995                task_specs.task_types.len()
1996            ));
1997        }
1998        let task_restore_code: Vec<proc_macro2::TokenStream> = keyframe_task_restore_order
1999            .iter()
2000            .map(|index| {
2001                let task_tuple_index = syn::Index::from(*index);
2002                quote! {
2003                    tasks.#task_tuple_index.thaw(&mut decoder).map_err(|e| CuError::from("Failed to thaw").add_cause(&e.to_string()))?
2004                }
2005            })
2006            .collect();
2007
2008        // Generate the code to create instances of the nodes
2009        // It maps the types to their index
2010        let (
2011            task_start_calls,
2012            task_stop_calls,
2013            task_preprocess_calls,
2014            task_postprocess_calls,
2015        ): (Vec<_>, Vec<_>, Vec<_>, Vec<_>) = itertools::multiunzip(
2016            (0..task_specs.task_types.len())
2017            .map(|index| {
2018                let task_index = int2sliceindex(index as u32);
2019                let task_enum_name = config_id_to_enum(&task_specs.ids[index]);
2020                let enum_name = Ident::new(&task_enum_name, Span::call_site());
2021                (
2022                    {  // Start calls
2023                        let monitoring_action = quote! {
2024                            let decision = self.copper_runtime.monitor.process_error(cu29::monitoring::ComponentId::new(#index), CuComponentState::Start, &error);
2025                            match decision {
2026                                Decision::Abort => {
2027                                    debug!("Start: ABORT decision from monitoring. Component '{}' errored out \
2028                                during start. Aborting all the other starts.", #mission_mod::monitor_component_label(cu29::monitoring::ComponentId::new(#index)));
2029                                    return Ok(());
2030
2031                                }
2032                                Decision::Ignore => {
2033                                    debug!("Start: IGNORE decision from monitoring. Component '{}' errored out \
2034                                during start. The runtime will continue.", #mission_mod::monitor_component_label(cu29::monitoring::ComponentId::new(#index)));
2035                                }
2036                                Decision::Shutdown => {
2037                                    debug!("Start: SHUTDOWN decision from monitoring. Component '{}' errored out \
2038                                during start. The runtime cannot continue.", #mission_mod::monitor_component_label(cu29::monitoring::ComponentId::new(#index)));
2039                                    return Err(CuError::new_with_cause("Component errored out during start.", error));
2040                                }
2041                            }
2042                        };
2043
2044                        let call_sim_callback = if sim_mode {
2045                            quote! {
2046                                // Ask the sim if this task should be executed or overridden by the sim.
2047                                let ovr = sim_callback(SimStep::#enum_name(CuTaskCallbackState::Start));
2048
2049                                let doit = if let SimOverride::Errored(reason) = ovr  {
2050                                    let error: CuError = reason.into();
2051                                    #monitoring_action
2052                                    false
2053                               }
2054                               else {
2055                                    ovr == SimOverride::ExecuteByRuntime
2056                               };
2057                            }
2058                        } else {
2059                            quote! {
2060                                let doit = true;  // in normal mode always execute the steps in the runtime.
2061                            }
2062                        };
2063
2064
2065                        quote! {
2066                            #call_sim_callback
2067                            if doit {
2068                                self.copper_runtime.record_execution_marker(
2069                                    cu29::monitoring::ExecutionMarker {
2070                                        component_id: cu29::monitoring::ComponentId::new(#index),
2071                                        step: CuComponentState::Start,
2072                                        culistid: None,
2073                                    }
2074                                );
2075                                let task = &mut self.copper_runtime.tasks.#task_index;
2076                                ctx.set_current_task(#index);
2077                                if let Err(error) = task.start(&ctx) {
2078                                    #monitoring_action
2079                                }
2080                            }
2081                        }
2082                    },
2083                    {  // Stop calls
2084                        let monitoring_action = quote! {
2085                                    let decision = self.copper_runtime.monitor.process_error(cu29::monitoring::ComponentId::new(#index), CuComponentState::Stop, &error);
2086                                    match decision {
2087                                        Decision::Abort => {
2088                                            debug!("Stop: ABORT decision from monitoring. Component '{}' errored out \
2089                                    during stop. Aborting all the other starts.", #mission_mod::monitor_component_label(cu29::monitoring::ComponentId::new(#index)));
2090                                            return Ok(());
2091
2092                                        }
2093                                        Decision::Ignore => {
2094                                            debug!("Stop: IGNORE decision from monitoring. Component '{}' errored out \
2095                                    during stop. The runtime will continue.", #mission_mod::monitor_component_label(cu29::monitoring::ComponentId::new(#index)));
2096                                        }
2097                                        Decision::Shutdown => {
2098                                            debug!("Stop: SHUTDOWN decision from monitoring. Component '{}' errored out \
2099                                    during stop. The runtime cannot continue.", #mission_mod::monitor_component_label(cu29::monitoring::ComponentId::new(#index)));
2100                                            return Err(CuError::new_with_cause("Component errored out during stop.", error));
2101                                        }
2102                                    }
2103                            };
2104                        let call_sim_callback = if sim_mode {
2105                            quote! {
2106                                // Ask the sim if this task should be executed or overridden by the sim.
2107                                let ovr = sim_callback(SimStep::#enum_name(CuTaskCallbackState::Stop));
2108
2109                                let doit = if let SimOverride::Errored(reason) = ovr  {
2110                                    let error: CuError = reason.into();
2111                                    #monitoring_action
2112                                    false
2113                               }
2114                               else {
2115                                    ovr == SimOverride::ExecuteByRuntime
2116                               };
2117                            }
2118                        } else {
2119                            quote! {
2120                                let doit = true;  // in normal mode always execute the steps in the runtime.
2121                            }
2122                        };
2123                        quote! {
2124                            #call_sim_callback
2125                            if doit {
2126                                self.copper_runtime.record_execution_marker(
2127                                    cu29::monitoring::ExecutionMarker {
2128                                        component_id: cu29::monitoring::ComponentId::new(#index),
2129                                        step: CuComponentState::Stop,
2130                                        culistid: None,
2131                                    }
2132                                );
2133                                let task = &mut self.copper_runtime.tasks.#task_index;
2134                                ctx.set_current_task(#index);
2135                                if let Err(error) = task.stop(&ctx) {
2136                                    #monitoring_action
2137                                }
2138                            }
2139                        }
2140                    },
2141                    {  // Preprocess calls
2142                        let monitoring_action = quote! {
2143                            let decision = monitor.process_error(cu29::monitoring::ComponentId::new(#index), CuComponentState::Preprocess, &error);
2144                            match decision {
2145                                Decision::Abort => {
2146                                    debug!("Preprocess: ABORT decision from monitoring. Component '{}' errored out \
2147                                during preprocess. Aborting all the other starts.", #mission_mod::monitor_component_label(cu29::monitoring::ComponentId::new(#index)));
2148                                    return Ok(());
2149
2150                                }
2151                                Decision::Ignore => {
2152                                    debug!("Preprocess: IGNORE decision from monitoring. Component '{}' errored out \
2153                                during preprocess. The runtime will continue.", #mission_mod::monitor_component_label(cu29::monitoring::ComponentId::new(#index)));
2154                                }
2155                                Decision::Shutdown => {
2156                                    debug!("Preprocess: SHUTDOWN decision from monitoring. Component '{}' errored out \
2157                                during preprocess. The runtime cannot continue.", #mission_mod::monitor_component_label(cu29::monitoring::ComponentId::new(#index)));
2158                                    return Err(CuError::new_with_cause("Component errored out during preprocess.", error));
2159                                }
2160                            }
2161                        };
2162                        let call_sim_callback = if sim_mode {
2163                            quote! {
2164                                // Ask the sim if this task should be executed or overridden by the sim.
2165                                let ovr = sim_callback(SimStep::#enum_name(CuTaskCallbackState::Preprocess));
2166
2167                                let doit = if let SimOverride::Errored(reason) = ovr  {
2168                                    let error: CuError = reason.into();
2169                                    #monitoring_action
2170                                    false
2171                                } else {
2172                                    ovr == SimOverride::ExecuteByRuntime
2173                                };
2174                            }
2175                        } else {
2176                            quote! {
2177                                let doit = true;  // in normal mode always execute the steps in the runtime.
2178                            }
2179                        };
2180                        quote! {
2181                            #call_sim_callback
2182                            if doit {
2183                                execution_probe.record(cu29::monitoring::ExecutionMarker {
2184                                    component_id: cu29::monitoring::ComponentId::new(#index),
2185                                    step: CuComponentState::Preprocess,
2186                                    culistid: None,
2187                                });
2188                                ctx.set_current_task(#index);
2189                                let maybe_error = {
2190                                    #rt_guard
2191                                    tasks.#task_index.preprocess(&ctx)
2192                                };
2193                                if let Err(error) = maybe_error {
2194                                    #monitoring_action
2195                                }
2196                            }
2197                        }
2198                    },
2199                    {  // Postprocess calls
2200                        let monitoring_action = quote! {
2201                            let decision = monitor.process_error(cu29::monitoring::ComponentId::new(#index), CuComponentState::Postprocess, &error);
2202                            match decision {
2203                                Decision::Abort => {
2204                                    debug!("Postprocess: ABORT decision from monitoring. Component '{}' errored out \
2205                                during postprocess. Aborting all the other starts.", #mission_mod::monitor_component_label(cu29::monitoring::ComponentId::new(#index)));
2206                                    return Ok(());
2207
2208                                }
2209                                Decision::Ignore => {
2210                                    debug!("Postprocess: IGNORE decision from monitoring. Component '{}' errored out \
2211                                during postprocess. The runtime will continue.", #mission_mod::monitor_component_label(cu29::monitoring::ComponentId::new(#index)));
2212                                }
2213                                Decision::Shutdown => {
2214                                    debug!("Postprocess: SHUTDOWN decision from monitoring. Component '{}' errored out \
2215                                during postprocess. The runtime cannot continue.", #mission_mod::monitor_component_label(cu29::monitoring::ComponentId::new(#index)));
2216                                    return Err(CuError::new_with_cause("Component errored out during postprocess.", error));
2217                                }
2218                            }
2219                        };
2220                        let call_sim_callback = if sim_mode {
2221                            quote! {
2222                                // Ask the sim if this task should be executed or overridden by the sim.
2223                                let ovr = sim_callback(SimStep::#enum_name(CuTaskCallbackState::Postprocess));
2224
2225                                let doit = if let SimOverride::Errored(reason) = ovr  {
2226                                    let error: CuError = reason.into();
2227                                    #monitoring_action
2228                                    false
2229                                } else {
2230                                    ovr == SimOverride::ExecuteByRuntime
2231                                };
2232                            }
2233                        } else {
2234                            quote! {
2235                                let doit = true;  // in normal mode always execute the steps in the runtime.
2236                            }
2237                        };
2238                        quote! {
2239                            #call_sim_callback
2240                            if doit {
2241                                execution_probe.record(cu29::monitoring::ExecutionMarker {
2242                                    component_id: cu29::monitoring::ComponentId::new(#index),
2243                                    step: CuComponentState::Postprocess,
2244                                    culistid: None,
2245                                });
2246                                ctx.set_current_task(#index);
2247                                let maybe_error = {
2248                                    #rt_guard
2249                                    tasks.#task_index.postprocess(&ctx)
2250                                };
2251                                if let Err(error) = maybe_error {
2252                                    #monitoring_action
2253                                }
2254                            }
2255                        }
2256                    }
2257                )
2258            })
2259        );
2260
2261        let bridge_start_calls: Vec<proc_macro2::TokenStream> = culist_bridge_specs
2262            .iter()
2263            .map(|spec| {
2264                let bridge_index = int2sliceindex(spec.tuple_index as u32);
2265                let monitor_index = syn::Index::from(
2266                    spec.monitor_index
2267                        .expect("Bridge missing monitor index for start"),
2268                );
2269                let enum_ident = Ident::new(
2270                    &config_id_to_enum(&format!("{}_bridge", spec.id)),
2271                    Span::call_site(),
2272                );
2273                let call_sim = if sim_mode {
2274                    quote! {
2275                        let doit = {
2276                            let state = SimStep::#enum_ident(cu29::simulation::CuBridgeLifecycleState::Start);
2277                            let ovr = sim_callback(state);
2278                            if let SimOverride::Errored(reason) = ovr {
2279                                let error: CuError = reason.into();
2280                                let decision = self.copper_runtime.monitor.process_error(cu29::monitoring::ComponentId::new(#monitor_index), CuComponentState::Start, &error);
2281                                match decision {
2282                                    Decision::Abort => { debug!("Start: ABORT decision from monitoring. Component '{}' errored out during start. Aborting all the other starts.", #mission_mod::monitor_component_label(cu29::monitoring::ComponentId::new(#monitor_index))); return Ok(()); }
2283                                    Decision::Ignore => { debug!("Start: IGNORE decision from monitoring. Component '{}' errored out during start. The runtime will continue.", #mission_mod::monitor_component_label(cu29::monitoring::ComponentId::new(#monitor_index))); false }
2284                                    Decision::Shutdown => { debug!("Start: SHUTDOWN decision from monitoring. Component '{}' errored out during start. The runtime cannot continue.", #mission_mod::monitor_component_label(cu29::monitoring::ComponentId::new(#monitor_index))); return Err(CuError::new_with_cause("Component errored out during start.", error)); }
2285                                }
2286                            } else {
2287                                ovr == SimOverride::ExecuteByRuntime
2288                            }
2289                        };
2290                    }
2291                } else {
2292                    quote! { let doit = true; }
2293                };
2294                quote! {
2295                    {
2296                        #call_sim
2297                        if !doit { return Ok(()); }
2298                        self.copper_runtime.record_execution_marker(
2299                            cu29::monitoring::ExecutionMarker {
2300                                component_id: cu29::monitoring::ComponentId::new(#monitor_index),
2301                                step: CuComponentState::Start,
2302                                culistid: None,
2303                            }
2304                        );
2305                        ctx.clear_current_task();
2306                        let bridge = &mut self.copper_runtime.bridges.#bridge_index;
2307                        if let Err(error) = bridge.start(&ctx) {
2308                            let decision = self.copper_runtime.monitor.process_error(cu29::monitoring::ComponentId::new(#monitor_index), CuComponentState::Start, &error);
2309                            match decision {
2310                                Decision::Abort => {
2311                                    debug!("Start: ABORT decision from monitoring. Component '{}' errored out during start. Aborting all the other starts.", #mission_mod::monitor_component_label(cu29::monitoring::ComponentId::new(#monitor_index)));
2312                                    return Ok(());
2313                                }
2314                                Decision::Ignore => {
2315                                    debug!("Start: IGNORE decision from monitoring. Component '{}' errored out during start. The runtime will continue.", #mission_mod::monitor_component_label(cu29::monitoring::ComponentId::new(#monitor_index)));
2316                                }
2317                                Decision::Shutdown => {
2318                                    debug!("Start: SHUTDOWN decision from monitoring. Component '{}' errored out during start. The runtime cannot continue.", #mission_mod::monitor_component_label(cu29::monitoring::ComponentId::new(#monitor_index)));
2319                                    return Err(CuError::new_with_cause("Component errored out during start.", error));
2320                                }
2321                            }
2322                        }
2323                    }
2324                }
2325            })
2326            .collect();
2327
2328        let bridge_stop_calls: Vec<proc_macro2::TokenStream> = culist_bridge_specs
2329            .iter()
2330            .map(|spec| {
2331                let bridge_index = int2sliceindex(spec.tuple_index as u32);
2332                let monitor_index = syn::Index::from(
2333                    spec.monitor_index
2334                        .expect("Bridge missing monitor index for stop"),
2335                );
2336                let enum_ident = Ident::new(
2337                    &config_id_to_enum(&format!("{}_bridge", spec.id)),
2338                    Span::call_site(),
2339                );
2340                let call_sim = if sim_mode {
2341                    quote! {
2342                        let doit = {
2343                            let state = SimStep::#enum_ident(cu29::simulation::CuBridgeLifecycleState::Stop);
2344                            let ovr = sim_callback(state);
2345                            if let SimOverride::Errored(reason) = ovr {
2346                                let error: CuError = reason.into();
2347                                let decision = self.copper_runtime.monitor.process_error(cu29::monitoring::ComponentId::new(#monitor_index), CuComponentState::Stop, &error);
2348                                match decision {
2349                                    Decision::Abort => { debug!("Stop: ABORT decision from monitoring. Component '{}' errored out during stop. Aborting all the other stops.", #mission_mod::monitor_component_label(cu29::monitoring::ComponentId::new(#monitor_index))); return Ok(()); }
2350                                    Decision::Ignore => { debug!("Stop: IGNORE decision from monitoring. Component '{}' errored out during stop. The runtime will continue.", #mission_mod::monitor_component_label(cu29::monitoring::ComponentId::new(#monitor_index))); false }
2351                                    Decision::Shutdown => { debug!("Stop: SHUTDOWN decision from monitoring. Component '{}' errored out during stop. The runtime cannot continue.", #mission_mod::monitor_component_label(cu29::monitoring::ComponentId::new(#monitor_index))); return Err(CuError::new_with_cause("Component errored out during stop.", error)); }
2352                                }
2353                            } else {
2354                                ovr == SimOverride::ExecuteByRuntime
2355                            }
2356                        };
2357                    }
2358                } else {
2359                    quote! { let doit = true; }
2360                };
2361                quote! {
2362                    {
2363                        #call_sim
2364                        if !doit { return Ok(()); }
2365                        self.copper_runtime.record_execution_marker(
2366                            cu29::monitoring::ExecutionMarker {
2367                                component_id: cu29::monitoring::ComponentId::new(#monitor_index),
2368                                step: CuComponentState::Stop,
2369                                culistid: None,
2370                            }
2371                        );
2372                        ctx.clear_current_task();
2373                        let bridge = &mut self.copper_runtime.bridges.#bridge_index;
2374                        if let Err(error) = bridge.stop(&ctx) {
2375                            let decision = self.copper_runtime.monitor.process_error(cu29::monitoring::ComponentId::new(#monitor_index), CuComponentState::Stop, &error);
2376                            match decision {
2377                                Decision::Abort => {
2378                                    debug!("Stop: ABORT decision from monitoring. Component '{}' errored out during stop. Aborting all the other stops.", #mission_mod::monitor_component_label(cu29::monitoring::ComponentId::new(#monitor_index)));
2379                                    return Ok(());
2380                                }
2381                                Decision::Ignore => {
2382                                    debug!("Stop: IGNORE decision from monitoring. Component '{}' errored out during stop. The runtime will continue.", #mission_mod::monitor_component_label(cu29::monitoring::ComponentId::new(#monitor_index)));
2383                                }
2384                                Decision::Shutdown => {
2385                                    debug!("Stop: SHUTDOWN decision from monitoring. Component '{}' errored out during stop. The runtime cannot continue.", #mission_mod::monitor_component_label(cu29::monitoring::ComponentId::new(#monitor_index)));
2386                                    return Err(CuError::new_with_cause("Component errored out during stop.", error));
2387                                }
2388                            }
2389                        }
2390                    }
2391                }
2392            })
2393            .collect();
2394
2395        let bridge_preprocess_calls: Vec<proc_macro2::TokenStream> = culist_bridge_specs
2396            .iter()
2397            .map(|spec| {
2398                let bridge_index = int2sliceindex(spec.tuple_index as u32);
2399                let monitor_index = syn::Index::from(
2400                    spec.monitor_index
2401                        .expect("Bridge missing monitor index for preprocess"),
2402                );
2403                let enum_ident = Ident::new(
2404                    &config_id_to_enum(&format!("{}_bridge", spec.id)),
2405                    Span::call_site(),
2406                );
2407                let call_sim = if sim_mode {
2408                    quote! {
2409                        let doit = {
2410                            let state = SimStep::#enum_ident(cu29::simulation::CuBridgeLifecycleState::Preprocess);
2411                            let ovr = sim_callback(state);
2412                            if let SimOverride::Errored(reason) = ovr {
2413                                let error: CuError = reason.into();
2414                                let decision = monitor.process_error(cu29::monitoring::ComponentId::new(#monitor_index), CuComponentState::Preprocess, &error);
2415                                match decision {
2416                                    Decision::Abort => { debug!("Preprocess: ABORT decision from monitoring. Component '{}' errored out during preprocess. Aborting all the other starts.", #mission_mod::monitor_component_label(cu29::monitoring::ComponentId::new(#monitor_index))); return Ok(()); }
2417                                    Decision::Ignore => { debug!("Preprocess: IGNORE decision from monitoring. Component '{}' errored out during preprocess. The runtime will continue.", #mission_mod::monitor_component_label(cu29::monitoring::ComponentId::new(#monitor_index))); false }
2418                                    Decision::Shutdown => { debug!("Preprocess: SHUTDOWN decision from monitoring. Component '{}' errored out during preprocess. The runtime cannot continue.", #mission_mod::monitor_component_label(cu29::monitoring::ComponentId::new(#monitor_index))); return Err(CuError::new_with_cause("Component errored out during preprocess.", error)); }
2419                                }
2420                            } else {
2421                                ovr == SimOverride::ExecuteByRuntime
2422                            }
2423                        };
2424                    }
2425                } else {
2426                    quote! { let doit = true; }
2427                };
2428                quote! {
2429                    {
2430                        #call_sim
2431                        if doit {
2432                            ctx.clear_current_task();
2433                            let bridge = &mut __cu_bridges.#bridge_index;
2434                            execution_probe.record(cu29::monitoring::ExecutionMarker {
2435                                component_id: cu29::monitoring::ComponentId::new(#monitor_index),
2436                                step: CuComponentState::Preprocess,
2437                                culistid: None,
2438                            });
2439                            let maybe_error = {
2440                                #rt_guard
2441                                bridge.preprocess(&ctx)
2442                            };
2443                            if let Err(error) = maybe_error {
2444                                let decision = monitor.process_error(cu29::monitoring::ComponentId::new(#monitor_index), CuComponentState::Preprocess, &error);
2445                                match decision {
2446                                    Decision::Abort => {
2447                                        debug!("Preprocess: ABORT decision from monitoring. Component '{}' errored out during preprocess. Aborting all the other starts.", #mission_mod::monitor_component_label(cu29::monitoring::ComponentId::new(#monitor_index)));
2448                                        return Ok(());
2449                                    }
2450                                    Decision::Ignore => {
2451                                        debug!("Preprocess: IGNORE decision from monitoring. Component '{}' errored out during preprocess. The runtime will continue.", #mission_mod::monitor_component_label(cu29::monitoring::ComponentId::new(#monitor_index)));
2452                                    }
2453                                    Decision::Shutdown => {
2454                                        debug!("Preprocess: SHUTDOWN decision from monitoring. Component '{}' errored out during preprocess. The runtime cannot continue.", #mission_mod::monitor_component_label(cu29::monitoring::ComponentId::new(#monitor_index)));
2455                                        return Err(CuError::new_with_cause("Component errored out during preprocess.", error));
2456                                    }
2457                                }
2458                            }
2459                        }
2460                    }
2461                }
2462            })
2463            .collect();
2464
2465        let bridge_postprocess_calls: Vec<proc_macro2::TokenStream> = culist_bridge_specs
2466            .iter()
2467            .map(|spec| {
2468                let bridge_index = int2sliceindex(spec.tuple_index as u32);
2469                let monitor_index = syn::Index::from(
2470                    spec.monitor_index
2471                        .expect("Bridge missing monitor index for postprocess"),
2472                );
2473                let enum_ident = Ident::new(
2474                    &config_id_to_enum(&format!("{}_bridge", spec.id)),
2475                    Span::call_site(),
2476                );
2477                let call_sim = if sim_mode {
2478                    quote! {
2479                        let doit = {
2480                            let state = SimStep::#enum_ident(cu29::simulation::CuBridgeLifecycleState::Postprocess);
2481                            let ovr = sim_callback(state);
2482                            if let SimOverride::Errored(reason) = ovr {
2483                                let error: CuError = reason.into();
2484                                let decision = monitor.process_error(cu29::monitoring::ComponentId::new(#monitor_index), CuComponentState::Postprocess, &error);
2485                                match decision {
2486                                    Decision::Abort => { debug!("Postprocess: ABORT decision from monitoring. Component '{}' errored out during postprocess. Aborting all the other starts.", #mission_mod::monitor_component_label(cu29::monitoring::ComponentId::new(#monitor_index))); return Ok(()); }
2487                                    Decision::Ignore => { debug!("Postprocess: IGNORE decision from monitoring. Component '{}' errored out during postprocess. The runtime will continue.", #mission_mod::monitor_component_label(cu29::monitoring::ComponentId::new(#monitor_index))); false }
2488                                    Decision::Shutdown => { debug!("Postprocess: SHUTDOWN decision from monitoring. Component '{}' errored out during postprocess. The runtime cannot continue.", #mission_mod::monitor_component_label(cu29::monitoring::ComponentId::new(#monitor_index))); return Err(CuError::new_with_cause("Component errored out during postprocess.", error)); }
2489                                }
2490                            } else {
2491                                ovr == SimOverride::ExecuteByRuntime
2492                            }
2493                        };
2494                    }
2495                } else {
2496                    quote! { let doit = true; }
2497                };
2498                quote! {
2499                    {
2500                        #call_sim
2501                        if doit {
2502                            ctx.clear_current_task();
2503                            let bridge = &mut __cu_bridges.#bridge_index;
2504                            kf_manager.freeze_any(clid, bridge)?;
2505                            execution_probe.record(cu29::monitoring::ExecutionMarker {
2506                                component_id: cu29::monitoring::ComponentId::new(#monitor_index),
2507                                step: CuComponentState::Postprocess,
2508                                culistid: Some(clid),
2509                            });
2510                            let maybe_error = {
2511                                #rt_guard
2512                                bridge.postprocess(&ctx)
2513                            };
2514                            if let Err(error) = maybe_error {
2515                                let decision = monitor.process_error(cu29::monitoring::ComponentId::new(#monitor_index), CuComponentState::Postprocess, &error);
2516                                match decision {
2517                                    Decision::Abort => {
2518                                        debug!("Postprocess: ABORT decision from monitoring. Component '{}' errored out during postprocess. Aborting all the other starts.", #mission_mod::monitor_component_label(cu29::monitoring::ComponentId::new(#monitor_index)));
2519                                        return Ok(());
2520                                    }
2521                                    Decision::Ignore => {
2522                                        debug!("Postprocess: IGNORE decision from monitoring. Component '{}' errored out during postprocess. The runtime will continue.", #mission_mod::monitor_component_label(cu29::monitoring::ComponentId::new(#monitor_index)));
2523                                    }
2524                                    Decision::Shutdown => {
2525                                        debug!("Postprocess: SHUTDOWN decision from monitoring. Component '{}' errored out during postprocess. The runtime cannot continue.", #mission_mod::monitor_component_label(cu29::monitoring::ComponentId::new(#monitor_index)));
2526                                        return Err(CuError::new_with_cause("Component errored out during postprocess.", error));
2527                                    }
2528                                }
2529                            }
2530                        }
2531                    }
2532                }
2533            })
2534            .collect();
2535
2536        let mut start_calls = bridge_start_calls;
2537        start_calls.extend(task_start_calls);
2538        let mut stop_calls = task_stop_calls;
2539        stop_calls.extend(bridge_stop_calls);
2540        let mut preprocess_calls = bridge_preprocess_calls;
2541        preprocess_calls.extend(task_preprocess_calls);
2542        let mut postprocess_calls = task_postprocess_calls;
2543        postprocess_calls.extend(bridge_postprocess_calls);
2544        let parallel_rt_run_supported = std && parallel_rt_enabled && !sim_mode;
2545
2546        // Bridges are frozen alongside tasks; restore them in the same order.
2547        let bridge_restore_code: Vec<proc_macro2::TokenStream> = culist_bridge_specs
2548            .iter()
2549            .enumerate()
2550            .map(|(index, _)| {
2551                let bridge_tuple_index = syn::Index::from(index);
2552                quote! {
2553                    __cu_bridges.#bridge_tuple_index
2554                        .thaw(&mut decoder)
2555                        .map_err(|e| CuError::from("Failed to thaw bridge").add_cause(&e.to_string()))?
2556                }
2557            })
2558            .collect();
2559
2560        let output_pack_sizes = collect_output_pack_sizes(&culist_plan);
2561        let runtime_plan_code_and_logging: Vec<(
2562            proc_macro2::TokenStream,
2563            proc_macro2::TokenStream,
2564        )> = culist_plan
2565            .steps
2566            .iter()
2567            .map(|unit| match unit {
2568                CuExecutionUnit::Step(step) => {
2569                    #[cfg(feature = "macro_debug")]
2570                    eprintln!(
2571                        "{} -> {} as {:?}. task_id: {} Input={:?}, Output={:?}",
2572                        step.node.get_id(),
2573                        step.node.get_type(),
2574                        step.task_type,
2575                        step.node_id,
2576                        step.input_msg_indices_types,
2577                        step.output_msg_pack
2578                    );
2579
2580                    match &culist_exec_entities[step.node_id as usize].kind {
2581                        ExecutionEntityKind::Task { task_index } => generate_task_execution_tokens(
2582                            step,
2583                            *task_index,
2584                            &task_specs,
2585                            StepGenerationContext::new(
2586                                &output_pack_sizes,
2587                                sim_mode,
2588                                &mission_mod,
2589                                ParallelLifecyclePlacement::default(),
2590                                false,
2591                            ),
2592                            TaskExecutionTokens::new(quote! {}, {
2593                                let node_index = int2sliceindex(*task_index as u32);
2594                                quote! { tasks.#node_index }
2595                            }),
2596                        ),
2597                        ExecutionEntityKind::BridgeRx {
2598                            bridge_index,
2599                            channel_index,
2600                        } => {
2601                            let spec = &culist_bridge_specs[*bridge_index];
2602                            generate_bridge_rx_execution_tokens(
2603                                step,
2604                                spec,
2605                                *channel_index,
2606                                StepGenerationContext::new(
2607                                    &output_pack_sizes,
2608                                    sim_mode,
2609                                    &mission_mod,
2610                                    ParallelLifecyclePlacement::default(),
2611                                    false,
2612                                ),
2613                                {
2614                                    let bridge_tuple_index =
2615                                        int2sliceindex(spec.tuple_index as u32);
2616                                    quote! { let bridge = &mut __cu_bridges.#bridge_tuple_index; }
2617                                },
2618                            )
2619                        }
2620                        ExecutionEntityKind::BridgeTx {
2621                            bridge_index,
2622                            channel_index,
2623                        } => {
2624                            let spec = &culist_bridge_specs[*bridge_index];
2625                            generate_bridge_tx_execution_tokens(
2626                                step,
2627                                spec,
2628                                *channel_index,
2629                                StepGenerationContext::new(
2630                                    &output_pack_sizes,
2631                                    sim_mode,
2632                                    &mission_mod,
2633                                    ParallelLifecyclePlacement::default(),
2634                                    false,
2635                                ),
2636                                {
2637                                    let bridge_tuple_index =
2638                                        int2sliceindex(spec.tuple_index as u32);
2639                                    quote! { let bridge = &mut __cu_bridges.#bridge_tuple_index; }
2640                                },
2641                            )
2642                        }
2643                    }
2644                }
2645                CuExecutionUnit::Loop(_) => {
2646                    panic!("Execution loops are not supported in runtime generation");
2647                }
2648            })
2649            .collect();
2650        let parallel_lifecycle_placements = if parallel_rt_run_supported {
2651            Some(build_parallel_lifecycle_placements(
2652                &culist_plan,
2653                &culist_exec_entities,
2654            ))
2655        } else {
2656            None
2657        };
2658        let runtime_plan_parallel_code_and_logging: Option<
2659            Vec<(proc_macro2::TokenStream, proc_macro2::TokenStream)>,
2660        > = if parallel_rt_run_supported {
2661            Some(
2662                culist_plan
2663                    .steps
2664                    .iter()
2665                    .enumerate()
2666                    .map(|(step_index, unit)| match unit {
2667                        CuExecutionUnit::Step(step) => match &culist_exec_entities
2668                            [step.node_id as usize]
2669                            .kind
2670                        {
2671                            ExecutionEntityKind::Task { task_index } => {
2672                                let task_index_ts = int2sliceindex(*task_index as u32);
2673                                generate_task_execution_tokens(
2674                                    step,
2675                                    *task_index,
2676                                    &task_specs,
2677                                    StepGenerationContext::new(
2678                                        &output_pack_sizes,
2679                                        false,
2680                                        &mission_mod,
2681                                        parallel_lifecycle_placements
2682                                            .as_ref()
2683                                            .expect("parallel lifecycle placements missing")[step_index],
2684                                        true,
2685                                    ),
2686                                    TaskExecutionTokens::new(quote! {
2687                                        let _task_lock = step_rt.task_locks.#task_index_ts.lock().expect("parallel task lock poisoned");
2688                                        let task = unsafe { step_rt.task_ptrs.#task_index_ts.as_mut() };
2689                                    }, quote! { (*task) }),
2690                                )
2691                            }
2692                            ExecutionEntityKind::BridgeRx {
2693                                bridge_index,
2694                                channel_index,
2695                            } => {
2696                                let spec = &culist_bridge_specs[*bridge_index];
2697                                let bridge_index_ts = int2sliceindex(spec.tuple_index as u32);
2698                                generate_bridge_rx_execution_tokens(
2699                                    step,
2700                                    spec,
2701                                    *channel_index,
2702                                    StepGenerationContext::new(
2703                                        &output_pack_sizes,
2704                                        false,
2705                                        &mission_mod,
2706                                        parallel_lifecycle_placements
2707                                            .as_ref()
2708                                            .expect("parallel lifecycle placements missing")
2709                                            [step_index],
2710                                        true,
2711                                    ),
2712                                    quote! {
2713                                        let _bridge_lock = step_rt.bridge_locks.#bridge_index_ts.lock().expect("parallel bridge lock poisoned");
2714                                        let bridge = unsafe { step_rt.bridge_ptrs.#bridge_index_ts.as_mut() };
2715                                    },
2716                                )
2717                            }
2718                            ExecutionEntityKind::BridgeTx {
2719                                bridge_index,
2720                                channel_index,
2721                            } => {
2722                                let spec = &culist_bridge_specs[*bridge_index];
2723                                let bridge_index_ts = int2sliceindex(spec.tuple_index as u32);
2724                                generate_bridge_tx_execution_tokens(
2725                                    step,
2726                                    spec,
2727                                    *channel_index,
2728                                    StepGenerationContext::new(
2729                                        &output_pack_sizes,
2730                                        false,
2731                                        &mission_mod,
2732                                        parallel_lifecycle_placements
2733                                            .as_ref()
2734                                            .expect("parallel lifecycle placements missing")[step_index],
2735                                        true,
2736                                    ),
2737                                    quote! {
2738                                        let _bridge_lock = step_rt.bridge_locks.#bridge_index_ts.lock().expect("parallel bridge lock poisoned");
2739                                        let bridge = unsafe { step_rt.bridge_ptrs.#bridge_index_ts.as_mut() };
2740                                    },
2741                                )
2742                            }
2743                        },
2744                        CuExecutionUnit::Loop(_) => {
2745                            panic!("Execution loops are not supported in runtime generation");
2746                        }
2747                    })
2748                    .collect(),
2749            )
2750        } else {
2751            None
2752        };
2753
2754        let sim_support = if sim_mode {
2755            Some(gen_sim_support(
2756                &culist_plan,
2757                &culist_exec_entities,
2758                &culist_bridge_specs,
2759            ))
2760        } else {
2761            None
2762        };
2763
2764        let recorded_replay_support = if sim_mode {
2765            Some(gen_recorded_replay_support(
2766                &culist_plan,
2767                &culist_exec_entities,
2768                &culist_bridge_specs,
2769            ))
2770        } else {
2771            None
2772        };
2773
2774        let (run_one_iteration, start_all_tasks, stop_all_tasks, run) = if sim_mode {
2775            (
2776                quote! {
2777                    fn run_one_iteration(&mut self, sim_callback: &mut impl FnMut(SimStep) -> SimOverride) -> CuResult<()>
2778                },
2779                quote! {
2780                    fn start_all_tasks(&mut self, sim_callback: &mut impl FnMut(SimStep) -> SimOverride) -> CuResult<()>
2781                },
2782                quote! {
2783                    fn stop_all_tasks(&mut self, sim_callback: &mut impl FnMut(SimStep) -> SimOverride) -> CuResult<()>
2784                },
2785                quote! {
2786                    fn run(&mut self, sim_callback: &mut impl FnMut(SimStep) -> SimOverride) -> CuResult<()>
2787                },
2788            )
2789        } else {
2790            (
2791                quote! {
2792                    fn run_one_iteration(&mut self) -> CuResult<()>
2793                },
2794                quote! {
2795                    fn start_all_tasks(&mut self) -> CuResult<()>
2796                },
2797                quote! {
2798                    fn stop_all_tasks(&mut self) -> CuResult<()>
2799                },
2800                quote! {
2801                    fn run(&mut self) -> CuResult<()>
2802                },
2803            )
2804        };
2805
2806        let sim_callback_arg = if sim_mode {
2807            Some(quote!(sim_callback))
2808        } else {
2809            None
2810        };
2811
2812        let app_trait = if sim_mode {
2813            quote!(CuSimApplication)
2814        } else {
2815            quote!(CuApplication)
2816        };
2817
2818        let sim_callback_on_new_calls = task_specs.ids.iter().enumerate().map(|(i, id)| {
2819            let enum_name = config_id_to_enum(id);
2820            let enum_ident = Ident::new(&enum_name, Span::call_site());
2821            quote! {
2822                // the answer is ignored, we have to instantiate the tasks anyway.
2823                sim_callback(SimStep::#enum_ident(CuTaskCallbackState::New(all_instances_configs[#i].cloned())));
2824            }
2825        });
2826
2827        let sim_callback_on_new_bridges = culist_bridge_specs.iter().map(|spec| {
2828            let enum_ident = Ident::new(
2829                &config_id_to_enum(&format!("{}_bridge", spec.id)),
2830                Span::call_site(),
2831            );
2832            let cfg_index = syn::Index::from(spec.config_index);
2833            quote! {
2834                sim_callback(SimStep::#enum_ident(
2835                    cu29::simulation::CuBridgeLifecycleState::New(config.bridges[#cfg_index].config.clone())
2836                ));
2837            }
2838        });
2839
2840        let sim_callback_on_new = if sim_mode {
2841            Some(quote! {
2842                let graph = config.get_graph(Some(#mission)).expect("Could not find the mission #mission");
2843                let all_instances_configs: Vec<Option<&ComponentConfig>> = graph
2844                    .get_all_nodes()
2845                    .iter()
2846                    .map(|(_, node)| node.get_instance_config())
2847                    .collect();
2848                #(#sim_callback_on_new_calls)*
2849                #(#sim_callback_on_new_bridges)*
2850            })
2851        } else {
2852            None
2853        };
2854
2855        let (runtime_plan_code, preprocess_logging_calls): (Vec<_>, Vec<_>) =
2856            itertools::multiunzip(runtime_plan_code_and_logging);
2857        let process_step_tasks_type = if sim_mode {
2858            quote!(CuSimTasks)
2859        } else {
2860            quote!(CuTasks)
2861        };
2862        let (
2863            parallel_process_step_idents,
2864            parallel_process_step_fn_defs,
2865            parallel_stage_worker_spawns,
2866        ): (
2867            Vec<Ident>,
2868            Vec<proc_macro2::TokenStream>,
2869            Vec<proc_macro2::TokenStream>,
2870        ) = if let Some(runtime_plan_parallel_code_and_logging) =
2871            &runtime_plan_parallel_code_and_logging
2872        {
2873            let (runtime_plan_parallel_step_code, _): (Vec<_>, Vec<_>) =
2874                itertools::multiunzip(runtime_plan_parallel_code_and_logging.clone());
2875            let parallel_process_step_idents: Vec<Ident> = (0..runtime_plan_parallel_step_code
2876                .len())
2877                .map(|index| format_ident!("__cu_parallel_process_step_{index}"))
2878                .collect();
2879            let parallel_process_step_fn_defs: Vec<proc_macro2::TokenStream> =
2880                parallel_process_step_idents
2881                    .iter()
2882                    .zip(runtime_plan_parallel_step_code.iter())
2883                    .map(|(step_ident, step_code)| {
2884                        quote! {
2885                            #[inline(always)]
2886                            fn #step_ident(
2887                                step_rt: &mut ParallelProcessStepRuntime<'_>,
2888                            ) -> cu29::curuntime::ProcessStepResult {
2889                                let clock = step_rt.clock;
2890                                let execution_probe = step_rt.execution_probe;
2891                                let monitor = step_rt.monitor;
2892                                let kf_manager = ParallelKeyFrameAccessor::new(
2893                                    step_rt.kf_manager_ptr,
2894                                    step_rt.kf_lock,
2895                                );
2896                                let culist = &mut *step_rt.culist;
2897                                let clid = step_rt.clid;
2898                                let ctx = &mut step_rt.ctx;
2899                                let msgs = &mut culist.msgs.0;
2900                                #step_code
2901                            }
2902                        }
2903                    })
2904                    .collect();
2905            let parallel_stage_worker_spawns: Vec<proc_macro2::TokenStream> =
2906                parallel_process_step_idents
2907                    .iter()
2908                    .enumerate()
2909                    .map(|(stage_index, step_ident)| {
2910                        let stage_index_lit = syn::Index::from(stage_index);
2911                        let receiver_ident =
2912                            format_ident!("__cu_parallel_stage_rx_{stage_index}");
2913                        quote! {
2914                            {
2915                                let mut #receiver_ident = stage_receivers
2916                                    .next()
2917                                    .expect("parallel stage receiver missing");
2918                                let mut next_stage_tx = stage_senders.next();
2919                                let done_tx = done_tx.clone();
2920                                let shutdown = std::sync::Arc::clone(&shutdown);
2921                                let clock = clock.clone();
2922                                let instance_id = instance_id;
2923                                let subsystem_code = subsystem_code;
2924                                let execution_probe_ptr = execution_probe_ptr;
2925                                let monitor_ptr = monitor_ptr;
2926                                let task_ptrs = task_ptrs;
2927                                let task_locks = std::sync::Arc::clone(&task_locks);
2928                                let bridge_ptrs = bridge_ptrs;
2929                                let bridge_locks = std::sync::Arc::clone(&bridge_locks);
2930                                let kf_manager_ptr = kf_manager_ptr;
2931                                let kf_lock = std::sync::Arc::clone(&kf_lock);
2932                                scope.spawn(move || {
2933                                    loop {
2934                                        let job = match #receiver_ident.recv() {
2935                                            Ok(job) => job,
2936                                            Err(_) => break,
2937                                        };
2938                                        let clid = job.clid;
2939                                        let culist = job.culist;
2940
2941                                        let terminal_result = if shutdown.load(Ordering::Acquire) {
2942                                            #mission_mod::ParallelWorkerResult {
2943                                                clid,
2944                                                culist: Some(culist),
2945                                                outcome: Err(CuError::from(
2946                                                    "Parallel runtime shutting down after an earlier stage failure",
2947                                                )),
2948                                                raw_payload_bytes: 0,
2949                                                handle_bytes: 0,
2950                                            }
2951                                        } else {
2952                                            match std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
2953                                                let execution_probe = unsafe { execution_probe_ptr.as_ref() };
2954                                                let monitor = unsafe { monitor_ptr.as_ref() };
2955                                                let mut culist = culist;
2956                                                let mut step_rt = #mission_mod::ParallelProcessStepRuntime {
2957                                                    clock: &clock,
2958                                                    execution_probe,
2959                                                    monitor,
2960                                                    task_ptrs: &task_ptrs,
2961                                                    task_locks: task_locks.as_ref(),
2962                                                    bridge_ptrs: &bridge_ptrs,
2963                                                    bridge_locks: bridge_locks.as_ref(),
2964                                                    kf_manager_ptr,
2965                                                    kf_lock: kf_lock.as_ref(),
2966                                                    culist: culist.as_mut(),
2967                                                    clid,
2968                                                    ctx: cu29::context::CuContext::from_runtime_metadata(
2969                                                        clock.clone(),
2970                                                        clid,
2971                                                        instance_id,
2972                                                        subsystem_code,
2973                                                        #mission_mod::TASK_IDS,
2974                                                    ),
2975                                                };
2976                                                let outcome = #step_ident(&mut step_rt);
2977                                                drop(step_rt);
2978                                                (culist, outcome)
2979                                            })) {
2980                                                Ok((culist, Ok(cu29::curuntime::ProcessStepOutcome::Continue))) => {
2981                                                    if shutdown.load(Ordering::Acquire) {
2982                                                        #mission_mod::ParallelWorkerResult {
2983                                                            clid,
2984                                                            culist: Some(culist),
2985                                                            outcome: Err(CuError::from(
2986                                                                "Parallel runtime shutting down after an earlier stage failure",
2987                                                            )),
2988                                                            raw_payload_bytes: 0,
2989                                                            handle_bytes: 0,
2990                                                        }
2991                                                    } else if let Some(next_stage_tx) = next_stage_tx.as_mut() {
2992                                                        let forwarded_job = #mission_mod::ParallelWorkerJob { clid, culist };
2993                                                        match next_stage_tx.send(forwarded_job) {
2994                                                            Ok(()) => continue,
2995                                                            Err(send_error) => {
2996                                                                let failed_job = send_error.0;
2997                                                                shutdown.store(true, Ordering::Release);
2998                                                                #mission_mod::ParallelWorkerResult {
2999                                                                    clid,
3000                                                                    culist: Some(failed_job.culist),
3001                                                                    outcome: Err(CuError::from(format!(
3002                                                                        "Parallel stage {} could not hand CopperList #{} to the next stage",
3003                                                                        #stage_index_lit,
3004                                                                        clid
3005                                                                    ))),
3006                                                                    raw_payload_bytes: 0,
3007                                                                    handle_bytes: 0,
3008                                                                }
3009                                                            }
3010                                                        }
3011                                                    } else {
3012                                                        #mission_mod::ParallelWorkerResult {
3013                                                            clid,
3014                                                            culist: Some(culist),
3015                                                            outcome: Ok(cu29::curuntime::ProcessStepOutcome::Continue),
3016                                                            raw_payload_bytes: 0,
3017                                                            handle_bytes: 0,
3018                                                        }
3019                                                    }
3020                                                }
3021                                                Ok((culist, Ok(cu29::curuntime::ProcessStepOutcome::AbortCopperList))) => {
3022                                                    #mission_mod::ParallelWorkerResult {
3023                                                        clid,
3024                                                        culist: Some(culist),
3025                                                        outcome: Ok(cu29::curuntime::ProcessStepOutcome::AbortCopperList),
3026                                                        raw_payload_bytes: 0,
3027                                                        handle_bytes: 0,
3028                                                    }
3029                                                }
3030                                                Ok((culist, Err(error))) => {
3031                                                    shutdown.store(true, Ordering::Release);
3032                                                    #mission_mod::ParallelWorkerResult {
3033                                                        clid,
3034                                                        culist: Some(culist),
3035                                                        outcome: Err(error),
3036                                                        raw_payload_bytes: 0,
3037                                                        handle_bytes: 0,
3038                                                    }
3039                                                }
3040                                                Err(payload) => {
3041                                                    shutdown.store(true, Ordering::Release);
3042                                                    let panic_message =
3043                                                        cu29::monitoring::panic_payload_to_string(payload.as_ref());
3044                                                    #mission_mod::ParallelWorkerResult {
3045                                                        clid,
3046                                                        culist: None,
3047                                                        outcome: Err(CuError::from(format!(
3048                                                            "Panic while processing CopperList #{} in stage {}: {}",
3049                                                            clid,
3050                                                            #stage_index_lit,
3051                                                            panic_message
3052                                                        ))),
3053                                                        raw_payload_bytes: 0,
3054                                                        handle_bytes: 0,
3055                                                    }
3056                                                }
3057                                            }
3058                                        };
3059
3060                                        if done_tx.send(terminal_result).is_err() {
3061                                            break;
3062                                        }
3063                                    }
3064                                });
3065                            }
3066                        }
3067                    })
3068                    .collect();
3069            (
3070                parallel_process_step_idents,
3071                parallel_process_step_fn_defs,
3072                parallel_stage_worker_spawns,
3073            )
3074        } else {
3075            (Vec::new(), Vec::new(), Vec::new())
3076        };
3077        let parallel_process_stage_count_tokens =
3078            proc_macro2::Literal::usize_unsuffixed(parallel_process_step_idents.len());
3079        let parallel_task_ptrs_type = if task_types.is_empty() {
3080            quote! { () }
3081        } else {
3082            let elems = task_types
3083                .iter()
3084                .map(|ty| quote! { ParallelSharedPtr<#ty> });
3085            quote! { (#(#elems),*,) }
3086        };
3087        let parallel_task_locks_type = if task_types.is_empty() {
3088            quote! { () }
3089        } else {
3090            let elems = (0..task_types.len()).map(|_| quote! { std::sync::Mutex<()> });
3091            quote! { (#(#elems),*,) }
3092        };
3093        let parallel_task_ptr_values = if task_types.is_empty() {
3094            quote! { () }
3095        } else {
3096            let elems = (0..task_types.len()).map(|index| {
3097                let index = syn::Index::from(index);
3098                quote! { ParallelSharedPtr::new(&mut runtime.tasks.#index as *mut _) }
3099            });
3100            quote! { (#(#elems),*,) }
3101        };
3102        let parallel_task_lock_values = if task_types.is_empty() {
3103            quote! { () }
3104        } else {
3105            let elems = (0..task_types.len()).map(|_| quote! { std::sync::Mutex::new(()) });
3106            quote! { (#(#elems),*,) }
3107        };
3108        let parallel_bridge_ptrs_type = if bridge_runtime_types.is_empty() {
3109            quote! { () }
3110        } else {
3111            let elems = bridge_runtime_types
3112                .iter()
3113                .map(|ty| quote! { ParallelSharedPtr<#ty> });
3114            quote! { (#(#elems),*,) }
3115        };
3116        let parallel_bridge_locks_type = if bridge_runtime_types.is_empty() {
3117            quote! { () }
3118        } else {
3119            let elems = (0..bridge_runtime_types.len()).map(|_| quote! { std::sync::Mutex<()> });
3120            quote! { (#(#elems),*,) }
3121        };
3122        let parallel_bridge_ptr_values = if bridge_runtime_types.is_empty() {
3123            quote! { () }
3124        } else {
3125            let elems = (0..bridge_runtime_types.len()).map(|index| {
3126                let index = syn::Index::from(index);
3127                quote! { ParallelSharedPtr::new(&mut runtime.bridges.#index as *mut _) }
3128            });
3129            quote! { (#(#elems),*,) }
3130        };
3131        let parallel_bridge_lock_values = if bridge_runtime_types.is_empty() {
3132            quote! { () }
3133        } else {
3134            let elems =
3135                (0..bridge_runtime_types.len()).map(|_| quote! { std::sync::Mutex::new(()) });
3136            quote! { (#(#elems),*,) }
3137        };
3138        let parallel_rt_support_tokens = if parallel_rt_run_supported {
3139            quote! {
3140                type ParallelTaskPtrs = #parallel_task_ptrs_type;
3141                type ParallelTaskLocks = #parallel_task_locks_type;
3142                type ParallelBridgePtrs = #parallel_bridge_ptrs_type;
3143                type ParallelBridgeLocks = #parallel_bridge_locks_type;
3144
3145                struct ParallelSharedPtr<T>(*mut T);
3146
3147                impl<T> Clone for ParallelSharedPtr<T> {
3148                    #[inline(always)]
3149                    fn clone(&self) -> Self {
3150                        *self
3151                    }
3152                }
3153
3154                impl<T> Copy for ParallelSharedPtr<T> {}
3155
3156                impl<T> ParallelSharedPtr<T> {
3157                    #[inline(always)]
3158                    const fn new(ptr: *mut T) -> Self {
3159                        Self(ptr)
3160                    }
3161
3162                    #[inline(always)]
3163                    const fn from_ref(ptr: *const T) -> Self {
3164                        Self(ptr as *mut T)
3165                    }
3166
3167                    #[inline(always)]
3168                    unsafe fn as_mut<'a>(self) -> &'a mut T {
3169                        unsafe { &mut *self.0 }
3170                    }
3171
3172                    #[inline(always)]
3173                    unsafe fn as_ref<'a>(self) -> &'a T {
3174                        unsafe { &*self.0 }
3175                    }
3176                }
3177
3178                unsafe impl<T: Send> Send for ParallelSharedPtr<T> {}
3179                unsafe impl<T: Send> Sync for ParallelSharedPtr<T> {}
3180
3181                struct ParallelKeyFrameAccessor<'a> {
3182                    ptr: ParallelSharedPtr<cu29::curuntime::KeyFramesManager>,
3183                    lock: &'a std::sync::Mutex<()>,
3184                }
3185
3186                impl<'a> ParallelKeyFrameAccessor<'a> {
3187                    #[inline(always)]
3188                    fn new(
3189                        ptr: ParallelSharedPtr<cu29::curuntime::KeyFramesManager>,
3190                        lock: &'a std::sync::Mutex<()>,
3191                    ) -> Self {
3192                        Self { ptr, lock }
3193                    }
3194
3195                    #[inline(always)]
3196                    fn freeze_task(
3197                        &self,
3198                        culistid: u64,
3199                        task: &impl cu29::cutask::Freezable,
3200                    ) -> CuResult<usize> {
3201                        let _guard = self.lock.lock().expect("parallel keyframe lock poisoned");
3202                        let manager = unsafe { self.ptr.as_mut() };
3203                        manager.freeze_task(culistid, task)
3204                    }
3205
3206                    #[inline(always)]
3207                    fn freeze_any(
3208                        &self,
3209                        culistid: u64,
3210                        item: &impl cu29::cutask::Freezable,
3211                    ) -> CuResult<usize> {
3212                        let _guard = self.lock.lock().expect("parallel keyframe lock poisoned");
3213                        let manager = unsafe { self.ptr.as_mut() };
3214                        manager.freeze_any(culistid, item)
3215                    }
3216                }
3217
3218                struct ParallelProcessStepRuntime<'a> {
3219                    clock: &'a RobotClock,
3220                    execution_probe: &'a cu29::monitoring::RuntimeExecutionProbe,
3221                    monitor: &'a #monitor_type,
3222                    task_ptrs: &'a ParallelTaskPtrs,
3223                    task_locks: &'a ParallelTaskLocks,
3224                    bridge_ptrs: &'a ParallelBridgePtrs,
3225                    bridge_locks: &'a ParallelBridgeLocks,
3226                    kf_manager_ptr: ParallelSharedPtr<cu29::curuntime::KeyFramesManager>,
3227                    kf_lock: &'a std::sync::Mutex<()>,
3228                    culist: &'a mut CuList,
3229                    clid: u64,
3230                    ctx: cu29::context::CuContext,
3231                }
3232
3233                struct ParallelWorkerJob {
3234                    clid: u64,
3235                    culist: Box<CuList>,
3236                }
3237
3238                struct ParallelWorkerResult {
3239                    clid: u64,
3240                    culist: Option<Box<CuList>>,
3241                    outcome: cu29::curuntime::ProcessStepResult,
3242                    raw_payload_bytes: u64,
3243                    handle_bytes: u64,
3244                }
3245
3246                #[inline(always)]
3247                fn assert_parallel_rt_send_bounds()
3248                where
3249                    CuList: Send,
3250                    #process_step_tasks_type: Send,
3251                    CuBridges: Send,
3252                    #monitor_type: Sync,
3253                {
3254                }
3255
3256                #(#parallel_process_step_fn_defs)*
3257            }
3258        } else {
3259            quote! {}
3260        };
3261
3262        let config_load_stmt =
3263            build_config_load_stmt(std, application_name, subsystem_id.as_deref());
3264
3265        let copperlist_count_check = quote! {
3266            let configured_copperlist_count = config
3267                .logging
3268                .as_ref()
3269                .and_then(|logging| logging.copperlist_count)
3270                .unwrap_or(#copperlist_count_tokens);
3271            if configured_copperlist_count != #copperlist_count_tokens {
3272                return Err(CuError::from(format!(
3273                    "Configured logging.copperlist_count ({configured_copperlist_count}) does not match the runtime compiled into this binary ({})",
3274                    #copperlist_count_tokens
3275                )));
3276            }
3277        };
3278
3279        let prepare_config_sig = if std {
3280            quote! {
3281                fn prepare_config(
3282                    instance_id: u32,
3283                    config_override: Option<CuConfig>,
3284                ) -> CuResult<(CuConfig, RuntimeLifecycleConfigSource)>
3285            }
3286        } else {
3287            quote! {
3288                fn prepare_config() -> CuResult<(CuConfig, RuntimeLifecycleConfigSource)>
3289            }
3290        };
3291
3292        let prepare_config_call = if std {
3293            quote! { Self::prepare_config(instance_id, config_override)? }
3294        } else {
3295            quote! { Self::prepare_config()? }
3296        };
3297
3298        let prepare_resources_sig = if std {
3299            quote! {
3300                pub fn prepare_resources_for_instance(
3301                    instance_id: u32,
3302                    config_override: Option<CuConfig>,
3303                ) -> CuResult<AppResources>
3304            }
3305        } else {
3306            quote! {
3307                pub fn prepare_resources() -> CuResult<AppResources>
3308            }
3309        };
3310
3311        let prepare_resources_compat_fn = if std {
3312            Some(quote! {
3313                pub fn prepare_resources(
3314                    config_override: Option<CuConfig>,
3315                ) -> CuResult<AppResources> {
3316                    Self::prepare_resources_for_instance(0, config_override)
3317                }
3318            })
3319        } else {
3320            None
3321        };
3322
3323        let init_resources_compat_fn = if std {
3324            Some(quote! {
3325                pub fn init_resources_for_instance(
3326                    instance_id: u32,
3327                    config_override: Option<CuConfig>,
3328                ) -> CuResult<AppResources> {
3329                    Self::prepare_resources_for_instance(instance_id, config_override)
3330                }
3331
3332                pub fn init_resources(
3333                    config_override: Option<CuConfig>,
3334                ) -> CuResult<AppResources> {
3335                    Self::prepare_resources(config_override)
3336                }
3337            })
3338        } else {
3339            Some(quote! {
3340                pub fn init_resources() -> CuResult<AppResources> {
3341                    Self::prepare_resources()
3342                }
3343            })
3344        };
3345
3346        let build_with_resources_sig = if sim_mode {
3347            quote! {
3348                fn build_with_resources<S: SectionStorage + 'static, L: UnifiedLogWrite<S> + 'static>(
3349                    clock: RobotClock,
3350                    unified_logger: Arc<Mutex<L>>,
3351                    app_resources: AppResources,
3352                    instance_id: u32,
3353                    sim_callback: &mut impl FnMut(SimStep) -> SimOverride,
3354                ) -> CuResult<Self>
3355            }
3356        } else {
3357            quote! {
3358                fn build_with_resources<S: SectionStorage + 'static, L: UnifiedLogWrite<S> + 'static>(
3359                    clock: RobotClock,
3360                    unified_logger: Arc<Mutex<L>>,
3361                    app_resources: AppResources,
3362                    instance_id: u32,
3363                ) -> CuResult<Self>
3364            }
3365        };
3366        let parallel_rt_metadata_arg = if std && parallel_rt_enabled {
3367            Some(quote! {
3368                &#mission_mod::PARALLEL_RT_METADATA,
3369            })
3370        } else {
3371            None
3372        };
3373
3374        let kill_handler = if std && signal_handler {
3375            Some(quote! {
3376                ctrlc::set_handler(move || {
3377                    STOP_FLAG.store(true, Ordering::SeqCst);
3378                }).expect("Error setting Ctrl-C handler");
3379            })
3380        } else {
3381            None
3382        };
3383
3384        let run_loop = if std {
3385            quote! {{
3386                let mut rate_limiter = self
3387                    .copper_runtime
3388                    .runtime_config
3389                    .rate_target_hz
3390                    .map(|rate| cu29::curuntime::LoopRateLimiter::from_rate_target_hz(
3391                        rate,
3392                        &self.copper_runtime.clock,
3393                    ))
3394                    .transpose()?;
3395                loop  {
3396                    let result = match std::panic::catch_unwind(std::panic::AssertUnwindSafe(
3397                        || <Self as #app_trait<S, L>>::run_one_iteration(self, #sim_callback_arg)
3398                    )) {
3399                        Ok(result) => result,
3400                        Err(payload) => {
3401                            let panic_message = cu29::monitoring::panic_payload_to_string(payload.as_ref());
3402                            self.copper_runtime.monitor.process_panic(&panic_message);
3403                            let _ = self.log_runtime_lifecycle_event(RuntimeLifecycleEvent::Panic {
3404                                message: panic_message.clone(),
3405                                file: None,
3406                                line: None,
3407                                column: None,
3408                            });
3409                            Err(CuError::from(format!(
3410                                "Panic while running one iteration: {}",
3411                                panic_message
3412                            )))
3413                        }
3414                    };
3415
3416                    if let Some(rate_limiter) = rate_limiter.as_mut() {
3417                        rate_limiter.limit(&self.copper_runtime.clock);
3418                    }
3419
3420                    if STOP_FLAG.load(Ordering::SeqCst) || result.is_err() {
3421                        break result;
3422                    }
3423                }
3424            }}
3425        } else {
3426            quote! {{
3427                let mut rate_limiter = self
3428                    .copper_runtime
3429                    .runtime_config
3430                    .rate_target_hz
3431                    .map(|rate| cu29::curuntime::LoopRateLimiter::from_rate_target_hz(
3432                        rate,
3433                        &self.copper_runtime.clock,
3434                    ))
3435                    .transpose()?;
3436                loop  {
3437                    let result = <Self as #app_trait<S, L>>::run_one_iteration(self, #sim_callback_arg);
3438                    if let Some(rate_limiter) = rate_limiter.as_mut() {
3439                        rate_limiter.limit(&self.copper_runtime.clock);
3440                    }
3441
3442                    if STOP_FLAG.load(Ordering::SeqCst) || result.is_err() {
3443                        break result;
3444                    }
3445                }
3446            }}
3447        };
3448
3449        #[cfg(feature = "macro_debug")]
3450        eprintln!("[build the run methods]");
3451        let run_body: proc_macro2::TokenStream = if parallel_rt_run_supported {
3452            quote! {
3453                static STOP_FLAG: AtomicBool = AtomicBool::new(false);
3454
3455                #kill_handler
3456
3457                <Self as #app_trait<S, L>>::start_all_tasks(self)?;
3458                let result = std::thread::scope(|scope| -> CuResult<()> {
3459                    #mission_mod::assert_parallel_rt_send_bounds();
3460
3461                    let runtime = &mut self.copper_runtime;
3462                    let clock = &runtime.clock;
3463                    let instance_id = runtime.instance_id();
3464                    let subsystem_code = runtime.subsystem_code();
3465                    let execution_probe = runtime.execution_probe.as_ref();
3466                    let monitor = &runtime.monitor;
3467                    let cl_manager = &mut runtime.copperlists_manager;
3468                    let parallel_rt = &runtime.parallel_rt;
3469                    let execution_probe_ptr =
3470                        #mission_mod::ParallelSharedPtr::from_ref(execution_probe as *const _);
3471                    let monitor_ptr =
3472                        #mission_mod::ParallelSharedPtr::from_ref(monitor as *const _);
3473                    let task_ptrs: #mission_mod::ParallelTaskPtrs = #parallel_task_ptr_values;
3474                    let task_locks = std::sync::Arc::new(#parallel_task_lock_values);
3475                    let bridge_ptrs: #mission_mod::ParallelBridgePtrs = #parallel_bridge_ptr_values;
3476                    let bridge_locks = std::sync::Arc::new(#parallel_bridge_lock_values);
3477                    let kf_manager_ptr =
3478                        #mission_mod::ParallelSharedPtr::new(&mut runtime.keyframes_manager as *mut _);
3479                    let kf_lock = std::sync::Arc::new(std::sync::Mutex::new(()));
3480                    let mut free_copperlists =
3481                        cu29::curuntime::allocate_boxed_copperlists::<CuStampedDataSet, #copperlist_count_tokens>();
3482                    let start_clid = cl_manager.next_cl_id();
3483                    parallel_rt.reset_cursors(start_clid);
3484
3485                    let stage_count = #parallel_process_stage_count_tokens;
3486                    debug_assert_eq!(parallel_rt.metadata().process_stage_count(), stage_count);
3487                    if stage_count == 0 {
3488                        return Err(CuError::from(
3489                            "Parallel runtime requires at least one generated process stage",
3490                        ));
3491                    }
3492
3493                    let queue_capacity = parallel_rt.in_flight_limit().max(1);
3494                    let mut stage_senders = Vec::with_capacity(stage_count);
3495                    let mut stage_receivers = Vec::with_capacity(stage_count);
3496                    for _stage_index in 0..stage_count {
3497                        let (stage_tx, stage_rx) =
3498                            cu29::parallel_queue::stage_queue::<#mission_mod::ParallelWorkerJob>(
3499                                queue_capacity,
3500                            );
3501                        stage_senders.push(stage_tx);
3502                        stage_receivers.push(stage_rx);
3503                    }
3504                    let (done_tx, done_rx) =
3505                        std::sync::mpsc::channel::<#mission_mod::ParallelWorkerResult>();
3506                    let shutdown = std::sync::Arc::new(AtomicBool::new(false));
3507                    let mut stage_senders = stage_senders.into_iter();
3508                    let mut entry_stage_tx = stage_senders
3509                        .next()
3510                        .expect("parallel stage pipeline has no entry queue");
3511                    let mut stage_receivers = stage_receivers.into_iter();
3512                    #(#parallel_stage_worker_spawns)*
3513                    drop(done_tx);
3514
3515                    let mut dispatch_limiter = runtime
3516                        .runtime_config
3517                        .rate_target_hz
3518                        .map(|rate| cu29::curuntime::LoopRateLimiter::from_rate_target_hz(rate, clock))
3519                        .transpose()?;
3520                    let mut in_flight = 0usize;
3521                    let mut stop_launching = false;
3522                    let mut next_launch_clid = start_clid;
3523                    let mut next_commit_clid = start_clid;
3524                    let mut pending_results =
3525                        std::collections::BTreeMap::<u64, #mission_mod::ParallelWorkerResult>::new();
3526                    let mut active_keyframe_clid: Option<u64> = None;
3527                    let mut fatal_error: Option<CuError> = None;
3528
3529                    loop {
3530                        while let Some(recycled_culist) = cl_manager.try_reclaim_boxed()? {
3531                            free_copperlists.push(recycled_culist);
3532                        }
3533
3534                        if !stop_launching && fatal_error.is_none() {
3535                            let next_clid = next_launch_clid;
3536                            let rate_ready = dispatch_limiter
3537                                .as_ref()
3538                                .map(|limiter| limiter.is_ready(clock))
3539                                .unwrap_or(true);
3540                            let keyframe_ready = {
3541                                let _keyframe_lock = kf_lock.lock().expect("parallel keyframe lock poisoned");
3542                                let kf_manager = unsafe { kf_manager_ptr.as_mut() };
3543                                active_keyframe_clid.is_none() || !kf_manager.captures_keyframe(next_clid)
3544                            };
3545
3546                            if in_flight < parallel_rt.in_flight_limit()
3547                                && rate_ready
3548                                && keyframe_ready
3549                                && !free_copperlists.is_empty()
3550                            {
3551                                // Parallel lifecycle is attached to component-local stage work,
3552                                // so dispatch itself can launch the next CopperList immediately.
3553                                let should_launch = true;
3554
3555                                if should_launch {
3556                                    let mut culist = free_copperlists
3557                                        .pop()
3558                                        .expect("parallel CopperList pool unexpectedly empty");
3559                                    let clid = next_clid;
3560                                    culist.id = clid;
3561                                    culist.change_state(cu29::copperlist::CopperListState::Initialized);
3562                                    {
3563                                        let _keyframe_lock =
3564                                            kf_lock.lock().expect("parallel keyframe lock poisoned");
3565                                        let kf_manager = unsafe { kf_manager_ptr.as_mut() };
3566                                        kf_manager.reset(clid, clock);
3567                                        if kf_manager.captures_keyframe(clid) {
3568                                            active_keyframe_clid = Some(clid);
3569                                        }
3570                                    }
3571                                    culist.change_state(cu29::copperlist::CopperListState::Processing);
3572                                    culist.msgs.init_zeroed();
3573                                    entry_stage_tx
3574                                        .send(#mission_mod::ParallelWorkerJob {
3575                                            clid,
3576                                            culist,
3577                                        })
3578                                        .map_err(|e| {
3579                                            shutdown.store(true, Ordering::Release);
3580                                            CuError::from("Failed to enqueue CopperList for parallel stage processing")
3581                                                .add_cause(e.to_string().as_str())
3582                                        })?;
3583                                    next_launch_clid += 1;
3584                                    in_flight += 1;
3585                                    if let Some(limiter) = dispatch_limiter.as_mut() {
3586                                        limiter.mark_tick(clock);
3587                                    }
3588                                }
3589
3590                                if STOP_FLAG.load(Ordering::SeqCst) {
3591                                    stop_launching = true;
3592                                }
3593                                continue;
3594                            }
3595                        }
3596
3597                        if in_flight == 0 {
3598                            if stop_launching || fatal_error.is_some() {
3599                                break;
3600                            }
3601
3602                            if free_copperlists.is_empty() {
3603                                free_copperlists.push(cl_manager.wait_reclaim_boxed()?);
3604                                continue;
3605                            }
3606
3607                            if let Some(limiter) = dispatch_limiter.as_ref()
3608                                && !limiter.is_ready(clock)
3609                            {
3610                                limiter.wait_until_ready(clock);
3611                                continue;
3612                            }
3613                        }
3614
3615                        let recv_result = if !stop_launching && fatal_error.is_none() {
3616                            if let Some(limiter) = dispatch_limiter.as_ref() {
3617                                if let Some(remaining) = limiter.remaining(clock)
3618                                    && in_flight > 0
3619                                {
3620                                    done_rx.recv_timeout(std::time::Duration::from(remaining))
3621                                } else {
3622                                    done_rx
3623                                        .recv()
3624                                        .map_err(|_| std::sync::mpsc::RecvTimeoutError::Disconnected)
3625                                }
3626                            } else {
3627                                done_rx
3628                                    .recv()
3629                                    .map_err(|_| std::sync::mpsc::RecvTimeoutError::Disconnected)
3630                            }
3631                        } else {
3632                            done_rx
3633                                .recv()
3634                                .map_err(|_| std::sync::mpsc::RecvTimeoutError::Disconnected)
3635                        };
3636
3637                        let worker_result = match recv_result {
3638                            Ok(worker_result) => worker_result,
3639                            Err(std::sync::mpsc::RecvTimeoutError::Timeout) => {
3640                                if STOP_FLAG.load(Ordering::SeqCst) {
3641                                    stop_launching = true;
3642                                }
3643                                continue;
3644                            }
3645                            Err(std::sync::mpsc::RecvTimeoutError::Disconnected) => {
3646                                shutdown.store(true, Ordering::Release);
3647                                return Err(CuError::from(
3648                                    "Parallel stage worker disconnected unexpectedly",
3649                                ));
3650                            }
3651                        };
3652                        in_flight = in_flight.saturating_sub(1);
3653                        pending_results.insert(worker_result.clid, worker_result);
3654
3655                        while let Some(worker_result) = pending_results.remove(&next_commit_clid) {
3656                            if fatal_error.is_none()
3657                                && parallel_rt.current_commit_clid() != worker_result.clid
3658                            {
3659                                shutdown.store(true, Ordering::Release);
3660                                fatal_error = Some(CuError::from(format!(
3661                                    "Parallel commit checkpoint out of sync: expected {}, got {}",
3662                                    parallel_rt.current_commit_clid(),
3663                                    worker_result.clid
3664                                )));
3665                                stop_launching = true;
3666                            }
3667
3668                            let mut worker_result = worker_result;
3669                            if fatal_error.is_none() {
3670                                match worker_result.outcome {
3671                                    Ok(cu29::curuntime::ProcessStepOutcome::AbortCopperList) => {
3672                                        let mut culist = worker_result
3673                                            .culist
3674                                            .take()
3675                                            .expect("parallel abort result missing CopperList ownership");
3676                                        let mut commit_ctx = cu29::context::CuContext::from_runtime_metadata(
3677                                            clock.clone(),
3678                                            worker_result.clid,
3679                                            instance_id,
3680                                            subsystem_code,
3681                                            #mission_mod::TASK_IDS,
3682                                        );
3683                                        commit_ctx.clear_current_task();
3684                                        let monitor_result = monitor.process_copperlist(
3685                                            &commit_ctx,
3686                                            #mission_mod::MONITOR_LAYOUT.view(&#mission_mod::collect_metadata(&culist)),
3687                                        );
3688                                        match cl_manager.end_of_processing_boxed(culist)? {
3689                                            cu29::curuntime::OwnedCopperListSubmission::Recycled(culist) => {
3690                                                free_copperlists.push(culist);
3691                                            }
3692                                            cu29::curuntime::OwnedCopperListSubmission::Pending => {}
3693                                        }
3694                                        monitor_result?;
3695                                    }
3696                                    Ok(cu29::curuntime::ProcessStepOutcome::Continue) => {
3697                                        let mut culist = worker_result
3698                                            .culist
3699                                            .take()
3700                                            .expect("parallel worker result missing CopperList ownership");
3701                                        let mut commit_ctx = cu29::context::CuContext::from_runtime_metadata(
3702                                            clock.clone(),
3703                                            worker_result.clid,
3704                                            instance_id,
3705                                            subsystem_code,
3706                                            #mission_mod::TASK_IDS,
3707                                        );
3708                                        commit_ctx.clear_current_task();
3709                                        let monitor_result = monitor.process_copperlist(
3710                                            &commit_ctx,
3711                                            #mission_mod::MONITOR_LAYOUT.view(&#mission_mod::collect_metadata(&culist)),
3712                                        );
3713
3714                                        #(#preprocess_logging_calls)*
3715
3716                                        match cl_manager.end_of_processing_boxed(culist)? {
3717                                            cu29::curuntime::OwnedCopperListSubmission::Recycled(culist) => {
3718                                                free_copperlists.push(culist);
3719                                            }
3720                                            cu29::curuntime::OwnedCopperListSubmission::Pending => {}
3721                                        }
3722                                        let keyframe_bytes = {
3723                                            let _keyframe_lock =
3724                                                kf_lock.lock().expect("parallel keyframe lock poisoned");
3725                                            let kf_manager = unsafe { kf_manager_ptr.as_mut() };
3726                                            kf_manager.end_of_processing(worker_result.clid)?;
3727                                            kf_manager.last_encoded_bytes
3728                                        };
3729                                        monitor_result?;
3730                                        let stats = cu29::monitoring::CopperListIoStats {
3731                                            raw_culist_bytes: core::mem::size_of::<CuList>() as u64
3732                                                + cl_manager.last_handle_bytes,
3733                                            handle_bytes: cl_manager.last_handle_bytes,
3734                                            encoded_culist_bytes: cl_manager.last_encoded_bytes,
3735                                            keyframe_bytes,
3736                                            structured_log_bytes_total: ::cu29::prelude::structured_log_bytes_total(),
3737                                            culistid: worker_result.clid,
3738                                        };
3739                                        monitor.observe_copperlist_io(stats);
3740
3741                                        // Postprocess, when present, now runs inside the owning
3742                                        // component stage instead of on the ordered commit path.
3743                                    }
3744                                    Err(error) => {
3745                                        shutdown.store(true, Ordering::Release);
3746                                        stop_launching = true;
3747                                        fatal_error = Some(error);
3748                                        if let Some(mut culist) = worker_result.culist.take() {
3749                                            culist.change_state(cu29::copperlist::CopperListState::Free);
3750                                            free_copperlists.push(culist);
3751                                        }
3752                                    }
3753                                }
3754                            } else if let Some(mut culist) = worker_result.culist.take() {
3755                                culist.change_state(cu29::copperlist::CopperListState::Free);
3756                                free_copperlists.push(culist);
3757                            }
3758
3759                            if active_keyframe_clid == Some(worker_result.clid) {
3760                                active_keyframe_clid = None;
3761                            }
3762                            parallel_rt.release_commit(worker_result.clid + 1);
3763                            next_commit_clid += 1;
3764                        }
3765
3766                        if STOP_FLAG.load(Ordering::SeqCst) {
3767                            stop_launching = true;
3768                        }
3769                    }
3770
3771                    drop(entry_stage_tx);
3772                    free_copperlists.extend(cl_manager.finish_pending_boxed()?);
3773                    if let Some(error) = fatal_error {
3774                        Err(error)
3775                    } else {
3776                        Ok(())
3777                    }
3778                });
3779
3780                if result.is_err() {
3781                    error!("A task errored out: {}", &result);
3782                }
3783                <Self as #app_trait<S, L>>::stop_all_tasks(self, #sim_callback_arg)?;
3784                let _ = self.log_shutdown_completed();
3785                result
3786            }
3787        } else {
3788            quote! {
3789                static STOP_FLAG: AtomicBool = AtomicBool::new(false);
3790
3791                #kill_handler
3792
3793                <Self as #app_trait<S, L>>::start_all_tasks(self, #sim_callback_arg)?;
3794                let result = #run_loop;
3795
3796                if result.is_err() {
3797                    error!("A task errored out: {}", &result);
3798                }
3799                <Self as #app_trait<S, L>>::stop_all_tasks(self, #sim_callback_arg)?;
3800                let _ = self.log_shutdown_completed();
3801                result
3802            }
3803        };
3804        let run_methods: proc_macro2::TokenStream = quote! {
3805
3806            #run_one_iteration {
3807
3808                // Pre-explode the runtime to avoid complexity with partial borrowing in the generated code.
3809                let runtime = &mut self.copper_runtime;
3810                let clock = &runtime.clock;
3811                let instance_id = runtime.instance_id();
3812                let subsystem_code = runtime.subsystem_code();
3813                let execution_probe = &runtime.execution_probe;
3814                let monitor = &mut runtime.monitor;
3815                let tasks = &mut runtime.tasks;
3816                let __cu_bridges = &mut runtime.bridges;
3817                let cl_manager = &mut runtime.copperlists_manager;
3818                let kf_manager = &mut runtime.keyframes_manager;
3819                let iteration_clid = cl_manager.next_cl_id();
3820                let mut ctx = cu29::context::CuContext::from_runtime_metadata(
3821                    clock.clone(),
3822                    iteration_clid,
3823                    instance_id,
3824                    subsystem_code,
3825                    #mission_mod::TASK_IDS,
3826                );
3827                let mut __cu_abort_copperlist = false;
3828
3829                // Preprocess calls can happen at any time, just packed them up front.
3830                #(#preprocess_calls)*
3831
3832                let culist = cl_manager.create()?;
3833                let clid = culist.id;
3834                debug_assert_eq!(clid, iteration_clid);
3835                kf_manager.reset(clid, clock); // beginning of processing, we empty the serialized frozen states of the tasks.
3836                culist.change_state(cu29::copperlist::CopperListState::Processing);
3837                culist.msgs.init_zeroed();
3838                let mut ctx = cu29::context::CuContext::from_runtime_metadata(
3839                    clock.clone(),
3840                    iteration_clid,
3841                    instance_id,
3842                    subsystem_code,
3843                    #mission_mod::TASK_IDS,
3844                );
3845                {
3846                    let msgs = &mut culist.msgs.0;
3847                    '__cu_process_steps: {
3848                    #(#runtime_plan_code)*
3849                    }
3850                } // drop(msgs);
3851                if __cu_abort_copperlist {
3852                    ctx.clear_current_task();
3853                    let monitor_result = monitor.process_copperlist(&ctx, #mission_mod::MONITOR_LAYOUT.view(&#mission_mod::collect_metadata(&culist)));
3854                    cl_manager.end_of_processing(clid)?;
3855                    monitor_result?;
3856                    return Ok(());
3857                }
3858                ctx.clear_current_task();
3859                let monitor_result = monitor.process_copperlist(&ctx, #mission_mod::MONITOR_LAYOUT.view(&#mission_mod::collect_metadata(&culist)));
3860
3861                // here drop the payloads if we don't want them to be logged.
3862                #(#preprocess_logging_calls)*
3863
3864                cl_manager.end_of_processing(clid)?;
3865                kf_manager.end_of_processing(clid)?;
3866                monitor_result?;
3867                let stats = cu29::monitoring::CopperListIoStats {
3868                    raw_culist_bytes: core::mem::size_of::<CuList>() as u64 + cl_manager.last_handle_bytes,
3869                    handle_bytes: cl_manager.last_handle_bytes,
3870                    encoded_culist_bytes: cl_manager.last_encoded_bytes,
3871                    keyframe_bytes: kf_manager.last_encoded_bytes,
3872                    structured_log_bytes_total: ::cu29::prelude::structured_log_bytes_total(),
3873                    culistid: clid,
3874                };
3875                monitor.observe_copperlist_io(stats);
3876
3877                // Postprocess calls can happen at any time, just packed them up at the end.
3878                #(#postprocess_calls)*
3879                Ok(())
3880            }
3881
3882            fn restore_keyframe(&mut self, keyframe: &KeyFrame) -> CuResult<()> {
3883                let runtime = &mut self.copper_runtime;
3884                let clock = &runtime.clock;
3885                let tasks = &mut runtime.tasks;
3886                let __cu_bridges = &mut runtime.bridges;
3887                let config = cu29::bincode::config::standard();
3888                let reader = cu29::bincode::de::read::SliceReader::new(&keyframe.serialized_tasks);
3889                let mut decoder = DecoderImpl::new(reader, config, ());
3890                #(#task_restore_code);*;
3891                #(#bridge_restore_code);*;
3892                Ok(())
3893            }
3894
3895            #start_all_tasks {
3896                let _ = self.log_runtime_lifecycle_event(RuntimeLifecycleEvent::MissionStarted {
3897                    mission: #mission.to_string(),
3898                });
3899                let lifecycle_clid = self.copper_runtime.copperlists_manager.last_cl_id();
3900                let mut ctx = cu29::context::CuContext::from_runtime_metadata(
3901                    self.copper_runtime.clock.clone(),
3902                    lifecycle_clid,
3903                    self.copper_runtime.instance_id(),
3904                    self.copper_runtime.subsystem_code(),
3905                    #mission_mod::TASK_IDS,
3906                );
3907                #(#start_calls)*
3908                ctx.clear_current_task();
3909                self.copper_runtime.monitor.start(&ctx)?;
3910                Ok(())
3911            }
3912
3913            #stop_all_tasks {
3914                let lifecycle_clid = self.copper_runtime.copperlists_manager.last_cl_id();
3915                let mut ctx = cu29::context::CuContext::from_runtime_metadata(
3916                    self.copper_runtime.clock.clone(),
3917                    lifecycle_clid,
3918                    self.copper_runtime.instance_id(),
3919                    self.copper_runtime.subsystem_code(),
3920                    #mission_mod::TASK_IDS,
3921                );
3922                #(#stop_calls)*
3923                ctx.clear_current_task();
3924                self.copper_runtime.monitor.stop(&ctx)?;
3925                self.copper_runtime.copperlists_manager.finish_pending()?;
3926                // TODO(lifecycle): emit typed stop reasons (completed/error/panic/requested)
3927                // once panic/reporting flow is finalized for std and no-std.
3928                let _ = self.log_runtime_lifecycle_event(RuntimeLifecycleEvent::MissionStopped {
3929                    mission: #mission.to_string(),
3930                    reason: "stop_all_tasks".to_string(),
3931                });
3932                Ok(())
3933            }
3934
3935            #run {
3936                #run_body
3937            }
3938        };
3939
3940        let tasks_type = if sim_mode {
3941            quote!(CuSimTasks)
3942        } else {
3943            quote!(CuTasks)
3944        };
3945
3946        let tasks_instanciator_fn = if sim_mode {
3947            quote!(tasks_instanciator_sim)
3948        } else {
3949            quote!(tasks_instanciator)
3950        };
3951
3952        let app_impl_decl = if sim_mode {
3953            quote!(impl<S: SectionStorage + 'static, L: UnifiedLogWrite<S> + 'static> CuSimApplication<S, L> for #application_name)
3954        } else {
3955            quote!(impl<S: SectionStorage + 'static, L: UnifiedLogWrite<S> + 'static> CuApplication<S, L> for #application_name)
3956        };
3957
3958        let simstep_type_decl = if sim_mode {
3959            quote!(
3960                type Step<'z> = SimStep<'z>;
3961            )
3962        } else {
3963            quote!()
3964        };
3965
3966        let mission_id_method = if sim_mode {
3967            quote! {
3968                fn mission_id() -> Option<&'static str> {
3969                    Some(#mission)
3970                }
3971            }
3972        } else {
3973            quote!()
3974        };
3975
3976        let app_resources_struct = quote! {
3977            pub struct AppResources {
3978                pub config: CuConfig,
3979                pub config_source: RuntimeLifecycleConfigSource,
3980                pub resources: ResourceManager,
3981            }
3982        };
3983
3984        let prepare_config_fn = quote! {
3985            #prepare_config_sig {
3986                let config_filename = #config_file;
3987
3988                #[cfg(target_os = "none")]
3989                ::cu29::prelude::info!("CuApp init: config file {}", config_filename);
3990                #[cfg(target_os = "none")]
3991                ::cu29::prelude::info!("CuApp init: loading config");
3992                #config_load_stmt
3993                #copperlist_count_check
3994                #[cfg(target_os = "none")]
3995                ::cu29::prelude::info!("CuApp init: config loaded");
3996                if let Some(runtime) = &config.runtime {
3997                    #[cfg(target_os = "none")]
3998                    ::cu29::prelude::info!(
3999                        "CuApp init: rate_target_hz={}",
4000                        runtime.rate_target_hz.unwrap_or(0)
4001                    );
4002                } else {
4003                    #[cfg(target_os = "none")]
4004                    ::cu29::prelude::info!("CuApp init: rate_target_hz=none");
4005                }
4006
4007                Ok((config, config_source))
4008            }
4009        };
4010
4011        let prepare_resources_fn = quote! {
4012            #prepare_resources_sig {
4013                let (config, config_source) = #prepare_config_call;
4014
4015                #[cfg(target_os = "none")]
4016                ::cu29::prelude::info!("CuApp init: building resources");
4017                let resources = #mission_mod::resources_instanciator(&config)?;
4018                #[cfg(target_os = "none")]
4019                ::cu29::prelude::info!("CuApp init: resources ready");
4020
4021                Ok(AppResources {
4022                    config,
4023                    config_source,
4024                    resources,
4025                })
4026            }
4027        };
4028
4029        let new_with_resources_compat_fn = if sim_mode {
4030            quote! {
4031                pub fn new_with_resources<S: SectionStorage + 'static, L: UnifiedLogWrite<S> + 'static>(
4032                    clock: RobotClock,
4033                    unified_logger: Arc<Mutex<L>>,
4034                    app_resources: AppResources,
4035                    instance_id: u32,
4036                    sim_callback: &mut impl FnMut(SimStep) -> SimOverride,
4037                ) -> CuResult<Self> {
4038                    Self::build_with_resources(
4039                        clock,
4040                        unified_logger,
4041                        app_resources,
4042                        instance_id,
4043                        sim_callback,
4044                    )
4045                }
4046            }
4047        } else {
4048            quote! {
4049                pub fn new_with_resources<S: SectionStorage + 'static, L: UnifiedLogWrite<S> + 'static>(
4050                    clock: RobotClock,
4051                    unified_logger: Arc<Mutex<L>>,
4052                    app_resources: AppResources,
4053                    instance_id: u32,
4054                ) -> CuResult<Self> {
4055                    Self::build_with_resources(clock, unified_logger, app_resources, instance_id)
4056                }
4057            }
4058        };
4059
4060        let build_with_resources_fn = quote! {
4061            #build_with_resources_sig {
4062                let AppResources {
4063                    config,
4064                    config_source,
4065                    resources,
4066                } = app_resources;
4067
4068                let structured_stream = ::cu29::prelude::stream_write::<
4069                    ::cu29::prelude::CuLogEntry,
4070                    S,
4071                >(
4072                    unified_logger.clone(),
4073                    ::cu29::prelude::UnifiedLogType::StructuredLogLine,
4074                    4096 * 10,
4075                )?;
4076                let logger_runtime = ::cu29::prelude::LoggerRuntime::init(
4077                    clock.clone(),
4078                    structured_stream,
4079                    None::<::cu29::prelude::NullLog>,
4080                );
4081
4082                // For simple cases we can say the section is just a bunch of Copper Lists.
4083                // But we can now have allocations outside of it so we can override it from the config.
4084                let mut default_section_size = size_of::<super::#mission_mod::CuList>() * 64;
4085                // Check if there is a logging configuration with section_size_mib
4086                if let Some(section_size_mib) = config.logging.as_ref().and_then(|l| l.section_size_mib) {
4087                    // Convert MiB to bytes
4088                    default_section_size = section_size_mib as usize * 1024usize * 1024usize;
4089                }
4090                #[cfg(target_os = "none")]
4091                ::cu29::prelude::info!(
4092                    "CuApp new: copperlist section size={}",
4093                    default_section_size
4094                );
4095                #[cfg(target_os = "none")]
4096                ::cu29::prelude::info!("CuApp new: creating copperlist stream");
4097                let copperlist_stream = stream_write::<#mission_mod::CuList, S>(
4098                    unified_logger.clone(),
4099                    UnifiedLogType::CopperList,
4100                    default_section_size,
4101                    // the 2 sizes are not directly related as we encode the CuList but we can
4102                    // assume the encoded size is close or lower than the non encoded one
4103                    // This is to be sure we have the size of at least a Culist and some.
4104                )?;
4105                #[cfg(target_os = "none")]
4106                ::cu29::prelude::info!("CuApp new: copperlist stream ready");
4107
4108                #[cfg(target_os = "none")]
4109                ::cu29::prelude::info!("CuApp new: creating keyframes stream");
4110                let keyframes_stream = stream_write::<KeyFrame, S>(
4111                    unified_logger.clone(),
4112                    UnifiedLogType::FrozenTasks,
4113                    1024 * 1024 * 10, // 10 MiB
4114                )?;
4115                #[cfg(target_os = "none")]
4116                ::cu29::prelude::info!("CuApp new: keyframes stream ready");
4117
4118                #[cfg(target_os = "none")]
4119                ::cu29::prelude::info!("CuApp new: creating runtime lifecycle stream");
4120                let mut runtime_lifecycle_stream = stream_write::<RuntimeLifecycleRecord, S>(
4121                    unified_logger.clone(),
4122                    UnifiedLogType::RuntimeLifecycle,
4123                    1024 * 64, // 64 KiB
4124                )?;
4125                let effective_config_ron = config
4126                    .serialize_ron()
4127                    .unwrap_or_else(|_| "<failed to serialize config>".to_string());
4128                ::cu29::logcodec::set_effective_config_ron::<super::#mission_mod::CuStampedDataSet>(&effective_config_ron);
4129                let stack_info = RuntimeLifecycleStackInfo {
4130                    app_name: env!("CARGO_PKG_NAME").to_string(),
4131                    app_version: env!("CARGO_PKG_VERSION").to_string(),
4132                    git_commit: #git_commit_tokens,
4133                    git_dirty: #git_dirty_tokens,
4134                    subsystem_id: #application_name::subsystem().id().map(str::to_string),
4135                    subsystem_code: #application_name::subsystem().code(),
4136                    instance_id,
4137                };
4138                runtime_lifecycle_stream.log(&RuntimeLifecycleRecord {
4139                    timestamp: clock.now(),
4140                    event: RuntimeLifecycleEvent::Instantiated {
4141                        config_source,
4142                        effective_config_ron,
4143                        stack: stack_info,
4144                    },
4145                })?;
4146                #[cfg(target_os = "none")]
4147                ::cu29::prelude::info!("CuApp new: runtime lifecycle stream ready");
4148
4149                #[cfg(target_os = "none")]
4150                ::cu29::prelude::info!("CuApp new: building runtime");
4151                let copper_runtime = CuRuntimeBuilder::<#mission_mod::#tasks_type, #mission_mod::CuBridges, #mission_mod::CuStampedDataSet, #monitor_type, #copperlist_count_tokens, _, _, _, _, _>::new(
4152                    clock,
4153                    &config,
4154                    #mission,
4155                    CuRuntimeParts::new(
4156                        #mission_mod::#tasks_instanciator_fn,
4157                        #mission_mod::MONITORED_COMPONENTS,
4158                        #mission_mod::CULIST_COMPONENT_MAPPING,
4159                        #parallel_rt_metadata_arg
4160                        #mission_mod::monitor_instanciator,
4161                        #mission_mod::bridges_instanciator,
4162                    ),
4163                    copperlist_stream,
4164                    keyframes_stream,
4165                )
4166                .with_subsystem(#application_name::subsystem())
4167                .with_instance_id(instance_id)
4168                .with_resources(resources)
4169                .build()?;
4170                #[cfg(target_os = "none")]
4171                ::cu29::prelude::info!("CuApp new: runtime built");
4172
4173                let application = Ok(#application_name {
4174                    copper_runtime,
4175                    runtime_lifecycle_stream: Some(Box::new(runtime_lifecycle_stream)),
4176                    logger_runtime,
4177                });
4178
4179                #sim_callback_on_new
4180
4181                application
4182            }
4183        };
4184
4185        let app_inherent_impl = quote! {
4186            impl #application_name {
4187                const SUBSYSTEM: cu29::prelude::app::Subsystem =
4188                    cu29::prelude::app::Subsystem::new(#subsystem_id_tokens, #subsystem_code_literal);
4189
4190                #[inline]
4191                pub fn subsystem() -> cu29::prelude::app::Subsystem {
4192                    Self::SUBSYSTEM
4193                }
4194
4195                pub fn original_config() -> String {
4196                    #copper_config_content.to_string()
4197                }
4198
4199                pub fn register_reflect_types(registry: &mut cu29::reflect::TypeRegistry) {
4200                    #(#reflect_type_registration_calls)*
4201                }
4202
4203                /// Returns a clone of the runtime clock handle.
4204                #[inline]
4205                pub fn clock(&self) -> cu29::clock::RobotClock {
4206                    self.copper_runtime.clock()
4207                }
4208
4209                /// Log one runtime lifecycle event with the current runtime timestamp.
4210                pub fn log_runtime_lifecycle_event(
4211                    &mut self,
4212                    event: RuntimeLifecycleEvent,
4213                ) -> CuResult<()> {
4214                    let timestamp = self.copper_runtime.clock.now();
4215                    let Some(stream) = self.runtime_lifecycle_stream.as_mut() else {
4216                        return Err(CuError::from("Runtime lifecycle stream is not initialized"));
4217                    };
4218                    stream.log(&RuntimeLifecycleRecord { timestamp, event })
4219                }
4220
4221                /// Convenience helper for manual execution loops to mark graceful shutdown.
4222                // TODO(lifecycle): add helper(s) for panic/error stop reporting once we wire
4223                // RuntimeLifecycleEvent::Panic across std/no-std execution models.
4224                pub fn log_shutdown_completed(&mut self) -> CuResult<()> {
4225                    self.log_runtime_lifecycle_event(RuntimeLifecycleEvent::ShutdownCompleted)
4226                }
4227
4228                #prepare_config_fn
4229                #prepare_resources_compat_fn
4230                #prepare_resources_fn
4231                #init_resources_compat_fn
4232                #new_with_resources_compat_fn
4233                #build_with_resources_fn
4234
4235                /// Mutable access to the underlying runtime (used by tools such as deterministic re-sim).
4236                #[inline]
4237                pub fn copper_runtime_mut(&mut self) -> &mut CuRuntime<#mission_mod::#tasks_type, #mission_mod::CuBridges, #mission_mod::CuStampedDataSet, #monitor_type, #copperlist_count_tokens> {
4238                    &mut self.copper_runtime
4239                }
4240            }
4241        };
4242
4243        let app_metadata_impl = quote! {
4244            impl cu29::prelude::app::CuSubsystemMetadata for #application_name {
4245                fn subsystem() -> cu29::prelude::app::Subsystem {
4246                    #application_name::subsystem()
4247                }
4248            }
4249        };
4250
4251        let app_reflect_impl = quote! {
4252            impl cu29::reflect::ReflectTaskIntrospection for #application_name {
4253                fn reflect_task(&self, task_id: &str) -> Option<&dyn cu29::reflect::Reflect> {
4254                    match task_id {
4255                        #(#task_reflect_read_arms)*
4256                        _ => None,
4257                    }
4258                }
4259
4260                fn reflect_task_mut(
4261                    &mut self,
4262                    task_id: &str,
4263                ) -> Option<&mut dyn cu29::reflect::Reflect> {
4264                    match task_id {
4265                        #(#task_reflect_write_arms)*
4266                        _ => None,
4267                    }
4268                }
4269
4270                fn register_reflect_types(registry: &mut cu29::reflect::TypeRegistry) {
4271                    #application_name::register_reflect_types(registry);
4272                }
4273            }
4274        };
4275
4276        let app_runtime_copperlist_impl = quote! {
4277            impl cu29::app::CurrentRuntimeCopperList<#mission_mod::CuStampedDataSet>
4278                for #application_name
4279            {
4280                fn current_runtime_copperlist_bytes(&self) -> Option<&[u8]> {
4281                    self.copper_runtime.copperlists_manager.last_completed_encoded()
4282                }
4283
4284                fn set_current_runtime_copperlist_bytes(
4285                    &mut self,
4286                    snapshot: Option<Vec<u8>>,
4287                ) {
4288                    self.copper_runtime
4289                        .copperlists_manager
4290                        .set_last_completed_encoded(snapshot);
4291                }
4292            }
4293        };
4294
4295        #[cfg(feature = "std")]
4296        #[cfg(feature = "macro_debug")]
4297        eprintln!("[build result]");
4298        let application_impl = quote! {
4299            #app_impl_decl {
4300                #simstep_type_decl
4301
4302                fn get_original_config() -> String {
4303                    Self::original_config()
4304                }
4305
4306                #mission_id_method
4307
4308                #run_methods
4309            }
4310        };
4311
4312        let recorded_replay_app_impl = if sim_mode {
4313            Some(quote! {
4314                impl<S: SectionStorage + 'static, L: UnifiedLogWrite<S> + 'static>
4315                    CuRecordedReplayApplication<S, L> for #application_name
4316                {
4317                    type RecordedDataSet = #mission_mod::CuStampedDataSet;
4318
4319                    fn replay_recorded_copperlist(
4320                        &mut self,
4321                        clock_mock: &RobotClockMock,
4322                        copperlist: &CopperList<Self::RecordedDataSet>,
4323                        keyframe: Option<&KeyFrame>,
4324                    ) -> CuResult<()> {
4325                        if let Some(keyframe) = keyframe {
4326                            if keyframe.culistid != copperlist.id {
4327                                return Err(CuError::from(format!(
4328                                    "Recorded keyframe culistid {} does not match copperlist {}",
4329                                    keyframe.culistid, copperlist.id
4330                                )));
4331                            }
4332
4333                            if !self.copper_runtime_mut().captures_keyframe(copperlist.id) {
4334                                return Err(CuError::from(format!(
4335                                    "CopperList {} is not configured to capture a keyframe in this runtime",
4336                                    copperlist.id
4337                                )));
4338                            }
4339
4340                            self.copper_runtime_mut()
4341                                .set_forced_keyframe_timestamp(keyframe.timestamp);
4342                            self.copper_runtime_mut().lock_keyframe(keyframe);
4343                            clock_mock.set_value(keyframe.timestamp.as_nanos());
4344                        } else {
4345                            let timestamp =
4346                                cu29::simulation::recorded_copperlist_timestamp(copperlist)
4347                                    .ok_or_else(|| {
4348                                        CuError::from(format!(
4349                                            "Recorded copperlist {} has no process_time.start timestamps",
4350                                            copperlist.id
4351                                        ))
4352                                    })?;
4353                            clock_mock.set_value(timestamp.as_nanos());
4354                        }
4355
4356                        let mut sim_callback = |step: SimStep<'_>| -> SimOverride {
4357                            #mission_mod::recorded_replay_step(step, copperlist)
4358                        };
4359                        <Self as CuSimApplication<S, L>>::run_one_iteration(self, &mut sim_callback)
4360                    }
4361                }
4362            })
4363        } else {
4364            None
4365        };
4366
4367        let distributed_replay_app_impl = if sim_mode {
4368            Some(quote! {
4369                impl<S: SectionStorage + 'static, L: UnifiedLogWrite<S> + 'static>
4370                    cu29::prelude::app::CuDistributedReplayApplication<S, L> for #application_name
4371                {
4372                    fn build_distributed_replay(
4373                        clock: cu29::clock::RobotClock,
4374                        unified_logger: std::sync::Arc<std::sync::Mutex<L>>,
4375                        instance_id: u32,
4376                        config_override: Option<cu29::config::CuConfig>,
4377                    ) -> CuResult<Self> {
4378                        let mut noop =
4379                            |_step: SimStep<'_>| cu29::simulation::SimOverride::ExecuteByRuntime;
4380                        let builder = Self::builder()
4381                            .with_logger::<S, L>(unified_logger)
4382                            .with_clock(clock)
4383                            .with_instance_id(instance_id);
4384                        let builder = if let Some(config_override) = config_override {
4385                            builder.with_config(config_override)
4386                        } else {
4387                            builder
4388                        };
4389                        builder.with_sim_callback(&mut noop).build()
4390                    }
4391                }
4392            })
4393        } else {
4394            None
4395        };
4396
4397        let builder_prepare_config_call = if std {
4398            quote! { #application_name::prepare_config(self.instance_id, self.config_override)? }
4399        } else {
4400            quote! {{
4401                let _ = self.config_override;
4402                #application_name::prepare_config()?
4403            }}
4404        };
4405
4406        let builder_with_config_method = if std {
4407            Some(quote! {
4408                #[allow(dead_code)]
4409                pub fn with_config(mut self, config_override: CuConfig) -> Self {
4410                    self.config_override = Some(config_override);
4411                    self
4412                }
4413            })
4414        } else {
4415            None
4416        };
4417
4418        let builder_default_clock = if std {
4419            quote! { Some(RobotClock::default()) }
4420        } else {
4421            quote! { None }
4422        };
4423
4424        let (
4425            builder_struct,
4426            builder_impl,
4427            builder_ctor,
4428            builder_log_path_generics,
4429            builder_sim_callback_method,
4430            builder_build_sim_callback_arg,
4431        ) = if sim_mode {
4432            (
4433                quote! {
4434                    #[allow(dead_code)]
4435                    pub struct #builder_name<'a, F, S, L, R>
4436                    where
4437                        S: SectionStorage + 'static,
4438                        L: UnifiedLogWrite<S> + 'static,
4439                        R: FnOnce(&CuConfig) -> CuResult<ResourceManager>,
4440                        F: FnMut(SimStep) -> SimOverride,
4441                    {
4442                        clock: Option<RobotClock>,
4443                        unified_logger: Arc<Mutex<L>>,
4444                        instance_id: u32,
4445                        config_override: Option<CuConfig>,
4446                        resources_factory: R,
4447                        sim_callback: Option<&'a mut F>,
4448                        _storage: core::marker::PhantomData<S>,
4449                    }
4450                },
4451                quote! {
4452                    impl<'a, F, S, L, R> #builder_name<'a, F, S, L, R>
4453                    where
4454                        S: SectionStorage + 'static,
4455                        L: UnifiedLogWrite<S> + 'static,
4456                        R: FnOnce(&CuConfig) -> CuResult<ResourceManager>,
4457                        F: FnMut(SimStep) -> SimOverride,
4458                },
4459                quote! {
4460                    #[allow(dead_code)]
4461                    pub fn builder<'a, F>() -> #builder_name<'a, F, cu29::prelude::NoopSectionStorage, cu29::prelude::NoopLogger, fn(&CuConfig) -> CuResult<ResourceManager>>
4462                    where
4463                        F: FnMut(SimStep) -> SimOverride,
4464                    {
4465                        #builder_name {
4466                            clock: #builder_default_clock,
4467                            unified_logger: Arc::new(Mutex::new(cu29::prelude::NoopLogger::new())),
4468                            instance_id: 0,
4469                            config_override: None,
4470                            resources_factory: #mission_mod::resources_instanciator as fn(&CuConfig) -> CuResult<ResourceManager>,
4471                            sim_callback: None,
4472                            _storage: core::marker::PhantomData,
4473                        }
4474                    }
4475                },
4476                quote! {'a, F, MmapSectionStorage, UnifiedLoggerWrite, R},
4477                Some(quote! {
4478                    #[allow(dead_code)]
4479                    pub fn with_sim_callback(mut self, sim_callback: &'a mut F) -> Self {
4480                        self.sim_callback = Some(sim_callback);
4481                        self
4482                    }
4483                }),
4484                Some(quote! {
4485                    self.sim_callback
4486                        .ok_or(CuError::from("Sim callback missing from builder"))?,
4487                }),
4488            )
4489        } else {
4490            (
4491                quote! {
4492                    #[allow(dead_code)]
4493                    pub struct #builder_name<S, L, R>
4494                    where
4495                        S: SectionStorage + 'static,
4496                        L: UnifiedLogWrite<S> + 'static,
4497                        R: FnOnce(&CuConfig) -> CuResult<ResourceManager>,
4498                    {
4499                        clock: Option<RobotClock>,
4500                        unified_logger: Arc<Mutex<L>>,
4501                        instance_id: u32,
4502                        config_override: Option<CuConfig>,
4503                        resources_factory: R,
4504                        _storage: core::marker::PhantomData<S>,
4505                    }
4506                },
4507                quote! {
4508                    impl<S, L, R> #builder_name<S, L, R>
4509                    where
4510                        S: SectionStorage + 'static,
4511                        L: UnifiedLogWrite<S> + 'static,
4512                        R: FnOnce(&CuConfig) -> CuResult<ResourceManager>,
4513                },
4514                quote! {
4515                    #[allow(dead_code)]
4516                    pub fn builder() -> #builder_name<cu29::prelude::NoopSectionStorage, cu29::prelude::NoopLogger, fn(&CuConfig) -> CuResult<ResourceManager>> {
4517                        #builder_name {
4518                            clock: #builder_default_clock,
4519                            unified_logger: Arc::new(Mutex::new(cu29::prelude::NoopLogger::new())),
4520                            instance_id: 0,
4521                            config_override: None,
4522                            resources_factory: #mission_mod::resources_instanciator as fn(&CuConfig) -> CuResult<ResourceManager>,
4523                            _storage: core::marker::PhantomData,
4524                        }
4525                    }
4526                },
4527                quote! {MmapSectionStorage, UnifiedLoggerWrite, R},
4528                None,
4529                None,
4530            )
4531        };
4532
4533        let builder_with_logger_generics = if sim_mode {
4534            quote! {'a, F, S2, L2, R}
4535        } else {
4536            quote! {S2, L2, R}
4537        };
4538
4539        let builder_with_resources_generics = if sim_mode {
4540            quote! {'a, F, S, L, R2}
4541        } else {
4542            quote! {S, L, R2}
4543        };
4544
4545        let builder_sim_callback_field_copy = if sim_mode {
4546            Some(quote! {
4547                sim_callback: self.sim_callback,
4548            })
4549        } else {
4550            None
4551        };
4552
4553        let builder_with_log_path_method = if std {
4554            Some(quote! {
4555                #[allow(dead_code)]
4556                pub fn with_log_path(
4557                    self,
4558                    path: impl AsRef<std::path::Path>,
4559                    slab_size: Option<usize>,
4560                ) -> CuResult<#builder_name<#builder_log_path_generics>> {
4561                    let preallocated_size = slab_size.unwrap_or(1024 * 1024 * 10);
4562                    let logger = cu29::prelude::UnifiedLoggerBuilder::new()
4563                        .write(true)
4564                        .create(true)
4565                        .file_base_name(path.as_ref())
4566                        .preallocated_size(preallocated_size)
4567                        .build()
4568                        .map_err(|e| CuError::new_with_cause("Failed to create unified logger", e))?;
4569                    let logger = match logger {
4570                        cu29::prelude::UnifiedLogger::Write(logger) => logger,
4571                        cu29::prelude::UnifiedLogger::Read(_) => {
4572                            return Err(CuError::from(
4573                                "UnifiedLoggerBuilder did not create a write-capable logger",
4574                            ));
4575                        }
4576                    };
4577                    Ok(self.with_logger::<MmapSectionStorage, UnifiedLoggerWrite>(Arc::new(Mutex::new(
4578                        logger,
4579                    ))))
4580                }
4581            })
4582        } else {
4583            None
4584        };
4585
4586        let builder_with_unified_logger_method = if std {
4587            Some(quote! {
4588                #[allow(dead_code)]
4589                pub fn with_unified_logger(
4590                    self,
4591                    unified_logger: Arc<Mutex<UnifiedLoggerWrite>>,
4592                ) -> #builder_name<#builder_log_path_generics> {
4593                    self.with_logger::<MmapSectionStorage, UnifiedLoggerWrite>(unified_logger)
4594                }
4595            })
4596        } else {
4597            None
4598        };
4599
4600        // backward compat on std non-parameterized impl.
4601        let std_application_impl = if sim_mode {
4602            // sim mode
4603            Some(quote! {
4604                        impl #application_name {
4605                            pub fn start_all_tasks(&mut self, sim_callback: &mut impl FnMut(SimStep) -> SimOverride) -> CuResult<()> {
4606                                <Self as #app_trait<MmapSectionStorage, UnifiedLoggerWrite>>::start_all_tasks(self, sim_callback)
4607                            }
4608                            pub fn run_one_iteration(&mut self, sim_callback: &mut impl FnMut(SimStep) -> SimOverride) -> CuResult<()> {
4609                                <Self as #app_trait<MmapSectionStorage, UnifiedLoggerWrite>>::run_one_iteration(self, sim_callback)
4610                            }
4611                            pub fn run(&mut self, sim_callback: &mut impl FnMut(SimStep) -> SimOverride) -> CuResult<()> {
4612                                <Self as #app_trait<MmapSectionStorage, UnifiedLoggerWrite>>::run(self, sim_callback)
4613                            }
4614                            pub fn stop_all_tasks(&mut self, sim_callback: &mut impl FnMut(SimStep) -> SimOverride) -> CuResult<()> {
4615                                <Self as #app_trait<MmapSectionStorage, UnifiedLoggerWrite>>::stop_all_tasks(self, sim_callback)
4616                            }
4617                            pub fn replay_recorded_copperlist(
4618                                &mut self,
4619                                clock_mock: &RobotClockMock,
4620                                copperlist: &CopperList<CuStampedDataSet>,
4621                                keyframe: Option<&KeyFrame>,
4622                            ) -> CuResult<()> {
4623                                <Self as CuRecordedReplayApplication<MmapSectionStorage, UnifiedLoggerWrite>>::replay_recorded_copperlist(
4624                                    self,
4625                                    clock_mock,
4626                                    copperlist,
4627                                    keyframe,
4628                                )
4629                            }
4630                        }
4631            })
4632        } else if std {
4633            // std and normal mode, we use the memory mapped starage for those
4634            Some(quote! {
4635                        impl #application_name {
4636                            pub fn start_all_tasks(&mut self) -> CuResult<()> {
4637                                <Self as #app_trait<MmapSectionStorage, UnifiedLoggerWrite>>::start_all_tasks(self)
4638                            }
4639                            pub fn run_one_iteration(&mut self) -> CuResult<()> {
4640                                <Self as #app_trait<MmapSectionStorage, UnifiedLoggerWrite>>::run_one_iteration(self)
4641                            }
4642                            pub fn run(&mut self) -> CuResult<()> {
4643                                <Self as #app_trait<MmapSectionStorage, UnifiedLoggerWrite>>::run(self)
4644                            }
4645                            pub fn stop_all_tasks(&mut self) -> CuResult<()> {
4646                                <Self as #app_trait<MmapSectionStorage, UnifiedLoggerWrite>>::stop_all_tasks(self)
4647                            }
4648                        }
4649            })
4650        } else {
4651            None // if no-std, let the user figure our the correct logger type they need to provide anyway.
4652        };
4653
4654        let application_builder = Some(quote! {
4655            #builder_struct
4656
4657            #builder_impl
4658            {
4659                #[allow(dead_code)]
4660                pub fn with_clock(mut self, clock: RobotClock) -> Self {
4661                    self.clock = Some(clock);
4662                    self
4663                }
4664
4665                #[allow(dead_code)]
4666                pub fn with_logger<S2, L2>(
4667                    self,
4668                    unified_logger: Arc<Mutex<L2>>,
4669                ) -> #builder_name<#builder_with_logger_generics>
4670                where
4671                    S2: SectionStorage + 'static,
4672                    L2: UnifiedLogWrite<S2> + 'static,
4673                {
4674                    #builder_name {
4675                        clock: self.clock,
4676                        unified_logger,
4677                        instance_id: self.instance_id,
4678                        config_override: self.config_override,
4679                        resources_factory: self.resources_factory,
4680                        #builder_sim_callback_field_copy
4681                        _storage: core::marker::PhantomData,
4682                    }
4683                }
4684
4685                #builder_with_unified_logger_method
4686
4687                #[allow(dead_code)]
4688                pub fn with_instance_id(mut self, instance_id: u32) -> Self {
4689                    self.instance_id = instance_id;
4690                    self
4691                }
4692
4693                pub fn with_resources<R2>(self, resources_factory: R2) -> #builder_name<#builder_with_resources_generics>
4694                where
4695                    R2: FnOnce(&CuConfig) -> CuResult<ResourceManager>,
4696                {
4697                    #builder_name {
4698                        clock: self.clock,
4699                        unified_logger: self.unified_logger,
4700                        instance_id: self.instance_id,
4701                        config_override: self.config_override,
4702                        resources_factory,
4703                        #builder_sim_callback_field_copy
4704                        _storage: core::marker::PhantomData,
4705                    }
4706                }
4707
4708                #builder_with_config_method
4709                #builder_with_log_path_method
4710                #builder_sim_callback_method
4711
4712                #[allow(dead_code)]
4713                pub fn build(self) -> CuResult<#application_name> {
4714                    let clock = self
4715                        .clock
4716                        .ok_or(CuError::from("Clock missing from builder"))?;
4717                    let (config, config_source) = #builder_prepare_config_call;
4718                    let resources = (self.resources_factory)(&config)?;
4719                    let app_resources = AppResources {
4720                        config,
4721                        config_source,
4722                        resources,
4723                    };
4724                    #application_name::build_with_resources(
4725                        clock,
4726                        self.unified_logger,
4727                        app_resources,
4728                        self.instance_id,
4729                        #builder_build_sim_callback_arg
4730                    )
4731                }
4732            }
4733        });
4734
4735        let app_builder_inherent_impl = quote! {
4736            impl #application_name {
4737                #builder_ctor
4738            }
4739        };
4740
4741        let sim_imports = if sim_mode {
4742            Some(quote! {
4743                use cu29::simulation::SimOverride;
4744                use cu29::simulation::CuTaskCallbackState;
4745                use cu29::simulation::CuSimSrcTask;
4746                use cu29::simulation::CuSimSinkTask;
4747                use cu29::simulation::CuSimBridge;
4748                use cu29::prelude::app::CuSimApplication;
4749                use cu29::prelude::app::CuRecordedReplayApplication;
4750                use cu29::cubridge::BridgeChannelSet;
4751            })
4752        } else {
4753            None
4754        };
4755
4756        let sim_tasks = if sim_mode {
4757            Some(quote! {
4758                // This is the variation with stubs for the sources and sinks in simulation mode.
4759                // Not used if the used doesn't generate Sim.
4760                pub type CuSimTasks = #task_types_tuple_sim;
4761            })
4762        } else {
4763            None
4764        };
4765
4766        let sim_inst_body = if task_sim_instances_init_code.is_empty() {
4767            quote! {
4768                let _ = resources;
4769                Ok(())
4770            }
4771        } else {
4772            quote! { Ok(( #(#task_sim_instances_init_code),*, )) }
4773        };
4774
4775        let sim_tasks_instanciator = if sim_mode {
4776            Some(quote! {
4777                pub fn tasks_instanciator_sim(
4778                    all_instances_configs: Vec<Option<&ComponentConfig>>,
4779                    resources: &mut ResourceManager,
4780                ) -> CuResult<CuSimTasks> {
4781                    #sim_inst_body
4782            }})
4783        } else {
4784            None
4785        };
4786
4787        let tasks_inst_body_std = if task_instances_init_code.is_empty() {
4788            quote! {
4789                let _ = resources;
4790                Ok(())
4791            }
4792        } else {
4793            quote! { Ok(( #(#task_instances_init_code),*, )) }
4794        };
4795
4796        let tasks_inst_body_nostd = if task_instances_init_code.is_empty() {
4797            quote! {
4798                let _ = resources;
4799                Ok(())
4800            }
4801        } else {
4802            quote! { Ok(( #(#task_instances_init_code),*, )) }
4803        };
4804
4805        let tasks_instanciator = if std {
4806            quote! {
4807                pub fn tasks_instanciator<'c>(
4808                    all_instances_configs: Vec<Option<&'c ComponentConfig>>,
4809                    resources: &mut ResourceManager,
4810                ) -> CuResult<CuTasks> {
4811                    #tasks_inst_body_std
4812                }
4813            }
4814        } else {
4815            // no thread pool in the no-std impl
4816            quote! {
4817                pub fn tasks_instanciator<'c>(
4818                    all_instances_configs: Vec<Option<&'c ComponentConfig>>,
4819                    resources: &mut ResourceManager,
4820                ) -> CuResult<CuTasks> {
4821                    #tasks_inst_body_nostd
4822                }
4823            }
4824        };
4825
4826        let imports = if std {
4827            quote! {
4828                use cu29::rayon::ThreadPool;
4829                use cu29::cuasynctask::CuAsyncSrcTask;
4830                use cu29::cuasynctask::CuAsyncTask;
4831                use cu29::resource::{ResourceBindings, ResourceManager};
4832                use cu29::prelude::SectionStorage;
4833                use cu29::prelude::UnifiedLoggerWrite;
4834                use cu29::prelude::memmap::MmapSectionStorage;
4835                use std::fmt::{Debug, Formatter};
4836                use std::fmt::Result as FmtResult;
4837                use std::mem::size_of;
4838                use std::boxed::Box;
4839                use std::sync::Arc;
4840                use std::sync::atomic::{AtomicBool, Ordering};
4841                use std::sync::Mutex;
4842            }
4843        } else {
4844            quote! {
4845                use alloc::boxed::Box;
4846                use alloc::sync::Arc;
4847                use alloc::string::String;
4848                use alloc::string::ToString;
4849                use core::sync::atomic::{AtomicBool, Ordering};
4850                use core::fmt::{Debug, Formatter};
4851                use core::fmt::Result as FmtResult;
4852                use core::mem::size_of;
4853                use spin::Mutex;
4854                use cu29::prelude::SectionStorage;
4855                use cu29::resource::{ResourceBindings, ResourceManager};
4856            }
4857        };
4858
4859        let task_mapping_defs = task_resource_mappings.defs.clone();
4860        let bridge_mapping_defs = bridge_resource_mappings.defs.clone();
4861
4862        // Convert the modified struct back into a TokenStream
4863        let mission_mod_tokens = quote! {
4864            mod #mission_mod {
4865                use super::*;  // import the modules the main app did.
4866
4867                use cu29::bincode::Encode;
4868                use cu29::bincode::enc::Encoder;
4869                use cu29::bincode::error::EncodeError;
4870                use cu29::bincode::Decode;
4871                use cu29::bincode::de::Decoder;
4872                use cu29::bincode::de::DecoderImpl;
4873                use cu29::bincode::error::DecodeError;
4874                use cu29::clock::RobotClock;
4875                use cu29::clock::RobotClockMock;
4876                use cu29::config::CuConfig;
4877                use cu29::config::ComponentConfig;
4878                use cu29::curuntime::CuRuntime;
4879                use cu29::curuntime::CuRuntimeBuilder;
4880                use cu29::curuntime::CuRuntimeParts;
4881                use cu29::curuntime::KeyFrame;
4882                use cu29::curuntime::RuntimeLifecycleConfigSource;
4883                use cu29::curuntime::RuntimeLifecycleEvent;
4884                use cu29::curuntime::RuntimeLifecycleRecord;
4885                use cu29::curuntime::RuntimeLifecycleStackInfo;
4886                use cu29::CuResult;
4887                use cu29::CuError;
4888                use cu29::cutask::CuSrcTask;
4889                use cu29::cutask::CuSinkTask;
4890                use cu29::cutask::CuTask;
4891                use cu29::cutask::CuMsg;
4892                use cu29::cutask::CuMsgMetadata;
4893                use cu29::copperlist::CopperList;
4894                use cu29::monitoring::CuMonitor; // Trait import.
4895                use cu29::monitoring::CuComponentState;
4896                use cu29::monitoring::Decision;
4897                use cu29::prelude::app::CuApplication;
4898                use cu29::prelude::debug;
4899                use cu29::prelude::stream_write;
4900                use cu29::prelude::UnifiedLogType;
4901                use cu29::prelude::UnifiedLogWrite;
4902                use cu29::prelude::WriteStream;
4903
4904                #imports
4905
4906                #sim_imports
4907
4908                // Not used if a monitor is present
4909                #[allow(unused_imports)]
4910                use cu29::monitoring::NoMonitor;
4911
4912                // This is the heart of everything.
4913                // CuTasks is the list of all the tasks types.
4914                // CuList is a CopperList with the list of all the messages types as msgs.
4915                pub type CuTasks = #task_types_tuple;
4916                pub type CuBridges = #bridges_type_tokens;
4917                #sim_bridge_channel_defs
4918                #resources_module
4919                #resources_instanciator_fn
4920                #task_mapping_defs
4921                #bridge_mapping_defs
4922
4923                #sim_tasks
4924                #sim_support
4925                #recorded_replay_support
4926                #sim_tasks_instanciator
4927
4928                pub const TASK_IDS: &'static [&'static str] = &[#( #task_ids ),*];
4929                pub const MONITORED_COMPONENTS: &'static [cu29::monitoring::MonitorComponentMetadata] =
4930                    &[#( #monitored_component_entries ),*];
4931                pub const CULIST_COMPONENT_MAPPING: &'static [cu29::monitoring::ComponentId] =
4932                    &[#( cu29::monitoring::ComponentId::new(#culist_component_mapping) ),*];
4933                pub const MONITOR_LAYOUT: cu29::monitoring::CopperListLayout =
4934                    cu29::monitoring::CopperListLayout::new(
4935                        MONITORED_COMPONENTS,
4936                        CULIST_COMPONENT_MAPPING,
4937                    );
4938                #parallel_rt_metadata_defs
4939
4940                #[inline]
4941                pub fn monitor_component_label(
4942                    component_id: cu29::monitoring::ComponentId,
4943                ) -> &'static str {
4944                    MONITORED_COMPONENTS[component_id.index()].id()
4945                }
4946
4947                #culist_support
4948                #parallel_rt_support_tokens
4949
4950                #tasks_instanciator
4951                #bridges_instanciator
4952
4953                pub fn monitor_instanciator(
4954                    config: &CuConfig,
4955                    metadata: ::cu29::monitoring::CuMonitoringMetadata,
4956                    runtime: ::cu29::monitoring::CuMonitoringRuntime,
4957                ) -> #monitor_type {
4958                    #monitor_instanciator_body
4959                }
4960
4961                // The application for this mission
4962                #app_resources_struct
4963                pub #application_struct
4964
4965                #app_inherent_impl
4966                #app_builder_inherent_impl
4967                #app_metadata_impl
4968                #app_reflect_impl
4969                #app_runtime_copperlist_impl
4970                #application_impl
4971                #recorded_replay_app_impl
4972                #distributed_replay_app_impl
4973
4974                #std_application_impl
4975
4976                #application_builder
4977            }
4978
4979        };
4980        all_missions_tokens.push(mission_mod_tokens);
4981    }
4982
4983    let default_application_tokens = if all_missions.contains_key("default") {
4984        let default_builder = quote! {
4985            #[allow(unused_imports)]
4986            use default::#builder_name;
4987        };
4988        quote! {
4989            #default_builder
4990
4991            #[allow(unused_imports)]
4992            use default::AppResources;
4993
4994            #[allow(unused_imports)]
4995            use default::resources as app_resources;
4996
4997            #[allow(unused_imports)]
4998            use default::#application_name;
4999        }
5000    } else {
5001        quote!() // do nothing
5002    };
5003
5004    let result: proc_macro2::TokenStream = quote! {
5005        #(#all_missions_tokens)*
5006        #default_application_tokens
5007    };
5008
5009    result.into()
5010}
5011
5012fn resolve_runtime_config(args: &CopperRuntimeArgs) -> CuResult<ResolvedRuntimeConfig> {
5013    let caller_root = utils::caller_crate_root();
5014    resolve_runtime_config_with_root(args, &caller_root)
5015}
5016
5017fn resolve_runtime_config_with_root(
5018    args: &CopperRuntimeArgs,
5019    caller_root: &Path,
5020) -> CuResult<ResolvedRuntimeConfig> {
5021    let filename = config_full_path_from_root(caller_root, &args.config_path);
5022    if !Path::new(&filename).exists() {
5023        return Err(CuError::from(format!(
5024            "The configuration file `{}` does not exist. Please provide a valid path.",
5025            args.config_path
5026        )));
5027    }
5028
5029    if let Some(subsystem_id) = args.subsystem_id.as_deref() {
5030        let multi_config = cu29_runtime::config::read_multi_configuration(filename.as_str())
5031            .map_err(|e| {
5032                CuError::from(format!(
5033                    "When `subsystem = \"{subsystem_id}\"` is provided, `config = \"{}\"` must point to a valid multi-Copper configuration: {e}",
5034                    args.config_path
5035                ))
5036            })?;
5037        let subsystem = multi_config.subsystem(subsystem_id).ok_or_else(|| {
5038            CuError::from(format!(
5039                "Subsystem '{subsystem_id}' was not found in multi-Copper configuration '{}'.",
5040                args.config_path
5041            ))
5042        })?;
5043        let bundled_local_config_content = read_to_string(&subsystem.config_path).map_err(|e| {
5044            CuError::from(format!(
5045                "Failed to read bundled local configuration for subsystem '{subsystem_id}' from '{}'.",
5046                subsystem.config_path
5047            ))
5048            .add_cause(e.to_string().as_str())
5049        })?;
5050
5051        Ok(ResolvedRuntimeConfig {
5052            local_config: subsystem.config.clone(),
5053            bundled_local_config_content,
5054            subsystem_id: Some(subsystem_id.to_string()),
5055            subsystem_code: subsystem.subsystem_code,
5056        })
5057    } else {
5058        Ok(ResolvedRuntimeConfig {
5059            local_config: read_configuration(filename.as_str())?,
5060            bundled_local_config_content: read_to_string(&filename).map_err(|e| {
5061                CuError::from(format!(
5062                    "Could not read the configuration file '{}'.",
5063                    args.config_path
5064                ))
5065                .add_cause(e.to_string().as_str())
5066            })?,
5067            subsystem_id: None,
5068            subsystem_code: 0,
5069        })
5070    }
5071}
5072
5073fn build_config_load_stmt(
5074    std_enabled: bool,
5075    application_name: &Ident,
5076    subsystem_id: Option<&str>,
5077) -> proc_macro2::TokenStream {
5078    if std_enabled {
5079        if let Some(subsystem_id) = subsystem_id {
5080            quote! {
5081                let (config, config_source) = if let Some(overridden_config) = config_override {
5082                    debug!("CuConfig: Overridden programmatically.");
5083                    (overridden_config, RuntimeLifecycleConfigSource::ProgrammaticOverride)
5084                } else if ::std::path::Path::new(config_filename).exists() {
5085                    let subsystem_id = #application_name::subsystem()
5086                        .id()
5087                        .expect("generated multi-Copper runtime is missing a subsystem id");
5088                    debug!(
5089                        "CuConfig: Reading multi-Copper configuration from file: {} (subsystem={})",
5090                        config_filename,
5091                        subsystem_id
5092                    );
5093                    let multi_config = cu29::config::read_multi_configuration(config_filename)?;
5094                    (
5095                        multi_config.resolve_subsystem_config_for_instance(subsystem_id, instance_id)?,
5096                        RuntimeLifecycleConfigSource::ExternalFile,
5097                    )
5098                } else {
5099                    let original_config = Self::original_config();
5100                    debug!(
5101                        "CuConfig: Using the bundled subsystem configuration compiled into the binary (subsystem={}).",
5102                        #subsystem_id
5103                    );
5104                    if instance_id != 0 {
5105                        debug!(
5106                            "CuConfig: runtime file '{}' is missing, so instance-specific overrides for instance_id={} cannot be resolved; using bundled subsystem defaults.",
5107                            config_filename,
5108                            instance_id
5109                        );
5110                    }
5111                    (
5112                        cu29::config::read_configuration_str(original_config, None)?,
5113                        RuntimeLifecycleConfigSource::BundledDefault,
5114                    )
5115                };
5116            }
5117        } else {
5118            quote! {
5119                let _ = instance_id;
5120                let (config, config_source) = if let Some(overridden_config) = config_override {
5121                    debug!("CuConfig: Overridden programmatically.");
5122                    (overridden_config, RuntimeLifecycleConfigSource::ProgrammaticOverride)
5123                } else if ::std::path::Path::new(config_filename).exists() {
5124                    debug!("CuConfig: Reading configuration from file: {}", config_filename);
5125                    (
5126                        cu29::config::read_configuration(config_filename)?,
5127                        RuntimeLifecycleConfigSource::ExternalFile,
5128                    )
5129                } else {
5130                    let original_config = Self::original_config();
5131                    debug!("CuConfig: Using the bundled configuration compiled into the binary.");
5132                    (
5133                        cu29::config::read_configuration_str(original_config, None)?,
5134                        RuntimeLifecycleConfigSource::BundledDefault,
5135                    )
5136                };
5137            }
5138        }
5139    } else {
5140        quote! {
5141            // Only the original config is available in no-std
5142            let original_config = Self::original_config();
5143            debug!("CuConfig: Using the bundled configuration compiled into the binary.");
5144            let config = cu29::config::read_configuration_str(original_config, None)?;
5145            let config_source = RuntimeLifecycleConfigSource::BundledDefault;
5146        }
5147    }
5148}
5149
5150fn config_full_path(config_file: &str) -> String {
5151    config_full_path_from_root(&utils::caller_crate_root(), config_file)
5152}
5153
5154fn config_full_path_from_root(caller_root: &Path, config_file: &str) -> String {
5155    let mut config_full_path = caller_root.to_path_buf();
5156    config_full_path.push(config_file);
5157    let filename = config_full_path
5158        .as_os_str()
5159        .to_str()
5160        .expect("Could not interpret the config file name");
5161    filename.to_string()
5162}
5163
5164fn read_config(config_file: &str) -> CuResult<CuConfig> {
5165    let filename = config_full_path(config_file);
5166    read_configuration(filename.as_str())
5167}
5168
5169fn extract_tasks_output_types(graph: &CuGraph) -> Vec<Option<Type>> {
5170    graph
5171        .get_all_nodes()
5172        .iter()
5173        .map(|(_, node)| {
5174            let id = node.get_id();
5175            let type_str = graph.get_node_output_msg_type(id.as_str());
5176            type_str.map(|type_str| {
5177                parse_str::<Type>(type_str.as_str()).expect("Could not parse output message type.")
5178            })
5179        })
5180        .collect()
5181}
5182
5183struct CuTaskSpecSet {
5184    pub ids: Vec<String>,
5185    pub cutypes: Vec<CuTaskType>,
5186    pub background_flags: Vec<bool>,
5187    pub logging_enabled: Vec<bool>,
5188    pub type_names: Vec<String>,
5189    pub task_types: Vec<Type>,
5190    pub instantiation_types: Vec<Type>,
5191    pub sim_task_types: Vec<Type>,
5192    pub run_in_sim_flags: Vec<bool>,
5193    #[allow(dead_code)]
5194    pub output_types: Vec<Option<Type>>,
5195    pub node_id_to_task_index: Vec<Option<usize>>,
5196}
5197
5198impl CuTaskSpecSet {
5199    pub fn from_graph(graph: &CuGraph) -> Self {
5200        let all_id_nodes: Vec<(NodeId, &Node)> = graph
5201            .get_all_nodes()
5202            .into_iter()
5203            .filter(|(_, node)| node.get_flavor() == Flavor::Task)
5204            .collect();
5205
5206        let ids = all_id_nodes
5207            .iter()
5208            .map(|(_, node)| node.get_id().to_string())
5209            .collect();
5210
5211        let cutypes: Vec<CuTaskType> = all_id_nodes
5212            .iter()
5213            .map(|(id, _)| find_task_type_for_id(graph, *id))
5214            .collect();
5215
5216        let background_flags: Vec<bool> = all_id_nodes
5217            .iter()
5218            .map(|(_, node)| node.is_background())
5219            .collect();
5220
5221        let logging_enabled: Vec<bool> = all_id_nodes
5222            .iter()
5223            .map(|(_, node)| node.is_logging_enabled())
5224            .collect();
5225
5226        let type_names: Vec<String> = all_id_nodes
5227            .iter()
5228            .map(|(_, node)| node.get_type().to_string())
5229            .collect();
5230
5231        let output_types = extract_tasks_output_types(graph);
5232
5233        let task_types = type_names
5234            .iter()
5235            .zip(cutypes.iter())
5236            .zip(background_flags.iter())
5237            .zip(output_types.iter())
5238            .map(|(((name, cutype), &background), output_type)| {
5239                let name_type = parse_str::<Type>(name).unwrap_or_else(|error| {
5240                    panic!("Could not transform {name} into a Task Rust type: {error}");
5241                });
5242                if background {
5243                    if let Some(output_type) = output_type {
5244                        match cutype {
5245                            CuTaskType::Source => {
5246                                parse_quote!(CuAsyncSrcTask<#name_type, #output_type>)
5247                            }
5248                            CuTaskType::Regular => {
5249                                parse_quote!(CuAsyncTask<#name_type, #output_type>)
5250                            }
5251                            CuTaskType::Sink => {
5252                                panic!("CuSinkTask {name} cannot be a background task, it should be a regular task.");
5253                            }
5254                        }
5255                    } else {
5256                        panic!("{name}: If a task is background, it has to have an output");
5257                    }
5258                } else {
5259                    name_type
5260                }
5261            })
5262            .collect();
5263
5264        let instantiation_types = type_names
5265            .iter()
5266            .zip(cutypes.iter())
5267            .zip(background_flags.iter())
5268            .zip(output_types.iter())
5269            .map(|(((name, cutype), &background), output_type)| {
5270                let name_type = parse_str::<Type>(name).unwrap_or_else(|error| {
5271                    panic!("Could not transform {name} into a Task Rust type: {error}");
5272                });
5273                if background {
5274                    if let Some(output_type) = output_type {
5275                        match cutype {
5276                            CuTaskType::Source => {
5277                                parse_quote!(CuAsyncSrcTask::<#name_type, #output_type>)
5278                            }
5279                            CuTaskType::Regular => {
5280                                parse_quote!(CuAsyncTask::<#name_type, #output_type>)
5281                            }
5282                            CuTaskType::Sink => {
5283                                panic!("CuSinkTask {name} cannot be a background task, it should be a regular task.");
5284                            }
5285                        }
5286                    } else {
5287                        panic!("{name}: If a task is background, it has to have an output");
5288                    }
5289                } else {
5290                    name_type
5291                }
5292            })
5293            .collect();
5294
5295        let sim_task_types = type_names
5296            .iter()
5297            .map(|name| {
5298                parse_str::<Type>(name).unwrap_or_else(|err| {
5299                    eprintln!("Could not transform {name} into a Task Rust type.");
5300                    panic!("{err}")
5301                })
5302            })
5303            .collect();
5304
5305        let run_in_sim_flags = all_id_nodes
5306            .iter()
5307            .map(|(_, node)| node.is_run_in_sim())
5308            .collect();
5309
5310        let mut node_id_to_task_index = vec![None; graph.node_count()];
5311        for (index, (node_id, _)) in all_id_nodes.iter().enumerate() {
5312            node_id_to_task_index[*node_id as usize] = Some(index);
5313        }
5314
5315        Self {
5316            ids,
5317            cutypes,
5318            background_flags,
5319            logging_enabled,
5320            type_names,
5321            task_types,
5322            instantiation_types,
5323            sim_task_types,
5324            run_in_sim_flags,
5325            output_types,
5326            node_id_to_task_index,
5327        }
5328    }
5329}
5330
5331#[derive(Clone)]
5332struct OutputPack {
5333    msg_types: Vec<Type>,
5334    msg_type_names: Vec<String>,
5335}
5336
5337impl OutputPack {
5338    fn slot_type(&self) -> Type {
5339        build_output_slot_type(&self.msg_types)
5340    }
5341
5342    fn is_multi(&self) -> bool {
5343        self.msg_types.len() > 1
5344    }
5345}
5346
5347fn build_output_slot_type(msg_types: &[Type]) -> Type {
5348    if msg_types.is_empty() {
5349        parse_quote! { () }
5350    } else if msg_types.len() == 1 {
5351        let msg_type = msg_types.first().unwrap();
5352        parse_quote! { CuMsg<#msg_type> }
5353    } else {
5354        parse_quote! { ( #( CuMsg<#msg_types> ),* ) }
5355    }
5356}
5357
5358fn flatten_slot_origin_ids(
5359    output_packs: &[OutputPack],
5360    slot_origin_ids: &[Option<String>],
5361) -> Vec<String> {
5362    let mut ids = Vec::new();
5363    for (slot, pack) in output_packs.iter().enumerate() {
5364        if pack.msg_types.is_empty() {
5365            continue;
5366        }
5367        let origin = slot_origin_ids
5368            .get(slot)
5369            .and_then(|origin| origin.as_ref())
5370            .unwrap_or_else(|| panic!("Missing slot origin id for copperlist output slot {slot}"));
5371        for _ in 0..pack.msg_types.len() {
5372            ids.push(origin.clone());
5373        }
5374    }
5375    ids
5376}
5377
5378fn flatten_task_output_specs(
5379    output_packs: &[OutputPack],
5380    slot_origin_ids: &[Option<String>],
5381) -> Vec<(String, String, String)> {
5382    let mut specs = Vec::new();
5383    for (slot, pack) in output_packs.iter().enumerate() {
5384        if pack.msg_types.is_empty() {
5385            continue;
5386        }
5387        let origin = slot_origin_ids
5388            .get(slot)
5389            .and_then(|origin| origin.as_ref())
5390            .unwrap_or_else(|| panic!("Missing slot origin id for copperlist output slot {slot}"));
5391        for (msg_type, payload_type) in pack.msg_type_names.iter().zip(pack.msg_types.iter()) {
5392            specs.push((
5393                origin.clone(),
5394                msg_type.clone(),
5395                rust_type_path_literal(payload_type),
5396            ));
5397        }
5398    }
5399    specs
5400}
5401
5402fn rust_type_path_literal(ty: &Type) -> String {
5403    ty.to_token_stream()
5404        .to_string()
5405        .chars()
5406        .filter(|ch| !ch.is_whitespace())
5407        .collect()
5408}
5409
5410fn extract_output_packs(runtime_plan: &CuExecutionLoop) -> Vec<OutputPack> {
5411    let mut packs: Vec<(u32, OutputPack)> = runtime_plan
5412        .steps
5413        .iter()
5414        .filter_map(|unit| match unit {
5415            CuExecutionUnit::Step(step) => {
5416                if let Some(output_pack) = &step.output_msg_pack {
5417                    let msg_types: Vec<Type> = output_pack
5418                        .msg_types
5419                        .iter()
5420                        .map(|output_msg_type| {
5421                            parse_str::<Type>(output_msg_type.as_str()).unwrap_or_else(|_| {
5422                                panic!(
5423                                    "Could not transform {output_msg_type} into a message Rust type."
5424                                )
5425                            })
5426                        })
5427                        .collect();
5428                    Some((
5429                        output_pack.culist_index,
5430                        OutputPack {
5431                            msg_types,
5432                            msg_type_names: output_pack.msg_types.clone(),
5433                        },
5434                    ))
5435                } else {
5436                    None
5437                }
5438            }
5439            CuExecutionUnit::Loop(_) => todo!("Needs to be implemented"),
5440        })
5441        .collect();
5442
5443    packs.sort_by_key(|(index, _)| *index);
5444    packs.into_iter().map(|(_, pack)| pack).collect()
5445}
5446
5447#[derive(Clone)]
5448struct SlotCodecBinding {
5449    payload_type: Type,
5450    task_id: String,
5451    msg_type: String,
5452    codec_type: syn::Path,
5453    codec_type_path: String,
5454}
5455
5456fn build_flat_slot_codec_bindings(
5457    cuconfig: &CuConfig,
5458    mission_label: Option<&str>,
5459    output_packs: &[OutputPack],
5460    node_output_positions: &HashMap<NodeId, usize>,
5461    task_names: &[(NodeId, String, String)],
5462) -> CuResult<Vec<Option<SlotCodecBinding>>> {
5463    let mut slot_task_ids: Vec<Option<String>> = vec![None; output_packs.len()];
5464    for (node_id, task_id, _) in task_names {
5465        let Some(output_position) = node_output_positions.get(node_id) else {
5466            continue;
5467        };
5468        slot_task_ids[*output_position] = Some(task_id.clone());
5469    }
5470
5471    let mut bindings =
5472        Vec::with_capacity(output_packs.iter().map(|pack| pack.msg_types.len()).sum());
5473    for (slot_idx, pack) in output_packs.iter().enumerate() {
5474        let task_id = slot_task_ids.get(slot_idx).and_then(|id| id.as_ref());
5475        for (port_idx, payload_type) in pack.msg_types.iter().enumerate() {
5476            let Some(task_id) = task_id else {
5477                bindings.push(None);
5478                continue;
5479            };
5480            let Some(msg_type) = pack.msg_type_names.get(port_idx) else {
5481                return Err(CuError::from(format!(
5482                    "Missing message type name for task '{task_id}' slot {slot_idx} port {port_idx}."
5483                )));
5484            };
5485
5486            let spec = cuconfig
5487                .find_task_node(mission_label, task_id)
5488                .and_then(|node| node.get_logging())
5489                .and_then(|logging| logging.codec_for_msg_type(msg_type))
5490                .map(|codec_id| {
5491                    cuconfig.find_logging_codec_spec(codec_id).ok_or_else(|| {
5492                        CuError::from(format!(
5493                            "Task '{task_id}' binds output '{msg_type}' to unknown logging codec '{codec_id}'."
5494                        ))
5495                    })
5496                })
5497                .transpose()?;
5498
5499            if let Some(spec) = spec {
5500                let codec_type = parse_str::<syn::Path>(&spec.type_).map_err(|_| {
5501                    CuError::from(format!(
5502                        "Logging codec '{}' for task '{task_id}' output '{msg_type}' is not a valid Rust type path.",
5503                        spec.type_
5504                    ))
5505                })?;
5506                bindings.push(Some(SlotCodecBinding {
5507                    payload_type: payload_type.clone(),
5508                    task_id: task_id.clone(),
5509                    msg_type: msg_type.clone(),
5510                    codec_type,
5511                    codec_type_path: spec.type_.clone(),
5512                }));
5513            } else {
5514                bindings.push(None);
5515            }
5516        }
5517    }
5518
5519    Ok(bindings)
5520}
5521
5522fn build_culist_codec_helpers(
5523    flat_codec_bindings: &[Option<SlotCodecBinding>],
5524    default_config_ron_ident: &Ident,
5525    mission_label: Option<&str>,
5526) -> (
5527    Vec<proc_macro2::TokenStream>,
5528    Vec<Option<Ident>>,
5529    Vec<Option<Ident>>,
5530) {
5531    let mission_tokens = if let Some(mission) = mission_label {
5532        let lit = LitStr::new(mission, Span::call_site());
5533        quote! { Some(#lit) }
5534    } else {
5535        quote! { None }
5536    };
5537
5538    let mut helpers = Vec::new();
5539    let mut encode_helper_names = Vec::with_capacity(flat_codec_bindings.len());
5540    let mut decode_helper_names = Vec::with_capacity(flat_codec_bindings.len());
5541
5542    for (flat_idx, binding) in flat_codec_bindings.iter().enumerate() {
5543        let Some(binding) = binding else {
5544            encode_helper_names.push(None);
5545            decode_helper_names.push(None);
5546            continue;
5547        };
5548
5549        let encode_fn = format_ident!("__cu_logcodec_encode_slot_{flat_idx}");
5550        let decode_fn = format_ident!("__cu_logcodec_decode_slot_{flat_idx}");
5551        let payload_type = &binding.payload_type;
5552        let codec_type = &binding.codec_type;
5553        let task_id = LitStr::new(&binding.task_id, Span::call_site());
5554        let msg_type = LitStr::new(&binding.msg_type, Span::call_site());
5555        let codec_type_path = LitStr::new(&binding.codec_type_path, Span::call_site());
5556
5557        helpers.push(quote! {
5558            fn #encode_fn<E: Encoder>(msg: &CuMsg<#payload_type>, encoder: &mut E) -> Result<(), EncodeError> {
5559                static STATE: ::cu29::logcodec::CodecState<#codec_type> = ::cu29::logcodec::CodecState::new();
5560                let config_entry = ::cu29::logcodec::effective_config_entry::<CuStampedDataSet>(#default_config_ron_ident);
5561                ::cu29::logcodec::with_codec_for_encode(
5562                    &STATE,
5563                    config_entry,
5564                    |effective_config_ron| {
5565                        ::cu29::logcodec::instantiate_codec::<#codec_type, #payload_type>(
5566                            effective_config_ron,
5567                            #mission_tokens,
5568                            #task_id,
5569                            #msg_type,
5570                            #codec_type_path,
5571                        )
5572                    },
5573                    |codec| ::cu29::logcodec::encode_msg_with_codec(msg, codec, encoder),
5574                )
5575            }
5576
5577            fn #decode_fn<D: Decoder<Context = ()>>(decoder: &mut D) -> Result<CuMsg<#payload_type>, DecodeError> {
5578                static STATE: ::cu29::logcodec::CodecState<#codec_type> = ::cu29::logcodec::CodecState::new();
5579                let config_entry = ::cu29::logcodec::effective_config_entry::<CuStampedDataSet>(#default_config_ron_ident);
5580                ::cu29::logcodec::with_codec_for_decode(
5581                    &STATE,
5582                    config_entry,
5583                    |effective_config_ron| {
5584                        ::cu29::logcodec::instantiate_codec::<#codec_type, #payload_type>(
5585                            effective_config_ron,
5586                            #mission_tokens,
5587                            #task_id,
5588                            #msg_type,
5589                            #codec_type_path,
5590                        )
5591                    },
5592                    |codec| ::cu29::logcodec::decode_msg_with_codec(decoder, codec),
5593                )
5594            }
5595        });
5596        encode_helper_names.push(Some(encode_fn));
5597        decode_helper_names.push(Some(decode_fn));
5598    }
5599
5600    (helpers, encode_helper_names, decode_helper_names)
5601}
5602
5603fn collect_output_pack_sizes(runtime_plan: &CuExecutionLoop) -> Vec<usize> {
5604    let mut sizes: Vec<(u32, usize)> = runtime_plan
5605        .steps
5606        .iter()
5607        .filter_map(|unit| match unit {
5608            CuExecutionUnit::Step(step) => step
5609                .output_msg_pack
5610                .as_ref()
5611                .map(|output_pack| (output_pack.culist_index, output_pack.msg_types.len())),
5612            CuExecutionUnit::Loop(_) => todo!("Needs to be implemented"),
5613        })
5614        .collect();
5615
5616    sizes.sort_by_key(|(index, _)| *index);
5617    sizes.into_iter().map(|(_, size)| size).collect()
5618}
5619
5620/// Builds the tuple of the CuList as a tuple off all the output slots.
5621fn build_culist_tuple(slot_types: &[Type]) -> TypeTuple {
5622    if slot_types.is_empty() {
5623        parse_quote! { () }
5624    } else {
5625        parse_quote! { ( #( #slot_types ),* ) }
5626    }
5627}
5628
5629/// This is the bincode encoding part of the CuStampedDataSet
5630fn build_culist_tuple_encode(
5631    output_packs: &[OutputPack],
5632    encode_helper_names: &[Option<Ident>],
5633) -> ItemImpl {
5634    let mut flat_idx = 0usize;
5635    let mut encode_fields = Vec::new();
5636
5637    for (slot_idx, pack) in output_packs.iter().enumerate() {
5638        let slot_index = syn::Index::from(slot_idx);
5639        if pack.is_multi() {
5640            for port_idx in 0..pack.msg_types.len() {
5641                let port_index = syn::Index::from(port_idx);
5642                let cache_index = flat_idx;
5643                let encode_helper = encode_helper_names[flat_idx].clone();
5644                flat_idx += 1;
5645                if let Some(encode_helper) = encode_helper {
5646                    encode_fields.push(quote! {
5647                        __cu_capture.select_slot(#cache_index);
5648                        #encode_helper(&self.0.#slot_index.#port_index, encoder)?;
5649                    });
5650                } else {
5651                    encode_fields.push(quote! {
5652                        __cu_capture.select_slot(#cache_index);
5653                        self.0.#slot_index.#port_index.encode(encoder)?;
5654                    });
5655                }
5656            }
5657        } else {
5658            let cache_index = flat_idx;
5659            let encode_helper = encode_helper_names[flat_idx].clone();
5660            flat_idx += 1;
5661            if let Some(encode_helper) = encode_helper {
5662                encode_fields.push(quote! {
5663                    __cu_capture.select_slot(#cache_index);
5664                    #encode_helper(&self.0.#slot_index, encoder)?;
5665                });
5666            } else {
5667                encode_fields.push(quote! {
5668                    __cu_capture.select_slot(#cache_index);
5669                    self.0.#slot_index.encode(encoder)?;
5670                });
5671            }
5672        }
5673    }
5674
5675    parse_quote! {
5676        impl Encode for CuStampedDataSet {
5677            fn encode<E: Encoder>(&self, encoder: &mut E) -> Result<(), EncodeError> {
5678                let __cu_capture = cu29::monitoring::start_copperlist_io_capture(&self.1);
5679                #(#encode_fields)*
5680                Ok(())
5681            }
5682        }
5683    }
5684}
5685
5686/// This is the bincode decoding part of the CuStampedDataSet
5687fn build_culist_tuple_decode(
5688    output_packs: &[OutputPack],
5689    slot_types: &[Type],
5690    cumsg_count: usize,
5691    decode_helper_names: &[Option<Ident>],
5692) -> ItemImpl {
5693    let mut flat_idx = 0usize;
5694    let mut decode_fields = Vec::with_capacity(slot_types.len());
5695    for (slot_idx, pack) in output_packs.iter().enumerate() {
5696        let slot_type = &slot_types[slot_idx];
5697        if pack.is_multi() {
5698            let mut slot_fields = Vec::with_capacity(pack.msg_types.len());
5699            for _ in 0..pack.msg_types.len() {
5700                let decode_helper = decode_helper_names[flat_idx].clone();
5701                flat_idx += 1;
5702                if let Some(decode_helper) = decode_helper {
5703                    slot_fields.push(quote! { #decode_helper(decoder)? });
5704                } else {
5705                    let msg_type = &pack.msg_types[slot_fields.len()];
5706                    slot_fields.push(quote! { <CuMsg<#msg_type> as Decode<()>>::decode(decoder)? });
5707                }
5708            }
5709            decode_fields.push(quote! { ( #(#slot_fields),* ) });
5710        } else if let Some(decode_helper) = decode_helper_names[flat_idx].clone() {
5711            flat_idx += 1;
5712            decode_fields.push(quote! { #decode_helper(decoder)? });
5713        } else {
5714            flat_idx += 1;
5715            decode_fields.push(quote! { <#slot_type as Decode<()>>::decode(decoder)? });
5716        }
5717    }
5718
5719    parse_quote! {
5720        impl Decode<()> for CuStampedDataSet {
5721            fn decode<D: Decoder<Context=()>>(decoder: &mut D) -> Result<Self, DecodeError> {
5722                Ok(CuStampedDataSet(
5723                    (
5724                        #(#decode_fields),*
5725                    ),
5726                    cu29::monitoring::CuMsgIoCache::<#cumsg_count>::default(),
5727                ))
5728            }
5729        }
5730    }
5731}
5732
5733fn build_culist_erasedcumsgs(output_packs: &[OutputPack]) -> ItemImpl {
5734    let mut casted_fields: Vec<proc_macro2::TokenStream> = Vec::new();
5735    for (idx, pack) in output_packs.iter().enumerate() {
5736        let slot_index = syn::Index::from(idx);
5737        if pack.is_multi() {
5738            for port_idx in 0..pack.msg_types.len() {
5739                let port_index = syn::Index::from(port_idx);
5740                casted_fields.push(quote! {
5741                    &self.0.#slot_index.#port_index as &dyn ErasedCuStampedData
5742                });
5743            }
5744        } else {
5745            casted_fields.push(quote! { &self.0.#slot_index as &dyn ErasedCuStampedData });
5746        }
5747    }
5748    parse_quote! {
5749        impl ErasedCuStampedDataSet for CuStampedDataSet {
5750            fn cumsgs(&self) -> Vec<&dyn ErasedCuStampedData> {
5751                vec![
5752                    #(#casted_fields),*
5753                ]
5754            }
5755        }
5756    }
5757}
5758
5759fn build_culist_tuple_debug(slot_types: &[Type]) -> ItemImpl {
5760    let indices: Vec<usize> = (0..slot_types.len()).collect();
5761
5762    let debug_fields: Vec<_> = indices
5763        .iter()
5764        .map(|i| {
5765            let idx = syn::Index::from(*i);
5766            quote! { .field(&self.0.#idx) }
5767        })
5768        .collect();
5769
5770    parse_quote! {
5771        impl Debug for CuStampedDataSet {
5772            fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
5773                f.debug_tuple("CuStampedDataSet")
5774                    #(#debug_fields)*
5775                    .finish()
5776            }
5777        }
5778    }
5779}
5780
5781/// This is the serde serialization part of the CuStampedDataSet
5782fn build_culist_tuple_serialize(slot_types: &[Type]) -> ItemImpl {
5783    let indices: Vec<usize> = (0..slot_types.len()).collect();
5784    let tuple_len = slot_types.len();
5785
5786    // Generate the serialization for each tuple field
5787    let serialize_fields: Vec<_> = indices
5788        .iter()
5789        .map(|i| {
5790            let idx = syn::Index::from(*i);
5791            quote! { &self.0.#idx }
5792        })
5793        .collect();
5794
5795    parse_quote! {
5796        impl Serialize for CuStampedDataSet {
5797            fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
5798            where
5799                S: serde::Serializer,
5800            {
5801                use serde::ser::SerializeTuple;
5802                let mut tuple = serializer.serialize_tuple(#tuple_len)?;
5803                #(tuple.serialize_element(#serialize_fields)?;)*
5804                tuple.end()
5805            }
5806        }
5807    }
5808}
5809
5810/// This is the default implementation for CuStampedDataSet
5811fn build_culist_tuple_default(slot_types: &[Type], cumsg_count: usize) -> ItemImpl {
5812    let default_fields: Vec<_> = slot_types
5813        .iter()
5814        .map(|slot_type| quote! { <#slot_type as Default>::default() })
5815        .collect();
5816
5817    parse_quote! {
5818        impl Default for CuStampedDataSet {
5819            fn default() -> CuStampedDataSet
5820            {
5821                CuStampedDataSet(
5822                    (
5823                        #(#default_fields),*
5824                    ),
5825                    cu29::monitoring::CuMsgIoCache::<#cumsg_count>::default(),
5826                )
5827            }
5828        }
5829    }
5830}
5831
5832fn collect_bridge_channel_usage(graph: &CuGraph) -> HashMap<BridgeChannelKey, String> {
5833    let mut usage = HashMap::new();
5834    for cnx in graph.edges() {
5835        if let Some(channel) = &cnx.src_channel {
5836            let key = BridgeChannelKey {
5837                bridge_id: cnx.src.clone(),
5838                channel_id: channel.clone(),
5839                direction: BridgeChannelDirection::Rx,
5840            };
5841            usage
5842                .entry(key)
5843                .and_modify(|msg| {
5844                    if msg != &cnx.msg {
5845                        panic!(
5846                            "Bridge '{}' channel '{}' is used with incompatible message types: {} vs {}",
5847                            cnx.src, channel, msg, cnx.msg
5848                        );
5849                    }
5850                })
5851                .or_insert(cnx.msg.clone());
5852        }
5853        if let Some(channel) = &cnx.dst_channel {
5854            let key = BridgeChannelKey {
5855                bridge_id: cnx.dst.clone(),
5856                channel_id: channel.clone(),
5857                direction: BridgeChannelDirection::Tx,
5858            };
5859            usage
5860                .entry(key)
5861                .and_modify(|msg| {
5862                    if msg != &cnx.msg {
5863                        panic!(
5864                            "Bridge '{}' channel '{}' is used with incompatible message types: {} vs {}",
5865                            cnx.dst, channel, msg, cnx.msg
5866                        );
5867                    }
5868                })
5869                .or_insert(cnx.msg.clone());
5870        }
5871    }
5872    usage
5873}
5874
5875fn build_bridge_specs(
5876    config: &CuConfig,
5877    graph: &CuGraph,
5878    channel_usage: &HashMap<BridgeChannelKey, String>,
5879) -> Vec<BridgeSpec> {
5880    let mut specs = Vec::new();
5881    for (bridge_index, bridge_cfg) in config.bridges.iter().enumerate() {
5882        if graph.get_node_id_by_name(bridge_cfg.id.as_str()).is_none() {
5883            continue;
5884        }
5885
5886        let type_path = parse_str::<Type>(bridge_cfg.type_.as_str()).unwrap_or_else(|err| {
5887            panic!(
5888                "Could not parse bridge type '{}' for '{}': {err}",
5889                bridge_cfg.type_, bridge_cfg.id
5890            )
5891        });
5892
5893        let mut rx_channels = Vec::new();
5894        let mut tx_channels = Vec::new();
5895
5896        for (channel_index, channel) in bridge_cfg.channels.iter().enumerate() {
5897            match channel {
5898                BridgeChannelConfigRepresentation::Rx { id, .. } => {
5899                    let key = BridgeChannelKey {
5900                        bridge_id: bridge_cfg.id.clone(),
5901                        channel_id: id.clone(),
5902                        direction: BridgeChannelDirection::Rx,
5903                    };
5904                    if let Some(msg_type) = channel_usage.get(&key) {
5905                        let msg_type_name = msg_type.clone();
5906                        let msg_type = parse_str::<Type>(msg_type).unwrap_or_else(|err| {
5907                            panic!(
5908                                "Could not parse message type '{msg_type}' for bridge '{}' channel '{}': {err}",
5909                                bridge_cfg.id, id
5910                            )
5911                        });
5912                        let const_ident =
5913                            Ident::new(&config_id_to_bridge_const(id.as_str()), Span::call_site());
5914                        rx_channels.push(BridgeChannelSpec {
5915                            id: id.clone(),
5916                            const_ident,
5917                            msg_type,
5918                            msg_type_name,
5919                            config_index: channel_index,
5920                            plan_node_id: None,
5921                            culist_index: None,
5922                            monitor_index: None,
5923                        });
5924                    }
5925                }
5926                BridgeChannelConfigRepresentation::Tx { id, .. } => {
5927                    let key = BridgeChannelKey {
5928                        bridge_id: bridge_cfg.id.clone(),
5929                        channel_id: id.clone(),
5930                        direction: BridgeChannelDirection::Tx,
5931                    };
5932                    if let Some(msg_type) = channel_usage.get(&key) {
5933                        let msg_type_name = msg_type.clone();
5934                        let msg_type = parse_str::<Type>(msg_type).unwrap_or_else(|err| {
5935                            panic!(
5936                                "Could not parse message type '{msg_type}' for bridge '{}' channel '{}': {err}",
5937                                bridge_cfg.id, id
5938                            )
5939                        });
5940                        let const_ident =
5941                            Ident::new(&config_id_to_bridge_const(id.as_str()), Span::call_site());
5942                        tx_channels.push(BridgeChannelSpec {
5943                            id: id.clone(),
5944                            const_ident,
5945                            msg_type,
5946                            msg_type_name,
5947                            config_index: channel_index,
5948                            plan_node_id: None,
5949                            culist_index: None,
5950                            monitor_index: None,
5951                        });
5952                    }
5953                }
5954            }
5955        }
5956
5957        if rx_channels.is_empty() && tx_channels.is_empty() {
5958            continue;
5959        }
5960
5961        specs.push(BridgeSpec {
5962            id: bridge_cfg.id.clone(),
5963            type_path,
5964            run_in_sim: bridge_cfg.is_run_in_sim(),
5965            config_index: bridge_index,
5966            tuple_index: 0,
5967            monitor_index: None,
5968            rx_channels,
5969            tx_channels,
5970        });
5971    }
5972
5973    for (tuple_index, spec) in specs.iter_mut().enumerate() {
5974        spec.tuple_index = tuple_index;
5975    }
5976
5977    specs
5978}
5979
5980fn collect_task_names(graph: &CuGraph) -> Vec<(NodeId, String, String)> {
5981    graph
5982        .get_all_nodes()
5983        .iter()
5984        .filter(|(_, node)| node.get_flavor() == Flavor::Task)
5985        .map(|(node_id, node)| {
5986            (
5987                *node_id,
5988                node.get_id().to_string(),
5989                config_id_to_struct_member(node.get_id().as_str()),
5990            )
5991        })
5992        .collect()
5993}
5994
5995#[derive(Clone, Copy)]
5996enum ResourceOwner {
5997    Task(usize),
5998    Bridge(usize),
5999}
6000
6001#[derive(Clone)]
6002struct ResourceKeySpec {
6003    bundle_index: usize,
6004    provider_path: syn::Path,
6005    resource_name: String,
6006    binding_name: String,
6007    owner: ResourceOwner,
6008}
6009
6010fn parse_resource_path(path: &str) -> CuResult<(String, String)> {
6011    let (bundle_id, name) = path.split_once('.').ok_or_else(|| {
6012        CuError::from(format!(
6013            "Resource '{path}' is missing a bundle prefix (expected bundle.resource)"
6014        ))
6015    })?;
6016
6017    if bundle_id.is_empty() || name.is_empty() {
6018        return Err(CuError::from(format!(
6019            "Resource '{path}' must use the 'bundle.resource' format"
6020        )));
6021    }
6022
6023    Ok((bundle_id.to_string(), name.to_string()))
6024}
6025
6026fn collect_resource_specs(
6027    graph: &CuGraph,
6028    task_specs: &CuTaskSpecSet,
6029    bridge_specs: &[BridgeSpec],
6030    bundle_specs: &[BundleSpec],
6031) -> CuResult<Vec<ResourceKeySpec>> {
6032    let mut bridge_lookup: BTreeMap<String, usize> = BTreeMap::new();
6033    for (idx, spec) in bridge_specs.iter().enumerate() {
6034        bridge_lookup.insert(spec.id.clone(), idx);
6035    }
6036
6037    let mut bundle_lookup: HashMap<String, (usize, syn::Path)> = HashMap::new();
6038    for (index, bundle) in bundle_specs.iter().enumerate() {
6039        bundle_lookup.insert(bundle.id.clone(), (index, bundle.provider_path.clone()));
6040    }
6041
6042    let mut specs = Vec::new();
6043
6044    for (node_id, node) in graph.get_all_nodes() {
6045        let resources = node.get_resources();
6046        if let Some(resources) = resources {
6047            let task_index = task_specs.node_id_to_task_index[node_id as usize];
6048            let owner = if let Some(task_index) = task_index {
6049                ResourceOwner::Task(task_index)
6050            } else if node.get_flavor() == Flavor::Bridge {
6051                let bridge_index = bridge_lookup.get(&node.get_id()).ok_or_else(|| {
6052                    CuError::from(format!(
6053                        "Resource mapping attached to unknown bridge node '{}'",
6054                        node.get_id()
6055                    ))
6056                })?;
6057                ResourceOwner::Bridge(*bridge_index)
6058            } else {
6059                return Err(CuError::from(format!(
6060                    "Resource mapping attached to non-task node '{}'",
6061                    node.get_id()
6062                )));
6063            };
6064
6065            for (binding_name, path) in resources {
6066                let (bundle_id, resource_name) = parse_resource_path(path)?;
6067                let (bundle_index, provider_path) =
6068                    bundle_lookup.get(&bundle_id).ok_or_else(|| {
6069                        CuError::from(format!(
6070                            "Resource '{}' references unknown bundle '{}'",
6071                            path, bundle_id
6072                        ))
6073                    })?;
6074                specs.push(ResourceKeySpec {
6075                    bundle_index: *bundle_index,
6076                    provider_path: provider_path.clone(),
6077                    resource_name,
6078                    binding_name: binding_name.clone(),
6079                    owner,
6080                });
6081            }
6082        }
6083    }
6084
6085    Ok(specs)
6086}
6087
6088fn build_bundle_list<'a>(config: &'a CuConfig, mission: &str) -> Vec<&'a ResourceBundleConfig> {
6089    config
6090        .resources
6091        .iter()
6092        .filter(|bundle| {
6093            bundle
6094                .missions
6095                .as_ref()
6096                .is_none_or(|missions| missions.iter().any(|m| m == mission))
6097        })
6098        .collect()
6099}
6100
6101struct BundleSpec {
6102    id: String,
6103    provider_path: syn::Path,
6104}
6105
6106fn build_bundle_specs(config: &CuConfig, mission: &str) -> CuResult<Vec<BundleSpec>> {
6107    build_bundle_list(config, mission)
6108        .into_iter()
6109        .map(|bundle| {
6110            let provider_path: syn::Path =
6111                syn::parse_str(bundle.provider.as_str()).map_err(|err| {
6112                    CuError::from(format!(
6113                        "Failed to parse provider path '{}' for bundle '{}': {err}",
6114                        bundle.provider, bundle.id
6115                    ))
6116                })?;
6117            Ok(BundleSpec {
6118                id: bundle.id.clone(),
6119                provider_path,
6120            })
6121        })
6122        .collect()
6123}
6124
6125fn build_resources_module(
6126    bundle_specs: &[BundleSpec],
6127) -> CuResult<(proc_macro2::TokenStream, proc_macro2::TokenStream)> {
6128    let bundle_consts = bundle_specs.iter().enumerate().map(|(index, bundle)| {
6129        let const_ident = Ident::new(
6130            &config_id_to_bridge_const(bundle.id.as_str()),
6131            Span::call_site(),
6132        );
6133        quote! { pub const #const_ident: BundleIndex = BundleIndex::new(#index); }
6134    });
6135
6136    let resources_module = quote! {
6137        pub mod resources {
6138            #![allow(dead_code)]
6139            use cu29::resource::BundleIndex;
6140
6141            pub mod bundles {
6142                use super::BundleIndex;
6143                #(#bundle_consts)*
6144            }
6145        }
6146    };
6147
6148    let bundle_counts = bundle_specs.iter().map(|bundle| {
6149        let provider_path = &bundle.provider_path;
6150        quote! { <#provider_path as cu29::resource::ResourceBundleDecl>::Id::COUNT }
6151    });
6152
6153    let bundle_inits = bundle_specs
6154        .iter()
6155        .enumerate()
6156        .map(|(index, bundle)| {
6157            let bundle_id = LitStr::new(bundle.id.as_str(), Span::call_site());
6158            let provider_path = &bundle.provider_path;
6159            quote! {
6160                let bundle_cfg = config
6161                    .resources
6162                    .iter()
6163                    .find(|b| b.id == #bundle_id)
6164                    .unwrap_or_else(|| panic!("Resource bundle '{}' missing from configuration", #bundle_id));
6165                let bundle_ctx = cu29::resource::BundleContext::<#provider_path>::new(
6166                    cu29::resource::BundleIndex::new(#index),
6167                    #bundle_id,
6168                );
6169                <#provider_path as cu29::resource::ResourceBundle>::build(
6170                    bundle_ctx,
6171                    bundle_cfg.config.as_ref(),
6172                    &mut manager,
6173                )?;
6174            }
6175            })
6176            .collect::<Vec<_>>();
6177
6178    let resources_instanciator = quote! {
6179        pub fn resources_instanciator(config: &CuConfig) -> CuResult<cu29::resource::ResourceManager> {
6180            let bundle_counts: &[usize] = &[ #(#bundle_counts),* ];
6181            let mut manager = cu29::resource::ResourceManager::new(bundle_counts);
6182            #(#bundle_inits)*
6183            Ok(manager)
6184        }
6185    };
6186
6187    Ok((resources_module, resources_instanciator))
6188}
6189
6190struct ResourceMappingTokens {
6191    defs: proc_macro2::TokenStream,
6192    refs: Vec<proc_macro2::TokenStream>,
6193}
6194
6195fn build_task_resource_mappings(
6196    resource_specs: &[ResourceKeySpec],
6197    task_specs: &CuTaskSpecSet,
6198) -> CuResult<ResourceMappingTokens> {
6199    let mut per_task: Vec<Vec<&ResourceKeySpec>> = vec![Vec::new(); task_specs.ids.len()];
6200
6201    for spec in resource_specs {
6202        let ResourceOwner::Task(task_index) = spec.owner else {
6203            continue;
6204        };
6205        per_task
6206            .get_mut(task_index)
6207            .ok_or_else(|| {
6208                CuError::from(format!(
6209                    "Resource '{}' mapped to invalid task index {}",
6210                    spec.binding_name, task_index
6211                ))
6212            })?
6213            .push(spec);
6214    }
6215
6216    let mut mapping_defs = Vec::new();
6217    let mut mapping_refs = Vec::new();
6218
6219    for (idx, entries) in per_task.iter().enumerate() {
6220        if entries.is_empty() {
6221            mapping_refs.push(quote! { None });
6222            continue;
6223        }
6224
6225        let binding_task_type = if task_specs.background_flags[idx] {
6226            &task_specs.sim_task_types[idx]
6227        } else {
6228            &task_specs.task_types[idx]
6229        };
6230
6231        let binding_trait = match task_specs.cutypes[idx] {
6232            CuTaskType::Source => quote! { CuSrcTask },
6233            CuTaskType::Regular => quote! { CuTask },
6234            CuTaskType::Sink => quote! { CuSinkTask },
6235        };
6236
6237        let entries_ident = format_ident!("TASK{}_RES_ENTRIES", idx);
6238        let map_ident = format_ident!("TASK{}_RES_MAPPING", idx);
6239        let binding_type = quote! {
6240            <<#binding_task_type as #binding_trait>::Resources<'_> as ResourceBindings>::Binding
6241        };
6242        let entry_tokens = entries.iter().map(|spec| {
6243            let binding_ident =
6244                Ident::new(&config_id_to_enum(spec.binding_name.as_str()), Span::call_site());
6245            let resource_ident =
6246                Ident::new(&config_id_to_enum(spec.resource_name.as_str()), Span::call_site());
6247            let bundle_index = spec.bundle_index;
6248            let provider_path = &spec.provider_path;
6249            quote! {
6250                (#binding_type::#binding_ident, cu29::resource::ResourceKey::new(
6251                    cu29::resource::BundleIndex::new(#bundle_index),
6252                    <#provider_path as cu29::resource::ResourceBundleDecl>::Id::#resource_ident as usize,
6253                ))
6254            }
6255        });
6256
6257        mapping_defs.push(quote! {
6258            const #entries_ident: &[(#binding_type, cu29::resource::ResourceKey)] = &[ #(#entry_tokens),* ];
6259            const #map_ident: cu29::resource::ResourceBindingMap<#binding_type> =
6260                cu29::resource::ResourceBindingMap::new(#entries_ident);
6261        });
6262        mapping_refs.push(quote! { Some(&#map_ident) });
6263    }
6264
6265    Ok(ResourceMappingTokens {
6266        defs: quote! { #(#mapping_defs)* },
6267        refs: mapping_refs,
6268    })
6269}
6270
6271fn build_bridge_resource_mappings(
6272    resource_specs: &[ResourceKeySpec],
6273    bridge_specs: &[BridgeSpec],
6274    sim_mode: bool,
6275) -> ResourceMappingTokens {
6276    let mut per_bridge: Vec<Vec<&ResourceKeySpec>> = vec![Vec::new(); bridge_specs.len()];
6277
6278    for spec in resource_specs {
6279        let ResourceOwner::Bridge(bridge_index) = spec.owner else {
6280            continue;
6281        };
6282        if sim_mode && !bridge_specs[bridge_index].run_in_sim {
6283            continue;
6284        }
6285        per_bridge[bridge_index].push(spec);
6286    }
6287
6288    let mut mapping_defs = Vec::new();
6289    let mut mapping_refs = Vec::new();
6290
6291    for (idx, entries) in per_bridge.iter().enumerate() {
6292        if entries.is_empty() {
6293            mapping_refs.push(quote! { None });
6294            continue;
6295        }
6296
6297        let bridge_type = &bridge_specs[idx].type_path;
6298        let binding_type = quote! {
6299            <<#bridge_type as cu29::cubridge::CuBridge>::Resources<'_> as ResourceBindings>::Binding
6300        };
6301        let entries_ident = format_ident!("BRIDGE{}_RES_ENTRIES", idx);
6302        let map_ident = format_ident!("BRIDGE{}_RES_MAPPING", idx);
6303        let entry_tokens = entries.iter().map(|spec| {
6304            let binding_ident =
6305                Ident::new(&config_id_to_enum(spec.binding_name.as_str()), Span::call_site());
6306            let resource_ident =
6307                Ident::new(&config_id_to_enum(spec.resource_name.as_str()), Span::call_site());
6308            let bundle_index = spec.bundle_index;
6309            let provider_path = &spec.provider_path;
6310            quote! {
6311                (#binding_type::#binding_ident, cu29::resource::ResourceKey::new(
6312                    cu29::resource::BundleIndex::new(#bundle_index),
6313                    <#provider_path as cu29::resource::ResourceBundleDecl>::Id::#resource_ident as usize,
6314                ))
6315            }
6316        });
6317
6318        mapping_defs.push(quote! {
6319            const #entries_ident: &[(#binding_type, cu29::resource::ResourceKey)] = &[ #(#entry_tokens),* ];
6320            const #map_ident: cu29::resource::ResourceBindingMap<#binding_type> =
6321                cu29::resource::ResourceBindingMap::new(#entries_ident);
6322        });
6323        mapping_refs.push(quote! { Some(&#map_ident) });
6324    }
6325
6326    ResourceMappingTokens {
6327        defs: quote! { #(#mapping_defs)* },
6328        refs: mapping_refs,
6329    }
6330}
6331
6332fn build_execution_plan(
6333    graph: &CuGraph,
6334    task_specs: &CuTaskSpecSet,
6335    bridge_specs: &mut [BridgeSpec],
6336) -> CuResult<(
6337    CuExecutionLoop,
6338    Vec<ExecutionEntity>,
6339    HashMap<NodeId, NodeId>,
6340)> {
6341    let mut plan_graph = CuGraph::default();
6342    let mut exec_entities = Vec::new();
6343    let mut original_to_plan = HashMap::new();
6344    let mut plan_to_original = HashMap::new();
6345    let mut name_to_original = HashMap::new();
6346    let mut channel_nodes = HashMap::new();
6347
6348    for (node_id, node) in graph.get_all_nodes() {
6349        name_to_original.insert(node.get_id(), node_id);
6350        if node.get_flavor() != Flavor::Task {
6351            continue;
6352        }
6353        let plan_node_id = plan_graph.add_node(node.clone())?;
6354        let task_index = task_specs.node_id_to_task_index[node_id as usize]
6355            .expect("Task missing from specifications");
6356        plan_to_original.insert(plan_node_id, node_id);
6357        original_to_plan.insert(node_id, plan_node_id);
6358        if plan_node_id as usize != exec_entities.len() {
6359            panic!("Unexpected node ordering while mirroring tasks in plan graph");
6360        }
6361        exec_entities.push(ExecutionEntity {
6362            kind: ExecutionEntityKind::Task { task_index },
6363        });
6364    }
6365
6366    for (bridge_index, spec) in bridge_specs.iter_mut().enumerate() {
6367        for (channel_index, channel_spec) in spec.rx_channels.iter_mut().enumerate() {
6368            let mut node = Node::new(
6369                format!("{}::rx::{}", spec.id, channel_spec.id).as_str(),
6370                "__CuBridgeRxChannel",
6371            );
6372            node.set_flavor(Flavor::Bridge);
6373            let plan_node_id = plan_graph.add_node(node)?;
6374            if plan_node_id as usize != exec_entities.len() {
6375                panic!("Unexpected node ordering while inserting bridge rx channel");
6376            }
6377            channel_spec.plan_node_id = Some(plan_node_id);
6378            exec_entities.push(ExecutionEntity {
6379                kind: ExecutionEntityKind::BridgeRx {
6380                    bridge_index,
6381                    channel_index,
6382                },
6383            });
6384            channel_nodes.insert(
6385                BridgeChannelKey {
6386                    bridge_id: spec.id.clone(),
6387                    channel_id: channel_spec.id.clone(),
6388                    direction: BridgeChannelDirection::Rx,
6389                },
6390                plan_node_id,
6391            );
6392        }
6393
6394        for (channel_index, channel_spec) in spec.tx_channels.iter_mut().enumerate() {
6395            let mut node = Node::new(
6396                format!("{}::tx::{}", spec.id, channel_spec.id).as_str(),
6397                "__CuBridgeTxChannel",
6398            );
6399            node.set_flavor(Flavor::Bridge);
6400            let plan_node_id = plan_graph.add_node(node)?;
6401            if plan_node_id as usize != exec_entities.len() {
6402                panic!("Unexpected node ordering while inserting bridge tx channel");
6403            }
6404            channel_spec.plan_node_id = Some(plan_node_id);
6405            exec_entities.push(ExecutionEntity {
6406                kind: ExecutionEntityKind::BridgeTx {
6407                    bridge_index,
6408                    channel_index,
6409                },
6410            });
6411            channel_nodes.insert(
6412                BridgeChannelKey {
6413                    bridge_id: spec.id.clone(),
6414                    channel_id: channel_spec.id.clone(),
6415                    direction: BridgeChannelDirection::Tx,
6416                },
6417                plan_node_id,
6418            );
6419        }
6420    }
6421
6422    for cnx in graph.edges() {
6423        let src_plan = if let Some(channel) = &cnx.src_channel {
6424            let key = BridgeChannelKey {
6425                bridge_id: cnx.src.clone(),
6426                channel_id: channel.clone(),
6427                direction: BridgeChannelDirection::Rx,
6428            };
6429            *channel_nodes
6430                .get(&key)
6431                .unwrap_or_else(|| panic!("Bridge source {:?} missing from plan graph", key))
6432        } else {
6433            let node_id = name_to_original
6434                .get(&cnx.src)
6435                .copied()
6436                .unwrap_or_else(|| panic!("Unknown source node '{}'", cnx.src));
6437            *original_to_plan
6438                .get(&node_id)
6439                .unwrap_or_else(|| panic!("Source node '{}' missing from plan", cnx.src))
6440        };
6441
6442        let dst_plan = if let Some(channel) = &cnx.dst_channel {
6443            let key = BridgeChannelKey {
6444                bridge_id: cnx.dst.clone(),
6445                channel_id: channel.clone(),
6446                direction: BridgeChannelDirection::Tx,
6447            };
6448            *channel_nodes
6449                .get(&key)
6450                .unwrap_or_else(|| panic!("Bridge destination {:?} missing from plan graph", key))
6451        } else {
6452            let node_id = name_to_original
6453                .get(&cnx.dst)
6454                .copied()
6455                .unwrap_or_else(|| panic!("Unknown destination node '{}'", cnx.dst));
6456            *original_to_plan
6457                .get(&node_id)
6458                .unwrap_or_else(|| panic!("Destination node '{}' missing from plan", cnx.dst))
6459        };
6460
6461        plan_graph
6462            .connect_ext_with_order(
6463                src_plan,
6464                dst_plan,
6465                &cnx.msg,
6466                cnx.missions.clone(),
6467                None,
6468                None,
6469                cnx.order,
6470            )
6471            .map_err(|e| CuError::from(e.to_string()))?;
6472    }
6473
6474    let runtime_plan = compute_runtime_plan(&plan_graph)?;
6475    Ok((runtime_plan, exec_entities, plan_to_original))
6476}
6477
6478fn collect_culist_metadata(
6479    runtime_plan: &CuExecutionLoop,
6480    exec_entities: &[ExecutionEntity],
6481    bridge_specs: &mut [BridgeSpec],
6482    plan_to_original: &HashMap<NodeId, NodeId>,
6483) -> (Vec<usize>, HashMap<NodeId, usize>) {
6484    let mut culist_order = Vec::new();
6485    let mut node_output_positions = HashMap::new();
6486
6487    for unit in &runtime_plan.steps {
6488        if let CuExecutionUnit::Step(step) = unit
6489            && let Some(output_pack) = &step.output_msg_pack
6490        {
6491            let output_idx = output_pack.culist_index;
6492            culist_order.push(output_idx as usize);
6493            match &exec_entities[step.node_id as usize].kind {
6494                ExecutionEntityKind::Task { .. } => {
6495                    if let Some(original_node_id) = plan_to_original.get(&step.node_id) {
6496                        node_output_positions.insert(*original_node_id, output_idx as usize);
6497                    }
6498                }
6499                ExecutionEntityKind::BridgeRx {
6500                    bridge_index,
6501                    channel_index,
6502                } => {
6503                    bridge_specs[*bridge_index].rx_channels[*channel_index].culist_index =
6504                        Some(output_idx as usize);
6505                }
6506                ExecutionEntityKind::BridgeTx {
6507                    bridge_index,
6508                    channel_index,
6509                } => {
6510                    bridge_specs[*bridge_index].tx_channels[*channel_index].culist_index =
6511                        Some(output_idx as usize);
6512                }
6513            }
6514        }
6515    }
6516
6517    (culist_order, node_output_positions)
6518}
6519
6520fn build_monitor_culist_component_mapping(
6521    runtime_plan: &CuExecutionLoop,
6522    exec_entities: &[ExecutionEntity],
6523    bridge_specs: &[BridgeSpec],
6524) -> Result<Vec<usize>, String> {
6525    let mut mapping = Vec::new();
6526    for unit in &runtime_plan.steps {
6527        if let CuExecutionUnit::Step(step) = unit
6528            && step.output_msg_pack.is_some()
6529        {
6530            let Some(entity) = exec_entities.get(step.node_id as usize) else {
6531                return Err(format!(
6532                    "Missing execution entity for plan node {} while building monitor mapping",
6533                    step.node_id
6534                ));
6535            };
6536            let component_index = match &entity.kind {
6537                ExecutionEntityKind::Task { task_index } => *task_index,
6538                ExecutionEntityKind::BridgeRx {
6539                    bridge_index,
6540                    channel_index,
6541                } => bridge_specs
6542                    .get(*bridge_index)
6543                    .and_then(|spec| spec.rx_channels.get(*channel_index))
6544                    .and_then(|channel| channel.monitor_index)
6545                    .ok_or_else(|| {
6546                        format!(
6547                            "Missing monitor index for bridge rx {}:{}",
6548                            bridge_index, channel_index
6549                        )
6550                    })?,
6551                ExecutionEntityKind::BridgeTx {
6552                    bridge_index,
6553                    channel_index,
6554                } => bridge_specs
6555                    .get(*bridge_index)
6556                    .and_then(|spec| spec.tx_channels.get(*channel_index))
6557                    .and_then(|channel| channel.monitor_index)
6558                    .ok_or_else(|| {
6559                        format!(
6560                            "Missing monitor index for bridge tx {}:{}",
6561                            bridge_index, channel_index
6562                        )
6563                    })?,
6564            };
6565            mapping.push(component_index);
6566        }
6567    }
6568    Ok(mapping)
6569}
6570
6571fn build_parallel_rt_stage_entries(
6572    runtime_plan: &CuExecutionLoop,
6573    exec_entities: &[ExecutionEntity],
6574    task_specs: &CuTaskSpecSet,
6575    bridge_specs: &[BridgeSpec],
6576) -> CuResult<Vec<proc_macro2::TokenStream>> {
6577    let mut entries = Vec::new();
6578
6579    for unit in &runtime_plan.steps {
6580        let CuExecutionUnit::Step(step) = unit else {
6581            todo!("parallel runtime metadata for nested loops is not implemented yet")
6582        };
6583
6584        let entity = exec_entities.get(step.node_id as usize).ok_or_else(|| {
6585            CuError::from(format!(
6586                "Missing execution entity for runtime plan node {} while building parallel runtime metadata",
6587                step.node_id
6588            ))
6589        })?;
6590
6591        let (label, kind_tokens, component_index) = match &entity.kind {
6592            ExecutionEntityKind::Task { task_index } => (
6593                task_specs
6594                    .ids
6595                    .get(*task_index)
6596                    .cloned()
6597                    .ok_or_else(|| {
6598                        CuError::from(format!(
6599                            "Missing task id for task index {} while building parallel runtime metadata",
6600                            task_index
6601                        ))
6602                    })?,
6603                quote! { cu29::parallel_rt::ParallelRtStageKind::Task },
6604                *task_index,
6605            ),
6606            ExecutionEntityKind::BridgeRx {
6607                bridge_index,
6608                channel_index,
6609            } => {
6610                let bridge = bridge_specs.get(*bridge_index).ok_or_else(|| {
6611                    CuError::from(format!(
6612                        "Missing bridge spec {} while building parallel runtime metadata",
6613                        bridge_index
6614                    ))
6615                })?;
6616                let channel = bridge.rx_channels.get(*channel_index).ok_or_else(|| {
6617                    CuError::from(format!(
6618                        "Missing bridge rx channel {}:{} while building parallel runtime metadata",
6619                        bridge_index, channel_index
6620                    ))
6621                })?;
6622                let component_index = channel.monitor_index.ok_or_else(|| {
6623                    CuError::from(format!(
6624                        "Missing monitor index for bridge rx {}:{} while building parallel runtime metadata",
6625                        bridge_index, channel_index
6626                    ))
6627                })?;
6628                (
6629                    format!("bridge::{}::rx::{}", bridge.id, channel.id),
6630                    quote! { cu29::parallel_rt::ParallelRtStageKind::BridgeRx },
6631                    component_index,
6632                )
6633            }
6634            ExecutionEntityKind::BridgeTx {
6635                bridge_index,
6636                channel_index,
6637            } => {
6638                let bridge = bridge_specs.get(*bridge_index).ok_or_else(|| {
6639                    CuError::from(format!(
6640                        "Missing bridge spec {} while building parallel runtime metadata",
6641                        bridge_index
6642                    ))
6643                })?;
6644                let channel = bridge.tx_channels.get(*channel_index).ok_or_else(|| {
6645                    CuError::from(format!(
6646                        "Missing bridge tx channel {}:{} while building parallel runtime metadata",
6647                        bridge_index, channel_index
6648                    ))
6649                })?;
6650                let component_index = channel.monitor_index.ok_or_else(|| {
6651                    CuError::from(format!(
6652                        "Missing monitor index for bridge tx {}:{} while building parallel runtime metadata",
6653                        bridge_index, channel_index
6654                    ))
6655                })?;
6656                (
6657                    format!("bridge::{}::tx::{}", bridge.id, channel.id),
6658                    quote! { cu29::parallel_rt::ParallelRtStageKind::BridgeTx },
6659                    component_index,
6660                )
6661            }
6662        };
6663
6664        let node_id = step.node_id;
6665        entries.push(quote! {
6666            cu29::parallel_rt::ParallelRtStageMetadata::new(
6667                #label,
6668                #kind_tokens,
6669                #node_id,
6670                cu29::monitoring::ComponentId::new(#component_index),
6671            )
6672        });
6673    }
6674
6675    Ok(entries)
6676}
6677
6678#[allow(dead_code)]
6679fn build_monitored_ids(task_ids: &[String], bridge_specs: &mut [BridgeSpec]) -> Vec<String> {
6680    let mut names = task_ids.to_vec();
6681    for spec in bridge_specs.iter_mut() {
6682        spec.monitor_index = Some(names.len());
6683        names.push(format!("bridge::{}", spec.id));
6684        for channel in spec.rx_channels.iter_mut() {
6685            channel.monitor_index = Some(names.len());
6686            names.push(format!("bridge::{}::rx::{}", spec.id, channel.id));
6687        }
6688        for channel in spec.tx_channels.iter_mut() {
6689            channel.monitor_index = Some(names.len());
6690            names.push(format!("bridge::{}::tx::{}", spec.id, channel.id));
6691        }
6692    }
6693    names
6694}
6695
6696fn wrap_process_step_tokens(
6697    wrap_process_step: bool,
6698    body: proc_macro2::TokenStream,
6699) -> proc_macro2::TokenStream {
6700    if wrap_process_step {
6701        quote! {{
6702            let __cu_process_step_result: cu29::curuntime::ProcessStepResult = (|| {
6703                #body
6704                Ok(cu29::curuntime::ProcessStepOutcome::Continue)
6705            })();
6706            __cu_process_step_result
6707        }}
6708    } else {
6709        body
6710    }
6711}
6712
6713fn abort_process_step_tokens(wrap_process_step: bool) -> proc_macro2::TokenStream {
6714    if wrap_process_step {
6715        quote! {
6716            return Ok(cu29::curuntime::ProcessStepOutcome::AbortCopperList);
6717        }
6718    } else {
6719        quote! {
6720            __cu_abort_copperlist = true;
6721            break '__cu_process_steps;
6722        }
6723    }
6724}
6725
6726fn parallel_task_lifecycle_tokens(
6727    task_kind: CuTaskType,
6728    task_type: &Type,
6729    component_index: usize,
6730    mission_mod: &Ident,
6731    task_instance: &proc_macro2::TokenStream,
6732    placement: ParallelLifecyclePlacement,
6733) -> (proc_macro2::TokenStream, proc_macro2::TokenStream) {
6734    let rt_guard = rtsan_guard_tokens();
6735    let abort_process_step = abort_process_step_tokens(true);
6736    let task_trait = match task_kind {
6737        CuTaskType::Source => quote! { cu29::cutask::CuSrcTask },
6738        CuTaskType::Sink => quote! { cu29::cutask::CuSinkTask },
6739        CuTaskType::Regular => quote! { cu29::cutask::CuTask },
6740    };
6741
6742    let preprocess = if placement.preprocess {
6743        quote! {
6744            execution_probe.record(cu29::monitoring::ExecutionMarker {
6745                component_id: cu29::monitoring::ComponentId::new(#component_index),
6746                step: CuComponentState::Preprocess,
6747                culistid: Some(clid),
6748            });
6749            ctx.set_current_task(#component_index);
6750            let maybe_error = {
6751                #rt_guard
6752                <#task_type as #task_trait>::preprocess(&mut #task_instance, &ctx)
6753            };
6754            if let Err(error) = maybe_error {
6755                let decision = monitor.process_error(
6756                    cu29::monitoring::ComponentId::new(#component_index),
6757                    CuComponentState::Preprocess,
6758                    &error,
6759                );
6760                match decision {
6761                    Decision::Abort => {
6762                        debug!(
6763                            "Preprocess: ABORT decision from monitoring. Component '{}' errored out during preprocess. Aborting CopperList {}.",
6764                            #mission_mod::monitor_component_label(cu29::monitoring::ComponentId::new(#component_index)),
6765                            clid
6766                        );
6767                        #abort_process_step
6768                    }
6769                    Decision::Ignore => {
6770                        debug!(
6771                            "Preprocess: IGNORE decision from monitoring. Component '{}' errored out during preprocess. The runtime will continue.",
6772                            #mission_mod::monitor_component_label(cu29::monitoring::ComponentId::new(#component_index))
6773                        );
6774                    }
6775                    Decision::Shutdown => {
6776                        debug!(
6777                            "Preprocess: SHUTDOWN decision from monitoring. Component '{}' errored out during preprocess. The runtime cannot continue.",
6778                            #mission_mod::monitor_component_label(cu29::monitoring::ComponentId::new(#component_index))
6779                        );
6780                        return Err(CuError::new_with_cause(
6781                            "Component errored out during preprocess.",
6782                            error,
6783                        ));
6784                    }
6785                }
6786            }
6787        }
6788    } else {
6789        quote! {}
6790    };
6791
6792    let postprocess = if placement.postprocess {
6793        quote! {
6794            execution_probe.record(cu29::monitoring::ExecutionMarker {
6795                component_id: cu29::monitoring::ComponentId::new(#component_index),
6796                step: CuComponentState::Postprocess,
6797                culistid: Some(clid),
6798            });
6799            ctx.set_current_task(#component_index);
6800            let maybe_error = {
6801                #rt_guard
6802                <#task_type as #task_trait>::postprocess(&mut #task_instance, &ctx)
6803            };
6804            if let Err(error) = maybe_error {
6805                let decision = monitor.process_error(
6806                    cu29::monitoring::ComponentId::new(#component_index),
6807                    CuComponentState::Postprocess,
6808                    &error,
6809                );
6810                match decision {
6811                    Decision::Abort => {
6812                        debug!(
6813                            "Postprocess: ABORT decision from monitoring. Component '{}' errored out during postprocess. Continuing with the completed CopperList.",
6814                            #mission_mod::monitor_component_label(cu29::monitoring::ComponentId::new(#component_index))
6815                        );
6816                    }
6817                    Decision::Ignore => {
6818                        debug!(
6819                            "Postprocess: IGNORE decision from monitoring. Component '{}' errored out during postprocess. The runtime will continue.",
6820                            #mission_mod::monitor_component_label(cu29::monitoring::ComponentId::new(#component_index))
6821                        );
6822                    }
6823                    Decision::Shutdown => {
6824                        debug!(
6825                            "Postprocess: SHUTDOWN decision from monitoring. Component '{}' errored out during postprocess. The runtime cannot continue.",
6826                            #mission_mod::monitor_component_label(cu29::monitoring::ComponentId::new(#component_index))
6827                        );
6828                        return Err(CuError::new_with_cause(
6829                            "Component errored out during postprocess.",
6830                            error,
6831                        ));
6832                    }
6833                }
6834            }
6835        }
6836    } else {
6837        quote! {}
6838    };
6839
6840    (preprocess, postprocess)
6841}
6842
6843fn parallel_bridge_lifecycle_tokens(
6844    bridge_type: &Type,
6845    component_index: usize,
6846    mission_mod: &Ident,
6847    placement: ParallelLifecyclePlacement,
6848) -> (proc_macro2::TokenStream, proc_macro2::TokenStream) {
6849    let rt_guard = rtsan_guard_tokens();
6850    let abort_process_step = abort_process_step_tokens(true);
6851
6852    let preprocess = if placement.preprocess {
6853        quote! {
6854            execution_probe.record(cu29::monitoring::ExecutionMarker {
6855                component_id: cu29::monitoring::ComponentId::new(#component_index),
6856                step: CuComponentState::Preprocess,
6857                culistid: Some(clid),
6858            });
6859            ctx.clear_current_task();
6860            let maybe_error = {
6861                #rt_guard
6862                <#bridge_type as cu29::cubridge::CuBridge>::preprocess(bridge, &ctx)
6863            };
6864            if let Err(error) = maybe_error {
6865                let decision = monitor.process_error(
6866                    cu29::monitoring::ComponentId::new(#component_index),
6867                    CuComponentState::Preprocess,
6868                    &error,
6869                );
6870                match decision {
6871                    Decision::Abort => {
6872                        debug!(
6873                            "Preprocess: ABORT decision from monitoring. Component '{}' errored out during preprocess. Aborting CopperList {}.",
6874                            #mission_mod::monitor_component_label(cu29::monitoring::ComponentId::new(#component_index)),
6875                            clid
6876                        );
6877                        #abort_process_step
6878                    }
6879                    Decision::Ignore => {
6880                        debug!(
6881                            "Preprocess: IGNORE decision from monitoring. Component '{}' errored out during preprocess. The runtime will continue.",
6882                            #mission_mod::monitor_component_label(cu29::monitoring::ComponentId::new(#component_index))
6883                        );
6884                    }
6885                    Decision::Shutdown => {
6886                        debug!(
6887                            "Preprocess: SHUTDOWN decision from monitoring. Component '{}' errored out during preprocess. The runtime cannot continue.",
6888                            #mission_mod::monitor_component_label(cu29::monitoring::ComponentId::new(#component_index))
6889                        );
6890                        return Err(CuError::new_with_cause(
6891                            "Component errored out during preprocess.",
6892                            error,
6893                        ));
6894                    }
6895                }
6896            }
6897        }
6898    } else {
6899        quote! {}
6900    };
6901
6902    let postprocess = if placement.postprocess {
6903        quote! {
6904            kf_manager.freeze_any(clid, bridge)?;
6905            execution_probe.record(cu29::monitoring::ExecutionMarker {
6906                component_id: cu29::monitoring::ComponentId::new(#component_index),
6907                step: CuComponentState::Postprocess,
6908                culistid: Some(clid),
6909            });
6910            ctx.clear_current_task();
6911            let maybe_error = {
6912                #rt_guard
6913                <#bridge_type as cu29::cubridge::CuBridge>::postprocess(bridge, &ctx)
6914            };
6915            if let Err(error) = maybe_error {
6916                let decision = monitor.process_error(
6917                    cu29::monitoring::ComponentId::new(#component_index),
6918                    CuComponentState::Postprocess,
6919                    &error,
6920                );
6921                match decision {
6922                    Decision::Abort => {
6923                        debug!(
6924                            "Postprocess: ABORT decision from monitoring. Component '{}' errored out during postprocess. Continuing with the completed CopperList.",
6925                            #mission_mod::monitor_component_label(cu29::monitoring::ComponentId::new(#component_index))
6926                        );
6927                    }
6928                    Decision::Ignore => {
6929                        debug!(
6930                            "Postprocess: IGNORE decision from monitoring. Component '{}' errored out during postprocess. The runtime will continue.",
6931                            #mission_mod::monitor_component_label(cu29::monitoring::ComponentId::new(#component_index))
6932                        );
6933                    }
6934                    Decision::Shutdown => {
6935                        debug!(
6936                            "Postprocess: SHUTDOWN decision from monitoring. Component '{}' errored out during postprocess. The runtime cannot continue.",
6937                            #mission_mod::monitor_component_label(cu29::monitoring::ComponentId::new(#component_index))
6938                        );
6939                        return Err(CuError::new_with_cause(
6940                            "Component errored out during postprocess.",
6941                            error,
6942                        ));
6943                    }
6944                }
6945            }
6946        }
6947    } else {
6948        quote! {}
6949    };
6950
6951    (preprocess, postprocess)
6952}
6953
6954#[derive(Clone, Copy)]
6955struct StepGenerationContext<'a> {
6956    output_pack_sizes: &'a [usize],
6957    sim_mode: bool,
6958    mission_mod: &'a Ident,
6959    lifecycle_placement: ParallelLifecyclePlacement,
6960    wrap_process_step: bool,
6961}
6962
6963impl<'a> StepGenerationContext<'a> {
6964    fn new(
6965        output_pack_sizes: &'a [usize],
6966        sim_mode: bool,
6967        mission_mod: &'a Ident,
6968        lifecycle_placement: ParallelLifecyclePlacement,
6969        wrap_process_step: bool,
6970    ) -> Self {
6971        Self {
6972            output_pack_sizes,
6973            sim_mode,
6974            mission_mod,
6975            lifecycle_placement,
6976            wrap_process_step,
6977        }
6978    }
6979}
6980
6981struct TaskExecutionTokens {
6982    setup: proc_macro2::TokenStream,
6983    instance: proc_macro2::TokenStream,
6984}
6985
6986impl TaskExecutionTokens {
6987    fn new(setup: proc_macro2::TokenStream, instance: proc_macro2::TokenStream) -> Self {
6988        Self { setup, instance }
6989    }
6990}
6991
6992fn generate_task_execution_tokens(
6993    step: &CuExecutionStep,
6994    task_index: usize,
6995    task_specs: &CuTaskSpecSet,
6996    ctx: StepGenerationContext<'_>,
6997    task_tokens: TaskExecutionTokens,
6998) -> (proc_macro2::TokenStream, proc_macro2::TokenStream) {
6999    let StepGenerationContext {
7000        output_pack_sizes,
7001        sim_mode,
7002        mission_mod,
7003        lifecycle_placement,
7004        wrap_process_step,
7005    } = ctx;
7006    let TaskExecutionTokens {
7007        setup: task_setup,
7008        instance: task_instance,
7009    } = task_tokens;
7010    let abort_process_step = abort_process_step_tokens(wrap_process_step);
7011    let comment_str = format!(
7012        "DEBUG ->> {} ({:?}) Id:{} I:{:?} O:{:?}",
7013        step.node.get_id(),
7014        step.task_type,
7015        step.node_id,
7016        step.input_msg_indices_types,
7017        step.output_msg_pack
7018    );
7019    let comment_tokens = quote! {{
7020        let _ = stringify!(#comment_str);
7021    }};
7022    let tid = task_index;
7023    let task_enum_name = config_id_to_enum(&task_specs.ids[tid]);
7024    let enum_name = Ident::new(&task_enum_name, Span::call_site());
7025    let task_hint = config_id_to_struct_member(&task_specs.ids[tid]);
7026    let source_slot_match_trait_ident = format_ident!(
7027        "__CuOutputSlotMustMatchTaskOutput__Task_{}__Add_dst___nc___connections_for_unused_outputs",
7028        task_hint
7029    );
7030    let source_slot_match_fn_ident = format_ident!(
7031        "__cu_source_output_slot_or_add_dst___nc___for_unused_outputs__task_{}",
7032        task_hint
7033    );
7034    let regular_slot_match_trait_ident = format_ident!(
7035        "__CuOutputSlotMustMatchTaskOutput__Task_{}__Add_dst___nc___connections_for_unused_outputs",
7036        task_hint
7037    );
7038    let regular_slot_match_fn_ident = format_ident!(
7039        "__cu_task_output_slot_or_add_dst___nc___for_unused_outputs__task_{}",
7040        task_hint
7041    );
7042    let rt_guard = rtsan_guard_tokens();
7043    let run_in_sim_flag = task_specs.run_in_sim_flags[tid];
7044    let task_type = &task_specs.task_types[tid];
7045    let (parallel_task_preprocess, parallel_task_postprocess) = parallel_task_lifecycle_tokens(
7046        step.task_type,
7047        task_type,
7048        tid,
7049        mission_mod,
7050        &task_instance,
7051        lifecycle_placement,
7052    );
7053    let maybe_sim_tick = if sim_mode && !run_in_sim_flag {
7054        quote! {
7055            if !doit {
7056                #task_instance.sim_tick();
7057            }
7058        }
7059    } else {
7060        quote!()
7061    };
7062
7063    let output_pack = step
7064        .output_msg_pack
7065        .as_ref()
7066        .expect("Task should have an output message pack.");
7067    let output_culist_index = int2sliceindex(output_pack.culist_index);
7068    let output_ports: Vec<syn::Index> = (0..output_pack.msg_types.len())
7069        .map(syn::Index::from)
7070        .collect();
7071    let output_clear_payload = if output_ports.len() == 1 {
7072        quote! { cumsg_output.clear_payload(); }
7073    } else {
7074        quote! { #(cumsg_output.#output_ports.clear_payload();)* }
7075    };
7076    let output_start_time = if output_ports.len() == 1 {
7077        quote! {
7078            if cumsg_output.metadata.process_time.start.is_none() {
7079                cumsg_output.metadata.process_time.start = cu29::curuntime::perf_now(clock).into();
7080            }
7081        }
7082    } else {
7083        quote! {
7084            let start_time = cu29::curuntime::perf_now(clock).into();
7085            #( if cumsg_output.#output_ports.metadata.process_time.start.is_none() {
7086                cumsg_output.#output_ports.metadata.process_time.start = start_time;
7087            } )*
7088        }
7089    };
7090    let output_end_time = if output_ports.len() == 1 {
7091        quote! {
7092            if cumsg_output.metadata.process_time.end.is_none() {
7093                cumsg_output.metadata.process_time.end = cu29::curuntime::perf_now(clock).into();
7094            }
7095        }
7096    } else {
7097        quote! {
7098            let end_time = cu29::curuntime::perf_now(clock).into();
7099            #( if cumsg_output.#output_ports.metadata.process_time.end.is_none() {
7100                cumsg_output.#output_ports.metadata.process_time.end = end_time;
7101            } )*
7102        }
7103    };
7104
7105    match step.task_type {
7106        CuTaskType::Source => {
7107            let monitoring_action = quote! {
7108                debug!("Component {}: Error during process: {}", #mission_mod::monitor_component_label(cu29::monitoring::ComponentId::new(#tid)), &error);
7109                let decision = monitor.process_error(cu29::monitoring::ComponentId::new(#tid), CuComponentState::Process, &error);
7110                match decision {
7111                    Decision::Abort => {
7112                        debug!("Process: ABORT decision from monitoring. Component '{}' errored out \
7113                                during process. Skipping the processing of CL {}.", #mission_mod::monitor_component_label(cu29::monitoring::ComponentId::new(#tid)), clid);
7114                        #abort_process_step
7115                    }
7116                    Decision::Ignore => {
7117                        debug!("Process: IGNORE decision from monitoring. Component '{}' errored out \
7118                                during process. The runtime will continue with a forced empty message.", #mission_mod::monitor_component_label(cu29::monitoring::ComponentId::new(#tid)));
7119                        let cumsg_output = &mut msgs.#output_culist_index;
7120                        #output_clear_payload
7121                    }
7122                    Decision::Shutdown => {
7123                        debug!("Process: SHUTDOWN decision from monitoring. Component '{}' errored out \
7124                                during process. The runtime cannot continue.", #mission_mod::monitor_component_label(cu29::monitoring::ComponentId::new(#tid)));
7125                        return Err(CuError::new_with_cause("Component errored out during process.", error));
7126                    }
7127                }
7128            };
7129
7130            let call_sim_callback = if sim_mode {
7131                quote! {
7132                    let doit = {
7133                        let cumsg_output = &mut msgs.#output_culist_index;
7134                        let state = CuTaskCallbackState::Process((), cumsg_output);
7135                        let ovr = sim_callback(SimStep::#enum_name(state));
7136
7137                        if let SimOverride::Errored(reason) = ovr  {
7138                            let error: CuError = reason.into();
7139                            #monitoring_action
7140                            false
7141                        } else {
7142                            ovr == SimOverride::ExecuteByRuntime
7143                        }
7144                    };
7145                }
7146            } else {
7147                quote! { let doit = true; }
7148            };
7149
7150            let logging_tokens = if !task_specs.logging_enabled[tid] {
7151                quote! {
7152                    let mut cumsg_output = &mut culist.msgs.0.#output_culist_index;
7153                    #output_clear_payload
7154                }
7155            } else {
7156                quote!()
7157            };
7158            let source_process_tokens = quote! {
7159                #[allow(non_camel_case_types)]
7160                trait #source_slot_match_trait_ident<Expected> {
7161                    fn __cu_cast_output_slot(slot: &mut Self) -> &mut Expected;
7162                }
7163                impl<T> #source_slot_match_trait_ident<T> for T {
7164                    fn __cu_cast_output_slot(slot: &mut Self) -> &mut T {
7165                        slot
7166                    }
7167                }
7168
7169                fn #source_slot_match_fn_ident<'a, Task, Slot>(
7170                    _task: &Task,
7171                    slot: &'a mut Slot,
7172                ) -> &'a mut Task::Output<'static>
7173                where
7174                    Task: cu29::cutask::CuSrcTask,
7175                    Slot: #source_slot_match_trait_ident<Task::Output<'static>>,
7176                {
7177                    <Slot as #source_slot_match_trait_ident<Task::Output<'static>>>::__cu_cast_output_slot(slot)
7178                }
7179
7180                #output_start_time
7181                let result = {
7182                    let cumsg_output = #source_slot_match_fn_ident::<
7183                        _,
7184                        _,
7185                    >(&#task_instance, cumsg_output);
7186                    #rt_guard
7187                    ctx.set_current_task(#tid);
7188                    #task_instance.process(&ctx, cumsg_output)
7189                };
7190                #output_end_time
7191                result
7192            };
7193
7194            (
7195                wrap_process_step_tokens(
7196                    wrap_process_step,
7197                    quote! {
7198                        #task_setup
7199                        #parallel_task_preprocess
7200                        #comment_tokens
7201                        kf_manager.freeze_task(clid, &#task_instance)?;
7202                        #call_sim_callback
7203                        let cumsg_output = &mut msgs.#output_culist_index;
7204                        #maybe_sim_tick
7205                        let maybe_error = if doit {
7206                            execution_probe.record(cu29::monitoring::ExecutionMarker {
7207                                component_id: cu29::monitoring::ComponentId::new(#tid),
7208                                step: CuComponentState::Process,
7209                                culistid: Some(clid),
7210                            });
7211                            #source_process_tokens
7212                        } else {
7213                            Ok(())
7214                        };
7215                        if let Err(error) = maybe_error {
7216                            #monitoring_action
7217                        }
7218                        #parallel_task_postprocess
7219                    },
7220                ),
7221                logging_tokens,
7222            )
7223        }
7224        CuTaskType::Sink => {
7225            let input_exprs: Vec<proc_macro2::TokenStream> = step
7226                .input_msg_indices_types
7227                .iter()
7228                .map(|input| {
7229                    let input_index = int2sliceindex(input.culist_index);
7230                    let output_size = output_pack_sizes
7231                        .get(input.culist_index as usize)
7232                        .copied()
7233                        .unwrap_or_else(|| {
7234                            panic!(
7235                                "Missing output pack size for culist index {}",
7236                                input.culist_index
7237                            )
7238                        });
7239                    if output_size > 1 {
7240                        let port_index = syn::Index::from(input.src_port);
7241                        quote! { msgs.#input_index.#port_index }
7242                    } else {
7243                        quote! { msgs.#input_index }
7244                    }
7245                })
7246                .collect();
7247            let inputs_type = if input_exprs.len() == 1 {
7248                let input = input_exprs.first().unwrap();
7249                quote! { #input }
7250            } else {
7251                quote! { (#(&#input_exprs),*) }
7252            };
7253
7254            let monitoring_action = quote! {
7255                debug!("Component {}: Error during process: {}", #mission_mod::monitor_component_label(cu29::monitoring::ComponentId::new(#tid)), &error);
7256                let decision = monitor.process_error(cu29::monitoring::ComponentId::new(#tid), CuComponentState::Process, &error);
7257                match decision {
7258                    Decision::Abort => {
7259                        debug!("Process: ABORT decision from monitoring. Component '{}' errored out \
7260                                during process. Skipping the processing of CL {}.", #mission_mod::monitor_component_label(cu29::monitoring::ComponentId::new(#tid)), clid);
7261                        #abort_process_step
7262                    }
7263                    Decision::Ignore => {
7264                        debug!("Process: IGNORE decision from monitoring. Component '{}' errored out \
7265                                during process. The runtime will continue with a forced empty message.", #mission_mod::monitor_component_label(cu29::monitoring::ComponentId::new(#tid)));
7266                        let cumsg_output = &mut msgs.#output_culist_index;
7267                        #output_clear_payload
7268                    }
7269                    Decision::Shutdown => {
7270                        debug!("Process: SHUTDOWN decision from monitoring. Component '{}' errored out \
7271                                during process. The runtime cannot continue.", #mission_mod::monitor_component_label(cu29::monitoring::ComponentId::new(#tid)));
7272                        return Err(CuError::new_with_cause("Component errored out during process.", error));
7273                    }
7274                }
7275            };
7276
7277            let call_sim_callback = if sim_mode {
7278                quote! {
7279                    let doit = {
7280                        let cumsg_input = &#inputs_type;
7281                        let cumsg_output = &mut msgs.#output_culist_index;
7282                        let state = CuTaskCallbackState::Process(cumsg_input, cumsg_output);
7283                        let ovr = sim_callback(SimStep::#enum_name(state));
7284
7285                        if let SimOverride::Errored(reason) = ovr  {
7286                            let error: CuError = reason.into();
7287                            #monitoring_action
7288                            false
7289                        } else {
7290                            ovr == SimOverride::ExecuteByRuntime
7291                        }
7292                    };
7293                }
7294            } else {
7295                quote! { let doit = true; }
7296            };
7297
7298            (
7299                wrap_process_step_tokens(
7300                    wrap_process_step,
7301                    quote! {
7302                        #task_setup
7303                        #parallel_task_preprocess
7304                        #comment_tokens
7305                        kf_manager.freeze_task(clid, &#task_instance)?;
7306                        #call_sim_callback
7307                        let cumsg_input = &#inputs_type;
7308                        let cumsg_output = &mut msgs.#output_culist_index;
7309                        let maybe_error = if doit {
7310                            execution_probe.record(cu29::monitoring::ExecutionMarker {
7311                                component_id: cu29::monitoring::ComponentId::new(#tid),
7312                                step: CuComponentState::Process,
7313                                culistid: Some(clid),
7314                            });
7315                            #output_start_time
7316                            let result = {
7317                                #rt_guard
7318                                ctx.set_current_task(#tid);
7319                                #task_instance.process(&ctx, cumsg_input)
7320                            };
7321                            #output_end_time
7322                            result
7323                        } else {
7324                            Ok(())
7325                        };
7326                        if let Err(error) = maybe_error {
7327                            #monitoring_action
7328                        }
7329                        #parallel_task_postprocess
7330                    },
7331                ),
7332                quote! {},
7333            )
7334        }
7335        CuTaskType::Regular => {
7336            let input_exprs: Vec<proc_macro2::TokenStream> = step
7337                .input_msg_indices_types
7338                .iter()
7339                .map(|input| {
7340                    let input_index = int2sliceindex(input.culist_index);
7341                    let output_size = output_pack_sizes
7342                        .get(input.culist_index as usize)
7343                        .copied()
7344                        .unwrap_or_else(|| {
7345                            panic!(
7346                                "Missing output pack size for culist index {}",
7347                                input.culist_index
7348                            )
7349                        });
7350                    if output_size > 1 {
7351                        let port_index = syn::Index::from(input.src_port);
7352                        quote! { msgs.#input_index.#port_index }
7353                    } else {
7354                        quote! { msgs.#input_index }
7355                    }
7356                })
7357                .collect();
7358            let inputs_type = if input_exprs.len() == 1 {
7359                let input = input_exprs.first().unwrap();
7360                quote! { #input }
7361            } else {
7362                quote! { (#(&#input_exprs),*) }
7363            };
7364
7365            let monitoring_action = quote! {
7366                debug!("Component {}: Error during process: {}", #mission_mod::monitor_component_label(cu29::monitoring::ComponentId::new(#tid)), &error);
7367                let decision = monitor.process_error(cu29::monitoring::ComponentId::new(#tid), CuComponentState::Process, &error);
7368                match decision {
7369                    Decision::Abort => {
7370                        debug!("Process: ABORT decision from monitoring. Component '{}' errored out \
7371                                during process. Skipping the processing of CL {}.", #mission_mod::monitor_component_label(cu29::monitoring::ComponentId::new(#tid)), clid);
7372                        #abort_process_step
7373                    }
7374                    Decision::Ignore => {
7375                        debug!("Process: IGNORE decision from monitoring. Component '{}' errored out \
7376                                during process. The runtime will continue with a forced empty message.", #mission_mod::monitor_component_label(cu29::monitoring::ComponentId::new(#tid)));
7377                        let cumsg_output = &mut msgs.#output_culist_index;
7378                        #output_clear_payload
7379                    }
7380                    Decision::Shutdown => {
7381                        debug!("Process: SHUTDOWN decision from monitoring. Component '{}' errored out \
7382                                during process. The runtime cannot continue.", #mission_mod::monitor_component_label(cu29::monitoring::ComponentId::new(#tid)));
7383                        return Err(CuError::new_with_cause("Component errored out during process.", error));
7384                    }
7385                }
7386            };
7387
7388            let call_sim_callback = if sim_mode {
7389                quote! {
7390                    let doit = {
7391                        let cumsg_input = &#inputs_type;
7392                        let cumsg_output = &mut msgs.#output_culist_index;
7393                        let state = CuTaskCallbackState::Process(cumsg_input, cumsg_output);
7394                        let ovr = sim_callback(SimStep::#enum_name(state));
7395
7396                        if let SimOverride::Errored(reason) = ovr  {
7397                            let error: CuError = reason.into();
7398                            #monitoring_action
7399                            false
7400                        }
7401                        else {
7402                            ovr == SimOverride::ExecuteByRuntime
7403                        }
7404                    };
7405                }
7406            } else {
7407                quote! { let doit = true; }
7408            };
7409
7410            let logging_tokens = if !task_specs.logging_enabled[tid] {
7411                quote! {
7412                    let mut cumsg_output = &mut culist.msgs.0.#output_culist_index;
7413                    #output_clear_payload
7414                }
7415            } else {
7416                quote!()
7417            };
7418            let regular_process_tokens = quote! {
7419                #[allow(non_camel_case_types)]
7420                trait #regular_slot_match_trait_ident<Expected> {
7421                    fn __cu_cast_output_slot(slot: &mut Self) -> &mut Expected;
7422                }
7423                impl<T> #regular_slot_match_trait_ident<T> for T {
7424                    fn __cu_cast_output_slot(slot: &mut Self) -> &mut T {
7425                        slot
7426                    }
7427                }
7428
7429                fn #regular_slot_match_fn_ident<'a, Task, Slot>(
7430                    _task: &Task,
7431                    slot: &'a mut Slot,
7432                ) -> &'a mut Task::Output<'static>
7433                where
7434                    Task: cu29::cutask::CuTask,
7435                    Slot: #regular_slot_match_trait_ident<Task::Output<'static>>,
7436                {
7437                    <Slot as #regular_slot_match_trait_ident<Task::Output<'static>>>::__cu_cast_output_slot(slot)
7438                }
7439
7440                #output_start_time
7441                let result = {
7442                    let cumsg_output = #regular_slot_match_fn_ident::<
7443                        _,
7444                        _,
7445                    >(&#task_instance, cumsg_output);
7446                    #rt_guard
7447                    ctx.set_current_task(#tid);
7448                    #task_instance.process(&ctx, cumsg_input, cumsg_output)
7449                };
7450                #output_end_time
7451                result
7452            };
7453
7454            (
7455                wrap_process_step_tokens(
7456                    wrap_process_step,
7457                    quote! {
7458                        #task_setup
7459                        #parallel_task_preprocess
7460                        #comment_tokens
7461                        kf_manager.freeze_task(clid, &#task_instance)?;
7462                        #call_sim_callback
7463                        let cumsg_input = &#inputs_type;
7464                        let cumsg_output = &mut msgs.#output_culist_index;
7465                        let maybe_error = if doit {
7466                            execution_probe.record(cu29::monitoring::ExecutionMarker {
7467                                component_id: cu29::monitoring::ComponentId::new(#tid),
7468                                step: CuComponentState::Process,
7469                                culistid: Some(clid),
7470                            });
7471                            #regular_process_tokens
7472                        } else {
7473                            Ok(())
7474                        };
7475                        if let Err(error) = maybe_error {
7476                            #monitoring_action
7477                        }
7478                        #parallel_task_postprocess
7479                    },
7480                ),
7481                logging_tokens,
7482            )
7483        }
7484    }
7485}
7486
7487fn generate_bridge_rx_execution_tokens(
7488    step: &CuExecutionStep,
7489    bridge_spec: &BridgeSpec,
7490    channel_index: usize,
7491    ctx: StepGenerationContext<'_>,
7492    bridge_setup: proc_macro2::TokenStream,
7493) -> (proc_macro2::TokenStream, proc_macro2::TokenStream) {
7494    let StepGenerationContext {
7495        output_pack_sizes: _,
7496        sim_mode,
7497        mission_mod,
7498        lifecycle_placement,
7499        wrap_process_step,
7500    } = ctx;
7501    let rt_guard = rtsan_guard_tokens();
7502    let abort_process_step = abort_process_step_tokens(wrap_process_step);
7503    let channel = &bridge_spec.rx_channels[channel_index];
7504    let output_pack = step
7505        .output_msg_pack
7506        .as_ref()
7507        .expect("Bridge Rx channel missing output pack");
7508    let port_index = output_pack
7509        .msg_types
7510        .iter()
7511        .position(|msg| msg == &channel.msg_type_name)
7512        .unwrap_or_else(|| {
7513            panic!(
7514                "Bridge Rx channel '{}' missing output port for '{}'",
7515                channel.id, channel.msg_type_name
7516            )
7517        });
7518    let culist_index_ts = int2sliceindex(output_pack.culist_index);
7519    let output_ref = if output_pack.msg_types.len() == 1 {
7520        quote! { &mut msgs.#culist_index_ts }
7521    } else {
7522        let port_index = syn::Index::from(port_index);
7523        quote! { &mut msgs.#culist_index_ts.#port_index }
7524    };
7525    let monitor_index = syn::Index::from(
7526        channel
7527            .monitor_index
7528            .expect("Bridge Rx channel missing monitor index"),
7529    );
7530    let bridge_type = runtime_bridge_type_for_spec(bridge_spec, sim_mode);
7531    let (parallel_bridge_preprocess, parallel_bridge_postprocess) =
7532        parallel_bridge_lifecycle_tokens(
7533            &bridge_type,
7534            bridge_spec
7535                .monitor_index
7536                .expect("Bridge missing monitor index for lifecycle"),
7537            mission_mod,
7538            lifecycle_placement,
7539        );
7540    let const_ident = &channel.const_ident;
7541    let enum_ident = Ident::new(
7542        &config_id_to_enum(&format!("{}_rx_{}", bridge_spec.id, channel.id)),
7543        Span::call_site(),
7544    );
7545
7546    let call_sim_callback = if sim_mode {
7547        quote! {
7548            let doit = {
7549                let state = SimStep::#enum_ident {
7550                    channel: &<#bridge_type as cu29::cubridge::CuBridge>::Rx::#const_ident,
7551                    msg: cumsg_output,
7552                };
7553                let ovr = sim_callback(state);
7554                if let SimOverride::Errored(reason) = ovr {
7555                    let error: CuError = reason.into();
7556                    let decision = monitor.process_error(cu29::monitoring::ComponentId::new(#monitor_index), CuComponentState::Process, &error);
7557                    match decision {
7558                        Decision::Abort => {
7559                            debug!("Process: ABORT decision from monitoring. Component '{}' errored out during process. Skipping the processing of CL {}.", #mission_mod::monitor_component_label(cu29::monitoring::ComponentId::new(#monitor_index)), clid);
7560                            #abort_process_step
7561                        }
7562                        Decision::Ignore => {
7563                            debug!("Process: IGNORE decision from monitoring. Component '{}' errored out during process. The runtime will continue with a forced empty message.", #mission_mod::monitor_component_label(cu29::monitoring::ComponentId::new(#monitor_index)));
7564                            cumsg_output.clear_payload();
7565                            false
7566                        }
7567                        Decision::Shutdown => {
7568                            debug!("Process: SHUTDOWN decision from monitoring. Component '{}' errored out during process. The runtime cannot continue.", #mission_mod::monitor_component_label(cu29::monitoring::ComponentId::new(#monitor_index)));
7569                            return Err(CuError::new_with_cause("Component errored out during process.", error));
7570                        }
7571                    }
7572                } else {
7573                    ovr == SimOverride::ExecuteByRuntime
7574                }
7575            };
7576        }
7577    } else {
7578        quote! { let doit = true; }
7579    };
7580    (
7581        wrap_process_step_tokens(
7582            wrap_process_step,
7583            quote! {
7584                #bridge_setup
7585                #parallel_bridge_preprocess
7586                let cumsg_output = #output_ref;
7587                #call_sim_callback
7588                if doit {
7589                    execution_probe.record(cu29::monitoring::ExecutionMarker {
7590                        component_id: cu29::monitoring::ComponentId::new(#monitor_index),
7591                        step: CuComponentState::Process,
7592                        culistid: Some(clid),
7593                    });
7594                    cumsg_output.metadata.process_time.start = cu29::curuntime::perf_now(clock).into();
7595                    let maybe_error = {
7596                        #rt_guard
7597                        ctx.clear_current_task();
7598                        bridge.receive(
7599                            &ctx,
7600                            &<#bridge_type as cu29::cubridge::CuBridge>::Rx::#const_ident,
7601                            cumsg_output,
7602                        )
7603                    };
7604                    cumsg_output.metadata.process_time.end = cu29::curuntime::perf_now(clock).into();
7605                    if let Err(error) = maybe_error {
7606                        let decision = monitor.process_error(cu29::monitoring::ComponentId::new(#monitor_index), CuComponentState::Process, &error);
7607                        match decision {
7608                            Decision::Abort => {
7609                                debug!("Process: ABORT decision from monitoring. Component '{}' errored out during process. Skipping the processing of CL {}.", #mission_mod::monitor_component_label(cu29::monitoring::ComponentId::new(#monitor_index)), clid);
7610                                #abort_process_step
7611                            }
7612                            Decision::Ignore => {
7613                                debug!("Process: IGNORE decision from monitoring. Component '{}' errored out during process. The runtime will continue with a forced empty message.", #mission_mod::monitor_component_label(cu29::monitoring::ComponentId::new(#monitor_index)));
7614                                cumsg_output.clear_payload();
7615                            }
7616                            Decision::Shutdown => {
7617                                debug!("Process: SHUTDOWN decision from monitoring. Component '{}' errored out during process. The runtime cannot continue.", #mission_mod::monitor_component_label(cu29::monitoring::ComponentId::new(#monitor_index)));
7618                                return Err(CuError::new_with_cause("Component errored out during process.", error));
7619                            }
7620                        }
7621                    }
7622                }
7623                #parallel_bridge_postprocess
7624            },
7625        ),
7626        quote! {},
7627    )
7628}
7629
7630fn generate_bridge_tx_execution_tokens(
7631    step: &CuExecutionStep,
7632    bridge_spec: &BridgeSpec,
7633    channel_index: usize,
7634    ctx: StepGenerationContext<'_>,
7635    bridge_setup: proc_macro2::TokenStream,
7636) -> (proc_macro2::TokenStream, proc_macro2::TokenStream) {
7637    let StepGenerationContext {
7638        output_pack_sizes,
7639        sim_mode,
7640        mission_mod,
7641        lifecycle_placement,
7642        wrap_process_step,
7643    } = ctx;
7644    let rt_guard = rtsan_guard_tokens();
7645    let abort_process_step = abort_process_step_tokens(wrap_process_step);
7646    let channel = &bridge_spec.tx_channels[channel_index];
7647    let monitor_index = syn::Index::from(
7648        channel
7649            .monitor_index
7650            .expect("Bridge Tx channel missing monitor index"),
7651    );
7652    let input = step
7653        .input_msg_indices_types
7654        .first()
7655        .expect("Bridge Tx channel should have exactly one input");
7656    let input_index = int2sliceindex(input.culist_index);
7657    let output_size = output_pack_sizes
7658        .get(input.culist_index as usize)
7659        .copied()
7660        .unwrap_or_else(|| {
7661            panic!(
7662                "Missing output pack size for culist index {}",
7663                input.culist_index
7664            )
7665        });
7666    let input_ref = if output_size > 1 {
7667        let port_index = syn::Index::from(input.src_port);
7668        quote! { &mut msgs.#input_index.#port_index }
7669    } else {
7670        quote! { &mut msgs.#input_index }
7671    };
7672    let output_pack = step
7673        .output_msg_pack
7674        .as_ref()
7675        .expect("Bridge Tx channel missing output pack");
7676    if output_pack.msg_types.len() != 1 {
7677        panic!(
7678            "Bridge Tx channel '{}' expected a single output message slot, got {}",
7679            channel.id,
7680            output_pack.msg_types.len()
7681        );
7682    }
7683    let output_index = int2sliceindex(output_pack.culist_index);
7684    let output_ref = quote! { &mut msgs.#output_index };
7685    let bridge_type = runtime_bridge_type_for_spec(bridge_spec, sim_mode);
7686    let (parallel_bridge_preprocess, parallel_bridge_postprocess) =
7687        parallel_bridge_lifecycle_tokens(
7688            &bridge_type,
7689            bridge_spec
7690                .monitor_index
7691                .expect("Bridge missing monitor index for lifecycle"),
7692            mission_mod,
7693            lifecycle_placement,
7694        );
7695    let const_ident = &channel.const_ident;
7696    let enum_ident = Ident::new(
7697        &config_id_to_enum(&format!("{}_tx_{}", bridge_spec.id, channel.id)),
7698        Span::call_site(),
7699    );
7700
7701    let call_sim_callback = if sim_mode {
7702        quote! {
7703            let doit = {
7704                let state = SimStep::#enum_ident {
7705                    channel: &<#bridge_type as cu29::cubridge::CuBridge>::Tx::#const_ident,
7706                    msg: &*cumsg_input,
7707                    output: cumsg_output,
7708                };
7709                let ovr = sim_callback(state);
7710                if let SimOverride::Errored(reason) = ovr  {
7711                    let error: CuError = reason.into();
7712                    let decision = monitor.process_error(cu29::monitoring::ComponentId::new(#monitor_index), CuComponentState::Process, &error);
7713                    match decision {
7714                        Decision::Abort => {
7715                            debug!("Process: ABORT decision from monitoring. Component '{}' errored out during process. Skipping the processing of CL {}.", #mission_mod::monitor_component_label(cu29::monitoring::ComponentId::new(#monitor_index)), clid);
7716                            #abort_process_step
7717                        }
7718                        Decision::Ignore => {
7719                            debug!("Process: IGNORE decision from monitoring. Component '{}' errored out during process. The runtime will continue with a forced empty message.", #mission_mod::monitor_component_label(cu29::monitoring::ComponentId::new(#monitor_index)));
7720                            false
7721                        }
7722                        Decision::Shutdown => {
7723                            debug!("Process: SHUTDOWN decision from monitoring. Component '{}' errored out during process. The runtime cannot continue.", #mission_mod::monitor_component_label(cu29::monitoring::ComponentId::new(#monitor_index)));
7724                            return Err(CuError::new_with_cause("Component errored out during process.", error));
7725                        }
7726                    }
7727                } else {
7728                    ovr == SimOverride::ExecuteByRuntime
7729                }
7730            };
7731        }
7732    } else {
7733        quote! { let doit = true; }
7734    };
7735    (
7736        wrap_process_step_tokens(
7737            wrap_process_step,
7738            quote! {
7739                #bridge_setup
7740                #parallel_bridge_preprocess
7741                let cumsg_input = #input_ref;
7742                let cumsg_output = #output_ref;
7743                let bridge_channel = &<#bridge_type as cu29::cubridge::CuBridge>::Tx::#const_ident;
7744                #call_sim_callback
7745                if doit {
7746                    execution_probe.record(cu29::monitoring::ExecutionMarker {
7747                        component_id: cu29::monitoring::ComponentId::new(#monitor_index),
7748                        step: CuComponentState::Process,
7749                        culistid: Some(clid),
7750                    });
7751                    cumsg_output.metadata.process_time.start = cu29::curuntime::perf_now(clock).into();
7752                    let maybe_error = if bridge_channel.should_send(cumsg_input.payload().is_some()) {
7753                        {
7754                            #rt_guard
7755                            ctx.clear_current_task();
7756                            bridge.send(
7757                                &ctx,
7758                                bridge_channel,
7759                                &*cumsg_input,
7760                            )
7761                        }
7762                    } else {
7763                        Ok(())
7764                    };
7765                    if let Err(error) = maybe_error {
7766                        let decision = monitor.process_error(cu29::monitoring::ComponentId::new(#monitor_index), CuComponentState::Process, &error);
7767                        match decision {
7768                            Decision::Abort => {
7769                                debug!("Process: ABORT decision from monitoring. Component '{}' errored out during process. Skipping the processing of CL {}.", #mission_mod::monitor_component_label(cu29::monitoring::ComponentId::new(#monitor_index)), clid);
7770                                #abort_process_step
7771                            }
7772                            Decision::Ignore => {
7773                                debug!("Process: IGNORE decision from monitoring. Component '{}' errored out during process. The runtime will continue with a forced empty message.", #mission_mod::monitor_component_label(cu29::monitoring::ComponentId::new(#monitor_index)));
7774                            }
7775                            Decision::Shutdown => {
7776                                debug!("Process: SHUTDOWN decision from monitoring. Component '{}' errored out during process. The runtime cannot continue.", #mission_mod::monitor_component_label(cu29::monitoring::ComponentId::new(#monitor_index)));
7777                                return Err(CuError::new_with_cause("Component errored out during process.", error));
7778                            }
7779                        }
7780                    }
7781                    cumsg_output.metadata.process_time.end = cu29::curuntime::perf_now(clock).into();
7782                }
7783                #parallel_bridge_postprocess
7784            },
7785        ),
7786        quote! {},
7787    )
7788}
7789
7790#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
7791enum BridgeChannelDirection {
7792    Rx,
7793    Tx,
7794}
7795
7796#[derive(Clone, Debug, PartialEq, Eq, Hash)]
7797struct BridgeChannelKey {
7798    bridge_id: String,
7799    channel_id: String,
7800    direction: BridgeChannelDirection,
7801}
7802
7803#[derive(Clone)]
7804struct BridgeChannelSpec {
7805    id: String,
7806    const_ident: Ident,
7807    #[allow(dead_code)]
7808    msg_type: Type,
7809    msg_type_name: String,
7810    config_index: usize,
7811    plan_node_id: Option<NodeId>,
7812    culist_index: Option<usize>,
7813    monitor_index: Option<usize>,
7814}
7815
7816#[derive(Clone)]
7817struct BridgeSpec {
7818    id: String,
7819    type_path: Type,
7820    run_in_sim: bool,
7821    config_index: usize,
7822    tuple_index: usize,
7823    monitor_index: Option<usize>,
7824    rx_channels: Vec<BridgeChannelSpec>,
7825    tx_channels: Vec<BridgeChannelSpec>,
7826}
7827
7828#[derive(Clone, Copy, Debug, Default)]
7829struct ParallelLifecyclePlacement {
7830    preprocess: bool,
7831    postprocess: bool,
7832}
7833
7834#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
7835enum ParallelLifecycleKey {
7836    Task(usize),
7837    Bridge(usize),
7838}
7839
7840fn build_parallel_lifecycle_placements(
7841    culist_plan: &CuExecutionLoop,
7842    culist_exec_entities: &[ExecutionEntity],
7843) -> Vec<ParallelLifecyclePlacement> {
7844    let step_keys: Vec<Option<ParallelLifecycleKey>> = culist_plan
7845        .steps
7846        .iter()
7847        .map(|unit| match unit {
7848            CuExecutionUnit::Step(step) => {
7849                match &culist_exec_entities[step.node_id as usize].kind {
7850                    ExecutionEntityKind::Task { task_index } => {
7851                        Some(ParallelLifecycleKey::Task(*task_index))
7852                    }
7853                    ExecutionEntityKind::BridgeRx { bridge_index, .. }
7854                    | ExecutionEntityKind::BridgeTx { bridge_index, .. } => {
7855                        Some(ParallelLifecycleKey::Bridge(*bridge_index))
7856                    }
7857                }
7858            }
7859            CuExecutionUnit::Loop(_) => None,
7860        })
7861        .collect();
7862
7863    let mut placements = vec![ParallelLifecyclePlacement::default(); step_keys.len()];
7864    let mut seen_forward = std::collections::HashSet::new();
7865    for (index, key) in step_keys.iter().enumerate() {
7866        let Some(key) = key else {
7867            continue;
7868        };
7869        if seen_forward.insert(*key) {
7870            placements[index].preprocess = true;
7871        }
7872    }
7873
7874    let mut seen_reverse = std::collections::HashSet::new();
7875    for (index, key) in step_keys.iter().enumerate().rev() {
7876        let Some(key) = key else {
7877            continue;
7878        };
7879        if seen_reverse.insert(*key) {
7880            placements[index].postprocess = true;
7881        }
7882    }
7883
7884    placements
7885}
7886
7887fn sim_bridge_channel_set_idents(bridge_tuple_index: usize) -> (Ident, Ident, Ident, Ident) {
7888    (
7889        format_ident!("__CuSimBridge{}TxChannels", bridge_tuple_index),
7890        format_ident!("__CuSimBridge{}TxId", bridge_tuple_index),
7891        format_ident!("__CuSimBridge{}RxChannels", bridge_tuple_index),
7892        format_ident!("__CuSimBridge{}RxId", bridge_tuple_index),
7893    )
7894}
7895
7896fn runtime_bridge_type_for_spec(bridge_spec: &BridgeSpec, sim_mode: bool) -> Type {
7897    if sim_mode && !bridge_spec.run_in_sim {
7898        let (tx_set_ident, _tx_id_ident, rx_set_ident, _rx_id_ident) =
7899            sim_bridge_channel_set_idents(bridge_spec.tuple_index);
7900        let tx_type: Type = if bridge_spec.tx_channels.is_empty() {
7901            parse_quote!(cu29::simulation::CuNoBridgeChannels)
7902        } else {
7903            parse_quote!(#tx_set_ident)
7904        };
7905        let rx_type: Type = if bridge_spec.rx_channels.is_empty() {
7906            parse_quote!(cu29::simulation::CuNoBridgeChannels)
7907        } else {
7908            parse_quote!(#rx_set_ident)
7909        };
7910        parse_quote!(cu29::simulation::CuSimBridge<#tx_type, #rx_type>)
7911    } else {
7912        bridge_spec.type_path.clone()
7913    }
7914}
7915
7916#[derive(Clone)]
7917struct ExecutionEntity {
7918    kind: ExecutionEntityKind,
7919}
7920
7921#[derive(Clone)]
7922enum ExecutionEntityKind {
7923    Task {
7924        task_index: usize,
7925    },
7926    BridgeRx {
7927        bridge_index: usize,
7928        channel_index: usize,
7929    },
7930    BridgeTx {
7931        bridge_index: usize,
7932        channel_index: usize,
7933    },
7934}
7935
7936#[cfg(test)]
7937mod tests {
7938    use std::fs;
7939    use std::path::{Path, PathBuf};
7940
7941    fn unique_test_dir(name: &str) -> PathBuf {
7942        let nanos = std::time::SystemTime::now()
7943            .duration_since(std::time::UNIX_EPOCH)
7944            .expect("system clock before unix epoch")
7945            .as_nanos();
7946        std::env::temp_dir().join(format!("cu29_derive_{name}_{nanos}"))
7947    }
7948
7949    fn write_file(path: &Path, content: &str) {
7950        if let Some(parent) = path.parent() {
7951            fs::create_dir_all(parent).expect("create parent dirs");
7952        }
7953        fs::write(path, content).expect("write file");
7954    }
7955
7956    // See tests/compile_file directory for more information
7957    #[test]
7958    fn test_compile_fail() {
7959        use rustc_version::{Channel, version_meta};
7960        use std::{env, fs, path::Path};
7961
7962        let log_index_dir = env::temp_dir()
7963            .join("cu29_derive_trybuild_log_index")
7964            .join("a")
7965            .join("b")
7966            .join("c");
7967        fs::create_dir_all(&log_index_dir).unwrap();
7968        unsafe {
7969            env::set_var("LOG_INDEX_DIR", &log_index_dir);
7970        }
7971
7972        let dir = Path::new("tests/compile_fail");
7973        for entry in fs::read_dir(dir).unwrap() {
7974            let entry = entry.unwrap();
7975            if !entry.file_type().unwrap().is_dir() {
7976                continue;
7977            }
7978            for file in fs::read_dir(entry.path()).unwrap() {
7979                let file = file.unwrap();
7980                let p = file.path();
7981                if p.extension().and_then(|x| x.to_str()) != Some("rs") {
7982                    continue;
7983                }
7984
7985                let base = p.with_extension("stderr"); // the file trybuild reads
7986                let src = match version_meta().unwrap().channel {
7987                    Channel::Beta => Path::new(&format!("{}.beta", base.display())).to_path_buf(),
7988                    _ => Path::new(&format!("{}.stable", base.display())).to_path_buf(),
7989                };
7990
7991                if src.exists() {
7992                    fs::copy(src, &base).unwrap();
7993                }
7994            }
7995        }
7996
7997        let t = trybuild::TestCases::new();
7998        t.compile_fail("tests/compile_fail/*/*.rs");
7999        t.pass("tests/compile_pass/*/*.rs");
8000    }
8001
8002    #[test]
8003    fn runtime_plan_keeps_nc_order_for_non_first_connected_output() {
8004        use super::*;
8005        use cu29::config::CuConfig;
8006        use cu29::curuntime::{CuExecutionUnit, compute_runtime_plan};
8007
8008        let config: CuConfig =
8009            read_config("tests/config/multi_output_source_non_first_connected_valid.ron")
8010                .expect("failed to read test config");
8011        let graph = config.get_graph(None).expect("missing graph");
8012        let src_id = graph.get_node_id_by_name("src").expect("missing src node");
8013
8014        let runtime = compute_runtime_plan(graph).expect("runtime plan failed");
8015        let src_step = runtime
8016            .steps
8017            .iter()
8018            .find_map(|step| match step {
8019                CuExecutionUnit::Step(step) if step.node_id == src_id => Some(step),
8020                _ => None,
8021            })
8022            .expect("missing source step");
8023
8024        assert_eq!(
8025            src_step.output_msg_pack.as_ref().unwrap().msg_types,
8026            vec!["i32", "bool"]
8027        );
8028    }
8029
8030    #[test]
8031    fn matching_task_ids_are_flattened_per_output_message() {
8032        use super::*;
8033        use cu29::config::CuConfig;
8034
8035        let config: CuConfig =
8036            read_config("tests/config/multi_output_source_non_first_connected_valid.ron")
8037                .expect("failed to read test config");
8038        let graph = config.get_graph(None).expect("missing graph");
8039        let task_specs = CuTaskSpecSet::from_graph(graph);
8040        let channel_usage = collect_bridge_channel_usage(graph);
8041        let mut bridge_specs = build_bridge_specs(&config, graph, &channel_usage);
8042        let (runtime_plan, exec_entities, plan_to_original) =
8043            build_execution_plan(graph, &task_specs, &mut bridge_specs)
8044                .expect("runtime plan failed");
8045        let output_packs = extract_output_packs(&runtime_plan);
8046        let task_names = collect_task_names(graph);
8047        let (_, node_output_positions) = collect_culist_metadata(
8048            &runtime_plan,
8049            &exec_entities,
8050            &mut bridge_specs,
8051            &plan_to_original,
8052        );
8053
8054        // Rebuild per-slot origin ids like `gen_culist_support` does.
8055        let mut slot_origin_ids: Vec<Option<String>> = vec![None; output_packs.len()];
8056        for (node_id, task_id, _) in task_names {
8057            let output_position = node_output_positions
8058                .get(&node_id)
8059                .unwrap_or_else(|| panic!("Task {task_id} (node id: {node_id}) not found"));
8060            slot_origin_ids[*output_position] = Some(task_id);
8061        }
8062
8063        let flattened_ids = flatten_slot_origin_ids(&output_packs, &slot_origin_ids);
8064
8065        // src emits two messages (i32 + bool), both map to src.
8066        // sink contributes its own output slot (CuMsg<()>), mapped to sink.
8067        assert_eq!(
8068            flattened_ids,
8069            vec!["src".to_string(), "src".to_string(), "sink".to_string()]
8070        );
8071    }
8072
8073    #[test]
8074    fn bridge_resources_are_collected() {
8075        use super::*;
8076        use cu29::config::{CuGraph, Flavor, Node};
8077        use std::collections::HashMap;
8078        use syn::parse_str;
8079
8080        let mut graph = CuGraph::default();
8081        let mut node = Node::new_with_flavor("radio", "bridge::Dummy", Flavor::Bridge);
8082        let mut res = HashMap::new();
8083        res.insert("serial".to_string(), "fc.serial0".to_string());
8084        node.set_resources(Some(res));
8085        graph.add_node(node).expect("bridge node");
8086
8087        let task_specs = CuTaskSpecSet::from_graph(&graph);
8088        let bridge_spec = BridgeSpec {
8089            id: "radio".to_string(),
8090            type_path: parse_str("bridge::Dummy").unwrap(),
8091            run_in_sim: true,
8092            config_index: 0,
8093            tuple_index: 0,
8094            monitor_index: None,
8095            rx_channels: Vec::new(),
8096            tx_channels: Vec::new(),
8097        };
8098
8099        let mut config = cu29::config::CuConfig::default();
8100        config.resources.push(ResourceBundleConfig {
8101            id: "fc".to_string(),
8102            provider: "board::Bundle".to_string(),
8103            config: None,
8104            missions: None,
8105        });
8106        let bundle_specs = build_bundle_specs(&config, "default").expect("bundle specs");
8107        let specs = collect_resource_specs(&graph, &task_specs, &[bridge_spec], &bundle_specs)
8108            .expect("collect specs");
8109        assert_eq!(specs.len(), 1);
8110        assert!(matches!(specs[0].owner, ResourceOwner::Bridge(0)));
8111        assert_eq!(specs[0].binding_name, "serial");
8112        assert_eq!(specs[0].bundle_index, 0);
8113        assert_eq!(specs[0].resource_name, "serial0");
8114    }
8115
8116    #[test]
8117    fn copper_runtime_args_parse_subsystem_mode() {
8118        use super::*;
8119        use quote::quote;
8120
8121        let args = CopperRuntimeArgs::parse_tokens(quote!(
8122            config = "multi_copper.ron",
8123            subsystem = "ping",
8124            sim_mode,
8125            ignore_resources
8126        ))
8127        .expect("parse runtime args");
8128
8129        assert_eq!(args.config_path, "multi_copper.ron");
8130        assert_eq!(args.subsystem_id.as_deref(), Some("ping"));
8131        assert!(args.sim_mode);
8132        assert!(args.ignore_resources);
8133    }
8134
8135    #[test]
8136    fn resolve_runtime_config_from_multi_config_selects_local_subsystem() {
8137        use super::*;
8138
8139        let root = unique_test_dir("multi_runtime_resolve");
8140        let alpha_config = root.join("alpha.ron");
8141        let beta_config = root.join("beta.ron");
8142        let network_config = root.join("multi.ron");
8143
8144        write_file(
8145            &alpha_config,
8146            r#"
8147(
8148    tasks: [
8149        (id: "src", type: "AlphaSource", run_in_sim: true),
8150        (id: "sink", type: "AlphaSink", run_in_sim: true),
8151    ],
8152    cnx: [
8153        (src: "src", dst: "sink", msg: "u32"),
8154    ],
8155)
8156"#,
8157        );
8158        write_file(
8159            &beta_config,
8160            r#"
8161(
8162    tasks: [
8163        (id: "src", type: "BetaSource", run_in_sim: true),
8164        (id: "sink", type: "BetaSink", run_in_sim: true),
8165    ],
8166    cnx: [
8167        (src: "src", dst: "sink", msg: "u64"),
8168    ],
8169)
8170"#,
8171        );
8172        write_file(
8173            &network_config,
8174            r#"
8175(
8176    subsystems: [
8177        (id: "beta", config: "beta.ron"),
8178        (id: "alpha", config: "alpha.ron"),
8179    ],
8180    interconnects: [],
8181)
8182"#,
8183        );
8184
8185        let args = CopperRuntimeArgs {
8186            config_path: "multi.ron".to_string(),
8187            subsystem_id: Some("beta".to_string()),
8188            sim_mode: false,
8189            ignore_resources: false,
8190        };
8191
8192        let resolved =
8193            resolve_runtime_config_with_root(&args, &root).expect("resolve multi runtime config");
8194
8195        assert_eq!(resolved.subsystem_id.as_deref(), Some("beta"));
8196        assert_eq!(resolved.subsystem_code, 1);
8197        let graph = resolved
8198            .local_config
8199            .get_graph(None)
8200            .expect("resolved local config graph");
8201        assert!(graph.get_node_id_by_name("src").is_some());
8202        assert!(resolved.bundled_local_config_content.contains("BetaSource"));
8203    }
8204
8205    #[test]
8206    fn resolve_runtime_config_rejects_missing_subsystem() {
8207        use super::*;
8208
8209        let root = unique_test_dir("multi_runtime_missing_subsystem");
8210        let alpha_config = root.join("alpha.ron");
8211        let network_config = root.join("multi.ron");
8212
8213        write_file(
8214            &alpha_config,
8215            r#"
8216(
8217    tasks: [
8218        (id: "src", type: "AlphaSource", run_in_sim: true),
8219        (id: "sink", type: "AlphaSink", run_in_sim: true),
8220    ],
8221    cnx: [
8222        (src: "src", dst: "sink", msg: "u32"),
8223    ],
8224)
8225"#,
8226        );
8227        write_file(
8228            &network_config,
8229            r#"
8230(
8231    subsystems: [
8232        (id: "alpha", config: "alpha.ron"),
8233    ],
8234    interconnects: [],
8235)
8236"#,
8237        );
8238
8239        let args = CopperRuntimeArgs {
8240            config_path: "multi.ron".to_string(),
8241            subsystem_id: Some("missing".to_string()),
8242            sim_mode: false,
8243            ignore_resources: false,
8244        };
8245
8246        let err = resolve_runtime_config_with_root(&args, &root).expect_err("missing subsystem");
8247        assert!(err.to_string().contains("Subsystem 'missing'"));
8248    }
8249}