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
115impl BehaviorExecutor {
116 pub fn new(behavior: BoxedBehavior, tick_rate_hz: f32) -> Self {
123 Self {
124 behavior,
125 tick_rate_hz,
126 max_ticks: None,
127 }
128 }
129
130 pub fn with_max_ticks(mut self, max_ticks: usize) -> Self {
132 self.max_ticks = Some(max_ticks);
133 self
134 }
135
136 pub async fn init(&mut self, ctx: &Context) -> anyhow::Result<()> {
138 info!("Initializing behavior: {}", self.behavior.name());
139 self.behavior.on_init(ctx).await?;
140 Ok(())
141 }
142
143 pub async fn run_until_complete(&mut self, ctx: &Context) -> anyhow::Result<(NodeStatus, ExecutionStats)> {
152 let mut stats = ExecutionStats::new();
153 let mut interval = tokio::time::interval(Duration::from_secs_f32(1.0 / self.tick_rate_hz));
154
155 info!(
156 "Starting behavior execution: {} at {} Hz",
157 self.behavior.name(),
158 self.tick_rate_hz
159 );
160
161 loop {
162 interval.tick().await;
164
165 if let Some(max_ticks) = self.max_ticks {
167 if stats.tick_count >= max_ticks {
168 warn!("Behavior exceeded maximum ticks ({})", max_ticks);
169 return Err(anyhow::anyhow!("Exceeded maximum ticks"));
170 }
171 }
172
173 let tick_start = Instant::now();
175 let status = self.behavior.tick(ctx).await?;
176 let tick_duration = tick_start.elapsed();
177
178 stats.update(tick_duration);
179
180 debug!(
181 "Tick #{}: status={}, duration={:?}",
182 stats.tick_count, status, tick_duration
183 );
184
185 if status.is_complete() {
187 info!(
188 "Behavior completed with status: {} after {} ticks ({:?})",
189 status, stats.tick_count, stats.total_duration
190 );
191 stats.finalize(status);
192 return Ok((status, stats));
193 }
194 }
195 }
196
197 pub async fn run_for_duration(
202 &mut self,
203 ctx: &Context,
204 duration: Duration,
205 ) -> anyhow::Result<(NodeStatus, ExecutionStats)> {
206 let mut stats = ExecutionStats::new();
207 let mut interval = tokio::time::interval(Duration::from_secs_f32(1.0 / self.tick_rate_hz));
208 let start_time = Instant::now();
209
210 info!(
211 "Starting behavior execution: {} for {:?} at {} Hz",
212 self.behavior.name(),
213 duration,
214 self.tick_rate_hz
215 );
216
217 let mut last_status = NodeStatus::Running;
218
219 while start_time.elapsed() < duration {
220 interval.tick().await;
221
222 let tick_start = Instant::now();
223 let status = self.behavior.tick(ctx).await?;
224 let tick_duration = tick_start.elapsed();
225
226 stats.update(tick_duration);
227 last_status = status;
228
229 debug!(
230 "Tick #{}: status={}, duration={:?}",
231 stats.tick_count, status, tick_duration
232 );
233
234 if status.is_complete() {
235 info!("Behavior completed early with status: {}", status);
236 break;
237 }
238 }
239
240 info!(
241 "Behavior execution ended after {} ticks ({:?})",
242 stats.tick_count, stats.total_duration
243 );
244 stats.finalize(last_status);
245 Ok((last_status, stats))
246 }
247
248 pub async fn terminate(&mut self, ctx: &Context) -> anyhow::Result<()> {
250 info!("Terminating behavior: {}", self.behavior.name());
251 self.behavior.on_terminate(ctx).await?;
252 Ok(())
253 }
254
255 pub async fn reset(&mut self) -> anyhow::Result<()> {
257 info!("Resetting behavior: {}", self.behavior.name());
258 self.behavior.reset().await?;
259 Ok(())
260 }
261}