Skip to main content

cu29_derive/
lib.rs

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