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