1extern crate proc_macro;
2
3use proc_macro::TokenStream;
4use quote::{format_ident, quote};
5use std::fs::read_to_string;
6use syn::meta::parser;
7use syn::Fields::{Named, Unnamed};
8use syn::{
9 parse_macro_input, parse_quote, parse_str, Field, Fields, ItemImpl, ItemStruct, LitStr, Type,
10 TypeTuple,
11};
12
13use crate::utils::config_id_to_enum;
14use cu29_runtime::config::CuConfig;
15use cu29_runtime::config::{read_configuration, CuGraph};
16use cu29_runtime::curuntime::{
17 compute_runtime_plan, find_task_type_for_id, CuExecutionLoop, CuExecutionUnit, CuTaskType,
18};
19use cu29_traits::CuResult;
20use proc_macro2::{Ident, Span};
21
22#[cfg(feature = "macro_debug")]
23use crate::format::{highlight_rust_code, rustfmt_generated_code};
24
25mod format;
26mod utils;
27
28const DEFAULT_CLNB: usize = 10;
30
31#[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
43#[proc_macro]
47pub fn gen_cumsgs(config_path_lit: TokenStream) -> TokenStream {
48 let config = parse_macro_input!(config_path_lit as LitStr).value();
49 if !std::path::Path::new(&config_full_path(&config)).exists() {
50 return return_error(format!(
51 "The configuration file `{config}` does not exist. Please provide a valid path."
52 ));
53 }
54 #[cfg(feature = "macro_debug")]
55 eprintln!("[gen culist support with {config:?}]");
56 let cuconfig = match read_config(&config) {
57 Ok(cuconfig) => cuconfig,
58 Err(e) => return return_error(e.to_string()),
59 };
60 let graph = cuconfig
61 .get_graph(None) .expect("Could not find the specified mission for gen_cumsgs");
63 let runtime_plan: CuExecutionLoop = match compute_runtime_plan(graph) {
64 Ok(plan) => plan,
65 Err(e) => return return_error(format!("Could not compute runtime plan: {e}")),
66 };
67
68 let all_tasks_member_ids: Vec<String> = graph
70 .get_all_nodes()
71 .iter()
72 .map(|(_, node)| utils::config_id_to_struct_member(node.get_id().as_str()))
73 .collect();
74
75 let taskid_order: Vec<usize> = runtime_plan
78 .steps
79 .iter()
80 .filter_map(|unit| match unit {
81 CuExecutionUnit::Step(step) => Some(step.node_id as usize),
82 _ => None,
83 })
84 .collect();
85
86 #[cfg(feature = "macro_debug")]
87 eprintln!(
88 "[The CuMsgs matching tasks ids are {:?}]",
89 taskid_order
90 .iter()
91 .map(|i| all_tasks_member_ids[*i].clone())
92 .collect::<Vec<_>>()
93 );
94
95 let support = gen_culist_support(&runtime_plan, &taskid_order, &all_tasks_member_ids);
96
97 let with_uses = quote! {
98 mod cumsgs {
99 use cu29::bincode::Encode;
100 use cu29::bincode::enc::Encoder;
101 use cu29::bincode::error::EncodeError;
102 use cu29::bincode::Decode;
103 use cu29::bincode::de::Decoder;
104 use cu29::bincode::error::DecodeError;
105 use cu29::copperlist::CopperList;
106 use cu29::cutask::CuMsgMetadata;
107 use cu29::cutask::CuMsg;
108 #support
109 }
110 use cumsgs::CuMsgs;
111 };
112 with_uses.into()
113}
114
115fn gen_culist_support(
117 runtime_plan: &CuExecutionLoop,
118 taskid_call_order: &[usize],
119 all_tasks_as_struct_member_name: &Vec<String>,
120) -> proc_macro2::TokenStream {
121 #[cfg(feature = "macro_debug")]
122 eprintln!("[Extract msgs types]");
123 let all_msgs_types_in_culist_order = extract_msg_types(runtime_plan);
124
125 let culist_size = all_msgs_types_in_culist_order.len();
126 let task_indices: Vec<_> = taskid_call_order
127 .iter()
128 .map(|i| syn::Index::from(*i))
129 .collect();
130
131 #[cfg(feature = "macro_debug")]
132 eprintln!("[build the copperlist struct]");
133 let msgs_types_tuple: TypeTuple = build_culist_tuple(&all_msgs_types_in_culist_order);
134
135 #[cfg(feature = "macro_debug")]
136 eprintln!("[build the copperlist tuple bincode support]");
137 let msgs_types_tuple_encode = build_culist_tuple_encode(&all_msgs_types_in_culist_order);
138 let msgs_types_tuple_decode = build_culist_tuple_decode(&all_msgs_types_in_culist_order);
139
140 #[cfg(feature = "macro_debug")]
141 eprintln!("[build the copperlist tuple debug support]");
142 let msgs_types_tuple_debug = build_culist_tuple_debug(&all_msgs_types_in_culist_order);
143
144 let collect_metadata_function = quote! {
145 pub fn collect_metadata<'a>(culist: &'a CuList) -> [&'a CuMsgMetadata; #culist_size] {
146 [#( &culist.msgs.0.#task_indices.metadata, )*]
147 }
148 };
149
150 let methods = itertools::multizip((all_tasks_as_struct_member_name, taskid_call_order)).map(
151 |(name, output_position)| {
152 let fn_name = format_ident!("get_{}_output", name);
153 let payload_type = all_msgs_types_in_culist_order[*output_position].clone();
154 let index = syn::Index::from(*output_position);
155 quote! {
156 pub fn #fn_name(&self) -> &CuMsg<#payload_type> {
157 &self.0.#index
158 }
159 }
160 },
161 );
162
163 quote! {
165 #collect_metadata_function
166
167 pub struct CuMsgs(pub #msgs_types_tuple);
168
169 pub type CuList = CopperList<CuMsgs>;
170
171 impl CuMsgs {
172 #(#methods)*
173
174 fn get_tuple(&self) -> &#msgs_types_tuple {
175 &self.0
176 }
177
178 fn get_tuple_mut(&mut self) -> &mut #msgs_types_tuple {
179 &mut self.0
180 }
181 }
182
183 #msgs_types_tuple_encode
185 #msgs_types_tuple_decode
186
187 #msgs_types_tuple_debug
189 }
190}
191
192fn gen_sim_support(runtime_plan: &CuExecutionLoop) -> proc_macro2::TokenStream {
193 #[cfg(feature = "macro_debug")]
194 eprintln!("[Sim: Build SimEnum]");
195 let plan_enum: Vec<proc_macro2::TokenStream> = runtime_plan
196 .steps
197 .iter()
198 .map(|unit| match unit {
199 CuExecutionUnit::Step(step) => {
200 let enum_entry_name = config_id_to_enum(step.node.get_id().as_str());
201 let enum_ident = Ident::new(&enum_entry_name, proc_macro2::Span::call_site());
202 let inputs: Vec<Type> = step
203 .input_msg_indices_types
204 .iter()
205 .map(|(_, t)| parse_str::<Type>(format!("CuMsg<{t}>").as_str()).unwrap())
206 .collect();
207 let output: Option<Type> = step
208 .output_msg_index_type
209 .as_ref()
210 .map(|(_, t)| parse_str::<Type>(format!("CuMsg<{t}>").as_str()).unwrap());
211 let no_output = parse_str::<Type>("CuMsg<()>").unwrap();
212 let output = output.as_ref().unwrap_or(&no_output);
213 quote! {
214 #enum_ident(cu29::simulation::CuTaskCallbackState<(#(&'a #inputs),*), &'a mut #output>)
215 }
216 }
217 CuExecutionUnit::Loop(_) => {
218 todo!("Needs to be implemented")
219 }
220 })
221 .collect();
222 quote! {
223 pub enum SimStep<'a> {
224 #(#plan_enum),*
225 }
226 }
227}
228
229#[proc_macro_attribute]
233pub fn copper_runtime(args: TokenStream, input: TokenStream) -> TokenStream {
234 #[cfg(feature = "macro_debug")]
235 eprintln!("[entry]");
236 let mut application_struct = parse_macro_input!(input as ItemStruct);
237 let application_name = &application_struct.ident;
238 let builder_name = format_ident!("{}Builder", application_name);
239
240 let mut config_file: Option<LitStr> = None;
241 let mut sim_mode = false;
242
243 let attribute_config_parser = parser(|meta| {
245 if meta.path.is_ident("config") {
246 config_file = Some(meta.value()?.parse()?);
247 Ok(())
248 } else if meta.path.is_ident("sim_mode") {
249 if meta.input.peek(syn::Token![=]) {
251 meta.input.parse::<syn::Token![=]>()?;
252 let value: syn::LitBool = meta.input.parse()?;
253 sim_mode = value.value();
254 Ok(())
255 } else {
256 sim_mode = true;
258 Ok(())
259 }
260 } else {
261 Err(meta.error("unsupported property"))
262 }
263 });
264
265 #[cfg(feature = "macro_debug")]
266 eprintln!("[parse]");
267 parse_macro_input!(args with attribute_config_parser);
269
270 let config_file = match config_file {
272 Some(file) => file.value(),
273 None => {
274 return return_error(
275 "Expected config file attribute like #[CopperRuntime(config = \"path\")]"
276 .to_string(),
277 )
278 }
279 };
280
281 if !std::path::Path::new(&config_full_path(&config_file)).exists() {
282 return return_error(format!(
283 "The configuration file `{config_file}` does not exist. Please provide a valid path."
284 ));
285 }
286
287 let copper_config = match read_config(&config_file) {
288 Ok(cuconfig) => cuconfig,
289 Err(e) => return return_error(e.to_string()),
290 };
291 let copper_config_content = match read_to_string(config_full_path(config_file.as_str())) {
292 Ok(ok) => ok,
293 Err(e) => return return_error(format!("Could not read the config file (should not happen because we just succeeded just before). {e}"))
294 };
295
296 #[cfg(feature = "macro_debug")]
297 eprintln!("[build monitor type]");
298 let monitor_type = if let Some(monitor_config) = copper_config.get_monitor_config() {
299 let monitor_type = parse_str::<Type>(monitor_config.get_type())
300 .expect("Could not transform the monitor type name into a Rust type.");
301 quote! { #monitor_type }
302 } else {
303 quote! { NoMonitor }
304 };
305
306 #[cfg(feature = "macro_debug")]
308 eprintln!("[build runtime field]");
309 let runtime_field: Field = if sim_mode {
311 parse_quote! {
312 copper_runtime: cu29::curuntime::CuRuntime<CuSimTasks, CuMsgs, #monitor_type, #DEFAULT_CLNB>
313 }
314 } else {
315 parse_quote! {
316 copper_runtime: cu29::curuntime::CuRuntime<CuTasks, CuMsgs, #monitor_type, #DEFAULT_CLNB>
317 }
318 };
319
320 #[cfg(feature = "macro_debug")]
321 eprintln!("[match struct anonymity]");
322 match &mut application_struct.fields {
323 Named(fields_named) => {
324 fields_named.named.push(runtime_field);
325 }
326 Unnamed(fields_unnamed) => {
327 fields_unnamed.unnamed.push(runtime_field);
328 }
329 Fields::Unit => {
330 panic!("This struct is a unit struct, it should have named or unnamed fields. use struct Something {{}} and not struct Something;")
331 }
332 };
333
334 let all_missions = copper_config.graphs.get_all_missions_graphs();
335 let mut all_missions_tokens = Vec::<proc_macro2::TokenStream>::new();
336 for (mission, graph) in &all_missions {
337 let mission_mod = parse_str::<Ident>(mission.as_str())
338 .expect("Could not make an identifier of the mission name");
339
340 #[cfg(feature = "macro_debug")]
341 eprintln!("[runtime plan for mission {mission}]");
342 let runtime_plan: CuExecutionLoop = match compute_runtime_plan(graph) {
343 Ok(plan) => plan,
344 Err(e) => return return_error(format!("Could not compute runtime plan: {e}")),
345 };
346 #[cfg(feature = "macro_debug")]
347 eprintln!("{runtime_plan:?}");
348
349 #[cfg(feature = "macro_debug")]
350 eprintln!("[extract tasks ids & types]");
351 let (all_tasks_ids, all_tasks_cutype, all_tasks_types_names, all_tasks_types) =
352 extract_tasks_types(graph);
353
354 let all_sim_tasks_types: Vec<Type> = all_tasks_ids
355 .iter()
356 .zip(&all_tasks_cutype)
357 .zip(&all_tasks_types)
358 .map(|((task_id, cutype), stype)| match cutype {
359 CuTaskType::Source => {
360 let msg_type = graph
361 .get_node_output_msg_type(task_id.as_str())
362 .unwrap_or_else(|| panic!("CuSrcTask {task_id} should have an outgoing connection with a valid output msg type"));
363 let sim_task_name = format!("cu29::simulation::CuSimSrcTask<{msg_type}>");
364 parse_str(sim_task_name.as_str()).unwrap_or_else(|_| panic!("Could not build the placeholder for simulation: {sim_task_name}"))
365 }
366 CuTaskType::Regular => stype.clone(),
367 CuTaskType::Sink => {
368 let msg_type = graph
369 .get_node_input_msg_type(task_id.as_str())
370 .unwrap_or_else(|| panic!("CuSinkTask {task_id} should have an incoming connection with a valid input msg type"));
371 let sim_task_name = format!("cu29::simulation::CuSimSinkTask<{msg_type}>");
372 parse_str(sim_task_name.as_str()).unwrap_or_else(|_| panic!("Could not build the placeholder for simulation: {sim_task_name}"))
373 }
374 })
375 .collect();
376
377 #[cfg(feature = "macro_debug")]
378 eprintln!("[build task tuples]");
379 let task_types_tuple: TypeTuple = parse_quote! {
382 (#(#all_tasks_types),*,)
383 };
384
385 let task_types_tuple_sim: TypeTuple = parse_quote! {
386 (#(#all_sim_tasks_types),*,)
387 };
388
389 #[cfg(feature = "macro_debug")]
390 eprintln!("[gen instances]");
391
392 let task_sim_instances_init_code = all_sim_tasks_types.iter().enumerate().map(|(index, ty)| {
393 let additional_error_info = format!(
394 "Failed to get create instance for {}, instance index {}.",
395 all_tasks_types_names[index], index
396 );
397
398 quote! {
399 <#ty>::new(all_instances_configs[#index]).map_err(|e| e.add_cause(#additional_error_info))?
400 }
401 }).collect::<Vec<_>>();
402
403 let (task_instances_init_code,
406 start_calls,
407 stop_calls,
408 preprocess_calls,
409 postprocess_calls): (Vec<_>, Vec<_>, Vec<_>, Vec<_>, Vec<_>) = itertools::multiunzip(all_tasks_types
410 .iter()
411 .enumerate()
412 .map(|(index, ty)| {
413 let task_index = int2sliceindex(index as u32);
414 let task_enum_name = config_id_to_enum(&all_tasks_ids[index]);
415 let enum_name = Ident::new(&task_enum_name, proc_macro2::Span::call_site());
416 let additional_error_info = format!(
417 "Failed to get create instance for {}, instance index {}.",
418 all_tasks_types_names[index], index
419 );
420 (
421 quote! {
422 #ty::new(all_instances_configs[#index]).map_err(|e| e.add_cause(#additional_error_info))?
423 },
424 {
425 let monitoring_action = quote! {
426 let decision = self.copper_runtime.monitor.process_error(#index, CuTaskState::Start, &error);
427 match decision {
428 Decision::Abort => {
429 debug!("Start: ABORT decision from monitoring. Task '{}' errored out \
430 during start. Aborting all the other starts.", #mission_mod::TASKS_IDS[#index]);
431 return Ok(());
432
433 }
434 Decision::Ignore => {
435 debug!("Start: IGNORE decision from monitoring. Task '{}' errored out \
436 during start. The runtime will continue.", #mission_mod::TASKS_IDS[#index]);
437 }
438 Decision::Shutdown => {
439 debug!("Start: SHUTDOWN decision from monitoring. Task '{}' errored out \
440 during start. The runtime cannot continue.", #mission_mod::TASKS_IDS[#index]);
441 return Err(CuError::new_with_cause("Task errored out during start.", error));
442 }
443 }
444 };
445
446 let call_sim_callback = if sim_mode {
447 quote! {
448 let ovr = sim_callback(SimStep::#enum_name(cu29::simulation::CuTaskCallbackState::Start));
450
451 let doit = if let cu29::simulation::SimOverride::Errored(reason) = ovr {
452 let error: CuError = reason.into();
453 #monitoring_action
454 false
455 }
456 else {
457 ovr == cu29::simulation::SimOverride::ExecuteByRuntime
458 };
459 }
460 } else {
461 quote! {
462 let doit = true; }
464 };
465
466
467 quote! {
468 #call_sim_callback
469 if doit {
470 let task = &mut self.copper_runtime.tasks.#task_index;
471 if let Err(error) = task.start(&self.copper_runtime.clock) {
472 #monitoring_action
473 }
474 }
475 }
476 },
477 {
478 let monitoring_action = quote! {
479 let decision = self.copper_runtime.monitor.process_error(#index, CuTaskState::Stop, &error);
480 match decision {
481 Decision::Abort => {
482 debug!("Stop: ABORT decision from monitoring. Task '{}' errored out \
483 during stop. Aborting all the other starts.", #mission_mod::TASKS_IDS[#index]);
484 return Ok(());
485
486 }
487 Decision::Ignore => {
488 debug!("Stop: IGNORE decision from monitoring. Task '{}' errored out \
489 during stop. The runtime will continue.", #mission_mod::TASKS_IDS[#index]);
490 }
491 Decision::Shutdown => {
492 debug!("Stop: SHUTDOWN decision from monitoring. Task '{}' errored out \
493 during stop. The runtime cannot continue.", #mission_mod::TASKS_IDS[#index]);
494 return Err(CuError::new_with_cause("Task errored out during stop.", error));
495 }
496 }
497 };
498 let call_sim_callback = if sim_mode {
499 quote! {
500 let ovr = sim_callback(SimStep::#enum_name(cu29::simulation::CuTaskCallbackState::Stop));
502
503 let doit = if let cu29::simulation::SimOverride::Errored(reason) = ovr {
504 let error: CuError = reason.into();
505 #monitoring_action
506 false
507 }
508 else {
509 ovr == cu29::simulation::SimOverride::ExecuteByRuntime
510 };
511 }
512 } else {
513 quote! {
514 let doit = true; }
516 };
517 quote! {
518 #call_sim_callback
519 if doit {
520 let task = &mut self.copper_runtime.tasks.#task_index;
521 if let Err(error) = task.stop(&self.copper_runtime.clock) {
522 #monitoring_action
523 }
524 }
525 }
526 },
527 {
528 let monitoring_action = quote! {
529 let decision = self.copper_runtime.monitor.process_error(#index, CuTaskState::Preprocess, &error);
530 match decision {
531 Decision::Abort => {
532 debug!("Preprocess: ABORT decision from monitoring. Task '{}' errored out \
533 during preprocess. Aborting all the other starts.", #mission_mod::TASKS_IDS[#index]);
534 return Ok(());
535
536 }
537 Decision::Ignore => {
538 debug!("Preprocess: IGNORE decision from monitoring. Task '{}' errored out \
539 during preprocess. The runtime will continue.", #mission_mod::TASKS_IDS[#index]);
540 }
541 Decision::Shutdown => {
542 debug!("Preprocess: SHUTDOWN decision from monitoring. Task '{}' errored out \
543 during preprocess. The runtime cannot continue.", #mission_mod::TASKS_IDS[#index]);
544 return Err(CuError::new_with_cause("Task errored out during preprocess.", error));
545 }
546 }
547 };
548 let call_sim_callback = if sim_mode {
549 quote! {
550 let ovr = sim_callback(SimStep::#enum_name(cu29::simulation::CuTaskCallbackState::Preprocess));
552
553 let doit = if let cu29::simulation::SimOverride::Errored(reason) = ovr {
554 let error: CuError = reason.into();
555 #monitoring_action
556 false
557 } else {
558 ovr == cu29::simulation::SimOverride::ExecuteByRuntime
559 };
560 }
561 } else {
562 quote! {
563 let doit = true; }
565 };
566 quote! {
567 #call_sim_callback
568 if doit {
569 let task = &mut self.copper_runtime.tasks.#task_index;
570 if let Err(error) = task.preprocess(&self.copper_runtime.clock) {
571 #monitoring_action
572 }
573 }
574 }
575 },
576 {
577 let monitoring_action = quote! {
578 let decision = self.copper_runtime.monitor.process_error(#index, CuTaskState::Postprocess, &error);
579 match decision {
580 Decision::Abort => {
581 debug!("Postprocess: ABORT decision from monitoring. Task '{}' errored out \
582 during postprocess. Aborting all the other starts.", #mission_mod::TASKS_IDS[#index]);
583 return Ok(());
584
585 }
586 Decision::Ignore => {
587 debug!("Postprocess: IGNORE decision from monitoring. Task '{}' errored out \
588 during postprocess. The runtime will continue.", #mission_mod::TASKS_IDS[#index]);
589 }
590 Decision::Shutdown => {
591 debug!("Postprocess: SHUTDOWN decision from monitoring. Task '{}' errored out \
592 during postprocess. The runtime cannot continue.", #mission_mod::TASKS_IDS[#index]);
593 return Err(CuError::new_with_cause("Task errored out during postprocess.", error));
594 }
595 }
596 };
597 let call_sim_callback = if sim_mode {
598 quote! {
599 let ovr = sim_callback(SimStep::#enum_name(cu29::simulation::CuTaskCallbackState::Postprocess));
601
602 let doit = if let cu29::simulation::SimOverride::Errored(reason) = ovr {
603 let error: CuError = reason.into();
604 #monitoring_action
605 false
606 } else {
607 ovr == cu29::simulation::SimOverride::ExecuteByRuntime
608 };
609 }
610 } else {
611 quote! {
612 let doit = true; }
614 };
615 quote! {
616 #call_sim_callback
617 if doit {
618 let task = &mut self.copper_runtime.tasks.#task_index;
619 if let Err(error) = task.postprocess(&self.copper_runtime.clock) {
620 #monitoring_action
621 }
622 }
623 }
624 }
625 )
626 })
627 );
628
629 let mut taskid_call_order: Vec<usize> = Vec::new();
632
633 let runtime_plan_code: Vec<proc_macro2::TokenStream> = runtime_plan.steps
634 .iter()
635 .map(|unit| {
636 match unit {
637 CuExecutionUnit::Step(step) => {
638 #[cfg(feature = "macro_debug")]
639 eprintln!(
640 "{} -> {} as {:?}. task_id: {} Input={:?}, Output={:?}",
641 step.node.get_id(),
642 step.node.get_type(),
643 step.task_type,
644 step.node_id,
645 step.input_msg_indices_types,
646 step.output_msg_index_type
647 );
648
649 let node_index = int2sliceindex(step.node_id);
650 let task_instance = quote! { self.copper_runtime.tasks.#node_index };
651 let comment_str = format!(
652 "/// {} ({:?}) Id:{} I:{:?} O:{:?}",
653 step.node.get_id(),
654 step.task_type,
655 step.node_id,
656 step.input_msg_indices_types,
657 step.output_msg_index_type
658 );
659 let comment_tokens: proc_macro2::TokenStream = parse_str(&comment_str).unwrap();
660 let tid = step.node_id as usize;
661 taskid_call_order.push(tid);
662
663 let task_enum_name = config_id_to_enum(&all_tasks_ids[tid]);
664 let enum_name = Ident::new(&task_enum_name, proc_macro2::Span::call_site());
665
666 let process_call = match step.task_type {
667 CuTaskType::Source => {
668 if let Some((index, _)) = &step.output_msg_index_type {
669 let output_culist_index = int2sliceindex(*index);
670
671 let monitoring_action = quote! {
672 debug!("Task {}: Error during process: {}", #mission_mod::TASKS_IDS[#tid], &error);
673 let decision = self.copper_runtime.monitor.process_error(#tid, CuTaskState::Process, &error);
674 match decision {
675 Decision::Abort => {
676 debug!("Process: ABORT decision from monitoring. Task '{}' errored out \
677 during process. Skipping the processing of CL {}.", #mission_mod::TASKS_IDS[#tid], id);
678 self.copper_runtime.monitor.process_copperlist(&#mission_mod::collect_metadata(&culist))?;
679 self.copper_runtime.end_of_processing(id);
680 return Ok(()); }
683 Decision::Ignore => {
684 debug!("Process: IGNORE decision from monitoring. Task '{}' errored out \
685 during process. The runtime will continue with a forced empty message.", #mission_mod::TASKS_IDS[#tid]);
686 let cumsg_output = &mut msgs.#output_culist_index;
687 cumsg_output.clear_payload();
688 }
689 Decision::Shutdown => {
690 debug!("Process: SHUTDOWN decision from monitoring. Task '{}' errored out \
691 during process. The runtime cannot continue.", #mission_mod::TASKS_IDS[#tid]);
692 return Err(CuError::new_with_cause("Task errored out during process.", error));
693 }
694 }
695 };
696 let call_sim_callback = if sim_mode {
697 quote! {
698 let doit = {
699 let cumsg_output = &mut msgs.#output_culist_index;
700 let state = cu29::simulation::CuTaskCallbackState::Process((), cumsg_output);
701 let ovr = sim_callback(SimStep::#enum_name(state));
702 if let cu29::simulation::SimOverride::Errored(reason) = ovr {
703 let error: CuError = reason.into();
704 #monitoring_action
705 false
706 } else {
707 ovr == cu29::simulation::SimOverride::ExecuteByRuntime
708 }
709 };
710 }
711 } else {
712 quote! {
713 let doit = true; }
715 };
716
717 quote! {
718 {
719 #comment_tokens
720 {
721 #call_sim_callback
722 let cumsg_output = &mut msgs.#output_culist_index;
723 cumsg_output.metadata.process_time.start = self.copper_runtime.clock.now().into();
724 let maybe_error = if doit {
725 #task_instance.process(&self.copper_runtime.clock, cumsg_output)
726 } else {
727 Ok(())
728 };
729 cumsg_output.metadata.process_time.end = self.copper_runtime.clock.now().into();
730 if let Err(error) = maybe_error {
731 #monitoring_action
732 }
733 }
734 }
735 }
736 } else {
737 panic!("Source task should have an output message index.");
738 }
739 }
740 CuTaskType::Sink => {
741 let indices = step.input_msg_indices_types.iter().map(|(index, _)| int2sliceindex(*index));
743 if let Some((output_index, _)) = &step.output_msg_index_type {
744 let output_culist_index = int2sliceindex(*output_index);
745
746 let monitoring_action = quote! {
747 debug!("Task {}: Error during process: {}", #mission_mod::TASKS_IDS[#tid], &error);
748 let decision = self.copper_runtime.monitor.process_error(#tid, CuTaskState::Process, &error);
749 match decision {
750 Decision::Abort => {
751 debug!("Process: ABORT decision from monitoring. Task '{}' errored out \
752 during process. Skipping the processing of CL {}.", #mission_mod::TASKS_IDS[#tid], id);
753 self.copper_runtime.monitor.process_copperlist(&#mission_mod::collect_metadata(&culist))?;
754 self.copper_runtime.end_of_processing(id);
755 return Ok(()); }
758 Decision::Ignore => {
759 debug!("Process: IGNORE decision from monitoring. Task '{}' errored out \
760 during process. The runtime will continue with a forced empty message.", #mission_mod::TASKS_IDS[#tid]);
761 let cumsg_output = &mut msgs.#output_culist_index;
762 cumsg_output.clear_payload();
763 }
764 Decision::Shutdown => {
765 debug!("Process: SHUTDOWN decision from monitoring. Task '{}' errored out \
766 during process. The runtime cannot continue.", #mission_mod::TASKS_IDS[#tid]);
767 return Err(CuError::new_with_cause("Task errored out during process.", error));
768 }
769 }
770 };
771
772 let call_sim_callback = if sim_mode {
773 quote! {
774 let doit = {
775 let cumsg_input = (#(&msgs.#indices),*);
776 let cumsg_output = &mut msgs.#output_culist_index;
778 let state = cu29::simulation::CuTaskCallbackState::Process(cumsg_input, cumsg_output);
779 let ovr = sim_callback(SimStep::#enum_name(state));
780
781 if let cu29::simulation::SimOverride::Errored(reason) = ovr {
782 let error: CuError = reason.into();
783 #monitoring_action
784 false
785 } else {
786 ovr == cu29::simulation::SimOverride::ExecuteByRuntime
787 }
788 };
789 }
790 } else {
791 quote! {
792 let doit = true; }
794 };
795
796 let indices = step.input_msg_indices_types.iter().map(|(index, _)| int2sliceindex(*index));
797 quote! {
798 {
799 #comment_tokens
800 #call_sim_callback
801 let cumsg_input = (#(&msgs.#indices),*);
802 let cumsg_output = &mut msgs.#output_culist_index;
804 cumsg_output.metadata.process_time.start = self.copper_runtime.clock.now().into();
805 let maybe_error = if doit {#task_instance.process(&self.copper_runtime.clock, cumsg_input)} else {Ok(())};
806 cumsg_output.metadata.process_time.end = self.copper_runtime.clock.now().into();
807 if let Err(error) = maybe_error {
808 #monitoring_action
809 }
810 }
811 }
812 } else {
813 panic!("Sink tasks should have a virtual output message index.");
814 }
815 }
816 CuTaskType::Regular => {
817 let indices = step.input_msg_indices_types.iter().map(|(index, _)| int2sliceindex(*index));
818 if let Some((output_index, _)) = &step.output_msg_index_type {
819 let output_culist_index = int2sliceindex(*output_index);
820
821 let monitoring_action = quote! {
822 debug!("Task {}: Error during process: {}", #mission_mod::TASKS_IDS[#tid], &error);
823 let decision = self.copper_runtime.monitor.process_error(#tid, CuTaskState::Process, &error);
824 match decision {
825 Decision::Abort => {
826 debug!("Process: ABORT decision from monitoring. Task '{}' errored out \
827 during process. Skipping the processing of CL {}.", #mission_mod::TASKS_IDS[#tid], id);
828 self.copper_runtime.monitor.process_copperlist(&#mission_mod::collect_metadata(&culist))?;
829 self.copper_runtime.end_of_processing(id);
830 return Ok(()); }
833 Decision::Ignore => {
834 debug!("Process: IGNORE decision from monitoring. Task '{}' errored out \
835 during process. The runtime will continue with a forced empty message.", #mission_mod::TASKS_IDS[#tid]);
836 let cumsg_output = &mut msgs.#output_culist_index;
837 cumsg_output.clear_payload();
838 }
839 Decision::Shutdown => {
840 debug!("Process: SHUTDOWN decision from monitoring. Task '{}' errored out \
841 during process. The runtime cannot continue.", #mission_mod::TASKS_IDS[#tid]);
842 return Err(CuError::new_with_cause("Task errored out during process.", error));
843 }
844 }
845 };
846
847 let call_sim_callback = if sim_mode {
848 quote! {
849 let doit = {
850 let cumsg_input = (#(&msgs.#indices),*);
851 let cumsg_output = &mut msgs.#output_culist_index;
852 let state = cu29::simulation::CuTaskCallbackState::Process(cumsg_input, cumsg_output);
853 let ovr = sim_callback(SimStep::#enum_name(state));
854
855 if let cu29::simulation::SimOverride::Errored(reason) = ovr {
856 let error: CuError = reason.into();
857 #monitoring_action
858 false
859 }
860 else {
861 ovr == cu29::simulation::SimOverride::ExecuteByRuntime
862 }
863 };
864 }
865 } else {
866 quote! {
867 let doit = true; }
869 };
870
871 let indices = step.input_msg_indices_types.iter().map(|(index, _)| int2sliceindex(*index));
872 quote! {
873 {
874 #comment_tokens
875 #call_sim_callback
876 let cumsg_input = (#(&msgs.#indices),*);
877 let cumsg_output = &mut msgs.#output_culist_index;
878 cumsg_output.metadata.process_time.start = self.copper_runtime.clock.now().into();
879 let maybe_error = if doit {#task_instance.process(&self.copper_runtime.clock, cumsg_input, cumsg_output)} else {Ok(())};
880 cumsg_output.metadata.process_time.end = self.copper_runtime.clock.now().into();
881 if let Err(error) = maybe_error {
882 #monitoring_action
883 }
884 }
885 }
886 } else {
887 panic!("Regular task should have an output message index.");
888 }
889 }
890 };
891
892 process_call
893 }
894 CuExecutionUnit::Loop(_) => todo!("Needs to be implemented"),
895 }
896 }).collect();
897 #[cfg(feature = "macro_debug")]
898 eprintln!("[Culist access order: {taskid_call_order:?}]");
899
900 let all_tasks_member_ids: Vec<String> = all_tasks_ids
902 .iter()
903 .map(|name| utils::config_id_to_struct_member(name.as_str()))
904 .collect();
905
906 #[cfg(feature = "macro_debug")]
907 eprintln!("[build the copperlist support]");
908 let culist_support: proc_macro2::TokenStream =
909 gen_culist_support(&runtime_plan, &taskid_call_order, &all_tasks_member_ids);
910
911 #[cfg(feature = "macro_debug")]
912 eprintln!("[build the sim support]");
913 let sim_support: proc_macro2::TokenStream = gen_sim_support(&runtime_plan);
914
915 let (new, run_one_iteration, start_all_tasks, stop_all_tasks, run) = if sim_mode {
916 (
917 quote! {
918 fn new(clock:RobotClock, unified_logger: Arc<Mutex<UnifiedLoggerWrite>>, config_override: Option<CuConfig>, sim_callback: &mut impl FnMut(SimStep) -> cu29::simulation::SimOverride) -> CuResult<Self>
919 },
920 quote! {
921 fn run_one_iteration(&mut self, sim_callback: &mut impl FnMut(SimStep) -> cu29::simulation::SimOverride) -> CuResult<()>
922 },
923 quote! {
924 fn start_all_tasks(&mut self, sim_callback: &mut impl FnMut(SimStep) -> cu29::simulation::SimOverride) -> CuResult<()>
925 },
926 quote! {
927 fn stop_all_tasks(&mut self, sim_callback: &mut impl FnMut(SimStep) -> cu29::simulation::SimOverride) -> CuResult<()>
928 },
929 quote! {
930 fn run(&mut self, sim_callback: &mut impl FnMut(SimStep) -> cu29::simulation::SimOverride) -> CuResult<()>
931 },
932 )
933 } else {
934 (
935 quote! {
936 fn new(clock:RobotClock, unified_logger: Arc<Mutex<UnifiedLoggerWrite>>, config_override: Option<CuConfig>) -> CuResult<Self>
937 },
938 quote! {
939 fn run_one_iteration(&mut self) -> CuResult<()>
940 },
941 quote! {
942 fn start_all_tasks(&mut self) -> CuResult<()>
943 },
944 quote! {
945 fn stop_all_tasks(&mut self) -> CuResult<()>
946 },
947 quote! {
948 fn run(&mut self) -> CuResult<()>
949 },
950 )
951 };
952
953 let sim_callback_arg = if sim_mode {
954 Some(quote!(sim_callback))
955 } else {
956 None
957 };
958
959 let sim_callback_on_new_calls = all_tasks_ids.iter().enumerate().map(|(i, id)| {
960 let enum_name = config_id_to_enum(id);
961 let enum_ident = Ident::new(&enum_name, Span::call_site());
962 quote! {
963 sim_callback(SimStep::#enum_ident(cu29::simulation::CuTaskCallbackState::New(all_instances_configs[#i].cloned())));
965 }
966 });
967
968 let sim_callback_on_new = if sim_mode {
969 Some(quote! {
970 let graph = config.get_graph(Some(#mission)).expect("Could not find the mission #mission");
971 let all_instances_configs: Vec<Option<&ComponentConfig>> = graph
972 .get_all_nodes()
973 .iter()
974 .map(|(_, node)| node.get_instance_config())
975 .collect();
976 #(#sim_callback_on_new_calls)*
977 })
978 } else {
979 None
980 };
981
982 #[cfg(feature = "macro_debug")]
983 eprintln!("[build the run methods]");
984 let run_methods = quote! {
985
986 #run_one_iteration {
987 #(#preprocess_calls)*
988 {
989 let mut culist: &mut _ = &mut self.copper_runtime.copper_lists_manager.create().expect("Ran out of space for copper lists"); let id = culist.id;
991 culist.change_state(cu29::copperlist::CopperListState::Processing);
992 {
993 let msgs = &mut culist.msgs.0;
994 #(#runtime_plan_code)*
995 } {
998 let md = #mission_mod::collect_metadata(&culist);
1000 let e2e = md.last().unwrap().process_time.end.unwrap() - md.first().unwrap().process_time.start.unwrap();
1001 let e2en: u64 = e2e.into();
1002 } self.copper_runtime.monitor.process_copperlist(&#mission_mod::collect_metadata(&culist))?;
1005 self.copper_runtime.end_of_processing(id);
1006
1007 }#(#postprocess_calls)*
1009 Ok(())
1010 }
1011
1012 #start_all_tasks {
1013 #(#start_calls)*
1014 self.copper_runtime.monitor.start(&self.copper_runtime.clock)?;
1015 Ok(())
1016 }
1017
1018 #stop_all_tasks {
1019 #(#stop_calls)*
1020 self.copper_runtime.monitor.stop(&self.copper_runtime.clock)?;
1021 Ok(())
1022 }
1023
1024 #run {
1025 self.start_all_tasks(#sim_callback_arg)?;
1026 let error = loop {
1027 let error = self.run_one_iteration(#sim_callback_arg);
1028 if error.is_err() {
1029 break error;
1030 }
1031 };
1032 debug!("A task errored out: {}", &error);
1033 self.stop_all_tasks(#sim_callback_arg)?;
1034 error
1035 }
1036 };
1037
1038 let tasks_type = if sim_mode {
1039 quote!(CuSimTasks)
1040 } else {
1041 quote!(CuTasks)
1042 };
1043
1044 let tasks_instanciator = if sim_mode {
1045 quote!(tasks_instanciator_sim)
1046 } else {
1047 quote!(tasks_instanciator)
1048 };
1049
1050 let app_impl_decl = if sim_mode {
1051 quote!(impl CuSimApplication for #application_name)
1052 } else {
1053 quote!(impl CuApplication for #application_name)
1054 };
1055 let simstep_type_decl = if sim_mode {
1056 quote!(
1057 type Step<'z> = SimStep<'z>;
1058 )
1059 } else {
1060 quote!()
1061 };
1062
1063 #[cfg(feature = "macro_debug")]
1064 eprintln!("[build result]");
1065
1066 let application_impl = quote! {
1067 #app_impl_decl {
1068 #simstep_type_decl
1069
1070 #new {
1071 let config_filename = #config_file;
1072 let config = if config_override.is_some() {
1073 let overridden_config = config_override.unwrap();
1074 debug!("CuConfig: Overridden programmatically: {}", &overridden_config.serialize_ron());
1075 overridden_config
1076 } else if std::path::Path::new(config_filename).exists() {
1077 debug!("CuConfig: Reading configuration from file: {}", config_filename);
1078 cu29::config::read_configuration(config_filename)?
1079 } else {
1080 let original_config = Self::get_original_config();
1081 debug!("CuConfig: Using the original configuration the project was compiled with: {}", &original_config);
1082 cu29::config::read_configuration_str(original_config, None)?
1083 };
1084
1085 let mut default_section_size = std::mem::size_of::<super::#mission_mod::CuList>() * 64;
1088 if let Some(section_size_mib) = config.logging.as_ref().and_then(|l| l.section_size_mib) {
1090 default_section_size = section_size_mib as usize * 1024usize * 1024usize;
1092 }
1093 let copperlist_stream = stream_write::<#mission_mod::CuList>(
1094 unified_logger.clone(),
1095 UnifiedLogType::CopperList,
1096 default_section_size,
1097 );
1101
1102 let application = Ok(#application_name {
1103 copper_runtime: CuRuntime::<#mission_mod::#tasks_type, #mission_mod::CuMsgs, #monitor_type, #DEFAULT_CLNB>::new(
1104 clock,
1105 &config,
1106 Some(#mission),
1107 #mission_mod::#tasks_instanciator,
1108 #mission_mod::monitor_instanciator,
1109 copperlist_stream)?,
1110 });
1111
1112 #sim_callback_on_new
1113
1114 application
1115 }
1116
1117 fn get_original_config() -> String {
1118 #copper_config_content.to_string()
1119 }
1120
1121 #run_methods
1122 }
1123 };
1124
1125 let (
1126 builder_struct,
1127 builder_new,
1128 builder_impl,
1129 builder_sim_callback_method,
1130 builder_build_sim_callback_arg,
1131 ) = if sim_mode {
1132 (
1133 quote! {
1134 pub struct #builder_name <'a, F> {
1135 clock: Option<RobotClock>,
1136 unified_logger: Option<Arc<Mutex<UnifiedLoggerWrite>>>,
1137 config_override: Option<CuConfig>,
1138 sim_callback: Option<&'a mut F>
1139 }
1140 },
1141 quote! {
1142 pub fn new() -> Self {
1143 Self {
1144 clock: None,
1145 unified_logger: None,
1146 config_override: None,
1147 sim_callback: None,
1148 }
1149 }
1150 },
1151 quote! {
1152 impl<'a, F> #builder_name <'a, F>
1153 where
1154 F: FnMut(SimStep) -> cu29::simulation::SimOverride,
1155 },
1156 Some(quote! {
1157 pub fn with_sim_callback(mut self, sim_callback: &'a mut F) -> Self
1158 {
1159 self.sim_callback = Some(sim_callback);
1160 self
1161 }
1162 }),
1163 Some(quote! {
1164 self.sim_callback
1165 .ok_or(CuError::from("Sim callback missing from builder"))?,
1166 }),
1167 )
1168 } else {
1169 (
1170 quote! {
1171 pub struct #builder_name {
1172 clock: Option<RobotClock>,
1173 unified_logger: Option<Arc<Mutex<UnifiedLoggerWrite>>>,
1174 config_override: Option<CuConfig>,
1175 }
1176 },
1177 quote! {
1178 pub fn new() -> Self {
1179 Self {
1180 clock: None,
1181 unified_logger: None,
1182 config_override: None,
1183 }
1184 }
1185 },
1186 quote! {
1187 impl #builder_name
1188 },
1189 None,
1190 None,
1191 )
1192 };
1193
1194 let application_builder = quote! {
1195 #builder_struct
1196
1197 #builder_impl
1198 {
1199 #builder_new
1200
1201 pub fn with_clock(mut self, clock: RobotClock) -> Self {
1202 self.clock = Some(clock);
1203 self
1204 }
1205
1206 pub fn with_unified_logger(mut self, unified_logger: Arc<Mutex<UnifiedLoggerWrite>>) -> Self {
1207 self.unified_logger = Some(unified_logger);
1208 self
1209 }
1210
1211 pub fn with_context(mut self, copper_ctx: &CopperContext) -> Self {
1212 self.clock = Some(copper_ctx.clock.clone());
1213 self.unified_logger = Some(copper_ctx.unified_logger.clone());
1214 self
1215 }
1216
1217 pub fn with_config(mut self, config_override: CuConfig) -> Self {
1218 self.config_override = Some(config_override);
1219 self
1220 }
1221
1222 #builder_sim_callback_method
1223
1224 pub fn build(self) -> CuResult<#application_name> {
1225 #application_name::new(
1226 self.clock
1227 .ok_or(CuError::from("Clock missing from builder"))?,
1228 self.unified_logger
1229 .ok_or(CuError::from("Unified logger missing from builder"))?,
1230 self.config_override,
1231 #builder_build_sim_callback_arg
1232 )
1233 }
1234 }
1235 };
1236
1237 let mission_mod_tokens = quote! {
1239 mod #mission_mod {
1240 use super::*; use cu29::bincode::Encode;
1243 use cu29::bincode::enc::Encoder;
1244 use cu29::bincode::error::EncodeError;
1245 use cu29::bincode::Decode;
1246 use cu29::bincode::de::Decoder;
1247 use cu29::bincode::error::DecodeError;
1248 use cu29::clock::RobotClock;
1249 use cu29::clock::OptionCuTime;
1250 use cu29::clock::ClockProvider;
1251 use cu29::config::CuConfig;
1252 use cu29::config::ComponentConfig;
1253 use cu29::config::MonitorConfig;
1254 use cu29::config::read_configuration;
1255 use cu29::config::read_configuration_str;
1256 use cu29::curuntime::CuRuntime;
1257 use cu29::curuntime::CopperContext;
1258 use cu29::CuResult;
1259 use cu29::CuError;
1260 use cu29::cutask::CuSrcTask;
1261 use cu29::cutask::CuSinkTask;
1262 use cu29::cutask::CuTask;
1263 use cu29::cutask::CuMsg;
1264 use cu29::cutask::CuMsgMetadata;
1265 use cu29::copperlist::CopperList;
1266 use cu29::monitoring::CuMonitor; use cu29::monitoring::NoMonitor;
1268 use cu29::monitoring::CuTaskState;
1269 use cu29::monitoring::Decision;
1270 use cu29::prelude::app::CuApplication;
1271 use cu29::prelude::app::CuSimApplication;
1272 use cu29::prelude::debug;
1273 use cu29::prelude::stream_write;
1274 use cu29::prelude::UnifiedLoggerWrite;
1275 use cu29::prelude::UnifiedLogType;
1276 use std::sync::Arc;
1277 use std::sync::Mutex;
1278
1279
1280 pub type CuTasks = #task_types_tuple;
1284
1285 pub type CuSimTasks = #task_types_tuple_sim;
1287
1288 pub const TASKS_IDS: &'static [&'static str] = &[#( #all_tasks_ids ),*];
1289
1290 #culist_support
1291
1292 #sim_support
1293
1294 pub fn tasks_instanciator(all_instances_configs: Vec<Option<&ComponentConfig>>) -> CuResult<CuTasks> {
1295 Ok(( #(#task_instances_init_code),*, ))
1296 }
1297
1298 pub fn tasks_instanciator_sim(all_instances_configs: Vec<Option<&ComponentConfig>>) -> CuResult<CuSimTasks> {
1299 Ok(( #(#task_sim_instances_init_code),*, ))
1300 }
1301
1302 pub fn monitor_instanciator(config: &CuConfig) -> #monitor_type {
1303 #monitor_type::new(config, #mission_mod::TASKS_IDS).expect("Failed to create the given monitor.")
1304 }
1305
1306 pub #application_struct
1308
1309 #application_impl
1310
1311 #application_builder
1312 }
1313
1314 };
1315 all_missions_tokens.push(mission_mod_tokens);
1316 }
1317
1318 let default_application_tokens = if all_missions.contains_key("default") {
1319 quote! {
1320 use default::#builder_name;
1321 use default::#application_name;
1322 }
1323 } else {
1324 quote! {}
1325 };
1326
1327 let result: proc_macro2::TokenStream = quote! {
1328 #(#all_missions_tokens)*
1329 #default_application_tokens
1330 };
1331
1332 #[cfg(feature = "macro_debug")]
1334 {
1335 let formatted_code = rustfmt_generated_code(result.to_string());
1336 eprintln!("\n === Gen. Runtime ===\n");
1337 eprintln!("{}", highlight_rust_code(formatted_code));
1338 eprintln!("\n === === === === === ===\n");
1339 }
1340 result.into()
1341}
1342
1343fn read_config(config_file: &str) -> CuResult<CuConfig> {
1344 let filename = config_full_path(config_file);
1345
1346 read_configuration(filename.as_str())
1347}
1348
1349fn config_full_path(config_file: &str) -> String {
1350 let mut config_full_path = utils::caller_crate_root();
1351 config_full_path.push(config_file);
1352 let filename = config_full_path
1353 .as_os_str()
1354 .to_str()
1355 .expect("Could not interpret the config file name");
1356 filename.to_string()
1357}
1358
1359fn extract_tasks_types(graph: &CuGraph) -> (Vec<String>, Vec<CuTaskType>, Vec<String>, Vec<Type>) {
1361 let all_id_nodes = graph.get_all_nodes();
1362
1363 let all_tasks_ids: Vec<String> = all_id_nodes
1365 .iter()
1366 .map(|(_, node)| node.get_id().to_string())
1367 .collect();
1368
1369 let all_task_cutype: Vec<CuTaskType> = all_id_nodes
1370 .iter()
1371 .map(|(id, _)| find_task_type_for_id(graph, *id))
1372 .collect();
1373
1374 let all_types_names: Vec<String> = all_id_nodes
1376 .iter()
1377 .map(|(_, node)| node.get_type().to_string())
1378 .collect();
1379
1380 let all_types: Vec<Type> = all_types_names
1382 .iter()
1383 .map(|name| {
1384 parse_str(name)
1385 .unwrap_or_else(|_| panic!("Could not transform {name} into a Task Rust type."))
1386 })
1387 .collect();
1388 (all_tasks_ids, all_task_cutype, all_types_names, all_types)
1389}
1390
1391fn extract_msg_types(runtime_plan: &CuExecutionLoop) -> Vec<Type> {
1392 runtime_plan
1393 .steps
1394 .iter()
1395 .filter_map(|unit| match unit {
1396 CuExecutionUnit::Step(step) => {
1397 if let Some((_, output_msg_type)) = &step.output_msg_index_type {
1398 Some(
1399 parse_str::<Type>(output_msg_type.as_str()).unwrap_or_else(|_| {
1400 panic!(
1401 "Could not transform {output_msg_type} into a message Rust type."
1402 )
1403 }),
1404 )
1405 } else {
1406 None
1407 }
1408 }
1409 CuExecutionUnit::Loop(_) => todo!("Needs to be implemented"),
1410 })
1411 .collect()
1412}
1413
1414fn build_culist_tuple(all_msgs_types_in_culist_order: &[Type]) -> TypeTuple {
1416 if all_msgs_types_in_culist_order.is_empty() {
1417 parse_quote! { () }
1418 } else {
1419 parse_quote! {
1420 ( #( CuMsg<#all_msgs_types_in_culist_order> ),* )
1421 }
1422 }
1423}
1424
1425fn build_culist_tuple_encode(all_msgs_types_in_culist_order: &[Type]) -> ItemImpl {
1427 let indices: Vec<usize> = (0..all_msgs_types_in_culist_order.len()).collect();
1428
1429 let encode_fields: Vec<_> = indices
1431 .iter()
1432 .map(|i| {
1433 let idx = syn::Index::from(*i);
1434 quote! { self.0.#idx.encode(encoder)?; }
1435 })
1436 .collect();
1437
1438 parse_quote! {
1439 impl Encode for CuMsgs {
1440 fn encode<E: Encoder>(&self, encoder: &mut E) -> Result<(), EncodeError> {
1441 #(#encode_fields)*
1442 Ok(())
1443 }
1444 }
1445 }
1446}
1447
1448fn build_culist_tuple_decode(all_msgs_types_in_culist_order: &[Type]) -> ItemImpl {
1450 let indices: Vec<usize> = (0..all_msgs_types_in_culist_order.len()).collect();
1451
1452 let decode_fields: Vec<_> = indices
1454 .iter()
1455 .map(|i| {
1456 let t = &all_msgs_types_in_culist_order[*i];
1457 quote! { CuMsg::<#t>::decode(decoder)? }
1458 })
1459 .collect();
1460
1461 parse_quote! {
1462 impl Decode<()> for CuMsgs {
1463 fn decode<D: Decoder<Context=()>>(decoder: &mut D) -> Result<Self, DecodeError> {
1464 Ok(CuMsgs ((
1465 #(#decode_fields),*
1466 )))
1467 }
1468 }
1469 }
1470}
1471
1472fn build_culist_tuple_debug(all_msgs_types_in_culist_order: &[Type]) -> ItemImpl {
1473 let indices: Vec<usize> = (0..all_msgs_types_in_culist_order.len()).collect();
1474
1475 let debug_fields: Vec<_> = indices
1476 .iter()
1477 .map(|i| {
1478 let idx = syn::Index::from(*i);
1479 quote! { .field(&self.0.#idx) }
1480 })
1481 .collect();
1482
1483 parse_quote! {
1484 impl std::fmt::Debug for CuMsgs {
1485 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1486 f.debug_tuple("CuMsgs")
1487 #(#debug_fields)*
1488 .finish()
1489 }
1490 }
1491 }
1492}
1493
1494#[cfg(test)]
1495mod tests {
1496 #[test]
1498 fn test_compile_fail() {
1499 let t = trybuild::TestCases::new();
1500 t.compile_fail("tests/compile_fail/*/*.rs");
1501 }
1502}