Skip to main content

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
39/// Statistics about behavior execution.
40#[derive(Debug, Clone)]
41pub struct ExecutionStats {
42    /// Total number of ticks executed
43    pub tick_count: usize,
44
45    /// Total execution time
46    pub total_duration: Duration,
47
48    /// Average tick duration
49    pub avg_tick_duration: Duration,
50
51    /// Minimum tick duration
52    pub min_tick_duration: Duration,
53
54    /// Maximum tick duration
55    pub max_tick_duration: Duration,
56
57    /// Final status
58    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
92/// Executor for behavior trees with tick-based execution.
93///
94/// This provides a simple interface for running behavior trees at a fixed rate.
95///
96/// # Example
97///
98/// ```rust,no_run
99/// use mecha10_behavior_runtime::prelude::*;
100///
101/// # async fn example(behavior: BoxedBehavior, ctx: Context) -> anyhow::Result<()> {
102/// let mut executor = BehaviorExecutor::new(behavior, 30.0);
103///
104/// let (status, stats) = executor.run_until_complete(&ctx).await?;
105/// println!("Behavior completed with status: {} in {} ticks", status, stats.tick_count);
106/// # Ok(())
107/// # }
108/// ```
109pub struct BehaviorExecutor {
110    behavior: BoxedBehavior,
111    tick_rate_hz: f32,
112    max_ticks: Option<usize>,
113}
114
115impl BehaviorExecutor {
116    /// Create a new behavior executor.
117    ///
118    /// # Arguments
119    ///
120    /// * `behavior` - The behavior to execute
121    /// * `tick_rate_hz` - Tick rate in Hz (e.g., 30.0 for 30 Hz)
122    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    /// Set the maximum number of ticks before timeout.
131    pub fn with_max_ticks(mut self, max_ticks: usize) -> Self {
132        self.max_ticks = Some(max_ticks);
133        self
134    }
135
136    /// Initialize the behavior.
137    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    /// Run the behavior until it completes (Success or Failure).
144    ///
145    /// This will tick the behavior at the configured rate until it returns
146    /// a terminal status (Success or Failure).
147    ///
148    /// # Returns
149    ///
150    /// The final status and execution statistics.
151    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            // Wait for next tick
163            interval.tick().await;
164
165            // Check max ticks limit
166            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            // Execute one tick
174            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            // Check for completion
186            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    /// Run the behavior for a fixed duration.
198    ///
199    /// This will tick the behavior at the configured rate for the specified duration,
200    /// regardless of whether it completes.
201    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    /// Terminate the behavior.
249    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    /// Reset the behavior to its initial state.
256    pub async fn reset(&mut self) -> anyhow::Result<()> {
257        info!("Resetting behavior: {}", self.behavior.name());
258        self.behavior.reset().await?;
259        Ok(())
260    }
261}