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 pub fn tick_period(&self) -> Duration {
40 Duration::from_secs_f32(1.0 / self.tick_rate_hz)
41 }
42}
43
44#[derive(Debug, Clone)]
46pub struct ExecutionStats {
47 pub tick_count: usize,
49
50 pub total_duration: Duration,
52
53 pub avg_tick_duration: Duration,
55
56 pub min_tick_duration: Duration,
58
59 pub max_tick_duration: Duration,
61
62 pub final_status: Option<NodeStatus>,
64}
65
66impl ExecutionStats {
67 fn new() -> Self {
68 Self {
69 tick_count: 0,
70 total_duration: Duration::ZERO,
71 avg_tick_duration: Duration::ZERO,
72 min_tick_duration: Duration::MAX,
73 max_tick_duration: Duration::ZERO,
74 final_status: None,
75 }
76 }
77
78 fn update(&mut self, tick_duration: Duration) {
79 self.tick_count += 1;
80 self.total_duration += tick_duration;
81
82 if tick_duration < self.min_tick_duration {
83 self.min_tick_duration = tick_duration;
84 }
85 if tick_duration > self.max_tick_duration {
86 self.max_tick_duration = tick_duration;
87 }
88
89 self.avg_tick_duration = self.total_duration / self.tick_count as u32;
90 }
91
92 fn finalize(&mut self, status: NodeStatus) {
93 self.final_status = Some(status);
94 }
95}
96
97pub struct BehaviorExecutor {
115 behavior: BoxedBehavior,
116 tick_rate_hz: f32,
117 max_ticks: Option<usize>,
118}
119
120impl BehaviorExecutor {
121 pub fn new(behavior: BoxedBehavior, tick_rate_hz: f32) -> Self {
128 Self {
129 behavior,
130 tick_rate_hz,
131 max_ticks: None,
132 }
133 }
134
135 pub fn with_max_ticks(mut self, max_ticks: usize) -> Self {
137 self.max_ticks = Some(max_ticks);
138 self
139 }
140
141 pub async fn init(&mut self, ctx: &Context) -> anyhow::Result<()> {
143 info!("Initializing behavior: {}", self.behavior.name());
144 self.behavior.on_init(ctx).await?;
145 Ok(())
146 }
147
148 pub async fn run_until_complete(&mut self, ctx: &Context) -> anyhow::Result<(NodeStatus, ExecutionStats)> {
157 let mut stats = ExecutionStats::new();
158 let mut interval = tokio::time::interval(Duration::from_secs_f32(1.0 / self.tick_rate_hz));
159
160 info!(
161 "Starting behavior execution: {} at {} Hz",
162 self.behavior.name(),
163 self.tick_rate_hz
164 );
165
166 loop {
167 interval.tick().await;
169
170 if let Some(max_ticks) = self.max_ticks {
172 if stats.tick_count >= max_ticks {
173 warn!("Behavior exceeded maximum ticks ({})", max_ticks);
174 return Err(anyhow::anyhow!("Exceeded maximum ticks"));
175 }
176 }
177
178 let tick_start = Instant::now();
180 let status = self.behavior.tick(ctx).await?;
181 let tick_duration = tick_start.elapsed();
182
183 stats.update(tick_duration);
184
185 debug!(
186 "Tick #{}: status={}, duration={:?}",
187 stats.tick_count, status, tick_duration
188 );
189
190 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 pub async fn terminate(&mut self, ctx: &Context) -> anyhow::Result<()> {
255 info!("Terminating behavior: {}", self.behavior.name());
256 self.behavior.on_terminate(ctx).await?;
257 Ok(())
258 }
259
260 pub async fn reset(&mut self) -> anyhow::Result<()> {
262 info!("Resetting behavior: {}", self.behavior.name());
263 self.behavior.reset().await?;
264 Ok(())
265 }
266}