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