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