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