1use proc_macro::TokenStream;
2use quote::{format_ident, quote};
3use std::collections::{BTreeMap, HashMap};
4use std::fs::read_to_string;
5use syn::Fields::{Named, Unnamed};
6use syn::meta::parser;
7use syn::{
8 Field, Fields, ItemImpl, ItemStruct, LitStr, Type, TypeTuple, parse_macro_input, parse_quote,
9 parse_str,
10};
11
12#[cfg(feature = "macro_debug")]
13use crate::format::rustfmt_generated_code;
14use crate::utils::{config_id_to_bridge_const, config_id_to_enum, config_id_to_struct_member};
15use cu29_runtime::config::CuConfig;
16use cu29_runtime::config::{
17 BridgeChannelConfigRepresentation, CuGraph, Flavor, Node, NodeId, ResourceBundleConfig,
18 read_configuration,
19};
20use cu29_runtime::curuntime::{
21 CuExecutionLoop, CuExecutionStep, CuExecutionUnit, CuTaskType, compute_runtime_plan,
22 find_task_type_for_id,
23};
24use cu29_traits::{CuError, CuResult};
25use proc_macro2::{Ident, Span};
26
27mod bundle_resources;
28mod format;
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
56#[proc_macro]
57pub fn resources(input: TokenStream) -> TokenStream {
58 resources::resources(input)
59}
60
61#[proc_macro]
62pub fn bundle_resources(input: TokenStream) -> TokenStream {
63 bundle_resources::bundle_resources(input)
64}
65
66#[proc_macro]
70pub fn gen_cumsgs(config_path_lit: TokenStream) -> TokenStream {
71 #[cfg(feature = "std")]
72 let std = true;
73
74 #[cfg(not(feature = "std"))]
75 let std = false;
76
77 let config = parse_macro_input!(config_path_lit as LitStr).value();
78 if !std::path::Path::new(&config_full_path(&config)).exists() {
79 return return_error(format!(
80 "The configuration file `{config}` does not exist. Please provide a valid path."
81 ));
82 }
83 #[cfg(feature = "macro_debug")]
84 eprintln!("[gen culist support with {config:?}]");
85 let cuconfig = match read_config(&config) {
86 Ok(cuconfig) => cuconfig,
87 Err(e) => return return_error(e.to_string()),
88 };
89 let graph = cuconfig
90 .get_graph(None) .expect("Could not find the specified mission for gen_cumsgs");
92 let task_specs = CuTaskSpecSet::from_graph(graph);
93 let channel_usage = collect_bridge_channel_usage(graph);
94 let mut bridge_specs = build_bridge_specs(&cuconfig, graph, &channel_usage);
95 let (culist_plan, exec_entities, plan_to_original) =
96 match build_execution_plan(graph, &task_specs, &mut bridge_specs) {
97 Ok(plan) => plan,
98 Err(e) => return return_error(format!("Could not compute copperlist plan: {e}")),
99 };
100 let task_member_names = collect_task_member_names(graph);
101 let (culist_order, node_output_positions) = collect_culist_metadata(
102 &culist_plan,
103 &exec_entities,
104 &mut bridge_specs,
105 &plan_to_original,
106 );
107
108 #[cfg(feature = "macro_debug")]
109 eprintln!(
110 "[The CuStampedDataSet matching tasks ids are {:?}]",
111 culist_order
112 );
113
114 let support = gen_culist_support(
115 &culist_plan,
116 &culist_order,
117 &node_output_positions,
118 &task_member_names,
119 );
120
121 let extra_imports = if !std {
122 quote! {
123 use core::fmt::Debug;
124 use core::fmt::Formatter;
125 use core::fmt::Result as FmtResult;
126 use alloc::vec;
127 }
128 } else {
129 quote! {
130 use std::fmt::Debug;
131 use std::fmt::Formatter;
132 use std::fmt::Result as FmtResult;
133 }
134 };
135
136 let with_uses = quote! {
137 mod cumsgs {
138 use cu29::bincode::Encode;
139 use cu29::bincode::enc::Encoder;
140 use cu29::bincode::error::EncodeError;
141 use cu29::bincode::Decode;
142 use cu29::bincode::de::Decoder;
143 use cu29::bincode::error::DecodeError;
144 use cu29::copperlist::CopperList;
145 use cu29::prelude::CuStampedData;
146 use cu29::prelude::ErasedCuStampedData;
147 use cu29::prelude::ErasedCuStampedDataSet;
148 use cu29::prelude::MatchingTasks;
149 use cu29::prelude::Serialize;
150 use cu29::prelude::CuMsg;
151 use cu29::prelude::CuMsgMetadata;
152 use cu29::prelude::CuListZeroedInit;
153 use cu29::prelude::CuCompactString;
154 #extra_imports
155 #support
156 }
157 use cumsgs::CuStampedDataSet;
158 type CuMsgs=CuStampedDataSet;
159 };
160 with_uses.into()
161}
162
163fn gen_culist_support(
165 runtime_plan: &CuExecutionLoop,
166 culist_indices_in_plan_order: &[usize],
167 node_output_positions: &HashMap<NodeId, usize>,
168 task_member_names: &[(NodeId, String)],
169) -> proc_macro2::TokenStream {
170 #[cfg(feature = "macro_debug")]
171 eprintln!("[Extract msgs types]");
172 let all_msgs_types_in_culist_order = extract_msg_types(runtime_plan);
173
174 let culist_size = all_msgs_types_in_culist_order.len();
175 let task_indices: Vec<_> = culist_indices_in_plan_order
176 .iter()
177 .map(|i| syn::Index::from(*i))
178 .collect();
179
180 #[cfg(feature = "macro_debug")]
181 eprintln!("[build the copperlist struct]");
182 let msgs_types_tuple: TypeTuple = build_culist_tuple(&all_msgs_types_in_culist_order);
183
184 #[cfg(feature = "macro_debug")]
185 eprintln!("[build the copperlist tuple bincode support]");
186 let msgs_types_tuple_encode = build_culist_tuple_encode(&all_msgs_types_in_culist_order);
187 let msgs_types_tuple_decode = build_culist_tuple_decode(&all_msgs_types_in_culist_order);
188
189 #[cfg(feature = "macro_debug")]
190 eprintln!("[build the copperlist tuple debug support]");
191 let msgs_types_tuple_debug = build_culist_tuple_debug(&all_msgs_types_in_culist_order);
192
193 #[cfg(feature = "macro_debug")]
194 eprintln!("[build the copperlist tuple serialize support]");
195 let msgs_types_tuple_serialize = build_culist_tuple_serialize(&all_msgs_types_in_culist_order);
196
197 #[cfg(feature = "macro_debug")]
198 eprintln!("[build the default tuple support]");
199 let msgs_types_tuple_default = build_culist_tuple_default(&all_msgs_types_in_culist_order);
200
201 #[cfg(feature = "macro_debug")]
202 eprintln!("[build erasedcumsgs]");
203
204 let erasedmsg_trait_impl = build_culist_erasedcumsgs(&all_msgs_types_in_culist_order);
205
206 let collect_metadata_function = quote! {
207 pub fn collect_metadata<'a>(culist: &'a CuList) -> [&'a CuMsgMetadata; #culist_size] {
208 [#( &culist.msgs.0.#task_indices.metadata, )*]
209 }
210 };
211
212 let task_name_literals: Vec<String> = task_member_names
213 .iter()
214 .map(|(_, name)| name.clone())
215 .collect();
216
217 let methods = task_member_names.iter().map(|(node_id, name)| {
218 let output_position = node_output_positions
219 .get(node_id)
220 .unwrap_or_else(|| panic!("Task {name} (id: {node_id}) not found in execution order"));
221
222 let fn_name = format_ident!("get_{}_output", name);
223 let payload_type = all_msgs_types_in_culist_order[*output_position].clone();
224 let index = syn::Index::from(*output_position);
225 quote! {
226 #[allow(dead_code)]
227 pub fn #fn_name(&self) -> &CuMsg<#payload_type> {
228 &self.0.#index
229 }
230 }
231 });
232
233 quote! {
235 #collect_metadata_function
236
237 pub struct CuStampedDataSet(pub #msgs_types_tuple);
238
239 pub type CuList = CopperList<CuStampedDataSet>;
240
241 impl CuStampedDataSet {
242 #(#methods)*
243
244 #[allow(dead_code)]
245 fn get_tuple(&self) -> &#msgs_types_tuple {
246 &self.0
247 }
248
249 #[allow(dead_code)]
250 fn get_tuple_mut(&mut self) -> &mut #msgs_types_tuple {
251 &mut self.0
252 }
253 }
254
255 impl MatchingTasks for CuStampedDataSet {
256 #[allow(dead_code)]
257 fn get_all_task_ids() -> &'static [&'static str] {
258 &[#(#task_name_literals),*]
259 }
260 }
261
262 #msgs_types_tuple_encode
264 #msgs_types_tuple_decode
265
266 #msgs_types_tuple_debug
268
269 #msgs_types_tuple_serialize
271
272 #msgs_types_tuple_default
274
275 #erasedmsg_trait_impl
277
278 impl CuListZeroedInit for CuStampedDataSet {
279 fn init_zeroed(&mut self) {
280 #(self.0.#task_indices.metadata.status_txt = CuCompactString::default();)*
281 }
282 }
283 }
284}
285
286fn gen_sim_support(
287 runtime_plan: &CuExecutionLoop,
288 exec_entities: &[ExecutionEntity],
289) -> proc_macro2::TokenStream {
290 #[cfg(feature = "macro_debug")]
291 eprintln!("[Sim: Build SimEnum]");
292 let plan_enum: Vec<proc_macro2::TokenStream> = runtime_plan
293 .steps
294 .iter()
295 .filter_map(|unit| match unit {
296 CuExecutionUnit::Step(step) => {
297 if !matches!(
298 exec_entities[step.node_id as usize].kind,
299 ExecutionEntityKind::Task { .. }
300 ) {
301 return None;
302 }
303 let enum_entry_name = config_id_to_enum(step.node.get_id().as_str());
304 let enum_ident = Ident::new(&enum_entry_name, Span::call_site());
305 let inputs: Vec<Type> = step
306 .input_msg_indices_types
307 .iter()
308 .map(|(_, t)| parse_str::<Type>(format!("CuMsg<{t}>").as_str()).unwrap())
309 .collect();
310 let output: Option<Type> = step
311 .output_msg_index_type
312 .as_ref()
313 .map(|(_, t)| parse_str::<Type>(format!("CuMsg<{t}>").as_str()).unwrap());
314 let no_output = parse_str::<Type>("CuMsg<()>").unwrap();
315 let output = output.as_ref().unwrap_or(&no_output);
316
317 let inputs_type = if inputs.is_empty() {
318 quote! { () }
319 } else if inputs.len() == 1 {
320 let input = inputs.first().unwrap();
321 quote! { &'a #input }
322 } else {
323 quote! { &'a (#(&'a #inputs),*) }
324 };
325
326 Some(quote! {
327 #enum_ident(CuTaskCallbackState<#inputs_type, &'a mut #output>)
328 })
329 }
330 CuExecutionUnit::Loop(_) => {
331 todo!("Needs to be implemented")
332 }
333 })
334 .collect();
335 let mut variants = plan_enum;
336 variants.push(quote! { __Phantom(core::marker::PhantomData<&'a ()>) });
337 quote! {
338 #[allow(dead_code, unused_lifetimes)]
340 pub enum SimStep<'a> {
341 #(#variants),*
342 }
343 }
344}
345
346#[proc_macro_attribute]
350pub fn copper_runtime(args: TokenStream, input: TokenStream) -> TokenStream {
351 #[cfg(feature = "macro_debug")]
352 eprintln!("[entry]");
353 let mut application_struct = parse_macro_input!(input as ItemStruct);
354
355 let application_name = &application_struct.ident;
356 let builder_name = format_ident!("{}Builder", application_name);
357
358 let mut config_file: Option<LitStr> = None;
359 let mut sim_mode = false;
360
361 #[cfg(feature = "std")]
362 let std = true;
363
364 #[cfg(not(feature = "std"))]
365 let std = false;
366
367 let rt_guard = rtsan_guard_tokens();
368
369 let attribute_config_parser = parser(|meta| {
371 if meta.path.is_ident("config") {
372 config_file = Some(meta.value()?.parse()?);
373 Ok(())
374 } else if meta.path.is_ident("sim_mode") {
375 if meta.input.peek(syn::Token![=]) {
377 meta.input.parse::<syn::Token![=]>()?;
378 let value: syn::LitBool = meta.input.parse()?;
379 sim_mode = value.value();
380 Ok(())
381 } else {
382 sim_mode = true;
384 Ok(())
385 }
386 } else {
387 Err(meta.error("unsupported property"))
388 }
389 });
390
391 #[cfg(feature = "macro_debug")]
392 eprintln!("[parse]");
393 parse_macro_input!(args with attribute_config_parser);
395
396 let config_file = match config_file {
407 Some(file) => file.value(),
408 None => {
409 return return_error(
410 "Expected config file attribute like #[CopperRuntime(config = \"path\")]"
411 .to_string(),
412 );
413 }
414 };
415
416 if !std::path::Path::new(&config_full_path(&config_file)).exists() {
417 return return_error(format!(
418 "The configuration file `{config_file}` does not exist. Please provide a valid path."
419 ));
420 }
421
422 let copper_config = match read_config(&config_file) {
423 Ok(cuconfig) => cuconfig,
424 Err(e) => return return_error(e.to_string()),
425 };
426 let copper_config_content = match read_to_string(config_full_path(config_file.as_str())) {
427 Ok(ok) => ok,
428 Err(e) => {
429 return return_error(format!(
430 "Could not read the config file (should not happen because we just succeeded just before). {e}"
431 ));
432 }
433 };
434
435 #[cfg(feature = "macro_debug")]
436 eprintln!("[build monitor type]");
437 let monitor_type = if let Some(monitor_config) = copper_config.get_monitor_config() {
438 let monitor_type = parse_str::<Type>(monitor_config.get_type())
439 .expect("Could not transform the monitor type name into a Rust type.");
440 quote! { #monitor_type }
441 } else {
442 quote! { NoMonitor }
443 };
444
445 #[cfg(feature = "macro_debug")]
447 eprintln!("[build runtime field]");
448 let runtime_field: Field = if sim_mode {
450 parse_quote! {
451 copper_runtime: cu29::curuntime::CuRuntime<CuSimTasks, CuBridges, CuStampedDataSet, #monitor_type, #DEFAULT_CLNB>
452 }
453 } else {
454 parse_quote! {
455 copper_runtime: cu29::curuntime::CuRuntime<CuTasks, CuBridges, CuStampedDataSet, #monitor_type, #DEFAULT_CLNB>
456 }
457 };
458
459 #[cfg(feature = "macro_debug")]
460 eprintln!("[match struct anonymity]");
461 match &mut application_struct.fields {
462 Named(fields_named) => {
463 fields_named.named.push(runtime_field);
464 }
465 Unnamed(fields_unnamed) => {
466 fields_unnamed.unnamed.push(runtime_field);
467 }
468 Fields::Unit => {
469 panic!(
470 "This struct is a unit struct, it should have named or unnamed fields. use struct Something {{}} and not struct Something;"
471 )
472 }
473 };
474
475 let all_missions = copper_config.graphs.get_all_missions_graphs();
476 let mut all_missions_tokens = Vec::<proc_macro2::TokenStream>::new();
477 for (mission, graph) in &all_missions {
478 let mission_mod = parse_str::<Ident>(mission.as_str())
479 .expect("Could not make an identifier of the mission name");
480
481 #[cfg(feature = "macro_debug")]
482 eprintln!("[extract tasks ids & types]");
483 let task_specs = CuTaskSpecSet::from_graph(graph);
484
485 let culist_channel_usage = collect_bridge_channel_usage(graph);
486 let mut culist_bridge_specs =
487 build_bridge_specs(&copper_config, graph, &culist_channel_usage);
488 let (culist_plan, culist_exec_entities, culist_plan_to_original) =
489 match build_execution_plan(graph, &task_specs, &mut culist_bridge_specs) {
490 Ok(plan) => plan,
491 Err(e) => return return_error(format!("Could not compute copperlist plan: {e}")),
492 };
493 let task_member_names = collect_task_member_names(graph);
494 let (culist_call_order, node_output_positions) = collect_culist_metadata(
495 &culist_plan,
496 &culist_exec_entities,
497 &mut culist_bridge_specs,
498 &culist_plan_to_original,
499 );
500
501 #[cfg(feature = "macro_debug")]
502 {
503 eprintln!("[runtime plan for mission {mission}]");
504 eprintln!("{culist_plan:?}");
505 }
506
507 let culist_support: proc_macro2::TokenStream = gen_culist_support(
508 &culist_plan,
509 &culist_call_order,
510 &node_output_positions,
511 &task_member_names,
512 );
513
514 let bundle_specs = match build_bundle_specs(&copper_config, mission.as_str()) {
515 Ok(specs) => specs,
516 Err(e) => return return_error(e.to_string()),
517 };
518 let threadpool_bundle_index = if task_specs.background_flags.iter().any(|&flag| flag) {
519 match bundle_specs
520 .iter()
521 .position(|bundle| bundle.id == "threadpool")
522 {
523 Some(index) => Some(index),
524 None => {
525 return return_error(
526 "Background tasks require the threadpool bundle to be configured"
527 .to_string(),
528 );
529 }
530 }
531 } else {
532 None
533 };
534
535 let resource_specs =
536 match collect_resource_specs(graph, &task_specs, &culist_bridge_specs, &bundle_specs) {
537 Ok(specs) => specs,
538 Err(e) => return return_error(e.to_string()),
539 };
540
541 let (resources_module, resources_instanciator_fn) =
542 match build_resources_module(&bundle_specs) {
543 Ok(tokens) => tokens,
544 Err(e) => return return_error(e.to_string()),
545 };
546 let task_resource_mappings =
547 match build_task_resource_mappings(&resource_specs, &task_specs) {
548 Ok(tokens) => tokens,
549 Err(e) => return return_error(e.to_string()),
550 };
551 let bridge_resource_mappings =
552 build_bridge_resource_mappings(&resource_specs, &culist_bridge_specs);
553
554 let ids = build_monitored_ids(&task_specs.ids, &mut culist_bridge_specs);
555
556 let bridge_types: Vec<Type> = culist_bridge_specs
557 .iter()
558 .map(|spec| spec.type_path.clone())
559 .collect();
560 let bridges_type_tokens: proc_macro2::TokenStream = if bridge_types.is_empty() {
561 quote! { () }
562 } else {
563 let tuple: TypeTuple = parse_quote! { (#(#bridge_types),*,) };
564 quote! { #tuple }
565 };
566
567 let bridge_binding_idents: Vec<Ident> = culist_bridge_specs
568 .iter()
569 .enumerate()
570 .map(|(idx, _)| format_ident!("bridge_{idx}"))
571 .collect();
572
573 let bridge_init_statements: Vec<proc_macro2::TokenStream> = culist_bridge_specs
574 .iter()
575 .enumerate()
576 .map(|(idx, spec)| {
577 let binding_ident = &bridge_binding_idents[idx];
578 let bridge_mapping_ref = bridge_resource_mappings.refs[idx].clone();
579 let bridge_type = &spec.type_path;
580 let bridge_name = spec.id.clone();
581 let config_index = syn::Index::from(spec.config_index);
582 let binding_error = LitStr::new(
583 &format!("Failed to bind resources for bridge '{}'", bridge_name),
584 Span::call_site(),
585 );
586 let tx_configs: Vec<proc_macro2::TokenStream> = spec
587 .tx_channels
588 .iter()
589 .map(|channel| {
590 let const_ident = &channel.const_ident;
591 let channel_name = channel.id.clone();
592 let channel_config_index = syn::Index::from(channel.config_index);
593 quote! {
594 {
595 let (channel_route, channel_config) = match &bridge_cfg.channels[#channel_config_index] {
596 cu29::config::BridgeChannelConfigRepresentation::Tx { route, config, .. } => {
597 (route.clone(), config.clone())
598 }
599 _ => panic!(
600 "Bridge '{}' channel '{}' expected to be Tx",
601 #bridge_name,
602 #channel_name
603 ),
604 };
605 cu29::cubridge::BridgeChannelConfig::from_static(
606 &<#bridge_type as cu29::cubridge::CuBridge>::Tx::#const_ident,
607 channel_route,
608 channel_config,
609 )
610 }
611 }
612 })
613 .collect();
614 let rx_configs: Vec<proc_macro2::TokenStream> = spec
615 .rx_channels
616 .iter()
617 .map(|channel| {
618 let const_ident = &channel.const_ident;
619 let channel_name = channel.id.clone();
620 let channel_config_index = syn::Index::from(channel.config_index);
621 quote! {
622 {
623 let (channel_route, channel_config) = match &bridge_cfg.channels[#channel_config_index] {
624 cu29::config::BridgeChannelConfigRepresentation::Rx { route, config, .. } => {
625 (route.clone(), config.clone())
626 }
627 _ => panic!(
628 "Bridge '{}' channel '{}' expected to be Rx",
629 #bridge_name,
630 #channel_name
631 ),
632 };
633 cu29::cubridge::BridgeChannelConfig::from_static(
634 &<#bridge_type as cu29::cubridge::CuBridge>::Rx::#const_ident,
635 channel_route,
636 channel_config,
637 )
638 }
639 }
640 })
641 .collect();
642 quote! {
643 let #binding_ident = {
644 let bridge_cfg = config
645 .bridges
646 .get(#config_index)
647 .unwrap_or_else(|| panic!("Bridge '{}' missing from configuration", #bridge_name));
648 let bridge_mapping = #bridge_mapping_ref;
649 let bridge_resources = <<#bridge_type as cu29::cubridge::CuBridge>::Resources<'_> as ResourceBindings>::from_bindings(
650 resources,
651 bridge_mapping,
652 )
653 .map_err(|e| cu29::CuError::new_with_cause(#binding_error, e))?;
654 let tx_channels: &[cu29::cubridge::BridgeChannelConfig<
655 <<#bridge_type as cu29::cubridge::CuBridge>::Tx as cu29::cubridge::BridgeChannelSet>::Id,
656 >] = &[#(#tx_configs),*];
657 let rx_channels: &[cu29::cubridge::BridgeChannelConfig<
658 <<#bridge_type as cu29::cubridge::CuBridge>::Rx as cu29::cubridge::BridgeChannelSet>::Id,
659 >] = &[#(#rx_configs),*];
660 <#bridge_type as cu29::cubridge::CuBridge>::new_with(
661 bridge_cfg.config.as_ref(),
662 tx_channels,
663 rx_channels,
664 bridge_resources,
665 )?
666 };
667 }
668 })
669 .collect();
670
671 let bridges_instanciator = if culist_bridge_specs.is_empty() {
672 quote! {
673 pub fn bridges_instanciator(_config: &CuConfig, resources: &mut ResourceManager) -> CuResult<CuBridges> {
674 let _ = resources;
675 Ok(())
676 }
677 }
678 } else {
679 let bridge_bindings = bridge_binding_idents.clone();
680 quote! {
681 pub fn bridges_instanciator(config: &CuConfig, resources: &mut ResourceManager) -> CuResult<CuBridges> {
682 #(#bridge_init_statements)*
683 Ok((#(#bridge_bindings),*,))
684 }
685 }
686 };
687
688 let all_sim_tasks_types: Vec<Type> = task_specs
689 .ids
690 .iter()
691 .zip(&task_specs.cutypes)
692 .zip(&task_specs.sim_task_types)
693 .zip(&task_specs.background_flags)
694 .zip(&task_specs.run_in_sim_flags)
695 .zip(task_specs.output_types.iter())
696 .map(|(((((task_id, task_type), sim_type), background), run_in_sim), output_type)| {
697 match task_type {
698 CuTaskType::Source => {
699 if *background {
700 panic!("CuSrcTask {task_id} cannot be a background task, it should be a regular task.");
701 }
702 if *run_in_sim {
703 sim_type.clone()
704 } else {
705 let msg_type = graph
706 .get_node_output_msg_type(task_id.as_str())
707 .unwrap_or_else(|| panic!("CuSrcTask {task_id} should have an outgoing connection with a valid output msg type"));
708 let sim_task_name = format!("CuSimSrcTask<{msg_type}>");
709 parse_str(sim_task_name.as_str()).unwrap_or_else(|_| panic!("Could not build the placeholder for simulation: {sim_task_name}"))
710 }
711 }
712 CuTaskType::Regular => {
713 if *background {
714 if let Some(out_ty) = output_type {
715 parse_quote!(CuAsyncTask<#sim_type, #out_ty>)
716 } else {
717 panic!("{task_id}: If a task is background, it has to have an output");
718 }
719 } else {
720 sim_type.clone()
722 }
723 },
724 CuTaskType::Sink => {
725 if *background {
726 panic!("CuSinkTask {task_id} cannot be a background task, it should be a regular task.");
727 }
728
729 if *run_in_sim {
730 sim_type.clone()
732 }
733 else {
734 let msg_types = graph
736 .get_node_input_msg_types(task_id.as_str())
737 .unwrap_or_else(|| panic!("CuSinkTask {task_id} should have an incoming connection with a valid input msg type"));
738 let msg_type = if msg_types.len() == 1 {
739 format!("({},)", msg_types[0])
740 } else {
741 format!("({})", msg_types.join(", "))
742 };
743 let sim_task_name = format!("CuSimSinkTask<{msg_type}>");
744 parse_str(sim_task_name.as_str()).unwrap_or_else(|_| panic!("Could not build the placeholder for simulation: {sim_task_name}"))
745 }
746 }
747 }
748 })
749 .collect();
750
751 #[cfg(feature = "macro_debug")]
752 eprintln!("[build task tuples]");
753
754 let task_types = &task_specs.task_types;
755 let task_types_tuple: TypeTuple = if task_types.is_empty() {
758 parse_quote! { () }
759 } else {
760 parse_quote! { (#(#task_types),*,) }
761 };
762
763 let task_types_tuple_sim: TypeTuple = if all_sim_tasks_types.is_empty() {
764 parse_quote! { () }
765 } else {
766 parse_quote! { (#(#all_sim_tasks_types),*,) }
767 };
768
769 #[cfg(feature = "macro_debug")]
770 eprintln!("[gen instances]");
771 let task_sim_instances_init_code = all_sim_tasks_types
772 .iter()
773 .enumerate()
774 .map(|(index, ty)| {
775 let additional_error_info = format!(
776 "Failed to get create instance for {}, instance index {}.",
777 task_specs.type_names[index], index
778 );
779 let mapping_ref = task_resource_mappings.refs[index].clone();
780 let background = task_specs.background_flags[index];
781 let inner_task_type = &task_specs.sim_task_types[index];
782 match task_specs.cutypes[index] {
783 CuTaskType::Source => quote! {
784 {
785 let resources = <<#ty as CuSrcTask>::Resources<'_> as ResourceBindings>::from_bindings(
786 resources,
787 #mapping_ref,
788 ).map_err(|e| e.add_cause(#additional_error_info))?;
789 <#ty>::new_with(all_instances_configs[#index], resources).map_err(|e| e.add_cause(#additional_error_info))?
790 }
791 },
792 CuTaskType::Regular => {
793 if background {
794 let threadpool_bundle_index = threadpool_bundle_index
795 .expect("threadpool bundle missing for background tasks");
796 quote! {
797 {
798 let inner_resources = <<#inner_task_type as CuTask>::Resources<'_> as ResourceBindings>::from_bindings(
799 resources,
800 #mapping_ref,
801 ).map_err(|e| e.add_cause(#additional_error_info))?;
802 let threadpool_key = cu29::resource::ResourceKey::new(
803 cu29::resource::BundleIndex::new(#threadpool_bundle_index),
804 <cu29::resource::ThreadPoolBundle as cu29::resource::ResourceBundleDecl>::Id::BgThreads as usize,
805 );
806 let threadpool = resources.borrow_shared_arc(threadpool_key)?;
807 let resources = cu29::cuasynctask::CuAsyncTaskResources {
808 inner: inner_resources,
809 threadpool,
810 };
811 <#ty>::new_with(all_instances_configs[#index], resources).map_err(|e| e.add_cause(#additional_error_info))?
812 }
813 }
814 } else {
815 quote! {
816 {
817 let resources = <<#ty as CuTask>::Resources<'_> as ResourceBindings>::from_bindings(
818 resources,
819 #mapping_ref,
820 ).map_err(|e| e.add_cause(#additional_error_info))?;
821 <#ty>::new_with(all_instances_configs[#index], resources).map_err(|e| e.add_cause(#additional_error_info))?
822 }
823 }
824 }
825 }
826 CuTaskType::Sink => quote! {
827 {
828 let resources = <<#ty as CuSinkTask>::Resources<'_> as ResourceBindings>::from_bindings(
829 resources,
830 #mapping_ref,
831 ).map_err(|e| e.add_cause(#additional_error_info))?;
832 <#ty>::new_with(all_instances_configs[#index], resources).map_err(|e| e.add_cause(#additional_error_info))?
833 }
834 },
835 }
836 })
837 .collect::<Vec<_>>();
838
839 let task_instances_init_code = task_specs
840 .instantiation_types
841 .iter()
842 .zip(&task_specs.background_flags)
843 .enumerate()
844 .map(|(index, (task_type, background))| {
845 let additional_error_info = format!(
846 "Failed to get create instance for {}, instance index {}.",
847 task_specs.type_names[index], index
848 );
849 let mapping_ref = task_resource_mappings.refs[index].clone();
850 let inner_task_type = &task_specs.sim_task_types[index];
851 match task_specs.cutypes[index] {
852 CuTaskType::Source => quote! {
853 {
854 let resources = <<#task_type as CuSrcTask>::Resources<'_> as ResourceBindings>::from_bindings(
855 resources,
856 #mapping_ref,
857 ).map_err(|e| e.add_cause(#additional_error_info))?;
858 #task_type::new_with(all_instances_configs[#index], resources).map_err(|e| e.add_cause(#additional_error_info))?
859 }
860 },
861 CuTaskType::Regular => {
862 if *background {
863 let threadpool_bundle_index = threadpool_bundle_index
864 .expect("threadpool bundle missing for background tasks");
865 quote! {
866 {
867 let inner_resources = <<#inner_task_type as CuTask>::Resources<'_> as ResourceBindings>::from_bindings(
868 resources,
869 #mapping_ref,
870 ).map_err(|e| e.add_cause(#additional_error_info))?;
871 let threadpool_key = cu29::resource::ResourceKey::new(
872 cu29::resource::BundleIndex::new(#threadpool_bundle_index),
873 <cu29::resource::ThreadPoolBundle as cu29::resource::ResourceBundleDecl>::Id::BgThreads as usize,
874 );
875 let threadpool = resources.borrow_shared_arc(threadpool_key)?;
876 let resources = cu29::cuasynctask::CuAsyncTaskResources {
877 inner: inner_resources,
878 threadpool,
879 };
880 #task_type::new_with(all_instances_configs[#index], resources).map_err(|e| e.add_cause(#additional_error_info))?
881 }
882 }
883 } else {
884 quote! {
885 {
886 let resources = <<#task_type as CuTask>::Resources<'_> as ResourceBindings>::from_bindings(
887 resources,
888 #mapping_ref,
889 ).map_err(|e| e.add_cause(#additional_error_info))?;
890 #task_type::new_with(all_instances_configs[#index], resources).map_err(|e| e.add_cause(#additional_error_info))?
891 }
892 }
893 }
894 }
895 CuTaskType::Sink => quote! {
896 {
897 let resources = <<#task_type as CuSinkTask>::Resources<'_> as ResourceBindings>::from_bindings(
898 resources,
899 #mapping_ref,
900 ).map_err(|e| e.add_cause(#additional_error_info))?;
901 #task_type::new_with(all_instances_configs[#index], resources).map_err(|e| e.add_cause(#additional_error_info))?
902 }
903 },
904 }
905 })
906 .collect::<Vec<_>>();
907
908 let (
911 task_restore_code,
912 task_start_calls,
913 task_stop_calls,
914 task_preprocess_calls,
915 task_postprocess_calls,
916 ): (Vec<_>, Vec<_>, Vec<_>, Vec<_>, Vec<_>) = itertools::multiunzip(
917 (0..task_specs.task_types.len())
918 .map(|index| {
919 let task_index = int2sliceindex(index as u32);
920 let task_tuple_index = syn::Index::from(index);
921 let task_enum_name = config_id_to_enum(&task_specs.ids[index]);
922 let enum_name = Ident::new(&task_enum_name, Span::call_site());
923 (
924 quote! {
926 tasks.#task_tuple_index.thaw(&mut decoder).map_err(|e| CuError::from("Failed to thaw").add_cause(&e.to_string()))?
927 },
928 { let monitoring_action = quote! {
930 let decision = self.copper_runtime.monitor.process_error(#index, CuTaskState::Start, &error);
931 match decision {
932 Decision::Abort => {
933 debug!("Start: ABORT decision from monitoring. Task '{}' errored out \
934 during start. Aborting all the other starts.", #mission_mod::TASKS_IDS[#index]);
935 return Ok(());
936
937 }
938 Decision::Ignore => {
939 debug!("Start: IGNORE decision from monitoring. Task '{}' errored out \
940 during start. The runtime will continue.", #mission_mod::TASKS_IDS[#index]);
941 }
942 Decision::Shutdown => {
943 debug!("Start: SHUTDOWN decision from monitoring. Task '{}' errored out \
944 during start. The runtime cannot continue.", #mission_mod::TASKS_IDS[#index]);
945 return Err(CuError::new_with_cause("Task errored out during start.", error));
946 }
947 }
948 };
949
950 let call_sim_callback = if sim_mode {
951 quote! {
952 let ovr = sim_callback(SimStep::#enum_name(CuTaskCallbackState::Start));
954
955 let doit = if let SimOverride::Errored(reason) = ovr {
956 let error: CuError = reason.into();
957 #monitoring_action
958 false
959 }
960 else {
961 ovr == SimOverride::ExecuteByRuntime
962 };
963 }
964 } else {
965 quote! {
966 let doit = true; }
968 };
969
970
971 quote! {
972 #call_sim_callback
973 if doit {
974 let task = &mut self.copper_runtime.tasks.#task_index;
975 if let Err(error) = task.start(&self.copper_runtime.clock) {
976 #monitoring_action
977 }
978 }
979 }
980 },
981 { let monitoring_action = quote! {
983 let decision = self.copper_runtime.monitor.process_error(#index, CuTaskState::Stop, &error);
984 match decision {
985 Decision::Abort => {
986 debug!("Stop: ABORT decision from monitoring. Task '{}' errored out \
987 during stop. Aborting all the other starts.", #mission_mod::TASKS_IDS[#index]);
988 return Ok(());
989
990 }
991 Decision::Ignore => {
992 debug!("Stop: IGNORE decision from monitoring. Task '{}' errored out \
993 during stop. The runtime will continue.", #mission_mod::TASKS_IDS[#index]);
994 }
995 Decision::Shutdown => {
996 debug!("Stop: SHUTDOWN decision from monitoring. Task '{}' errored out \
997 during stop. The runtime cannot continue.", #mission_mod::TASKS_IDS[#index]);
998 return Err(CuError::new_with_cause("Task errored out during stop.", error));
999 }
1000 }
1001 };
1002 let call_sim_callback = if sim_mode {
1003 quote! {
1004 let ovr = sim_callback(SimStep::#enum_name(CuTaskCallbackState::Stop));
1006
1007 let doit = if let SimOverride::Errored(reason) = ovr {
1008 let error: CuError = reason.into();
1009 #monitoring_action
1010 false
1011 }
1012 else {
1013 ovr == SimOverride::ExecuteByRuntime
1014 };
1015 }
1016 } else {
1017 quote! {
1018 let doit = true; }
1020 };
1021 quote! {
1022 #call_sim_callback
1023 if doit {
1024 let task = &mut self.copper_runtime.tasks.#task_index;
1025 if let Err(error) = task.stop(&self.copper_runtime.clock) {
1026 #monitoring_action
1027 }
1028 }
1029 }
1030 },
1031 { let monitoring_action = quote! {
1033 let decision = monitor.process_error(#index, CuTaskState::Preprocess, &error);
1034 match decision {
1035 Decision::Abort => {
1036 debug!("Preprocess: ABORT decision from monitoring. Task '{}' errored out \
1037 during preprocess. Aborting all the other starts.", #mission_mod::TASKS_IDS[#index]);
1038 return Ok(());
1039
1040 }
1041 Decision::Ignore => {
1042 debug!("Preprocess: IGNORE decision from monitoring. Task '{}' errored out \
1043 during preprocess. The runtime will continue.", #mission_mod::TASKS_IDS[#index]);
1044 }
1045 Decision::Shutdown => {
1046 debug!("Preprocess: SHUTDOWN decision from monitoring. Task '{}' errored out \
1047 during preprocess. The runtime cannot continue.", #mission_mod::TASKS_IDS[#index]);
1048 return Err(CuError::new_with_cause("Task errored out during preprocess.", error));
1049 }
1050 }
1051 };
1052 let call_sim_callback = if sim_mode {
1053 quote! {
1054 let ovr = sim_callback(SimStep::#enum_name(CuTaskCallbackState::Preprocess));
1056
1057 let doit = if let SimOverride::Errored(reason) = ovr {
1058 let error: CuError = reason.into();
1059 #monitoring_action
1060 false
1061 } else {
1062 ovr == SimOverride::ExecuteByRuntime
1063 };
1064 }
1065 } else {
1066 quote! {
1067 let doit = true; }
1069 };
1070 quote! {
1071 #call_sim_callback
1072 if doit {
1073 let maybe_error = {
1074 #rt_guard
1075 tasks.#task_index.preprocess(clock)
1076 };
1077 if let Err(error) = maybe_error {
1078 #monitoring_action
1079 }
1080 }
1081 }
1082 },
1083 { let monitoring_action = quote! {
1085 let decision = monitor.process_error(#index, CuTaskState::Postprocess, &error);
1086 match decision {
1087 Decision::Abort => {
1088 debug!("Postprocess: ABORT decision from monitoring. Task '{}' errored out \
1089 during postprocess. Aborting all the other starts.", #mission_mod::TASKS_IDS[#index]);
1090 return Ok(());
1091
1092 }
1093 Decision::Ignore => {
1094 debug!("Postprocess: IGNORE decision from monitoring. Task '{}' errored out \
1095 during postprocess. The runtime will continue.", #mission_mod::TASKS_IDS[#index]);
1096 }
1097 Decision::Shutdown => {
1098 debug!("Postprocess: SHUTDOWN decision from monitoring. Task '{}' errored out \
1099 during postprocess. The runtime cannot continue.", #mission_mod::TASKS_IDS[#index]);
1100 return Err(CuError::new_with_cause("Task errored out during postprocess.", error));
1101 }
1102 }
1103 };
1104 let call_sim_callback = if sim_mode {
1105 quote! {
1106 let ovr = sim_callback(SimStep::#enum_name(CuTaskCallbackState::Postprocess));
1108
1109 let doit = if let SimOverride::Errored(reason) = ovr {
1110 let error: CuError = reason.into();
1111 #monitoring_action
1112 false
1113 } else {
1114 ovr == SimOverride::ExecuteByRuntime
1115 };
1116 }
1117 } else {
1118 quote! {
1119 let doit = true; }
1121 };
1122 quote! {
1123 #call_sim_callback
1124 if doit {
1125 let maybe_error = {
1126 #rt_guard
1127 tasks.#task_index.postprocess(clock)
1128 };
1129 if let Err(error) = maybe_error {
1130 #monitoring_action
1131 }
1132 }
1133 }
1134 }
1135 )
1136 })
1137 );
1138
1139 let bridge_start_calls: Vec<proc_macro2::TokenStream> = culist_bridge_specs
1140 .iter()
1141 .map(|spec| {
1142 let bridge_index = int2sliceindex(spec.tuple_index as u32);
1143 let monitor_index = syn::Index::from(
1144 spec.monitor_index
1145 .expect("Bridge missing monitor index for start"),
1146 );
1147 quote! {
1148 {
1149 let bridge = &mut self.copper_runtime.bridges.#bridge_index;
1150 if let Err(error) = bridge.start(&self.copper_runtime.clock) {
1151 let decision = self.copper_runtime.monitor.process_error(#monitor_index, CuTaskState::Start, &error);
1152 match decision {
1153 Decision::Abort => {
1154 debug!("Start: ABORT decision from monitoring. Task '{}' errored out during start. Aborting all the other starts.", #mission_mod::TASKS_IDS[#monitor_index]);
1155 return Ok(());
1156 }
1157 Decision::Ignore => {
1158 debug!("Start: IGNORE decision from monitoring. Task '{}' errored out during start. The runtime will continue.", #mission_mod::TASKS_IDS[#monitor_index]);
1159 }
1160 Decision::Shutdown => {
1161 debug!("Start: SHUTDOWN decision from monitoring. Task '{}' errored out during start. The runtime cannot continue.", #mission_mod::TASKS_IDS[#monitor_index]);
1162 return Err(CuError::new_with_cause("Task errored out during start.", error));
1163 }
1164 }
1165 }
1166 }
1167 }
1168 })
1169 .collect();
1170
1171 let bridge_stop_calls: Vec<proc_macro2::TokenStream> = culist_bridge_specs
1172 .iter()
1173 .map(|spec| {
1174 let bridge_index = int2sliceindex(spec.tuple_index as u32);
1175 let monitor_index = syn::Index::from(
1176 spec.monitor_index
1177 .expect("Bridge missing monitor index for stop"),
1178 );
1179 quote! {
1180 {
1181 let bridge = &mut self.copper_runtime.bridges.#bridge_index;
1182 if let Err(error) = bridge.stop(&self.copper_runtime.clock) {
1183 let decision = self.copper_runtime.monitor.process_error(#monitor_index, CuTaskState::Stop, &error);
1184 match decision {
1185 Decision::Abort => {
1186 debug!("Stop: ABORT decision from monitoring. Task '{}' errored out during stop. Aborting all the other stops.", #mission_mod::TASKS_IDS[#monitor_index]);
1187 return Ok(());
1188 }
1189 Decision::Ignore => {
1190 debug!("Stop: IGNORE decision from monitoring. Task '{}' errored out during stop. The runtime will continue.", #mission_mod::TASKS_IDS[#monitor_index]);
1191 }
1192 Decision::Shutdown => {
1193 debug!("Stop: SHUTDOWN decision from monitoring. Task '{}' errored out during stop. The runtime cannot continue.", #mission_mod::TASKS_IDS[#monitor_index]);
1194 return Err(CuError::new_with_cause("Task errored out during stop.", error));
1195 }
1196 }
1197 }
1198 }
1199 }
1200 })
1201 .collect();
1202
1203 let bridge_preprocess_calls: Vec<proc_macro2::TokenStream> = culist_bridge_specs
1204 .iter()
1205 .map(|spec| {
1206 let bridge_index = int2sliceindex(spec.tuple_index as u32);
1207 let monitor_index = syn::Index::from(
1208 spec.monitor_index
1209 .expect("Bridge missing monitor index for preprocess"),
1210 );
1211 quote! {
1212 {
1213 let bridge = &mut bridges.#bridge_index;
1214 let maybe_error = {
1215 #rt_guard
1216 bridge.preprocess(clock)
1217 };
1218 if let Err(error) = maybe_error {
1219 let decision = monitor.process_error(#monitor_index, CuTaskState::Preprocess, &error);
1220 match decision {
1221 Decision::Abort => {
1222 debug!("Preprocess: ABORT decision from monitoring. Task '{}' errored out during preprocess. Aborting all the other starts.", #mission_mod::TASKS_IDS[#monitor_index]);
1223 return Ok(());
1224 }
1225 Decision::Ignore => {
1226 debug!("Preprocess: IGNORE decision from monitoring. Task '{}' errored out during preprocess. The runtime will continue.", #mission_mod::TASKS_IDS[#monitor_index]);
1227 }
1228 Decision::Shutdown => {
1229 debug!("Preprocess: SHUTDOWN decision from monitoring. Task '{}' errored out during preprocess. The runtime cannot continue.", #mission_mod::TASKS_IDS[#monitor_index]);
1230 return Err(CuError::new_with_cause("Task errored out during preprocess.", error));
1231 }
1232 }
1233 }
1234 }
1235 }
1236 })
1237 .collect();
1238
1239 let bridge_postprocess_calls: Vec<proc_macro2::TokenStream> = culist_bridge_specs
1240 .iter()
1241 .map(|spec| {
1242 let bridge_index = int2sliceindex(spec.tuple_index as u32);
1243 let monitor_index = syn::Index::from(
1244 spec.monitor_index
1245 .expect("Bridge missing monitor index for postprocess"),
1246 );
1247 quote! {
1248 {
1249 let bridge = &mut bridges.#bridge_index;
1250 let maybe_error = {
1251 #rt_guard
1252 bridge.postprocess(clock)
1253 };
1254 if let Err(error) = maybe_error {
1255 let decision = monitor.process_error(#monitor_index, CuTaskState::Postprocess, &error);
1256 match decision {
1257 Decision::Abort => {
1258 debug!("Postprocess: ABORT decision from monitoring. Task '{}' errored out during postprocess. Aborting all the other starts.", #mission_mod::TASKS_IDS[#monitor_index]);
1259 return Ok(());
1260 }
1261 Decision::Ignore => {
1262 debug!("Postprocess: IGNORE decision from monitoring. Task '{}' errored out during postprocess. The runtime will continue.", #mission_mod::TASKS_IDS[#monitor_index]);
1263 }
1264 Decision::Shutdown => {
1265 debug!("Postprocess: SHUTDOWN decision from monitoring. Task '{}' errored out during postprocess. The runtime cannot continue.", #mission_mod::TASKS_IDS[#monitor_index]);
1266 return Err(CuError::new_with_cause("Task errored out during postprocess.", error));
1267 }
1268 }
1269 }
1270 }
1271 }
1272 })
1273 .collect();
1274
1275 let mut start_calls = bridge_start_calls;
1276 start_calls.extend(task_start_calls);
1277 let mut stop_calls = task_stop_calls;
1278 stop_calls.extend(bridge_stop_calls);
1279 let mut preprocess_calls = bridge_preprocess_calls;
1280 preprocess_calls.extend(task_preprocess_calls);
1281 let mut postprocess_calls = task_postprocess_calls;
1282 postprocess_calls.extend(bridge_postprocess_calls);
1283
1284 let runtime_plan_code_and_logging: Vec<(
1285 proc_macro2::TokenStream,
1286 proc_macro2::TokenStream,
1287 )> = culist_plan
1288 .steps
1289 .iter()
1290 .map(|unit| match unit {
1291 CuExecutionUnit::Step(step) => {
1292 #[cfg(feature = "macro_debug")]
1293 eprintln!(
1294 "{} -> {} as {:?}. task_id: {} Input={:?}, Output={:?}",
1295 step.node.get_id(),
1296 step.node.get_type(),
1297 step.task_type,
1298 step.node_id,
1299 step.input_msg_indices_types,
1300 step.output_msg_index_type
1301 );
1302
1303 match &culist_exec_entities[step.node_id as usize].kind {
1304 ExecutionEntityKind::Task { task_index } => generate_task_execution_tokens(
1305 step,
1306 *task_index,
1307 &task_specs,
1308 sim_mode,
1309 &mission_mod,
1310 ),
1311 ExecutionEntityKind::BridgeRx {
1312 bridge_index,
1313 channel_index,
1314 } => {
1315 let spec = &culist_bridge_specs[*bridge_index];
1316 generate_bridge_rx_execution_tokens(spec, *channel_index, &mission_mod)
1317 }
1318 ExecutionEntityKind::BridgeTx {
1319 bridge_index,
1320 channel_index,
1321 } => {
1322 let spec = &culist_bridge_specs[*bridge_index];
1323 generate_bridge_tx_execution_tokens(
1324 step,
1325 spec,
1326 *channel_index,
1327 &mission_mod,
1328 )
1329 }
1330 }
1331 }
1332 CuExecutionUnit::Loop(_) => {
1333 panic!("Execution loops are not supported in runtime generation");
1334 }
1335 })
1336 .collect();
1337
1338 let sim_support = if sim_mode {
1339 Some(gen_sim_support(&culist_plan, &culist_exec_entities))
1340 } else {
1341 None
1342 };
1343
1344 let (new, run_one_iteration, start_all_tasks, stop_all_tasks, run) = if sim_mode {
1345 (
1346 quote! {
1347 fn new(clock:RobotClock, unified_logger: Arc<Mutex<L>>, config_override: Option<CuConfig>, sim_callback: &mut impl FnMut(SimStep) -> SimOverride) -> CuResult<Self>
1348 },
1349 quote! {
1350 fn run_one_iteration(&mut self, sim_callback: &mut impl FnMut(SimStep) -> SimOverride) -> CuResult<()>
1351 },
1352 quote! {
1353 fn start_all_tasks(&mut self, sim_callback: &mut impl FnMut(SimStep) -> SimOverride) -> CuResult<()>
1354 },
1355 quote! {
1356 fn stop_all_tasks(&mut self, sim_callback: &mut impl FnMut(SimStep) -> SimOverride) -> CuResult<()>
1357 },
1358 quote! {
1359 fn run(&mut self, sim_callback: &mut impl FnMut(SimStep) -> SimOverride) -> CuResult<()>
1360 },
1361 )
1362 } else {
1363 (
1364 if std {
1365 quote! {
1366 fn new(clock:RobotClock, unified_logger: Arc<Mutex<L>>, config_override: Option<CuConfig>) -> CuResult<Self>
1367 }
1368 } else {
1369 quote! {
1370 fn new(clock:RobotClock, unified_logger: Arc<Mutex<L>>) -> CuResult<Self>
1372 }
1373 },
1374 quote! {
1375 fn run_one_iteration(&mut self) -> CuResult<()>
1376 },
1377 quote! {
1378 fn start_all_tasks(&mut self) -> CuResult<()>
1379 },
1380 quote! {
1381 fn stop_all_tasks(&mut self) -> CuResult<()>
1382 },
1383 quote! {
1384 fn run(&mut self) -> CuResult<()>
1385 },
1386 )
1387 };
1388
1389 let sim_callback_arg = if sim_mode {
1390 Some(quote!(sim_callback))
1391 } else {
1392 None
1393 };
1394
1395 let app_trait = if sim_mode {
1396 quote!(CuSimApplication)
1397 } else {
1398 quote!(CuApplication)
1399 };
1400
1401 let sim_callback_on_new_calls = task_specs.ids.iter().enumerate().map(|(i, id)| {
1402 let enum_name = config_id_to_enum(id);
1403 let enum_ident = Ident::new(&enum_name, Span::call_site());
1404 quote! {
1405 sim_callback(SimStep::#enum_ident(CuTaskCallbackState::New(all_instances_configs[#i].cloned())));
1407 }
1408 });
1409
1410 let sim_callback_on_new = if sim_mode {
1411 Some(quote! {
1412 let graph = config.get_graph(Some(#mission)).expect("Could not find the mission #mission");
1413 let all_instances_configs: Vec<Option<&ComponentConfig>> = graph
1414 .get_all_nodes()
1415 .iter()
1416 .map(|(_, node)| node.get_instance_config())
1417 .collect();
1418 #(#sim_callback_on_new_calls)*
1419 })
1420 } else {
1421 None
1422 };
1423
1424 let (runtime_plan_code, preprocess_logging_calls): (Vec<_>, Vec<_>) =
1425 itertools::multiunzip(runtime_plan_code_and_logging);
1426
1427 let config_load_stmt = if std {
1428 quote! {
1429 let config = if let Some(overridden_config) = config_override {
1430 debug!("CuConfig: Overridden programmatically: {}", overridden_config.serialize_ron());
1431 overridden_config
1432 } else if ::std::path::Path::new(config_filename).exists() {
1433 debug!("CuConfig: Reading configuration from file: {}", config_filename);
1434 cu29::config::read_configuration(config_filename)?
1435 } else {
1436 let original_config = Self::original_config();
1437 debug!("CuConfig: Using the original configuration the project was compiled with: {}", &original_config);
1438 cu29::config::read_configuration_str(original_config, None)?
1439 };
1440 }
1441 } else {
1442 quote! {
1443 let original_config = Self::original_config();
1445 debug!("CuConfig: Using the original configuration the project was compiled with: {}", &original_config);
1446 let config = cu29::config::read_configuration_str(original_config, None)?;
1447 }
1448 };
1449
1450 let init_resources_sig = if std {
1451 quote! {
1452 pub fn init_resources(config_override: Option<CuConfig>) -> CuResult<AppResources>
1453 }
1454 } else {
1455 quote! {
1456 pub fn init_resources() -> CuResult<AppResources>
1457 }
1458 };
1459
1460 let init_resources_call = if std {
1461 quote! { Self::init_resources(config_override)? }
1462 } else {
1463 quote! { Self::init_resources()? }
1464 };
1465
1466 let new_with_resources_sig = if sim_mode {
1467 quote! {
1468 pub fn new_with_resources<S: SectionStorage + 'static, L: UnifiedLogWrite<S> + 'static>(
1469 clock: RobotClock,
1470 unified_logger: Arc<Mutex<L>>,
1471 app_resources: AppResources,
1472 sim_callback: &mut impl FnMut(SimStep) -> SimOverride,
1473 ) -> CuResult<Self>
1474 }
1475 } else {
1476 quote! {
1477 pub fn new_with_resources<S: SectionStorage + 'static, L: UnifiedLogWrite<S> + 'static>(
1478 clock: RobotClock,
1479 unified_logger: Arc<Mutex<L>>,
1480 app_resources: AppResources,
1481 ) -> CuResult<Self>
1482 }
1483 };
1484
1485 let new_with_resources_call = if sim_mode {
1486 quote! { Self::new_with_resources(clock, unified_logger, app_resources, sim_callback) }
1487 } else {
1488 quote! { Self::new_with_resources(clock, unified_logger, app_resources) }
1489 };
1490
1491 let kill_handler = if std {
1492 Some(quote! {
1493 ctrlc::set_handler(move || {
1494 STOP_FLAG.store(true, Ordering::SeqCst);
1495 }).expect("Error setting Ctrl-C handler");
1496 })
1497 } else {
1498 None
1499 };
1500
1501 let run_loop = if std {
1502 quote! {
1503 loop {
1504 let iter_start = self.copper_runtime.clock.now();
1505 let result = <Self as #app_trait<S, L>>::run_one_iteration(self, #sim_callback_arg);
1506
1507 if let Some(rate) = self.copper_runtime.runtime_config.rate_target_hz {
1508 let period: CuDuration = (1_000_000_000u64 / rate).into();
1509 let elapsed = self.copper_runtime.clock.now() - iter_start;
1510 if elapsed < period {
1511 std::thread::sleep(std::time::Duration::from_nanos(period.as_nanos() - elapsed.as_nanos()));
1512 }
1513 }
1514
1515 if STOP_FLAG.load(Ordering::SeqCst) || result.is_err() {
1516 break result;
1517 }
1518 }
1519 }
1520 } else {
1521 quote! {
1522 loop {
1523 let iter_start = self.copper_runtime.clock.now();
1524 let result = <Self as #app_trait<S, L>>::run_one_iteration(self, #sim_callback_arg);
1525 if let Some(rate) = self.copper_runtime.runtime_config.rate_target_hz {
1526 let period: CuDuration = (1_000_000_000u64 / rate).into();
1527 let elapsed = self.copper_runtime.clock.now() - iter_start;
1528 if elapsed < period {
1529 busy_wait_for(period - elapsed);
1530 }
1531 }
1532
1533 if STOP_FLAG.load(Ordering::SeqCst) || result.is_err() {
1534 break result;
1535 }
1536 }
1537 }
1538 };
1539
1540 #[cfg(feature = "macro_debug")]
1541 eprintln!("[build the run methods]");
1542 let run_methods = quote! {
1543
1544 #run_one_iteration {
1545
1546 let runtime = &mut self.copper_runtime;
1548 let clock = &runtime.clock;
1549 let monitor = &mut runtime.monitor;
1550 let tasks = &mut runtime.tasks;
1551 let bridges = &mut runtime.bridges;
1552 let cl_manager = &mut runtime.copperlists_manager;
1553 let kf_manager = &mut runtime.keyframes_manager;
1554
1555 #(#preprocess_calls)*
1557
1558 let culist = cl_manager.inner.create().expect("Ran out of space for copper lists"); let clid = culist.id;
1560 kf_manager.reset(clid, clock); culist.change_state(cu29::copperlist::CopperListState::Processing);
1562 culist.msgs.init_zeroed();
1563 {
1564 let msgs = &mut culist.msgs.0;
1565 #(#runtime_plan_code)*
1566 } monitor.process_copperlist(&#mission_mod::collect_metadata(&culist))?;
1568
1569 #(#preprocess_logging_calls)*
1571
1572 cl_manager.end_of_processing(clid)?;
1573 kf_manager.end_of_processing(clid)?;
1574
1575 #(#postprocess_calls)*
1577 Ok(())
1578 }
1579
1580 fn restore_keyframe(&mut self, keyframe: &KeyFrame) -> CuResult<()> {
1581 let runtime = &mut self.copper_runtime;
1582 let clock = &runtime.clock;
1583 let tasks = &mut runtime.tasks;
1584 let config = cu29::bincode::config::standard();
1585 let reader = cu29::bincode::de::read::SliceReader::new(&keyframe.serialized_tasks);
1586 let mut decoder = DecoderImpl::new(reader, config, ());
1587 #(#task_restore_code);*;
1588 Ok(())
1589 }
1590
1591 #start_all_tasks {
1592 #(#start_calls)*
1593 self.copper_runtime.monitor.start(&self.copper_runtime.clock)?;
1594 Ok(())
1595 }
1596
1597 #stop_all_tasks {
1598 #(#stop_calls)*
1599 self.copper_runtime.monitor.stop(&self.copper_runtime.clock)?;
1600 Ok(())
1601 }
1602
1603 #run {
1604 static STOP_FLAG: AtomicBool = AtomicBool::new(false);
1605
1606 #kill_handler
1607
1608 <Self as #app_trait<S, L>>::start_all_tasks(self, #sim_callback_arg)?;
1609 let result = #run_loop;
1610
1611 if result.is_err() {
1612 error!("A task errored out: {}", &result);
1613 }
1614 <Self as #app_trait<S, L>>::stop_all_tasks(self, #sim_callback_arg)?;
1615 result
1616 }
1617 };
1618
1619 let tasks_type = if sim_mode {
1620 quote!(CuSimTasks)
1621 } else {
1622 quote!(CuTasks)
1623 };
1624
1625 let tasks_instanciator_fn = if sim_mode {
1626 quote!(tasks_instanciator_sim)
1627 } else {
1628 quote!(tasks_instanciator)
1629 };
1630
1631 let app_impl_decl = if sim_mode {
1632 quote!(impl<S: SectionStorage + 'static, L: UnifiedLogWrite<S> + 'static> CuSimApplication<S, L> for #application_name)
1633 } else {
1634 quote!(impl<S: SectionStorage + 'static, L: UnifiedLogWrite<S> + 'static> CuApplication<S, L> for #application_name)
1635 };
1636
1637 let simstep_type_decl = if sim_mode {
1638 quote!(
1639 type Step<'z> = SimStep<'z>;
1640 )
1641 } else {
1642 quote!()
1643 };
1644
1645 let app_resources_struct = quote! {
1646 pub struct AppResources {
1647 pub config: CuConfig,
1648 pub resources: ResourceManager,
1649 }
1650 };
1651
1652 let init_resources_fn = quote! {
1653 #init_resources_sig {
1654 let config_filename = #config_file;
1655
1656 #[cfg(target_os = "none")]
1657 ::cu29::prelude::info!("CuApp init: config file {}", config_filename);
1658 #[cfg(target_os = "none")]
1659 ::cu29::prelude::info!("CuApp init: loading config");
1660 #config_load_stmt
1661 #[cfg(target_os = "none")]
1662 ::cu29::prelude::info!("CuApp init: config loaded");
1663 if let Some(runtime) = &config.runtime {
1664 #[cfg(target_os = "none")]
1665 ::cu29::prelude::info!(
1666 "CuApp init: rate_target_hz={}",
1667 runtime.rate_target_hz.unwrap_or(0)
1668 );
1669 } else {
1670 #[cfg(target_os = "none")]
1671 ::cu29::prelude::info!("CuApp init: rate_target_hz=none");
1672 }
1673
1674 #[cfg(target_os = "none")]
1675 ::cu29::prelude::info!("CuApp init: building resources");
1676 let resources = #mission_mod::resources_instanciator(&config)?;
1677 #[cfg(target_os = "none")]
1678 ::cu29::prelude::info!("CuApp init: resources ready");
1679
1680 Ok(AppResources { config, resources })
1681 }
1682 };
1683
1684 let new_with_resources_fn = quote! {
1685 #new_with_resources_sig {
1686 let AppResources { config, resources } = app_resources;
1687
1688 #[cfg(target_os = "none")]
1689 {
1690 let structured_stream = ::cu29::prelude::stream_write::<
1691 ::cu29::prelude::CuLogEntry,
1692 S,
1693 >(
1694 unified_logger.clone(),
1695 ::cu29::prelude::UnifiedLogType::StructuredLogLine,
1696 4096 * 10,
1697 )?;
1698 let _logger_runtime = ::cu29::prelude::LoggerRuntime::init(
1699 clock.clone(),
1700 structured_stream,
1701 None::<::cu29::prelude::NullLog>,
1702 );
1703 }
1704
1705 let mut default_section_size = size_of::<super::#mission_mod::CuList>() * 64;
1708 if let Some(section_size_mib) = config.logging.as_ref().and_then(|l| l.section_size_mib) {
1710 default_section_size = section_size_mib as usize * 1024usize * 1024usize;
1712 }
1713 #[cfg(target_os = "none")]
1714 ::cu29::prelude::info!(
1715 "CuApp new: copperlist section size={}",
1716 default_section_size
1717 );
1718 #[cfg(target_os = "none")]
1719 ::cu29::prelude::info!("CuApp new: creating copperlist stream");
1720 let copperlist_stream = stream_write::<#mission_mod::CuList, S>(
1721 unified_logger.clone(),
1722 UnifiedLogType::CopperList,
1723 default_section_size,
1724 )?;
1728 #[cfg(target_os = "none")]
1729 ::cu29::prelude::info!("CuApp new: copperlist stream ready");
1730
1731 #[cfg(target_os = "none")]
1732 ::cu29::prelude::info!("CuApp new: creating keyframes stream");
1733 let keyframes_stream = stream_write::<KeyFrame, S>(
1734 unified_logger.clone(),
1735 UnifiedLogType::FrozenTasks,
1736 1024 * 1024 * 10, )?;
1738 #[cfg(target_os = "none")]
1739 ::cu29::prelude::info!("CuApp new: keyframes stream ready");
1740
1741 #[cfg(target_os = "none")]
1742 ::cu29::prelude::info!("CuApp new: building runtime");
1743 let copper_runtime = CuRuntime::<#mission_mod::#tasks_type, #mission_mod::CuBridges, #mission_mod::CuStampedDataSet, #monitor_type, #DEFAULT_CLNB>::new_with_resources(
1744 clock,
1745 &config,
1746 Some(#mission),
1747 resources,
1748 #mission_mod::#tasks_instanciator_fn,
1749 #mission_mod::monitor_instanciator,
1750 #mission_mod::bridges_instanciator,
1751 copperlist_stream,
1752 keyframes_stream)?;
1753 #[cfg(target_os = "none")]
1754 ::cu29::prelude::info!("CuApp new: runtime built");
1755
1756 let application = Ok(#application_name { copper_runtime });
1757
1758 #sim_callback_on_new
1759
1760 application
1761 }
1762 };
1763
1764 let app_inherent_impl = quote! {
1765 impl #application_name {
1766 pub fn original_config() -> String {
1767 #copper_config_content.to_string()
1768 }
1769
1770 #init_resources_fn
1771
1772 #new_with_resources_fn
1773 }
1774 };
1775
1776 #[cfg(feature = "std")]
1777 #[cfg(feature = "macro_debug")]
1778 eprintln!("[build result]");
1779 let application_impl = quote! {
1780 #app_impl_decl {
1781 #simstep_type_decl
1782
1783 #new {
1784 let app_resources = #init_resources_call;
1785 #new_with_resources_call
1786 }
1787
1788 fn get_original_config() -> String {
1789 Self::original_config()
1790 }
1791
1792 #run_methods
1793 }
1794 };
1795
1796 let (
1797 builder_struct,
1798 builder_new,
1799 builder_impl,
1800 builder_sim_callback_method,
1801 builder_build_sim_callback_arg,
1802 ) = if sim_mode {
1803 (
1804 quote! {
1805 #[allow(dead_code)]
1806 pub struct #builder_name <'a, F> {
1807 clock: Option<RobotClock>,
1808 unified_logger: Option<Arc<Mutex<UnifiedLoggerWrite>>>,
1809 config_override: Option<CuConfig>,
1810 sim_callback: Option<&'a mut F>
1811 }
1812 },
1813 quote! {
1814 #[allow(dead_code)]
1815 pub fn new() -> Self {
1816 Self {
1817 clock: None,
1818 unified_logger: None,
1819 config_override: None,
1820 sim_callback: None,
1821 }
1822 }
1823 },
1824 quote! {
1825 impl<'a, F> #builder_name <'a, F>
1826 where
1827 F: FnMut(SimStep) -> SimOverride,
1828 },
1829 Some(quote! {
1830 pub fn with_sim_callback(mut self, sim_callback: &'a mut F) -> Self
1831 {
1832 self.sim_callback = Some(sim_callback);
1833 self
1834 }
1835 }),
1836 Some(quote! {
1837 self.sim_callback
1838 .ok_or(CuError::from("Sim callback missing from builder"))?,
1839 }),
1840 )
1841 } else {
1842 (
1843 quote! {
1844 #[allow(dead_code)]
1845 pub struct #builder_name {
1846 clock: Option<RobotClock>,
1847 unified_logger: Option<Arc<Mutex<UnifiedLoggerWrite>>>,
1848 config_override: Option<CuConfig>,
1849 }
1850 },
1851 quote! {
1852 #[allow(dead_code)]
1853 pub fn new() -> Self {
1854 Self {
1855 clock: None,
1856 unified_logger: None,
1857 config_override: None,
1858 }
1859 }
1860 },
1861 quote! {
1862 impl #builder_name
1863 },
1864 None,
1865 None,
1866 )
1867 };
1868
1869 let std_application_impl = if sim_mode {
1871 Some(quote! {
1873 impl #application_name {
1874 pub fn start_all_tasks(&mut self, sim_callback: &mut impl FnMut(SimStep) -> SimOverride) -> CuResult<()> {
1875 <Self as #app_trait<MmapSectionStorage, UnifiedLoggerWrite>>::start_all_tasks(self, sim_callback)
1876 }
1877 pub fn run_one_iteration(&mut self, sim_callback: &mut impl FnMut(SimStep) -> SimOverride) -> CuResult<()> {
1878 <Self as #app_trait<MmapSectionStorage, UnifiedLoggerWrite>>::run_one_iteration(self, sim_callback)
1879 }
1880 pub fn run(&mut self, sim_callback: &mut impl FnMut(SimStep) -> SimOverride) -> CuResult<()> {
1881 <Self as #app_trait<MmapSectionStorage, UnifiedLoggerWrite>>::run(self, sim_callback)
1882 }
1883 pub fn stop_all_tasks(&mut self, sim_callback: &mut impl FnMut(SimStep) -> SimOverride) -> CuResult<()> {
1884 <Self as #app_trait<MmapSectionStorage, UnifiedLoggerWrite>>::stop_all_tasks(self, sim_callback)
1885 }
1886 }
1887 })
1888 } else if std {
1889 Some(quote! {
1891 impl #application_name {
1892 pub fn start_all_tasks(&mut self) -> CuResult<()> {
1893 <Self as #app_trait<MmapSectionStorage, UnifiedLoggerWrite>>::start_all_tasks(self)
1894 }
1895 pub fn run_one_iteration(&mut self) -> CuResult<()> {
1896 <Self as #app_trait<MmapSectionStorage, UnifiedLoggerWrite>>::run_one_iteration(self)
1897 }
1898 pub fn run(&mut self) -> CuResult<()> {
1899 <Self as #app_trait<MmapSectionStorage, UnifiedLoggerWrite>>::run(self)
1900 }
1901 pub fn stop_all_tasks(&mut self) -> CuResult<()> {
1902 <Self as #app_trait<MmapSectionStorage, UnifiedLoggerWrite>>::stop_all_tasks(self)
1903 }
1904 }
1905 })
1906 } else {
1907 None };
1909
1910 let application_builder = if std {
1911 Some(quote! {
1912 #builder_struct
1913
1914 #builder_impl
1915 {
1916 #builder_new
1917
1918 #[allow(dead_code)]
1919 pub fn with_clock(mut self, clock: RobotClock) -> Self {
1920 self.clock = Some(clock);
1921 self
1922 }
1923
1924 #[allow(dead_code)]
1925 pub fn with_unified_logger(mut self, unified_logger: Arc<Mutex<UnifiedLoggerWrite>>) -> Self {
1926 self.unified_logger = Some(unified_logger);
1927 self
1928 }
1929
1930 #[allow(dead_code)]
1931 pub fn with_context(mut self, copper_ctx: &CopperContext) -> Self {
1932 self.clock = Some(copper_ctx.clock.clone());
1933 self.unified_logger = Some(copper_ctx.unified_logger.clone());
1934 self
1935 }
1936
1937 #[allow(dead_code)]
1938 pub fn with_config(mut self, config_override: CuConfig) -> Self {
1939 self.config_override = Some(config_override);
1940 self
1941 }
1942
1943 #builder_sim_callback_method
1944
1945 #[allow(dead_code)]
1946 pub fn build(self) -> CuResult<#application_name> {
1947 #application_name::new(
1948 self.clock
1949 .ok_or(CuError::from("Clock missing from builder"))?,
1950 self.unified_logger
1951 .ok_or(CuError::from("Unified logger missing from builder"))?,
1952 self.config_override,
1953 #builder_build_sim_callback_arg
1954 )
1955 }
1956 }
1957 })
1958 } else {
1959 None
1961 };
1962
1963 let sim_imports = if sim_mode {
1964 Some(quote! {
1965 use cu29::simulation::SimOverride;
1966 use cu29::simulation::CuTaskCallbackState;
1967 use cu29::simulation::CuSimSrcTask;
1968 use cu29::simulation::CuSimSinkTask;
1969 use cu29::prelude::app::CuSimApplication;
1970 })
1971 } else {
1972 None
1973 };
1974
1975 let sim_tasks = if sim_mode {
1976 Some(quote! {
1977 pub type CuSimTasks = #task_types_tuple_sim;
1980 })
1981 } else {
1982 None
1983 };
1984
1985 let sim_inst_body = if task_sim_instances_init_code.is_empty() {
1986 quote! {
1987 let _ = resources;
1988 Ok(())
1989 }
1990 } else {
1991 quote! { Ok(( #(#task_sim_instances_init_code),*, )) }
1992 };
1993
1994 let sim_tasks_instanciator = if sim_mode {
1995 Some(quote! {
1996 pub fn tasks_instanciator_sim(
1997 all_instances_configs: Vec<Option<&ComponentConfig>>,
1998 resources: &mut ResourceManager,
1999 ) -> CuResult<CuSimTasks> {
2000 #sim_inst_body
2001 }})
2002 } else {
2003 None
2004 };
2005
2006 let tasks_inst_body_std = if task_instances_init_code.is_empty() {
2007 quote! {
2008 let _ = resources;
2009 Ok(())
2010 }
2011 } else {
2012 quote! { Ok(( #(#task_instances_init_code),*, )) }
2013 };
2014
2015 let tasks_inst_body_nostd = if task_instances_init_code.is_empty() {
2016 quote! {
2017 let _ = resources;
2018 Ok(())
2019 }
2020 } else {
2021 quote! { Ok(( #(#task_instances_init_code),*, )) }
2022 };
2023
2024 let tasks_instanciator = if std {
2025 quote! {
2026 pub fn tasks_instanciator<'c>(
2027 all_instances_configs: Vec<Option<&'c ComponentConfig>>,
2028 resources: &mut ResourceManager,
2029 ) -> CuResult<CuTasks> {
2030 #tasks_inst_body_std
2031 }
2032 }
2033 } else {
2034 quote! {
2036 pub fn tasks_instanciator<'c>(
2037 all_instances_configs: Vec<Option<&'c ComponentConfig>>,
2038 resources: &mut ResourceManager,
2039 ) -> CuResult<CuTasks> {
2040 #tasks_inst_body_nostd
2041 }
2042 }
2043 };
2044
2045 let imports = if std {
2046 quote! {
2047 use cu29::rayon::ThreadPool;
2048 use cu29::cuasynctask::CuAsyncTask;
2049 use cu29::curuntime::CopperContext;
2050 use cu29::resource::{ResourceBindings, ResourceManager};
2051 use cu29::prelude::SectionStorage;
2052 use cu29::prelude::UnifiedLoggerWrite;
2053 use cu29::prelude::memmap::MmapSectionStorage;
2054 use std::fmt::{Debug, Formatter};
2055 use std::fmt::Result as FmtResult;
2056 use std::mem::size_of;
2057 use std::sync::Arc;
2058 use std::sync::atomic::{AtomicBool, Ordering};
2059 use std::sync::Mutex;
2060 }
2061 } else {
2062 quote! {
2063 use alloc::sync::Arc;
2064 use alloc::string::String;
2065 use alloc::string::ToString;
2066 use core::sync::atomic::{AtomicBool, Ordering};
2067 use core::fmt::{Debug, Formatter};
2068 use core::fmt::Result as FmtResult;
2069 use core::mem::size_of;
2070 use spin::Mutex;
2071 use cu29::prelude::SectionStorage;
2072 use cu29::resource::{ResourceBindings, ResourceManager};
2073 }
2074 };
2075
2076 let task_mapping_defs = task_resource_mappings.defs.clone();
2077 let bridge_mapping_defs = bridge_resource_mappings.defs.clone();
2078
2079 let mission_mod_tokens = quote! {
2081 mod #mission_mod {
2082 use super::*; use cu29::bincode::Encode;
2085 use cu29::bincode::enc::Encoder;
2086 use cu29::bincode::error::EncodeError;
2087 use cu29::bincode::Decode;
2088 use cu29::bincode::de::Decoder;
2089 use cu29::bincode::de::DecoderImpl;
2090 use cu29::bincode::error::DecodeError;
2091 use cu29::clock::RobotClock;
2092 use cu29::config::CuConfig;
2093 use cu29::config::ComponentConfig;
2094 use cu29::curuntime::CuRuntime;
2095 use cu29::curuntime::KeyFrame;
2096 use cu29::CuResult;
2097 use cu29::CuError;
2098 use cu29::cutask::CuSrcTask;
2099 use cu29::cutask::CuSinkTask;
2100 use cu29::cutask::CuTask;
2101 use cu29::cutask::CuMsg;
2102 use cu29::cutask::CuMsgMetadata;
2103 use cu29::copperlist::CopperList;
2104 use cu29::monitoring::CuMonitor; use cu29::monitoring::CuTaskState;
2106 use cu29::monitoring::Decision;
2107 use cu29::prelude::app::CuApplication;
2108 use cu29::prelude::debug;
2109 use cu29::prelude::stream_write;
2110 use cu29::prelude::UnifiedLogType;
2111 use cu29::prelude::UnifiedLogWrite;
2112
2113 #imports
2114
2115 #sim_imports
2116
2117 #[allow(unused_imports)]
2119 use cu29::monitoring::NoMonitor;
2120
2121 pub type CuTasks = #task_types_tuple;
2125 pub type CuBridges = #bridges_type_tokens;
2126 #resources_module
2127 #resources_instanciator_fn
2128 #task_mapping_defs
2129 #bridge_mapping_defs
2130
2131 #sim_tasks
2132 #sim_support
2133 #sim_tasks_instanciator
2134
2135 pub const TASKS_IDS: &'static [&'static str] = &[#( #ids ),*];
2136
2137 #culist_support
2138 #tasks_instanciator
2139 #bridges_instanciator
2140
2141 pub fn monitor_instanciator(config: &CuConfig) -> #monitor_type {
2142 #monitor_type::new(config, #mission_mod::TASKS_IDS).expect("Failed to create the given monitor.")
2143 }
2144
2145 #app_resources_struct
2147 pub #application_struct
2148
2149 #app_inherent_impl
2150 #application_impl
2151
2152 #std_application_impl
2153
2154 #application_builder
2155 }
2156
2157 };
2158 all_missions_tokens.push(mission_mod_tokens);
2159 }
2160
2161 let default_application_tokens = if all_missions.contains_key("default") {
2162 let default_builder = if std {
2163 Some(quote! {
2164 #[allow(unused_imports)]
2166 use default::#builder_name;
2167 })
2168 } else {
2169 None
2170 };
2171 quote! {
2172 #default_builder
2173
2174 #[allow(unused_imports)]
2175 use default::AppResources;
2176
2177 #[allow(unused_imports)]
2178 use default::resources as app_resources;
2179
2180 #[allow(unused_imports)]
2181 use default::#application_name;
2182 }
2183 } else {
2184 quote!() };
2186
2187 let result: proc_macro2::TokenStream = quote! {
2188 #(#all_missions_tokens)*
2189 #default_application_tokens
2190 };
2191
2192 #[cfg(feature = "macro_debug")]
2194 {
2195 let formatted_code = rustfmt_generated_code(result.to_string());
2196 eprintln!("\n === Gen. Runtime ===\n");
2197 eprintln!("{formatted_code}");
2198 eprintln!("\n === === === === === ===\n");
2201 }
2202 result.into()
2203}
2204
2205fn read_config(config_file: &str) -> CuResult<CuConfig> {
2206 let filename = config_full_path(config_file);
2207
2208 read_configuration(filename.as_str())
2209}
2210
2211fn config_full_path(config_file: &str) -> String {
2212 let mut config_full_path = utils::caller_crate_root();
2213 config_full_path.push(config_file);
2214 let filename = config_full_path
2215 .as_os_str()
2216 .to_str()
2217 .expect("Could not interpret the config file name");
2218 filename.to_string()
2219}
2220
2221fn extract_tasks_output_types(graph: &CuGraph) -> Vec<Option<Type>> {
2222 graph
2223 .get_all_nodes()
2224 .iter()
2225 .map(|(_, node)| {
2226 let id = node.get_id();
2227 let type_str = graph.get_node_output_msg_type(id.as_str());
2228 type_str.map(|type_str| {
2229 parse_str::<Type>(type_str.as_str()).expect("Could not parse output message type.")
2230 })
2231 })
2232 .collect()
2233}
2234
2235struct CuTaskSpecSet {
2236 pub ids: Vec<String>,
2237 pub cutypes: Vec<CuTaskType>,
2238 pub background_flags: Vec<bool>,
2239 pub logging_enabled: Vec<bool>,
2240 pub type_names: Vec<String>,
2241 pub task_types: Vec<Type>,
2242 pub instantiation_types: Vec<Type>,
2243 pub sim_task_types: Vec<Type>,
2244 pub run_in_sim_flags: Vec<bool>,
2245 #[allow(dead_code)]
2246 pub output_types: Vec<Option<Type>>,
2247 pub node_id_to_task_index: Vec<Option<usize>>,
2248}
2249
2250impl CuTaskSpecSet {
2251 pub fn from_graph(graph: &CuGraph) -> Self {
2252 let all_id_nodes: Vec<(NodeId, &Node)> = graph
2253 .get_all_nodes()
2254 .into_iter()
2255 .filter(|(_, node)| node.get_flavor() == Flavor::Task)
2256 .collect();
2257
2258 let ids = all_id_nodes
2259 .iter()
2260 .map(|(_, node)| node.get_id().to_string())
2261 .collect();
2262
2263 let cutypes = all_id_nodes
2264 .iter()
2265 .map(|(id, _)| find_task_type_for_id(graph, *id))
2266 .collect();
2267
2268 let background_flags: Vec<bool> = all_id_nodes
2269 .iter()
2270 .map(|(_, node)| node.is_background())
2271 .collect();
2272
2273 let logging_enabled: Vec<bool> = all_id_nodes
2274 .iter()
2275 .map(|(_, node)| node.is_logging_enabled())
2276 .collect();
2277
2278 let type_names: Vec<String> = all_id_nodes
2279 .iter()
2280 .map(|(_, node)| node.get_type().to_string())
2281 .collect();
2282
2283 let output_types = extract_tasks_output_types(graph);
2284
2285 let task_types = type_names
2286 .iter()
2287 .zip(background_flags.iter())
2288 .zip(output_types.iter())
2289 .map(|((name, &background), output_type)| {
2290 let name_type = parse_str::<Type>(name).unwrap_or_else(|error| {
2291 panic!("Could not transform {name} into a Task Rust type: {error}");
2292 });
2293 if background {
2294 if let Some(output_type) = output_type {
2295 parse_quote!(CuAsyncTask<#name_type, #output_type>)
2296 } else {
2297 panic!("{name}: If a task is background, it has to have an output");
2298 }
2299 } else {
2300 name_type
2301 }
2302 })
2303 .collect();
2304
2305 let instantiation_types = type_names
2306 .iter()
2307 .zip(background_flags.iter())
2308 .zip(output_types.iter())
2309 .map(|((name, &background), output_type)| {
2310 let name_type = parse_str::<Type>(name).unwrap_or_else(|error| {
2311 panic!("Could not transform {name} into a Task Rust type: {error}");
2312 });
2313 if background {
2314 if let Some(output_type) = output_type {
2315 parse_quote!(CuAsyncTask::<#name_type, #output_type>)
2316 } else {
2317 panic!("{name}: If a task is background, it has to have an output");
2318 }
2319 } else {
2320 name_type
2321 }
2322 })
2323 .collect();
2324
2325 let sim_task_types = type_names
2326 .iter()
2327 .map(|name| {
2328 parse_str::<Type>(name).unwrap_or_else(|err| {
2329 eprintln!("Could not transform {name} into a Task Rust type.");
2330 panic!("{err}")
2331 })
2332 })
2333 .collect();
2334
2335 let run_in_sim_flags = all_id_nodes
2336 .iter()
2337 .map(|(_, node)| node.is_run_in_sim())
2338 .collect();
2339
2340 let mut node_id_to_task_index = vec![None; graph.node_count()];
2341 for (index, (node_id, _)) in all_id_nodes.iter().enumerate() {
2342 node_id_to_task_index[*node_id as usize] = Some(index);
2343 }
2344
2345 Self {
2346 ids,
2347 cutypes,
2348 background_flags,
2349 logging_enabled,
2350 type_names,
2351 task_types,
2352 instantiation_types,
2353 sim_task_types,
2354 run_in_sim_flags,
2355 output_types,
2356 node_id_to_task_index,
2357 }
2358 }
2359}
2360
2361fn extract_msg_types(runtime_plan: &CuExecutionLoop) -> Vec<Type> {
2362 runtime_plan
2363 .steps
2364 .iter()
2365 .filter_map(|unit| match unit {
2366 CuExecutionUnit::Step(step) => {
2367 if let Some((_, output_msg_type)) = &step.output_msg_index_type {
2368 Some(
2369 parse_str::<Type>(output_msg_type.as_str()).unwrap_or_else(|_| {
2370 panic!(
2371 "Could not transform {output_msg_type} into a message Rust type."
2372 )
2373 }),
2374 )
2375 } else {
2376 None
2377 }
2378 }
2379 CuExecutionUnit::Loop(_) => todo!("Needs to be implemented"),
2380 })
2381 .collect()
2382}
2383
2384fn build_culist_tuple(all_msgs_types_in_culist_order: &[Type]) -> TypeTuple {
2386 if all_msgs_types_in_culist_order.is_empty() {
2387 parse_quote! { () }
2388 } else {
2389 parse_quote! {
2390 ( #( CuMsg<#all_msgs_types_in_culist_order> ),* )
2391 }
2392 }
2393}
2394
2395fn build_culist_tuple_encode(all_msgs_types_in_culist_order: &[Type]) -> ItemImpl {
2397 let indices: Vec<usize> = (0..all_msgs_types_in_culist_order.len()).collect();
2398
2399 let encode_fields: Vec<_> = indices
2401 .iter()
2402 .map(|i| {
2403 let idx = syn::Index::from(*i);
2404 quote! { self.0.#idx.encode(encoder)?; }
2405 })
2406 .collect();
2407
2408 parse_quote! {
2409 impl Encode for CuStampedDataSet {
2410 fn encode<E: Encoder>(&self, encoder: &mut E) -> Result<(), EncodeError> {
2411 #(#encode_fields)*
2412 Ok(())
2413 }
2414 }
2415 }
2416}
2417
2418fn build_culist_tuple_decode(all_msgs_types_in_culist_order: &[Type]) -> ItemImpl {
2420 let indices: Vec<usize> = (0..all_msgs_types_in_culist_order.len()).collect();
2421
2422 let decode_fields: Vec<_> = indices
2424 .iter()
2425 .map(|i| {
2426 let t = &all_msgs_types_in_culist_order[*i];
2427 quote! { CuMsg::<#t>::decode(decoder)? }
2428 })
2429 .collect();
2430
2431 parse_quote! {
2432 impl Decode<()> for CuStampedDataSet {
2433 fn decode<D: Decoder<Context=()>>(decoder: &mut D) -> Result<Self, DecodeError> {
2434 Ok(CuStampedDataSet ((
2435 #(#decode_fields),*
2436 )))
2437 }
2438 }
2439 }
2440}
2441
2442fn build_culist_erasedcumsgs(all_msgs_types_in_culist_order: &[Type]) -> ItemImpl {
2443 let indices: Vec<usize> = (0..all_msgs_types_in_culist_order.len()).collect();
2444 let casted_fields: Vec<_> = indices
2445 .iter()
2446 .map(|i| {
2447 let idx = syn::Index::from(*i);
2448 quote! { &self.0.#idx as &dyn ErasedCuStampedData }
2449 })
2450 .collect();
2451 parse_quote! {
2452 impl ErasedCuStampedDataSet for CuStampedDataSet {
2453 fn cumsgs(&self) -> Vec<&dyn ErasedCuStampedData> {
2454 vec![
2455 #(#casted_fields),*
2456 ]
2457 }
2458 }
2459 }
2460}
2461
2462fn build_culist_tuple_debug(all_msgs_types_in_culist_order: &[Type]) -> ItemImpl {
2463 let indices: Vec<usize> = (0..all_msgs_types_in_culist_order.len()).collect();
2464
2465 let debug_fields: Vec<_> = indices
2466 .iter()
2467 .map(|i| {
2468 let idx = syn::Index::from(*i);
2469 quote! { .field(&self.0.#idx) }
2470 })
2471 .collect();
2472
2473 parse_quote! {
2474 impl Debug for CuStampedDataSet {
2475 fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
2476 f.debug_tuple("CuStampedDataSet")
2477 #(#debug_fields)*
2478 .finish()
2479 }
2480 }
2481 }
2482}
2483
2484fn build_culist_tuple_serialize(all_msgs_types_in_culist_order: &[Type]) -> ItemImpl {
2486 let indices: Vec<usize> = (0..all_msgs_types_in_culist_order.len()).collect();
2487 let tuple_len = all_msgs_types_in_culist_order.len();
2488
2489 let serialize_fields: Vec<_> = indices
2491 .iter()
2492 .map(|i| {
2493 let idx = syn::Index::from(*i);
2494 quote! { &self.0.#idx }
2495 })
2496 .collect();
2497
2498 parse_quote! {
2499 impl Serialize for CuStampedDataSet {
2500 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
2501 where
2502 S: serde::Serializer,
2503 {
2504 use serde::ser::SerializeTuple;
2505 let mut tuple = serializer.serialize_tuple(#tuple_len)?;
2506 #(tuple.serialize_element(#serialize_fields)?;)*
2507 tuple.end()
2508 }
2509 }
2510 }
2511}
2512
2513fn build_culist_tuple_default(all_msgs_types_in_culist_order: &[Type]) -> ItemImpl {
2515 let default_fields: Vec<_> = all_msgs_types_in_culist_order
2517 .iter()
2518 .map(|msg_type| quote! { CuStampedData::<#msg_type, CuMsgMetadata>::default() })
2519 .collect();
2520
2521 parse_quote! {
2522 impl Default for CuStampedDataSet {
2523 fn default() -> CuStampedDataSet
2524 {
2525 CuStampedDataSet((
2526 #(#default_fields),*
2527 ))
2528 }
2529 }
2530 }
2531}
2532
2533fn collect_bridge_channel_usage(graph: &CuGraph) -> HashMap<BridgeChannelKey, String> {
2534 let mut usage = HashMap::new();
2535 for cnx in graph.edges() {
2536 if let Some(channel) = &cnx.src_channel {
2537 let key = BridgeChannelKey {
2538 bridge_id: cnx.src.clone(),
2539 channel_id: channel.clone(),
2540 direction: BridgeChannelDirection::Rx,
2541 };
2542 usage
2543 .entry(key)
2544 .and_modify(|msg| {
2545 if msg != &cnx.msg {
2546 panic!(
2547 "Bridge '{}' channel '{}' is used with incompatible message types: {} vs {}",
2548 cnx.src, channel, msg, cnx.msg
2549 );
2550 }
2551 })
2552 .or_insert(cnx.msg.clone());
2553 }
2554 if let Some(channel) = &cnx.dst_channel {
2555 let key = BridgeChannelKey {
2556 bridge_id: cnx.dst.clone(),
2557 channel_id: channel.clone(),
2558 direction: BridgeChannelDirection::Tx,
2559 };
2560 usage
2561 .entry(key)
2562 .and_modify(|msg| {
2563 if msg != &cnx.msg {
2564 panic!(
2565 "Bridge '{}' channel '{}' is used with incompatible message types: {} vs {}",
2566 cnx.dst, channel, msg, cnx.msg
2567 );
2568 }
2569 })
2570 .or_insert(cnx.msg.clone());
2571 }
2572 }
2573 usage
2574}
2575
2576fn build_bridge_specs(
2577 config: &CuConfig,
2578 graph: &CuGraph,
2579 channel_usage: &HashMap<BridgeChannelKey, String>,
2580) -> Vec<BridgeSpec> {
2581 let mut specs = Vec::new();
2582 for (bridge_index, bridge_cfg) in config.bridges.iter().enumerate() {
2583 if graph.get_node_id_by_name(bridge_cfg.id.as_str()).is_none() {
2584 continue;
2585 }
2586
2587 let type_path = parse_str::<Type>(bridge_cfg.type_.as_str()).unwrap_or_else(|err| {
2588 panic!(
2589 "Could not parse bridge type '{}' for '{}': {err}",
2590 bridge_cfg.type_, bridge_cfg.id
2591 )
2592 });
2593
2594 let mut rx_channels = Vec::new();
2595 let mut tx_channels = Vec::new();
2596
2597 for (channel_index, channel) in bridge_cfg.channels.iter().enumerate() {
2598 match channel {
2599 BridgeChannelConfigRepresentation::Rx { id, .. } => {
2600 let key = BridgeChannelKey {
2601 bridge_id: bridge_cfg.id.clone(),
2602 channel_id: id.clone(),
2603 direction: BridgeChannelDirection::Rx,
2604 };
2605 if let Some(msg_type) = channel_usage.get(&key) {
2606 let msg_type = parse_str::<Type>(msg_type).unwrap_or_else(|err| {
2607 panic!(
2608 "Could not parse message type '{msg_type}' for bridge '{}' channel '{}': {err}",
2609 bridge_cfg.id, id
2610 )
2611 });
2612 let const_ident =
2613 Ident::new(&config_id_to_bridge_const(id.as_str()), Span::call_site());
2614 rx_channels.push(BridgeChannelSpec {
2615 id: id.clone(),
2616 const_ident,
2617 msg_type,
2618 config_index: channel_index,
2619 plan_node_id: None,
2620 culist_index: None,
2621 monitor_index: None,
2622 });
2623 }
2624 }
2625 BridgeChannelConfigRepresentation::Tx { id, .. } => {
2626 let key = BridgeChannelKey {
2627 bridge_id: bridge_cfg.id.clone(),
2628 channel_id: id.clone(),
2629 direction: BridgeChannelDirection::Tx,
2630 };
2631 if let Some(msg_type) = channel_usage.get(&key) {
2632 let msg_type = parse_str::<Type>(msg_type).unwrap_or_else(|err| {
2633 panic!(
2634 "Could not parse message type '{msg_type}' for bridge '{}' channel '{}': {err}",
2635 bridge_cfg.id, id
2636 )
2637 });
2638 let const_ident =
2639 Ident::new(&config_id_to_bridge_const(id.as_str()), Span::call_site());
2640 tx_channels.push(BridgeChannelSpec {
2641 id: id.clone(),
2642 const_ident,
2643 msg_type,
2644 config_index: channel_index,
2645 plan_node_id: None,
2646 culist_index: None,
2647 monitor_index: None,
2648 });
2649 }
2650 }
2651 }
2652 }
2653
2654 if rx_channels.is_empty() && tx_channels.is_empty() {
2655 continue;
2656 }
2657
2658 specs.push(BridgeSpec {
2659 id: bridge_cfg.id.clone(),
2660 type_path,
2661 config_index: bridge_index,
2662 tuple_index: 0,
2663 monitor_index: None,
2664 rx_channels,
2665 tx_channels,
2666 });
2667 }
2668
2669 for (tuple_index, spec) in specs.iter_mut().enumerate() {
2670 spec.tuple_index = tuple_index;
2671 }
2672
2673 specs
2674}
2675
2676fn collect_task_member_names(graph: &CuGraph) -> Vec<(NodeId, String)> {
2677 graph
2678 .get_all_nodes()
2679 .iter()
2680 .filter(|(_, node)| node.get_flavor() == Flavor::Task)
2681 .map(|(node_id, node)| (*node_id, config_id_to_struct_member(node.get_id().as_str())))
2682 .collect()
2683}
2684
2685#[derive(Clone, Copy)]
2686enum ResourceOwner {
2687 Task(usize),
2688 Bridge(usize),
2689}
2690
2691#[derive(Clone)]
2692struct ResourceKeySpec {
2693 bundle_index: usize,
2694 provider_path: syn::Path,
2695 resource_name: String,
2696 binding_name: String,
2697 owner: ResourceOwner,
2698}
2699
2700fn parse_resource_path(path: &str) -> CuResult<(String, String)> {
2701 let (bundle_id, name) = path.split_once('.').ok_or_else(|| {
2702 CuError::from(format!(
2703 "Resource '{path}' is missing a bundle prefix (expected bundle.resource)"
2704 ))
2705 })?;
2706
2707 if bundle_id.is_empty() || name.is_empty() {
2708 return Err(CuError::from(format!(
2709 "Resource '{path}' must use the 'bundle.resource' format"
2710 )));
2711 }
2712
2713 Ok((bundle_id.to_string(), name.to_string()))
2714}
2715
2716fn collect_resource_specs(
2717 graph: &CuGraph,
2718 task_specs: &CuTaskSpecSet,
2719 bridge_specs: &[BridgeSpec],
2720 bundle_specs: &[BundleSpec],
2721) -> CuResult<Vec<ResourceKeySpec>> {
2722 let mut bridge_lookup: BTreeMap<String, usize> = BTreeMap::new();
2723 for (idx, spec) in bridge_specs.iter().enumerate() {
2724 bridge_lookup.insert(spec.id.clone(), idx);
2725 }
2726
2727 let mut bundle_lookup: HashMap<String, (usize, syn::Path)> = HashMap::new();
2728 for (index, bundle) in bundle_specs.iter().enumerate() {
2729 bundle_lookup.insert(bundle.id.clone(), (index, bundle.provider_path.clone()));
2730 }
2731
2732 let mut specs = Vec::new();
2733
2734 for (node_id, node) in graph.get_all_nodes() {
2735 let resources = node.get_resources();
2736 if let Some(resources) = resources {
2737 let task_index = task_specs.node_id_to_task_index[node_id as usize];
2738 let owner = if let Some(task_index) = task_index {
2739 ResourceOwner::Task(task_index)
2740 } else if node.get_flavor() == Flavor::Bridge {
2741 let bridge_index = bridge_lookup.get(&node.get_id()).ok_or_else(|| {
2742 CuError::from(format!(
2743 "Resource mapping attached to unknown bridge node '{}'",
2744 node.get_id()
2745 ))
2746 })?;
2747 ResourceOwner::Bridge(*bridge_index)
2748 } else {
2749 return Err(CuError::from(format!(
2750 "Resource mapping attached to non-task node '{}'",
2751 node.get_id()
2752 )));
2753 };
2754
2755 for (binding_name, path) in resources {
2756 let (bundle_id, resource_name) = parse_resource_path(path)?;
2757 let (bundle_index, provider_path) =
2758 bundle_lookup.get(&bundle_id).ok_or_else(|| {
2759 CuError::from(format!(
2760 "Resource '{}' references unknown bundle '{}'",
2761 path, bundle_id
2762 ))
2763 })?;
2764 specs.push(ResourceKeySpec {
2765 bundle_index: *bundle_index,
2766 provider_path: provider_path.clone(),
2767 resource_name,
2768 binding_name: binding_name.clone(),
2769 owner,
2770 });
2771 }
2772 }
2773 }
2774
2775 Ok(specs)
2776}
2777
2778fn build_bundle_list<'a>(config: &'a CuConfig, mission: &str) -> Vec<&'a ResourceBundleConfig> {
2779 config
2780 .resources
2781 .iter()
2782 .filter(|bundle| {
2783 bundle
2784 .missions
2785 .as_ref()
2786 .is_none_or(|missions| missions.iter().any(|m| m == mission))
2787 })
2788 .collect()
2789}
2790
2791struct BundleSpec {
2792 id: String,
2793 provider_path: syn::Path,
2794}
2795
2796fn build_bundle_specs(config: &CuConfig, mission: &str) -> CuResult<Vec<BundleSpec>> {
2797 build_bundle_list(config, mission)
2798 .into_iter()
2799 .map(|bundle| {
2800 let provider_path: syn::Path =
2801 syn::parse_str(bundle.provider.as_str()).map_err(|err| {
2802 CuError::from(format!(
2803 "Failed to parse provider path '{}' for bundle '{}': {err}",
2804 bundle.provider, bundle.id
2805 ))
2806 })?;
2807 Ok(BundleSpec {
2808 id: bundle.id.clone(),
2809 provider_path,
2810 })
2811 })
2812 .collect()
2813}
2814
2815fn build_resources_module(
2816 bundle_specs: &[BundleSpec],
2817) -> CuResult<(proc_macro2::TokenStream, proc_macro2::TokenStream)> {
2818 let bundle_consts = bundle_specs.iter().enumerate().map(|(index, bundle)| {
2819 let const_ident = Ident::new(
2820 &config_id_to_bridge_const(bundle.id.as_str()),
2821 Span::call_site(),
2822 );
2823 quote! { pub const #const_ident: BundleIndex = BundleIndex::new(#index); }
2824 });
2825
2826 let resources_module = quote! {
2827 pub mod resources {
2828 #![allow(dead_code)]
2829 use cu29::resource::BundleIndex;
2830
2831 pub mod bundles {
2832 use super::BundleIndex;
2833 #(#bundle_consts)*
2834 }
2835 }
2836 };
2837
2838 let bundle_counts = bundle_specs.iter().map(|bundle| {
2839 let provider_path = &bundle.provider_path;
2840 quote! { <#provider_path as cu29::resource::ResourceBundleDecl>::Id::COUNT }
2841 });
2842
2843 let bundle_inits = bundle_specs
2844 .iter()
2845 .enumerate()
2846 .map(|(index, bundle)| {
2847 let bundle_id = LitStr::new(bundle.id.as_str(), Span::call_site());
2848 let provider_path = &bundle.provider_path;
2849 quote! {
2850 let bundle_cfg = config
2851 .resources
2852 .iter()
2853 .find(|b| b.id == #bundle_id)
2854 .unwrap_or_else(|| panic!("Resource bundle '{}' missing from configuration", #bundle_id));
2855 let bundle_ctx = cu29::resource::BundleContext::<#provider_path>::new(
2856 cu29::resource::BundleIndex::new(#index),
2857 #bundle_id,
2858 );
2859 <#provider_path as cu29::resource::ResourceBundle>::build(
2860 bundle_ctx,
2861 bundle_cfg.config.as_ref(),
2862 &mut manager,
2863 )?;
2864 }
2865 })
2866 .collect::<Vec<_>>();
2867
2868 let resources_instanciator = quote! {
2869 pub fn resources_instanciator(config: &CuConfig) -> CuResult<cu29::resource::ResourceManager> {
2870 let bundle_counts: &[usize] = &[ #(#bundle_counts),* ];
2871 let mut manager = cu29::resource::ResourceManager::new(bundle_counts);
2872 #(#bundle_inits)*
2873 Ok(manager)
2874 }
2875 };
2876
2877 Ok((resources_module, resources_instanciator))
2878}
2879
2880struct ResourceMappingTokens {
2881 defs: proc_macro2::TokenStream,
2882 refs: Vec<proc_macro2::TokenStream>,
2883}
2884
2885fn build_task_resource_mappings(
2886 resource_specs: &[ResourceKeySpec],
2887 task_specs: &CuTaskSpecSet,
2888) -> CuResult<ResourceMappingTokens> {
2889 let mut per_task: Vec<Vec<&ResourceKeySpec>> = vec![Vec::new(); task_specs.ids.len()];
2890
2891 for spec in resource_specs {
2892 let ResourceOwner::Task(task_index) = spec.owner else {
2893 continue;
2894 };
2895 per_task
2896 .get_mut(task_index)
2897 .ok_or_else(|| {
2898 CuError::from(format!(
2899 "Resource '{}' mapped to invalid task index {}",
2900 spec.binding_name, task_index
2901 ))
2902 })?
2903 .push(spec);
2904 }
2905
2906 let mut mapping_defs = Vec::new();
2907 let mut mapping_refs = Vec::new();
2908
2909 for (idx, entries) in per_task.iter().enumerate() {
2910 if entries.is_empty() {
2911 mapping_refs.push(quote! { None });
2912 continue;
2913 }
2914
2915 let binding_task_type = if task_specs.background_flags[idx] {
2916 &task_specs.sim_task_types[idx]
2917 } else {
2918 &task_specs.task_types[idx]
2919 };
2920
2921 let binding_trait = match task_specs.cutypes[idx] {
2922 CuTaskType::Source => quote! { CuSrcTask },
2923 CuTaskType::Regular => quote! { CuTask },
2924 CuTaskType::Sink => quote! { CuSinkTask },
2925 };
2926
2927 let entries_ident = format_ident!("TASK{}_RES_ENTRIES", idx);
2928 let map_ident = format_ident!("TASK{}_RES_MAPPING", idx);
2929 let binding_type = quote! {
2930 <<#binding_task_type as #binding_trait>::Resources<'_> as ResourceBindings>::Binding
2931 };
2932 let entry_tokens = entries.iter().map(|spec| {
2933 let binding_ident =
2934 Ident::new(&config_id_to_enum(spec.binding_name.as_str()), Span::call_site());
2935 let resource_ident =
2936 Ident::new(&config_id_to_enum(spec.resource_name.as_str()), Span::call_site());
2937 let bundle_index = spec.bundle_index;
2938 let provider_path = &spec.provider_path;
2939 quote! {
2940 (#binding_type::#binding_ident, cu29::resource::ResourceKey::new(
2941 cu29::resource::BundleIndex::new(#bundle_index),
2942 <#provider_path as cu29::resource::ResourceBundleDecl>::Id::#resource_ident as usize,
2943 ))
2944 }
2945 });
2946
2947 mapping_defs.push(quote! {
2948 const #entries_ident: &[(#binding_type, cu29::resource::ResourceKey)] = &[ #(#entry_tokens),* ];
2949 const #map_ident: cu29::resource::ResourceBindingMap<#binding_type> =
2950 cu29::resource::ResourceBindingMap::new(#entries_ident);
2951 });
2952 mapping_refs.push(quote! { Some(&#map_ident) });
2953 }
2954
2955 Ok(ResourceMappingTokens {
2956 defs: quote! { #(#mapping_defs)* },
2957 refs: mapping_refs,
2958 })
2959}
2960
2961fn build_bridge_resource_mappings(
2962 resource_specs: &[ResourceKeySpec],
2963 bridge_specs: &[BridgeSpec],
2964) -> ResourceMappingTokens {
2965 let mut per_bridge: Vec<Vec<&ResourceKeySpec>> = vec![Vec::new(); bridge_specs.len()];
2966
2967 for spec in resource_specs {
2968 let ResourceOwner::Bridge(bridge_index) = spec.owner else {
2969 continue;
2970 };
2971 per_bridge[bridge_index].push(spec);
2972 }
2973
2974 let mut mapping_defs = Vec::new();
2975 let mut mapping_refs = Vec::new();
2976
2977 for (idx, entries) in per_bridge.iter().enumerate() {
2978 if entries.is_empty() {
2979 mapping_refs.push(quote! { None });
2980 continue;
2981 }
2982
2983 let bridge_type = &bridge_specs[idx].type_path;
2984 let binding_type = quote! {
2985 <<#bridge_type as cu29::cubridge::CuBridge>::Resources<'_> as ResourceBindings>::Binding
2986 };
2987 let entries_ident = format_ident!("BRIDGE{}_RES_ENTRIES", idx);
2988 let map_ident = format_ident!("BRIDGE{}_RES_MAPPING", idx);
2989 let entry_tokens = entries.iter().map(|spec| {
2990 let binding_ident =
2991 Ident::new(&config_id_to_enum(spec.binding_name.as_str()), Span::call_site());
2992 let resource_ident =
2993 Ident::new(&config_id_to_enum(spec.resource_name.as_str()), Span::call_site());
2994 let bundle_index = spec.bundle_index;
2995 let provider_path = &spec.provider_path;
2996 quote! {
2997 (#binding_type::#binding_ident, cu29::resource::ResourceKey::new(
2998 cu29::resource::BundleIndex::new(#bundle_index),
2999 <#provider_path as cu29::resource::ResourceBundleDecl>::Id::#resource_ident as usize,
3000 ))
3001 }
3002 });
3003
3004 mapping_defs.push(quote! {
3005 const #entries_ident: &[(#binding_type, cu29::resource::ResourceKey)] = &[ #(#entry_tokens),* ];
3006 const #map_ident: cu29::resource::ResourceBindingMap<#binding_type> =
3007 cu29::resource::ResourceBindingMap::new(#entries_ident);
3008 });
3009 mapping_refs.push(quote! { Some(&#map_ident) });
3010 }
3011
3012 ResourceMappingTokens {
3013 defs: quote! { #(#mapping_defs)* },
3014 refs: mapping_refs,
3015 }
3016}
3017
3018fn build_execution_plan(
3019 graph: &CuGraph,
3020 task_specs: &CuTaskSpecSet,
3021 bridge_specs: &mut [BridgeSpec],
3022) -> CuResult<(
3023 CuExecutionLoop,
3024 Vec<ExecutionEntity>,
3025 HashMap<NodeId, NodeId>,
3026)> {
3027 let mut plan_graph = CuGraph::default();
3028 let mut exec_entities = Vec::new();
3029 let mut original_to_plan = HashMap::new();
3030 let mut plan_to_original = HashMap::new();
3031 let mut name_to_original = HashMap::new();
3032 let mut channel_nodes = HashMap::new();
3033
3034 for (node_id, node) in graph.get_all_nodes() {
3035 name_to_original.insert(node.get_id(), node_id);
3036 if node.get_flavor() != Flavor::Task {
3037 continue;
3038 }
3039 let plan_node_id = plan_graph.add_node(node.clone())?;
3040 let task_index = task_specs.node_id_to_task_index[node_id as usize]
3041 .expect("Task missing from specifications");
3042 plan_to_original.insert(plan_node_id, node_id);
3043 original_to_plan.insert(node_id, plan_node_id);
3044 if plan_node_id as usize != exec_entities.len() {
3045 panic!("Unexpected node ordering while mirroring tasks in plan graph");
3046 }
3047 exec_entities.push(ExecutionEntity {
3048 kind: ExecutionEntityKind::Task { task_index },
3049 });
3050 }
3051
3052 for (bridge_index, spec) in bridge_specs.iter_mut().enumerate() {
3053 for (channel_index, channel_spec) in spec.rx_channels.iter_mut().enumerate() {
3054 let mut node = Node::new(
3055 format!("{}::rx::{}", spec.id, channel_spec.id).as_str(),
3056 "__CuBridgeRxChannel",
3057 );
3058 node.set_flavor(Flavor::Bridge);
3059 let plan_node_id = plan_graph.add_node(node)?;
3060 if plan_node_id as usize != exec_entities.len() {
3061 panic!("Unexpected node ordering while inserting bridge rx channel");
3062 }
3063 channel_spec.plan_node_id = Some(plan_node_id);
3064 exec_entities.push(ExecutionEntity {
3065 kind: ExecutionEntityKind::BridgeRx {
3066 bridge_index,
3067 channel_index,
3068 },
3069 });
3070 channel_nodes.insert(
3071 BridgeChannelKey {
3072 bridge_id: spec.id.clone(),
3073 channel_id: channel_spec.id.clone(),
3074 direction: BridgeChannelDirection::Rx,
3075 },
3076 plan_node_id,
3077 );
3078 }
3079
3080 for (channel_index, channel_spec) in spec.tx_channels.iter_mut().enumerate() {
3081 let mut node = Node::new(
3082 format!("{}::tx::{}", spec.id, channel_spec.id).as_str(),
3083 "__CuBridgeTxChannel",
3084 );
3085 node.set_flavor(Flavor::Bridge);
3086 let plan_node_id = plan_graph.add_node(node)?;
3087 if plan_node_id as usize != exec_entities.len() {
3088 panic!("Unexpected node ordering while inserting bridge tx channel");
3089 }
3090 channel_spec.plan_node_id = Some(plan_node_id);
3091 exec_entities.push(ExecutionEntity {
3092 kind: ExecutionEntityKind::BridgeTx {
3093 bridge_index,
3094 channel_index,
3095 },
3096 });
3097 channel_nodes.insert(
3098 BridgeChannelKey {
3099 bridge_id: spec.id.clone(),
3100 channel_id: channel_spec.id.clone(),
3101 direction: BridgeChannelDirection::Tx,
3102 },
3103 plan_node_id,
3104 );
3105 }
3106 }
3107
3108 for cnx in graph.edges() {
3109 let src_plan = if let Some(channel) = &cnx.src_channel {
3110 let key = BridgeChannelKey {
3111 bridge_id: cnx.src.clone(),
3112 channel_id: channel.clone(),
3113 direction: BridgeChannelDirection::Rx,
3114 };
3115 *channel_nodes
3116 .get(&key)
3117 .unwrap_or_else(|| panic!("Bridge source {:?} missing from plan graph", key))
3118 } else {
3119 let node_id = name_to_original
3120 .get(&cnx.src)
3121 .copied()
3122 .unwrap_or_else(|| panic!("Unknown source node '{}'", cnx.src));
3123 *original_to_plan
3124 .get(&node_id)
3125 .unwrap_or_else(|| panic!("Source node '{}' missing from plan", cnx.src))
3126 };
3127
3128 let dst_plan = if let Some(channel) = &cnx.dst_channel {
3129 let key = BridgeChannelKey {
3130 bridge_id: cnx.dst.clone(),
3131 channel_id: channel.clone(),
3132 direction: BridgeChannelDirection::Tx,
3133 };
3134 *channel_nodes
3135 .get(&key)
3136 .unwrap_or_else(|| panic!("Bridge destination {:?} missing from plan graph", key))
3137 } else {
3138 let node_id = name_to_original
3139 .get(&cnx.dst)
3140 .copied()
3141 .unwrap_or_else(|| panic!("Unknown destination node '{}'", cnx.dst));
3142 *original_to_plan
3143 .get(&node_id)
3144 .unwrap_or_else(|| panic!("Destination node '{}' missing from plan", cnx.dst))
3145 };
3146
3147 plan_graph
3148 .connect_ext(
3149 src_plan,
3150 dst_plan,
3151 &cnx.msg,
3152 cnx.missions.clone(),
3153 None,
3154 None,
3155 )
3156 .map_err(|e| CuError::from(e.to_string()))?;
3157 }
3158
3159 let runtime_plan = compute_runtime_plan(&plan_graph)?;
3160 Ok((runtime_plan, exec_entities, plan_to_original))
3161}
3162
3163fn collect_culist_metadata(
3164 runtime_plan: &CuExecutionLoop,
3165 exec_entities: &[ExecutionEntity],
3166 bridge_specs: &mut [BridgeSpec],
3167 plan_to_original: &HashMap<NodeId, NodeId>,
3168) -> (Vec<usize>, HashMap<NodeId, usize>) {
3169 let mut culist_order = Vec::new();
3170 let mut node_output_positions = HashMap::new();
3171
3172 for unit in &runtime_plan.steps {
3173 if let CuExecutionUnit::Step(step) = unit
3174 && let Some((output_idx, _)) = &step.output_msg_index_type
3175 {
3176 culist_order.push(*output_idx as usize);
3177 match &exec_entities[step.node_id as usize].kind {
3178 ExecutionEntityKind::Task { .. } => {
3179 if let Some(original_node_id) = plan_to_original.get(&step.node_id) {
3180 node_output_positions.insert(*original_node_id, *output_idx as usize);
3181 }
3182 }
3183 ExecutionEntityKind::BridgeRx {
3184 bridge_index,
3185 channel_index,
3186 } => {
3187 bridge_specs[*bridge_index].rx_channels[*channel_index].culist_index =
3188 Some(*output_idx as usize);
3189 }
3190 ExecutionEntityKind::BridgeTx { .. } => {}
3191 }
3192 }
3193 }
3194
3195 (culist_order, node_output_positions)
3196}
3197
3198#[allow(dead_code)]
3199fn build_monitored_ids(task_ids: &[String], bridge_specs: &mut [BridgeSpec]) -> Vec<String> {
3200 let mut names = task_ids.to_vec();
3201 for spec in bridge_specs.iter_mut() {
3202 spec.monitor_index = Some(names.len());
3203 names.push(format!("bridge::{}", spec.id));
3204 for channel in spec.rx_channels.iter_mut() {
3205 channel.monitor_index = Some(names.len());
3206 names.push(format!("bridge::{}::rx::{}", spec.id, channel.id));
3207 }
3208 for channel in spec.tx_channels.iter_mut() {
3209 channel.monitor_index = Some(names.len());
3210 names.push(format!("bridge::{}::tx::{}", spec.id, channel.id));
3211 }
3212 }
3213 names
3214}
3215
3216fn generate_task_execution_tokens(
3217 step: &CuExecutionStep,
3218 task_index: usize,
3219 task_specs: &CuTaskSpecSet,
3220 sim_mode: bool,
3221 mission_mod: &Ident,
3222) -> (proc_macro2::TokenStream, proc_macro2::TokenStream) {
3223 let node_index = int2sliceindex(task_index as u32);
3224 let task_instance = quote! { tasks.#node_index };
3225 let comment_str = format!(
3226 "DEBUG ->> {} ({:?}) Id:{} I:{:?} O:{:?}",
3227 step.node.get_id(),
3228 step.task_type,
3229 step.node_id,
3230 step.input_msg_indices_types,
3231 step.output_msg_index_type
3232 );
3233 let comment_tokens = quote! {{
3234 let _ = stringify!(#comment_str);
3235 }};
3236 let tid = task_index;
3237 let task_enum_name = config_id_to_enum(&task_specs.ids[tid]);
3238 let enum_name = Ident::new(&task_enum_name, Span::call_site());
3239 let rt_guard = rtsan_guard_tokens();
3240
3241 match step.task_type {
3242 CuTaskType::Source => {
3243 if let Some((output_index, _)) = &step.output_msg_index_type {
3244 let output_culist_index = int2sliceindex(*output_index);
3245
3246 let monitoring_action = quote! {
3247 debug!("Task {}: Error during process: {}", #mission_mod::TASKS_IDS[#tid], &error);
3248 let decision = monitor.process_error(#tid, CuTaskState::Process, &error);
3249 match decision {
3250 Decision::Abort => {
3251 debug!("Process: ABORT decision from monitoring. Task '{}' errored out \
3252 during process. Skipping the processing of CL {}.", #mission_mod::TASKS_IDS[#tid], clid);
3253 monitor.process_copperlist(&#mission_mod::collect_metadata(&culist))?;
3254 cl_manager.end_of_processing(clid)?;
3255 return Ok(());
3256 }
3257 Decision::Ignore => {
3258 debug!("Process: IGNORE decision from monitoring. Task '{}' errored out \
3259 during process. The runtime will continue with a forced empty message.", #mission_mod::TASKS_IDS[#tid]);
3260 let cumsg_output = &mut msgs.#output_culist_index;
3261 cumsg_output.clear_payload();
3262 }
3263 Decision::Shutdown => {
3264 debug!("Process: SHUTDOWN decision from monitoring. Task '{}' errored out \
3265 during process. The runtime cannot continue.", #mission_mod::TASKS_IDS[#tid]);
3266 return Err(CuError::new_with_cause("Task errored out during process.", error));
3267 }
3268 }
3269 };
3270
3271 let call_sim_callback = if sim_mode {
3272 quote! {
3273 let doit = {
3274 let cumsg_output = &mut msgs.#output_culist_index;
3275 let state = CuTaskCallbackState::Process((), cumsg_output);
3276 let ovr = sim_callback(SimStep::#enum_name(state));
3277
3278 if let SimOverride::Errored(reason) = ovr {
3279 let error: CuError = reason.into();
3280 #monitoring_action
3281 false
3282 } else {
3283 ovr == SimOverride::ExecuteByRuntime
3284 }
3285 };
3286 }
3287 } else {
3288 quote! { let doit = true; }
3289 };
3290
3291 let logging_tokens = if !task_specs.logging_enabled[tid] {
3292 let output_culist_index = int2sliceindex(*output_index);
3293 quote! {
3294 let mut cumsg_output = &mut culist.msgs.0.#output_culist_index;
3295 cumsg_output.clear_payload();
3296 }
3297 } else {
3298 quote!()
3299 };
3300
3301 (
3302 quote! {
3303 {
3304 #comment_tokens
3305 kf_manager.freeze_task(clid, &#task_instance)?;
3306 #call_sim_callback
3307 let cumsg_output = &mut msgs.#output_culist_index;
3308 cumsg_output.metadata.process_time.start = clock.now().into();
3309 let maybe_error = if doit {
3310 #rt_guard
3311 #task_instance.process(clock, cumsg_output)
3312 } else {
3313 Ok(())
3314 };
3315 cumsg_output.metadata.process_time.end = clock.now().into();
3316 if let Err(error) = maybe_error {
3317 #monitoring_action
3318 }
3319 }
3320 },
3321 logging_tokens,
3322 )
3323 } else {
3324 panic!("Source task should have an output message index.");
3325 }
3326 }
3327 CuTaskType::Sink => {
3328 if let Some((output_index, _)) = &step.output_msg_index_type {
3329 let output_culist_index = int2sliceindex(*output_index);
3330 let indices = step
3331 .input_msg_indices_types
3332 .iter()
3333 .map(|(index, _)| int2sliceindex(*index));
3334
3335 let monitoring_action = quote! {
3336 debug!("Task {}: Error during process: {}", #mission_mod::TASKS_IDS[#tid], &error);
3337 let decision = monitor.process_error(#tid, CuTaskState::Process, &error);
3338 match decision {
3339 Decision::Abort => {
3340 debug!("Process: ABORT decision from monitoring. Task '{}' errored out \
3341 during process. Skipping the processing of CL {}.", #mission_mod::TASKS_IDS[#tid], clid);
3342 monitor.process_copperlist(&#mission_mod::collect_metadata(&culist))?;
3343 cl_manager.end_of_processing(clid)?;
3344 return Ok(());
3345 }
3346 Decision::Ignore => {
3347 debug!("Process: IGNORE decision from monitoring. Task '{}' errored out \
3348 during process. The runtime will continue with a forced empty message.", #mission_mod::TASKS_IDS[#tid]);
3349 let cumsg_output = &mut msgs.#output_culist_index;
3350 cumsg_output.clear_payload();
3351 }
3352 Decision::Shutdown => {
3353 debug!("Process: SHUTDOWN decision from monitoring. Task '{}' errored out \
3354 during process. The runtime cannot continue.", #mission_mod::TASKS_IDS[#tid]);
3355 return Err(CuError::new_with_cause("Task errored out during process.", error));
3356 }
3357 }
3358 };
3359
3360 let inputs_type = if indices.len() == 1 {
3361 quote! { #(msgs.#indices)* }
3362 } else {
3363 quote! { (#(&msgs.#indices),*) }
3364 };
3365
3366 let call_sim_callback = if sim_mode {
3367 quote! {
3368 let doit = {
3369 let cumsg_input = &#inputs_type;
3370 let cumsg_output = &mut msgs.#output_culist_index;
3371 let state = CuTaskCallbackState::Process(cumsg_input, cumsg_output);
3372 let ovr = sim_callback(SimStep::#enum_name(state));
3373
3374 if let SimOverride::Errored(reason) = ovr {
3375 let error: CuError = reason.into();
3376 #monitoring_action
3377 false
3378 } else {
3379 ovr == SimOverride::ExecuteByRuntime
3380 }
3381 };
3382 }
3383 } else {
3384 quote! { let doit = true; }
3385 };
3386
3387 (
3388 quote! {
3389 {
3390 #comment_tokens
3391 kf_manager.freeze_task(clid, &#task_instance)?;
3392 #call_sim_callback
3393 let cumsg_input = &#inputs_type;
3394 let cumsg_output = &mut msgs.#output_culist_index;
3395 cumsg_output.metadata.process_time.start = clock.now().into();
3396 let maybe_error = if doit {
3397 #rt_guard
3398 #task_instance.process(clock, cumsg_input)
3399 } else {
3400 Ok(())
3401 };
3402 cumsg_output.metadata.process_time.end = clock.now().into();
3403 if let Err(error) = maybe_error {
3404 #monitoring_action
3405 }
3406 }
3407 },
3408 quote! {},
3409 )
3410 } else {
3411 panic!("Sink tasks should have a virtual output message index.");
3412 }
3413 }
3414 CuTaskType::Regular => {
3415 if let Some((output_index, _)) = &step.output_msg_index_type {
3416 let output_culist_index = int2sliceindex(*output_index);
3417 let indices = step
3418 .input_msg_indices_types
3419 .iter()
3420 .map(|(index, _)| int2sliceindex(*index));
3421
3422 let monitoring_action = quote! {
3423 debug!("Task {}: Error during process: {}", #mission_mod::TASKS_IDS[#tid], &error);
3424 let decision = monitor.process_error(#tid, CuTaskState::Process, &error);
3425 match decision {
3426 Decision::Abort => {
3427 debug!("Process: ABORT decision from monitoring. Task '{}' errored out \
3428 during process. Skipping the processing of CL {}.", #mission_mod::TASKS_IDS[#tid], clid);
3429 monitor.process_copperlist(&#mission_mod::collect_metadata(&culist))?;
3430 cl_manager.end_of_processing(clid)?;
3431 return Ok(());
3432 }
3433 Decision::Ignore => {
3434 debug!("Process: IGNORE decision from monitoring. Task '{}' errored out \
3435 during process. The runtime will continue with a forced empty message.", #mission_mod::TASKS_IDS[#tid]);
3436 let cumsg_output = &mut msgs.#output_culist_index;
3437 cumsg_output.clear_payload();
3438 }
3439 Decision::Shutdown => {
3440 debug!("Process: SHUTDOWN decision from monitoring. Task '{}' errored out \
3441 during process. The runtime cannot continue.", #mission_mod::TASKS_IDS[#tid]);
3442 return Err(CuError::new_with_cause("Task errored out during process.", error));
3443 }
3444 }
3445 };
3446
3447 let inputs_type = if indices.len() == 1 {
3448 quote! { #(msgs.#indices)* }
3449 } else {
3450 quote! { (#(&msgs.#indices),*) }
3451 };
3452
3453 let call_sim_callback = if sim_mode {
3454 quote! {
3455 let doit = {
3456 let cumsg_input = &#inputs_type;
3457 let cumsg_output = &mut msgs.#output_culist_index;
3458 let state = CuTaskCallbackState::Process(cumsg_input, cumsg_output);
3459 let ovr = sim_callback(SimStep::#enum_name(state));
3460
3461 if let SimOverride::Errored(reason) = ovr {
3462 let error: CuError = reason.into();
3463 #monitoring_action
3464 false
3465 }
3466 else {
3467 ovr == SimOverride::ExecuteByRuntime
3468 }
3469 };
3470 }
3471 } else {
3472 quote! { let doit = true; }
3473 };
3474
3475 let logging_tokens = if !task_specs.logging_enabled[tid] {
3476 let output_culist_index = int2sliceindex(*output_index);
3477 quote! {
3478 let mut cumsg_output = &mut culist.msgs.0.#output_culist_index;
3479 cumsg_output.clear_payload();
3480 }
3481 } else {
3482 quote!()
3483 };
3484
3485 (
3486 quote! {
3487 {
3488 #comment_tokens
3489 kf_manager.freeze_task(clid, &#task_instance)?;
3490 #call_sim_callback
3491 let cumsg_input = &#inputs_type;
3492 let cumsg_output = &mut msgs.#output_culist_index;
3493 cumsg_output.metadata.process_time.start = clock.now().into();
3494 let maybe_error = if doit {
3495 #rt_guard
3496 #task_instance.process(clock, cumsg_input, cumsg_output)
3497 } else {
3498 Ok(())
3499 };
3500 cumsg_output.metadata.process_time.end = clock.now().into();
3501 if let Err(error) = maybe_error {
3502 #monitoring_action
3503 }
3504 }
3505 },
3506 logging_tokens,
3507 )
3508 } else {
3509 panic!("Regular task should have an output message index.");
3510 }
3511 }
3512 }
3513}
3514
3515fn generate_bridge_rx_execution_tokens(
3516 bridge_spec: &BridgeSpec,
3517 channel_index: usize,
3518 mission_mod: &Ident,
3519) -> (proc_macro2::TokenStream, proc_macro2::TokenStream) {
3520 let rt_guard = rtsan_guard_tokens();
3521 let bridge_tuple_index = int2sliceindex(bridge_spec.tuple_index as u32);
3522 let channel = &bridge_spec.rx_channels[channel_index];
3523 let culist_index = channel
3524 .culist_index
3525 .unwrap_or_else(|| panic!("Bridge Rx channel missing output index"));
3526 let culist_index_ts = int2sliceindex(culist_index as u32);
3527 let monitor_index = syn::Index::from(
3528 channel
3529 .monitor_index
3530 .expect("Bridge Rx channel missing monitor index"),
3531 );
3532 let bridge_type = &bridge_spec.type_path;
3533 let const_ident = &channel.const_ident;
3534 (
3535 quote! {
3536 {
3537 let bridge = &mut bridges.#bridge_tuple_index;
3538 let cumsg_output = &mut msgs.#culist_index_ts;
3539 cumsg_output.metadata.process_time.start = clock.now().into();
3540 let maybe_error = {
3541 #rt_guard
3542 bridge.receive(
3543 clock,
3544 &<#bridge_type as cu29::cubridge::CuBridge>::Rx::#const_ident,
3545 cumsg_output,
3546 )
3547 };
3548 cumsg_output.metadata.process_time.end = clock.now().into();
3549 if let Err(error) = maybe_error {
3550 let decision = monitor.process_error(#monitor_index, CuTaskState::Process, &error);
3551 match decision {
3552 Decision::Abort => {
3553 debug!("Process: ABORT decision from monitoring. Task '{}' errored out during process. Skipping the processing of CL {}.", #mission_mod::TASKS_IDS[#monitor_index], clid);
3554 monitor.process_copperlist(&#mission_mod::collect_metadata(&culist))?;
3555 cl_manager.end_of_processing(clid)?;
3556 return Ok(());
3557 }
3558 Decision::Ignore => {
3559 debug!("Process: IGNORE decision from monitoring. Task '{}' errored out during process. The runtime will continue with a forced empty message.", #mission_mod::TASKS_IDS[#monitor_index]);
3560 let cumsg_output = &mut msgs.#culist_index_ts;
3561 cumsg_output.clear_payload();
3562 }
3563 Decision::Shutdown => {
3564 debug!("Process: SHUTDOWN decision from monitoring. Task '{}' errored out during process. The runtime cannot continue.", #mission_mod::TASKS_IDS[#monitor_index]);
3565 return Err(CuError::new_with_cause("Task errored out during process.", error));
3566 }
3567 }
3568 }
3569 }
3570 },
3571 quote! {},
3572 )
3573}
3574
3575fn generate_bridge_tx_execution_tokens(
3576 step: &CuExecutionStep,
3577 bridge_spec: &BridgeSpec,
3578 channel_index: usize,
3579 mission_mod: &Ident,
3580) -> (proc_macro2::TokenStream, proc_macro2::TokenStream) {
3581 let rt_guard = rtsan_guard_tokens();
3582 let channel = &bridge_spec.tx_channels[channel_index];
3583 let monitor_index = syn::Index::from(
3584 channel
3585 .monitor_index
3586 .expect("Bridge Tx channel missing monitor index"),
3587 );
3588 let input_index = step
3589 .input_msg_indices_types
3590 .first()
3591 .map(|(idx, _)| int2sliceindex(*idx))
3592 .expect("Bridge Tx channel should have exactly one input");
3593 let bridge_tuple_index = int2sliceindex(bridge_spec.tuple_index as u32);
3594 let bridge_type = &bridge_spec.type_path;
3595 let const_ident = &channel.const_ident;
3596 (
3597 quote! {
3598 {
3599 let bridge = &mut bridges.#bridge_tuple_index;
3600 let cumsg_input = &mut msgs.#input_index;
3601 cumsg_input.metadata.process_time.start = clock.now().into();
3603 let maybe_error = {
3604 #rt_guard
3605 bridge.send(
3606 clock,
3607 &<#bridge_type as cu29::cubridge::CuBridge>::Tx::#const_ident,
3608 &*cumsg_input,
3609 )
3610 };
3611 if let Err(error) = maybe_error {
3612 let decision = monitor.process_error(#monitor_index, CuTaskState::Process, &error);
3613 match decision {
3614 Decision::Abort => {
3615 debug!("Process: ABORT decision from monitoring. Task '{}' errored out during process. Skipping the processing of CL {}.", #mission_mod::TASKS_IDS[#monitor_index], clid);
3616 monitor.process_copperlist(&#mission_mod::collect_metadata(&culist))?;
3617 cl_manager.end_of_processing(clid)?;
3618 return Ok(());
3619 }
3620 Decision::Ignore => {
3621 debug!("Process: IGNORE decision from monitoring. Task '{}' errored out during process. The runtime will continue with a forced empty message.", #mission_mod::TASKS_IDS[#monitor_index]);
3622 }
3623 Decision::Shutdown => {
3624 debug!("Process: SHUTDOWN decision from monitoring. Task '{}' errored out during process. The runtime cannot continue.", #mission_mod::TASKS_IDS[#monitor_index]);
3625 return Err(CuError::new_with_cause("Task errored out during process.", error));
3626 }
3627 }
3628 }
3629 cumsg_input.metadata.process_time.end = clock.now().into();
3630 }
3631 },
3632 quote! {},
3633 )
3634}
3635
3636#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
3637enum BridgeChannelDirection {
3638 Rx,
3639 Tx,
3640}
3641
3642#[derive(Clone, Debug, PartialEq, Eq, Hash)]
3643struct BridgeChannelKey {
3644 bridge_id: String,
3645 channel_id: String,
3646 direction: BridgeChannelDirection,
3647}
3648
3649#[derive(Clone)]
3650struct BridgeChannelSpec {
3651 id: String,
3652 const_ident: Ident,
3653 #[allow(dead_code)]
3654 msg_type: Type,
3655 config_index: usize,
3656 plan_node_id: Option<NodeId>,
3657 culist_index: Option<usize>,
3658 monitor_index: Option<usize>,
3659}
3660
3661#[derive(Clone)]
3662struct BridgeSpec {
3663 id: String,
3664 type_path: Type,
3665 config_index: usize,
3666 tuple_index: usize,
3667 monitor_index: Option<usize>,
3668 rx_channels: Vec<BridgeChannelSpec>,
3669 tx_channels: Vec<BridgeChannelSpec>,
3670}
3671
3672#[derive(Clone)]
3673struct ExecutionEntity {
3674 kind: ExecutionEntityKind,
3675}
3676
3677#[derive(Clone)]
3678enum ExecutionEntityKind {
3679 Task {
3680 task_index: usize,
3681 },
3682 BridgeRx {
3683 bridge_index: usize,
3684 channel_index: usize,
3685 },
3686 BridgeTx {
3687 bridge_index: usize,
3688 channel_index: usize,
3689 },
3690}
3691
3692#[cfg(test)]
3693mod tests {
3694 #[test]
3696 fn test_compile_fail() {
3697 use rustc_version::{Channel, version_meta};
3698 use std::{fs, path::Path};
3699
3700 let dir = Path::new("tests/compile_fail");
3701 for entry in fs::read_dir(dir).unwrap() {
3702 let entry = entry.unwrap();
3703 if !entry.file_type().unwrap().is_dir() {
3704 continue;
3705 }
3706 for file in fs::read_dir(entry.path()).unwrap() {
3707 let file = file.unwrap();
3708 let p = file.path();
3709 if p.extension().and_then(|x| x.to_str()) != Some("rs") {
3710 continue;
3711 }
3712
3713 let base = p.with_extension("stderr"); let src = match version_meta().unwrap().channel {
3715 Channel::Beta => Path::new(&format!("{}.beta", base.display())).to_path_buf(),
3716 _ => Path::new(&format!("{}.stable", base.display())).to_path_buf(),
3717 };
3718
3719 if src.exists() {
3720 fs::copy(src, &base).unwrap();
3721 }
3722 }
3723 }
3724
3725 let t = trybuild::TestCases::new();
3726 t.compile_fail("tests/compile_fail/*/*.rs");
3727 }
3728
3729 #[test]
3730 fn bridge_resources_are_collected() {
3731 use super::*;
3732 use cu29::config::{CuGraph, Flavor, Node};
3733 use std::collections::HashMap;
3734 use syn::parse_str;
3735
3736 let mut graph = CuGraph::default();
3737 let mut node = Node::new_with_flavor("radio", "bridge::Dummy", Flavor::Bridge);
3738 let mut res = HashMap::new();
3739 res.insert("serial".to_string(), "fc.serial0".to_string());
3740 node.set_resources(Some(res));
3741 graph.add_node(node).expect("bridge node");
3742
3743 let task_specs = CuTaskSpecSet::from_graph(&graph);
3744 let bridge_spec = BridgeSpec {
3745 id: "radio".to_string(),
3746 type_path: parse_str("bridge::Dummy").unwrap(),
3747 config_index: 0,
3748 tuple_index: 0,
3749 monitor_index: None,
3750 rx_channels: Vec::new(),
3751 tx_channels: Vec::new(),
3752 };
3753
3754 let mut config = cu29::config::CuConfig::default();
3755 config.resources.push(ResourceBundleConfig {
3756 id: "fc".to_string(),
3757 provider: "board::Bundle".to_string(),
3758 config: None,
3759 missions: None,
3760 });
3761 let bundle_specs = build_bundle_specs(&config, "default").expect("bundle specs");
3762 let specs = collect_resource_specs(&graph, &task_specs, &[bridge_spec], &bundle_specs)
3763 .expect("collect specs");
3764 assert_eq!(specs.len(), 1);
3765 assert!(matches!(specs[0].owner, ResourceOwner::Bridge(0)));
3766 assert_eq!(specs[0].binding_name, "serial");
3767 assert_eq!(specs[0].bundle_index, 0);
3768 assert_eq!(specs[0].resource_name, "serial0");
3769 }
3770}