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