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