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::from("Failed to thaw").add_cause(&e.to_string()))?
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 cnx in graph.edges() {
2508 if let Some(channel) = &cnx.src_channel {
2509 let key = BridgeChannelKey {
2510 bridge_id: cnx.src.clone(),
2511 channel_id: channel.clone(),
2512 direction: BridgeChannelDirection::Rx,
2513 };
2514 usage
2515 .entry(key)
2516 .and_modify(|msg| {
2517 if msg != &cnx.msg {
2518 panic!(
2519 "Bridge '{}' channel '{}' is used with incompatible message types: {} vs {}",
2520 cnx.src, channel, msg, cnx.msg
2521 );
2522 }
2523 })
2524 .or_insert(cnx.msg.clone());
2525 }
2526 if let Some(channel) = &cnx.dst_channel {
2527 let key = BridgeChannelKey {
2528 bridge_id: cnx.dst.clone(),
2529 channel_id: channel.clone(),
2530 direction: BridgeChannelDirection::Tx,
2531 };
2532 usage
2533 .entry(key)
2534 .and_modify(|msg| {
2535 if msg != &cnx.msg {
2536 panic!(
2537 "Bridge '{}' channel '{}' is used with incompatible message types: {} vs {}",
2538 cnx.dst, channel, msg, cnx.msg
2539 );
2540 }
2541 })
2542 .or_insert(cnx.msg.clone());
2543 }
2544 }
2545 usage
2546}
2547
2548fn build_bridge_specs(
2549 config: &CuConfig,
2550 graph: &CuGraph,
2551 channel_usage: &HashMap<BridgeChannelKey, String>,
2552) -> Vec<BridgeSpec> {
2553 let mut specs = Vec::new();
2554 for (bridge_index, bridge_cfg) in config.bridges.iter().enumerate() {
2555 if graph.get_node_id_by_name(bridge_cfg.id.as_str()).is_none() {
2556 continue;
2557 }
2558
2559 let type_path = parse_str::<Type>(bridge_cfg.type_.as_str()).unwrap_or_else(|err| {
2560 panic!(
2561 "Could not parse bridge type '{}' for '{}': {err}",
2562 bridge_cfg.type_, bridge_cfg.id
2563 )
2564 });
2565
2566 let mut rx_channels = Vec::new();
2567 let mut tx_channels = Vec::new();
2568
2569 for (channel_index, channel) in bridge_cfg.channels.iter().enumerate() {
2570 match channel {
2571 BridgeChannelConfigRepresentation::Rx { id, .. } => {
2572 let key = BridgeChannelKey {
2573 bridge_id: bridge_cfg.id.clone(),
2574 channel_id: id.clone(),
2575 direction: BridgeChannelDirection::Rx,
2576 };
2577 if let Some(msg_type) = channel_usage.get(&key) {
2578 let msg_type = parse_str::<Type>(msg_type).unwrap_or_else(|err| {
2579 panic!(
2580 "Could not parse message type '{msg_type}' for bridge '{}' channel '{}': {err}",
2581 bridge_cfg.id, id
2582 )
2583 });
2584 let const_ident =
2585 Ident::new(&config_id_to_bridge_const(id.as_str()), Span::call_site());
2586 rx_channels.push(BridgeChannelSpec {
2587 id: id.clone(),
2588 const_ident,
2589 msg_type,
2590 config_index: channel_index,
2591 plan_node_id: None,
2592 culist_index: None,
2593 monitor_index: None,
2594 });
2595 }
2596 }
2597 BridgeChannelConfigRepresentation::Tx { id, .. } => {
2598 let key = BridgeChannelKey {
2599 bridge_id: bridge_cfg.id.clone(),
2600 channel_id: id.clone(),
2601 direction: BridgeChannelDirection::Tx,
2602 };
2603 if let Some(msg_type) = channel_usage.get(&key) {
2604 let msg_type = parse_str::<Type>(msg_type).unwrap_or_else(|err| {
2605 panic!(
2606 "Could not parse message type '{msg_type}' for bridge '{}' channel '{}': {err}",
2607 bridge_cfg.id, id
2608 )
2609 });
2610 let const_ident =
2611 Ident::new(&config_id_to_bridge_const(id.as_str()), Span::call_site());
2612 tx_channels.push(BridgeChannelSpec {
2613 id: id.clone(),
2614 const_ident,
2615 msg_type,
2616 config_index: channel_index,
2617 plan_node_id: None,
2618 culist_index: None,
2619 monitor_index: None,
2620 });
2621 }
2622 }
2623 }
2624 }
2625
2626 if rx_channels.is_empty() && tx_channels.is_empty() {
2627 continue;
2628 }
2629
2630 specs.push(BridgeSpec {
2631 id: bridge_cfg.id.clone(),
2632 type_path,
2633 config_index: bridge_index,
2634 tuple_index: 0,
2635 monitor_index: None,
2636 rx_channels,
2637 tx_channels,
2638 });
2639 }
2640
2641 for (tuple_index, spec) in specs.iter_mut().enumerate() {
2642 spec.tuple_index = tuple_index;
2643 }
2644
2645 specs
2646}
2647
2648fn collect_task_member_names(graph: &CuGraph) -> Vec<(NodeId, String)> {
2649 graph
2650 .get_all_nodes()
2651 .iter()
2652 .filter(|(_, node)| node.get_flavor() == Flavor::Task)
2653 .map(|(node_id, node)| (*node_id, config_id_to_struct_member(node.get_id().as_str())))
2654 .collect()
2655}
2656
2657#[derive(Clone, Copy)]
2658enum ResourceOwner {
2659 Task(usize),
2660 Bridge(usize),
2661}
2662
2663#[derive(Clone)]
2664struct ResourceKeySpec {
2665 bundle_index: usize,
2666 provider_path: syn::Path,
2667 resource_name: String,
2668 binding_name: String,
2669 owner: ResourceOwner,
2670}
2671
2672fn parse_resource_path(path: &str) -> CuResult<(String, String)> {
2673 let (bundle_id, name) = path.split_once('.').ok_or_else(|| {
2674 CuError::from(format!(
2675 "Resource '{path}' is missing a bundle prefix (expected bundle.resource)"
2676 ))
2677 })?;
2678
2679 if bundle_id.is_empty() || name.is_empty() {
2680 return Err(CuError::from(format!(
2681 "Resource '{path}' must use the 'bundle.resource' format"
2682 )));
2683 }
2684
2685 Ok((bundle_id.to_string(), name.to_string()))
2686}
2687
2688fn collect_resource_specs(
2689 graph: &CuGraph,
2690 task_specs: &CuTaskSpecSet,
2691 bridge_specs: &[BridgeSpec],
2692 bundle_specs: &[BundleSpec],
2693) -> CuResult<Vec<ResourceKeySpec>> {
2694 let mut bridge_lookup: BTreeMap<String, usize> = BTreeMap::new();
2695 for (idx, spec) in bridge_specs.iter().enumerate() {
2696 bridge_lookup.insert(spec.id.clone(), idx);
2697 }
2698
2699 let mut bundle_lookup: HashMap<String, (usize, syn::Path)> = HashMap::new();
2700 for (index, bundle) in bundle_specs.iter().enumerate() {
2701 bundle_lookup.insert(bundle.id.clone(), (index, bundle.provider_path.clone()));
2702 }
2703
2704 let mut specs = Vec::new();
2705
2706 for (node_id, node) in graph.get_all_nodes() {
2707 let resources = node.get_resources();
2708 if let Some(resources) = resources {
2709 let task_index = task_specs.node_id_to_task_index[node_id as usize];
2710 let owner = if let Some(task_index) = task_index {
2711 ResourceOwner::Task(task_index)
2712 } else if node.get_flavor() == Flavor::Bridge {
2713 let bridge_index = bridge_lookup.get(&node.get_id()).ok_or_else(|| {
2714 CuError::from(format!(
2715 "Resource mapping attached to unknown bridge node '{}'",
2716 node.get_id()
2717 ))
2718 })?;
2719 ResourceOwner::Bridge(*bridge_index)
2720 } else {
2721 return Err(CuError::from(format!(
2722 "Resource mapping attached to non-task node '{}'",
2723 node.get_id()
2724 )));
2725 };
2726
2727 for (binding_name, path) in resources {
2728 let (bundle_id, resource_name) = parse_resource_path(path)?;
2729 let (bundle_index, provider_path) =
2730 bundle_lookup.get(&bundle_id).ok_or_else(|| {
2731 CuError::from(format!(
2732 "Resource '{}' references unknown bundle '{}'",
2733 path, bundle_id
2734 ))
2735 })?;
2736 specs.push(ResourceKeySpec {
2737 bundle_index: *bundle_index,
2738 provider_path: provider_path.clone(),
2739 resource_name,
2740 binding_name: binding_name.clone(),
2741 owner,
2742 });
2743 }
2744 }
2745 }
2746
2747 Ok(specs)
2748}
2749
2750fn build_bundle_list<'a>(config: &'a CuConfig, mission: &str) -> Vec<&'a ResourceBundleConfig> {
2751 config
2752 .resources
2753 .iter()
2754 .filter(|bundle| {
2755 bundle
2756 .missions
2757 .as_ref()
2758 .is_none_or(|missions| missions.iter().any(|m| m == mission))
2759 })
2760 .collect()
2761}
2762
2763struct BundleSpec {
2764 id: String,
2765 provider_path: syn::Path,
2766}
2767
2768fn build_bundle_specs(config: &CuConfig, mission: &str) -> CuResult<Vec<BundleSpec>> {
2769 build_bundle_list(config, mission)
2770 .into_iter()
2771 .map(|bundle| {
2772 let provider_path: syn::Path =
2773 syn::parse_str(bundle.provider.as_str()).map_err(|err| {
2774 CuError::from(format!(
2775 "Failed to parse provider path '{}' for bundle '{}': {err}",
2776 bundle.provider, bundle.id
2777 ))
2778 })?;
2779 Ok(BundleSpec {
2780 id: bundle.id.clone(),
2781 provider_path,
2782 })
2783 })
2784 .collect()
2785}
2786
2787fn build_resources_module(
2788 bundle_specs: &[BundleSpec],
2789) -> CuResult<(proc_macro2::TokenStream, proc_macro2::TokenStream)> {
2790 let bundle_consts = bundle_specs.iter().enumerate().map(|(index, bundle)| {
2791 let const_ident = Ident::new(
2792 &config_id_to_bridge_const(bundle.id.as_str()),
2793 Span::call_site(),
2794 );
2795 quote! { pub const #const_ident: BundleIndex = BundleIndex::new(#index); }
2796 });
2797
2798 let resources_module = quote! {
2799 pub mod resources {
2800 #![allow(dead_code)]
2801 use cu29::resource::BundleIndex;
2802
2803 pub mod bundles {
2804 use super::BundleIndex;
2805 #(#bundle_consts)*
2806 }
2807 }
2808 };
2809
2810 let bundle_counts = bundle_specs.iter().map(|bundle| {
2811 let provider_path = &bundle.provider_path;
2812 quote! { <#provider_path as cu29::resource::ResourceBundleDecl>::Id::COUNT }
2813 });
2814
2815 let bundle_inits = bundle_specs
2816 .iter()
2817 .enumerate()
2818 .map(|(index, bundle)| {
2819 let bundle_id = LitStr::new(bundle.id.as_str(), Span::call_site());
2820 let provider_path = &bundle.provider_path;
2821 quote! {
2822 let bundle_cfg = config
2823 .resources
2824 .iter()
2825 .find(|b| b.id == #bundle_id)
2826 .unwrap_or_else(|| panic!("Resource bundle '{}' missing from configuration", #bundle_id));
2827 let bundle_ctx = cu29::resource::BundleContext::<#provider_path>::new(
2828 cu29::resource::BundleIndex::new(#index),
2829 #bundle_id,
2830 );
2831 <#provider_path as cu29::resource::ResourceBundle>::build(
2832 bundle_ctx,
2833 bundle_cfg.config.as_ref(),
2834 &mut manager,
2835 )?;
2836 }
2837 })
2838 .collect::<Vec<_>>();
2839
2840 let resources_instanciator = quote! {
2841 pub fn resources_instanciator(config: &CuConfig) -> CuResult<cu29::resource::ResourceManager> {
2842 let bundle_counts: &[usize] = &[ #(#bundle_counts),* ];
2843 let mut manager = cu29::resource::ResourceManager::new(bundle_counts);
2844 #(#bundle_inits)*
2845 Ok(manager)
2846 }
2847 };
2848
2849 Ok((resources_module, resources_instanciator))
2850}
2851
2852struct ResourceMappingTokens {
2853 defs: proc_macro2::TokenStream,
2854 refs: Vec<proc_macro2::TokenStream>,
2855}
2856
2857fn build_task_resource_mappings(
2858 resource_specs: &[ResourceKeySpec],
2859 task_specs: &CuTaskSpecSet,
2860) -> CuResult<ResourceMappingTokens> {
2861 let mut per_task: Vec<Vec<&ResourceKeySpec>> = vec![Vec::new(); task_specs.ids.len()];
2862
2863 for spec in resource_specs {
2864 let ResourceOwner::Task(task_index) = spec.owner else {
2865 continue;
2866 };
2867 per_task
2868 .get_mut(task_index)
2869 .ok_or_else(|| {
2870 CuError::from(format!(
2871 "Resource '{}' mapped to invalid task index {}",
2872 spec.binding_name, task_index
2873 ))
2874 })?
2875 .push(spec);
2876 }
2877
2878 let mut mapping_defs = Vec::new();
2879 let mut mapping_refs = Vec::new();
2880
2881 for (idx, entries) in per_task.iter().enumerate() {
2882 if entries.is_empty() {
2883 mapping_refs.push(quote! { None });
2884 continue;
2885 }
2886
2887 let binding_task_type = if task_specs.background_flags[idx] {
2888 &task_specs.sim_task_types[idx]
2889 } else {
2890 &task_specs.task_types[idx]
2891 };
2892
2893 let binding_trait = match task_specs.cutypes[idx] {
2894 CuTaskType::Source => quote! { CuSrcTask },
2895 CuTaskType::Regular => quote! { CuTask },
2896 CuTaskType::Sink => quote! { CuSinkTask },
2897 };
2898
2899 let entries_ident = format_ident!("TASK{}_RES_ENTRIES", idx);
2900 let map_ident = format_ident!("TASK{}_RES_MAPPING", idx);
2901 let binding_type = quote! {
2902 <<#binding_task_type as #binding_trait>::Resources<'_> as ResourceBindings>::Binding
2903 };
2904 let entry_tokens = entries.iter().map(|spec| {
2905 let binding_ident =
2906 Ident::new(&config_id_to_enum(spec.binding_name.as_str()), Span::call_site());
2907 let resource_ident =
2908 Ident::new(&config_id_to_enum(spec.resource_name.as_str()), Span::call_site());
2909 let bundle_index = spec.bundle_index;
2910 let provider_path = &spec.provider_path;
2911 quote! {
2912 (#binding_type::#binding_ident, cu29::resource::ResourceKey::new(
2913 cu29::resource::BundleIndex::new(#bundle_index),
2914 <#provider_path as cu29::resource::ResourceBundleDecl>::Id::#resource_ident as usize,
2915 ))
2916 }
2917 });
2918
2919 mapping_defs.push(quote! {
2920 const #entries_ident: &[(#binding_type, cu29::resource::ResourceKey)] = &[ #(#entry_tokens),* ];
2921 const #map_ident: cu29::resource::ResourceBindingMap<#binding_type> =
2922 cu29::resource::ResourceBindingMap::new(#entries_ident);
2923 });
2924 mapping_refs.push(quote! { Some(&#map_ident) });
2925 }
2926
2927 Ok(ResourceMappingTokens {
2928 defs: quote! { #(#mapping_defs)* },
2929 refs: mapping_refs,
2930 })
2931}
2932
2933fn build_bridge_resource_mappings(
2934 resource_specs: &[ResourceKeySpec],
2935 bridge_specs: &[BridgeSpec],
2936) -> ResourceMappingTokens {
2937 let mut per_bridge: Vec<Vec<&ResourceKeySpec>> = vec![Vec::new(); bridge_specs.len()];
2938
2939 for spec in resource_specs {
2940 let ResourceOwner::Bridge(bridge_index) = spec.owner else {
2941 continue;
2942 };
2943 per_bridge[bridge_index].push(spec);
2944 }
2945
2946 let mut mapping_defs = Vec::new();
2947 let mut mapping_refs = Vec::new();
2948
2949 for (idx, entries) in per_bridge.iter().enumerate() {
2950 if entries.is_empty() {
2951 mapping_refs.push(quote! { None });
2952 continue;
2953 }
2954
2955 let bridge_type = &bridge_specs[idx].type_path;
2956 let binding_type = quote! {
2957 <<#bridge_type as cu29::cubridge::CuBridge>::Resources<'_> as ResourceBindings>::Binding
2958 };
2959 let entries_ident = format_ident!("BRIDGE{}_RES_ENTRIES", idx);
2960 let map_ident = format_ident!("BRIDGE{}_RES_MAPPING", idx);
2961 let entry_tokens = entries.iter().map(|spec| {
2962 let binding_ident =
2963 Ident::new(&config_id_to_enum(spec.binding_name.as_str()), Span::call_site());
2964 let resource_ident =
2965 Ident::new(&config_id_to_enum(spec.resource_name.as_str()), Span::call_site());
2966 let bundle_index = spec.bundle_index;
2967 let provider_path = &spec.provider_path;
2968 quote! {
2969 (#binding_type::#binding_ident, cu29::resource::ResourceKey::new(
2970 cu29::resource::BundleIndex::new(#bundle_index),
2971 <#provider_path as cu29::resource::ResourceBundleDecl>::Id::#resource_ident as usize,
2972 ))
2973 }
2974 });
2975
2976 mapping_defs.push(quote! {
2977 const #entries_ident: &[(#binding_type, cu29::resource::ResourceKey)] = &[ #(#entry_tokens),* ];
2978 const #map_ident: cu29::resource::ResourceBindingMap<#binding_type> =
2979 cu29::resource::ResourceBindingMap::new(#entries_ident);
2980 });
2981 mapping_refs.push(quote! { Some(&#map_ident) });
2982 }
2983
2984 ResourceMappingTokens {
2985 defs: quote! { #(#mapping_defs)* },
2986 refs: mapping_refs,
2987 }
2988}
2989
2990fn build_execution_plan(
2991 graph: &CuGraph,
2992 task_specs: &CuTaskSpecSet,
2993 bridge_specs: &mut [BridgeSpec],
2994) -> CuResult<(
2995 CuExecutionLoop,
2996 Vec<ExecutionEntity>,
2997 HashMap<NodeId, NodeId>,
2998)> {
2999 let mut plan_graph = CuGraph::default();
3000 let mut exec_entities = Vec::new();
3001 let mut original_to_plan = HashMap::new();
3002 let mut plan_to_original = HashMap::new();
3003 let mut name_to_original = HashMap::new();
3004 let mut channel_nodes = HashMap::new();
3005
3006 for (node_id, node) in graph.get_all_nodes() {
3007 name_to_original.insert(node.get_id(), node_id);
3008 if node.get_flavor() != Flavor::Task {
3009 continue;
3010 }
3011 let plan_node_id = plan_graph.add_node(node.clone())?;
3012 let task_index = task_specs.node_id_to_task_index[node_id as usize]
3013 .expect("Task missing from specifications");
3014 plan_to_original.insert(plan_node_id, node_id);
3015 original_to_plan.insert(node_id, plan_node_id);
3016 if plan_node_id as usize != exec_entities.len() {
3017 panic!("Unexpected node ordering while mirroring tasks in plan graph");
3018 }
3019 exec_entities.push(ExecutionEntity {
3020 kind: ExecutionEntityKind::Task { task_index },
3021 });
3022 }
3023
3024 for (bridge_index, spec) in bridge_specs.iter_mut().enumerate() {
3025 for (channel_index, channel_spec) in spec.rx_channels.iter_mut().enumerate() {
3026 let mut node = Node::new(
3027 format!("{}::rx::{}", spec.id, channel_spec.id).as_str(),
3028 "__CuBridgeRxChannel",
3029 );
3030 node.set_flavor(Flavor::Bridge);
3031 let plan_node_id = plan_graph.add_node(node)?;
3032 if plan_node_id as usize != exec_entities.len() {
3033 panic!("Unexpected node ordering while inserting bridge rx channel");
3034 }
3035 channel_spec.plan_node_id = Some(plan_node_id);
3036 exec_entities.push(ExecutionEntity {
3037 kind: ExecutionEntityKind::BridgeRx {
3038 bridge_index,
3039 channel_index,
3040 },
3041 });
3042 channel_nodes.insert(
3043 BridgeChannelKey {
3044 bridge_id: spec.id.clone(),
3045 channel_id: channel_spec.id.clone(),
3046 direction: BridgeChannelDirection::Rx,
3047 },
3048 plan_node_id,
3049 );
3050 }
3051
3052 for (channel_index, channel_spec) in spec.tx_channels.iter_mut().enumerate() {
3053 let mut node = Node::new(
3054 format!("{}::tx::{}", spec.id, channel_spec.id).as_str(),
3055 "__CuBridgeTxChannel",
3056 );
3057 node.set_flavor(Flavor::Bridge);
3058 let plan_node_id = plan_graph.add_node(node)?;
3059 if plan_node_id as usize != exec_entities.len() {
3060 panic!("Unexpected node ordering while inserting bridge tx channel");
3061 }
3062 channel_spec.plan_node_id = Some(plan_node_id);
3063 exec_entities.push(ExecutionEntity {
3064 kind: ExecutionEntityKind::BridgeTx {
3065 bridge_index,
3066 channel_index,
3067 },
3068 });
3069 channel_nodes.insert(
3070 BridgeChannelKey {
3071 bridge_id: spec.id.clone(),
3072 channel_id: channel_spec.id.clone(),
3073 direction: BridgeChannelDirection::Tx,
3074 },
3075 plan_node_id,
3076 );
3077 }
3078 }
3079
3080 for cnx in graph.edges() {
3081 let src_plan = if let Some(channel) = &cnx.src_channel {
3082 let key = BridgeChannelKey {
3083 bridge_id: cnx.src.clone(),
3084 channel_id: channel.clone(),
3085 direction: BridgeChannelDirection::Rx,
3086 };
3087 *channel_nodes
3088 .get(&key)
3089 .unwrap_or_else(|| panic!("Bridge source {:?} missing from plan graph", key))
3090 } else {
3091 let node_id = name_to_original
3092 .get(&cnx.src)
3093 .copied()
3094 .unwrap_or_else(|| panic!("Unknown source node '{}'", cnx.src));
3095 *original_to_plan
3096 .get(&node_id)
3097 .unwrap_or_else(|| panic!("Source node '{}' missing from plan", cnx.src))
3098 };
3099
3100 let dst_plan = if let Some(channel) = &cnx.dst_channel {
3101 let key = BridgeChannelKey {
3102 bridge_id: cnx.dst.clone(),
3103 channel_id: channel.clone(),
3104 direction: BridgeChannelDirection::Tx,
3105 };
3106 *channel_nodes
3107 .get(&key)
3108 .unwrap_or_else(|| panic!("Bridge destination {:?} missing from plan graph", key))
3109 } else {
3110 let node_id = name_to_original
3111 .get(&cnx.dst)
3112 .copied()
3113 .unwrap_or_else(|| panic!("Unknown destination node '{}'", cnx.dst));
3114 *original_to_plan
3115 .get(&node_id)
3116 .unwrap_or_else(|| panic!("Destination node '{}' missing from plan", cnx.dst))
3117 };
3118
3119 plan_graph
3120 .connect_ext(
3121 src_plan,
3122 dst_plan,
3123 &cnx.msg,
3124 cnx.missions.clone(),
3125 None,
3126 None,
3127 )
3128 .map_err(|e| CuError::from(e.to_string()))?;
3129 }
3130
3131 let runtime_plan = compute_runtime_plan(&plan_graph)?;
3132 Ok((runtime_plan, exec_entities, plan_to_original))
3133}
3134
3135fn collect_culist_metadata(
3136 runtime_plan: &CuExecutionLoop,
3137 exec_entities: &[ExecutionEntity],
3138 bridge_specs: &mut [BridgeSpec],
3139 plan_to_original: &HashMap<NodeId, NodeId>,
3140) -> (Vec<usize>, HashMap<NodeId, usize>) {
3141 let mut culist_order = Vec::new();
3142 let mut node_output_positions = HashMap::new();
3143
3144 for unit in &runtime_plan.steps {
3145 if let CuExecutionUnit::Step(step) = unit
3146 && let Some((output_idx, _)) = &step.output_msg_index_type
3147 {
3148 culist_order.push(*output_idx as usize);
3149 match &exec_entities[step.node_id as usize].kind {
3150 ExecutionEntityKind::Task { .. } => {
3151 if let Some(original_node_id) = plan_to_original.get(&step.node_id) {
3152 node_output_positions.insert(*original_node_id, *output_idx as usize);
3153 }
3154 }
3155 ExecutionEntityKind::BridgeRx {
3156 bridge_index,
3157 channel_index,
3158 } => {
3159 bridge_specs[*bridge_index].rx_channels[*channel_index].culist_index =
3160 Some(*output_idx as usize);
3161 }
3162 ExecutionEntityKind::BridgeTx { .. } => {}
3163 }
3164 }
3165 }
3166
3167 (culist_order, node_output_positions)
3168}
3169
3170#[allow(dead_code)]
3171fn build_monitored_ids(task_ids: &[String], bridge_specs: &mut [BridgeSpec]) -> Vec<String> {
3172 let mut names = task_ids.to_vec();
3173 for spec in bridge_specs.iter_mut() {
3174 spec.monitor_index = Some(names.len());
3175 names.push(format!("bridge::{}", spec.id));
3176 for channel in spec.rx_channels.iter_mut() {
3177 channel.monitor_index = Some(names.len());
3178 names.push(format!("bridge::{}::rx::{}", spec.id, channel.id));
3179 }
3180 for channel in spec.tx_channels.iter_mut() {
3181 channel.monitor_index = Some(names.len());
3182 names.push(format!("bridge::{}::tx::{}", spec.id, channel.id));
3183 }
3184 }
3185 names
3186}
3187
3188fn generate_task_execution_tokens(
3189 step: &CuExecutionStep,
3190 task_index: usize,
3191 task_specs: &CuTaskSpecSet,
3192 sim_mode: bool,
3193 mission_mod: &Ident,
3194) -> (proc_macro2::TokenStream, proc_macro2::TokenStream) {
3195 let node_index = int2sliceindex(task_index as u32);
3196 let task_instance = quote! { tasks.#node_index };
3197 let comment_str = format!(
3198 "DEBUG ->> {} ({:?}) Id:{} I:{:?} O:{:?}",
3199 step.node.get_id(),
3200 step.task_type,
3201 step.node_id,
3202 step.input_msg_indices_types,
3203 step.output_msg_index_type
3204 );
3205 let comment_tokens = quote! {{
3206 let _ = stringify!(#comment_str);
3207 }};
3208 let tid = task_index;
3209 let task_enum_name = config_id_to_enum(&task_specs.ids[tid]);
3210 let enum_name = Ident::new(&task_enum_name, Span::call_site());
3211
3212 match step.task_type {
3213 CuTaskType::Source => {
3214 if let Some((output_index, _)) = &step.output_msg_index_type {
3215 let output_culist_index = int2sliceindex(*output_index);
3216
3217 let monitoring_action = quote! {
3218 debug!("Task {}: Error during process: {}", #mission_mod::TASKS_IDS[#tid], &error);
3219 let decision = monitor.process_error(#tid, CuTaskState::Process, &error);
3220 match decision {
3221 Decision::Abort => {
3222 debug!("Process: ABORT decision from monitoring. Task '{}' errored out \
3223 during process. Skipping the processing of CL {}.", #mission_mod::TASKS_IDS[#tid], clid);
3224 monitor.process_copperlist(&#mission_mod::collect_metadata(&culist))?;
3225 cl_manager.end_of_processing(clid)?;
3226 return Ok(());
3227 }
3228 Decision::Ignore => {
3229 debug!("Process: IGNORE decision from monitoring. Task '{}' errored out \
3230 during process. The runtime will continue with a forced empty message.", #mission_mod::TASKS_IDS[#tid]);
3231 let cumsg_output = &mut msgs.#output_culist_index;
3232 cumsg_output.clear_payload();
3233 }
3234 Decision::Shutdown => {
3235 debug!("Process: SHUTDOWN decision from monitoring. Task '{}' errored out \
3236 during process. The runtime cannot continue.", #mission_mod::TASKS_IDS[#tid]);
3237 return Err(CuError::new_with_cause("Task errored out during process.", error));
3238 }
3239 }
3240 };
3241
3242 let call_sim_callback = if sim_mode {
3243 quote! {
3244 let doit = {
3245 let cumsg_output = &mut msgs.#output_culist_index;
3246 let state = CuTaskCallbackState::Process((), cumsg_output);
3247 let ovr = sim_callback(SimStep::#enum_name(state));
3248
3249 if let SimOverride::Errored(reason) = ovr {
3250 let error: CuError = reason.into();
3251 #monitoring_action
3252 false
3253 } else {
3254 ovr == SimOverride::ExecuteByRuntime
3255 }
3256 };
3257 }
3258 } else {
3259 quote! { let doit = true; }
3260 };
3261
3262 let logging_tokens = if !task_specs.logging_enabled[tid] {
3263 let output_culist_index = int2sliceindex(*output_index);
3264 quote! {
3265 let mut cumsg_output = &mut culist.msgs.0.#output_culist_index;
3266 cumsg_output.clear_payload();
3267 }
3268 } else {
3269 quote!()
3270 };
3271
3272 (
3273 quote! {
3274 {
3275 #comment_tokens
3276 kf_manager.freeze_task(clid, &#task_instance)?;
3277 #call_sim_callback
3278 let cumsg_output = &mut msgs.#output_culist_index;
3279 cumsg_output.metadata.process_time.start = clock.now().into();
3280 let maybe_error = if doit { #task_instance.process(clock, cumsg_output) } else { Ok(()) };
3281 cumsg_output.metadata.process_time.end = clock.now().into();
3282 if let Err(error) = maybe_error {
3283 #monitoring_action
3284 }
3285 }
3286 },
3287 logging_tokens,
3288 )
3289 } else {
3290 panic!("Source task should have an output message index.");
3291 }
3292 }
3293 CuTaskType::Sink => {
3294 if let Some((output_index, _)) = &step.output_msg_index_type {
3295 let output_culist_index = int2sliceindex(*output_index);
3296 let indices = step
3297 .input_msg_indices_types
3298 .iter()
3299 .map(|(index, _)| int2sliceindex(*index));
3300
3301 let monitoring_action = quote! {
3302 debug!("Task {}: Error during process: {}", #mission_mod::TASKS_IDS[#tid], &error);
3303 let decision = monitor.process_error(#tid, CuTaskState::Process, &error);
3304 match decision {
3305 Decision::Abort => {
3306 debug!("Process: ABORT decision from monitoring. Task '{}' errored out \
3307 during process. Skipping the processing of CL {}.", #mission_mod::TASKS_IDS[#tid], clid);
3308 monitor.process_copperlist(&#mission_mod::collect_metadata(&culist))?;
3309 cl_manager.end_of_processing(clid)?;
3310 return Ok(());
3311 }
3312 Decision::Ignore => {
3313 debug!("Process: IGNORE decision from monitoring. Task '{}' errored out \
3314 during process. The runtime will continue with a forced empty message.", #mission_mod::TASKS_IDS[#tid]);
3315 let cumsg_output = &mut msgs.#output_culist_index;
3316 cumsg_output.clear_payload();
3317 }
3318 Decision::Shutdown => {
3319 debug!("Process: SHUTDOWN decision from monitoring. Task '{}' errored out \
3320 during process. The runtime cannot continue.", #mission_mod::TASKS_IDS[#tid]);
3321 return Err(CuError::new_with_cause("Task errored out during process.", error));
3322 }
3323 }
3324 };
3325
3326 let inputs_type = if indices.len() == 1 {
3327 quote! { #(msgs.#indices)* }
3328 } else {
3329 quote! { (#(&msgs.#indices),*) }
3330 };
3331
3332 let call_sim_callback = if sim_mode {
3333 quote! {
3334 let doit = {
3335 let cumsg_input = &#inputs_type;
3336 let cumsg_output = &mut msgs.#output_culist_index;
3337 let state = CuTaskCallbackState::Process(cumsg_input, cumsg_output);
3338 let ovr = sim_callback(SimStep::#enum_name(state));
3339
3340 if let SimOverride::Errored(reason) = ovr {
3341 let error: CuError = reason.into();
3342 #monitoring_action
3343 false
3344 } else {
3345 ovr == SimOverride::ExecuteByRuntime
3346 }
3347 };
3348 }
3349 } else {
3350 quote! { let doit = true; }
3351 };
3352
3353 (
3354 quote! {
3355 {
3356 #comment_tokens
3357 kf_manager.freeze_task(clid, &#task_instance)?;
3358 #call_sim_callback
3359 let cumsg_input = &#inputs_type;
3360 let cumsg_output = &mut msgs.#output_culist_index;
3361 cumsg_output.metadata.process_time.start = clock.now().into();
3362 let maybe_error = if doit { #task_instance.process(clock, cumsg_input) } else { Ok(()) };
3363 cumsg_output.metadata.process_time.end = clock.now().into();
3364 if let Err(error) = maybe_error {
3365 #monitoring_action
3366 }
3367 }
3368 },
3369 quote! {},
3370 )
3371 } else {
3372 panic!("Sink tasks should have a virtual output message index.");
3373 }
3374 }
3375 CuTaskType::Regular => {
3376 if let Some((output_index, _)) = &step.output_msg_index_type {
3377 let output_culist_index = int2sliceindex(*output_index);
3378 let indices = step
3379 .input_msg_indices_types
3380 .iter()
3381 .map(|(index, _)| int2sliceindex(*index));
3382
3383 let monitoring_action = quote! {
3384 debug!("Task {}: Error during process: {}", #mission_mod::TASKS_IDS[#tid], &error);
3385 let decision = monitor.process_error(#tid, CuTaskState::Process, &error);
3386 match decision {
3387 Decision::Abort => {
3388 debug!("Process: ABORT decision from monitoring. Task '{}' errored out \
3389 during process. Skipping the processing of CL {}.", #mission_mod::TASKS_IDS[#tid], clid);
3390 monitor.process_copperlist(&#mission_mod::collect_metadata(&culist))?;
3391 cl_manager.end_of_processing(clid)?;
3392 return Ok(());
3393 }
3394 Decision::Ignore => {
3395 debug!("Process: IGNORE decision from monitoring. Task '{}' errored out \
3396 during process. The runtime will continue with a forced empty message.", #mission_mod::TASKS_IDS[#tid]);
3397 let cumsg_output = &mut msgs.#output_culist_index;
3398 cumsg_output.clear_payload();
3399 }
3400 Decision::Shutdown => {
3401 debug!("Process: SHUTDOWN decision from monitoring. Task '{}' errored out \
3402 during process. The runtime cannot continue.", #mission_mod::TASKS_IDS[#tid]);
3403 return Err(CuError::new_with_cause("Task errored out during process.", error));
3404 }
3405 }
3406 };
3407
3408 let inputs_type = if indices.len() == 1 {
3409 quote! { #(msgs.#indices)* }
3410 } else {
3411 quote! { (#(&msgs.#indices),*) }
3412 };
3413
3414 let call_sim_callback = if sim_mode {
3415 quote! {
3416 let doit = {
3417 let cumsg_input = &#inputs_type;
3418 let cumsg_output = &mut msgs.#output_culist_index;
3419 let state = CuTaskCallbackState::Process(cumsg_input, cumsg_output);
3420 let ovr = sim_callback(SimStep::#enum_name(state));
3421
3422 if let SimOverride::Errored(reason) = ovr {
3423 let error: CuError = reason.into();
3424 #monitoring_action
3425 false
3426 }
3427 else {
3428 ovr == SimOverride::ExecuteByRuntime
3429 }
3430 };
3431 }
3432 } else {
3433 quote! { let doit = true; }
3434 };
3435
3436 let logging_tokens = if !task_specs.logging_enabled[tid] {
3437 let output_culist_index = int2sliceindex(*output_index);
3438 quote! {
3439 let mut cumsg_output = &mut culist.msgs.0.#output_culist_index;
3440 cumsg_output.clear_payload();
3441 }
3442 } else {
3443 quote!()
3444 };
3445
3446 (
3447 quote! {
3448 {
3449 #comment_tokens
3450 kf_manager.freeze_task(clid, &#task_instance)?;
3451 #call_sim_callback
3452 let cumsg_input = &#inputs_type;
3453 let cumsg_output = &mut msgs.#output_culist_index;
3454 cumsg_output.metadata.process_time.start = clock.now().into();
3455 let maybe_error = if doit { #task_instance.process(clock, cumsg_input, cumsg_output) } else { Ok(()) };
3456 cumsg_output.metadata.process_time.end = clock.now().into();
3457 if let Err(error) = maybe_error {
3458 #monitoring_action
3459 }
3460 }
3461 },
3462 logging_tokens,
3463 )
3464 } else {
3465 panic!("Regular task should have an output message index.");
3466 }
3467 }
3468 }
3469}
3470
3471fn generate_bridge_rx_execution_tokens(
3472 bridge_spec: &BridgeSpec,
3473 channel_index: usize,
3474 mission_mod: &Ident,
3475) -> (proc_macro2::TokenStream, proc_macro2::TokenStream) {
3476 let bridge_tuple_index = int2sliceindex(bridge_spec.tuple_index as u32);
3477 let channel = &bridge_spec.rx_channels[channel_index];
3478 let culist_index = channel
3479 .culist_index
3480 .unwrap_or_else(|| panic!("Bridge Rx channel missing output index"));
3481 let culist_index_ts = int2sliceindex(culist_index as u32);
3482 let monitor_index = syn::Index::from(
3483 channel
3484 .monitor_index
3485 .expect("Bridge Rx channel missing monitor index"),
3486 );
3487 let bridge_type = &bridge_spec.type_path;
3488 let const_ident = &channel.const_ident;
3489 (
3490 quote! {
3491 {
3492 let bridge = &mut bridges.#bridge_tuple_index;
3493 let cumsg_output = &mut msgs.#culist_index_ts;
3494 cumsg_output.metadata.process_time.start = clock.now().into();
3495 let maybe_error = bridge.receive(
3496 clock,
3497 &<#bridge_type as cu29::cubridge::CuBridge>::Rx::#const_ident,
3498 cumsg_output,
3499 );
3500 cumsg_output.metadata.process_time.end = clock.now().into();
3501 if let Err(error) = maybe_error {
3502 let decision = monitor.process_error(#monitor_index, CuTaskState::Process, &error);
3503 match decision {
3504 Decision::Abort => {
3505 debug!("Process: ABORT decision from monitoring. Task '{}' errored out during process. Skipping the processing of CL {}.", #mission_mod::TASKS_IDS[#monitor_index], clid);
3506 monitor.process_copperlist(&#mission_mod::collect_metadata(&culist))?;
3507 cl_manager.end_of_processing(clid)?;
3508 return Ok(());
3509 }
3510 Decision::Ignore => {
3511 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]);
3512 let cumsg_output = &mut msgs.#culist_index_ts;
3513 cumsg_output.clear_payload();
3514 }
3515 Decision::Shutdown => {
3516 debug!("Process: SHUTDOWN decision from monitoring. Task '{}' errored out during process. The runtime cannot continue.", #mission_mod::TASKS_IDS[#monitor_index]);
3517 return Err(CuError::new_with_cause("Task errored out during process.", error));
3518 }
3519 }
3520 }
3521 }
3522 },
3523 quote! {},
3524 )
3525}
3526
3527fn generate_bridge_tx_execution_tokens(
3528 step: &CuExecutionStep,
3529 bridge_spec: &BridgeSpec,
3530 channel_index: usize,
3531 mission_mod: &Ident,
3532) -> (proc_macro2::TokenStream, proc_macro2::TokenStream) {
3533 let channel = &bridge_spec.tx_channels[channel_index];
3534 let monitor_index = syn::Index::from(
3535 channel
3536 .monitor_index
3537 .expect("Bridge Tx channel missing monitor index"),
3538 );
3539 let input_index = step
3540 .input_msg_indices_types
3541 .first()
3542 .map(|(idx, _)| int2sliceindex(*idx))
3543 .expect("Bridge Tx channel should have exactly one input");
3544 let bridge_tuple_index = int2sliceindex(bridge_spec.tuple_index as u32);
3545 let bridge_type = &bridge_spec.type_path;
3546 let const_ident = &channel.const_ident;
3547 (
3548 quote! {
3549 {
3550 let bridge = &mut bridges.#bridge_tuple_index;
3551 let cumsg_input = &mut msgs.#input_index;
3552 cumsg_input.metadata.process_time.start = clock.now().into();
3554 if let Err(error) = bridge.send(
3555 clock,
3556 &<#bridge_type as cu29::cubridge::CuBridge>::Tx::#const_ident,
3557 &*cumsg_input,
3558 ) {
3559 let decision = monitor.process_error(#monitor_index, CuTaskState::Process, &error);
3560 match decision {
3561 Decision::Abort => {
3562 debug!("Process: ABORT decision from monitoring. Task '{}' errored out during process. Skipping the processing of CL {}.", #mission_mod::TASKS_IDS[#monitor_index], clid);
3563 monitor.process_copperlist(&#mission_mod::collect_metadata(&culist))?;
3564 cl_manager.end_of_processing(clid)?;
3565 return Ok(());
3566 }
3567 Decision::Ignore => {
3568 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]);
3569 }
3570 Decision::Shutdown => {
3571 debug!("Process: SHUTDOWN decision from monitoring. Task '{}' errored out during process. The runtime cannot continue.", #mission_mod::TASKS_IDS[#monitor_index]);
3572 return Err(CuError::new_with_cause("Task errored out during process.", error));
3573 }
3574 }
3575 }
3576 cumsg_input.metadata.process_time.end = clock.now().into();
3577 }
3578 },
3579 quote! {},
3580 )
3581}
3582
3583#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
3584enum BridgeChannelDirection {
3585 Rx,
3586 Tx,
3587}
3588
3589#[derive(Clone, Debug, PartialEq, Eq, Hash)]
3590struct BridgeChannelKey {
3591 bridge_id: String,
3592 channel_id: String,
3593 direction: BridgeChannelDirection,
3594}
3595
3596#[derive(Clone)]
3597struct BridgeChannelSpec {
3598 id: String,
3599 const_ident: Ident,
3600 #[allow(dead_code)]
3601 msg_type: Type,
3602 config_index: usize,
3603 plan_node_id: Option<NodeId>,
3604 culist_index: Option<usize>,
3605 monitor_index: Option<usize>,
3606}
3607
3608#[derive(Clone)]
3609struct BridgeSpec {
3610 id: String,
3611 type_path: Type,
3612 config_index: usize,
3613 tuple_index: usize,
3614 monitor_index: Option<usize>,
3615 rx_channels: Vec<BridgeChannelSpec>,
3616 tx_channels: Vec<BridgeChannelSpec>,
3617}
3618
3619#[derive(Clone)]
3620struct ExecutionEntity {
3621 kind: ExecutionEntityKind,
3622}
3623
3624#[derive(Clone)]
3625enum ExecutionEntityKind {
3626 Task {
3627 task_index: usize,
3628 },
3629 BridgeRx {
3630 bridge_index: usize,
3631 channel_index: usize,
3632 },
3633 BridgeTx {
3634 bridge_index: usize,
3635 channel_index: usize,
3636 },
3637}
3638
3639#[cfg(test)]
3640mod tests {
3641 #[test]
3643 fn test_compile_fail() {
3644 use rustc_version::{Channel, version_meta};
3645 use std::{fs, path::Path};
3646
3647 let dir = Path::new("tests/compile_fail");
3648 for entry in fs::read_dir(dir).unwrap() {
3649 let entry = entry.unwrap();
3650 if !entry.file_type().unwrap().is_dir() {
3651 continue;
3652 }
3653 for file in fs::read_dir(entry.path()).unwrap() {
3654 let file = file.unwrap();
3655 let p = file.path();
3656 if p.extension().and_then(|x| x.to_str()) != Some("rs") {
3657 continue;
3658 }
3659
3660 let base = p.with_extension("stderr"); let src = match version_meta().unwrap().channel {
3662 Channel::Beta => Path::new(&format!("{}.beta", base.display())).to_path_buf(),
3663 _ => Path::new(&format!("{}.stable", base.display())).to_path_buf(),
3664 };
3665
3666 if src.exists() {
3667 fs::copy(src, &base).unwrap();
3668 }
3669 }
3670 }
3671
3672 let t = trybuild::TestCases::new();
3673 t.compile_fail("tests/compile_fail/*/*.rs");
3674 }
3675
3676 #[test]
3677 fn bridge_resources_are_collected() {
3678 use super::*;
3679 use cu29::config::{CuGraph, Flavor, Node};
3680 use std::collections::HashMap;
3681 use syn::parse_str;
3682
3683 let mut graph = CuGraph::default();
3684 let mut node = Node::new_with_flavor("radio", "bridge::Dummy", Flavor::Bridge);
3685 let mut res = HashMap::new();
3686 res.insert("serial".to_string(), "fc.serial0".to_string());
3687 node.set_resources(Some(res));
3688 graph.add_node(node).expect("bridge node");
3689
3690 let task_specs = CuTaskSpecSet::from_graph(&graph);
3691 let bridge_spec = BridgeSpec {
3692 id: "radio".to_string(),
3693 type_path: parse_str("bridge::Dummy").unwrap(),
3694 config_index: 0,
3695 tuple_index: 0,
3696 monitor_index: None,
3697 rx_channels: Vec::new(),
3698 tx_channels: Vec::new(),
3699 };
3700
3701 let mut config = cu29::config::CuConfig::default();
3702 config.resources.push(ResourceBundleConfig {
3703 id: "fc".to_string(),
3704 provider: "board::Bundle".to_string(),
3705 config: None,
3706 missions: None,
3707 });
3708 let bundle_specs = build_bundle_specs(&config, "default").expect("bundle specs");
3709 let specs = collect_resource_specs(&graph, &task_specs, &[bridge_spec], &bundle_specs)
3710 .expect("collect specs");
3711 assert_eq!(specs.len(), 1);
3712 assert!(matches!(specs[0].owner, ResourceOwner::Bridge(0)));
3713 assert_eq!(specs[0].binding_name, "serial");
3714 assert_eq!(specs[0].bundle_index, 0);
3715 assert_eq!(specs[0].resource_name, "serial0");
3716 }
3717}