mecha10_behavior_runtime/
execution.rs1use crate::{BoxedBehavior, NodeStatus};
6use mecha10_core::Context;
7use std::time::{Duration, Instant};
8use tracing::{debug, info, warn};
9
10#[derive(Clone)]
14pub struct ExecutionContext {
15 ctx: Context,
17
18 tick_rate_hz: f32,
20}
21
22impl ExecutionContext {
23 pub fn new(ctx: Context, tick_rate_hz: f32) -> Self {
25 Self { ctx, tick_rate_hz }
26 }
27
28 pub fn context(&self) -> &Context {
30 &self.ctx
31 }
32
33 pub fn tick_rate_hz(&self) -> f32 {
35 self.tick_rate_hz
36 }
37}
38
39#[derive(Debug, Clone)]
41pub struct ExecutionStats {
42 pub tick_count: usize,
44
45 pub total_duration: Duration,
47
48 pub avg_tick_duration: Duration,
50
51 pub min_tick_duration: Duration,
53
54 pub max_tick_duration: Duration,
56
57 pub final_status: Option<NodeStatus>,
59}
60
61impl ExecutionStats {
62 fn new() -> Self {
63 Self {
64 tick_count: 0,
65 total_duration: Duration::ZERO,
66 avg_tick_duration: Duration::ZERO,
67 min_tick_duration: Duration::MAX,
68 max_tick_duration: Duration::ZERO,
69 final_status: None,
70 }
71 }
72
73 fn update(&mut self, tick_duration: Duration) {
74 self.tick_count += 1;
75 self.total_duration += tick_duration;
76
77 if tick_duration < self.min_tick_duration {
78 self.min_tick_duration = tick_duration;
79 }
80 if tick_duration > self.max_tick_duration {
81 self.max_tick_duration = tick_duration;
82 }
83
84 self.avg_tick_duration = self.total_duration / self.tick_count as u32;
85 }
86
87 fn finalize(&mut self, status: NodeStatus) {
88 self.final_status = Some(status);
89 }
90}
91
92pub struct BehaviorExecutor {
110 behavior: BoxedBehavior,
111 tick_rate_hz: f32,
112 max_ticks: Option<usize>,
113}
114
115macro_rules! lifecycle_step {
119 ($(#[$meta:meta])* $fn_name:ident, $verb:literal, $behavior_method:ident) => {
120 $(#[$meta])*
121 pub async fn $fn_name(&mut self, ctx: &Context) -> anyhow::Result<()> {
122 info!(concat!($verb, " behavior: {}"), self.behavior.name());
123 self.behavior.$behavior_method(ctx).await?;
124 Ok(())
125 }
126 };
127}
128
129impl BehaviorExecutor {
130 mecha10_core::new_with_defaults!(
131 pub fn new(behavior: BoxedBehavior, tick_rate_hz: f32) -> Self {
138 max_ticks: None
139 }
140 );
141
142 mecha10_core::builder_setter!(
143 into_some, with_max_ticks, max_ticks, usize
145 );
146
147 lifecycle_step!(
148 init, "Initializing", on_init
150 );
151
152 pub async fn run_until_complete(&mut self, ctx: &Context) -> anyhow::Result<(NodeStatus, ExecutionStats)> {
161 let mut stats = ExecutionStats::new();
162 let mut interval = tokio::time::interval(Duration::from_secs_f32(1.0 / self.tick_rate_hz));
163
164 info!(
165 "Starting behavior execution: {} at {} Hz",
166 self.behavior.name(),
167 self.tick_rate_hz
168 );
169
170 loop {
171 interval.tick().await;
172
173 if let Some(max_ticks) = self.max_ticks {
174 if stats.tick_count >= max_ticks {
175 warn!("Behavior exceeded maximum ticks ({})", max_ticks);
176 return Err(anyhow::anyhow!("Exceeded maximum ticks"));
177 }
178 }
179
180 let tick_start = Instant::now();
181 let status = self.behavior.tick(ctx).await?;
182 let tick_duration = tick_start.elapsed();
183
184 stats.update(tick_duration);
185
186 debug!(
187 "Tick #{}: status={}, duration={:?}",
188 stats.tick_count, status, tick_duration
189 );
190
191 if status.is_complete() {
192 info!(
193 "Behavior completed with status: {} after {} ticks ({:?})",
194 status, stats.tick_count, stats.total_duration
195 );
196 stats.finalize(status);
197 return Ok((status, stats));
198 }
199 }
200 }
201
202 pub async fn run_for_duration(
207 &mut self,
208 ctx: &Context,
209 duration: Duration,
210 ) -> anyhow::Result<(NodeStatus, ExecutionStats)> {
211 let mut stats = ExecutionStats::new();
212 let mut interval = tokio::time::interval(Duration::from_secs_f32(1.0 / self.tick_rate_hz));
213 let start_time = Instant::now();
214
215 info!(
216 "Starting behavior execution: {} for {:?} at {} Hz",
217 self.behavior.name(),
218 duration,
219 self.tick_rate_hz
220 );
221
222 let mut last_status = NodeStatus::Running;
223
224 while start_time.elapsed() < duration {
225 interval.tick().await;
226
227 let tick_start = Instant::now();
228 let status = self.behavior.tick(ctx).await?;
229 let tick_duration = tick_start.elapsed();
230
231 stats.update(tick_duration);
232 last_status = status;
233
234 debug!(
235 "Tick #{}: status={}, duration={:?}",
236 stats.tick_count, status, tick_duration
237 );
238
239 if status.is_complete() {
240 info!("Behavior completed early with status: {}", status);
241 break;
242 }
243 }
244
245 info!(
246 "Behavior execution ended after {} ticks ({:?})",
247 stats.tick_count, stats.total_duration
248 );
249 stats.finalize(last_status);
250 Ok((last_status, stats))
251 }
252
253 lifecycle_step!(
254 terminate, "Terminating", on_terminate
256 );
257
258 pub async fn reset(&mut self) -> anyhow::Result<()> {
260 info!("Resetting behavior: {}", self.behavior.name());
261 self.behavior.reset().await?;
262 Ok(())
263 }
264}