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