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