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::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 use cu29::prelude::ErasedCuMsg;
109 use cu29::prelude::ErasedCuMsgs;
110 use cu29::prelude::MatchingTasks;
111 use cu29::prelude::Serialize;
112 #support
113 }
114 use cumsgs::CuMsgs;
115 };
116 with_uses.into()
117}
118
119fn gen_culist_support(
121 runtime_plan: &CuExecutionLoop,
122 taskid_call_order: &[usize],
123 all_tasks_as_struct_member_name: &Vec<String>,
124) -> proc_macro2::TokenStream {
125 #[cfg(feature = "macro_debug")]
126 eprintln!("[Extract msgs types]");
127 let all_msgs_types_in_culist_order = extract_msg_types(runtime_plan);
128
129 let culist_size = all_msgs_types_in_culist_order.len();
130 let task_indices: Vec<_> = taskid_call_order
131 .iter()
132 .map(|i| syn::Index::from(*i))
133 .collect();
134
135 #[cfg(feature = "macro_debug")]
136 eprintln!("[build the copperlist struct]");
137 let msgs_types_tuple: TypeTuple = build_culist_tuple(&all_msgs_types_in_culist_order);
138
139 #[cfg(feature = "macro_debug")]
140 eprintln!("[build the copperlist tuple bincode support]");
141 let msgs_types_tuple_encode = build_culist_tuple_encode(&all_msgs_types_in_culist_order);
142 let msgs_types_tuple_decode = build_culist_tuple_decode(&all_msgs_types_in_culist_order);
143
144 #[cfg(feature = "macro_debug")]
145 eprintln!("[build the copperlist tuple debug support]");
146 let msgs_types_tuple_debug = build_culist_tuple_debug(&all_msgs_types_in_culist_order);
147
148 #[cfg(feature = "macro_debug")]
149 eprintln!("[build the copperlist tuple serialize support]");
150 let msgs_types_tuple_serialize = build_culist_tuple_serialize(&all_msgs_types_in_culist_order);
151
152 let erasedmsg_trait_impl = build_culist_erasedcumsgs(&all_msgs_types_in_culist_order);
153
154 let collect_metadata_function = quote! {
155 pub fn collect_metadata<'a>(culist: &'a CuList) -> [&'a CuMsgMetadata; #culist_size] {
156 [#( &culist.msgs.0.#task_indices.metadata, )*]
157 }
158 };
159
160 let methods = itertools::multizip((all_tasks_as_struct_member_name, taskid_call_order)).map(
161 |(name, output_position)| {
162 let fn_name = format_ident!("get_{}_output", name);
163 let payload_type = all_msgs_types_in_culist_order[*output_position].clone();
164 let index = syn::Index::from(*output_position);
165 quote! {
166 #[allow(dead_code)]
167 pub fn #fn_name(&self) -> &CuMsg<#payload_type> {
168 &self.0.#index
169 }
170 }
171 },
172 );
173
174 quote! {
176 #collect_metadata_function
177
178 pub struct CuMsgs(pub #msgs_types_tuple);
179
180 pub type CuList = CopperList<CuMsgs>;
181
182 impl CuMsgs {
183 #(#methods)*
184
185 #[allow(dead_code)]
186 fn get_tuple(&self) -> &#msgs_types_tuple {
187 &self.0
188 }
189
190 #[allow(dead_code)]
191 fn get_tuple_mut(&mut self) -> &mut #msgs_types_tuple {
192 &mut self.0
193 }
194 }
195
196 impl MatchingTasks for CuMsgs {
197 #[allow(dead_code)]
198 fn get_all_task_ids() -> &'static [&'static str] {
199 &[#(#all_tasks_as_struct_member_name),*]
200 }
201 }
202
203 #msgs_types_tuple_encode
205 #msgs_types_tuple_decode
206
207 #msgs_types_tuple_debug
209
210 #msgs_types_tuple_serialize
212
213 #erasedmsg_trait_impl
215 }
216}
217
218fn gen_sim_support(runtime_plan: &CuExecutionLoop) -> proc_macro2::TokenStream {
219 #[cfg(feature = "macro_debug")]
220 eprintln!("[Sim: Build SimEnum]");
221 let plan_enum: Vec<proc_macro2::TokenStream> = runtime_plan
222 .steps
223 .iter()
224 .map(|unit| match unit {
225 CuExecutionUnit::Step(step) => {
226 let enum_entry_name = config_id_to_enum(step.node.get_id().as_str());
227 let enum_ident = Ident::new(&enum_entry_name, proc_macro2::Span::call_site());
228 let inputs: Vec<Type> = step
229 .input_msg_indices_types
230 .iter()
231 .map(|(_, t)| parse_str::<Type>(format!("CuMsg<{t}>").as_str()).unwrap())
232 .collect();
233 let output: Option<Type> = step
234 .output_msg_index_type
235 .as_ref()
236 .map(|(_, t)| parse_str::<Type>(format!("CuMsg<{t}>").as_str()).unwrap());
237 let no_output = parse_str::<Type>("CuMsg<()>").unwrap();
238 let output = output.as_ref().unwrap_or(&no_output);
239
240 let inputs_type = if inputs.len() == 1 {
241 quote! { &'a #(#inputs)* }
242 } else {
243 quote! { (#(&'a #inputs),*) }
244 };
245
246 quote! {
247 #enum_ident(cu29::simulation::CuTaskCallbackState<#inputs_type, &'a mut #output>)
248 }
249 }
250 CuExecutionUnit::Loop(_) => {
251 todo!("Needs to be implemented")
252 }
253 })
254 .collect();
255 quote! {
256 #[allow(dead_code)]
258 pub enum SimStep<'a> {
259 #(#plan_enum),*
260 }
261 }
262}
263
264#[proc_macro_attribute]
268pub fn copper_runtime(args: TokenStream, input: TokenStream) -> TokenStream {
269 #[cfg(feature = "macro_debug")]
270 eprintln!("[entry]");
271 let mut application_struct = parse_macro_input!(input as ItemStruct);
272 let application_name = &application_struct.ident;
273 let builder_name = format_ident!("{}Builder", application_name);
274
275 let mut config_file: Option<LitStr> = None;
276 let mut sim_mode = false;
277
278 let attribute_config_parser = parser(|meta| {
280 if meta.path.is_ident("config") {
281 config_file = Some(meta.value()?.parse()?);
282 Ok(())
283 } else if meta.path.is_ident("sim_mode") {
284 if meta.input.peek(syn::Token![=]) {
286 meta.input.parse::<syn::Token![=]>()?;
287 let value: syn::LitBool = meta.input.parse()?;
288 sim_mode = value.value();
289 Ok(())
290 } else {
291 sim_mode = true;
293 Ok(())
294 }
295 } else {
296 Err(meta.error("unsupported property"))
297 }
298 });
299
300 #[cfg(feature = "macro_debug")]
301 eprintln!("[parse]");
302 parse_macro_input!(args with attribute_config_parser);
304
305 let config_file = match config_file {
307 Some(file) => file.value(),
308 None => {
309 return return_error(
310 "Expected config file attribute like #[CopperRuntime(config = \"path\")]"
311 .to_string(),
312 )
313 }
314 };
315
316 if !std::path::Path::new(&config_full_path(&config_file)).exists() {
317 return return_error(format!(
318 "The configuration file `{config_file}` does not exist. Please provide a valid path."
319 ));
320 }
321
322 let copper_config = match read_config(&config_file) {
323 Ok(cuconfig) => cuconfig,
324 Err(e) => return return_error(e.to_string()),
325 };
326 let copper_config_content = match read_to_string(config_full_path(config_file.as_str())) {
327 Ok(ok) => ok,
328 Err(e) => return return_error(format!("Could not read the config file (should not happen because we just succeeded just before). {e}"))
329 };
330
331 #[cfg(feature = "macro_debug")]
332 eprintln!("[build monitor type]");
333 let monitor_type = if let Some(monitor_config) = copper_config.get_monitor_config() {
334 let monitor_type = parse_str::<Type>(monitor_config.get_type())
335 .expect("Could not transform the monitor type name into a Rust type.");
336 quote! { #monitor_type }
337 } else {
338 quote! { NoMonitor }
339 };
340
341 #[cfg(feature = "macro_debug")]
343 eprintln!("[build runtime field]");
344 let runtime_field: Field = if sim_mode {
346 parse_quote! {
347 copper_runtime: cu29::curuntime::CuRuntime<CuSimTasks, CuMsgs, #monitor_type, #DEFAULT_CLNB>
348 }
349 } else {
350 parse_quote! {
351 copper_runtime: cu29::curuntime::CuRuntime<CuTasks, CuMsgs, #monitor_type, #DEFAULT_CLNB>
352 }
353 };
354
355 #[cfg(feature = "macro_debug")]
356 eprintln!("[match struct anonymity]");
357 match &mut application_struct.fields {
358 Named(fields_named) => {
359 fields_named.named.push(runtime_field);
360 }
361 Unnamed(fields_unnamed) => {
362 fields_unnamed.unnamed.push(runtime_field);
363 }
364 Fields::Unit => {
365 panic!("This struct is a unit struct, it should have named or unnamed fields. use struct Something {{}} and not struct Something;")
366 }
367 };
368
369 let all_missions = copper_config.graphs.get_all_missions_graphs();
370 let mut all_missions_tokens = Vec::<proc_macro2::TokenStream>::new();
371 for (mission, graph) in &all_missions {
372 let mission_mod = parse_str::<Ident>(mission.as_str())
373 .expect("Could not make an identifier of the mission name");
374
375 #[cfg(feature = "macro_debug")]
376 eprintln!("[runtime plan for mission {mission}]");
377 let runtime_plan: CuExecutionLoop = match compute_runtime_plan(graph) {
378 Ok(plan) => plan,
379 Err(e) => return return_error(format!("Could not compute runtime plan: {e}")),
380 };
381 #[cfg(feature = "macro_debug")]
382 eprintln!("{runtime_plan:?}");
383
384 #[cfg(feature = "macro_debug")]
385 eprintln!("[extract tasks ids & types]");
386 let (all_tasks_ids, all_tasks_cutype, all_tasks_types_names, all_tasks_types) =
387 extract_tasks_types(graph);
388
389 let all_sim_tasks_types: Vec<Type> = all_tasks_ids
390 .iter()
391 .zip(&all_tasks_cutype)
392 .zip(&all_tasks_types)
393 .map(|((task_id, cutype), stype)| match cutype {
394 CuTaskType::Source => {
395 let msg_type = graph
396 .get_node_output_msg_type(task_id.as_str())
397 .unwrap_or_else(|| panic!("CuSrcTask {task_id} should have an outgoing connection with a valid output msg type"));
398 let sim_task_name = format!("cu29::simulation::CuSimSrcTask<{msg_type}>");
399 parse_str(sim_task_name.as_str()).unwrap_or_else(|_| panic!("Could not build the placeholder for simulation: {sim_task_name}"))
400 }
401 CuTaskType::Regular => stype.clone(),
402 CuTaskType::Sink => {
403 let msg_type = graph
404 .get_node_input_msg_type(task_id.as_str())
405 .unwrap_or_else(|| panic!("CuSinkTask {task_id} should have an incoming connection with a valid input msg type"));
406 let sim_task_name = format!("cu29::simulation::CuSimSinkTask<{msg_type}>");
407 parse_str(sim_task_name.as_str()).unwrap_or_else(|_| panic!("Could not build the placeholder for simulation: {sim_task_name}"))
408 }
409 })
410 .collect();
411
412 #[cfg(feature = "macro_debug")]
413 eprintln!("[build task tuples]");
414 let task_types_tuple: TypeTuple = parse_quote! {
417 (#(#all_tasks_types),*,)
418 };
419
420 let task_types_tuple_sim: TypeTuple = parse_quote! {
421 (#(#all_sim_tasks_types),*,)
422 };
423
424 #[cfg(feature = "macro_debug")]
425 eprintln!("[gen instances]");
426
427 let task_sim_instances_init_code = all_sim_tasks_types.iter().enumerate().map(|(index, ty)| {
428 let additional_error_info = format!(
429 "Failed to get create instance for {}, instance index {}.",
430 all_tasks_types_names[index], index
431 );
432
433 quote! {
434 <#ty>::new(all_instances_configs[#index]).map_err(|e| e.add_cause(#additional_error_info))?
435 }
436 }).collect::<Vec<_>>();
437
438 let (task_instances_init_code,
441 task_restore_code,
442 start_calls,
443 stop_calls,
444 preprocess_calls,
445 postprocess_calls): (Vec<_>, Vec<_>, Vec<_>, Vec<_>, Vec<_>, Vec<_>) = itertools::multiunzip(all_tasks_types
446 .iter()
447 .enumerate()
448 .map(|(index, ty)| {
449 let task_index = int2sliceindex(index as u32);
450 let task_tuple_index = syn::Index::from(index);
451 let task_enum_name = config_id_to_enum(&all_tasks_ids[index]);
452 let enum_name = Ident::new(&task_enum_name, proc_macro2::Span::call_site());
453 let additional_error_info = format!(
454 "Failed to get create instance for {}, instance index {}.",
455 all_tasks_types_names[index], index
456 );
457 ( quote! {
459 #ty::new(all_instances_configs[#index]).map_err(|e| e.add_cause(#additional_error_info))?
460 },
461 quote! {
463 tasks.#task_tuple_index.thaw(&mut decoder).map_err(|e| CuError::new_with_cause("Failed to thaw", e))?
464 },
465 { let monitoring_action = quote! {
467 let decision = self.copper_runtime.monitor.process_error(#index, CuTaskState::Start, &error);
468 match decision {
469 Decision::Abort => {
470 debug!("Start: ABORT decision from monitoring. Task '{}' errored out \
471 during start. Aborting all the other starts.", #mission_mod::TASKS_IDS[#index]);
472 return Ok(());
473
474 }
475 Decision::Ignore => {
476 debug!("Start: IGNORE decision from monitoring. Task '{}' errored out \
477 during start. The runtime will continue.", #mission_mod::TASKS_IDS[#index]);
478 }
479 Decision::Shutdown => {
480 debug!("Start: SHUTDOWN decision from monitoring. Task '{}' errored out \
481 during start. The runtime cannot continue.", #mission_mod::TASKS_IDS[#index]);
482 return Err(CuError::new_with_cause("Task errored out during start.", error));
483 }
484 }
485 };
486
487 let call_sim_callback = if sim_mode {
488 quote! {
489 let ovr = sim_callback(SimStep::#enum_name(cu29::simulation::CuTaskCallbackState::Start));
491
492 let doit = if let cu29::simulation::SimOverride::Errored(reason) = ovr {
493 let error: CuError = reason.into();
494 #monitoring_action
495 false
496 }
497 else {
498 ovr == cu29::simulation::SimOverride::ExecuteByRuntime
499 };
500 }
501 } else {
502 quote! {
503 let doit = true; }
505 };
506
507
508 quote! {
509 #call_sim_callback
510 if doit {
511 let task = &mut self.copper_runtime.tasks.#task_index;
512 if let Err(error) = task.start(&self.copper_runtime.clock) {
513 #monitoring_action
514 }
515 }
516 }
517 },
518 { let monitoring_action = quote! {
520 let decision = self.copper_runtime.monitor.process_error(#index, CuTaskState::Stop, &error);
521 match decision {
522 Decision::Abort => {
523 debug!("Stop: ABORT decision from monitoring. Task '{}' errored out \
524 during stop. Aborting all the other starts.", #mission_mod::TASKS_IDS[#index]);
525 return Ok(());
526
527 }
528 Decision::Ignore => {
529 debug!("Stop: IGNORE decision from monitoring. Task '{}' errored out \
530 during stop. The runtime will continue.", #mission_mod::TASKS_IDS[#index]);
531 }
532 Decision::Shutdown => {
533 debug!("Stop: SHUTDOWN decision from monitoring. Task '{}' errored out \
534 during stop. The runtime cannot continue.", #mission_mod::TASKS_IDS[#index]);
535 return Err(CuError::new_with_cause("Task errored out during stop.", error));
536 }
537 }
538 };
539 let call_sim_callback = if sim_mode {
540 quote! {
541 let ovr = sim_callback(SimStep::#enum_name(cu29::simulation::CuTaskCallbackState::Stop));
543
544 let doit = if let cu29::simulation::SimOverride::Errored(reason) = ovr {
545 let error: CuError = reason.into();
546 #monitoring_action
547 false
548 }
549 else {
550 ovr == cu29::simulation::SimOverride::ExecuteByRuntime
551 };
552 }
553 } else {
554 quote! {
555 let doit = true; }
557 };
558 quote! {
559 #call_sim_callback
560 if doit {
561 let task = &mut self.copper_runtime.tasks.#task_index;
562 if let Err(error) = task.stop(&self.copper_runtime.clock) {
563 #monitoring_action
564 }
565 }
566 }
567 },
568 { let monitoring_action = quote! {
570 let decision = monitor.process_error(#index, CuTaskState::Preprocess, &error);
571 match decision {
572 Decision::Abort => {
573 debug!("Preprocess: ABORT decision from monitoring. Task '{}' errored out \
574 during preprocess. Aborting all the other starts.", #mission_mod::TASKS_IDS[#index]);
575 return Ok(());
576
577 }
578 Decision::Ignore => {
579 debug!("Preprocess: IGNORE decision from monitoring. Task '{}' errored out \
580 during preprocess. The runtime will continue.", #mission_mod::TASKS_IDS[#index]);
581 }
582 Decision::Shutdown => {
583 debug!("Preprocess: SHUTDOWN decision from monitoring. Task '{}' errored out \
584 during preprocess. The runtime cannot continue.", #mission_mod::TASKS_IDS[#index]);
585 return Err(CuError::new_with_cause("Task errored out during preprocess.", error));
586 }
587 }
588 };
589 let call_sim_callback = if sim_mode {
590 quote! {
591 let ovr = sim_callback(SimStep::#enum_name(cu29::simulation::CuTaskCallbackState::Preprocess));
593
594 let doit = if let cu29::simulation::SimOverride::Errored(reason) = ovr {
595 let error: CuError = reason.into();
596 #monitoring_action
597 false
598 } else {
599 ovr == cu29::simulation::SimOverride::ExecuteByRuntime
600 };
601 }
602 } else {
603 quote! {
604 let doit = true; }
606 };
607 quote! {
608 #call_sim_callback
609 if doit {
610 if let Err(error) = tasks.#task_index.preprocess(clock) {
611 #monitoring_action
612 }
613 }
614 }
615 },
616 { let monitoring_action = quote! {
618 let decision = monitor.process_error(#index, CuTaskState::Postprocess, &error);
619 match decision {
620 Decision::Abort => {
621 debug!("Postprocess: ABORT decision from monitoring. Task '{}' errored out \
622 during postprocess. Aborting all the other starts.", #mission_mod::TASKS_IDS[#index]);
623 return Ok(());
624
625 }
626 Decision::Ignore => {
627 debug!("Postprocess: IGNORE decision from monitoring. Task '{}' errored out \
628 during postprocess. The runtime will continue.", #mission_mod::TASKS_IDS[#index]);
629 }
630 Decision::Shutdown => {
631 debug!("Postprocess: SHUTDOWN decision from monitoring. Task '{}' errored out \
632 during postprocess. The runtime cannot continue.", #mission_mod::TASKS_IDS[#index]);
633 return Err(CuError::new_with_cause("Task errored out during postprocess.", error));
634 }
635 }
636 };
637 let call_sim_callback = if sim_mode {
638 quote! {
639 let ovr = sim_callback(SimStep::#enum_name(cu29::simulation::CuTaskCallbackState::Postprocess));
641
642 let doit = if let cu29::simulation::SimOverride::Errored(reason) = ovr {
643 let error: CuError = reason.into();
644 #monitoring_action
645 false
646 } else {
647 ovr == cu29::simulation::SimOverride::ExecuteByRuntime
648 };
649 }
650 } else {
651 quote! {
652 let doit = true; }
654 };
655 quote! {
656 #call_sim_callback
657 if doit {
658 if let Err(error) = tasks.#task_index.postprocess(clock) {
659 #monitoring_action
660 }
661 }
662 }
663 }
664 )
665 })
666 );
667
668 let mut taskid_call_order: Vec<usize> = Vec::new();
671
672 let runtime_plan_code: Vec<proc_macro2::TokenStream> = runtime_plan.steps
673 .iter()
674 .map(|unit| {
675 match unit {
676 CuExecutionUnit::Step(step) => {
677 #[cfg(feature = "macro_debug")]
678 eprintln!(
679 "{} -> {} as {:?}. task_id: {} Input={:?}, Output={:?}",
680 step.node.get_id(),
681 step.node.get_type(),
682 step.task_type,
683 step.node_id,
684 step.input_msg_indices_types,
685 step.output_msg_index_type
686 );
687
688 let node_index = int2sliceindex(step.node_id);
689 let task_instance = quote! { tasks.#node_index };
690 let comment_str = format!(
691 "DEBUG ->> {} ({:?}) Id:{} I:{:?} O:{:?}",
692 step.node.get_id(),
693 step.task_type,
694 step.node_id,
695 step.input_msg_indices_types,
696 step.output_msg_index_type
697 );
698 let comment_tokens = quote! {
699 {
700 let _ = stringify!(#comment_str);
701 }
702 };
703 let tid = step.node_id as usize;
705 taskid_call_order.push(tid);
706
707 let task_enum_name = config_id_to_enum(&all_tasks_ids[tid]);
708 let enum_name = Ident::new(&task_enum_name, proc_macro2::Span::call_site());
709
710 let process_call = match step.task_type {
711 CuTaskType::Source => {
712 if let Some((index, _)) = &step.output_msg_index_type {
713 let output_culist_index = int2sliceindex(*index);
714
715 let monitoring_action = quote! {
716 debug!("Task {}: Error during process: {}", #mission_mod::TASKS_IDS[#tid], &error);
717 let decision = monitor.process_error(#tid, CuTaskState::Process, &error);
718 match decision {
719 Decision::Abort => {
720 debug!("Process: ABORT decision from monitoring. Task '{}' errored out \
721 during process. Skipping the processing of CL {}.", #mission_mod::TASKS_IDS[#tid], clid);
722 monitor.process_copperlist(&#mission_mod::collect_metadata(&culist))?;
723 cl_manager.end_of_processing(clid)?;
724 return Ok(()); }
727 Decision::Ignore => {
728 debug!("Process: IGNORE decision from monitoring. Task '{}' errored out \
729 during process. The runtime will continue with a forced empty message.", #mission_mod::TASKS_IDS[#tid]);
730 let cumsg_output = &mut msgs.#output_culist_index;
731 cumsg_output.clear_payload();
732 }
733 Decision::Shutdown => {
734 debug!("Process: SHUTDOWN decision from monitoring. Task '{}' errored out \
735 during process. The runtime cannot continue.", #mission_mod::TASKS_IDS[#tid]);
736 return Err(CuError::new_with_cause("Task errored out during process.", error));
737 }
738 }
739 };
740 let call_sim_callback = if sim_mode {
741 quote! {
742 let doit = {
743 let cumsg_output = &mut msgs.#output_culist_index;
744 let state = cu29::simulation::CuTaskCallbackState::Process((), cumsg_output);
745 let ovr = sim_callback(SimStep::#enum_name(state));
746 if let cu29::simulation::SimOverride::Errored(reason) = ovr {
747 let error: CuError = reason.into();
748 #monitoring_action
749 false
750 } else {
751 ovr == cu29::simulation::SimOverride::ExecuteByRuntime
752 }
753 };
754 }
755 } else {
756 quote! {
757 let doit = true; }
759 };
760
761 quote! {
762 {
763 #comment_tokens
764 {
765 kf_manager.freeze_task(clid, &#task_instance)?;
767 #call_sim_callback
768 let cumsg_output = &mut msgs.#output_culist_index;
769 cumsg_output.metadata.process_time.start = clock.now().into();
770 let maybe_error = if doit {
771 #task_instance.process(clock, cumsg_output)
772 } else {
773 Ok(())
774 };
775 cumsg_output.metadata.process_time.end = clock.now().into();
776 if let Err(error) = maybe_error {
777 #monitoring_action
778 }
779 }
780 }
781 }
782 } else {
783 panic!("Source task should have an output message index.");
784 }
785 }
786 CuTaskType::Sink => {
787 let indices = step.input_msg_indices_types.iter().map(|(index, _)| int2sliceindex(*index));
789 if let Some((output_index, _)) = &step.output_msg_index_type {
790 let output_culist_index = int2sliceindex(*output_index);
791
792 let monitoring_action = quote! {
793 debug!("Task {}: Error during process: {}", #mission_mod::TASKS_IDS[#tid], &error);
794 let decision = monitor.process_error(#tid, CuTaskState::Process, &error);
795 match decision {
796 Decision::Abort => {
797 debug!("Process: ABORT decision from monitoring. Task '{}' errored out \
798 during process. Skipping the processing of CL {}.", #mission_mod::TASKS_IDS[#tid], clid);
799 monitor.process_copperlist(&#mission_mod::collect_metadata(&culist))?;
800 cl_manager.end_of_processing(clid)?;
801 return Ok(()); }
804 Decision::Ignore => {
805 debug!("Process: IGNORE decision from monitoring. Task '{}' errored out \
806 during process. The runtime will continue with a forced empty message.", #mission_mod::TASKS_IDS[#tid]);
807 let cumsg_output = &mut msgs.#output_culist_index;
808 cumsg_output.clear_payload();
809 }
810 Decision::Shutdown => {
811 debug!("Process: SHUTDOWN decision from monitoring. Task '{}' errored out \
812 during process. The runtime cannot continue.", #mission_mod::TASKS_IDS[#tid]);
813 return Err(CuError::new_with_cause("Task errored out during process.", error));
814 }
815 }
816 };
817
818 let call_sim_callback = if sim_mode {
819 let inputs_type = if indices.len() == 1 {
820 quote! { #(&msgs.#indices)* }
822 } else {
823 quote! { (#(&msgs.#indices),*) }
825 };
826
827 quote! {
828 let doit = {
829 let cumsg_input = #inputs_type;
830 let cumsg_output = &mut msgs.#output_culist_index;
832 let state = cu29::simulation::CuTaskCallbackState::Process(cumsg_input, cumsg_output);
833 let ovr = sim_callback(SimStep::#enum_name(state));
834
835 if let cu29::simulation::SimOverride::Errored(reason) = ovr {
836 let error: CuError = reason.into();
837 #monitoring_action
838 false
839 } else {
840 ovr == cu29::simulation::SimOverride::ExecuteByRuntime
841 }
842 };
843 }
844 } else {
845 quote! {
846 let doit = true; }
848 };
849
850 let indices = step.input_msg_indices_types.iter().map(|(index, _)| int2sliceindex(*index));
851
852 let inputs_type = if indices.len() == 1 {
853 quote! { #(&msgs.#indices)* }
855 } else {
856 quote! { (#(&msgs.#indices),*) }
858 };
859
860 quote! {
861 {
862 #comment_tokens
863 kf_manager.freeze_task(clid, &#task_instance)?;
865 #call_sim_callback
866 let cumsg_input = #inputs_type;
867 let cumsg_output = &mut msgs.#output_culist_index;
869 cumsg_output.metadata.process_time.start = clock.now().into();
870 let maybe_error = if doit {#task_instance.process(clock, cumsg_input)} else {Ok(())};
871 cumsg_output.metadata.process_time.end = clock.now().into();
872 if let Err(error) = maybe_error {
873 #monitoring_action
874 }
875 }
876 }
877 } else {
878 panic!("Sink tasks should have a virtual output message index.");
879 }
880 }
881 CuTaskType::Regular => {
882 let indices = step.input_msg_indices_types.iter().map(|(index, _)| int2sliceindex(*index));
883 if let Some((output_index, _)) = &step.output_msg_index_type {
884 let output_culist_index = int2sliceindex(*output_index);
885
886 let monitoring_action = quote! {
887 debug!("Task {}: Error during process: {}", #mission_mod::TASKS_IDS[#tid], &error);
888 let decision = monitor.process_error(#tid, CuTaskState::Process, &error);
889 match decision {
890 Decision::Abort => {
891 debug!("Process: ABORT decision from monitoring. Task '{}' errored out \
892 during process. Skipping the processing of CL {}.", #mission_mod::TASKS_IDS[#tid], clid);
893 monitor.process_copperlist(&#mission_mod::collect_metadata(&culist))?;
894 cl_manager.end_of_processing(clid)?;
895 return Ok(()); }
898 Decision::Ignore => {
899 debug!("Process: IGNORE decision from monitoring. Task '{}' errored out \
900 during process. The runtime will continue with a forced empty message.", #mission_mod::TASKS_IDS[#tid]);
901 let cumsg_output = &mut msgs.#output_culist_index;
902 cumsg_output.clear_payload();
903 }
904 Decision::Shutdown => {
905 debug!("Process: SHUTDOWN decision from monitoring. Task '{}' errored out \
906 during process. The runtime cannot continue.", #mission_mod::TASKS_IDS[#tid]);
907 return Err(CuError::new_with_cause("Task errored out during process.", error));
908 }
909 }
910 };
911
912 let call_sim_callback = if sim_mode {
913 let inputs_type = if indices.len() == 1 {
914 quote! { #(&msgs.#indices)* }
916 } else {
917 quote! { (#(&msgs.#indices),*) }
919 };
920
921 quote! {
922 let doit = {
923 let cumsg_input = #inputs_type;
924 let cumsg_output = &mut msgs.#output_culist_index;
925 let state = cu29::simulation::CuTaskCallbackState::Process(cumsg_input, cumsg_output);
926 let ovr = sim_callback(SimStep::#enum_name(state));
927
928 if let cu29::simulation::SimOverride::Errored(reason) = ovr {
929 let error: CuError = reason.into();
930 #monitoring_action
931 false
932 }
933 else {
934 ovr == cu29::simulation::SimOverride::ExecuteByRuntime
935 }
936 };
937 }
938 } else {
939 quote! {
940 let doit = true; }
942 };
943
944 let indices = step.input_msg_indices_types.iter().map(|(index, _)| int2sliceindex(*index));
945 let inputs_type = if indices.len() == 1 {
946 quote! { #(&msgs.#indices)* }
948 } else {
949 quote! { (#(&msgs.#indices),*) }
951 };
952 quote! {
953 {
954 #comment_tokens
955 kf_manager.freeze_task(clid, &#task_instance)?;
957 #call_sim_callback
958 let cumsg_input = #inputs_type;
959 let cumsg_output = &mut msgs.#output_culist_index;
960 cumsg_output.metadata.process_time.start = clock.now().into();
961 let maybe_error = if doit {#task_instance.process(clock, cumsg_input, cumsg_output)} else {Ok(())};
962 cumsg_output.metadata.process_time.end = clock.now().into();
963 if let Err(error) = maybe_error {
964 #monitoring_action
965 }
966 }
967 }
968 } else {
969 panic!("Regular task should have an output message index.");
970 }
971 }
972 };
973
974 process_call
975 }
976 CuExecutionUnit::Loop(_) => todo!("Needs to be implemented"),
977 }
978 }).collect();
979 #[cfg(feature = "macro_debug")]
980 eprintln!("[Culist access order: {taskid_call_order:?}]");
981
982 let all_tasks_member_ids: Vec<String> = all_tasks_ids
984 .iter()
985 .map(|name| utils::config_id_to_struct_member(name.as_str()))
986 .collect();
987
988 #[cfg(feature = "macro_debug")]
989 eprintln!("[build the copperlist support]");
990 let culist_support: proc_macro2::TokenStream =
991 gen_culist_support(&runtime_plan, &taskid_call_order, &all_tasks_member_ids);
992
993 #[cfg(feature = "macro_debug")]
994 eprintln!("[build the sim support]");
995 let sim_support: proc_macro2::TokenStream = gen_sim_support(&runtime_plan);
996
997 let (new, run_one_iteration, start_all_tasks, stop_all_tasks, run) = if sim_mode {
998 (
999 quote! {
1000 fn new(clock:RobotClock, unified_logger: Arc<Mutex<UnifiedLoggerWrite>>, config_override: Option<CuConfig>, sim_callback: &mut impl FnMut(SimStep) -> cu29::simulation::SimOverride) -> CuResult<Self>
1001 },
1002 quote! {
1003 fn run_one_iteration(&mut self, sim_callback: &mut impl FnMut(SimStep) -> cu29::simulation::SimOverride) -> CuResult<()>
1004 },
1005 quote! {
1006 fn start_all_tasks(&mut self, sim_callback: &mut impl FnMut(SimStep) -> cu29::simulation::SimOverride) -> CuResult<()>
1007 },
1008 quote! {
1009 fn stop_all_tasks(&mut self, sim_callback: &mut impl FnMut(SimStep) -> cu29::simulation::SimOverride) -> CuResult<()>
1010 },
1011 quote! {
1012 fn run(&mut self, sim_callback: &mut impl FnMut(SimStep) -> cu29::simulation::SimOverride) -> CuResult<()>
1013 },
1014 )
1015 } else {
1016 (
1017 quote! {
1018 fn new(clock:RobotClock, unified_logger: Arc<Mutex<UnifiedLoggerWrite>>, config_override: Option<CuConfig>) -> CuResult<Self>
1019 },
1020 quote! {
1021 fn run_one_iteration(&mut self) -> CuResult<()>
1022 },
1023 quote! {
1024 fn start_all_tasks(&mut self) -> CuResult<()>
1025 },
1026 quote! {
1027 fn stop_all_tasks(&mut self) -> CuResult<()>
1028 },
1029 quote! {
1030 fn run(&mut self) -> CuResult<()>
1031 },
1032 )
1033 };
1034
1035 let sim_callback_arg = if sim_mode {
1036 Some(quote!(sim_callback))
1037 } else {
1038 None
1039 };
1040
1041 let sim_callback_on_new_calls = all_tasks_ids.iter().enumerate().map(|(i, id)| {
1042 let enum_name = config_id_to_enum(id);
1043 let enum_ident = Ident::new(&enum_name, Span::call_site());
1044 quote! {
1045 sim_callback(SimStep::#enum_ident(cu29::simulation::CuTaskCallbackState::New(all_instances_configs[#i].cloned())));
1047 }
1048 });
1049
1050 let sim_callback_on_new = if sim_mode {
1051 Some(quote! {
1052 let graph = config.get_graph(Some(#mission)).expect("Could not find the mission #mission");
1053 let all_instances_configs: Vec<Option<&ComponentConfig>> = graph
1054 .get_all_nodes()
1055 .iter()
1056 .map(|(_, node)| node.get_instance_config())
1057 .collect();
1058 #(#sim_callback_on_new_calls)*
1059 })
1060 } else {
1061 None
1062 };
1063
1064 #[cfg(feature = "macro_debug")]
1065 eprintln!("[build the run methods]");
1066 let run_methods = quote! {
1067
1068 #run_one_iteration {
1069
1070 let runtime = &mut self.copper_runtime;
1072 let clock = &runtime.clock;
1073 let monitor = &mut runtime.monitor;
1074 let tasks = &mut runtime.tasks;
1075 let cl_manager = &mut runtime.copperlists_manager;
1076 let kf_manager = &mut runtime.keyframes_manager;
1077
1078 #(#preprocess_calls)*
1080
1081 let culist = cl_manager.inner.create().expect("Ran out of space for copper lists"); let clid = culist.id;
1083 kf_manager.reset(clid, clock); culist.change_state(cu29::copperlist::CopperListState::Processing);
1085 {
1086 let msgs = &mut culist.msgs.0;
1087 #(#runtime_plan_code)*
1088 } monitor.process_copperlist(&#mission_mod::collect_metadata(&culist))?;
1090 cl_manager.end_of_processing(clid)?;
1091 kf_manager.end_of_processing(clid)?;
1092
1093 #(#postprocess_calls)*
1095 Ok(())
1096 }
1097
1098 fn restore_keyframe(&mut self, keyframe: &KeyFrame) -> CuResult<()> {
1099 let runtime = &mut self.copper_runtime;
1100 let clock = &runtime.clock;
1101 let tasks = &mut runtime.tasks;
1102 let config = cu29::bincode::config::standard();
1103 let reader = cu29::bincode::de::read::SliceReader::new(&keyframe.serialized_tasks);
1104 let mut decoder = DecoderImpl::new(reader, config, ());
1105 #(#task_restore_code);*;
1106 Ok(())
1107 }
1108
1109 #start_all_tasks {
1110 #(#start_calls)*
1111 self.copper_runtime.monitor.start(&self.copper_runtime.clock)?;
1112 Ok(())
1113 }
1114
1115 #stop_all_tasks {
1116 #(#stop_calls)*
1117 self.copper_runtime.monitor.stop(&self.copper_runtime.clock)?;
1118 Ok(())
1119 }
1120
1121 #run {
1122 static STOP_FLAG: AtomicBool = AtomicBool::new(false);
1123 ctrlc::set_handler(move || {
1124 STOP_FLAG.store(true, Ordering::SeqCst);
1125 }).expect("Error setting Ctrl-C handler");
1126
1127 self.start_all_tasks(#sim_callback_arg)?;
1128 let result = loop {
1129 let result = self.run_one_iteration(#sim_callback_arg);
1130
1131 if STOP_FLAG.load(Ordering::SeqCst) || result.is_err() {
1132 break result;
1133 }
1134 };
1135 if result.is_err() {
1136 error!("A task errored out: {}", &result);
1137 }
1138 self.stop_all_tasks(#sim_callback_arg)?;
1139 result
1140 }
1141 };
1142
1143 let tasks_type = if sim_mode {
1144 quote!(CuSimTasks)
1145 } else {
1146 quote!(CuTasks)
1147 };
1148
1149 let tasks_instanciator = if sim_mode {
1150 quote!(tasks_instanciator_sim)
1151 } else {
1152 quote!(tasks_instanciator)
1153 };
1154
1155 let app_impl_decl = if sim_mode {
1156 quote!(impl CuSimApplication for #application_name)
1157 } else {
1158 quote!(impl CuApplication for #application_name)
1159 };
1160 let simstep_type_decl = if sim_mode {
1161 quote!(
1162 type Step<'z> = SimStep<'z>;
1163 )
1164 } else {
1165 quote!()
1166 };
1167
1168 #[cfg(feature = "macro_debug")]
1169 eprintln!("[build result]");
1170 let application_impl = quote! {
1171 #app_impl_decl {
1172 #simstep_type_decl
1173
1174 #new {
1175 let config_filename = #config_file;
1176 let config = if config_override.is_some() {
1177 let overridden_config = config_override.unwrap();
1178 debug!("CuConfig: Overridden programmatically: {}", &overridden_config.serialize_ron());
1179 overridden_config
1180 } else if std::path::Path::new(config_filename).exists() {
1181 debug!("CuConfig: Reading configuration from file: {}", config_filename);
1182 cu29::config::read_configuration(config_filename)?
1183 } else {
1184 let original_config = Self::get_original_config();
1185 debug!("CuConfig: Using the original configuration the project was compiled with: {}", &original_config);
1186 cu29::config::read_configuration_str(original_config, None)?
1187 };
1188
1189 let mut default_section_size = std::mem::size_of::<super::#mission_mod::CuList>() * 64;
1192 if let Some(section_size_mib) = config.logging.as_ref().and_then(|l| l.section_size_mib) {
1194 default_section_size = section_size_mib as usize * 1024usize * 1024usize;
1196 }
1197 let copperlist_stream = stream_write::<#mission_mod::CuList>(
1198 unified_logger.clone(),
1199 UnifiedLogType::CopperList,
1200 default_section_size,
1201 );
1205
1206 let keyframes_stream = stream_write::<KeyFrame>(
1207 unified_logger.clone(),
1208 UnifiedLogType::FrozenTasks,
1209 1024 * 1024 * 10, );
1211
1212 let application = Ok(#application_name {
1213 copper_runtime: CuRuntime::<#mission_mod::#tasks_type, #mission_mod::CuMsgs, #monitor_type, #DEFAULT_CLNB>::new(
1214 clock,
1215 &config,
1216 Some(#mission),
1217 #mission_mod::#tasks_instanciator,
1218 #mission_mod::monitor_instanciator,
1219 copperlist_stream,
1220 keyframes_stream)?, });
1222
1223 #sim_callback_on_new
1224
1225 application
1226 }
1227
1228 fn get_original_config() -> String {
1229 #copper_config_content.to_string()
1230 }
1231
1232 #run_methods
1233 }
1234 };
1235
1236 let (
1237 builder_struct,
1238 builder_new,
1239 builder_impl,
1240 builder_sim_callback_method,
1241 builder_build_sim_callback_arg,
1242 ) = if sim_mode {
1243 (
1244 quote! {
1245 #[allow(dead_code)]
1246 pub struct #builder_name <'a, F> {
1247 clock: Option<RobotClock>,
1248 unified_logger: Option<Arc<Mutex<UnifiedLoggerWrite>>>,
1249 config_override: Option<CuConfig>,
1250 sim_callback: Option<&'a mut F>
1251 }
1252 },
1253 quote! {
1254 #[allow(dead_code)]
1255 pub fn new() -> Self {
1256 Self {
1257 clock: None,
1258 unified_logger: None,
1259 config_override: None,
1260 sim_callback: None,
1261 }
1262 }
1263 },
1264 quote! {
1265 impl<'a, F> #builder_name <'a, F>
1266 where
1267 F: FnMut(SimStep) -> cu29::simulation::SimOverride,
1268 },
1269 Some(quote! {
1270 pub fn with_sim_callback(mut self, sim_callback: &'a mut F) -> Self
1271 {
1272 self.sim_callback = Some(sim_callback);
1273 self
1274 }
1275 }),
1276 Some(quote! {
1277 self.sim_callback
1278 .ok_or(CuError::from("Sim callback missing from builder"))?,
1279 }),
1280 )
1281 } else {
1282 (
1283 quote! {
1284 #[allow(dead_code)]
1285 pub struct #builder_name {
1286 clock: Option<RobotClock>,
1287 unified_logger: Option<Arc<Mutex<UnifiedLoggerWrite>>>,
1288 config_override: Option<CuConfig>,
1289 }
1290 },
1291 quote! {
1292 #[allow(dead_code)]
1293 pub fn new() -> Self {
1294 Self {
1295 clock: None,
1296 unified_logger: None,
1297 config_override: None,
1298 }
1299 }
1300 },
1301 quote! {
1302 impl #builder_name
1303 },
1304 None,
1305 None,
1306 )
1307 };
1308
1309 let application_builder = quote! {
1310 #builder_struct
1311
1312 #builder_impl
1313 {
1314 #builder_new
1315
1316 #[allow(dead_code)]
1317 pub fn with_clock(mut self, clock: RobotClock) -> Self {
1318 self.clock = Some(clock);
1319 self
1320 }
1321
1322 #[allow(dead_code)]
1323 pub fn with_unified_logger(mut self, unified_logger: Arc<Mutex<UnifiedLoggerWrite>>) -> Self {
1324 self.unified_logger = Some(unified_logger);
1325 self
1326 }
1327
1328 #[allow(dead_code)]
1329 pub fn with_context(mut self, copper_ctx: &CopperContext) -> Self {
1330 self.clock = Some(copper_ctx.clock.clone());
1331 self.unified_logger = Some(copper_ctx.unified_logger.clone());
1332 self
1333 }
1334
1335 #[allow(dead_code)]
1336 pub fn with_config(mut self, config_override: CuConfig) -> Self {
1337 self.config_override = Some(config_override);
1338 self
1339 }
1340
1341 #builder_sim_callback_method
1342
1343 #[allow(dead_code)]
1344 pub fn build(self) -> CuResult<#application_name> {
1345 #application_name::new(
1346 self.clock
1347 .ok_or(CuError::from("Clock missing from builder"))?,
1348 self.unified_logger
1349 .ok_or(CuError::from("Unified logger missing from builder"))?,
1350 self.config_override,
1351 #builder_build_sim_callback_arg
1352 )
1353 }
1354 }
1355 };
1356
1357 let mission_mod_tokens = quote! {
1359 mod #mission_mod {
1360 use super::*; use cu29::bincode::Encode;
1363 use cu29::bincode::enc::Encoder;
1364 use cu29::bincode::error::EncodeError;
1365 use cu29::bincode::Decode;
1366 use cu29::bincode::de::Decoder;
1367 use cu29::bincode::de::DecoderImpl;
1368 use cu29::bincode::error::DecodeError;
1369 use cu29::clock::RobotClock;
1370 use cu29::config::CuConfig;
1371 use cu29::config::ComponentConfig;
1372 use cu29::curuntime::CuRuntime;
1373 use cu29::curuntime::KeyFrame;
1374 use cu29::curuntime::CopperContext;
1375 use cu29::CuResult;
1376 use cu29::CuError;
1377 use cu29::cutask::CuSrcTask;
1378 use cu29::cutask::CuSinkTask;
1379 use cu29::cutask::CuTask;
1380 use cu29::cutask::CuMsg;
1381 use cu29::cutask::CuMsgMetadata;
1382 use cu29::copperlist::CopperList;
1383 use cu29::monitoring::CuMonitor; use cu29::monitoring::CuTaskState;
1385 use cu29::monitoring::Decision;
1386 use cu29::prelude::app::CuApplication;
1387 use cu29::prelude::debug;
1388 use cu29::prelude::stream_write;
1389 use cu29::prelude::UnifiedLoggerWrite;
1390 use cu29::prelude::UnifiedLogType;
1391 use std::sync::Arc;
1392 use std::sync::Mutex;
1393 use std::sync::atomic::{AtomicBool, Ordering};
1394
1395 #[allow(unused_imports)]
1397 use cu29::prelude::app::CuSimApplication;
1398
1399 #[allow(unused_imports)]
1401 use cu29::monitoring::NoMonitor;
1402
1403 pub type CuTasks = #task_types_tuple;
1407
1408 #[allow(dead_code)]
1411 pub type CuSimTasks = #task_types_tuple_sim;
1412
1413 pub const TASKS_IDS: &'static [&'static str] = &[#( #all_tasks_ids ),*];
1414
1415 #culist_support
1416
1417 #sim_support
1418
1419 pub fn tasks_instanciator(all_instances_configs: Vec<Option<&ComponentConfig>>) -> CuResult<CuTasks> {
1420 Ok(( #(#task_instances_init_code),*, ))
1421 }
1422
1423 #[allow(dead_code)]
1424 pub fn tasks_instanciator_sim(all_instances_configs: Vec<Option<&ComponentConfig>>) -> CuResult<CuSimTasks> {
1425 Ok(( #(#task_sim_instances_init_code),*, ))
1426 }
1427
1428 pub fn monitor_instanciator(config: &CuConfig) -> #monitor_type {
1429 #monitor_type::new(config, #mission_mod::TASKS_IDS).expect("Failed to create the given monitor.")
1430 }
1431
1432 pub #application_struct
1434
1435 #application_impl
1436
1437 #application_builder
1438 }
1439
1440 };
1441 all_missions_tokens.push(mission_mod_tokens);
1442 }
1443
1444 let default_application_tokens = if all_missions.contains_key("default") {
1445 quote! {
1446 #[allow(unused_imports)]
1448 use default::#builder_name;
1449
1450 #[allow(unused_imports)]
1451 use default::#application_name;
1452 }
1453 } else {
1454 quote! {}
1455 };
1456
1457 let result: proc_macro2::TokenStream = quote! {
1458 #(#all_missions_tokens)*
1459 #default_application_tokens
1460 };
1461
1462 #[cfg(feature = "macro_debug")]
1464 {
1465 let formatted_code = rustfmt_generated_code(result.to_string());
1466 eprintln!("\n === Gen. Runtime ===\n");
1467 eprintln!("{formatted_code}");
1468 eprintln!("\n === === === === === ===\n");
1471 }
1472 result.into()
1473}
1474
1475fn read_config(config_file: &str) -> CuResult<CuConfig> {
1476 let filename = config_full_path(config_file);
1477
1478 read_configuration(filename.as_str())
1479}
1480
1481fn config_full_path(config_file: &str) -> String {
1482 let mut config_full_path = utils::caller_crate_root();
1483 config_full_path.push(config_file);
1484 let filename = config_full_path
1485 .as_os_str()
1486 .to_str()
1487 .expect("Could not interpret the config file name");
1488 filename.to_string()
1489}
1490
1491fn extract_tasks_types(graph: &CuGraph) -> (Vec<String>, Vec<CuTaskType>, Vec<String>, Vec<Type>) {
1493 let all_id_nodes = graph.get_all_nodes();
1494
1495 let all_tasks_ids: Vec<String> = all_id_nodes
1497 .iter()
1498 .map(|(_, node)| node.get_id().to_string())
1499 .collect();
1500
1501 let all_task_cutype: Vec<CuTaskType> = all_id_nodes
1502 .iter()
1503 .map(|(id, _)| find_task_type_for_id(graph, *id))
1504 .collect();
1505
1506 let all_types_names: Vec<String> = all_id_nodes
1508 .iter()
1509 .map(|(_, node)| node.get_type().to_string())
1510 .collect();
1511
1512 let all_types: Vec<Type> = all_types_names
1514 .iter()
1515 .map(|name| {
1516 parse_str(name)
1517 .unwrap_or_else(|_| panic!("Could not transform {name} into a Task Rust type."))
1518 })
1519 .collect();
1520 (all_tasks_ids, all_task_cutype, all_types_names, all_types)
1521}
1522
1523fn extract_msg_types(runtime_plan: &CuExecutionLoop) -> Vec<Type> {
1524 runtime_plan
1525 .steps
1526 .iter()
1527 .filter_map(|unit| match unit {
1528 CuExecutionUnit::Step(step) => {
1529 if let Some((_, output_msg_type)) = &step.output_msg_index_type {
1530 Some(
1531 parse_str::<Type>(output_msg_type.as_str()).unwrap_or_else(|_| {
1532 panic!(
1533 "Could not transform {output_msg_type} into a message Rust type."
1534 )
1535 }),
1536 )
1537 } else {
1538 None
1539 }
1540 }
1541 CuExecutionUnit::Loop(_) => todo!("Needs to be implemented"),
1542 })
1543 .collect()
1544}
1545
1546fn build_culist_tuple(all_msgs_types_in_culist_order: &[Type]) -> TypeTuple {
1548 if all_msgs_types_in_culist_order.is_empty() {
1549 parse_quote! { () }
1550 } else {
1551 parse_quote! {
1552 ( #( CuMsg<#all_msgs_types_in_culist_order> ),* )
1553 }
1554 }
1555}
1556
1557fn build_culist_tuple_encode(all_msgs_types_in_culist_order: &[Type]) -> ItemImpl {
1559 let indices: Vec<usize> = (0..all_msgs_types_in_culist_order.len()).collect();
1560
1561 let encode_fields: Vec<_> = indices
1563 .iter()
1564 .map(|i| {
1565 let idx = syn::Index::from(*i);
1566 quote! { self.0.#idx.encode(encoder)?; }
1567 })
1568 .collect();
1569
1570 parse_quote! {
1571 impl Encode for CuMsgs {
1572 fn encode<E: Encoder>(&self, encoder: &mut E) -> Result<(), EncodeError> {
1573 #(#encode_fields)*
1574 Ok(())
1575 }
1576 }
1577 }
1578}
1579
1580fn build_culist_tuple_decode(all_msgs_types_in_culist_order: &[Type]) -> ItemImpl {
1582 let indices: Vec<usize> = (0..all_msgs_types_in_culist_order.len()).collect();
1583
1584 let decode_fields: Vec<_> = indices
1586 .iter()
1587 .map(|i| {
1588 let t = &all_msgs_types_in_culist_order[*i];
1589 quote! { CuMsg::<#t>::decode(decoder)? }
1590 })
1591 .collect();
1592
1593 parse_quote! {
1594 impl Decode<()> for CuMsgs {
1595 fn decode<D: Decoder<Context=()>>(decoder: &mut D) -> Result<Self, DecodeError> {
1596 Ok(CuMsgs ((
1597 #(#decode_fields),*
1598 )))
1599 }
1600 }
1601 }
1602}
1603
1604fn build_culist_erasedcumsgs(all_msgs_types_in_culist_order: &[Type]) -> ItemImpl {
1605 let indices: Vec<usize> = (0..all_msgs_types_in_culist_order.len()).collect();
1606 let casted_fields: Vec<_> = indices
1607 .iter()
1608 .map(|i| {
1609 let idx = syn::Index::from(*i);
1610 quote! { &self.0.#idx as &dyn ErasedCuMsg }
1611 })
1612 .collect();
1613 parse_quote! {
1614 impl ErasedCuMsgs for CuMsgs {
1615 fn cumsgs(&self) -> Vec<&dyn ErasedCuMsg> {
1616 vec![
1617 #(#casted_fields),*
1618 ]
1619 }
1620 }
1621 }
1622}
1623
1624fn build_culist_tuple_debug(all_msgs_types_in_culist_order: &[Type]) -> ItemImpl {
1625 let indices: Vec<usize> = (0..all_msgs_types_in_culist_order.len()).collect();
1626
1627 let debug_fields: Vec<_> = indices
1628 .iter()
1629 .map(|i| {
1630 let idx = syn::Index::from(*i);
1631 quote! { .field(&self.0.#idx) }
1632 })
1633 .collect();
1634
1635 parse_quote! {
1636 impl std::fmt::Debug for CuMsgs {
1637 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1638 f.debug_tuple("CuMsgs")
1639 #(#debug_fields)*
1640 .finish()
1641 }
1642 }
1643 }
1644}
1645
1646fn build_culist_tuple_serialize(all_msgs_types_in_culist_order: &[Type]) -> ItemImpl {
1648 let indices: Vec<usize> = (0..all_msgs_types_in_culist_order.len()).collect();
1649 let tuple_len = all_msgs_types_in_culist_order.len();
1650
1651 let serialize_fields: Vec<_> = indices
1653 .iter()
1654 .map(|i| {
1655 let idx = syn::Index::from(*i);
1656 quote! { &self.0.#idx }
1657 })
1658 .collect();
1659
1660 parse_quote! {
1661 impl Serialize for CuMsgs {
1662 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
1663 where
1664 S: serde::Serializer,
1665 {
1666 use serde::ser::SerializeTuple;
1667 let mut tuple = serializer.serialize_tuple(#tuple_len)?;
1668 #(tuple.serialize_element(#serialize_fields)?;)*
1669 tuple.end()
1670 }
1671 }
1672 }
1673}
1674
1675#[cfg(test)]
1676mod tests {
1677 #[test]
1679 fn test_compile_fail() {
1680 let t = trybuild::TestCases::new();
1681 t.compile_fail("tests/compile_fail/*/*.rs");
1682 }
1683}