mecha10_behavior_runtime/
execution.rs

1//! Execution engine for behavior trees
2//!
3//! This module provides the executor that runs behavior trees with tick-based execution.
4
5use crate::{BoxedBehavior, NodeStatus};
6use mecha10_core::Context;
7use std::time::{Duration, Instant};
8use tracing::{debug, info, warn};
9
10/// Execution context for behavior trees.
11///
12/// This wraps the Mecha10 Context and provides additional execution state.
13#[derive(Clone)]
14pub struct ExecutionContext {
15    /// Underlying Mecha10 context
16    ctx: Context,
17
18    /// Tick rate for the executor
19    tick_rate_hz: f32,
20}
21
22impl ExecutionContext {
23    /// Create a new execution context.
24    pub fn new(ctx: Context, tick_rate_hz: f32) -> Self {
25        Self { ctx, tick_rate_hz }
26    }
27
28    /// Get the underlying context.
29    pub fn context(&self) -> &Context {
30        &self.ctx
31    }
32
33    /// Get the tick rate in Hz.
34    pub fn tick_rate_hz(&self) -> f32 {
35        self.tick_rate_hz
36    }
37
38    /// Get the tick period as a duration.
39    pub fn tick_period(&self) -> Duration {
40        Duration::from_secs_f32(1.0 / self.tick_rate_hz)
41    }
42}
43
44/// Statistics about behavior execution.
45#[derive(Debug, Clone)]
46pub struct ExecutionStats {
47    /// Total number of ticks executed
48    pub tick_count: usize,
49
50    /// Total execution time
51    pub total_duration: Duration,
52
53    /// Average tick duration
54    pub avg_tick_duration: Duration,
55
56    /// Minimum tick duration
57    pub min_tick_duration: Duration,
58
59    /// Maximum tick duration
60    pub max_tick_duration: Duration,
61
62    /// Final status
63    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
97/// Executor for behavior trees with tick-based execution.
98///
99/// This provides a simple interface for running behavior trees at a fixed rate.
100///
101/// # Example
102///
103/// ```rust,no_run
104/// use mecha10_behavior_runtime::prelude::*;
105///
106/// # async fn example(behavior: BoxedBehavior, ctx: Context) -> anyhow::Result<()> {
107/// let mut executor = BehaviorExecutor::new(behavior, 30.0);
108///
109/// let (status, stats) = executor.run_until_complete(&ctx).await?;
110/// println!("Behavior completed with status: {} in {} ticks", status, stats.tick_count);
111/// # Ok(())
112/// # }
113/// ```
114pub struct BehaviorExecutor {
115    behavior: BoxedBehavior,
116    tick_rate_hz: f32,
117    max_ticks: Option<usize>,
118}
119
120impl BehaviorExecutor {
121    /// Create a new behavior executor.
122    ///
123    /// # Arguments
124    ///
125    /// * `behavior` - The behavior to execute
126    /// * `tick_rate_hz` - Tick rate in Hz (e.g., 30.0 for 30 Hz)
127    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    /// Set the maximum number of ticks before timeout.
136    pub fn with_max_ticks(mut self, max_ticks: usize) -> Self {
137        self.max_ticks = Some(max_ticks);
138        self
139    }
140
141    /// Initialize the behavior.
142    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    /// Run the behavior until it completes (Success or Failure).
149    ///
150    /// This will tick the behavior at the configured rate until it returns
151    /// a terminal status (Success or Failure).
152    ///
153    /// # Returns
154    ///
155    /// The final status and execution statistics.
156    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            // Wait for next tick
168            interval.tick().await;
169
170            // Check max ticks limit
171            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            // Execute one tick
179            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            // Check for completion
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    /// Run the behavior for a fixed duration.
203    ///
204    /// This will tick the behavior at the configured rate for the specified duration,
205    /// regardless of whether it completes.
206    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    /// Terminate the behavior.
254    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    /// Reset the behavior to its initial state.
261    pub async fn reset(&mut self) -> anyhow::Result<()> {
262        info!("Resetting behavior: {}", self.behavior.name());
263        self.behavior.reset().await?;
264        Ok(())
265    }
266}