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