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