1use proc_macro::TokenStream;
2use quote::{format_ident, quote};
3use std::fs::read_to_string;
4use syn::meta::parser;
5use syn::Fields::{Named, Unnamed};
6use syn::{
7 parse_macro_input, parse_quote, parse_str, Field, Fields, ItemImpl, ItemStruct, LitStr, Type,
8 TypeTuple,
9};
10
11#[cfg(feature = "macro_debug")]
12use crate::format::rustfmt_generated_code;
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
22mod format;
23mod utils;
24
25const DEFAULT_CLNB: usize = 10;
27
28#[inline]
29fn int2sliceindex(i: u32) -> syn::Index {
30 syn::Index::from(i as usize)
31}
32
33#[inline(always)]
34fn return_error(msg: String) -> TokenStream {
35 syn::Error::new(Span::call_site(), msg)
36 .to_compile_error()
37 .into()
38}
39
40#[proc_macro]
44pub fn gen_cumsgs(config_path_lit: TokenStream) -> TokenStream {
45 let config = parse_macro_input!(config_path_lit as LitStr).value();
46 if !std::path::Path::new(&config_full_path(&config)).exists() {
47 return return_error(format!(
48 "The configuration file `{config}` does not exist. Please provide a valid path."
49 ));
50 }
51 #[cfg(feature = "macro_debug")]
52 eprintln!("[gen culist support with {config:?}]");
53 let cuconfig = match read_config(&config) {
54 Ok(cuconfig) => cuconfig,
55 Err(e) => return return_error(e.to_string()),
56 };
57 let graph = cuconfig
58 .get_graph(None) .expect("Could not find the specified mission for gen_cumsgs");
60 let runtime_plan: CuExecutionLoop = match compute_runtime_plan(graph) {
61 Ok(plan) => plan,
62 Err(e) => return return_error(format!("Could not compute runtime plan: {e}")),
63 };
64
65 let all_tasks_member_ids: Vec<String> = graph
67 .get_all_nodes()
68 .iter()
69 .map(|(_, node)| utils::config_id_to_struct_member(node.get_id().as_str()))
70 .collect();
71
72 let taskid_order: Vec<usize> = runtime_plan
75 .steps
76 .iter()
77 .filter_map(|unit| match unit {
78 CuExecutionUnit::Step(step) => Some(step.node_id as usize),
79 _ => None,
80 })
81 .collect();
82
83 #[cfg(feature = "macro_debug")]
84 eprintln!(
85 "[The CuStampedDataSet matching tasks ids are {:?}]",
86 taskid_order
87 .iter()
88 .map(|i| all_tasks_member_ids[*i].clone())
89 .collect::<Vec<_>>()
90 );
91
92 let support = gen_culist_support(&runtime_plan, &taskid_order, &all_tasks_member_ids);
93
94 let with_uses = quote! {
95 mod cumsgs {
96 use cu29::bincode::Encode;
97 use cu29::bincode::enc::Encoder;
98 use cu29::bincode::error::EncodeError;
99 use cu29::bincode::Decode;
100 use cu29::bincode::de::Decoder;
101 use cu29::bincode::error::DecodeError;
102 use cu29::copperlist::CopperList;
103 use cu29::prelude::CuStampedData;
104 use cu29::prelude::ErasedCuStampedData;
105 use cu29::prelude::ErasedCuStampedDataSet;
106 use cu29::prelude::MatchingTasks;
107 use cu29::prelude::Serialize;
108 use cu29::prelude::CuMsg;
109 use cu29::prelude::CuMsgMetadata;
110 use cu29::prelude::CuListZeroedInit;
111 use cu29::prelude::CuCompactString;
112 #support
113 }
114 use cumsgs::CuStampedDataSet;
115 type CuMsgs=CuStampedDataSet;
116 };
117 with_uses.into()
118}
119
120fn gen_culist_support(
122 runtime_plan: &CuExecutionLoop,
123 taskid_call_order: &[usize],
124 all_tasks_as_struct_member_name: &Vec<String>,
125) -> proc_macro2::TokenStream {
126 #[cfg(feature = "macro_debug")]
127 eprintln!("[Extract msgs types]");
128 let all_msgs_types_in_culist_order = extract_msg_types(runtime_plan);
129
130 let culist_size = all_msgs_types_in_culist_order.len();
131 let task_indices: Vec<_> = taskid_call_order
132 .iter()
133 .map(|i| syn::Index::from(*i))
134 .collect();
135
136 #[cfg(feature = "macro_debug")]
137 eprintln!("[build the copperlist struct]");
138 let msgs_types_tuple: TypeTuple = build_culist_tuple(&all_msgs_types_in_culist_order);
139
140 #[cfg(feature = "macro_debug")]
141 eprintln!("[build the copperlist tuple bincode support]");
142 let msgs_types_tuple_encode = build_culist_tuple_encode(&all_msgs_types_in_culist_order);
143 let msgs_types_tuple_decode = build_culist_tuple_decode(&all_msgs_types_in_culist_order);
144
145 #[cfg(feature = "macro_debug")]
146 eprintln!("[build the copperlist tuple debug support]");
147 let msgs_types_tuple_debug = build_culist_tuple_debug(&all_msgs_types_in_culist_order);
148
149 #[cfg(feature = "macro_debug")]
150 eprintln!("[build the copperlist tuple serialize support]");
151 let msgs_types_tuple_serialize = build_culist_tuple_serialize(&all_msgs_types_in_culist_order);
152
153 #[cfg(feature = "macro_debug")]
154 eprintln!("[build the default tuple support]");
155 let msgs_types_tuple_default = build_culist_tuple_default(&all_msgs_types_in_culist_order);
156
157 #[cfg(feature = "macro_debug")]
158 eprintln!("[build erasedcumsgs]");
159
160 let erasedmsg_trait_impl = build_culist_erasedcumsgs(&all_msgs_types_in_culist_order);
161
162 let collect_metadata_function = quote! {
163 pub fn collect_metadata<'a>(culist: &'a CuList) -> [&'a CuMsgMetadata; #culist_size] {
164 [#( &culist.msgs.0.#task_indices.metadata, )*]
165 }
166 };
167
168 let methods = itertools::multizip((all_tasks_as_struct_member_name, taskid_call_order)).map(
169 |(name, output_position)| {
170 let fn_name = format_ident!("get_{}_output", name);
171 let payload_type = all_msgs_types_in_culist_order[*output_position].clone();
172 let index = syn::Index::from(*output_position);
173 quote! {
174 #[allow(dead_code)]
175 pub fn #fn_name(&self) -> &CuMsg<#payload_type> {
176 &self.0.#index
177 }
178 }
179 },
180 );
181
182 quote! {
184 #collect_metadata_function
185
186 pub struct CuStampedDataSet(pub #msgs_types_tuple);
187
188 pub type CuList = CopperList<CuStampedDataSet>;
189
190 impl CuStampedDataSet {
191 #(#methods)*
192
193 #[allow(dead_code)]
194 fn get_tuple(&self) -> &#msgs_types_tuple {
195 &self.0
196 }
197
198 #[allow(dead_code)]
199 fn get_tuple_mut(&mut self) -> &mut #msgs_types_tuple {
200 &mut self.0
201 }
202 }
203
204 impl MatchingTasks for CuStampedDataSet {
205 #[allow(dead_code)]
206 fn get_all_task_ids() -> &'static [&'static str] {
207 &[#(#all_tasks_as_struct_member_name),*]
208 }
209 }
210
211 #msgs_types_tuple_encode
213 #msgs_types_tuple_decode
214
215 #msgs_types_tuple_debug
217
218 #msgs_types_tuple_serialize
220
221 #msgs_types_tuple_default
223
224 #erasedmsg_trait_impl
226
227 impl CuListZeroedInit for CuStampedDataSet {
228 fn init_zeroed(&mut self) {
229 #(self.0.#task_indices.metadata.status_txt = CuCompactString::default();)*
230 }
231 }
232 }
233}
234
235fn gen_sim_support(runtime_plan: &CuExecutionLoop) -> proc_macro2::TokenStream {
236 #[cfg(feature = "macro_debug")]
237 eprintln!("[Sim: Build SimEnum]");
238 let plan_enum: Vec<proc_macro2::TokenStream> = runtime_plan
239 .steps
240 .iter()
241 .map(|unit| match unit {
242 CuExecutionUnit::Step(step) => {
243 let enum_entry_name = config_id_to_enum(step.node.get_id().as_str());
244 let enum_ident = Ident::new(&enum_entry_name, Span::call_site());
245 let inputs: Vec<Type> = step
246 .input_msg_indices_types
247 .iter()
248 .map(|(_, t)| parse_str::<Type>(format!("CuMsg<{t}>").as_str()).unwrap())
249 .collect();
250 let output: Option<Type> = step
251 .output_msg_index_type
252 .as_ref()
253 .map(|(_, t)| parse_str::<Type>(format!("CuMsg<{t}>").as_str()).unwrap());
254 let no_output = parse_str::<Type>("CuMsg<()>").unwrap();
255 let output = output.as_ref().unwrap_or(&no_output);
256
257 let inputs_type = if inputs.is_empty() {
258 quote! { () }
259 } else if inputs.len() == 1 {
260 let input = inputs.first().unwrap();
261 quote! { &'a #input }
262 } else {
263 quote! { &'a (#(&'a #inputs),*) }
264 };
265
266 quote! {
267 #enum_ident(cu29::simulation::CuTaskCallbackState<#inputs_type, &'a mut #output>)
268 }
269 }
270 CuExecutionUnit::Loop(_) => {
271 todo!("Needs to be implemented")
272 }
273 })
274 .collect();
275 quote! {
276 #[allow(dead_code)]
278 pub enum SimStep<'a> {
279 #(#plan_enum),*
280 }
281 }
282}
283
284#[proc_macro_attribute]
288pub fn copper_runtime(args: TokenStream, input: TokenStream) -> TokenStream {
289 #[cfg(feature = "macro_debug")]
290 eprintln!("[entry]");
291 let mut application_struct = parse_macro_input!(input as ItemStruct);
292
293 let application_name = &application_struct.ident;
294 let builder_name = format_ident!("{}Builder", application_name);
295
296 let mut config_file: Option<LitStr> = None;
297 let mut sim_mode = false;
298
299 let attribute_config_parser = parser(|meta| {
301 if meta.path.is_ident("config") {
302 config_file = Some(meta.value()?.parse()?);
303 Ok(())
304 } else if meta.path.is_ident("sim_mode") {
305 if meta.input.peek(syn::Token![=]) {
307 meta.input.parse::<syn::Token![=]>()?;
308 let value: syn::LitBool = meta.input.parse()?;
309 sim_mode = value.value();
310 Ok(())
311 } else {
312 sim_mode = true;
314 Ok(())
315 }
316 } else {
317 Err(meta.error("unsupported property"))
318 }
319 });
320
321 #[cfg(feature = "macro_debug")]
322 eprintln!("[parse]");
323 parse_macro_input!(args with attribute_config_parser);
325
326 let config_file = match config_file {
328 Some(file) => file.value(),
329 None => {
330 return return_error(
331 "Expected config file attribute like #[CopperRuntime(config = \"path\")]"
332 .to_string(),
333 )
334 }
335 };
336
337 if !std::path::Path::new(&config_full_path(&config_file)).exists() {
338 return return_error(format!(
339 "The configuration file `{config_file}` does not exist. Please provide a valid path."
340 ));
341 }
342
343 let copper_config = match read_config(&config_file) {
344 Ok(cuconfig) => cuconfig,
345 Err(e) => return return_error(e.to_string()),
346 };
347 let copper_config_content = match read_to_string(config_full_path(config_file.as_str())) {
348 Ok(ok) => ok,
349 Err(e) => return return_error(format!("Could not read the config file (should not happen because we just succeeded just before). {e}"))
350 };
351
352 #[cfg(feature = "macro_debug")]
353 eprintln!("[build monitor type]");
354 let monitor_type = if let Some(monitor_config) = copper_config.get_monitor_config() {
355 let monitor_type = parse_str::<Type>(monitor_config.get_type())
356 .expect("Could not transform the monitor type name into a Rust type.");
357 quote! { #monitor_type }
358 } else {
359 quote! { NoMonitor }
360 };
361
362 #[cfg(feature = "macro_debug")]
364 eprintln!("[build runtime field]");
365 let runtime_field: Field = if sim_mode {
367 parse_quote! {
368 copper_runtime: cu29::curuntime::CuRuntime<CuSimTasks, CuStampedDataSet, #monitor_type, #DEFAULT_CLNB>
369 }
370 } else {
371 parse_quote! {
372 copper_runtime: cu29::curuntime::CuRuntime<CuTasks, CuStampedDataSet, #monitor_type, #DEFAULT_CLNB>
373 }
374 };
375
376 #[cfg(feature = "macro_debug")]
377 eprintln!("[match struct anonymity]");
378 match &mut application_struct.fields {
379 Named(fields_named) => {
380 fields_named.named.push(runtime_field);
381 }
382 Unnamed(fields_unnamed) => {
383 fields_unnamed.unnamed.push(runtime_field);
384 }
385 Fields::Unit => {
386 panic!("This struct is a unit struct, it should have named or unnamed fields. use struct Something {{}} and not struct Something;")
387 }
388 };
389
390 let all_missions = copper_config.graphs.get_all_missions_graphs();
391 let mut all_missions_tokens = Vec::<proc_macro2::TokenStream>::new();
392 for (mission, graph) in &all_missions {
393 let mission_mod = parse_str::<Ident>(mission.as_str())
394 .expect("Could not make an identifier of the mission name");
395
396 #[cfg(feature = "macro_debug")]
397 eprintln!("[extract tasks ids & types]");
398 let task_specs = CuTaskSpecSet::from_graph(graph);
399
400 #[cfg(feature = "macro_debug")]
401 eprintln!("[runtime plan for mission {mission}]");
402 let runtime_plan: CuExecutionLoop = match compute_runtime_plan(graph) {
403 Ok(plan) => plan,
404 Err(e) => return return_error(format!("Could not compute runtime plan: {e}")),
405 };
406
407 #[cfg(feature = "macro_debug")]
408 eprintln!("{runtime_plan:?}");
409
410 let all_sim_tasks_types: Vec<Type> = task_specs.ids
411 .iter()
412 .zip(&task_specs.cutypes)
413 .zip(&task_specs.sim_task_types)
414 .zip(&task_specs.background_flags)
415 .map(|(((task_id, cutype), stype), background)| {
416 match cutype {
417 CuTaskType::Source => {
418 if *background {
419 panic!("CuSrcTask {task_id} cannot be a background task, it should be a regular task.");
420 }
421 let msg_type = graph
422 .get_node_output_msg_type(task_id.as_str())
423 .unwrap_or_else(|| panic!("CuSrcTask {task_id} should have an outgoing connection with a valid output msg type"));
424 let sim_task_name = format!("cu29::simulation::CuSimSrcTask<{msg_type}>");
425 parse_str(sim_task_name.as_str()).unwrap_or_else(|_| panic!("Could not build the placeholder for simulation: {sim_task_name}"))
426 }
427 CuTaskType::Regular => {
428 stype.clone()
430 },
431 CuTaskType::Sink => {
432 if *background {
433 panic!("CuSinkTask {task_id} cannot be a background task, it should be a regular task.");
434 }
435 let msg_type = graph
436 .get_node_input_msg_type(task_id.as_str())
437 .unwrap_or_else(|| panic!("CuSinkTask {task_id} should have an incoming connection with a valid input msg type"));
438 let sim_task_name = format!("cu29::simulation::CuSimSinkTask<{msg_type}>");
439 parse_str(sim_task_name.as_str()).unwrap_or_else(|_| panic!("Could not build the placeholder for simulation: {sim_task_name}"))
440 }
441 }
442 })
443 .collect();
444
445 #[cfg(feature = "macro_debug")]
446 eprintln!("[build task tuples]");
447
448 let task_types = &task_specs.task_types;
449 let task_types_tuple: TypeTuple = parse_quote! {
452 (#(#task_types),*,)
453 };
454
455 let task_types_tuple_sim: TypeTuple = parse_quote! {
456 (#(#all_sim_tasks_types),*,)
457 };
458
459 #[cfg(feature = "macro_debug")]
460 eprintln!("[gen instances]");
461 let task_sim_instances_init_code = all_sim_tasks_types.iter().enumerate().map(|(index, ty)| {
463 let additional_error_info = format!(
464 "Failed to get create instance for {}, instance index {}.",
465 task_specs.type_names[index], index
466 );
467
468 quote! {
469 <#ty>::new(all_instances_configs[#index]).map_err(|e| e.add_cause(#additional_error_info))?
470 }
471 }).collect::<Vec<_>>();
472
473 let task_instances_init_code = task_specs.instantiation_types.iter().zip(&task_specs.background_flags).enumerate().map(|(index, (task_type, background))| {
474 let additional_error_info = format!(
475 "Failed to get create instance for {}, instance index {}.",
476 task_specs.type_names[index], index
477 );
478 if *background {
479 quote! {
480 #task_type::new(all_instances_configs[#index], threadpool).map_err(|e| e.add_cause(#additional_error_info))?
481 }
482 } else {
483 quote! {
484 #task_type::new(all_instances_configs[#index]).map_err(|e| e.add_cause(#additional_error_info))?
485 }
486 }
487 }).collect::<Vec<_>>();
488
489 let (
492 task_restore_code,
493 start_calls,
494 stop_calls,
495 preprocess_calls,
496 postprocess_calls,
497 ): (Vec<_>, Vec<_>, Vec<_>, Vec<_>, Vec<_>) = itertools::multiunzip(
498 (0..task_specs.task_types.len())
499 .map(|index| {
500 let task_index = int2sliceindex(index as u32);
501 let task_tuple_index = syn::Index::from(index);
502 let task_enum_name = config_id_to_enum(&task_specs.ids[index]);
503 let enum_name = Ident::new(&task_enum_name, Span::call_site());
504 (
505 quote! {
507 tasks.#task_tuple_index.thaw(&mut decoder).map_err(|e| CuError::new_with_cause("Failed to thaw", e))?
508 },
509 { let monitoring_action = quote! {
511 let decision = self.copper_runtime.monitor.process_error(#index, CuTaskState::Start, &error);
512 match decision {
513 Decision::Abort => {
514 debug!("Start: ABORT decision from monitoring. Task '{}' errored out \
515 during start. Aborting all the other starts.", #mission_mod::TASKS_IDS[#index]);
516 return Ok(());
517
518 }
519 Decision::Ignore => {
520 debug!("Start: IGNORE decision from monitoring. Task '{}' errored out \
521 during start. The runtime will continue.", #mission_mod::TASKS_IDS[#index]);
522 }
523 Decision::Shutdown => {
524 debug!("Start: SHUTDOWN decision from monitoring. Task '{}' errored out \
525 during start. The runtime cannot continue.", #mission_mod::TASKS_IDS[#index]);
526 return Err(CuError::new_with_cause("Task errored out during start.", error));
527 }
528 }
529 };
530
531 let call_sim_callback = if sim_mode {
532 quote! {
533 let ovr = sim_callback(SimStep::#enum_name(cu29::simulation::CuTaskCallbackState::Start));
535
536 let doit = if let cu29::simulation::SimOverride::Errored(reason) = ovr {
537 let error: CuError = reason.into();
538 #monitoring_action
539 false
540 }
541 else {
542 ovr == cu29::simulation::SimOverride::ExecuteByRuntime
543 };
544 }
545 } else {
546 quote! {
547 let doit = true; }
549 };
550
551
552 quote! {
553 #call_sim_callback
554 if doit {
555 let task = &mut self.copper_runtime.tasks.#task_index;
556 if let Err(error) = task.start(&self.copper_runtime.clock) {
557 #monitoring_action
558 }
559 }
560 }
561 },
562 { let monitoring_action = quote! {
564 let decision = self.copper_runtime.monitor.process_error(#index, CuTaskState::Stop, &error);
565 match decision {
566 Decision::Abort => {
567 debug!("Stop: ABORT decision from monitoring. Task '{}' errored out \
568 during stop. Aborting all the other starts.", #mission_mod::TASKS_IDS[#index]);
569 return Ok(());
570
571 }
572 Decision::Ignore => {
573 debug!("Stop: IGNORE decision from monitoring. Task '{}' errored out \
574 during stop. The runtime will continue.", #mission_mod::TASKS_IDS[#index]);
575 }
576 Decision::Shutdown => {
577 debug!("Stop: SHUTDOWN decision from monitoring. Task '{}' errored out \
578 during stop. The runtime cannot continue.", #mission_mod::TASKS_IDS[#index]);
579 return Err(CuError::new_with_cause("Task errored out during stop.", error));
580 }
581 }
582 };
583 let call_sim_callback = if sim_mode {
584 quote! {
585 let ovr = sim_callback(SimStep::#enum_name(cu29::simulation::CuTaskCallbackState::Stop));
587
588 let doit = if let cu29::simulation::SimOverride::Errored(reason) = ovr {
589 let error: CuError = reason.into();
590 #monitoring_action
591 false
592 }
593 else {
594 ovr == cu29::simulation::SimOverride::ExecuteByRuntime
595 };
596 }
597 } else {
598 quote! {
599 let doit = true; }
601 };
602 quote! {
603 #call_sim_callback
604 if doit {
605 let task = &mut self.copper_runtime.tasks.#task_index;
606 if let Err(error) = task.stop(&self.copper_runtime.clock) {
607 #monitoring_action
608 }
609 }
610 }
611 },
612 { let monitoring_action = quote! {
614 let decision = monitor.process_error(#index, CuTaskState::Preprocess, &error);
615 match decision {
616 Decision::Abort => {
617 debug!("Preprocess: ABORT decision from monitoring. Task '{}' errored out \
618 during preprocess. Aborting all the other starts.", #mission_mod::TASKS_IDS[#index]);
619 return Ok(());
620
621 }
622 Decision::Ignore => {
623 debug!("Preprocess: IGNORE decision from monitoring. Task '{}' errored out \
624 during preprocess. The runtime will continue.", #mission_mod::TASKS_IDS[#index]);
625 }
626 Decision::Shutdown => {
627 debug!("Preprocess: SHUTDOWN decision from monitoring. Task '{}' errored out \
628 during preprocess. The runtime cannot continue.", #mission_mod::TASKS_IDS[#index]);
629 return Err(CuError::new_with_cause("Task errored out during preprocess.", error));
630 }
631 }
632 };
633 let call_sim_callback = if sim_mode {
634 quote! {
635 let ovr = sim_callback(SimStep::#enum_name(cu29::simulation::CuTaskCallbackState::Preprocess));
637
638 let doit = if let cu29::simulation::SimOverride::Errored(reason) = ovr {
639 let error: CuError = reason.into();
640 #monitoring_action
641 false
642 } else {
643 ovr == cu29::simulation::SimOverride::ExecuteByRuntime
644 };
645 }
646 } else {
647 quote! {
648 let doit = true; }
650 };
651 quote! {
652 #call_sim_callback
653 if doit {
654 if let Err(error) = tasks.#task_index.preprocess(clock) {
655 #monitoring_action
656 }
657 }
658 }
659 },
660 { let monitoring_action = quote! {
662 let decision = monitor.process_error(#index, CuTaskState::Postprocess, &error);
663 match decision {
664 Decision::Abort => {
665 debug!("Postprocess: ABORT decision from monitoring. Task '{}' errored out \
666 during postprocess. Aborting all the other starts.", #mission_mod::TASKS_IDS[#index]);
667 return Ok(());
668
669 }
670 Decision::Ignore => {
671 debug!("Postprocess: IGNORE decision from monitoring. Task '{}' errored out \
672 during postprocess. The runtime will continue.", #mission_mod::TASKS_IDS[#index]);
673 }
674 Decision::Shutdown => {
675 debug!("Postprocess: SHUTDOWN decision from monitoring. Task '{}' errored out \
676 during postprocess. The runtime cannot continue.", #mission_mod::TASKS_IDS[#index]);
677 return Err(CuError::new_with_cause("Task errored out during postprocess.", error));
678 }
679 }
680 };
681 let call_sim_callback = if sim_mode {
682 quote! {
683 let ovr = sim_callback(SimStep::#enum_name(cu29::simulation::CuTaskCallbackState::Postprocess));
685
686 let doit = if let cu29::simulation::SimOverride::Errored(reason) = ovr {
687 let error: CuError = reason.into();
688 #monitoring_action
689 false
690 } else {
691 ovr == cu29::simulation::SimOverride::ExecuteByRuntime
692 };
693 }
694 } else {
695 quote! {
696 let doit = true; }
698 };
699 quote! {
700 #call_sim_callback
701 if doit {
702 if let Err(error) = tasks.#task_index.postprocess(clock) {
703 #monitoring_action
704 }
705 }
706 }
707 }
708 )
709 })
710 );
711
712 let mut taskid_call_order: Vec<usize> = Vec::new();
715
716 let runtime_plan_code_and_logging: Vec<(proc_macro2::TokenStream, proc_macro2::TokenStream)> = runtime_plan.steps
717 .iter()
718 .map(|unit| {
719 match unit {
720 CuExecutionUnit::Step(step) => {
721 #[cfg(feature = "macro_debug")]
722 eprintln!(
723 "{} -> {} as {:?}. task_id: {} Input={:?}, Output={:?}",
724 step.node.get_id(),
725 step.node.get_type(),
726 step.task_type,
727 step.node_id,
728 step.input_msg_indices_types,
729 step.output_msg_index_type
730 );
731
732 let node_index = int2sliceindex(step.node_id);
733 let task_instance = quote! { tasks.#node_index };
734 let comment_str = format!(
735 "DEBUG ->> {} ({:?}) Id:{} I:{:?} O:{:?}",
736 step.node.get_id(),
737 step.task_type,
738 step.node_id,
739 step.input_msg_indices_types,
740 step.output_msg_index_type
741 );
742 let comment_tokens = quote! {
743 {
744 let _ = stringify!(#comment_str);
745 }
746 };
747 let tid = step.node_id as usize;
749 taskid_call_order.push(tid);
750
751 let task_enum_name = config_id_to_enum(&task_specs.ids[tid]);
752 let enum_name = Ident::new(&task_enum_name, proc_macro2::Span::call_site());
753
754 let (process_call, preprocess_logging) = match step.task_type {
755 CuTaskType::Source => {
756 if let Some((output_index, _)) = &step.output_msg_index_type {
757 let output_culist_index = int2sliceindex(*output_index);
758
759 let monitoring_action = quote! {
760 debug!("Task {}: Error during process: {}", #mission_mod::TASKS_IDS[#tid], &error);
761 let decision = monitor.process_error(#tid, CuTaskState::Process, &error);
762 match decision {
763 Decision::Abort => {
764 debug!("Process: ABORT decision from monitoring. Task '{}' errored out \
765 during process. Skipping the processing of CL {}.", #mission_mod::TASKS_IDS[#tid], clid);
766 monitor.process_copperlist(&#mission_mod::collect_metadata(&culist))?;
767 cl_manager.end_of_processing(clid)?;
768 return Ok(()); }
771 Decision::Ignore => {
772 debug!("Process: IGNORE decision from monitoring. Task '{}' errored out \
773 during process. The runtime will continue with a forced empty message.", #mission_mod::TASKS_IDS[#tid]);
774 let cumsg_output = &mut msgs.#output_culist_index;
775 cumsg_output.clear_payload();
776 }
777 Decision::Shutdown => {
778 debug!("Process: SHUTDOWN decision from monitoring. Task '{}' errored out \
779 during process. The runtime cannot continue.", #mission_mod::TASKS_IDS[#tid]);
780 return Err(CuError::new_with_cause("Task errored out during process.", error));
781 }
782 }
783 };
784 let call_sim_callback = if sim_mode {
785 quote! {
786 let doit = {
787 let cumsg_output = &mut msgs.#output_culist_index;
788 let state = cu29::simulation::CuTaskCallbackState::Process((), cumsg_output);
789 let ovr = sim_callback(SimStep::#enum_name(state));
790 if let cu29::simulation::SimOverride::Errored(reason) = ovr {
791 let error: CuError = reason.into();
792 #monitoring_action
793 false
794 } else {
795 ovr == cu29::simulation::SimOverride::ExecuteByRuntime
796 }
797 };
798 }
799 } else {
800 quote! {
801 let doit = true; }
803 };
804
805 (quote! { {
807 #comment_tokens
808 {
809 kf_manager.freeze_task(clid, &#task_instance)?;
811 #call_sim_callback
812 let cumsg_output = &mut msgs.#output_culist_index;
813 cumsg_output.metadata.process_time.start = clock.now().into();
814 let maybe_error = if doit {
815 #task_instance.process(clock, cumsg_output)
816 } else {
817 Ok(())
818 };
819 cumsg_output.metadata.process_time.end = clock.now().into();
820 if let Err(error) = maybe_error {
821 #monitoring_action
822 }
823 }
824 }
825 }, { if !task_specs.logging_enabled[*output_index as usize] {
827
828 #[cfg(feature = "macro_debug")]
829 eprintln!(
830 "{} -> Logging Disabled",
831 step.node.get_id(),
832 );
833
834
835 let output_culist_index = int2sliceindex(*output_index);
836 quote! {
837 let mut cumsg_output = &mut culist.msgs.0.#output_culist_index;
838 cumsg_output.clear_payload();
839 }
840 } else {
841 #[cfg(feature = "macro_debug")]
842 eprintln!(
843 "{} -> Logging Enabled",
844 step.node.get_id(),
845 );
846 quote!() }
848 }
849 )
850 } else {
851 panic!("Source task should have an output message index.");
852 }
853 }
854 CuTaskType::Sink => {
855 let indices = step.input_msg_indices_types.iter().map(|(index, _)| int2sliceindex(*index));
857 if let Some((output_index, _)) = &step.output_msg_index_type {
858 let output_culist_index = int2sliceindex(*output_index);
859
860 let monitoring_action = quote! {
861 debug!("Task {}: Error during process: {}", #mission_mod::TASKS_IDS[#tid], &error);
862 let decision = monitor.process_error(#tid, CuTaskState::Process, &error);
863 match decision {
864 Decision::Abort => {
865 debug!("Process: ABORT decision from monitoring. Task '{}' errored out \
866 during process. Skipping the processing of CL {}.", #mission_mod::TASKS_IDS[#tid], clid);
867 monitor.process_copperlist(&#mission_mod::collect_metadata(&culist))?;
868 cl_manager.end_of_processing(clid)?;
869 return Ok(()); }
872 Decision::Ignore => {
873 debug!("Process: IGNORE decision from monitoring. Task '{}' errored out \
874 during process. The runtime will continue with a forced empty message.", #mission_mod::TASKS_IDS[#tid]);
875 let cumsg_output = &mut msgs.#output_culist_index;
876 cumsg_output.clear_payload();
877 }
878 Decision::Shutdown => {
879 debug!("Process: SHUTDOWN decision from monitoring. Task '{}' errored out \
880 during process. The runtime cannot continue.", #mission_mod::TASKS_IDS[#tid]);
881 return Err(CuError::new_with_cause("Task errored out during process.", error));
882 }
883 }
884 };
885
886 let call_sim_callback = if sim_mode {
887 let inputs_type = if indices.len() == 1 {
888 quote! { #(msgs.#indices)* }
890 } else {
891 quote! { (#(&msgs.#indices),*) }
893 };
894
895 quote! {
896 let doit = {
897 let cumsg_input = &#inputs_type;
898 let cumsg_output = &mut msgs.#output_culist_index;
900 let state = cu29::simulation::CuTaskCallbackState::Process(cumsg_input, cumsg_output);
901 let ovr = sim_callback(SimStep::#enum_name(state));
902
903 if let cu29::simulation::SimOverride::Errored(reason) = ovr {
904 let error: CuError = reason.into();
905 #monitoring_action
906 false
907 } else {
908 ovr == cu29::simulation::SimOverride::ExecuteByRuntime
909 }
910 };
911 }
912 } else {
913 quote! {
914 let doit = true; }
916 };
917
918 let indices = step.input_msg_indices_types.iter().map(|(index, _)| int2sliceindex(*index));
919
920 let inputs_type = if indices.len() == 1 {
921 quote! { #(msgs.#indices)* }
923 } else {
924 quote! { (#(&msgs.#indices),*) }
926 };
927
928 (quote! {
929 {
930 #comment_tokens
931 kf_manager.freeze_task(clid, &#task_instance)?;
933 #call_sim_callback
934 let cumsg_input = &#inputs_type;
935 let cumsg_output = &mut msgs.#output_culist_index;
937 cumsg_output.metadata.process_time.start = clock.now().into();
938 let maybe_error = if doit {#task_instance.process(clock, cumsg_input)} else {Ok(())};
939 cumsg_output.metadata.process_time.end = clock.now().into();
940 if let Err(error) = maybe_error {
941 #monitoring_action
942 }
943 }
944 }, {
945 quote!() })
947 } else {
948 panic!("Sink tasks should have a virtual output message index.");
949 }
950 }
951 CuTaskType::Regular => {
952 let indices = step.input_msg_indices_types.iter().map(|(index, _)| int2sliceindex(*index));
953 if let Some((output_index, _)) = &step.output_msg_index_type {
954 let output_culist_index = int2sliceindex(*output_index);
955
956 let monitoring_action = quote! {
957 debug!("Task {}: Error during process: {}", #mission_mod::TASKS_IDS[#tid], &error);
958 let decision = monitor.process_error(#tid, CuTaskState::Process, &error);
959 match decision {
960 Decision::Abort => {
961 debug!("Process: ABORT decision from monitoring. Task '{}' errored out \
962 during process. Skipping the processing of CL {}.", #mission_mod::TASKS_IDS[#tid], clid);
963 monitor.process_copperlist(&#mission_mod::collect_metadata(&culist))?;
964 cl_manager.end_of_processing(clid)?;
965 return Ok(()); }
968 Decision::Ignore => {
969 debug!("Process: IGNORE decision from monitoring. Task '{}' errored out \
970 during process. The runtime will continue with a forced empty message.", #mission_mod::TASKS_IDS[#tid]);
971 let cumsg_output = &mut msgs.#output_culist_index;
972 cumsg_output.clear_payload();
973 }
974 Decision::Shutdown => {
975 debug!("Process: SHUTDOWN decision from monitoring. Task '{}' errored out \
976 during process. The runtime cannot continue.", #mission_mod::TASKS_IDS[#tid]);
977 return Err(CuError::new_with_cause("Task errored out during process.", error));
978 }
979 }
980 };
981
982 let call_sim_callback = if sim_mode {
983 let inputs_type = if indices.len() == 1 {
984 quote! { #(msgs.#indices)* }
986 } else {
987 quote! { (#(&msgs.#indices),*) }
989 };
990
991 quote! {
992 let doit = {
993 let cumsg_input = &#inputs_type;
994 let cumsg_output = &mut msgs.#output_culist_index;
995 let state = cu29::simulation::CuTaskCallbackState::Process(cumsg_input, cumsg_output);
996 let ovr = sim_callback(SimStep::#enum_name(state));
997
998 if let cu29::simulation::SimOverride::Errored(reason) = ovr {
999 let error: CuError = reason.into();
1000 #monitoring_action
1001 false
1002 }
1003 else {
1004 ovr == cu29::simulation::SimOverride::ExecuteByRuntime
1005 }
1006 };
1007 }
1008 } else {
1009 quote! {
1010 let doit = true; }
1012 };
1013
1014 let indices = step.input_msg_indices_types.iter().map(|(index, _)| int2sliceindex(*index));
1015 let inputs_type = if indices.len() == 1 {
1016 quote! { #(msgs.#indices)* }
1018 } else {
1019 quote! { (#(&msgs.#indices),*) }
1021 };
1022
1023 (quote! {
1024 {
1025 #comment_tokens
1026 kf_manager.freeze_task(clid, &#task_instance)?;
1028 #call_sim_callback
1029 let cumsg_input = &#inputs_type;
1030 let cumsg_output = &mut msgs.#output_culist_index;
1031 cumsg_output.metadata.process_time.start = clock.now().into();
1032 let maybe_error = if doit {#task_instance.process(clock, cumsg_input, cumsg_output)} else {Ok(())};
1033 cumsg_output.metadata.process_time.end = clock.now().into();
1034 if let Err(error) = maybe_error {
1035 #monitoring_action
1036 }
1037 }
1038 }, {
1039
1040 if !task_specs.logging_enabled[*output_index as usize] {
1041 #[cfg(feature = "macro_debug")]
1042 eprintln!(
1043 "{} -> Logging Disabled",
1044 step.node.get_id(),
1045 );
1046 let output_culist_index = int2sliceindex(*output_index);
1047 quote! {
1048 let mut cumsg_output = &mut culist.msgs.0.#output_culist_index;
1049 cumsg_output.clear_payload();
1050 }
1051 } else {
1052 #[cfg(feature = "macro_debug")]
1053 eprintln!(
1054 "{} -> Logging Enabled",
1055 step.node.get_id(),
1056 );
1057 quote!() }
1059 })
1060 } else {
1061 panic!("Regular task should have an output message index.");
1062 }
1063 }
1064 };
1065
1066 (process_call, preprocess_logging)
1067 }
1068 CuExecutionUnit::Loop(_) => todo!("Needs to be implemented"),
1069 }
1070 }).collect();
1071 #[cfg(feature = "macro_debug")]
1072 eprintln!("[Culist access order: {taskid_call_order:?}]");
1073
1074 let all_tasks_member_ids: Vec<String> = task_specs
1076 .ids
1077 .iter()
1078 .map(|name| utils::config_id_to_struct_member(name.as_str()))
1079 .collect();
1080
1081 #[cfg(feature = "macro_debug")]
1082 eprintln!("[build the copperlist support]");
1083 let culist_support: proc_macro2::TokenStream =
1084 gen_culist_support(&runtime_plan, &taskid_call_order, &all_tasks_member_ids);
1085
1086 #[cfg(feature = "macro_debug")]
1087 eprintln!("[build the sim support]");
1088 let sim_support: proc_macro2::TokenStream = gen_sim_support(&runtime_plan);
1089
1090 let (new, run_one_iteration, start_all_tasks, stop_all_tasks, run) = if sim_mode {
1091 (
1092 quote! {
1093 fn new(clock:RobotClock, unified_logger: Arc<Mutex<UnifiedLoggerWrite>>, config_override: Option<CuConfig>, sim_callback: &mut impl FnMut(SimStep) -> cu29::simulation::SimOverride) -> CuResult<Self>
1094 },
1095 quote! {
1096 fn run_one_iteration(&mut self, sim_callback: &mut impl FnMut(SimStep) -> cu29::simulation::SimOverride) -> CuResult<()>
1097 },
1098 quote! {
1099 fn start_all_tasks(&mut self, sim_callback: &mut impl FnMut(SimStep) -> cu29::simulation::SimOverride) -> CuResult<()>
1100 },
1101 quote! {
1102 fn stop_all_tasks(&mut self, sim_callback: &mut impl FnMut(SimStep) -> cu29::simulation::SimOverride) -> CuResult<()>
1103 },
1104 quote! {
1105 fn run(&mut self, sim_callback: &mut impl FnMut(SimStep) -> cu29::simulation::SimOverride) -> CuResult<()>
1106 },
1107 )
1108 } else {
1109 (
1110 quote! {
1111 fn new(clock:RobotClock, unified_logger: Arc<Mutex<UnifiedLoggerWrite>>, config_override: Option<CuConfig>) -> CuResult<Self>
1112 },
1113 quote! {
1114 fn run_one_iteration(&mut self) -> CuResult<()>
1115 },
1116 quote! {
1117 fn start_all_tasks(&mut self) -> CuResult<()>
1118 },
1119 quote! {
1120 fn stop_all_tasks(&mut self) -> CuResult<()>
1121 },
1122 quote! {
1123 fn run(&mut self) -> CuResult<()>
1124 },
1125 )
1126 };
1127
1128 let sim_callback_arg = if sim_mode {
1129 Some(quote!(sim_callback))
1130 } else {
1131 None
1132 };
1133
1134 let sim_callback_on_new_calls = task_specs.ids.iter().enumerate().map(|(i, id)| {
1135 let enum_name = config_id_to_enum(id);
1136 let enum_ident = Ident::new(&enum_name, Span::call_site());
1137 quote! {
1138 sim_callback(SimStep::#enum_ident(cu29::simulation::CuTaskCallbackState::New(all_instances_configs[#i].cloned())));
1140 }
1141 });
1142
1143 let sim_callback_on_new = if sim_mode {
1144 Some(quote! {
1145 let graph = config.get_graph(Some(#mission)).expect("Could not find the mission #mission");
1146 let all_instances_configs: Vec<Option<&ComponentConfig>> = graph
1147 .get_all_nodes()
1148 .iter()
1149 .map(|(_, node)| node.get_instance_config())
1150 .collect();
1151 #(#sim_callback_on_new_calls)*
1152 })
1153 } else {
1154 None
1155 };
1156
1157 let (runtime_plan_code, preprocess_logging_calls): (Vec<_>, Vec<_>) =
1158 itertools::multiunzip(runtime_plan_code_and_logging);
1159
1160 #[cfg(feature = "macro_debug")]
1161 eprintln!("[build the run methods]");
1162 let run_methods = quote! {
1163
1164 #run_one_iteration {
1165
1166 let runtime = &mut self.copper_runtime;
1168 let clock = &runtime.clock;
1169 let monitor = &mut runtime.monitor;
1170 let tasks = &mut runtime.tasks;
1171 let cl_manager = &mut runtime.copperlists_manager;
1172 let kf_manager = &mut runtime.keyframes_manager;
1173
1174 #(#preprocess_calls)*
1176
1177 let culist = cl_manager.inner.create().expect("Ran out of space for copper lists"); let clid = culist.id;
1179 kf_manager.reset(clid, clock); culist.change_state(cu29::copperlist::CopperListState::Processing);
1181 culist.msgs.init_zeroed();
1182 {
1183 let msgs = &mut culist.msgs.0;
1184 #(#runtime_plan_code)*
1185 } monitor.process_copperlist(&#mission_mod::collect_metadata(&culist))?;
1187
1188 #(#preprocess_logging_calls)*
1190
1191 cl_manager.end_of_processing(clid)?;
1192 kf_manager.end_of_processing(clid)?;
1193
1194 #(#postprocess_calls)*
1196 Ok(())
1197 }
1198
1199 fn restore_keyframe(&mut self, keyframe: &KeyFrame) -> CuResult<()> {
1200 let runtime = &mut self.copper_runtime;
1201 let clock = &runtime.clock;
1202 let tasks = &mut runtime.tasks;
1203 let config = cu29::bincode::config::standard();
1204 let reader = cu29::bincode::de::read::SliceReader::new(&keyframe.serialized_tasks);
1205 let mut decoder = DecoderImpl::new(reader, config, ());
1206 #(#task_restore_code);*;
1207 Ok(())
1208 }
1209
1210 #start_all_tasks {
1211 #(#start_calls)*
1212 self.copper_runtime.monitor.start(&self.copper_runtime.clock)?;
1213 Ok(())
1214 }
1215
1216 #stop_all_tasks {
1217 #(#stop_calls)*
1218 self.copper_runtime.monitor.stop(&self.copper_runtime.clock)?;
1219 Ok(())
1220 }
1221
1222 #run {
1223 static STOP_FLAG: AtomicBool = AtomicBool::new(false);
1224 ctrlc::set_handler(move || {
1225 STOP_FLAG.store(true, Ordering::SeqCst);
1226 }).expect("Error setting Ctrl-C handler");
1227
1228 self.start_all_tasks(#sim_callback_arg)?;
1229 let result = loop {
1230 let iter_start = self.copper_runtime.clock.now();
1231 let result = self.run_one_iteration(#sim_callback_arg);
1232
1233 if let Some(rate) = self.copper_runtime.runtime_config.rate_target_hz {
1234 let period = 1_000_000_000u64 / rate;
1235 let elapsed = self.copper_runtime.clock.now() - iter_start;
1236 if elapsed.as_nanos() < period {
1237 std::thread::sleep(std::time::Duration::from_nanos(period - elapsed.as_nanos()));
1238 }
1239 }
1240
1241 if STOP_FLAG.load(Ordering::SeqCst) || result.is_err() {
1242 break result;
1243 }
1244 };
1245 if result.is_err() {
1246 error!("A task errored out: {}", &result);
1247 }
1248 self.stop_all_tasks(#sim_callback_arg)?;
1249 result
1250 }
1251 };
1252
1253 let tasks_type = if sim_mode {
1254 quote!(CuSimTasks)
1255 } else {
1256 quote!(CuTasks)
1257 };
1258
1259 let tasks_instanciator_fn = if sim_mode {
1260 quote!(tasks_instanciator_sim)
1261 } else {
1262 quote!(tasks_instanciator)
1263 };
1264
1265 let app_impl_decl = if sim_mode {
1266 quote!(impl CuSimApplication for #application_name)
1267 } else {
1268 quote!(impl CuApplication for #application_name)
1269 };
1270 let simstep_type_decl = if sim_mode {
1271 quote!(
1272 type Step<'z> = SimStep<'z>;
1273 )
1274 } else {
1275 quote!()
1276 };
1277
1278 #[cfg(feature = "macro_debug")]
1279 eprintln!("[build result]");
1280 let application_impl = quote! {
1281 #app_impl_decl {
1282 #simstep_type_decl
1283
1284 #new {
1285 let config_filename = #config_file;
1286 let config = if config_override.is_some() {
1287 let overridden_config = config_override.unwrap();
1288 debug!("CuConfig: Overridden programmatically: {}", &overridden_config.serialize_ron());
1289 overridden_config
1290 } else if std::path::Path::new(config_filename).exists() {
1291 debug!("CuConfig: Reading configuration from file: {}", config_filename);
1292 cu29::config::read_configuration(config_filename)?
1293 } else {
1294 let original_config = Self::get_original_config();
1295 debug!("CuConfig: Using the original configuration the project was compiled with: {}", &original_config);
1296 cu29::config::read_configuration_str(original_config, None)?
1297 };
1298
1299 let mut default_section_size = std::mem::size_of::<super::#mission_mod::CuList>() * 64;
1302 if let Some(section_size_mib) = config.logging.as_ref().and_then(|l| l.section_size_mib) {
1304 default_section_size = section_size_mib as usize * 1024usize * 1024usize;
1306 }
1307 let copperlist_stream = stream_write::<#mission_mod::CuList>(
1308 unified_logger.clone(),
1309 UnifiedLogType::CopperList,
1310 default_section_size,
1311 );
1315
1316 let keyframes_stream = stream_write::<KeyFrame>(
1317 unified_logger.clone(),
1318 UnifiedLogType::FrozenTasks,
1319 1024 * 1024 * 10, );
1321
1322
1323 let application = Ok(#application_name {
1324 copper_runtime: CuRuntime::<#mission_mod::#tasks_type, #mission_mod::CuStampedDataSet, #monitor_type, #DEFAULT_CLNB>::new(
1325 clock,
1326 &config,
1327 Some(#mission),
1328 #mission_mod::#tasks_instanciator_fn,
1329 #mission_mod::monitor_instanciator,
1330 copperlist_stream,
1331 keyframes_stream)?, });
1333
1334 #sim_callback_on_new
1335
1336 application
1337 }
1338
1339 fn get_original_config() -> String {
1340 #copper_config_content.to_string()
1341 }
1342
1343 #run_methods
1344 }
1345 };
1346
1347 let (
1348 builder_struct,
1349 builder_new,
1350 builder_impl,
1351 builder_sim_callback_method,
1352 builder_build_sim_callback_arg,
1353 ) = if sim_mode {
1354 (
1355 quote! {
1356 #[allow(dead_code)]
1357 pub struct #builder_name <'a, F> {
1358 clock: Option<RobotClock>,
1359 unified_logger: Option<Arc<Mutex<UnifiedLoggerWrite>>>,
1360 config_override: Option<CuConfig>,
1361 sim_callback: Option<&'a mut F>
1362 }
1363 },
1364 quote! {
1365 #[allow(dead_code)]
1366 pub fn new() -> Self {
1367 Self {
1368 clock: None,
1369 unified_logger: None,
1370 config_override: None,
1371 sim_callback: None,
1372 }
1373 }
1374 },
1375 quote! {
1376 impl<'a, F> #builder_name <'a, F>
1377 where
1378 F: FnMut(SimStep) -> cu29::simulation::SimOverride,
1379 },
1380 Some(quote! {
1381 pub fn with_sim_callback(mut self, sim_callback: &'a mut F) -> Self
1382 {
1383 self.sim_callback = Some(sim_callback);
1384 self
1385 }
1386 }),
1387 Some(quote! {
1388 self.sim_callback
1389 .ok_or(CuError::from("Sim callback missing from builder"))?,
1390 }),
1391 )
1392 } else {
1393 (
1394 quote! {
1395 #[allow(dead_code)]
1396 pub struct #builder_name {
1397 clock: Option<RobotClock>,
1398 unified_logger: Option<Arc<Mutex<UnifiedLoggerWrite>>>,
1399 config_override: Option<CuConfig>,
1400 }
1401 },
1402 quote! {
1403 #[allow(dead_code)]
1404 pub fn new() -> Self {
1405 Self {
1406 clock: None,
1407 unified_logger: None,
1408 config_override: None,
1409 }
1410 }
1411 },
1412 quote! {
1413 impl #builder_name
1414 },
1415 None,
1416 None,
1417 )
1418 };
1419
1420 let application_builder = quote! {
1421 #builder_struct
1422
1423 #builder_impl
1424 {
1425 #builder_new
1426
1427 #[allow(dead_code)]
1428 pub fn with_clock(mut self, clock: RobotClock) -> Self {
1429 self.clock = Some(clock);
1430 self
1431 }
1432
1433 #[allow(dead_code)]
1434 pub fn with_unified_logger(mut self, unified_logger: Arc<Mutex<UnifiedLoggerWrite>>) -> Self {
1435 self.unified_logger = Some(unified_logger);
1436 self
1437 }
1438
1439 #[allow(dead_code)]
1440 pub fn with_context(mut self, copper_ctx: &CopperContext) -> Self {
1441 self.clock = Some(copper_ctx.clock.clone());
1442 self.unified_logger = Some(copper_ctx.unified_logger.clone());
1443 self
1444 }
1445
1446 #[allow(dead_code)]
1447 pub fn with_config(mut self, config_override: CuConfig) -> Self {
1448 self.config_override = Some(config_override);
1449 self
1450 }
1451
1452 #builder_sim_callback_method
1453
1454 #[allow(dead_code)]
1455 pub fn build(self) -> CuResult<#application_name> {
1456 #application_name::new(
1457 self.clock
1458 .ok_or(CuError::from("Clock missing from builder"))?,
1459 self.unified_logger
1460 .ok_or(CuError::from("Unified logger missing from builder"))?,
1461 self.config_override,
1462 #builder_build_sim_callback_arg
1463 )
1464 }
1465 }
1466 };
1467
1468 let ids = task_specs.ids;
1469 let mission_mod_tokens = quote! {
1471 mod #mission_mod {
1472 use super::*; use cu29::bincode::Encode;
1475 use cu29::bincode::enc::Encoder;
1476 use cu29::bincode::error::EncodeError;
1477 use cu29::bincode::Decode;
1478 use cu29::bincode::de::Decoder;
1479 use cu29::bincode::de::DecoderImpl;
1480 use cu29::bincode::error::DecodeError;
1481 use cu29::rayon::ThreadPool;
1482 use cu29::clock::RobotClock;
1483 use cu29::config::CuConfig;
1484 use cu29::config::ComponentConfig;
1485 use cu29::cuasynctask::CuAsyncTask;
1486 use cu29::curuntime::CuRuntime;
1487 use cu29::curuntime::KeyFrame;
1488 use cu29::curuntime::CopperContext;
1489 use cu29::CuResult;
1490 use cu29::CuError;
1491 use cu29::cutask::CuSrcTask;
1492 use cu29::cutask::CuSinkTask;
1493 use cu29::cutask::CuTask;
1494 use cu29::cutask::CuMsg;
1495 use cu29::cutask::CuMsgMetadata;
1496 use cu29::copperlist::CopperList;
1497 use cu29::monitoring::CuMonitor; use cu29::monitoring::CuTaskState;
1499 use cu29::monitoring::Decision;
1500 use cu29::prelude::app::CuApplication;
1501 use cu29::prelude::debug;
1502 use cu29::prelude::stream_write;
1503 use cu29::prelude::UnifiedLoggerWrite;
1504 use cu29::prelude::UnifiedLogType;
1505 use std::sync::Arc;
1506 use std::sync::Mutex;
1507 use std::sync::atomic::{AtomicBool, Ordering};
1508
1509 #[allow(unused_imports)]
1511 use cu29::prelude::app::CuSimApplication;
1512
1513 #[allow(unused_imports)]
1515 use cu29::monitoring::NoMonitor;
1516
1517 pub type CuTasks = #task_types_tuple;
1521
1522 #[allow(dead_code)]
1525 pub type CuSimTasks = #task_types_tuple_sim;
1526
1527 pub const TASKS_IDS: &'static [&'static str] = &[#( #ids ),*];
1528
1529 #culist_support
1530
1531 #sim_support
1532
1533 pub fn tasks_instanciator<'c>(all_instances_configs: Vec<Option<&'c ComponentConfig>>, threadpool: Arc<ThreadPool>) -> CuResult<CuTasks> {
1534 Ok(( #(#task_instances_init_code),*, ))
1535 }
1536
1537 #[allow(dead_code)]
1538 pub fn tasks_instanciator_sim(all_instances_configs: Vec<Option<&ComponentConfig>>, _threadpool: Arc<ThreadPool>) -> CuResult<CuSimTasks> {
1539 Ok(( #(#task_sim_instances_init_code),*, ))
1540 }
1541
1542 pub fn monitor_instanciator(config: &CuConfig) -> #monitor_type {
1543 #monitor_type::new(config, #mission_mod::TASKS_IDS).expect("Failed to create the given monitor.")
1544 }
1545
1546 pub #application_struct
1548
1549 #application_impl
1550
1551 #application_builder
1552 }
1553
1554 };
1555 all_missions_tokens.push(mission_mod_tokens);
1556 }
1557
1558 let default_application_tokens = if all_missions.contains_key("default") {
1559 quote! {
1560 #[allow(unused_imports)]
1562 use default::#builder_name;
1563
1564 #[allow(unused_imports)]
1565 use default::#application_name;
1566 }
1567 } else {
1568 quote!() };
1570
1571 let result: proc_macro2::TokenStream = quote! {
1572 #(#all_missions_tokens)*
1573 #default_application_tokens
1574 };
1575
1576 #[cfg(feature = "macro_debug")]
1578 {
1579 let formatted_code = rustfmt_generated_code(result.to_string());
1580 eprintln!("\n === Gen. Runtime ===\n");
1581 eprintln!("{formatted_code}");
1582 eprintln!("\n === === === === === ===\n");
1585 }
1586 result.into()
1587}
1588
1589fn read_config(config_file: &str) -> CuResult<CuConfig> {
1590 let filename = config_full_path(config_file);
1591
1592 read_configuration(filename.as_str())
1593}
1594
1595fn config_full_path(config_file: &str) -> String {
1596 let mut config_full_path = utils::caller_crate_root();
1597 config_full_path.push(config_file);
1598 let filename = config_full_path
1599 .as_os_str()
1600 .to_str()
1601 .expect("Could not interpret the config file name");
1602 filename.to_string()
1603}
1604
1605fn extract_tasks_output_types(graph: &CuGraph) -> Vec<Option<Type>> {
1606 let result = graph
1607 .get_all_nodes()
1608 .iter()
1609 .map(|(_, node)| {
1610 let id = node.get_id();
1611 let type_str = graph.get_node_output_msg_type(id.as_str());
1612 let result = type_str.map(|type_str| {
1613 let result = parse_str::<Type>(type_str.as_str())
1614 .expect("Could not parse output message type.");
1615 result
1616 });
1617 result
1618 })
1619 .collect();
1620 result
1621}
1622
1623struct CuTaskSpecSet {
1624 pub ids: Vec<String>,
1625 pub cutypes: Vec<CuTaskType>,
1626 pub background_flags: Vec<bool>,
1627 pub logging_enabled: Vec<bool>,
1628 pub type_names: Vec<String>,
1629 pub task_types: Vec<Type>,
1630 pub instantiation_types: Vec<Type>,
1631 pub sim_task_types: Vec<Type>,
1632 #[allow(dead_code)]
1633 pub output_types: Vec<Option<Type>>,
1634}
1635
1636impl CuTaskSpecSet {
1637 pub fn from_graph(graph: &CuGraph) -> Self {
1638 let all_id_nodes = graph.get_all_nodes();
1639
1640 let ids = all_id_nodes
1641 .iter()
1642 .map(|(_, node)| node.get_id().to_string())
1643 .collect();
1644
1645 let cutypes = all_id_nodes
1646 .iter()
1647 .map(|(id, _)| find_task_type_for_id(graph, *id))
1648 .collect();
1649
1650 let background_flags: Vec<bool> = all_id_nodes
1651 .iter()
1652 .map(|(_, node)| node.is_background())
1653 .collect();
1654
1655 let logging_enabled: Vec<bool> = all_id_nodes
1656 .iter()
1657 .map(|(_, node)| node.is_logging_enabled())
1658 .collect();
1659
1660 let type_names: Vec<String> = all_id_nodes
1661 .iter()
1662 .map(|(_, node)| node.get_type().to_string())
1663 .collect();
1664
1665 let output_types = extract_tasks_output_types(graph);
1666
1667 let task_types = type_names
1668 .iter()
1669 .zip(background_flags.iter())
1670 .zip(output_types.iter())
1671 .map(|((name, &background), output_type)| {
1672 let name_type = parse_str::<Type>(name).unwrap_or_else(|error| {
1673 panic!("Could not transform {name} into a Task Rust type: {error}");
1674 });
1675 if background {
1676 if let Some(output_type) = output_type {
1677 parse_quote!(cu29::cuasynctask::CuAsyncTask<#name_type, #output_type>)
1678 } else {
1679 panic!("{name}: If a task is background, it has to have an output");
1680 }
1681 } else {
1682 name_type
1683 }
1684 })
1685 .collect();
1686
1687 let instantiation_types = type_names
1688 .iter()
1689 .zip(background_flags.iter())
1690 .zip(output_types.iter())
1691 .map(|((name, &background), output_type)| {
1692 let name_type = parse_str::<Type>(name).unwrap_or_else(|error| {
1693 panic!("Could not transform {name} into a Task Rust type: {error}");
1694 });
1695 if background {
1696 if let Some(output_type) = output_type {
1697 parse_quote!(cu29::cuasynctask::CuAsyncTask::<#name_type, #output_type>)
1698 } else {
1699 panic!("{name}: If a task is background, it has to have an output");
1700 }
1701 } else {
1702 name_type
1703 }
1704 })
1705 .collect();
1706
1707 let sim_task_types = type_names
1708 .iter()
1709 .map(|name| {
1710 parse_str::<Type>(name).unwrap_or_else(|err| {
1711 eprintln!("Could not transform {name} into a Task Rust type.");
1712 panic!("{err}")
1713 })
1714 })
1715 .collect();
1716
1717 Self {
1718 ids,
1719 cutypes,
1720 background_flags,
1721 logging_enabled,
1722 type_names,
1723 task_types,
1724 instantiation_types,
1725 sim_task_types,
1726 output_types,
1727 }
1728 }
1729}
1730
1731fn extract_msg_types(runtime_plan: &CuExecutionLoop) -> Vec<Type> {
1732 runtime_plan
1733 .steps
1734 .iter()
1735 .filter_map(|unit| match unit {
1736 CuExecutionUnit::Step(step) => {
1737 if let Some((_, output_msg_type)) = &step.output_msg_index_type {
1738 Some(
1739 parse_str::<Type>(output_msg_type.as_str()).unwrap_or_else(|_| {
1740 panic!(
1741 "Could not transform {output_msg_type} into a message Rust type."
1742 )
1743 }),
1744 )
1745 } else {
1746 None
1747 }
1748 }
1749 CuExecutionUnit::Loop(_) => todo!("Needs to be implemented"),
1750 })
1751 .collect()
1752}
1753
1754fn build_culist_tuple(all_msgs_types_in_culist_order: &[Type]) -> TypeTuple {
1756 if all_msgs_types_in_culist_order.is_empty() {
1757 parse_quote! { () }
1758 } else {
1759 parse_quote! {
1760 ( #( CuMsg<#all_msgs_types_in_culist_order> ),* )
1761 }
1762 }
1763}
1764
1765fn build_culist_tuple_encode(all_msgs_types_in_culist_order: &[Type]) -> ItemImpl {
1767 let indices: Vec<usize> = (0..all_msgs_types_in_culist_order.len()).collect();
1768
1769 let encode_fields: Vec<_> = indices
1771 .iter()
1772 .map(|i| {
1773 let idx = syn::Index::from(*i);
1774 quote! { self.0.#idx.encode(encoder)?; }
1775 })
1776 .collect();
1777
1778 parse_quote! {
1779 impl Encode for CuStampedDataSet {
1780 fn encode<E: Encoder>(&self, encoder: &mut E) -> Result<(), EncodeError> {
1781 #(#encode_fields)*
1782 Ok(())
1783 }
1784 }
1785 }
1786}
1787
1788fn build_culist_tuple_decode(all_msgs_types_in_culist_order: &[Type]) -> ItemImpl {
1790 let indices: Vec<usize> = (0..all_msgs_types_in_culist_order.len()).collect();
1791
1792 let decode_fields: Vec<_> = indices
1794 .iter()
1795 .map(|i| {
1796 let t = &all_msgs_types_in_culist_order[*i];
1797 quote! { CuMsg::<#t>::decode(decoder)? }
1798 })
1799 .collect();
1800
1801 parse_quote! {
1802 impl Decode<()> for CuStampedDataSet {
1803 fn decode<D: Decoder<Context=()>>(decoder: &mut D) -> Result<Self, DecodeError> {
1804 Ok(CuStampedDataSet ((
1805 #(#decode_fields),*
1806 )))
1807 }
1808 }
1809 }
1810}
1811
1812fn build_culist_erasedcumsgs(all_msgs_types_in_culist_order: &[Type]) -> ItemImpl {
1813 let indices: Vec<usize> = (0..all_msgs_types_in_culist_order.len()).collect();
1814 let casted_fields: Vec<_> = indices
1815 .iter()
1816 .map(|i| {
1817 let idx = syn::Index::from(*i);
1818 quote! { &self.0.#idx as &dyn ErasedCuStampedData }
1819 })
1820 .collect();
1821 parse_quote! {
1822 impl ErasedCuStampedDataSet for CuStampedDataSet {
1823 fn cumsgs(&self) -> Vec<&dyn ErasedCuStampedData> {
1824 vec![
1825 #(#casted_fields),*
1826 ]
1827 }
1828 }
1829 }
1830}
1831
1832fn build_culist_tuple_debug(all_msgs_types_in_culist_order: &[Type]) -> ItemImpl {
1833 let indices: Vec<usize> = (0..all_msgs_types_in_culist_order.len()).collect();
1834
1835 let debug_fields: Vec<_> = indices
1836 .iter()
1837 .map(|i| {
1838 let idx = syn::Index::from(*i);
1839 quote! { .field(&self.0.#idx) }
1840 })
1841 .collect();
1842
1843 parse_quote! {
1844 impl std::fmt::Debug for CuStampedDataSet {
1845 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1846 f.debug_tuple("CuStampedDataSet")
1847 #(#debug_fields)*
1848 .finish()
1849 }
1850 }
1851 }
1852}
1853
1854fn build_culist_tuple_serialize(all_msgs_types_in_culist_order: &[Type]) -> ItemImpl {
1856 let indices: Vec<usize> = (0..all_msgs_types_in_culist_order.len()).collect();
1857 let tuple_len = all_msgs_types_in_culist_order.len();
1858
1859 let serialize_fields: Vec<_> = indices
1861 .iter()
1862 .map(|i| {
1863 let idx = syn::Index::from(*i);
1864 quote! { &self.0.#idx }
1865 })
1866 .collect();
1867
1868 parse_quote! {
1869 impl Serialize for CuStampedDataSet {
1870 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
1871 where
1872 S: serde::Serializer,
1873 {
1874 use serde::ser::SerializeTuple;
1875 let mut tuple = serializer.serialize_tuple(#tuple_len)?;
1876 #(tuple.serialize_element(#serialize_fields)?;)*
1877 tuple.end()
1878 }
1879 }
1880 }
1881}
1882
1883fn build_culist_tuple_default(all_msgs_types_in_culist_order: &[Type]) -> ItemImpl {
1885 let default_fields: Vec<_> = all_msgs_types_in_culist_order
1887 .iter()
1888 .map(|msg_type| quote! { CuStampedData::<#msg_type, CuMsgMetadata>::default() })
1889 .collect();
1890
1891 parse_quote! {
1892 impl Default for CuStampedDataSet {
1893 fn default() -> CuStampedDataSet
1894 {
1895 CuStampedDataSet((
1896 #(#default_fields),*
1897 ))
1898 }
1899 }
1900 }
1901}
1902
1903#[cfg(test)]
1904mod tests {
1905 #[test]
1907 fn test_compile_fail() {
1908 let t = trybuild::TestCases::new();
1909 t.compile_fail("tests/compile_fail/*/*.rs");
1910 }
1911}