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; #[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 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#[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
688fn 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 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 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 #msgs_types_tuple_encode
1102 #msgs_types_tuple_decode
1103
1104 #msgs_types_tuple_debug
1106
1107 #msgs_types_tuple_serialize
1109
1110 #msgs_types_tuple_default
1112
1113 #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 let mut variants = plan_enum;
1229
1230 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 #[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#[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 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 #[cfg(feature = "macro_debug")]
1508 eprintln!("[build runtime field]");
1509 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 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 sim_type.clone()
2063 }
2064 else {
2065 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 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 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 { 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 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; }
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 { 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 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; }
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 { 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 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; }
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 { 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 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; }
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 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 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 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 }
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 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)*
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); 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 } 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 #(#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)*
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 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 let mut default_section_size = size_of::<super::#mission_mod::CuList>() * 64;
4419 if let Some(section_size_mib) = config.logging.as_ref().and_then(|l| l.section_size_mib) {
4421 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 )?;
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, )?;
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, )?;
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 #[inline]
4539 pub fn clock(&self) -> cu29::clock::RobotClock {
4540 self.copper_runtime.clock()
4541 }
4542
4543 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 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 #[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 let std_application_impl = if sim_mode {
4936 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 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 };
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 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 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 let mission_mod_tokens = quote! {
5198 mod #mission_mod {
5199 use super::*; 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; 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 #[allow(unused_imports)]
5244 use cu29::monitoring::NoMonitor;
5245
5246 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 #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!() };
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 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
6488fn 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
6497fn 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
6554fn 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
6649fn 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 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
6678fn 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 #[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"); 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 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 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(®ular_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}