Skip to main content

mecha10_behavior_runtime/actions/
timing.rs

1//! Action nodes that gate on elapsed time, or do nothing.
2
3use crate::{BehaviorNode, Context, NodeStatus};
4use async_trait::async_trait;
5use serde::{Deserialize, Serialize};
6use serde_json::Value;
7use std::time::{Duration, Instant};
8
9/// TimerNode - Waits for a specified duration
10///
11/// Returns Running until the duration elapses, then returns Success.
12///
13/// # Configuration
14///
15/// ```json
16/// {
17///   "type": "timer",
18///   "config": {
19///     "duration_secs": 5.0
20///   }
21/// }
22/// ```
23#[derive(Debug)]
24pub struct TimerNode {
25    duration: Duration,
26    start_time: Option<Instant>,
27}
28
29#[derive(Debug, Serialize, Deserialize)]
30struct TimerConfig {
31    duration_secs: f64,
32}
33
34impl TimerNode {
35    /// Create a new TimerNode from JSON configuration
36    pub fn from_json(config: Value) -> anyhow::Result<Self> {
37        let cfg: TimerConfig = serde_json::from_value(config)?;
38        Ok(Self {
39            duration: Duration::from_secs_f64(cfg.duration_secs),
40            start_time: None,
41        })
42    }
43}
44
45#[async_trait]
46impl BehaviorNode for TimerNode {
47    async fn tick(&mut self, _ctx: &Context) -> anyhow::Result<NodeStatus> {
48        let now = Instant::now();
49
50        // Initialize start time on first tick
51        if self.start_time.is_none() {
52            self.start_time = Some(now);
53        }
54
55        // Check if duration has elapsed
56        if now.duration_since(self.start_time.unwrap()) >= self.duration {
57            Ok(NodeStatus::Success)
58        } else {
59            Ok(NodeStatus::Running)
60        }
61    }
62
63    async fn reset(&mut self) -> anyhow::Result<()> {
64        self.start_time = None;
65        Ok(())
66    }
67
68    fn name(&self) -> &str {
69        "TimerNode"
70    }
71}
72
73/// IdleNode - Returns Success immediately
74///
75/// This node does nothing and returns Success on every tick.
76/// Useful for testing and as a placeholder in behavior trees.
77///
78/// # Configuration
79///
80/// ```json
81/// {
82///   "type": "idle",
83///   "config": {}
84/// }
85/// ```
86#[derive(Debug)]
87pub struct IdleNode;
88
89impl IdleNode {
90    /// Create a new IdleNode from JSON configuration
91    pub fn from_json(_config: Value) -> anyhow::Result<Self> {
92        Ok(Self)
93    }
94}
95
96#[async_trait]
97impl BehaviorNode for IdleNode {
98    async fn tick(&mut self, _ctx: &Context) -> anyhow::Result<NodeStatus> {
99        Ok(NodeStatus::Success)
100    }
101
102    fn name(&self) -> &str {
103        "IdleNode"
104    }
105}
106
107/// PauseNode - Pauses execution for a specified duration
108///
109/// Similar to TimerNode but semantically represents a deliberate pause in behavior.
110/// Returns Running until the duration elapses, then returns Success.
111///
112/// # Configuration
113///
114/// ```json
115/// {
116///   "type": "pause",
117///   "config": {
118///     "duration_secs": 2.0
119///   }
120/// }
121/// ```
122#[derive(Debug)]
123pub struct PauseNode {
124    duration: Duration,
125    start_time: Option<Instant>,
126}
127
128#[derive(Debug, Serialize, Deserialize)]
129struct PauseConfig {
130    duration_secs: f64,
131}
132
133impl PauseNode {
134    /// Create a new PauseNode from JSON configuration
135    pub fn from_json(config: Value) -> anyhow::Result<Self> {
136        let cfg: PauseConfig = serde_json::from_value(config)?;
137        Ok(Self {
138            duration: Duration::from_secs_f64(cfg.duration_secs),
139            start_time: None,
140        })
141    }
142}
143
144#[async_trait]
145impl BehaviorNode for PauseNode {
146    async fn tick(&mut self, _ctx: &Context) -> anyhow::Result<NodeStatus> {
147        let now = Instant::now();
148
149        // Initialize start time on first tick
150        if self.start_time.is_none() {
151            self.start_time = Some(now);
152            tracing::debug!("PauseNode: starting pause for {:?}", self.duration);
153        }
154
155        // Check if duration has elapsed
156        if now.duration_since(self.start_time.unwrap()) >= self.duration {
157            tracing::debug!("PauseNode: pause complete");
158            Ok(NodeStatus::Success)
159        } else {
160            Ok(NodeStatus::Running)
161        }
162    }
163
164    async fn reset(&mut self) -> anyhow::Result<()> {
165        self.start_time = None;
166        Ok(())
167    }
168
169    fn name(&self) -> &str {
170        "PauseNode"
171    }
172}