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
12use crate::utils::{config_id_to_bridge_const, config_id_to_enum, config_id_to_struct_member};
13use cu29_runtime::config::CuConfig;
14use cu29_runtime::config::{
15 BridgeChannelConfigRepresentation, CuGraph, Flavor, Node, NodeId, ResourceBundleConfig,
16 read_configuration,
17};
18use cu29_runtime::curuntime::{
19 CuExecutionLoop, CuExecutionStep, CuExecutionUnit, CuTaskType, compute_runtime_plan,
20 find_task_type_for_id,
21};
22use cu29_traits::{CuError, CuResult};
23use proc_macro2::{Ident, Span};
24
25mod bundle_resources;
26mod resources;
27mod utils;
28
29const DEFAULT_CLNB: usize = 2; #[inline]
32fn int2sliceindex(i: u32) -> syn::Index {
33 syn::Index::from(i as usize)
34}
35
36#[inline(always)]
37fn return_error(msg: String) -> TokenStream {
38 syn::Error::new(Span::call_site(), msg)
39 .to_compile_error()
40 .into()
41}
42
43fn rtsan_guard_tokens() -> proc_macro2::TokenStream {
44 if cfg!(feature = "rtsan") {
45 quote! {
46 let _rt_guard = ::cu29::rtsan::ScopedSanitizeRealtime::default();
47 }
48 } else {
49 quote! {}
50 }
51}
52
53#[proc_macro]
54pub fn resources(input: TokenStream) -> TokenStream {
55 resources::resources(input)
56}
57
58#[proc_macro]
59pub fn bundle_resources(input: TokenStream) -> TokenStream {
60 bundle_resources::bundle_resources(input)
61}
62
63#[proc_macro]
67pub fn gen_cumsgs(config_path_lit: TokenStream) -> TokenStream {
68 #[cfg(feature = "std")]
69 let std = true;
70
71 #[cfg(not(feature = "std"))]
72 let std = false;
73
74 let config = parse_macro_input!(config_path_lit as LitStr).value();
75 if !std::path::Path::new(&config_full_path(&config)).exists() {
76 return return_error(format!(
77 "The configuration file `{config}` does not exist. Please provide a valid path."
78 ));
79 }
80 #[cfg(feature = "macro_debug")]
81 eprintln!("[gen culist support with {config:?}]");
82 let cuconfig = match read_config(&config) {
83 Ok(cuconfig) => cuconfig,
84 Err(e) => return return_error(e.to_string()),
85 };
86 let graph = cuconfig
87 .get_graph(None) .expect("Could not find the specified mission for gen_cumsgs");
89 let task_specs = CuTaskSpecSet::from_graph(graph);
90 let channel_usage = collect_bridge_channel_usage(graph);
91 let mut bridge_specs = build_bridge_specs(&cuconfig, graph, &channel_usage);
92 let (culist_plan, exec_entities, plan_to_original) =
93 match build_execution_plan(graph, &task_specs, &mut bridge_specs) {
94 Ok(plan) => plan,
95 Err(e) => return return_error(format!("Could not compute copperlist plan: {e}")),
96 };
97 let task_member_names = collect_task_member_names(graph);
98 let (culist_order, node_output_positions) = collect_culist_metadata(
99 &culist_plan,
100 &exec_entities,
101 &mut bridge_specs,
102 &plan_to_original,
103 );
104
105 #[cfg(feature = "macro_debug")]
106 eprintln!(
107 "[The CuStampedDataSet matching tasks ids are {:?}]",
108 culist_order
109 );
110
111 let support = gen_culist_support(
112 &culist_plan,
113 &culist_order,
114 &node_output_positions,
115 &task_member_names,
116 &bridge_specs,
117 );
118
119 let extra_imports = if !std {
120 quote! {
121 use core::fmt::Debug;
122 use core::fmt::Formatter;
123 use core::fmt::Result as FmtResult;
124 use alloc::vec;
125 use alloc::vec::Vec;
126 }
127 } else {
128 quote! {
129 use std::fmt::Debug;
130 use std::fmt::Formatter;
131 use std::fmt::Result as FmtResult;
132 }
133 };
134
135 let with_uses = quote! {
136 mod cumsgs {
137 use cu29::bincode::Encode;
138 use cu29::bincode::enc::Encoder;
139 use cu29::bincode::error::EncodeError;
140 use cu29::bincode::Decode;
141 use cu29::bincode::de::Decoder;
142 use cu29::bincode::error::DecodeError;
143 use cu29::copperlist::CopperList;
144 use cu29::prelude::ErasedCuStampedData;
145 use cu29::prelude::ErasedCuStampedDataSet;
146 use cu29::prelude::MatchingTasks;
147 use cu29::prelude::Serialize;
148 use cu29::prelude::CuMsg;
149 use cu29::prelude::CuMsgMetadata;
150 use cu29::prelude::CuListZeroedInit;
151 use cu29::prelude::CuCompactString;
152 #extra_imports
153 #support
154 }
155 use cumsgs::CuStampedDataSet;
156 type CuMsgs=CuStampedDataSet;
157 };
158 with_uses.into()
159}
160
161fn gen_culist_support(
163 runtime_plan: &CuExecutionLoop,
164 culist_indices_in_plan_order: &[usize],
165 node_output_positions: &HashMap<NodeId, usize>,
166 task_member_names: &[(NodeId, String)],
167 bridge_specs: &[BridgeSpec],
168) -> proc_macro2::TokenStream {
169 #[cfg(feature = "macro_debug")]
170 eprintln!("[Extract msgs types]");
171 let output_packs = extract_output_packs(runtime_plan);
172 let slot_types: Vec<Type> = output_packs.iter().map(|pack| pack.slot_type()).collect();
173
174 let culist_size = output_packs.len();
175
176 #[cfg(feature = "macro_debug")]
177 eprintln!("[build the copperlist struct]");
178 let msgs_types_tuple: TypeTuple = build_culist_tuple(&slot_types);
179
180 #[cfg(feature = "macro_debug")]
181 eprintln!("[build the copperlist tuple bincode support]");
182 let msgs_types_tuple_encode = build_culist_tuple_encode(&slot_types);
183 let msgs_types_tuple_decode = build_culist_tuple_decode(&slot_types);
184
185 #[cfg(feature = "macro_debug")]
186 eprintln!("[build the copperlist tuple debug support]");
187 let msgs_types_tuple_debug = build_culist_tuple_debug(&slot_types);
188
189 #[cfg(feature = "macro_debug")]
190 eprintln!("[build the copperlist tuple serialize support]");
191 let msgs_types_tuple_serialize = build_culist_tuple_serialize(&slot_types);
192
193 #[cfg(feature = "macro_debug")]
194 eprintln!("[build the default tuple support]");
195 let msgs_types_tuple_default = build_culist_tuple_default(&slot_types);
196
197 #[cfg(feature = "macro_debug")]
198 eprintln!("[build erasedcumsgs]");
199
200 let erasedmsg_trait_impl = build_culist_erasedcumsgs(&output_packs);
201
202 let metadata_accessors: Vec<proc_macro2::TokenStream> = culist_indices_in_plan_order
203 .iter()
204 .map(|idx| {
205 let slot_index = syn::Index::from(*idx);
206 let pack = output_packs
207 .get(*idx)
208 .unwrap_or_else(|| panic!("Missing output pack for index {idx}"));
209 if pack.is_multi() {
210 quote! { &culist.msgs.0.#slot_index.0.metadata }
211 } else {
212 quote! { &culist.msgs.0.#slot_index.metadata }
213 }
214 })
215 .collect();
216 let mut zeroed_init_tokens: Vec<proc_macro2::TokenStream> = Vec::new();
217 for idx in culist_indices_in_plan_order {
218 let slot_index = syn::Index::from(*idx);
219 let pack = output_packs
220 .get(*idx)
221 .unwrap_or_else(|| panic!("Missing output pack for index {idx}"));
222 if pack.is_multi() {
223 for port_idx in 0..pack.msg_types.len() {
224 let port_index = syn::Index::from(port_idx);
225 zeroed_init_tokens.push(quote! {
226 self.0.#slot_index.#port_index.metadata.status_txt = CuCompactString::default();
227 self.0.#slot_index.#port_index.metadata.process_time.start =
228 cu29::clock::OptionCuTime::none();
229 self.0.#slot_index.#port_index.metadata.process_time.end =
230 cu29::clock::OptionCuTime::none();
231 });
232 }
233 } else {
234 zeroed_init_tokens.push(quote! {
235 self.0.#slot_index.metadata.status_txt = CuCompactString::default();
236 self.0.#slot_index.metadata.process_time.start = cu29::clock::OptionCuTime::none();
237 self.0.#slot_index.metadata.process_time.end = cu29::clock::OptionCuTime::none();
238 });
239 }
240 }
241 let collect_metadata_function = quote! {
242 pub fn collect_metadata<'a>(culist: &'a CuList) -> [&'a CuMsgMetadata; #culist_size] {
243 [#( #metadata_accessors, )*]
244 }
245 };
246
247 let cumsg_count: usize = output_packs.iter().map(|pack| pack.msg_types.len()).sum();
248
249 let payload_bytes_accumulators: Vec<proc_macro2::TokenStream> = culist_indices_in_plan_order
250 .iter()
251 .map(|idx| {
252 let slot_index = syn::Index::from(*idx);
253 let pack = output_packs
254 .get(*idx)
255 .unwrap_or_else(|| panic!("Missing output pack for index {idx}"));
256 if pack.is_multi() {
257 let iter = (0..pack.msg_types.len()).map(|port_idx| {
258 let port_index = syn::Index::from(port_idx);
259 quote! {
260 if let Some(payload) = culist.msgs.0.#slot_index.#port_index.payload() {
261 raw += cu29::monitoring::CuPayloadSize::raw_bytes(payload);
262 handles += cu29::monitoring::CuPayloadSize::handle_bytes(payload);
263 }
264 }
265 });
266 quote! { #(#iter)* }
267 } else {
268 quote! {
269 if let Some(payload) = culist.msgs.0.#slot_index.payload() {
270 raw += cu29::monitoring::CuPayloadSize::raw_bytes(payload);
271 handles += cu29::monitoring::CuPayloadSize::handle_bytes(payload);
272 }
273 }
274 }
275 })
276 .collect();
277
278 let payload_raw_bytes_accumulators: Vec<proc_macro2::TokenStream> = output_packs
279 .iter()
280 .enumerate()
281 .map(|(slot_idx, pack)| {
282 let slot_index = syn::Index::from(slot_idx);
283 if pack.is_multi() {
284 let iter = (0..pack.msg_types.len()).map(|port_idx| {
285 let port_index = syn::Index::from(port_idx);
286 quote! {
287 if let Some(payload) = self.0.#slot_index.#port_index.payload() {
288 bytes.push(Some(
289 cu29::monitoring::CuPayloadSize::raw_bytes(payload) as u64
290 ));
291 } else {
292 bytes.push(None);
293 }
294 }
295 });
296 quote! { #(#iter)* }
297 } else {
298 quote! {
299 if let Some(payload) = self.0.#slot_index.payload() {
300 bytes.push(Some(
301 cu29::monitoring::CuPayloadSize::raw_bytes(payload) as u64
302 ));
303 } else {
304 bytes.push(None);
305 }
306 }
307 }
308 })
309 .collect();
310
311 let compute_payload_bytes_fn = quote! {
312 pub fn compute_payload_bytes(culist: &CuList) -> (u64, u64) {
313 let mut raw: usize = 0;
314 let mut handles: usize = 0;
315 #(#payload_bytes_accumulators)*
316 (raw as u64, handles as u64)
317 }
318 };
319
320 let payload_raw_bytes_impl = quote! {
321 impl ::cu29::CuPayloadRawBytes for CuStampedDataSet {
322 fn payload_raw_bytes(&self) -> Vec<Option<u64>> {
323 let mut bytes: Vec<Option<u64>> = Vec::with_capacity(#cumsg_count);
324 #(#payload_raw_bytes_accumulators)*
325 bytes
326 }
327 }
328 };
329
330 let task_name_literals: Vec<String> = task_member_names
331 .iter()
332 .map(|(_, name)| name.clone())
333 .collect();
334
335 let mut slot_task_names: Vec<Option<String>> = vec![None; output_packs.len()];
336
337 let mut methods = Vec::new();
338 for (node_id, name) in task_member_names {
339 let output_position = node_output_positions
340 .get(node_id)
341 .unwrap_or_else(|| panic!("Task {name} (id: {node_id}) not found in execution order"));
342 let pack = output_packs
343 .get(*output_position)
344 .unwrap_or_else(|| panic!("Missing output pack for task {name}"));
345 let slot_index = syn::Index::from(*output_position);
346 slot_task_names[*output_position] = Some(name.clone());
347
348 if pack.msg_types.len() == 1 {
349 let fn_name = format_ident!("get_{}_output", name);
350 let payload_type = pack.msg_types.first().unwrap();
351 methods.push(quote! {
352 #[allow(dead_code)]
353 pub fn #fn_name(&self) -> &CuMsg<#payload_type> {
354 &self.0.#slot_index
355 }
356 });
357 } else {
358 let outputs_fn = format_ident!("get_{}_outputs", name);
359 let slot_type = pack.slot_type();
360 for (port_idx, payload_type) in pack.msg_types.iter().enumerate() {
361 let fn_name = format_ident!("get_{}_output_{}", name, port_idx);
362 let port_index = syn::Index::from(port_idx);
363 methods.push(quote! {
364 #[allow(dead_code)]
365 pub fn #fn_name(&self) -> &CuMsg<#payload_type> {
366 &self.0.#slot_index.#port_index
367 }
368 });
369 }
370 methods.push(quote! {
371 #[allow(dead_code)]
372 pub fn #outputs_fn(&self) -> &#slot_type {
373 &self.0.#slot_index
374 }
375 });
376 }
377 }
378
379 let mut logviz_blocks = Vec::new();
380 for (slot_idx, pack) in output_packs.iter().enumerate() {
381 if pack.msg_types.is_empty() {
382 continue;
383 }
384 let slot_index = syn::Index::from(slot_idx);
385 let slot_name = slot_task_names.get(slot_idx).and_then(|name| name.as_ref());
386
387 if pack.is_multi() {
388 for (port_idx, _) in pack.msg_types.iter().enumerate() {
389 let port_index = syn::Index::from(port_idx);
390 let path_expr = if let Some(name) = slot_name {
391 let lit = LitStr::new(name, Span::call_site());
392 quote! { format!("{}/{}", #lit, #port_idx) }
393 } else {
394 quote! { format!("slot_{}/{}", #slot_idx, #port_idx) }
395 };
396 logviz_blocks.push(quote! {
397 {
398 let msg = &self.0.#slot_index.#port_index;
399 if let Some(payload) = msg.payload() {
400 ::cu29_logviz::apply_tov(rec, &msg.tov);
401 let path = #path_expr;
402 ::cu29_logviz::log_payload_auto(rec, &path, payload)?;
403 }
404 }
405 });
406 }
407 } else {
408 let path_expr = if let Some(name) = slot_name {
409 let lit = LitStr::new(name, Span::call_site());
410 quote! { #lit.to_string() }
411 } else {
412 quote! { format!("slot_{}", #slot_idx) }
413 };
414 logviz_blocks.push(quote! {
415 {
416 let msg = &self.0.#slot_index;
417 if let Some(payload) = msg.payload() {
418 ::cu29_logviz::apply_tov(rec, &msg.tov);
419 let path = #path_expr;
420 ::cu29_logviz::log_payload_auto(rec, &path, payload)?;
421 }
422 }
423 });
424 }
425 }
426
427 let logviz_impl = if cfg!(feature = "logviz") {
428 quote! {
429 impl ::cu29_logviz::LogvizDataSet for CuStampedDataSet {
430 fn logviz_emit(
431 &self,
432 rec: &::cu29_logviz::RecordingStream,
433 ) -> ::cu29::prelude::CuResult<()> {
434 #(#logviz_blocks)*
435 Ok(())
436 }
437 }
438 }
439 } else {
440 quote! {}
441 };
442 for spec in bridge_specs {
444 for channel in &spec.rx_channels {
445 if let Some(culist_index) = channel.culist_index {
446 let slot_index = syn::Index::from(culist_index);
447 let bridge_name = config_id_to_struct_member(spec.id.as_str());
448 let channel_name = config_id_to_struct_member(channel.id.as_str());
449 let fn_name = format_ident!("get_{}_rx_{}", bridge_name, channel_name);
450 let msg_type = &channel.msg_type;
451
452 methods.push(quote! {
453 #[allow(dead_code)]
454 pub fn #fn_name(&self) -> &CuMsg<#msg_type> {
455 &self.0.#slot_index
456 }
457 });
458 }
459 }
460 }
461
462 quote! {
464 #collect_metadata_function
465 #compute_payload_bytes_fn
466
467 pub struct CuStampedDataSet(pub #msgs_types_tuple);
468
469 pub type CuList = CopperList<CuStampedDataSet>;
470
471 impl CuStampedDataSet {
472 #(#methods)*
473
474 #[allow(dead_code)]
475 fn get_tuple(&self) -> &#msgs_types_tuple {
476 &self.0
477 }
478
479 #[allow(dead_code)]
480 fn get_tuple_mut(&mut self) -> &mut #msgs_types_tuple {
481 &mut self.0
482 }
483 }
484
485 #payload_raw_bytes_impl
486 #logviz_impl
487
488 impl MatchingTasks for CuStampedDataSet {
489 #[allow(dead_code)]
490 fn get_all_task_ids() -> &'static [&'static str] {
491 &[#(#task_name_literals),*]
492 }
493 }
494
495 #msgs_types_tuple_encode
501 #msgs_types_tuple_decode
502
503 #msgs_types_tuple_debug
505
506 #msgs_types_tuple_serialize
508
509 #msgs_types_tuple_default
511
512 #erasedmsg_trait_impl
514
515 impl CuListZeroedInit for CuStampedDataSet {
516 fn init_zeroed(&mut self) {
517 #(#zeroed_init_tokens)*
518 }
519 }
520 }
521}
522
523fn gen_sim_support(
524 runtime_plan: &CuExecutionLoop,
525 exec_entities: &[ExecutionEntity],
526 bridge_specs: &[BridgeSpec],
527) -> proc_macro2::TokenStream {
528 #[cfg(feature = "macro_debug")]
529 eprintln!("[Sim: Build SimEnum]");
530 let plan_enum: Vec<proc_macro2::TokenStream> = runtime_plan
531 .steps
532 .iter()
533 .map(|unit| match unit {
534 CuExecutionUnit::Step(step) => match &exec_entities[step.node_id as usize].kind {
535 ExecutionEntityKind::Task { .. } => {
536 let enum_entry_name = config_id_to_enum(step.node.get_id().as_str());
537 let enum_ident = Ident::new(&enum_entry_name, Span::call_site());
538 let inputs: Vec<Type> = step
539 .input_msg_indices_types
540 .iter()
541 .map(|input| {
542 parse_str::<Type>(format!("CuMsg<{}>", input.msg_type).as_str()).unwrap()
543 })
544 .collect();
545 let output: Option<Type> = step.output_msg_pack.as_ref().map(|pack| {
546 let msg_types: Vec<Type> = pack
547 .msg_types
548 .iter()
549 .map(|msg_type| {
550 parse_str::<Type>(msg_type.as_str()).unwrap_or_else(|_| {
551 panic!("Could not transform {msg_type} into a message Rust type.")
552 })
553 })
554 .collect();
555 build_output_slot_type(&msg_types)
556 });
557 let no_output = parse_str::<Type>("CuMsg<()>").unwrap();
558 let output = output.as_ref().unwrap_or(&no_output);
559
560 let inputs_type = if inputs.is_empty() {
561 quote! { () }
562 } else if inputs.len() == 1 {
563 let input = inputs.first().unwrap();
564 quote! { &'a #input }
565 } else {
566 quote! { &'a (#(&'a #inputs),*) }
567 };
568
569 quote! {
570 #enum_ident(CuTaskCallbackState<#inputs_type, &'a mut #output>)
571 }
572 }
573 ExecutionEntityKind::BridgeRx { bridge_index, channel_index } => {
574 let bridge_spec = &bridge_specs[*bridge_index];
575 let channel = &bridge_spec.rx_channels[*channel_index];
576 let enum_entry_name = config_id_to_enum(&format!("{}_rx_{}", bridge_spec.id, channel.id));
577 let enum_ident = Ident::new(&enum_entry_name, Span::call_site());
578 let channel_type: Type = parse_str::<Type>(channel.msg_type_name.as_str()).unwrap();
579 let bridge_type = &bridge_spec.type_path;
580 let _const_ident = &channel.const_ident;
581 quote! {
582 #enum_ident {
583 channel: &'static cu29::cubridge::BridgeChannel<< <#bridge_type as cu29::cubridge::CuBridge>::Rx as cu29::cubridge::BridgeChannelSet >::Id, #channel_type>,
584 msg: &'a mut CuMsg<#channel_type>,
585 }
586 }
587 }
588 ExecutionEntityKind::BridgeTx { bridge_index, channel_index } => {
589 let bridge_spec = &bridge_specs[*bridge_index];
590 let channel = &bridge_spec.tx_channels[*channel_index];
591 let enum_entry_name = config_id_to_enum(&format!("{}_tx_{}", bridge_spec.id, channel.id));
592 let enum_ident = Ident::new(&enum_entry_name, Span::call_site());
593 let channel_type: Type = parse_str::<Type>(channel.msg_type_name.as_str()).unwrap();
594 let bridge_type = &bridge_spec.type_path;
595 let _const_ident = &channel.const_ident;
596 quote! {
597 #enum_ident {
598 channel: &'static cu29::cubridge::BridgeChannel<< <#bridge_type as cu29::cubridge::CuBridge>::Tx as cu29::cubridge::BridgeChannelSet >::Id, #channel_type>,
599 msg: &'a CuMsg<#channel_type>,
600 }
601 }
602 }
603 },
604 CuExecutionUnit::Loop(_) => {
605 todo!("Needs to be implemented")
606 }
607 })
608 .collect();
609
610 let mut variants = plan_enum;
612
613 for bridge_spec in bridge_specs {
615 let enum_entry_name = config_id_to_enum(&format!("{}_bridge", bridge_spec.id));
616 let enum_ident = Ident::new(&enum_entry_name, Span::call_site());
617 variants.push(quote! {
618 #enum_ident(cu29::simulation::CuBridgeLifecycleState)
619 });
620 }
621
622 variants.push(quote! { __Phantom(core::marker::PhantomData<&'a ()>) });
623 quote! {
624 #[allow(dead_code, unused_lifetimes)]
626 pub enum SimStep<'a> {
627 #(#variants),*
628 }
629 }
630}
631
632#[proc_macro_attribute]
636pub fn copper_runtime(args: TokenStream, input: TokenStream) -> TokenStream {
637 #[cfg(feature = "macro_debug")]
638 eprintln!("[entry]");
639 let mut application_struct = parse_macro_input!(input as ItemStruct);
640
641 let application_name = &application_struct.ident;
642 let builder_name = format_ident!("{}Builder", application_name);
643
644 let mut config_file: Option<LitStr> = None;
645 let mut sim_mode = false;
646
647 #[cfg(feature = "std")]
648 let std = true;
649
650 #[cfg(not(feature = "std"))]
651 let std = false;
652
653 let rt_guard = rtsan_guard_tokens();
654
655 let attribute_config_parser = parser(|meta| {
657 if meta.path.is_ident("config") {
658 config_file = Some(meta.value()?.parse()?);
659 Ok(())
660 } else if meta.path.is_ident("sim_mode") {
661 if meta.input.peek(syn::Token![=]) {
663 meta.input.parse::<syn::Token![=]>()?;
664 let value: syn::LitBool = meta.input.parse()?;
665 sim_mode = value.value();
666 Ok(())
667 } else {
668 sim_mode = true;
670 Ok(())
671 }
672 } else {
673 Err(meta.error("unsupported property"))
674 }
675 });
676
677 #[cfg(feature = "macro_debug")]
678 eprintln!("[parse]");
679 parse_macro_input!(args with attribute_config_parser);
681
682 let config_file = match config_file {
693 Some(file) => file.value(),
694 None => {
695 return return_error(
696 "Expected config file attribute like #[CopperRuntime(config = \"path\")]"
697 .to_string(),
698 );
699 }
700 };
701
702 if !std::path::Path::new(&config_full_path(&config_file)).exists() {
703 return return_error(format!(
704 "The configuration file `{config_file}` does not exist. Please provide a valid path."
705 ));
706 }
707
708 let copper_config = match read_config(&config_file) {
709 Ok(cuconfig) => cuconfig,
710 Err(e) => return return_error(e.to_string()),
711 };
712 let copper_config_content = match read_to_string(config_full_path(config_file.as_str())) {
713 Ok(ok) => ok,
714 Err(e) => {
715 return return_error(format!(
716 "Could not read the config file (should not happen because we just succeeded just before). {e}"
717 ));
718 }
719 };
720
721 #[cfg(feature = "macro_debug")]
722 eprintln!("[build monitor type]");
723 let monitor_type = if let Some(monitor_config) = copper_config.get_monitor_config() {
724 let monitor_type = parse_str::<Type>(monitor_config.get_type())
725 .expect("Could not transform the monitor type name into a Rust type.");
726 quote! { #monitor_type }
727 } else {
728 quote! { NoMonitor }
729 };
730
731 #[cfg(feature = "macro_debug")]
733 eprintln!("[build runtime field]");
734 let runtime_field: Field = if sim_mode {
736 parse_quote! {
737 copper_runtime: cu29::curuntime::CuRuntime<CuSimTasks, CuBridges, CuStampedDataSet, #monitor_type, #DEFAULT_CLNB>
738 }
739 } else {
740 parse_quote! {
741 copper_runtime: cu29::curuntime::CuRuntime<CuTasks, CuBridges, CuStampedDataSet, #monitor_type, #DEFAULT_CLNB>
742 }
743 };
744
745 #[cfg(feature = "macro_debug")]
746 eprintln!("[match struct anonymity]");
747 match &mut application_struct.fields {
748 Named(fields_named) => {
749 fields_named.named.push(runtime_field);
750 }
751 Unnamed(fields_unnamed) => {
752 fields_unnamed.unnamed.push(runtime_field);
753 }
754 Fields::Unit => {
755 panic!(
756 "This struct is a unit struct, it should have named or unnamed fields. use struct Something {{}} and not struct Something;"
757 )
758 }
759 };
760
761 let all_missions = copper_config.graphs.get_all_missions_graphs();
762 let mut all_missions_tokens = Vec::<proc_macro2::TokenStream>::new();
763 for (mission, graph) in &all_missions {
764 let mission_mod = parse_str::<Ident>(mission.as_str())
765 .expect("Could not make an identifier of the mission name");
766
767 #[cfg(feature = "macro_debug")]
768 eprintln!("[extract tasks ids & types]");
769 let task_specs = CuTaskSpecSet::from_graph(graph);
770
771 let culist_channel_usage = collect_bridge_channel_usage(graph);
772 let mut culist_bridge_specs =
773 build_bridge_specs(&copper_config, graph, &culist_channel_usage);
774 let (culist_plan, culist_exec_entities, culist_plan_to_original) =
775 match build_execution_plan(graph, &task_specs, &mut culist_bridge_specs) {
776 Ok(plan) => plan,
777 Err(e) => return return_error(format!("Could not compute copperlist plan: {e}")),
778 };
779 let task_member_names = collect_task_member_names(graph);
780 let (culist_call_order, node_output_positions) = collect_culist_metadata(
781 &culist_plan,
782 &culist_exec_entities,
783 &mut culist_bridge_specs,
784 &culist_plan_to_original,
785 );
786
787 #[cfg(feature = "macro_debug")]
788 {
789 eprintln!("[runtime plan for mission {mission}]");
790 eprintln!("{culist_plan:?}");
791 }
792
793 let culist_support: proc_macro2::TokenStream = gen_culist_support(
794 &culist_plan,
795 &culist_call_order,
796 &node_output_positions,
797 &task_member_names,
798 &culist_bridge_specs,
799 );
800
801 let bundle_specs = match build_bundle_specs(&copper_config, mission.as_str()) {
802 Ok(specs) => specs,
803 Err(e) => return return_error(e.to_string()),
804 };
805 let threadpool_bundle_index = if task_specs.background_flags.iter().any(|&flag| flag) {
806 match bundle_specs
807 .iter()
808 .position(|bundle| bundle.id == "threadpool")
809 {
810 Some(index) => Some(index),
811 None => {
812 return return_error(
813 "Background tasks require the threadpool bundle to be configured"
814 .to_string(),
815 );
816 }
817 }
818 } else {
819 None
820 };
821
822 let resource_specs =
823 match collect_resource_specs(graph, &task_specs, &culist_bridge_specs, &bundle_specs) {
824 Ok(specs) => specs,
825 Err(e) => return return_error(e.to_string()),
826 };
827
828 let (resources_module, resources_instanciator_fn) =
829 match build_resources_module(&bundle_specs) {
830 Ok(tokens) => tokens,
831 Err(e) => return return_error(e.to_string()),
832 };
833 let task_resource_mappings =
834 match build_task_resource_mappings(&resource_specs, &task_specs) {
835 Ok(tokens) => tokens,
836 Err(e) => return return_error(e.to_string()),
837 };
838 let bridge_resource_mappings =
839 build_bridge_resource_mappings(&resource_specs, &culist_bridge_specs);
840
841 let ids = build_monitored_ids(&task_specs.ids, &mut culist_bridge_specs);
842
843 let task_reflect_read_arms: Vec<proc_macro2::TokenStream> = task_specs
844 .ids
845 .iter()
846 .enumerate()
847 .map(|(index, task_id)| {
848 let task_index = syn::Index::from(index);
849 let task_id_lit = LitStr::new(task_id, Span::call_site());
850 quote! {
851 #task_id_lit => Some(&self.copper_runtime.tasks.#task_index as &dyn cu29::reflect::Reflect),
852 }
853 })
854 .collect();
855
856 let task_reflect_write_arms: Vec<proc_macro2::TokenStream> = task_specs
857 .ids
858 .iter()
859 .enumerate()
860 .map(|(index, task_id)| {
861 let task_index = syn::Index::from(index);
862 let task_id_lit = LitStr::new(task_id, Span::call_site());
863 quote! {
864 #task_id_lit => Some(&mut self.copper_runtime.tasks.#task_index as &mut dyn cu29::reflect::Reflect),
865 }
866 })
867 .collect();
868
869 let mut reflect_registry_types: BTreeMap<String, Type> = BTreeMap::new();
870 let mut add_reflect_type = |ty: Type| {
871 let key = quote! { #ty }.to_string();
872 reflect_registry_types.entry(key).or_insert(ty);
873 };
874
875 for task_type in &task_specs.task_types {
876 add_reflect_type(task_type.clone());
877 }
878
879 for bridge_spec in &culist_bridge_specs {
880 add_reflect_type(bridge_spec.type_path.clone());
881 for channel in bridge_spec
882 .rx_channels
883 .iter()
884 .chain(bridge_spec.tx_channels.iter())
885 {
886 add_reflect_type(channel.msg_type.clone());
887 }
888 }
889
890 for output_pack in extract_output_packs(&culist_plan) {
891 for msg_type in output_pack.msg_types {
892 add_reflect_type(msg_type);
893 }
894 }
895
896 let reflect_type_registration_calls: Vec<proc_macro2::TokenStream> = reflect_registry_types
897 .values()
898 .map(|ty| {
899 quote! {
900 registry.register::<#ty>();
901 }
902 })
903 .collect();
904
905 let bridge_types: Vec<Type> = culist_bridge_specs
906 .iter()
907 .map(|spec| spec.type_path.clone())
908 .collect();
909 let bridges_type_tokens: proc_macro2::TokenStream = if bridge_types.is_empty() {
910 quote! { () }
911 } else {
912 let tuple: TypeTuple = parse_quote! { (#(#bridge_types),*,) };
913 quote! { #tuple }
914 };
915
916 let bridge_binding_idents: Vec<Ident> = culist_bridge_specs
917 .iter()
918 .enumerate()
919 .map(|(idx, _)| format_ident!("bridge_{idx}"))
920 .collect();
921
922 let bridge_init_statements: Vec<proc_macro2::TokenStream> = culist_bridge_specs
923 .iter()
924 .enumerate()
925 .map(|(idx, spec)| {
926 let binding_ident = &bridge_binding_idents[idx];
927 let bridge_mapping_ref = bridge_resource_mappings.refs[idx].clone();
928 let bridge_type = &spec.type_path;
929 let bridge_name = spec.id.clone();
930 let config_index = syn::Index::from(spec.config_index);
931 let binding_error = LitStr::new(
932 &format!("Failed to bind resources for bridge '{}'", bridge_name),
933 Span::call_site(),
934 );
935 let tx_configs: Vec<proc_macro2::TokenStream> = spec
936 .tx_channels
937 .iter()
938 .map(|channel| {
939 let const_ident = &channel.const_ident;
940 let channel_name = channel.id.clone();
941 let channel_config_index = syn::Index::from(channel.config_index);
942 quote! {
943 {
944 let (channel_route, channel_config) = match &bridge_cfg.channels[#channel_config_index] {
945 cu29::config::BridgeChannelConfigRepresentation::Tx { route, config, .. } => {
946 (route.clone(), config.clone())
947 }
948 _ => panic!(
949 "Bridge '{}' channel '{}' expected to be Tx",
950 #bridge_name,
951 #channel_name
952 ),
953 };
954 cu29::cubridge::BridgeChannelConfig::from_static(
955 &<#bridge_type as cu29::cubridge::CuBridge>::Tx::#const_ident,
956 channel_route,
957 channel_config,
958 )
959 }
960 }
961 })
962 .collect();
963 let rx_configs: Vec<proc_macro2::TokenStream> = spec
964 .rx_channels
965 .iter()
966 .map(|channel| {
967 let const_ident = &channel.const_ident;
968 let channel_name = channel.id.clone();
969 let channel_config_index = syn::Index::from(channel.config_index);
970 quote! {
971 {
972 let (channel_route, channel_config) = match &bridge_cfg.channels[#channel_config_index] {
973 cu29::config::BridgeChannelConfigRepresentation::Rx { route, config, .. } => {
974 (route.clone(), config.clone())
975 }
976 _ => panic!(
977 "Bridge '{}' channel '{}' expected to be Rx",
978 #bridge_name,
979 #channel_name
980 ),
981 };
982 cu29::cubridge::BridgeChannelConfig::from_static(
983 &<#bridge_type as cu29::cubridge::CuBridge>::Rx::#const_ident,
984 channel_route,
985 channel_config,
986 )
987 }
988 }
989 })
990 .collect();
991 quote! {
992 let #binding_ident = {
993 let bridge_cfg = config
994 .bridges
995 .get(#config_index)
996 .unwrap_or_else(|| panic!("Bridge '{}' missing from configuration", #bridge_name));
997 let bridge_mapping = #bridge_mapping_ref;
998 let bridge_resources = <<#bridge_type as cu29::cubridge::CuBridge>::Resources<'_> as ResourceBindings>::from_bindings(
999 resources,
1000 bridge_mapping,
1001 )
1002 .map_err(|e| cu29::CuError::new_with_cause(#binding_error, e))?;
1003 let tx_channels: &[cu29::cubridge::BridgeChannelConfig<
1004 <<#bridge_type as cu29::cubridge::CuBridge>::Tx as cu29::cubridge::BridgeChannelSet>::Id,
1005 >] = &[#(#tx_configs),*];
1006 let rx_channels: &[cu29::cubridge::BridgeChannelConfig<
1007 <<#bridge_type as cu29::cubridge::CuBridge>::Rx as cu29::cubridge::BridgeChannelSet>::Id,
1008 >] = &[#(#rx_configs),*];
1009 <#bridge_type as cu29::cubridge::CuBridge>::new(
1010 bridge_cfg.config.as_ref(),
1011 tx_channels,
1012 rx_channels,
1013 bridge_resources,
1014 )?
1015 };
1016 }
1017 })
1018 .collect();
1019
1020 let bridges_instanciator = if culist_bridge_specs.is_empty() {
1021 quote! {
1022 pub fn bridges_instanciator(_config: &CuConfig, resources: &mut ResourceManager) -> CuResult<CuBridges> {
1023 let _ = resources;
1024 Ok(())
1025 }
1026 }
1027 } else {
1028 let bridge_bindings = bridge_binding_idents.clone();
1029 quote! {
1030 pub fn bridges_instanciator(config: &CuConfig, resources: &mut ResourceManager) -> CuResult<CuBridges> {
1031 #(#bridge_init_statements)*
1032 Ok((#(#bridge_bindings),*,))
1033 }
1034 }
1035 };
1036
1037 let all_sim_tasks_types: Vec<Type> = task_specs
1038 .ids
1039 .iter()
1040 .zip(&task_specs.cutypes)
1041 .zip(&task_specs.sim_task_types)
1042 .zip(&task_specs.background_flags)
1043 .zip(&task_specs.run_in_sim_flags)
1044 .zip(task_specs.output_types.iter())
1045 .map(|(((((task_id, task_type), sim_type), background), run_in_sim), output_type)| {
1046 match task_type {
1047 CuTaskType::Source => {
1048 if *background {
1049 panic!("CuSrcTask {task_id} cannot be a background task, it should be a regular task.");
1050 }
1051 if *run_in_sim {
1052 sim_type.clone()
1053 } else {
1054 let msg_type = graph
1055 .get_node_output_msg_type(task_id.as_str())
1056 .unwrap_or_else(|| panic!("CuSrcTask {task_id} should have an outgoing connection with a valid output msg type"));
1057 let sim_task_name = format!("CuSimSrcTask<{msg_type}>");
1058 parse_str(sim_task_name.as_str()).unwrap_or_else(|_| panic!("Could not build the placeholder for simulation: {sim_task_name}"))
1059 }
1060 }
1061 CuTaskType::Regular => {
1062 if *background {
1063 if let Some(out_ty) = output_type {
1064 parse_quote!(CuAsyncTask<#sim_type, #out_ty>)
1065 } else {
1066 panic!("{task_id}: If a task is background, it has to have an output");
1067 }
1068 } else {
1069 sim_type.clone()
1071 }
1072 },
1073 CuTaskType::Sink => {
1074 if *background {
1075 panic!("CuSinkTask {task_id} cannot be a background task, it should be a regular task.");
1076 }
1077
1078 if *run_in_sim {
1079 sim_type.clone()
1081 }
1082 else {
1083 let msg_types = graph
1085 .get_node_input_msg_types(task_id.as_str())
1086 .unwrap_or_else(|| panic!("CuSinkTask {task_id} should have an incoming connection with a valid input msg type"));
1087 let msg_type = if msg_types.len() == 1 {
1088 format!("({},)", msg_types[0])
1089 } else {
1090 format!("({})", msg_types.join(", "))
1091 };
1092 let sim_task_name = format!("CuSimSinkTask<{msg_type}>");
1093 parse_str(sim_task_name.as_str()).unwrap_or_else(|_| panic!("Could not build the placeholder for simulation: {sim_task_name}"))
1094 }
1095 }
1096 }
1097 })
1098 .collect();
1099
1100 #[cfg(feature = "macro_debug")]
1101 eprintln!("[build task tuples]");
1102
1103 let task_types = &task_specs.task_types;
1104 let task_types_tuple: TypeTuple = if task_types.is_empty() {
1107 parse_quote! { () }
1108 } else {
1109 parse_quote! { (#(#task_types),*,) }
1110 };
1111
1112 let task_types_tuple_sim: TypeTuple = if all_sim_tasks_types.is_empty() {
1113 parse_quote! { () }
1114 } else {
1115 parse_quote! { (#(#all_sim_tasks_types),*,) }
1116 };
1117
1118 #[cfg(feature = "macro_debug")]
1119 eprintln!("[gen instances]");
1120 let task_sim_instances_init_code = all_sim_tasks_types
1121 .iter()
1122 .enumerate()
1123 .map(|(index, ty)| {
1124 let additional_error_info = format!(
1125 "Failed to get create instance for {}, instance index {}.",
1126 task_specs.type_names[index], index
1127 );
1128 let mapping_ref = task_resource_mappings.refs[index].clone();
1129 let background = task_specs.background_flags[index];
1130 let inner_task_type = &task_specs.sim_task_types[index];
1131 match task_specs.cutypes[index] {
1132 CuTaskType::Source => quote! {
1133 {
1134 let resources = <<#ty as CuSrcTask>::Resources<'_> as ResourceBindings>::from_bindings(
1135 resources,
1136 #mapping_ref,
1137 ).map_err(|e| e.add_cause(#additional_error_info))?;
1138 <#ty as CuSrcTask>::new(all_instances_configs[#index], resources)
1139 .map_err(|e| e.add_cause(#additional_error_info))?
1140 }
1141 },
1142 CuTaskType::Regular => {
1143 if background {
1144 let threadpool_bundle_index = threadpool_bundle_index
1145 .expect("threadpool bundle missing for background tasks");
1146 quote! {
1147 {
1148 let inner_resources = <<#inner_task_type as CuTask>::Resources<'_> as ResourceBindings>::from_bindings(
1149 resources,
1150 #mapping_ref,
1151 ).map_err(|e| e.add_cause(#additional_error_info))?;
1152 let threadpool_key = cu29::resource::ResourceKey::new(
1153 cu29::resource::BundleIndex::new(#threadpool_bundle_index),
1154 <cu29::resource::ThreadPoolBundle as cu29::resource::ResourceBundleDecl>::Id::BgThreads as usize,
1155 );
1156 let threadpool = resources.borrow_shared_arc(threadpool_key)?;
1157 let resources = cu29::cuasynctask::CuAsyncTaskResources {
1158 inner: inner_resources,
1159 threadpool,
1160 };
1161 <#ty as CuTask>::new(all_instances_configs[#index], resources)
1162 .map_err(|e| e.add_cause(#additional_error_info))?
1163 }
1164 }
1165 } else {
1166 quote! {
1167 {
1168 let resources = <<#ty as CuTask>::Resources<'_> as ResourceBindings>::from_bindings(
1169 resources,
1170 #mapping_ref,
1171 ).map_err(|e| e.add_cause(#additional_error_info))?;
1172 <#ty as CuTask>::new(all_instances_configs[#index], resources)
1173 .map_err(|e| e.add_cause(#additional_error_info))?
1174 }
1175 }
1176 }
1177 }
1178 CuTaskType::Sink => quote! {
1179 {
1180 let resources = <<#ty as CuSinkTask>::Resources<'_> as ResourceBindings>::from_bindings(
1181 resources,
1182 #mapping_ref,
1183 ).map_err(|e| e.add_cause(#additional_error_info))?;
1184 <#ty as CuSinkTask>::new(all_instances_configs[#index], resources)
1185 .map_err(|e| e.add_cause(#additional_error_info))?
1186 }
1187 },
1188 }
1189 })
1190 .collect::<Vec<_>>();
1191
1192 let task_instances_init_code = task_specs
1193 .instantiation_types
1194 .iter()
1195 .zip(&task_specs.background_flags)
1196 .enumerate()
1197 .map(|(index, (task_type, background))| {
1198 let additional_error_info = format!(
1199 "Failed to get create instance for {}, instance index {}.",
1200 task_specs.type_names[index], index
1201 );
1202 let mapping_ref = task_resource_mappings.refs[index].clone();
1203 let inner_task_type = &task_specs.sim_task_types[index];
1204 match task_specs.cutypes[index] {
1205 CuTaskType::Source => quote! {
1206 {
1207 let resources = <<#task_type as CuSrcTask>::Resources<'_> as ResourceBindings>::from_bindings(
1208 resources,
1209 #mapping_ref,
1210 ).map_err(|e| e.add_cause(#additional_error_info))?;
1211 <#task_type as CuSrcTask>::new(all_instances_configs[#index], resources)
1212 .map_err(|e| e.add_cause(#additional_error_info))?
1213 }
1214 },
1215 CuTaskType::Regular => {
1216 if *background {
1217 let threadpool_bundle_index = threadpool_bundle_index
1218 .expect("threadpool bundle missing for background tasks");
1219 quote! {
1220 {
1221 let inner_resources = <<#inner_task_type as CuTask>::Resources<'_> as ResourceBindings>::from_bindings(
1222 resources,
1223 #mapping_ref,
1224 ).map_err(|e| e.add_cause(#additional_error_info))?;
1225 let threadpool_key = cu29::resource::ResourceKey::new(
1226 cu29::resource::BundleIndex::new(#threadpool_bundle_index),
1227 <cu29::resource::ThreadPoolBundle as cu29::resource::ResourceBundleDecl>::Id::BgThreads as usize,
1228 );
1229 let threadpool = resources.borrow_shared_arc(threadpool_key)?;
1230 let resources = cu29::cuasynctask::CuAsyncTaskResources {
1231 inner: inner_resources,
1232 threadpool,
1233 };
1234 <#task_type as CuTask>::new(all_instances_configs[#index], resources)
1235 .map_err(|e| e.add_cause(#additional_error_info))?
1236 }
1237 }
1238 } else {
1239 quote! {
1240 {
1241 let resources = <<#task_type as CuTask>::Resources<'_> as ResourceBindings>::from_bindings(
1242 resources,
1243 #mapping_ref,
1244 ).map_err(|e| e.add_cause(#additional_error_info))?;
1245 <#task_type as CuTask>::new(all_instances_configs[#index], resources)
1246 .map_err(|e| e.add_cause(#additional_error_info))?
1247 }
1248 }
1249 }
1250 }
1251 CuTaskType::Sink => quote! {
1252 {
1253 let resources = <<#task_type as CuSinkTask>::Resources<'_> as ResourceBindings>::from_bindings(
1254 resources,
1255 #mapping_ref,
1256 ).map_err(|e| e.add_cause(#additional_error_info))?;
1257 <#task_type as CuSinkTask>::new(all_instances_configs[#index], resources)
1258 .map_err(|e| e.add_cause(#additional_error_info))?
1259 }
1260 },
1261 }
1262 })
1263 .collect::<Vec<_>>();
1264
1265 let (
1268 task_restore_code,
1269 task_start_calls,
1270 task_stop_calls,
1271 task_preprocess_calls,
1272 task_postprocess_calls,
1273 ): (Vec<_>, Vec<_>, Vec<_>, Vec<_>, Vec<_>) = itertools::multiunzip(
1274 (0..task_specs.task_types.len())
1275 .map(|index| {
1276 let task_index = int2sliceindex(index as u32);
1277 let task_tuple_index = syn::Index::from(index);
1278 let task_enum_name = config_id_to_enum(&task_specs.ids[index]);
1279 let enum_name = Ident::new(&task_enum_name, Span::call_site());
1280 (
1281 quote! {
1283 tasks.#task_tuple_index.thaw(&mut decoder).map_err(|e| CuError::from("Failed to thaw").add_cause(&e.to_string()))?
1284 },
1285 { let monitoring_action = quote! {
1287 let decision = self.copper_runtime.monitor.process_error(#index, CuTaskState::Start, &error);
1288 match decision {
1289 Decision::Abort => {
1290 debug!("Start: ABORT decision from monitoring. Task '{}' errored out \
1291 during start. Aborting all the other starts.", #mission_mod::TASKS_IDS[#index]);
1292 return Ok(());
1293
1294 }
1295 Decision::Ignore => {
1296 debug!("Start: IGNORE decision from monitoring. Task '{}' errored out \
1297 during start. The runtime will continue.", #mission_mod::TASKS_IDS[#index]);
1298 }
1299 Decision::Shutdown => {
1300 debug!("Start: SHUTDOWN decision from monitoring. Task '{}' errored out \
1301 during start. The runtime cannot continue.", #mission_mod::TASKS_IDS[#index]);
1302 return Err(CuError::new_with_cause("Task errored out during start.", error));
1303 }
1304 }
1305 };
1306
1307 let call_sim_callback = if sim_mode {
1308 quote! {
1309 let ovr = sim_callback(SimStep::#enum_name(CuTaskCallbackState::Start));
1311
1312 let doit = if let SimOverride::Errored(reason) = ovr {
1313 let error: CuError = reason.into();
1314 #monitoring_action
1315 false
1316 }
1317 else {
1318 ovr == SimOverride::ExecuteByRuntime
1319 };
1320 }
1321 } else {
1322 quote! {
1323 let doit = true; }
1325 };
1326
1327
1328 quote! {
1329 #call_sim_callback
1330 if doit {
1331 let task = &mut self.copper_runtime.tasks.#task_index;
1332 if let Err(error) = task.start(&self.copper_runtime.clock) {
1333 #monitoring_action
1334 }
1335 }
1336 }
1337 },
1338 { let monitoring_action = quote! {
1340 let decision = self.copper_runtime.monitor.process_error(#index, CuTaskState::Stop, &error);
1341 match decision {
1342 Decision::Abort => {
1343 debug!("Stop: ABORT decision from monitoring. Task '{}' errored out \
1344 during stop. Aborting all the other starts.", #mission_mod::TASKS_IDS[#index]);
1345 return Ok(());
1346
1347 }
1348 Decision::Ignore => {
1349 debug!("Stop: IGNORE decision from monitoring. Task '{}' errored out \
1350 during stop. The runtime will continue.", #mission_mod::TASKS_IDS[#index]);
1351 }
1352 Decision::Shutdown => {
1353 debug!("Stop: SHUTDOWN decision from monitoring. Task '{}' errored out \
1354 during stop. The runtime cannot continue.", #mission_mod::TASKS_IDS[#index]);
1355 return Err(CuError::new_with_cause("Task errored out during stop.", error));
1356 }
1357 }
1358 };
1359 let call_sim_callback = if sim_mode {
1360 quote! {
1361 let ovr = sim_callback(SimStep::#enum_name(CuTaskCallbackState::Stop));
1363
1364 let doit = if let SimOverride::Errored(reason) = ovr {
1365 let error: CuError = reason.into();
1366 #monitoring_action
1367 false
1368 }
1369 else {
1370 ovr == SimOverride::ExecuteByRuntime
1371 };
1372 }
1373 } else {
1374 quote! {
1375 let doit = true; }
1377 };
1378 quote! {
1379 #call_sim_callback
1380 if doit {
1381 let task = &mut self.copper_runtime.tasks.#task_index;
1382 if let Err(error) = task.stop(&self.copper_runtime.clock) {
1383 #monitoring_action
1384 }
1385 }
1386 }
1387 },
1388 { let monitoring_action = quote! {
1390 let decision = monitor.process_error(#index, CuTaskState::Preprocess, &error);
1391 match decision {
1392 Decision::Abort => {
1393 debug!("Preprocess: ABORT decision from monitoring. Task '{}' errored out \
1394 during preprocess. Aborting all the other starts.", #mission_mod::TASKS_IDS[#index]);
1395 return Ok(());
1396
1397 }
1398 Decision::Ignore => {
1399 debug!("Preprocess: IGNORE decision from monitoring. Task '{}' errored out \
1400 during preprocess. The runtime will continue.", #mission_mod::TASKS_IDS[#index]);
1401 }
1402 Decision::Shutdown => {
1403 debug!("Preprocess: SHUTDOWN decision from monitoring. Task '{}' errored out \
1404 during preprocess. The runtime cannot continue.", #mission_mod::TASKS_IDS[#index]);
1405 return Err(CuError::new_with_cause("Task errored out during preprocess.", error));
1406 }
1407 }
1408 };
1409 let call_sim_callback = if sim_mode {
1410 quote! {
1411 let ovr = sim_callback(SimStep::#enum_name(CuTaskCallbackState::Preprocess));
1413
1414 let doit = if let SimOverride::Errored(reason) = ovr {
1415 let error: CuError = reason.into();
1416 #monitoring_action
1417 false
1418 } else {
1419 ovr == SimOverride::ExecuteByRuntime
1420 };
1421 }
1422 } else {
1423 quote! {
1424 let doit = true; }
1426 };
1427 quote! {
1428 #call_sim_callback
1429 if doit {
1430 let maybe_error = {
1431 #rt_guard
1432 tasks.#task_index.preprocess(clock)
1433 };
1434 if let Err(error) = maybe_error {
1435 #monitoring_action
1436 }
1437 }
1438 }
1439 },
1440 { let monitoring_action = quote! {
1442 let decision = monitor.process_error(#index, CuTaskState::Postprocess, &error);
1443 match decision {
1444 Decision::Abort => {
1445 debug!("Postprocess: ABORT decision from monitoring. Task '{}' errored out \
1446 during postprocess. Aborting all the other starts.", #mission_mod::TASKS_IDS[#index]);
1447 return Ok(());
1448
1449 }
1450 Decision::Ignore => {
1451 debug!("Postprocess: IGNORE decision from monitoring. Task '{}' errored out \
1452 during postprocess. The runtime will continue.", #mission_mod::TASKS_IDS[#index]);
1453 }
1454 Decision::Shutdown => {
1455 debug!("Postprocess: SHUTDOWN decision from monitoring. Task '{}' errored out \
1456 during postprocess. The runtime cannot continue.", #mission_mod::TASKS_IDS[#index]);
1457 return Err(CuError::new_with_cause("Task errored out during postprocess.", error));
1458 }
1459 }
1460 };
1461 let call_sim_callback = if sim_mode {
1462 quote! {
1463 let ovr = sim_callback(SimStep::#enum_name(CuTaskCallbackState::Postprocess));
1465
1466 let doit = if let SimOverride::Errored(reason) = ovr {
1467 let error: CuError = reason.into();
1468 #monitoring_action
1469 false
1470 } else {
1471 ovr == SimOverride::ExecuteByRuntime
1472 };
1473 }
1474 } else {
1475 quote! {
1476 let doit = true; }
1478 };
1479 quote! {
1480 #call_sim_callback
1481 if doit {
1482 let maybe_error = {
1483 #rt_guard
1484 tasks.#task_index.postprocess(clock)
1485 };
1486 if let Err(error) = maybe_error {
1487 #monitoring_action
1488 }
1489 }
1490 }
1491 }
1492 )
1493 })
1494 );
1495
1496 let bridge_start_calls: Vec<proc_macro2::TokenStream> = culist_bridge_specs
1497 .iter()
1498 .map(|spec| {
1499 let bridge_index = int2sliceindex(spec.tuple_index as u32);
1500 let monitor_index = syn::Index::from(
1501 spec.monitor_index
1502 .expect("Bridge missing monitor index for start"),
1503 );
1504 let enum_ident = Ident::new(
1505 &config_id_to_enum(&format!("{}_bridge", spec.id)),
1506 Span::call_site(),
1507 );
1508 let call_sim = if sim_mode {
1509 quote! {
1510 let doit = {
1511 let state = SimStep::#enum_ident(cu29::simulation::CuBridgeLifecycleState::Start);
1512 let ovr = sim_callback(state);
1513 if let SimOverride::Errored(reason) = ovr {
1514 let error: CuError = reason.into();
1515 let decision = self.copper_runtime.monitor.process_error(#monitor_index, CuTaskState::Start, &error);
1516 match decision {
1517 Decision::Abort => { debug!("Start: ABORT decision from monitoring. Task '{}' errored out during start. Aborting all the other starts.", #mission_mod::TASKS_IDS[#monitor_index]); return Ok(()); }
1518 Decision::Ignore => { debug!("Start: IGNORE decision from monitoring. Task '{}' errored out during start. The runtime will continue.", #mission_mod::TASKS_IDS[#monitor_index]); false }
1519 Decision::Shutdown => { debug!("Start: SHUTDOWN decision from monitoring. Task '{}' errored out during start. The runtime cannot continue.", #mission_mod::TASKS_IDS[#monitor_index]); return Err(CuError::new_with_cause("Task errored out during start.", error)); }
1520 }
1521 } else {
1522 ovr == SimOverride::ExecuteByRuntime
1523 }
1524 };
1525 }
1526 } else {
1527 quote! { let doit = true; }
1528 };
1529 quote! {
1530 {
1531 #call_sim
1532 if !doit { return Ok(()); }
1533 let bridge = &mut self.copper_runtime.bridges.#bridge_index;
1534 if let Err(error) = bridge.start(&self.copper_runtime.clock) {
1535 let decision = self.copper_runtime.monitor.process_error(#monitor_index, CuTaskState::Start, &error);
1536 match decision {
1537 Decision::Abort => {
1538 debug!("Start: ABORT decision from monitoring. Task '{}' errored out during start. Aborting all the other starts.", #mission_mod::TASKS_IDS[#monitor_index]);
1539 return Ok(());
1540 }
1541 Decision::Ignore => {
1542 debug!("Start: IGNORE decision from monitoring. Task '{}' errored out during start. The runtime will continue.", #mission_mod::TASKS_IDS[#monitor_index]);
1543 }
1544 Decision::Shutdown => {
1545 debug!("Start: SHUTDOWN decision from monitoring. Task '{}' errored out during start. The runtime cannot continue.", #mission_mod::TASKS_IDS[#monitor_index]);
1546 return Err(CuError::new_with_cause("Task errored out during start.", error));
1547 }
1548 }
1549 }
1550 }
1551 }
1552 })
1553 .collect();
1554
1555 let bridge_stop_calls: Vec<proc_macro2::TokenStream> = culist_bridge_specs
1556 .iter()
1557 .map(|spec| {
1558 let bridge_index = int2sliceindex(spec.tuple_index as u32);
1559 let monitor_index = syn::Index::from(
1560 spec.monitor_index
1561 .expect("Bridge missing monitor index for stop"),
1562 );
1563 let enum_ident = Ident::new(
1564 &config_id_to_enum(&format!("{}_bridge", spec.id)),
1565 Span::call_site(),
1566 );
1567 let call_sim = if sim_mode {
1568 quote! {
1569 let doit = {
1570 let state = SimStep::#enum_ident(cu29::simulation::CuBridgeLifecycleState::Stop);
1571 let ovr = sim_callback(state);
1572 if let SimOverride::Errored(reason) = ovr {
1573 let error: CuError = reason.into();
1574 let decision = self.copper_runtime.monitor.process_error(#monitor_index, CuTaskState::Stop, &error);
1575 match decision {
1576 Decision::Abort => { debug!("Stop: ABORT decision from monitoring. Task '{}' errored out during stop. Aborting all the other stops.", #mission_mod::TASKS_IDS[#monitor_index]); return Ok(()); }
1577 Decision::Ignore => { debug!("Stop: IGNORE decision from monitoring. Task '{}' errored out during stop. The runtime will continue.", #mission_mod::TASKS_IDS[#monitor_index]); false }
1578 Decision::Shutdown => { debug!("Stop: SHUTDOWN decision from monitoring. Task '{}' errored out during stop. The runtime cannot continue.", #mission_mod::TASKS_IDS[#monitor_index]); return Err(CuError::new_with_cause("Task errored out during stop.", error)); }
1579 }
1580 } else {
1581 ovr == SimOverride::ExecuteByRuntime
1582 }
1583 };
1584 }
1585 } else {
1586 quote! { let doit = true; }
1587 };
1588 quote! {
1589 {
1590 #call_sim
1591 if !doit { return Ok(()); }
1592 let bridge = &mut self.copper_runtime.bridges.#bridge_index;
1593 if let Err(error) = bridge.stop(&self.copper_runtime.clock) {
1594 let decision = self.copper_runtime.monitor.process_error(#monitor_index, CuTaskState::Stop, &error);
1595 match decision {
1596 Decision::Abort => {
1597 debug!("Stop: ABORT decision from monitoring. Task '{}' errored out during stop. Aborting all the other stops.", #mission_mod::TASKS_IDS[#monitor_index]);
1598 return Ok(());
1599 }
1600 Decision::Ignore => {
1601 debug!("Stop: IGNORE decision from monitoring. Task '{}' errored out during stop. The runtime will continue.", #mission_mod::TASKS_IDS[#monitor_index]);
1602 }
1603 Decision::Shutdown => {
1604 debug!("Stop: SHUTDOWN decision from monitoring. Task '{}' errored out during stop. The runtime cannot continue.", #mission_mod::TASKS_IDS[#monitor_index]);
1605 return Err(CuError::new_with_cause("Task errored out during stop.", error));
1606 }
1607 }
1608 }
1609 }
1610 }
1611 })
1612 .collect();
1613
1614 let bridge_preprocess_calls: Vec<proc_macro2::TokenStream> = culist_bridge_specs
1615 .iter()
1616 .map(|spec| {
1617 let bridge_index = int2sliceindex(spec.tuple_index as u32);
1618 let monitor_index = syn::Index::from(
1619 spec.monitor_index
1620 .expect("Bridge missing monitor index for preprocess"),
1621 );
1622 let enum_ident = Ident::new(
1623 &config_id_to_enum(&format!("{}_bridge", spec.id)),
1624 Span::call_site(),
1625 );
1626 let call_sim = if sim_mode {
1627 quote! {
1628 let doit = {
1629 let state = SimStep::#enum_ident(cu29::simulation::CuBridgeLifecycleState::Preprocess);
1630 let ovr = sim_callback(state);
1631 if let SimOverride::Errored(reason) = ovr {
1632 let error: CuError = reason.into();
1633 let decision = monitor.process_error(#monitor_index, CuTaskState::Preprocess, &error);
1634 match decision {
1635 Decision::Abort => { debug!("Preprocess: ABORT decision from monitoring. Task '{}' errored out during preprocess. Aborting all the other starts.", #mission_mod::TASKS_IDS[#monitor_index]); return Ok(()); }
1636 Decision::Ignore => { debug!("Preprocess: IGNORE decision from monitoring. Task '{}' errored out during preprocess. The runtime will continue.", #mission_mod::TASKS_IDS[#monitor_index]); false }
1637 Decision::Shutdown => { debug!("Preprocess: SHUTDOWN decision from monitoring. Task '{}' errored out during preprocess. The runtime cannot continue.", #mission_mod::TASKS_IDS[#monitor_index]); return Err(CuError::new_with_cause("Task errored out during preprocess.", error)); }
1638 }
1639 } else {
1640 ovr == SimOverride::ExecuteByRuntime
1641 }
1642 };
1643 }
1644 } else {
1645 quote! { let doit = true; }
1646 };
1647 quote! {
1648 {
1649 #call_sim
1650 if doit {
1651 let bridge = &mut __cu_bridges.#bridge_index;
1652 let maybe_error = {
1653 #rt_guard
1654 bridge.preprocess(clock)
1655 };
1656 if let Err(error) = maybe_error {
1657 let decision = monitor.process_error(#monitor_index, CuTaskState::Preprocess, &error);
1658 match decision {
1659 Decision::Abort => {
1660 debug!("Preprocess: ABORT decision from monitoring. Task '{}' errored out during preprocess. Aborting all the other starts.", #mission_mod::TASKS_IDS[#monitor_index]);
1661 return Ok(());
1662 }
1663 Decision::Ignore => {
1664 debug!("Preprocess: IGNORE decision from monitoring. Task '{}' errored out during preprocess. The runtime will continue.", #mission_mod::TASKS_IDS[#monitor_index]);
1665 }
1666 Decision::Shutdown => {
1667 debug!("Preprocess: SHUTDOWN decision from monitoring. Task '{}' errored out during preprocess. The runtime cannot continue.", #mission_mod::TASKS_IDS[#monitor_index]);
1668 return Err(CuError::new_with_cause("Task errored out during preprocess.", error));
1669 }
1670 }
1671 }
1672 }
1673 }
1674 }
1675 })
1676 .collect();
1677
1678 let bridge_postprocess_calls: Vec<proc_macro2::TokenStream> = culist_bridge_specs
1679 .iter()
1680 .map(|spec| {
1681 let bridge_index = int2sliceindex(spec.tuple_index as u32);
1682 let monitor_index = syn::Index::from(
1683 spec.monitor_index
1684 .expect("Bridge missing monitor index for postprocess"),
1685 );
1686 let enum_ident = Ident::new(
1687 &config_id_to_enum(&format!("{}_bridge", spec.id)),
1688 Span::call_site(),
1689 );
1690 let call_sim = if sim_mode {
1691 quote! {
1692 let doit = {
1693 let state = SimStep::#enum_ident(cu29::simulation::CuBridgeLifecycleState::Postprocess);
1694 let ovr = sim_callback(state);
1695 if let SimOverride::Errored(reason) = ovr {
1696 let error: CuError = reason.into();
1697 let decision = monitor.process_error(#monitor_index, CuTaskState::Postprocess, &error);
1698 match decision {
1699 Decision::Abort => { debug!("Postprocess: ABORT decision from monitoring. Task '{}' errored out during postprocess. Aborting all the other starts.", #mission_mod::TASKS_IDS[#monitor_index]); return Ok(()); }
1700 Decision::Ignore => { debug!("Postprocess: IGNORE decision from monitoring. Task '{}' errored out during postprocess. The runtime will continue.", #mission_mod::TASKS_IDS[#monitor_index]); false }
1701 Decision::Shutdown => { debug!("Postprocess: SHUTDOWN decision from monitoring. Task '{}' errored out during postprocess. The runtime cannot continue.", #mission_mod::TASKS_IDS[#monitor_index]); return Err(CuError::new_with_cause("Task errored out during postprocess.", error)); }
1702 }
1703 } else {
1704 ovr == SimOverride::ExecuteByRuntime
1705 }
1706 };
1707 }
1708 } else {
1709 quote! { let doit = true; }
1710 };
1711 quote! {
1712 {
1713 #call_sim
1714 if doit {
1715 let bridge = &mut __cu_bridges.#bridge_index;
1716 kf_manager.freeze_any(clid, bridge)?;
1717 let maybe_error = {
1718 #rt_guard
1719 bridge.postprocess(clock)
1720 };
1721 if let Err(error) = maybe_error {
1722 let decision = monitor.process_error(#monitor_index, CuTaskState::Postprocess, &error);
1723 match decision {
1724 Decision::Abort => {
1725 debug!("Postprocess: ABORT decision from monitoring. Task '{}' errored out during postprocess. Aborting all the other starts.", #mission_mod::TASKS_IDS[#monitor_index]);
1726 return Ok(());
1727 }
1728 Decision::Ignore => {
1729 debug!("Postprocess: IGNORE decision from monitoring. Task '{}' errored out during postprocess. The runtime will continue.", #mission_mod::TASKS_IDS[#monitor_index]);
1730 }
1731 Decision::Shutdown => {
1732 debug!("Postprocess: SHUTDOWN decision from monitoring. Task '{}' errored out during postprocess. The runtime cannot continue.", #mission_mod::TASKS_IDS[#monitor_index]);
1733 return Err(CuError::new_with_cause("Task errored out during postprocess.", error));
1734 }
1735 }
1736 }
1737 }
1738 }
1739 }
1740 })
1741 .collect();
1742
1743 let mut start_calls = bridge_start_calls;
1744 start_calls.extend(task_start_calls);
1745 let mut stop_calls = task_stop_calls;
1746 stop_calls.extend(bridge_stop_calls);
1747 let mut preprocess_calls = bridge_preprocess_calls;
1748 preprocess_calls.extend(task_preprocess_calls);
1749 let mut postprocess_calls = task_postprocess_calls;
1750 postprocess_calls.extend(bridge_postprocess_calls);
1751
1752 let bridge_restore_code: Vec<proc_macro2::TokenStream> = culist_bridge_specs
1754 .iter()
1755 .enumerate()
1756 .map(|(index, _)| {
1757 let bridge_tuple_index = syn::Index::from(index);
1758 quote! {
1759 __cu_bridges.#bridge_tuple_index
1760 .thaw(&mut decoder)
1761 .map_err(|e| CuError::from("Failed to thaw bridge").add_cause(&e.to_string()))?
1762 }
1763 })
1764 .collect();
1765
1766 let output_pack_sizes = collect_output_pack_sizes(&culist_plan);
1767 let runtime_plan_code_and_logging: Vec<(
1768 proc_macro2::TokenStream,
1769 proc_macro2::TokenStream,
1770 )> = culist_plan
1771 .steps
1772 .iter()
1773 .map(|unit| match unit {
1774 CuExecutionUnit::Step(step) => {
1775 #[cfg(feature = "macro_debug")]
1776 eprintln!(
1777 "{} -> {} as {:?}. task_id: {} Input={:?}, Output={:?}",
1778 step.node.get_id(),
1779 step.node.get_type(),
1780 step.task_type,
1781 step.node_id,
1782 step.input_msg_indices_types,
1783 step.output_msg_pack
1784 );
1785
1786 match &culist_exec_entities[step.node_id as usize].kind {
1787 ExecutionEntityKind::Task { task_index } => generate_task_execution_tokens(
1788 step,
1789 *task_index,
1790 &task_specs,
1791 &output_pack_sizes,
1792 sim_mode,
1793 &mission_mod,
1794 ),
1795 ExecutionEntityKind::BridgeRx {
1796 bridge_index,
1797 channel_index,
1798 } => {
1799 let spec = &culist_bridge_specs[*bridge_index];
1800 generate_bridge_rx_execution_tokens(
1801 step,
1802 spec,
1803 *channel_index,
1804 &mission_mod,
1805 sim_mode,
1806 )
1807 }
1808 ExecutionEntityKind::BridgeTx {
1809 bridge_index,
1810 channel_index,
1811 } => {
1812 let spec = &culist_bridge_specs[*bridge_index];
1813 generate_bridge_tx_execution_tokens(
1814 step,
1815 spec,
1816 *channel_index,
1817 &output_pack_sizes,
1818 &mission_mod,
1819 sim_mode,
1820 )
1821 }
1822 }
1823 }
1824 CuExecutionUnit::Loop(_) => {
1825 panic!("Execution loops are not supported in runtime generation");
1826 }
1827 })
1828 .collect();
1829
1830 let sim_support = if sim_mode {
1831 Some(gen_sim_support(
1832 &culist_plan,
1833 &culist_exec_entities,
1834 &culist_bridge_specs,
1835 ))
1836 } else {
1837 None
1838 };
1839
1840 let (new, run_one_iteration, start_all_tasks, stop_all_tasks, run) = if sim_mode {
1841 (
1842 quote! {
1843 fn new(clock:RobotClock, unified_logger: Arc<Mutex<L>>, config_override: Option<CuConfig>, sim_callback: &mut impl FnMut(SimStep) -> SimOverride) -> CuResult<Self>
1844 },
1845 quote! {
1846 fn run_one_iteration(&mut self, sim_callback: &mut impl FnMut(SimStep) -> SimOverride) -> CuResult<()>
1847 },
1848 quote! {
1849 fn start_all_tasks(&mut self, sim_callback: &mut impl FnMut(SimStep) -> SimOverride) -> CuResult<()>
1850 },
1851 quote! {
1852 fn stop_all_tasks(&mut self, sim_callback: &mut impl FnMut(SimStep) -> SimOverride) -> CuResult<()>
1853 },
1854 quote! {
1855 fn run(&mut self, sim_callback: &mut impl FnMut(SimStep) -> SimOverride) -> CuResult<()>
1856 },
1857 )
1858 } else {
1859 (
1860 if std {
1861 quote! {
1862 fn new(clock:RobotClock, unified_logger: Arc<Mutex<L>>, config_override: Option<CuConfig>) -> CuResult<Self>
1863 }
1864 } else {
1865 quote! {
1866 fn new(clock:RobotClock, unified_logger: Arc<Mutex<L>>) -> CuResult<Self>
1868 }
1869 },
1870 quote! {
1871 fn run_one_iteration(&mut self) -> CuResult<()>
1872 },
1873 quote! {
1874 fn start_all_tasks(&mut self) -> CuResult<()>
1875 },
1876 quote! {
1877 fn stop_all_tasks(&mut self) -> CuResult<()>
1878 },
1879 quote! {
1880 fn run(&mut self) -> CuResult<()>
1881 },
1882 )
1883 };
1884
1885 let sim_callback_arg = if sim_mode {
1886 Some(quote!(sim_callback))
1887 } else {
1888 None
1889 };
1890
1891 let app_trait = if sim_mode {
1892 quote!(CuSimApplication)
1893 } else {
1894 quote!(CuApplication)
1895 };
1896
1897 let sim_callback_on_new_calls = task_specs.ids.iter().enumerate().map(|(i, id)| {
1898 let enum_name = config_id_to_enum(id);
1899 let enum_ident = Ident::new(&enum_name, Span::call_site());
1900 quote! {
1901 sim_callback(SimStep::#enum_ident(CuTaskCallbackState::New(all_instances_configs[#i].cloned())));
1903 }
1904 });
1905
1906 let sim_callback_on_new_bridges = culist_bridge_specs.iter().map(|spec| {
1907 let enum_ident = Ident::new(
1908 &config_id_to_enum(&format!("{}_bridge", spec.id)),
1909 Span::call_site(),
1910 );
1911 let cfg_index = syn::Index::from(spec.config_index);
1912 quote! {
1913 sim_callback(SimStep::#enum_ident(
1914 cu29::simulation::CuBridgeLifecycleState::New(config.bridges[#cfg_index].config.clone())
1915 ));
1916 }
1917 });
1918
1919 let sim_callback_on_new = if sim_mode {
1920 Some(quote! {
1921 let graph = config.get_graph(Some(#mission)).expect("Could not find the mission #mission");
1922 let all_instances_configs: Vec<Option<&ComponentConfig>> = graph
1923 .get_all_nodes()
1924 .iter()
1925 .map(|(_, node)| node.get_instance_config())
1926 .collect();
1927 #(#sim_callback_on_new_calls)*
1928 #(#sim_callback_on_new_bridges)*
1929 })
1930 } else {
1931 None
1932 };
1933
1934 let (runtime_plan_code, preprocess_logging_calls): (Vec<_>, Vec<_>) =
1935 itertools::multiunzip(runtime_plan_code_and_logging);
1936
1937 let config_load_stmt = if std {
1938 quote! {
1939 let config = if let Some(overridden_config) = config_override {
1940 let serialized = overridden_config
1941 .serialize_ron()
1942 .unwrap_or_else(|err| format!("<failed to serialize config: {err}>"));
1943 debug!("CuConfig: Overridden programmatically: {}", serialized);
1944 overridden_config
1945 } else if ::std::path::Path::new(config_filename).exists() {
1946 debug!("CuConfig: Reading configuration from file: {}", config_filename);
1947 cu29::config::read_configuration(config_filename)?
1948 } else {
1949 let original_config = Self::original_config();
1950 debug!("CuConfig: Using the original configuration the project was compiled with: {}", &original_config);
1951 cu29::config::read_configuration_str(original_config, None)?
1952 };
1953 }
1954 } else {
1955 quote! {
1956 let original_config = Self::original_config();
1958 debug!("CuConfig: Using the original configuration the project was compiled with: {}", &original_config);
1959 let config = cu29::config::read_configuration_str(original_config, None)?;
1960 }
1961 };
1962
1963 let init_resources_sig = if std {
1964 quote! {
1965 pub fn init_resources(config_override: Option<CuConfig>) -> CuResult<AppResources>
1966 }
1967 } else {
1968 quote! {
1969 pub fn init_resources() -> CuResult<AppResources>
1970 }
1971 };
1972
1973 let init_resources_call = if std {
1974 quote! { Self::init_resources(config_override)? }
1975 } else {
1976 quote! { Self::init_resources()? }
1977 };
1978
1979 let new_with_resources_sig = if sim_mode {
1980 quote! {
1981 pub fn new_with_resources<S: SectionStorage + 'static, L: UnifiedLogWrite<S> + 'static>(
1982 clock: RobotClock,
1983 unified_logger: Arc<Mutex<L>>,
1984 app_resources: AppResources,
1985 sim_callback: &mut impl FnMut(SimStep) -> SimOverride,
1986 ) -> CuResult<Self>
1987 }
1988 } else {
1989 quote! {
1990 pub fn new_with_resources<S: SectionStorage + 'static, L: UnifiedLogWrite<S> + 'static>(
1991 clock: RobotClock,
1992 unified_logger: Arc<Mutex<L>>,
1993 app_resources: AppResources,
1994 ) -> CuResult<Self>
1995 }
1996 };
1997
1998 let new_with_resources_call = if sim_mode {
1999 quote! { Self::new_with_resources(clock, unified_logger, app_resources, sim_callback) }
2000 } else {
2001 quote! { Self::new_with_resources(clock, unified_logger, app_resources) }
2002 };
2003
2004 let kill_handler = if std {
2005 Some(quote! {
2006 ctrlc::set_handler(move || {
2007 STOP_FLAG.store(true, Ordering::SeqCst);
2008 }).expect("Error setting Ctrl-C handler");
2009 })
2010 } else {
2011 None
2012 };
2013
2014 let run_loop = if std {
2015 quote! {
2016 loop {
2017 let iter_start = self.copper_runtime.clock.now();
2018 let result = <Self as #app_trait<S, L>>::run_one_iteration(self, #sim_callback_arg);
2019
2020 if let Some(rate) = self.copper_runtime.runtime_config.rate_target_hz {
2021 let period: CuDuration = (1_000_000_000u64 / rate).into();
2022 let elapsed = self.copper_runtime.clock.now() - iter_start;
2023 if elapsed < period {
2024 std::thread::sleep(std::time::Duration::from_nanos(period.as_nanos() - elapsed.as_nanos()));
2025 }
2026 }
2027
2028 if STOP_FLAG.load(Ordering::SeqCst) || result.is_err() {
2029 break result;
2030 }
2031 }
2032 }
2033 } else {
2034 quote! {
2035 loop {
2036 let iter_start = self.copper_runtime.clock.now();
2037 let result = <Self as #app_trait<S, L>>::run_one_iteration(self, #sim_callback_arg);
2038 if let Some(rate) = self.copper_runtime.runtime_config.rate_target_hz {
2039 let period: CuDuration = (1_000_000_000u64 / rate).into();
2040 let elapsed = self.copper_runtime.clock.now() - iter_start;
2041 if elapsed < period {
2042 busy_wait_for(period - elapsed);
2043 }
2044 }
2045
2046 if STOP_FLAG.load(Ordering::SeqCst) || result.is_err() {
2047 break result;
2048 }
2049 }
2050 }
2051 };
2052
2053 #[cfg(feature = "macro_debug")]
2054 eprintln!("[build the run methods]");
2055 let run_methods: proc_macro2::TokenStream = quote! {
2056
2057 #run_one_iteration {
2058
2059 let runtime = &mut self.copper_runtime;
2061 let clock = &runtime.clock;
2062 let monitor = &mut runtime.monitor;
2063 let tasks = &mut runtime.tasks;
2064 let __cu_bridges = &mut runtime.bridges;
2065 let cl_manager = &mut runtime.copperlists_manager;
2066 let kf_manager = &mut runtime.keyframes_manager;
2067
2068 #(#preprocess_calls)*
2070
2071 let culist = cl_manager.inner.create().expect("Ran out of space for copper lists"); let clid = culist.id;
2073 kf_manager.reset(clid, clock); culist.change_state(cu29::copperlist::CopperListState::Processing);
2075 culist.msgs.init_zeroed();
2076 {
2077 let msgs = &mut culist.msgs.0;
2078 #(#runtime_plan_code)*
2079 } let (raw_payload_bytes, handle_bytes) = #mission_mod::compute_payload_bytes(&culist);
2081 let monitor_result = monitor.process_copperlist(&#mission_mod::collect_metadata(&culist));
2082
2083 #(#preprocess_logging_calls)*
2085
2086 cl_manager.end_of_processing(clid)?;
2087 kf_manager.end_of_processing(clid)?;
2088 monitor_result?;
2089 let stats = cu29::monitoring::CopperListIoStats {
2090 raw_culist_bytes: core::mem::size_of::<CuList>() as u64 + raw_payload_bytes,
2091 handle_bytes,
2092 encoded_culist_bytes: cl_manager.last_encoded_bytes,
2093 keyframe_bytes: kf_manager.last_encoded_bytes,
2094 structured_log_bytes_total: ::cu29::prelude::structured_log_bytes_total(),
2095 culistid: clid,
2096 };
2097 monitor.observe_copperlist_io(stats);
2098
2099 #(#postprocess_calls)*
2101 Ok(())
2102 }
2103
2104 fn restore_keyframe(&mut self, keyframe: &KeyFrame) -> CuResult<()> {
2105 let runtime = &mut self.copper_runtime;
2106 let clock = &runtime.clock;
2107 let tasks = &mut runtime.tasks;
2108 let __cu_bridges = &mut runtime.bridges;
2109 let config = cu29::bincode::config::standard();
2110 let reader = cu29::bincode::de::read::SliceReader::new(&keyframe.serialized_tasks);
2111 let mut decoder = DecoderImpl::new(reader, config, ());
2112 #(#task_restore_code);*;
2113 #(#bridge_restore_code);*;
2114 Ok(())
2115 }
2116
2117 #start_all_tasks {
2118 #(#start_calls)*
2119 self.copper_runtime.monitor.start(&self.copper_runtime.clock)?;
2120 Ok(())
2121 }
2122
2123 #stop_all_tasks {
2124 #(#stop_calls)*
2125 self.copper_runtime.monitor.stop(&self.copper_runtime.clock)?;
2126 Ok(())
2127 }
2128
2129 #run {
2130 static STOP_FLAG: AtomicBool = AtomicBool::new(false);
2131
2132 #kill_handler
2133
2134 <Self as #app_trait<S, L>>::start_all_tasks(self, #sim_callback_arg)?;
2135 let result = #run_loop;
2136
2137 if result.is_err() {
2138 error!("A task errored out: {}", &result);
2139 }
2140 <Self as #app_trait<S, L>>::stop_all_tasks(self, #sim_callback_arg)?;
2141 result
2142 }
2143 };
2144
2145 let tasks_type = if sim_mode {
2146 quote!(CuSimTasks)
2147 } else {
2148 quote!(CuTasks)
2149 };
2150
2151 let tasks_instanciator_fn = if sim_mode {
2152 quote!(tasks_instanciator_sim)
2153 } else {
2154 quote!(tasks_instanciator)
2155 };
2156
2157 let app_impl_decl = if sim_mode {
2158 quote!(impl<S: SectionStorage + 'static, L: UnifiedLogWrite<S> + 'static> CuSimApplication<S, L> for #application_name)
2159 } else {
2160 quote!(impl<S: SectionStorage + 'static, L: UnifiedLogWrite<S> + 'static> CuApplication<S, L> for #application_name)
2161 };
2162
2163 let simstep_type_decl = if sim_mode {
2164 quote!(
2165 type Step<'z> = SimStep<'z>;
2166 )
2167 } else {
2168 quote!()
2169 };
2170
2171 let app_resources_struct = quote! {
2172 pub struct AppResources {
2173 pub config: CuConfig,
2174 pub resources: ResourceManager,
2175 }
2176 };
2177
2178 let init_resources_fn = quote! {
2179 #init_resources_sig {
2180 let config_filename = #config_file;
2181
2182 #[cfg(target_os = "none")]
2183 ::cu29::prelude::info!("CuApp init: config file {}", config_filename);
2184 #[cfg(target_os = "none")]
2185 ::cu29::prelude::info!("CuApp init: loading config");
2186 #config_load_stmt
2187 #[cfg(target_os = "none")]
2188 ::cu29::prelude::info!("CuApp init: config loaded");
2189 if let Some(runtime) = &config.runtime {
2190 #[cfg(target_os = "none")]
2191 ::cu29::prelude::info!(
2192 "CuApp init: rate_target_hz={}",
2193 runtime.rate_target_hz.unwrap_or(0)
2194 );
2195 } else {
2196 #[cfg(target_os = "none")]
2197 ::cu29::prelude::info!("CuApp init: rate_target_hz=none");
2198 }
2199
2200 #[cfg(target_os = "none")]
2201 ::cu29::prelude::info!("CuApp init: building resources");
2202 let resources = #mission_mod::resources_instanciator(&config)?;
2203 #[cfg(target_os = "none")]
2204 ::cu29::prelude::info!("CuApp init: resources ready");
2205
2206 Ok(AppResources { config, resources })
2207 }
2208 };
2209
2210 let new_with_resources_fn = quote! {
2211 #new_with_resources_sig {
2212 let AppResources { config, resources } = app_resources;
2213
2214 #[cfg(target_os = "none")]
2215 {
2216 let structured_stream = ::cu29::prelude::stream_write::<
2217 ::cu29::prelude::CuLogEntry,
2218 S,
2219 >(
2220 unified_logger.clone(),
2221 ::cu29::prelude::UnifiedLogType::StructuredLogLine,
2222 4096 * 10,
2223 )?;
2224 let _logger_runtime = ::cu29::prelude::LoggerRuntime::init(
2225 clock.clone(),
2226 structured_stream,
2227 None::<::cu29::prelude::NullLog>,
2228 );
2229 }
2230
2231 let mut default_section_size = size_of::<super::#mission_mod::CuList>() * 64;
2234 if let Some(section_size_mib) = config.logging.as_ref().and_then(|l| l.section_size_mib) {
2236 default_section_size = section_size_mib as usize * 1024usize * 1024usize;
2238 }
2239 #[cfg(target_os = "none")]
2240 ::cu29::prelude::info!(
2241 "CuApp new: copperlist section size={}",
2242 default_section_size
2243 );
2244 #[cfg(target_os = "none")]
2245 ::cu29::prelude::info!("CuApp new: creating copperlist stream");
2246 let copperlist_stream = stream_write::<#mission_mod::CuList, S>(
2247 unified_logger.clone(),
2248 UnifiedLogType::CopperList,
2249 default_section_size,
2250 )?;
2254 #[cfg(target_os = "none")]
2255 ::cu29::prelude::info!("CuApp new: copperlist stream ready");
2256
2257 #[cfg(target_os = "none")]
2258 ::cu29::prelude::info!("CuApp new: creating keyframes stream");
2259 let keyframes_stream = stream_write::<KeyFrame, S>(
2260 unified_logger.clone(),
2261 UnifiedLogType::FrozenTasks,
2262 1024 * 1024 * 10, )?;
2264 #[cfg(target_os = "none")]
2265 ::cu29::prelude::info!("CuApp new: keyframes stream ready");
2266
2267 #[cfg(target_os = "none")]
2268 ::cu29::prelude::info!("CuApp new: building runtime");
2269 let copper_runtime = CuRuntime::<#mission_mod::#tasks_type, #mission_mod::CuBridges, #mission_mod::CuStampedDataSet, #monitor_type, #DEFAULT_CLNB>::new_with_resources(
2270 clock,
2271 &config,
2272 Some(#mission),
2273 resources,
2274 #mission_mod::#tasks_instanciator_fn,
2275 #mission_mod::monitor_instanciator,
2276 #mission_mod::bridges_instanciator,
2277 copperlist_stream,
2278 keyframes_stream)?;
2279 #[cfg(target_os = "none")]
2280 ::cu29::prelude::info!("CuApp new: runtime built");
2281
2282 let application = Ok(#application_name { copper_runtime });
2283
2284 #sim_callback_on_new
2285
2286 application
2287 }
2288 };
2289
2290 let app_inherent_impl = quote! {
2291 impl #application_name {
2292 pub fn original_config() -> String {
2293 #copper_config_content.to_string()
2294 }
2295
2296 pub fn register_reflect_types(registry: &mut cu29::reflect::TypeRegistry) {
2297 #(#reflect_type_registration_calls)*
2298 }
2299
2300 #init_resources_fn
2301
2302 #new_with_resources_fn
2303
2304 #[inline]
2306 pub fn copper_runtime_mut(&mut self) -> &mut CuRuntime<#mission_mod::#tasks_type, #mission_mod::CuBridges, #mission_mod::CuStampedDataSet, #monitor_type, #DEFAULT_CLNB> {
2307 &mut self.copper_runtime
2308 }
2309 }
2310 };
2311
2312 let app_reflect_impl = quote! {
2313 impl cu29::reflect::ReflectTaskIntrospection for #application_name {
2314 fn reflect_task(&self, task_id: &str) -> Option<&dyn cu29::reflect::Reflect> {
2315 match task_id {
2316 #(#task_reflect_read_arms)*
2317 _ => None,
2318 }
2319 }
2320
2321 fn reflect_task_mut(
2322 &mut self,
2323 task_id: &str,
2324 ) -> Option<&mut dyn cu29::reflect::Reflect> {
2325 match task_id {
2326 #(#task_reflect_write_arms)*
2327 _ => None,
2328 }
2329 }
2330
2331 fn register_reflect_types(registry: &mut cu29::reflect::TypeRegistry) {
2332 #application_name::register_reflect_types(registry);
2333 }
2334 }
2335 };
2336
2337 #[cfg(feature = "std")]
2338 #[cfg(feature = "macro_debug")]
2339 eprintln!("[build result]");
2340 let application_impl = quote! {
2341 #app_impl_decl {
2342 #simstep_type_decl
2343
2344 #new {
2345 let app_resources = #init_resources_call;
2346 #new_with_resources_call
2347 }
2348
2349 fn get_original_config() -> String {
2350 Self::original_config()
2351 }
2352
2353 #run_methods
2354 }
2355 };
2356
2357 let (
2358 builder_struct,
2359 builder_new,
2360 builder_impl,
2361 builder_sim_callback_method,
2362 builder_build_sim_callback_arg,
2363 ) = if sim_mode {
2364 (
2365 quote! {
2366 #[allow(dead_code)]
2367 pub struct #builder_name <'a, F> {
2368 clock: Option<RobotClock>,
2369 unified_logger: Option<Arc<Mutex<UnifiedLoggerWrite>>>,
2370 config_override: Option<CuConfig>,
2371 sim_callback: Option<&'a mut F>
2372 }
2373 },
2374 quote! {
2375 #[allow(dead_code)]
2376 pub fn new() -> Self {
2377 Self {
2378 clock: None,
2379 unified_logger: None,
2380 config_override: None,
2381 sim_callback: None,
2382 }
2383 }
2384 },
2385 quote! {
2386 impl<'a, F> #builder_name <'a, F>
2387 where
2388 F: FnMut(SimStep) -> SimOverride,
2389 },
2390 Some(quote! {
2391 pub fn with_sim_callback(mut self, sim_callback: &'a mut F) -> Self
2392 {
2393 self.sim_callback = Some(sim_callback);
2394 self
2395 }
2396 }),
2397 Some(quote! {
2398 self.sim_callback
2399 .ok_or(CuError::from("Sim callback missing from builder"))?,
2400 }),
2401 )
2402 } else {
2403 (
2404 quote! {
2405 #[allow(dead_code)]
2406 pub struct #builder_name {
2407 clock: Option<RobotClock>,
2408 unified_logger: Option<Arc<Mutex<UnifiedLoggerWrite>>>,
2409 config_override: Option<CuConfig>,
2410 }
2411 },
2412 quote! {
2413 #[allow(dead_code)]
2414 pub fn new() -> Self {
2415 Self {
2416 clock: None,
2417 unified_logger: None,
2418 config_override: None,
2419 }
2420 }
2421 },
2422 quote! {
2423 impl #builder_name
2424 },
2425 None,
2426 None,
2427 )
2428 };
2429
2430 let std_application_impl = if sim_mode {
2432 Some(quote! {
2434 impl #application_name {
2435 pub fn start_all_tasks(&mut self, sim_callback: &mut impl FnMut(SimStep) -> SimOverride) -> CuResult<()> {
2436 <Self as #app_trait<MmapSectionStorage, UnifiedLoggerWrite>>::start_all_tasks(self, sim_callback)
2437 }
2438 pub fn run_one_iteration(&mut self, sim_callback: &mut impl FnMut(SimStep) -> SimOverride) -> CuResult<()> {
2439 <Self as #app_trait<MmapSectionStorage, UnifiedLoggerWrite>>::run_one_iteration(self, sim_callback)
2440 }
2441 pub fn run(&mut self, sim_callback: &mut impl FnMut(SimStep) -> SimOverride) -> CuResult<()> {
2442 <Self as #app_trait<MmapSectionStorage, UnifiedLoggerWrite>>::run(self, sim_callback)
2443 }
2444 pub fn stop_all_tasks(&mut self, sim_callback: &mut impl FnMut(SimStep) -> SimOverride) -> CuResult<()> {
2445 <Self as #app_trait<MmapSectionStorage, UnifiedLoggerWrite>>::stop_all_tasks(self, sim_callback)
2446 }
2447 }
2448 })
2449 } else if std {
2450 Some(quote! {
2452 impl #application_name {
2453 pub fn start_all_tasks(&mut self) -> CuResult<()> {
2454 <Self as #app_trait<MmapSectionStorage, UnifiedLoggerWrite>>::start_all_tasks(self)
2455 }
2456 pub fn run_one_iteration(&mut self) -> CuResult<()> {
2457 <Self as #app_trait<MmapSectionStorage, UnifiedLoggerWrite>>::run_one_iteration(self)
2458 }
2459 pub fn run(&mut self) -> CuResult<()> {
2460 <Self as #app_trait<MmapSectionStorage, UnifiedLoggerWrite>>::run(self)
2461 }
2462 pub fn stop_all_tasks(&mut self) -> CuResult<()> {
2463 <Self as #app_trait<MmapSectionStorage, UnifiedLoggerWrite>>::stop_all_tasks(self)
2464 }
2465 }
2466 })
2467 } else {
2468 None };
2470
2471 let application_builder = if std {
2472 Some(quote! {
2473 #builder_struct
2474
2475 #builder_impl
2476 {
2477 #builder_new
2478
2479 #[allow(dead_code)]
2480 pub fn with_clock(mut self, clock: RobotClock) -> Self {
2481 self.clock = Some(clock);
2482 self
2483 }
2484
2485 #[allow(dead_code)]
2486 pub fn with_unified_logger(mut self, unified_logger: Arc<Mutex<UnifiedLoggerWrite>>) -> Self {
2487 self.unified_logger = Some(unified_logger);
2488 self
2489 }
2490
2491 #[allow(dead_code)]
2492 pub fn with_context(mut self, copper_ctx: &CopperContext) -> Self {
2493 self.clock = Some(copper_ctx.clock.clone());
2494 self.unified_logger = Some(copper_ctx.unified_logger.clone());
2495 self
2496 }
2497
2498 #[allow(dead_code)]
2499 pub fn with_config(mut self, config_override: CuConfig) -> Self {
2500 self.config_override = Some(config_override);
2501 self
2502 }
2503
2504 #builder_sim_callback_method
2505
2506 #[allow(dead_code)]
2507 pub fn build(self) -> CuResult<#application_name> {
2508 #application_name::new(
2509 self.clock
2510 .ok_or(CuError::from("Clock missing from builder"))?,
2511 self.unified_logger
2512 .ok_or(CuError::from("Unified logger missing from builder"))?,
2513 self.config_override,
2514 #builder_build_sim_callback_arg
2515 )
2516 }
2517 }
2518 })
2519 } else {
2520 None
2522 };
2523
2524 let sim_imports = if sim_mode {
2525 Some(quote! {
2526 use cu29::simulation::SimOverride;
2527 use cu29::simulation::CuTaskCallbackState;
2528 use cu29::simulation::CuSimSrcTask;
2529 use cu29::simulation::CuSimSinkTask;
2530 use cu29::prelude::app::CuSimApplication;
2531 use cu29::cubridge::BridgeChannelSet;
2532 })
2533 } else {
2534 None
2535 };
2536
2537 let sim_tasks = if sim_mode {
2538 Some(quote! {
2539 pub type CuSimTasks = #task_types_tuple_sim;
2542 })
2543 } else {
2544 None
2545 };
2546
2547 let sim_inst_body = if task_sim_instances_init_code.is_empty() {
2548 quote! {
2549 let _ = resources;
2550 Ok(())
2551 }
2552 } else {
2553 quote! { Ok(( #(#task_sim_instances_init_code),*, )) }
2554 };
2555
2556 let sim_tasks_instanciator = if sim_mode {
2557 Some(quote! {
2558 pub fn tasks_instanciator_sim(
2559 all_instances_configs: Vec<Option<&ComponentConfig>>,
2560 resources: &mut ResourceManager,
2561 ) -> CuResult<CuSimTasks> {
2562 #sim_inst_body
2563 }})
2564 } else {
2565 None
2566 };
2567
2568 let tasks_inst_body_std = if task_instances_init_code.is_empty() {
2569 quote! {
2570 let _ = resources;
2571 Ok(())
2572 }
2573 } else {
2574 quote! { Ok(( #(#task_instances_init_code),*, )) }
2575 };
2576
2577 let tasks_inst_body_nostd = if task_instances_init_code.is_empty() {
2578 quote! {
2579 let _ = resources;
2580 Ok(())
2581 }
2582 } else {
2583 quote! { Ok(( #(#task_instances_init_code),*, )) }
2584 };
2585
2586 let tasks_instanciator = if std {
2587 quote! {
2588 pub fn tasks_instanciator<'c>(
2589 all_instances_configs: Vec<Option<&'c ComponentConfig>>,
2590 resources: &mut ResourceManager,
2591 ) -> CuResult<CuTasks> {
2592 #tasks_inst_body_std
2593 }
2594 }
2595 } else {
2596 quote! {
2598 pub fn tasks_instanciator<'c>(
2599 all_instances_configs: Vec<Option<&'c ComponentConfig>>,
2600 resources: &mut ResourceManager,
2601 ) -> CuResult<CuTasks> {
2602 #tasks_inst_body_nostd
2603 }
2604 }
2605 };
2606
2607 let imports = if std {
2608 quote! {
2609 use cu29::rayon::ThreadPool;
2610 use cu29::cuasynctask::CuAsyncTask;
2611 use cu29::curuntime::CopperContext;
2612 use cu29::resource::{ResourceBindings, ResourceManager};
2613 use cu29::prelude::SectionStorage;
2614 use cu29::prelude::UnifiedLoggerWrite;
2615 use cu29::prelude::memmap::MmapSectionStorage;
2616 use std::fmt::{Debug, Formatter};
2617 use std::fmt::Result as FmtResult;
2618 use std::mem::size_of;
2619 use std::sync::Arc;
2620 use std::sync::atomic::{AtomicBool, Ordering};
2621 use std::sync::Mutex;
2622 }
2623 } else {
2624 quote! {
2625 use alloc::sync::Arc;
2626 use alloc::string::String;
2627 use alloc::string::ToString;
2628 use core::sync::atomic::{AtomicBool, Ordering};
2629 use core::fmt::{Debug, Formatter};
2630 use core::fmt::Result as FmtResult;
2631 use core::mem::size_of;
2632 use spin::Mutex;
2633 use cu29::prelude::SectionStorage;
2634 use cu29::resource::{ResourceBindings, ResourceManager};
2635 }
2636 };
2637
2638 let task_mapping_defs = task_resource_mappings.defs.clone();
2639 let bridge_mapping_defs = bridge_resource_mappings.defs.clone();
2640
2641 let mission_mod_tokens = quote! {
2643 mod #mission_mod {
2644 use super::*; use cu29::bincode::Encode;
2647 use cu29::bincode::enc::Encoder;
2648 use cu29::bincode::error::EncodeError;
2649 use cu29::bincode::Decode;
2650 use cu29::bincode::de::Decoder;
2651 use cu29::bincode::de::DecoderImpl;
2652 use cu29::bincode::error::DecodeError;
2653 use cu29::clock::RobotClock;
2654 use cu29::config::CuConfig;
2655 use cu29::config::ComponentConfig;
2656 use cu29::curuntime::CuRuntime;
2657 use cu29::curuntime::KeyFrame;
2658 use cu29::CuResult;
2659 use cu29::CuError;
2660 use cu29::cutask::CuSrcTask;
2661 use cu29::cutask::CuSinkTask;
2662 use cu29::cutask::CuTask;
2663 use cu29::cutask::CuMsg;
2664 use cu29::cutask::CuMsgMetadata;
2665 use cu29::copperlist::CopperList;
2666 use cu29::monitoring::CuMonitor; use cu29::monitoring::CuTaskState;
2668 use cu29::monitoring::Decision;
2669 use cu29::prelude::app::CuApplication;
2670 use cu29::prelude::debug;
2671 use cu29::prelude::stream_write;
2672 use cu29::prelude::UnifiedLogType;
2673 use cu29::prelude::UnifiedLogWrite;
2674
2675 #imports
2676
2677 #sim_imports
2678
2679 #[allow(unused_imports)]
2681 use cu29::monitoring::NoMonitor;
2682
2683 pub type CuTasks = #task_types_tuple;
2687 pub type CuBridges = #bridges_type_tokens;
2688 #resources_module
2689 #resources_instanciator_fn
2690 #task_mapping_defs
2691 #bridge_mapping_defs
2692
2693 #sim_tasks
2694 #sim_support
2695 #sim_tasks_instanciator
2696
2697 pub const TASKS_IDS: &'static [&'static str] = &[#( #ids ),*];
2698
2699 #culist_support
2700 #tasks_instanciator
2701 #bridges_instanciator
2702
2703 pub fn monitor_instanciator(config: &CuConfig) -> #monitor_type {
2704 let mut monitor = #monitor_type::new(config, #mission_mod::TASKS_IDS)
2705 .expect("Failed to create the given monitor.");
2706 let copperlist_info = ::cu29::monitoring::CopperListInfo::new(
2707 core::mem::size_of::<CuList>(),
2708 #DEFAULT_CLNB,
2709 );
2710 monitor.set_copperlist_info(copperlist_info);
2711 monitor
2712 }
2713
2714 #app_resources_struct
2716 pub #application_struct
2717
2718 #app_inherent_impl
2719 #app_reflect_impl
2720 #application_impl
2721
2722 #std_application_impl
2723
2724 #application_builder
2725 }
2726
2727 };
2728 all_missions_tokens.push(mission_mod_tokens);
2729 }
2730
2731 let default_application_tokens = if all_missions.contains_key("default") {
2732 let default_builder = if std {
2733 Some(quote! {
2734 #[allow(unused_imports)]
2736 use default::#builder_name;
2737 })
2738 } else {
2739 None
2740 };
2741 quote! {
2742 #default_builder
2743
2744 #[allow(unused_imports)]
2745 use default::AppResources;
2746
2747 #[allow(unused_imports)]
2748 use default::resources as app_resources;
2749
2750 #[allow(unused_imports)]
2751 use default::#application_name;
2752 }
2753 } else {
2754 quote!() };
2756
2757 let result: proc_macro2::TokenStream = quote! {
2758 #(#all_missions_tokens)*
2759 #default_application_tokens
2760 };
2761
2762 result.into()
2763}
2764
2765fn read_config(config_file: &str) -> CuResult<CuConfig> {
2766 let filename = config_full_path(config_file);
2767
2768 read_configuration(filename.as_str())
2769}
2770
2771fn config_full_path(config_file: &str) -> String {
2772 let mut config_full_path = utils::caller_crate_root();
2773 config_full_path.push(config_file);
2774 let filename = config_full_path
2775 .as_os_str()
2776 .to_str()
2777 .expect("Could not interpret the config file name");
2778 filename.to_string()
2779}
2780
2781fn extract_tasks_output_types(graph: &CuGraph) -> Vec<Option<Type>> {
2782 graph
2783 .get_all_nodes()
2784 .iter()
2785 .map(|(_, node)| {
2786 let id = node.get_id();
2787 let type_str = graph.get_node_output_msg_type(id.as_str());
2788 type_str.map(|type_str| {
2789 parse_str::<Type>(type_str.as_str()).expect("Could not parse output message type.")
2790 })
2791 })
2792 .collect()
2793}
2794
2795struct CuTaskSpecSet {
2796 pub ids: Vec<String>,
2797 pub cutypes: Vec<CuTaskType>,
2798 pub background_flags: Vec<bool>,
2799 pub logging_enabled: Vec<bool>,
2800 pub type_names: Vec<String>,
2801 pub task_types: Vec<Type>,
2802 pub instantiation_types: Vec<Type>,
2803 pub sim_task_types: Vec<Type>,
2804 pub run_in_sim_flags: Vec<bool>,
2805 #[allow(dead_code)]
2806 pub output_types: Vec<Option<Type>>,
2807 pub node_id_to_task_index: Vec<Option<usize>>,
2808}
2809
2810impl CuTaskSpecSet {
2811 pub fn from_graph(graph: &CuGraph) -> Self {
2812 let all_id_nodes: Vec<(NodeId, &Node)> = graph
2813 .get_all_nodes()
2814 .into_iter()
2815 .filter(|(_, node)| node.get_flavor() == Flavor::Task)
2816 .collect();
2817
2818 let ids = all_id_nodes
2819 .iter()
2820 .map(|(_, node)| node.get_id().to_string())
2821 .collect();
2822
2823 let cutypes = all_id_nodes
2824 .iter()
2825 .map(|(id, _)| find_task_type_for_id(graph, *id))
2826 .collect();
2827
2828 let background_flags: Vec<bool> = all_id_nodes
2829 .iter()
2830 .map(|(_, node)| node.is_background())
2831 .collect();
2832
2833 let logging_enabled: Vec<bool> = all_id_nodes
2834 .iter()
2835 .map(|(_, node)| node.is_logging_enabled())
2836 .collect();
2837
2838 let type_names: Vec<String> = all_id_nodes
2839 .iter()
2840 .map(|(_, node)| node.get_type().to_string())
2841 .collect();
2842
2843 let output_types = extract_tasks_output_types(graph);
2844
2845 let task_types = type_names
2846 .iter()
2847 .zip(background_flags.iter())
2848 .zip(output_types.iter())
2849 .map(|((name, &background), output_type)| {
2850 let name_type = parse_str::<Type>(name).unwrap_or_else(|error| {
2851 panic!("Could not transform {name} into a Task Rust type: {error}");
2852 });
2853 if background {
2854 if let Some(output_type) = output_type {
2855 parse_quote!(CuAsyncTask<#name_type, #output_type>)
2856 } else {
2857 panic!("{name}: If a task is background, it has to have an output");
2858 }
2859 } else {
2860 name_type
2861 }
2862 })
2863 .collect();
2864
2865 let instantiation_types = type_names
2866 .iter()
2867 .zip(background_flags.iter())
2868 .zip(output_types.iter())
2869 .map(|((name, &background), output_type)| {
2870 let name_type = parse_str::<Type>(name).unwrap_or_else(|error| {
2871 panic!("Could not transform {name} into a Task Rust type: {error}");
2872 });
2873 if background {
2874 if let Some(output_type) = output_type {
2875 parse_quote!(CuAsyncTask::<#name_type, #output_type>)
2876 } else {
2877 panic!("{name}: If a task is background, it has to have an output");
2878 }
2879 } else {
2880 name_type
2881 }
2882 })
2883 .collect();
2884
2885 let sim_task_types = type_names
2886 .iter()
2887 .map(|name| {
2888 parse_str::<Type>(name).unwrap_or_else(|err| {
2889 eprintln!("Could not transform {name} into a Task Rust type.");
2890 panic!("{err}")
2891 })
2892 })
2893 .collect();
2894
2895 let run_in_sim_flags = all_id_nodes
2896 .iter()
2897 .map(|(_, node)| node.is_run_in_sim())
2898 .collect();
2899
2900 let mut node_id_to_task_index = vec![None; graph.node_count()];
2901 for (index, (node_id, _)) in all_id_nodes.iter().enumerate() {
2902 node_id_to_task_index[*node_id as usize] = Some(index);
2903 }
2904
2905 Self {
2906 ids,
2907 cutypes,
2908 background_flags,
2909 logging_enabled,
2910 type_names,
2911 task_types,
2912 instantiation_types,
2913 sim_task_types,
2914 run_in_sim_flags,
2915 output_types,
2916 node_id_to_task_index,
2917 }
2918 }
2919}
2920
2921#[derive(Clone)]
2922struct OutputPack {
2923 msg_types: Vec<Type>,
2924}
2925
2926impl OutputPack {
2927 fn slot_type(&self) -> Type {
2928 build_output_slot_type(&self.msg_types)
2929 }
2930
2931 fn is_multi(&self) -> bool {
2932 self.msg_types.len() > 1
2933 }
2934}
2935
2936fn build_output_slot_type(msg_types: &[Type]) -> Type {
2937 if msg_types.is_empty() {
2938 parse_quote! { () }
2939 } else if msg_types.len() == 1 {
2940 let msg_type = msg_types.first().unwrap();
2941 parse_quote! { CuMsg<#msg_type> }
2942 } else {
2943 parse_quote! { ( #( CuMsg<#msg_types> ),* ) }
2944 }
2945}
2946
2947fn extract_output_packs(runtime_plan: &CuExecutionLoop) -> Vec<OutputPack> {
2948 let mut packs: Vec<(u32, OutputPack)> = runtime_plan
2949 .steps
2950 .iter()
2951 .filter_map(|unit| match unit {
2952 CuExecutionUnit::Step(step) => {
2953 if let Some(output_pack) = &step.output_msg_pack {
2954 let msg_types: Vec<Type> = output_pack
2955 .msg_types
2956 .iter()
2957 .map(|output_msg_type| {
2958 parse_str::<Type>(output_msg_type.as_str()).unwrap_or_else(|_| {
2959 panic!(
2960 "Could not transform {output_msg_type} into a message Rust type."
2961 )
2962 })
2963 })
2964 .collect();
2965 Some((output_pack.culist_index, OutputPack { msg_types }))
2966 } else {
2967 None
2968 }
2969 }
2970 CuExecutionUnit::Loop(_) => todo!("Needs to be implemented"),
2971 })
2972 .collect();
2973
2974 packs.sort_by_key(|(index, _)| *index);
2975 packs.into_iter().map(|(_, pack)| pack).collect()
2976}
2977
2978fn collect_output_pack_sizes(runtime_plan: &CuExecutionLoop) -> Vec<usize> {
2979 let mut sizes: Vec<(u32, usize)> = runtime_plan
2980 .steps
2981 .iter()
2982 .filter_map(|unit| match unit {
2983 CuExecutionUnit::Step(step) => step
2984 .output_msg_pack
2985 .as_ref()
2986 .map(|output_pack| (output_pack.culist_index, output_pack.msg_types.len())),
2987 CuExecutionUnit::Loop(_) => todo!("Needs to be implemented"),
2988 })
2989 .collect();
2990
2991 sizes.sort_by_key(|(index, _)| *index);
2992 sizes.into_iter().map(|(_, size)| size).collect()
2993}
2994
2995fn build_culist_tuple(slot_types: &[Type]) -> TypeTuple {
2997 if slot_types.is_empty() {
2998 parse_quote! { () }
2999 } else {
3000 parse_quote! { ( #( #slot_types ),* ) }
3001 }
3002}
3003
3004fn build_culist_tuple_encode(slot_types: &[Type]) -> ItemImpl {
3006 let indices: Vec<usize> = (0..slot_types.len()).collect();
3007
3008 let encode_fields: Vec<_> = indices
3010 .iter()
3011 .map(|i| {
3012 let idx = syn::Index::from(*i);
3013 quote! { self.0.#idx.encode(encoder)?; }
3014 })
3015 .collect();
3016
3017 parse_quote! {
3018 impl Encode for CuStampedDataSet {
3019 fn encode<E: Encoder>(&self, encoder: &mut E) -> Result<(), EncodeError> {
3020 #(#encode_fields)*
3021 Ok(())
3022 }
3023 }
3024 }
3025}
3026
3027fn build_culist_tuple_decode(slot_types: &[Type]) -> ItemImpl {
3029 let indices: Vec<usize> = (0..slot_types.len()).collect();
3030
3031 let decode_fields: Vec<_> = indices
3032 .iter()
3033 .map(|i| {
3034 let slot_type = &slot_types[*i];
3035 quote! { <#slot_type as Decode<()>>::decode(decoder)? }
3036 })
3037 .collect();
3038
3039 parse_quote! {
3040 impl Decode<()> for CuStampedDataSet {
3041 fn decode<D: Decoder<Context=()>>(decoder: &mut D) -> Result<Self, DecodeError> {
3042 Ok(CuStampedDataSet ((
3043 #(#decode_fields),*
3044 )))
3045 }
3046 }
3047 }
3048}
3049
3050fn build_culist_erasedcumsgs(output_packs: &[OutputPack]) -> ItemImpl {
3051 let mut casted_fields: Vec<proc_macro2::TokenStream> = Vec::new();
3052 for (idx, pack) in output_packs.iter().enumerate() {
3053 let slot_index = syn::Index::from(idx);
3054 if pack.is_multi() {
3055 for port_idx in 0..pack.msg_types.len() {
3056 let port_index = syn::Index::from(port_idx);
3057 casted_fields.push(quote! {
3058 &self.0.#slot_index.#port_index as &dyn ErasedCuStampedData
3059 });
3060 }
3061 } else {
3062 casted_fields.push(quote! { &self.0.#slot_index as &dyn ErasedCuStampedData });
3063 }
3064 }
3065 parse_quote! {
3066 impl ErasedCuStampedDataSet for CuStampedDataSet {
3067 fn cumsgs(&self) -> Vec<&dyn ErasedCuStampedData> {
3068 vec![
3069 #(#casted_fields),*
3070 ]
3071 }
3072 }
3073 }
3074}
3075
3076fn build_culist_tuple_debug(slot_types: &[Type]) -> ItemImpl {
3077 let indices: Vec<usize> = (0..slot_types.len()).collect();
3078
3079 let debug_fields: Vec<_> = indices
3080 .iter()
3081 .map(|i| {
3082 let idx = syn::Index::from(*i);
3083 quote! { .field(&self.0.#idx) }
3084 })
3085 .collect();
3086
3087 parse_quote! {
3088 impl Debug for CuStampedDataSet {
3089 fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
3090 f.debug_tuple("CuStampedDataSet")
3091 #(#debug_fields)*
3092 .finish()
3093 }
3094 }
3095 }
3096}
3097
3098fn build_culist_tuple_serialize(slot_types: &[Type]) -> ItemImpl {
3100 let indices: Vec<usize> = (0..slot_types.len()).collect();
3101 let tuple_len = slot_types.len();
3102
3103 let serialize_fields: Vec<_> = indices
3105 .iter()
3106 .map(|i| {
3107 let idx = syn::Index::from(*i);
3108 quote! { &self.0.#idx }
3109 })
3110 .collect();
3111
3112 parse_quote! {
3113 impl Serialize for CuStampedDataSet {
3114 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
3115 where
3116 S: serde::Serializer,
3117 {
3118 use serde::ser::SerializeTuple;
3119 let mut tuple = serializer.serialize_tuple(#tuple_len)?;
3120 #(tuple.serialize_element(#serialize_fields)?;)*
3121 tuple.end()
3122 }
3123 }
3124 }
3125}
3126
3127fn build_culist_tuple_default(slot_types: &[Type]) -> ItemImpl {
3129 let default_fields: Vec<_> = slot_types
3130 .iter()
3131 .map(|slot_type| quote! { <#slot_type as Default>::default() })
3132 .collect();
3133
3134 parse_quote! {
3135 impl Default for CuStampedDataSet {
3136 fn default() -> CuStampedDataSet
3137 {
3138 CuStampedDataSet((
3139 #(#default_fields),*
3140 ))
3141 }
3142 }
3143 }
3144}
3145
3146fn collect_bridge_channel_usage(graph: &CuGraph) -> HashMap<BridgeChannelKey, String> {
3147 let mut usage = HashMap::new();
3148 for cnx in graph.edges() {
3149 if let Some(channel) = &cnx.src_channel {
3150 let key = BridgeChannelKey {
3151 bridge_id: cnx.src.clone(),
3152 channel_id: channel.clone(),
3153 direction: BridgeChannelDirection::Rx,
3154 };
3155 usage
3156 .entry(key)
3157 .and_modify(|msg| {
3158 if msg != &cnx.msg {
3159 panic!(
3160 "Bridge '{}' channel '{}' is used with incompatible message types: {} vs {}",
3161 cnx.src, channel, msg, cnx.msg
3162 );
3163 }
3164 })
3165 .or_insert(cnx.msg.clone());
3166 }
3167 if let Some(channel) = &cnx.dst_channel {
3168 let key = BridgeChannelKey {
3169 bridge_id: cnx.dst.clone(),
3170 channel_id: channel.clone(),
3171 direction: BridgeChannelDirection::Tx,
3172 };
3173 usage
3174 .entry(key)
3175 .and_modify(|msg| {
3176 if msg != &cnx.msg {
3177 panic!(
3178 "Bridge '{}' channel '{}' is used with incompatible message types: {} vs {}",
3179 cnx.dst, channel, msg, cnx.msg
3180 );
3181 }
3182 })
3183 .or_insert(cnx.msg.clone());
3184 }
3185 }
3186 usage
3187}
3188
3189fn build_bridge_specs(
3190 config: &CuConfig,
3191 graph: &CuGraph,
3192 channel_usage: &HashMap<BridgeChannelKey, String>,
3193) -> Vec<BridgeSpec> {
3194 let mut specs = Vec::new();
3195 for (bridge_index, bridge_cfg) in config.bridges.iter().enumerate() {
3196 if graph.get_node_id_by_name(bridge_cfg.id.as_str()).is_none() {
3197 continue;
3198 }
3199
3200 let type_path = parse_str::<Type>(bridge_cfg.type_.as_str()).unwrap_or_else(|err| {
3201 panic!(
3202 "Could not parse bridge type '{}' for '{}': {err}",
3203 bridge_cfg.type_, bridge_cfg.id
3204 )
3205 });
3206
3207 let mut rx_channels = Vec::new();
3208 let mut tx_channels = Vec::new();
3209
3210 for (channel_index, channel) in bridge_cfg.channels.iter().enumerate() {
3211 match channel {
3212 BridgeChannelConfigRepresentation::Rx { id, .. } => {
3213 let key = BridgeChannelKey {
3214 bridge_id: bridge_cfg.id.clone(),
3215 channel_id: id.clone(),
3216 direction: BridgeChannelDirection::Rx,
3217 };
3218 if let Some(msg_type) = channel_usage.get(&key) {
3219 let msg_type_name = msg_type.clone();
3220 let msg_type = parse_str::<Type>(msg_type).unwrap_or_else(|err| {
3221 panic!(
3222 "Could not parse message type '{msg_type}' for bridge '{}' channel '{}': {err}",
3223 bridge_cfg.id, id
3224 )
3225 });
3226 let const_ident =
3227 Ident::new(&config_id_to_bridge_const(id.as_str()), Span::call_site());
3228 rx_channels.push(BridgeChannelSpec {
3229 id: id.clone(),
3230 const_ident,
3231 msg_type,
3232 msg_type_name,
3233 config_index: channel_index,
3234 plan_node_id: None,
3235 culist_index: None,
3236 monitor_index: None,
3237 });
3238 }
3239 }
3240 BridgeChannelConfigRepresentation::Tx { id, .. } => {
3241 let key = BridgeChannelKey {
3242 bridge_id: bridge_cfg.id.clone(),
3243 channel_id: id.clone(),
3244 direction: BridgeChannelDirection::Tx,
3245 };
3246 if let Some(msg_type) = channel_usage.get(&key) {
3247 let msg_type_name = msg_type.clone();
3248 let msg_type = parse_str::<Type>(msg_type).unwrap_or_else(|err| {
3249 panic!(
3250 "Could not parse message type '{msg_type}' for bridge '{}' channel '{}': {err}",
3251 bridge_cfg.id, id
3252 )
3253 });
3254 let const_ident =
3255 Ident::new(&config_id_to_bridge_const(id.as_str()), Span::call_site());
3256 tx_channels.push(BridgeChannelSpec {
3257 id: id.clone(),
3258 const_ident,
3259 msg_type,
3260 msg_type_name,
3261 config_index: channel_index,
3262 plan_node_id: None,
3263 culist_index: None,
3264 monitor_index: None,
3265 });
3266 }
3267 }
3268 }
3269 }
3270
3271 if rx_channels.is_empty() && tx_channels.is_empty() {
3272 continue;
3273 }
3274
3275 specs.push(BridgeSpec {
3276 id: bridge_cfg.id.clone(),
3277 type_path,
3278 config_index: bridge_index,
3279 tuple_index: 0,
3280 monitor_index: None,
3281 rx_channels,
3282 tx_channels,
3283 });
3284 }
3285
3286 for (tuple_index, spec) in specs.iter_mut().enumerate() {
3287 spec.tuple_index = tuple_index;
3288 }
3289
3290 specs
3291}
3292
3293fn collect_task_member_names(graph: &CuGraph) -> Vec<(NodeId, String)> {
3294 graph
3295 .get_all_nodes()
3296 .iter()
3297 .filter(|(_, node)| node.get_flavor() == Flavor::Task)
3298 .map(|(node_id, node)| (*node_id, config_id_to_struct_member(node.get_id().as_str())))
3299 .collect()
3300}
3301
3302#[derive(Clone, Copy)]
3303enum ResourceOwner {
3304 Task(usize),
3305 Bridge(usize),
3306}
3307
3308#[derive(Clone)]
3309struct ResourceKeySpec {
3310 bundle_index: usize,
3311 provider_path: syn::Path,
3312 resource_name: String,
3313 binding_name: String,
3314 owner: ResourceOwner,
3315}
3316
3317fn parse_resource_path(path: &str) -> CuResult<(String, String)> {
3318 let (bundle_id, name) = path.split_once('.').ok_or_else(|| {
3319 CuError::from(format!(
3320 "Resource '{path}' is missing a bundle prefix (expected bundle.resource)"
3321 ))
3322 })?;
3323
3324 if bundle_id.is_empty() || name.is_empty() {
3325 return Err(CuError::from(format!(
3326 "Resource '{path}' must use the 'bundle.resource' format"
3327 )));
3328 }
3329
3330 Ok((bundle_id.to_string(), name.to_string()))
3331}
3332
3333fn collect_resource_specs(
3334 graph: &CuGraph,
3335 task_specs: &CuTaskSpecSet,
3336 bridge_specs: &[BridgeSpec],
3337 bundle_specs: &[BundleSpec],
3338) -> CuResult<Vec<ResourceKeySpec>> {
3339 let mut bridge_lookup: BTreeMap<String, usize> = BTreeMap::new();
3340 for (idx, spec) in bridge_specs.iter().enumerate() {
3341 bridge_lookup.insert(spec.id.clone(), idx);
3342 }
3343
3344 let mut bundle_lookup: HashMap<String, (usize, syn::Path)> = HashMap::new();
3345 for (index, bundle) in bundle_specs.iter().enumerate() {
3346 bundle_lookup.insert(bundle.id.clone(), (index, bundle.provider_path.clone()));
3347 }
3348
3349 let mut specs = Vec::new();
3350
3351 for (node_id, node) in graph.get_all_nodes() {
3352 let resources = node.get_resources();
3353 if let Some(resources) = resources {
3354 let task_index = task_specs.node_id_to_task_index[node_id as usize];
3355 let owner = if let Some(task_index) = task_index {
3356 ResourceOwner::Task(task_index)
3357 } else if node.get_flavor() == Flavor::Bridge {
3358 let bridge_index = bridge_lookup.get(&node.get_id()).ok_or_else(|| {
3359 CuError::from(format!(
3360 "Resource mapping attached to unknown bridge node '{}'",
3361 node.get_id()
3362 ))
3363 })?;
3364 ResourceOwner::Bridge(*bridge_index)
3365 } else {
3366 return Err(CuError::from(format!(
3367 "Resource mapping attached to non-task node '{}'",
3368 node.get_id()
3369 )));
3370 };
3371
3372 for (binding_name, path) in resources {
3373 let (bundle_id, resource_name) = parse_resource_path(path)?;
3374 let (bundle_index, provider_path) =
3375 bundle_lookup.get(&bundle_id).ok_or_else(|| {
3376 CuError::from(format!(
3377 "Resource '{}' references unknown bundle '{}'",
3378 path, bundle_id
3379 ))
3380 })?;
3381 specs.push(ResourceKeySpec {
3382 bundle_index: *bundle_index,
3383 provider_path: provider_path.clone(),
3384 resource_name,
3385 binding_name: binding_name.clone(),
3386 owner,
3387 });
3388 }
3389 }
3390 }
3391
3392 Ok(specs)
3393}
3394
3395fn build_bundle_list<'a>(config: &'a CuConfig, mission: &str) -> Vec<&'a ResourceBundleConfig> {
3396 config
3397 .resources
3398 .iter()
3399 .filter(|bundle| {
3400 bundle
3401 .missions
3402 .as_ref()
3403 .is_none_or(|missions| missions.iter().any(|m| m == mission))
3404 })
3405 .collect()
3406}
3407
3408struct BundleSpec {
3409 id: String,
3410 provider_path: syn::Path,
3411}
3412
3413fn build_bundle_specs(config: &CuConfig, mission: &str) -> CuResult<Vec<BundleSpec>> {
3414 build_bundle_list(config, mission)
3415 .into_iter()
3416 .map(|bundle| {
3417 let provider_path: syn::Path =
3418 syn::parse_str(bundle.provider.as_str()).map_err(|err| {
3419 CuError::from(format!(
3420 "Failed to parse provider path '{}' for bundle '{}': {err}",
3421 bundle.provider, bundle.id
3422 ))
3423 })?;
3424 Ok(BundleSpec {
3425 id: bundle.id.clone(),
3426 provider_path,
3427 })
3428 })
3429 .collect()
3430}
3431
3432fn build_resources_module(
3433 bundle_specs: &[BundleSpec],
3434) -> CuResult<(proc_macro2::TokenStream, proc_macro2::TokenStream)> {
3435 let bundle_consts = bundle_specs.iter().enumerate().map(|(index, bundle)| {
3436 let const_ident = Ident::new(
3437 &config_id_to_bridge_const(bundle.id.as_str()),
3438 Span::call_site(),
3439 );
3440 quote! { pub const #const_ident: BundleIndex = BundleIndex::new(#index); }
3441 });
3442
3443 let resources_module = quote! {
3444 pub mod resources {
3445 #![allow(dead_code)]
3446 use cu29::resource::BundleIndex;
3447
3448 pub mod bundles {
3449 use super::BundleIndex;
3450 #(#bundle_consts)*
3451 }
3452 }
3453 };
3454
3455 let bundle_counts = bundle_specs.iter().map(|bundle| {
3456 let provider_path = &bundle.provider_path;
3457 quote! { <#provider_path as cu29::resource::ResourceBundleDecl>::Id::COUNT }
3458 });
3459
3460 let bundle_inits = bundle_specs
3461 .iter()
3462 .enumerate()
3463 .map(|(index, bundle)| {
3464 let bundle_id = LitStr::new(bundle.id.as_str(), Span::call_site());
3465 let provider_path = &bundle.provider_path;
3466 quote! {
3467 let bundle_cfg = config
3468 .resources
3469 .iter()
3470 .find(|b| b.id == #bundle_id)
3471 .unwrap_or_else(|| panic!("Resource bundle '{}' missing from configuration", #bundle_id));
3472 let bundle_ctx = cu29::resource::BundleContext::<#provider_path>::new(
3473 cu29::resource::BundleIndex::new(#index),
3474 #bundle_id,
3475 );
3476 <#provider_path as cu29::resource::ResourceBundle>::build(
3477 bundle_ctx,
3478 bundle_cfg.config.as_ref(),
3479 &mut manager,
3480 )?;
3481 }
3482 })
3483 .collect::<Vec<_>>();
3484
3485 let resources_instanciator = quote! {
3486 pub fn resources_instanciator(config: &CuConfig) -> CuResult<cu29::resource::ResourceManager> {
3487 let bundle_counts: &[usize] = &[ #(#bundle_counts),* ];
3488 let mut manager = cu29::resource::ResourceManager::new(bundle_counts);
3489 #(#bundle_inits)*
3490 Ok(manager)
3491 }
3492 };
3493
3494 Ok((resources_module, resources_instanciator))
3495}
3496
3497struct ResourceMappingTokens {
3498 defs: proc_macro2::TokenStream,
3499 refs: Vec<proc_macro2::TokenStream>,
3500}
3501
3502fn build_task_resource_mappings(
3503 resource_specs: &[ResourceKeySpec],
3504 task_specs: &CuTaskSpecSet,
3505) -> CuResult<ResourceMappingTokens> {
3506 let mut per_task: Vec<Vec<&ResourceKeySpec>> = vec![Vec::new(); task_specs.ids.len()];
3507
3508 for spec in resource_specs {
3509 let ResourceOwner::Task(task_index) = spec.owner else {
3510 continue;
3511 };
3512 per_task
3513 .get_mut(task_index)
3514 .ok_or_else(|| {
3515 CuError::from(format!(
3516 "Resource '{}' mapped to invalid task index {}",
3517 spec.binding_name, task_index
3518 ))
3519 })?
3520 .push(spec);
3521 }
3522
3523 let mut mapping_defs = Vec::new();
3524 let mut mapping_refs = Vec::new();
3525
3526 for (idx, entries) in per_task.iter().enumerate() {
3527 if entries.is_empty() {
3528 mapping_refs.push(quote! { None });
3529 continue;
3530 }
3531
3532 let binding_task_type = if task_specs.background_flags[idx] {
3533 &task_specs.sim_task_types[idx]
3534 } else {
3535 &task_specs.task_types[idx]
3536 };
3537
3538 let binding_trait = match task_specs.cutypes[idx] {
3539 CuTaskType::Source => quote! { CuSrcTask },
3540 CuTaskType::Regular => quote! { CuTask },
3541 CuTaskType::Sink => quote! { CuSinkTask },
3542 };
3543
3544 let entries_ident = format_ident!("TASK{}_RES_ENTRIES", idx);
3545 let map_ident = format_ident!("TASK{}_RES_MAPPING", idx);
3546 let binding_type = quote! {
3547 <<#binding_task_type as #binding_trait>::Resources<'_> as ResourceBindings>::Binding
3548 };
3549 let entry_tokens = entries.iter().map(|spec| {
3550 let binding_ident =
3551 Ident::new(&config_id_to_enum(spec.binding_name.as_str()), Span::call_site());
3552 let resource_ident =
3553 Ident::new(&config_id_to_enum(spec.resource_name.as_str()), Span::call_site());
3554 let bundle_index = spec.bundle_index;
3555 let provider_path = &spec.provider_path;
3556 quote! {
3557 (#binding_type::#binding_ident, cu29::resource::ResourceKey::new(
3558 cu29::resource::BundleIndex::new(#bundle_index),
3559 <#provider_path as cu29::resource::ResourceBundleDecl>::Id::#resource_ident as usize,
3560 ))
3561 }
3562 });
3563
3564 mapping_defs.push(quote! {
3565 const #entries_ident: &[(#binding_type, cu29::resource::ResourceKey)] = &[ #(#entry_tokens),* ];
3566 const #map_ident: cu29::resource::ResourceBindingMap<#binding_type> =
3567 cu29::resource::ResourceBindingMap::new(#entries_ident);
3568 });
3569 mapping_refs.push(quote! { Some(&#map_ident) });
3570 }
3571
3572 Ok(ResourceMappingTokens {
3573 defs: quote! { #(#mapping_defs)* },
3574 refs: mapping_refs,
3575 })
3576}
3577
3578fn build_bridge_resource_mappings(
3579 resource_specs: &[ResourceKeySpec],
3580 bridge_specs: &[BridgeSpec],
3581) -> ResourceMappingTokens {
3582 let mut per_bridge: Vec<Vec<&ResourceKeySpec>> = vec![Vec::new(); bridge_specs.len()];
3583
3584 for spec in resource_specs {
3585 let ResourceOwner::Bridge(bridge_index) = spec.owner else {
3586 continue;
3587 };
3588 per_bridge[bridge_index].push(spec);
3589 }
3590
3591 let mut mapping_defs = Vec::new();
3592 let mut mapping_refs = Vec::new();
3593
3594 for (idx, entries) in per_bridge.iter().enumerate() {
3595 if entries.is_empty() {
3596 mapping_refs.push(quote! { None });
3597 continue;
3598 }
3599
3600 let bridge_type = &bridge_specs[idx].type_path;
3601 let binding_type = quote! {
3602 <<#bridge_type as cu29::cubridge::CuBridge>::Resources<'_> as ResourceBindings>::Binding
3603 };
3604 let entries_ident = format_ident!("BRIDGE{}_RES_ENTRIES", idx);
3605 let map_ident = format_ident!("BRIDGE{}_RES_MAPPING", idx);
3606 let entry_tokens = entries.iter().map(|spec| {
3607 let binding_ident =
3608 Ident::new(&config_id_to_enum(spec.binding_name.as_str()), Span::call_site());
3609 let resource_ident =
3610 Ident::new(&config_id_to_enum(spec.resource_name.as_str()), Span::call_site());
3611 let bundle_index = spec.bundle_index;
3612 let provider_path = &spec.provider_path;
3613 quote! {
3614 (#binding_type::#binding_ident, cu29::resource::ResourceKey::new(
3615 cu29::resource::BundleIndex::new(#bundle_index),
3616 <#provider_path as cu29::resource::ResourceBundleDecl>::Id::#resource_ident as usize,
3617 ))
3618 }
3619 });
3620
3621 mapping_defs.push(quote! {
3622 const #entries_ident: &[(#binding_type, cu29::resource::ResourceKey)] = &[ #(#entry_tokens),* ];
3623 const #map_ident: cu29::resource::ResourceBindingMap<#binding_type> =
3624 cu29::resource::ResourceBindingMap::new(#entries_ident);
3625 });
3626 mapping_refs.push(quote! { Some(&#map_ident) });
3627 }
3628
3629 ResourceMappingTokens {
3630 defs: quote! { #(#mapping_defs)* },
3631 refs: mapping_refs,
3632 }
3633}
3634
3635fn build_execution_plan(
3636 graph: &CuGraph,
3637 task_specs: &CuTaskSpecSet,
3638 bridge_specs: &mut [BridgeSpec],
3639) -> CuResult<(
3640 CuExecutionLoop,
3641 Vec<ExecutionEntity>,
3642 HashMap<NodeId, NodeId>,
3643)> {
3644 let mut plan_graph = CuGraph::default();
3645 let mut exec_entities = Vec::new();
3646 let mut original_to_plan = HashMap::new();
3647 let mut plan_to_original = HashMap::new();
3648 let mut name_to_original = HashMap::new();
3649 let mut channel_nodes = HashMap::new();
3650
3651 for (node_id, node) in graph.get_all_nodes() {
3652 name_to_original.insert(node.get_id(), node_id);
3653 if node.get_flavor() != Flavor::Task {
3654 continue;
3655 }
3656 let plan_node_id = plan_graph.add_node(node.clone())?;
3657 let task_index = task_specs.node_id_to_task_index[node_id as usize]
3658 .expect("Task missing from specifications");
3659 plan_to_original.insert(plan_node_id, node_id);
3660 original_to_plan.insert(node_id, plan_node_id);
3661 if plan_node_id as usize != exec_entities.len() {
3662 panic!("Unexpected node ordering while mirroring tasks in plan graph");
3663 }
3664 exec_entities.push(ExecutionEntity {
3665 kind: ExecutionEntityKind::Task { task_index },
3666 });
3667 }
3668
3669 for (bridge_index, spec) in bridge_specs.iter_mut().enumerate() {
3670 for (channel_index, channel_spec) in spec.rx_channels.iter_mut().enumerate() {
3671 let mut node = Node::new(
3672 format!("{}::rx::{}", spec.id, channel_spec.id).as_str(),
3673 "__CuBridgeRxChannel",
3674 );
3675 node.set_flavor(Flavor::Bridge);
3676 let plan_node_id = plan_graph.add_node(node)?;
3677 if plan_node_id as usize != exec_entities.len() {
3678 panic!("Unexpected node ordering while inserting bridge rx channel");
3679 }
3680 channel_spec.plan_node_id = Some(plan_node_id);
3681 exec_entities.push(ExecutionEntity {
3682 kind: ExecutionEntityKind::BridgeRx {
3683 bridge_index,
3684 channel_index,
3685 },
3686 });
3687 channel_nodes.insert(
3688 BridgeChannelKey {
3689 bridge_id: spec.id.clone(),
3690 channel_id: channel_spec.id.clone(),
3691 direction: BridgeChannelDirection::Rx,
3692 },
3693 plan_node_id,
3694 );
3695 }
3696
3697 for (channel_index, channel_spec) in spec.tx_channels.iter_mut().enumerate() {
3698 let mut node = Node::new(
3699 format!("{}::tx::{}", spec.id, channel_spec.id).as_str(),
3700 "__CuBridgeTxChannel",
3701 );
3702 node.set_flavor(Flavor::Bridge);
3703 let plan_node_id = plan_graph.add_node(node)?;
3704 if plan_node_id as usize != exec_entities.len() {
3705 panic!("Unexpected node ordering while inserting bridge tx channel");
3706 }
3707 channel_spec.plan_node_id = Some(plan_node_id);
3708 exec_entities.push(ExecutionEntity {
3709 kind: ExecutionEntityKind::BridgeTx {
3710 bridge_index,
3711 channel_index,
3712 },
3713 });
3714 channel_nodes.insert(
3715 BridgeChannelKey {
3716 bridge_id: spec.id.clone(),
3717 channel_id: channel_spec.id.clone(),
3718 direction: BridgeChannelDirection::Tx,
3719 },
3720 plan_node_id,
3721 );
3722 }
3723 }
3724
3725 for cnx in graph.edges() {
3726 let src_plan = if let Some(channel) = &cnx.src_channel {
3727 let key = BridgeChannelKey {
3728 bridge_id: cnx.src.clone(),
3729 channel_id: channel.clone(),
3730 direction: BridgeChannelDirection::Rx,
3731 };
3732 *channel_nodes
3733 .get(&key)
3734 .unwrap_or_else(|| panic!("Bridge source {:?} missing from plan graph", key))
3735 } else {
3736 let node_id = name_to_original
3737 .get(&cnx.src)
3738 .copied()
3739 .unwrap_or_else(|| panic!("Unknown source node '{}'", cnx.src));
3740 *original_to_plan
3741 .get(&node_id)
3742 .unwrap_or_else(|| panic!("Source node '{}' missing from plan", cnx.src))
3743 };
3744
3745 let dst_plan = if let Some(channel) = &cnx.dst_channel {
3746 let key = BridgeChannelKey {
3747 bridge_id: cnx.dst.clone(),
3748 channel_id: channel.clone(),
3749 direction: BridgeChannelDirection::Tx,
3750 };
3751 *channel_nodes
3752 .get(&key)
3753 .unwrap_or_else(|| panic!("Bridge destination {:?} missing from plan graph", key))
3754 } else {
3755 let node_id = name_to_original
3756 .get(&cnx.dst)
3757 .copied()
3758 .unwrap_or_else(|| panic!("Unknown destination node '{}'", cnx.dst));
3759 *original_to_plan
3760 .get(&node_id)
3761 .unwrap_or_else(|| panic!("Destination node '{}' missing from plan", cnx.dst))
3762 };
3763
3764 plan_graph
3765 .connect_ext(
3766 src_plan,
3767 dst_plan,
3768 &cnx.msg,
3769 cnx.missions.clone(),
3770 None,
3771 None,
3772 )
3773 .map_err(|e| CuError::from(e.to_string()))?;
3774 }
3775
3776 let runtime_plan = compute_runtime_plan(&plan_graph)?;
3777 Ok((runtime_plan, exec_entities, plan_to_original))
3778}
3779
3780fn collect_culist_metadata(
3781 runtime_plan: &CuExecutionLoop,
3782 exec_entities: &[ExecutionEntity],
3783 bridge_specs: &mut [BridgeSpec],
3784 plan_to_original: &HashMap<NodeId, NodeId>,
3785) -> (Vec<usize>, HashMap<NodeId, usize>) {
3786 let mut culist_order = Vec::new();
3787 let mut node_output_positions = HashMap::new();
3788
3789 for unit in &runtime_plan.steps {
3790 if let CuExecutionUnit::Step(step) = unit
3791 && let Some(output_pack) = &step.output_msg_pack
3792 {
3793 let output_idx = output_pack.culist_index;
3794 culist_order.push(output_idx as usize);
3795 match &exec_entities[step.node_id as usize].kind {
3796 ExecutionEntityKind::Task { .. } => {
3797 if let Some(original_node_id) = plan_to_original.get(&step.node_id) {
3798 node_output_positions.insert(*original_node_id, output_idx as usize);
3799 }
3800 }
3801 ExecutionEntityKind::BridgeRx {
3802 bridge_index,
3803 channel_index,
3804 } => {
3805 bridge_specs[*bridge_index].rx_channels[*channel_index].culist_index =
3806 Some(output_idx as usize);
3807 }
3808 ExecutionEntityKind::BridgeTx { .. } => {}
3809 }
3810 }
3811 }
3812
3813 (culist_order, node_output_positions)
3814}
3815
3816#[allow(dead_code)]
3817fn build_monitored_ids(task_ids: &[String], bridge_specs: &mut [BridgeSpec]) -> Vec<String> {
3818 let mut names = task_ids.to_vec();
3819 for spec in bridge_specs.iter_mut() {
3820 spec.monitor_index = Some(names.len());
3821 names.push(format!("bridge::{}", spec.id));
3822 for channel in spec.rx_channels.iter_mut() {
3823 channel.monitor_index = Some(names.len());
3824 names.push(format!("bridge::{}::rx::{}", spec.id, channel.id));
3825 }
3826 for channel in spec.tx_channels.iter_mut() {
3827 channel.monitor_index = Some(names.len());
3828 names.push(format!("bridge::{}::tx::{}", spec.id, channel.id));
3829 }
3830 }
3831 names
3832}
3833
3834fn generate_task_execution_tokens(
3835 step: &CuExecutionStep,
3836 task_index: usize,
3837 task_specs: &CuTaskSpecSet,
3838 output_pack_sizes: &[usize],
3839 sim_mode: bool,
3840 mission_mod: &Ident,
3841) -> (proc_macro2::TokenStream, proc_macro2::TokenStream) {
3842 let node_index = int2sliceindex(task_index as u32);
3843 let task_instance = quote! { tasks.#node_index };
3844 let comment_str = format!(
3845 "DEBUG ->> {} ({:?}) Id:{} I:{:?} O:{:?}",
3846 step.node.get_id(),
3847 step.task_type,
3848 step.node_id,
3849 step.input_msg_indices_types,
3850 step.output_msg_pack
3851 );
3852 let comment_tokens = quote! {{
3853 let _ = stringify!(#comment_str);
3854 }};
3855 let tid = task_index;
3856 let task_enum_name = config_id_to_enum(&task_specs.ids[tid]);
3857 let enum_name = Ident::new(&task_enum_name, Span::call_site());
3858 let rt_guard = rtsan_guard_tokens();
3859 let run_in_sim_flag = task_specs.run_in_sim_flags[tid];
3860 let maybe_sim_tick = if sim_mode && !run_in_sim_flag {
3861 quote! {
3862 if !doit {
3863 #task_instance.sim_tick();
3864 }
3865 }
3866 } else {
3867 quote!()
3868 };
3869
3870 let output_pack = step
3871 .output_msg_pack
3872 .as_ref()
3873 .expect("Task should have an output message pack.");
3874 let output_culist_index = int2sliceindex(output_pack.culist_index);
3875 let output_ports: Vec<syn::Index> = (0..output_pack.msg_types.len())
3876 .map(syn::Index::from)
3877 .collect();
3878 let output_clear_payload = if output_ports.len() == 1 {
3879 quote! { cumsg_output.clear_payload(); }
3880 } else {
3881 quote! { #(cumsg_output.#output_ports.clear_payload();)* }
3882 };
3883 let output_start_time = if output_ports.len() == 1 {
3884 quote! {
3885 if cumsg_output.metadata.process_time.start.is_none() {
3886 cumsg_output.metadata.process_time.start = clock.now().into();
3887 }
3888 }
3889 } else {
3890 quote! {
3891 let start_time = clock.now().into();
3892 #( if cumsg_output.#output_ports.metadata.process_time.start.is_none() {
3893 cumsg_output.#output_ports.metadata.process_time.start = start_time;
3894 } )*
3895 }
3896 };
3897 let output_end_time = if output_ports.len() == 1 {
3898 quote! {
3899 if cumsg_output.metadata.process_time.end.is_none() {
3900 cumsg_output.metadata.process_time.end = clock.now().into();
3901 }
3902 }
3903 } else {
3904 quote! {
3905 let end_time = clock.now().into();
3906 #( if cumsg_output.#output_ports.metadata.process_time.end.is_none() {
3907 cumsg_output.#output_ports.metadata.process_time.end = end_time;
3908 } )*
3909 }
3910 };
3911
3912 match step.task_type {
3913 CuTaskType::Source => {
3914 let monitoring_action = quote! {
3915 debug!("Task {}: Error during process: {}", #mission_mod::TASKS_IDS[#tid], &error);
3916 let decision = monitor.process_error(#tid, CuTaskState::Process, &error);
3917 match decision {
3918 Decision::Abort => {
3919 debug!("Process: ABORT decision from monitoring. Task '{}' errored out \
3920 during process. Skipping the processing of CL {}.", #mission_mod::TASKS_IDS[#tid], clid);
3921 let monitor_result = monitor.process_copperlist(&#mission_mod::collect_metadata(&culist));
3922 cl_manager.end_of_processing(clid)?;
3923 monitor_result?;
3924 return Ok(());
3925 }
3926 Decision::Ignore => {
3927 debug!("Process: IGNORE decision from monitoring. Task '{}' errored out \
3928 during process. The runtime will continue with a forced empty message.", #mission_mod::TASKS_IDS[#tid]);
3929 let cumsg_output = &mut msgs.#output_culist_index;
3930 #output_clear_payload
3931 }
3932 Decision::Shutdown => {
3933 debug!("Process: SHUTDOWN decision from monitoring. Task '{}' errored out \
3934 during process. The runtime cannot continue.", #mission_mod::TASKS_IDS[#tid]);
3935 return Err(CuError::new_with_cause("Task errored out during process.", error));
3936 }
3937 }
3938 };
3939
3940 let call_sim_callback = if sim_mode {
3941 quote! {
3942 let doit = {
3943 let cumsg_output = &mut msgs.#output_culist_index;
3944 let state = CuTaskCallbackState::Process((), cumsg_output);
3945 let ovr = sim_callback(SimStep::#enum_name(state));
3946
3947 if let SimOverride::Errored(reason) = ovr {
3948 let error: CuError = reason.into();
3949 #monitoring_action
3950 false
3951 } else {
3952 ovr == SimOverride::ExecuteByRuntime
3953 }
3954 };
3955 }
3956 } else {
3957 quote! { let doit = true; }
3958 };
3959
3960 let logging_tokens = if !task_specs.logging_enabled[tid] {
3961 quote! {
3962 let mut cumsg_output = &mut culist.msgs.0.#output_culist_index;
3963 #output_clear_payload
3964 }
3965 } else {
3966 quote!()
3967 };
3968
3969 (
3970 quote! {
3971 {
3972 #comment_tokens
3973 kf_manager.freeze_task(clid, &#task_instance)?;
3974 #call_sim_callback
3975 let cumsg_output = &mut msgs.#output_culist_index;
3976 #maybe_sim_tick
3977 let maybe_error = if doit {
3978 #output_start_time
3979 let result = {
3980 #rt_guard
3981 #task_instance.process(clock, cumsg_output)
3982 };
3983 #output_end_time
3984 result
3985 } else {
3986 Ok(())
3987 };
3988 if let Err(error) = maybe_error {
3989 #monitoring_action
3990 }
3991 }
3992 },
3993 logging_tokens,
3994 )
3995 }
3996 CuTaskType::Sink => {
3997 let input_exprs: Vec<proc_macro2::TokenStream> = step
3998 .input_msg_indices_types
3999 .iter()
4000 .map(|input| {
4001 let input_index = int2sliceindex(input.culist_index);
4002 let output_size = output_pack_sizes
4003 .get(input.culist_index as usize)
4004 .copied()
4005 .unwrap_or_else(|| {
4006 panic!(
4007 "Missing output pack size for culist index {}",
4008 input.culist_index
4009 )
4010 });
4011 if output_size > 1 {
4012 let port_index = syn::Index::from(input.src_port);
4013 quote! { msgs.#input_index.#port_index }
4014 } else {
4015 quote! { msgs.#input_index }
4016 }
4017 })
4018 .collect();
4019 let inputs_type = if input_exprs.len() == 1 {
4020 let input = input_exprs.first().unwrap();
4021 quote! { #input }
4022 } else {
4023 quote! { (#(&#input_exprs),*) }
4024 };
4025
4026 let monitoring_action = quote! {
4027 debug!("Task {}: Error during process: {}", #mission_mod::TASKS_IDS[#tid], &error);
4028 let decision = monitor.process_error(#tid, CuTaskState::Process, &error);
4029 match decision {
4030 Decision::Abort => {
4031 debug!("Process: ABORT decision from monitoring. Task '{}' errored out \
4032 during process. Skipping the processing of CL {}.", #mission_mod::TASKS_IDS[#tid], clid);
4033 let monitor_result = monitor.process_copperlist(&#mission_mod::collect_metadata(&culist));
4034 cl_manager.end_of_processing(clid)?;
4035 monitor_result?;
4036 return Ok(());
4037 }
4038 Decision::Ignore => {
4039 debug!("Process: IGNORE decision from monitoring. Task '{}' errored out \
4040 during process. The runtime will continue with a forced empty message.", #mission_mod::TASKS_IDS[#tid]);
4041 let cumsg_output = &mut msgs.#output_culist_index;
4042 #output_clear_payload
4043 }
4044 Decision::Shutdown => {
4045 debug!("Process: SHUTDOWN decision from monitoring. Task '{}' errored out \
4046 during process. The runtime cannot continue.", #mission_mod::TASKS_IDS[#tid]);
4047 return Err(CuError::new_with_cause("Task errored out during process.", error));
4048 }
4049 }
4050 };
4051
4052 let call_sim_callback = if sim_mode {
4053 quote! {
4054 let doit = {
4055 let cumsg_input = &#inputs_type;
4056 let cumsg_output = &mut msgs.#output_culist_index;
4057 let state = CuTaskCallbackState::Process(cumsg_input, cumsg_output);
4058 let ovr = sim_callback(SimStep::#enum_name(state));
4059
4060 if let SimOverride::Errored(reason) = ovr {
4061 let error: CuError = reason.into();
4062 #monitoring_action
4063 false
4064 } else {
4065 ovr == SimOverride::ExecuteByRuntime
4066 }
4067 };
4068 }
4069 } else {
4070 quote! { let doit = true; }
4071 };
4072
4073 (
4074 quote! {
4075 {
4076 #comment_tokens
4077 kf_manager.freeze_task(clid, &#task_instance)?;
4078 #call_sim_callback
4079 let cumsg_input = &#inputs_type;
4080 let cumsg_output = &mut msgs.#output_culist_index;
4081 let maybe_error = if doit {
4082 #output_start_time
4083 let result = {
4084 #rt_guard
4085 #task_instance.process(clock, cumsg_input)
4086 };
4087 #output_end_time
4088 result
4089 } else {
4090 Ok(())
4091 };
4092 if let Err(error) = maybe_error {
4093 #monitoring_action
4094 }
4095 }
4096 },
4097 quote! {},
4098 )
4099 }
4100 CuTaskType::Regular => {
4101 let input_exprs: Vec<proc_macro2::TokenStream> = step
4102 .input_msg_indices_types
4103 .iter()
4104 .map(|input| {
4105 let input_index = int2sliceindex(input.culist_index);
4106 let output_size = output_pack_sizes
4107 .get(input.culist_index as usize)
4108 .copied()
4109 .unwrap_or_else(|| {
4110 panic!(
4111 "Missing output pack size for culist index {}",
4112 input.culist_index
4113 )
4114 });
4115 if output_size > 1 {
4116 let port_index = syn::Index::from(input.src_port);
4117 quote! { msgs.#input_index.#port_index }
4118 } else {
4119 quote! { msgs.#input_index }
4120 }
4121 })
4122 .collect();
4123 let inputs_type = if input_exprs.len() == 1 {
4124 let input = input_exprs.first().unwrap();
4125 quote! { #input }
4126 } else {
4127 quote! { (#(&#input_exprs),*) }
4128 };
4129
4130 let monitoring_action = quote! {
4131 debug!("Task {}: Error during process: {}", #mission_mod::TASKS_IDS[#tid], &error);
4132 let decision = monitor.process_error(#tid, CuTaskState::Process, &error);
4133 match decision {
4134 Decision::Abort => {
4135 debug!("Process: ABORT decision from monitoring. Task '{}' errored out \
4136 during process. Skipping the processing of CL {}.", #mission_mod::TASKS_IDS[#tid], clid);
4137 let monitor_result = monitor.process_copperlist(&#mission_mod::collect_metadata(&culist));
4138 cl_manager.end_of_processing(clid)?;
4139 monitor_result?;
4140 return Ok(());
4141 }
4142 Decision::Ignore => {
4143 debug!("Process: IGNORE decision from monitoring. Task '{}' errored out \
4144 during process. The runtime will continue with a forced empty message.", #mission_mod::TASKS_IDS[#tid]);
4145 let cumsg_output = &mut msgs.#output_culist_index;
4146 #output_clear_payload
4147 }
4148 Decision::Shutdown => {
4149 debug!("Process: SHUTDOWN decision from monitoring. Task '{}' errored out \
4150 during process. The runtime cannot continue.", #mission_mod::TASKS_IDS[#tid]);
4151 return Err(CuError::new_with_cause("Task errored out during process.", error));
4152 }
4153 }
4154 };
4155
4156 let call_sim_callback = if sim_mode {
4157 quote! {
4158 let doit = {
4159 let cumsg_input = &#inputs_type;
4160 let cumsg_output = &mut msgs.#output_culist_index;
4161 let state = CuTaskCallbackState::Process(cumsg_input, cumsg_output);
4162 let ovr = sim_callback(SimStep::#enum_name(state));
4163
4164 if let SimOverride::Errored(reason) = ovr {
4165 let error: CuError = reason.into();
4166 #monitoring_action
4167 false
4168 }
4169 else {
4170 ovr == SimOverride::ExecuteByRuntime
4171 }
4172 };
4173 }
4174 } else {
4175 quote! { let doit = true; }
4176 };
4177
4178 let logging_tokens = if !task_specs.logging_enabled[tid] {
4179 quote! {
4180 let mut cumsg_output = &mut culist.msgs.0.#output_culist_index;
4181 #output_clear_payload
4182 }
4183 } else {
4184 quote!()
4185 };
4186
4187 (
4188 quote! {
4189 {
4190 #comment_tokens
4191 kf_manager.freeze_task(clid, &#task_instance)?;
4192 #call_sim_callback
4193 let cumsg_input = &#inputs_type;
4194 let cumsg_output = &mut msgs.#output_culist_index;
4195 let maybe_error = if doit {
4196 #output_start_time
4197 let result = {
4198 #rt_guard
4199 #task_instance.process(clock, cumsg_input, cumsg_output)
4200 };
4201 #output_end_time
4202 result
4203 } else {
4204 Ok(())
4205 };
4206 if let Err(error) = maybe_error {
4207 #monitoring_action
4208 }
4209 }
4210 },
4211 logging_tokens,
4212 )
4213 }
4214 }
4215}
4216
4217fn generate_bridge_rx_execution_tokens(
4218 step: &CuExecutionStep,
4219 bridge_spec: &BridgeSpec,
4220 channel_index: usize,
4221 mission_mod: &Ident,
4222 sim_mode: bool,
4223) -> (proc_macro2::TokenStream, proc_macro2::TokenStream) {
4224 let rt_guard = rtsan_guard_tokens();
4225 let bridge_tuple_index = int2sliceindex(bridge_spec.tuple_index as u32);
4226 let channel = &bridge_spec.rx_channels[channel_index];
4227 let output_pack = step
4228 .output_msg_pack
4229 .as_ref()
4230 .expect("Bridge Rx channel missing output pack");
4231 let port_index = output_pack
4232 .msg_types
4233 .iter()
4234 .position(|msg| msg == &channel.msg_type_name)
4235 .unwrap_or_else(|| {
4236 panic!(
4237 "Bridge Rx channel '{}' missing output port for '{}'",
4238 channel.id, channel.msg_type_name
4239 )
4240 });
4241 let culist_index_ts = int2sliceindex(output_pack.culist_index);
4242 let output_ref = if output_pack.msg_types.len() == 1 {
4243 quote! { &mut msgs.#culist_index_ts }
4244 } else {
4245 let port_index = syn::Index::from(port_index);
4246 quote! { &mut msgs.#culist_index_ts.#port_index }
4247 };
4248 let monitor_index = syn::Index::from(
4249 channel
4250 .monitor_index
4251 .expect("Bridge Rx channel missing monitor index"),
4252 );
4253 let bridge_type = &bridge_spec.type_path;
4254 let const_ident = &channel.const_ident;
4255 let enum_ident = Ident::new(
4256 &config_id_to_enum(&format!("{}_rx_{}", bridge_spec.id, channel.id)),
4257 Span::call_site(),
4258 );
4259
4260 let call_sim_callback = if sim_mode {
4261 quote! {
4262 let doit = {
4263 let state = SimStep::#enum_ident {
4264 channel: &<#bridge_type as cu29::cubridge::CuBridge>::Rx::#const_ident,
4265 msg: cumsg_output,
4266 };
4267 let ovr = sim_callback(state);
4268 if let SimOverride::Errored(reason) = ovr {
4269 let error: CuError = reason.into();
4270 let decision = monitor.process_error(#monitor_index, CuTaskState::Process, &error);
4271 match decision {
4272 Decision::Abort => {
4273 debug!("Process: ABORT decision from monitoring. Task '{}' errored out during process. Skipping the processing of CL {}.", #mission_mod::TASKS_IDS[#monitor_index], clid);
4274 let monitor_result =
4275 monitor.process_copperlist(&#mission_mod::collect_metadata(&culist));
4276 cl_manager.end_of_processing(clid)?;
4277 monitor_result?;
4278 return Ok(());
4279 }
4280 Decision::Ignore => {
4281 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]);
4282 cumsg_output.clear_payload();
4283 false
4284 }
4285 Decision::Shutdown => {
4286 debug!("Process: SHUTDOWN decision from monitoring. Task '{}' errored out during process. The runtime cannot continue.", #mission_mod::TASKS_IDS[#monitor_index]);
4287 return Err(CuError::new_with_cause("Task errored out during process.", error));
4288 }
4289 }
4290 } else {
4291 ovr == SimOverride::ExecuteByRuntime
4292 }
4293 };
4294 }
4295 } else {
4296 quote! { let doit = true; }
4297 };
4298 (
4299 quote! {
4300 {
4301 let bridge = &mut __cu_bridges.#bridge_tuple_index;
4302 let cumsg_output = #output_ref;
4303 #call_sim_callback
4304 if doit {
4305 cumsg_output.metadata.process_time.start = clock.now().into();
4306 let maybe_error = {
4307 #rt_guard
4308 bridge.receive(
4309 clock,
4310 &<#bridge_type as cu29::cubridge::CuBridge>::Rx::#const_ident,
4311 cumsg_output,
4312 )
4313 };
4314 cumsg_output.metadata.process_time.end = clock.now().into();
4315 if let Err(error) = maybe_error {
4316 let decision = monitor.process_error(#monitor_index, CuTaskState::Process, &error);
4317 match decision {
4318 Decision::Abort => {
4319 debug!("Process: ABORT decision from monitoring. Task '{}' errored out during process. Skipping the processing of CL {}.", #mission_mod::TASKS_IDS[#monitor_index], clid);
4320 let monitor_result = monitor
4321 .process_copperlist(&#mission_mod::collect_metadata(&culist));
4322 cl_manager.end_of_processing(clid)?;
4323 monitor_result?;
4324 return Ok(());
4325 }
4326 Decision::Ignore => {
4327 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]);
4328 cumsg_output.clear_payload();
4329 }
4330 Decision::Shutdown => {
4331 debug!("Process: SHUTDOWN decision from monitoring. Task '{}' errored out during process. The runtime cannot continue.", #mission_mod::TASKS_IDS[#monitor_index]);
4332 return Err(CuError::new_with_cause("Task errored out during process.", error));
4333 }
4334 }
4335 }
4336 }
4337 }
4338 },
4339 quote! {},
4340 )
4341}
4342
4343fn generate_bridge_tx_execution_tokens(
4344 step: &CuExecutionStep,
4345 bridge_spec: &BridgeSpec,
4346 channel_index: usize,
4347 output_pack_sizes: &[usize],
4348 mission_mod: &Ident,
4349 sim_mode: bool,
4350) -> (proc_macro2::TokenStream, proc_macro2::TokenStream) {
4351 let rt_guard = rtsan_guard_tokens();
4352 let channel = &bridge_spec.tx_channels[channel_index];
4353 let monitor_index = syn::Index::from(
4354 channel
4355 .monitor_index
4356 .expect("Bridge Tx channel missing monitor index"),
4357 );
4358 let input = step
4359 .input_msg_indices_types
4360 .first()
4361 .expect("Bridge Tx channel should have exactly one input");
4362 let input_index = int2sliceindex(input.culist_index);
4363 let output_size = output_pack_sizes
4364 .get(input.culist_index as usize)
4365 .copied()
4366 .unwrap_or_else(|| {
4367 panic!(
4368 "Missing output pack size for culist index {}",
4369 input.culist_index
4370 )
4371 });
4372 let input_ref = if output_size > 1 {
4373 let port_index = syn::Index::from(input.src_port);
4374 quote! { &mut msgs.#input_index.#port_index }
4375 } else {
4376 quote! { &mut msgs.#input_index }
4377 };
4378 let bridge_tuple_index = int2sliceindex(bridge_spec.tuple_index as u32);
4379 let bridge_type = &bridge_spec.type_path;
4380 let const_ident = &channel.const_ident;
4381 let enum_ident = Ident::new(
4382 &config_id_to_enum(&format!("{}_tx_{}", bridge_spec.id, channel.id)),
4383 Span::call_site(),
4384 );
4385
4386 let call_sim_callback = if sim_mode {
4387 quote! {
4388 let doit = {
4389 let state = SimStep::#enum_ident {
4390 channel: &<#bridge_type as cu29::cubridge::CuBridge>::Tx::#const_ident,
4391 msg: &*cumsg_input,
4392 };
4393 let ovr = sim_callback(state);
4394 if let SimOverride::Errored(reason) = ovr {
4395 let error: CuError = reason.into();
4396 let decision = monitor.process_error(#monitor_index, CuTaskState::Process, &error);
4397 match decision {
4398 Decision::Abort => {
4399 debug!("Process: ABORT decision from monitoring. Task '{}' errored out during process. Skipping the processing of CL {}.", #mission_mod::TASKS_IDS[#monitor_index], clid);
4400 let monitor_result =
4401 monitor.process_copperlist(&#mission_mod::collect_metadata(&culist));
4402 cl_manager.end_of_processing(clid)?;
4403 monitor_result?;
4404 return Ok(());
4405 }
4406 Decision::Ignore => {
4407 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]);
4408 false
4409 }
4410 Decision::Shutdown => {
4411 debug!("Process: SHUTDOWN decision from monitoring. Task '{}' errored out during process. The runtime cannot continue.", #mission_mod::TASKS_IDS[#monitor_index]);
4412 return Err(CuError::new_with_cause("Task errored out during process.", error));
4413 }
4414 }
4415 } else {
4416 ovr == SimOverride::ExecuteByRuntime
4417 }
4418 };
4419 }
4420 } else {
4421 quote! { let doit = true; }
4422 };
4423 (
4424 quote! {
4425 {
4426 let bridge = &mut __cu_bridges.#bridge_tuple_index;
4427 let cumsg_input = #input_ref;
4428 #call_sim_callback
4430 if doit {
4431 if cumsg_input.metadata.process_time.start.is_none() {
4432 cumsg_input.metadata.process_time.start = clock.now().into();
4433 }
4434 let maybe_error = {
4435 #rt_guard
4436 bridge.send(
4437 clock,
4438 &<#bridge_type as cu29::cubridge::CuBridge>::Tx::#const_ident,
4439 &*cumsg_input,
4440 )
4441 };
4442 if let Err(error) = maybe_error {
4443 let decision = monitor.process_error(#monitor_index, CuTaskState::Process, &error);
4444 match decision {
4445 Decision::Abort => {
4446 debug!("Process: ABORT decision from monitoring. Task '{}' errored out during process. Skipping the processing of CL {}.", #mission_mod::TASKS_IDS[#monitor_index], clid);
4447 let monitor_result = monitor
4448 .process_copperlist(&#mission_mod::collect_metadata(&culist));
4449 cl_manager.end_of_processing(clid)?;
4450 monitor_result?;
4451 return Ok(());
4452 }
4453 Decision::Ignore => {
4454 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]);
4455 }
4456 Decision::Shutdown => {
4457 debug!("Process: SHUTDOWN decision from monitoring. Task '{}' errored out during process. The runtime cannot continue.", #mission_mod::TASKS_IDS[#monitor_index]);
4458 return Err(CuError::new_with_cause("Task errored out during process.", error));
4459 }
4460 }
4461 }
4462 if cumsg_input.metadata.process_time.end.is_none() {
4463 cumsg_input.metadata.process_time.end = clock.now().into();
4464 }
4465 }
4466 }
4467 },
4468 quote! {},
4469 )
4470}
4471
4472#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
4473enum BridgeChannelDirection {
4474 Rx,
4475 Tx,
4476}
4477
4478#[derive(Clone, Debug, PartialEq, Eq, Hash)]
4479struct BridgeChannelKey {
4480 bridge_id: String,
4481 channel_id: String,
4482 direction: BridgeChannelDirection,
4483}
4484
4485#[derive(Clone)]
4486struct BridgeChannelSpec {
4487 id: String,
4488 const_ident: Ident,
4489 #[allow(dead_code)]
4490 msg_type: Type,
4491 msg_type_name: String,
4492 config_index: usize,
4493 plan_node_id: Option<NodeId>,
4494 culist_index: Option<usize>,
4495 monitor_index: Option<usize>,
4496}
4497
4498#[derive(Clone)]
4499struct BridgeSpec {
4500 id: String,
4501 type_path: Type,
4502 config_index: usize,
4503 tuple_index: usize,
4504 monitor_index: Option<usize>,
4505 rx_channels: Vec<BridgeChannelSpec>,
4506 tx_channels: Vec<BridgeChannelSpec>,
4507}
4508
4509#[derive(Clone)]
4510struct ExecutionEntity {
4511 kind: ExecutionEntityKind,
4512}
4513
4514#[derive(Clone)]
4515enum ExecutionEntityKind {
4516 Task {
4517 task_index: usize,
4518 },
4519 BridgeRx {
4520 bridge_index: usize,
4521 channel_index: usize,
4522 },
4523 BridgeTx {
4524 bridge_index: usize,
4525 channel_index: usize,
4526 },
4527}
4528
4529#[cfg(test)]
4530mod tests {
4531 #[test]
4533 fn test_compile_fail() {
4534 use rustc_version::{Channel, version_meta};
4535 use std::{fs, path::Path};
4536
4537 let dir = Path::new("tests/compile_fail");
4538 for entry in fs::read_dir(dir).unwrap() {
4539 let entry = entry.unwrap();
4540 if !entry.file_type().unwrap().is_dir() {
4541 continue;
4542 }
4543 for file in fs::read_dir(entry.path()).unwrap() {
4544 let file = file.unwrap();
4545 let p = file.path();
4546 if p.extension().and_then(|x| x.to_str()) != Some("rs") {
4547 continue;
4548 }
4549
4550 let base = p.with_extension("stderr"); let src = match version_meta().unwrap().channel {
4552 Channel::Beta => Path::new(&format!("{}.beta", base.display())).to_path_buf(),
4553 _ => Path::new(&format!("{}.stable", base.display())).to_path_buf(),
4554 };
4555
4556 if src.exists() {
4557 fs::copy(src, &base).unwrap();
4558 }
4559 }
4560 }
4561
4562 let t = trybuild::TestCases::new();
4563 t.compile_fail("tests/compile_fail/*/*.rs");
4564 }
4565
4566 #[test]
4567 fn bridge_resources_are_collected() {
4568 use super::*;
4569 use cu29::config::{CuGraph, Flavor, Node};
4570 use std::collections::HashMap;
4571 use syn::parse_str;
4572
4573 let mut graph = CuGraph::default();
4574 let mut node = Node::new_with_flavor("radio", "bridge::Dummy", Flavor::Bridge);
4575 let mut res = HashMap::new();
4576 res.insert("serial".to_string(), "fc.serial0".to_string());
4577 node.set_resources(Some(res));
4578 graph.add_node(node).expect("bridge node");
4579
4580 let task_specs = CuTaskSpecSet::from_graph(&graph);
4581 let bridge_spec = BridgeSpec {
4582 id: "radio".to_string(),
4583 type_path: parse_str("bridge::Dummy").unwrap(),
4584 config_index: 0,
4585 tuple_index: 0,
4586 monitor_index: None,
4587 rx_channels: Vec::new(),
4588 tx_channels: Vec::new(),
4589 };
4590
4591 let mut config = cu29::config::CuConfig::default();
4592 config.resources.push(ResourceBundleConfig {
4593 id: "fc".to_string(),
4594 provider: "board::Bundle".to_string(),
4595 config: None,
4596 missions: None,
4597 });
4598 let bundle_specs = build_bundle_specs(&config, "default").expect("bundle specs");
4599 let specs = collect_resource_specs(&graph, &task_specs, &[bridge_spec], &bundle_specs)
4600 .expect("collect specs");
4601 assert_eq!(specs.len(), 1);
4602 assert!(matches!(specs[0].owner, ResourceOwner::Bridge(0)));
4603 assert_eq!(specs[0].binding_name, "serial");
4604 assert_eq!(specs[0].bundle_index, 0);
4605 assert_eq!(specs[0].resource_name, "serial0");
4606 }
4607}