use crate::{BoxedBehavior, NodeStatus};
use mecha10_core::Context;
use std::time::{Duration, Instant};
use tracing::{debug, info, warn};
#[derive(Clone)]
pub struct ExecutionContext {
ctx: Context,
tick_rate_hz: f32,
}
impl ExecutionContext {
pub fn new(ctx: Context, tick_rate_hz: f32) -> Self {
Self { ctx, tick_rate_hz }
}
pub fn context(&self) -> &Context {
&self.ctx
}
pub fn tick_rate_hz(&self) -> f32 {
self.tick_rate_hz
}
pub fn tick_period(&self) -> Duration {
Duration::from_secs_f32(1.0 / self.tick_rate_hz)
}
}
#[derive(Debug, Clone)]
pub struct ExecutionStats {
pub tick_count: usize,
pub total_duration: Duration,
pub avg_tick_duration: Duration,
pub min_tick_duration: Duration,
pub max_tick_duration: Duration,
pub final_status: Option<NodeStatus>,
}
impl ExecutionStats {
fn new() -> Self {
Self {
tick_count: 0,
total_duration: Duration::ZERO,
avg_tick_duration: Duration::ZERO,
min_tick_duration: Duration::MAX,
max_tick_duration: Duration::ZERO,
final_status: None,
}
}
fn update(&mut self, tick_duration: Duration) {
self.tick_count += 1;
self.total_duration += tick_duration;
if tick_duration < self.min_tick_duration {
self.min_tick_duration = tick_duration;
}
if tick_duration > self.max_tick_duration {
self.max_tick_duration = tick_duration;
}
self.avg_tick_duration = self.total_duration / self.tick_count as u32;
}
fn finalize(&mut self, status: NodeStatus) {
self.final_status = Some(status);
}
}
pub struct BehaviorExecutor {
behavior: BoxedBehavior,
tick_rate_hz: f32,
max_ticks: Option<usize>,
}
impl BehaviorExecutor {
pub fn new(behavior: BoxedBehavior, tick_rate_hz: f32) -> Self {
Self {
behavior,
tick_rate_hz,
max_ticks: None,
}
}
pub fn with_max_ticks(mut self, max_ticks: usize) -> Self {
self.max_ticks = Some(max_ticks);
self
}
pub async fn init(&mut self, ctx: &Context) -> anyhow::Result<()> {
info!("Initializing behavior: {}", self.behavior.name());
self.behavior.on_init(ctx).await?;
Ok(())
}
pub async fn run_until_complete(&mut self, ctx: &Context) -> anyhow::Result<(NodeStatus, ExecutionStats)> {
let mut stats = ExecutionStats::new();
let mut interval = tokio::time::interval(Duration::from_secs_f32(1.0 / self.tick_rate_hz));
info!(
"Starting behavior execution: {} at {} Hz",
self.behavior.name(),
self.tick_rate_hz
);
loop {
interval.tick().await;
if let Some(max_ticks) = self.max_ticks {
if stats.tick_count >= max_ticks {
warn!("Behavior exceeded maximum ticks ({})", max_ticks);
return Err(anyhow::anyhow!("Exceeded maximum ticks"));
}
}
let tick_start = Instant::now();
let status = self.behavior.tick(ctx).await?;
let tick_duration = tick_start.elapsed();
stats.update(tick_duration);
debug!(
"Tick #{}: status={}, duration={:?}",
stats.tick_count, status, tick_duration
);
if status.is_complete() {
info!(
"Behavior completed with status: {} after {} ticks ({:?})",
status, stats.tick_count, stats.total_duration
);
stats.finalize(status);
return Ok((status, stats));
}
}
}
pub async fn run_for_duration(
&mut self,
ctx: &Context,
duration: Duration,
) -> anyhow::Result<(NodeStatus, ExecutionStats)> {
let mut stats = ExecutionStats::new();
let mut interval = tokio::time::interval(Duration::from_secs_f32(1.0 / self.tick_rate_hz));
let start_time = Instant::now();
info!(
"Starting behavior execution: {} for {:?} at {} Hz",
self.behavior.name(),
duration,
self.tick_rate_hz
);
let mut last_status = NodeStatus::Running;
while start_time.elapsed() < duration {
interval.tick().await;
let tick_start = Instant::now();
let status = self.behavior.tick(ctx).await?;
let tick_duration = tick_start.elapsed();
stats.update(tick_duration);
last_status = status;
debug!(
"Tick #{}: status={}, duration={:?}",
stats.tick_count, status, tick_duration
);
if status.is_complete() {
info!("Behavior completed early with status: {}", status);
break;
}
}
info!(
"Behavior execution ended after {} ticks ({:?})",
stats.tick_count, stats.total_duration
);
stats.finalize(last_status);
Ok((last_status, stats))
}
pub async fn terminate(&mut self, ctx: &Context) -> anyhow::Result<()> {
info!("Terminating behavior: {}", self.behavior.name());
self.behavior.on_terminate(ctx).await?;
Ok(())
}
pub async fn reset(&mut self) -> anyhow::Result<()> {
info!("Resetting behavior: {}", self.behavior.name());
self.behavior.reset().await?;
Ok(())
}
}