Skip to main content

mecha10_behavior_runtime/actions/
timing.rs

1//! Action nodes that gate on elapsed time, or do nothing.
2
3use super::elapsed_timer::{reset_timer_field, ElapsedTimer};
4use crate::{BehaviorNode, Context, NodeStatus};
5use async_trait::async_trait;
6use serde::{Deserialize, Serialize};
7use serde_json::Value;
8use std::time::Duration;
9
10/// Generate `from_json` for a node whose only state is a `duration_secs`-configured
11/// [`ElapsedTimer`]. `TimerNode`/`PauseNode` had identical `from_json` bodies here (parse a
12/// `{ duration_secs }` config struct, convert to a `Duration`, start with a fresh timer) -
13/// cargo-dupes flagged them as an exact duplicate.
14macro_rules! from_json_with_duration {
15    ($config:ident) => {
16        /// Create a new node from JSON configuration
17        pub fn from_json(config: Value) -> anyhow::Result<Self> {
18            let cfg: $config = serde_json::from_value(config)?;
19            Ok(Self {
20                duration: Duration::from_secs_f64(cfg.duration_secs),
21                timer: ElapsedTimer::default(),
22            })
23        }
24    };
25}
26
27/// TimerNode - Waits for a specified duration
28///
29/// Returns Running until the duration elapses, then returns Success.
30///
31/// # Configuration
32///
33/// ```json
34/// {
35///   "type": "timer",
36///   "config": {
37///     "duration_secs": 5.0
38///   }
39/// }
40/// ```
41#[derive(Debug, Default)]
42pub struct TimerNode {
43    duration: Duration,
44    timer: ElapsedTimer,
45}
46
47#[derive(Debug, Serialize, Deserialize)]
48struct TimerConfig {
49    duration_secs: f64,
50}
51
52impl TimerNode {
53    from_json_with_duration!(TimerConfig);
54}
55
56#[async_trait]
57impl BehaviorNode for TimerNode {
58    async fn tick(&mut self, _ctx: &Context) -> anyhow::Result<NodeStatus> {
59        if self.timer.elapsed_since_start(self.duration) {
60            Ok(NodeStatus::Success)
61        } else {
62            Ok(NodeStatus::Running)
63        }
64    }
65
66    reset_timer_field!(timer);
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, Default)]
123pub struct PauseNode {
124    duration: Duration,
125    timer: ElapsedTimer,
126}
127
128#[derive(Debug, Serialize, Deserialize)]
129struct PauseConfig {
130    duration_secs: f64,
131}
132
133impl PauseNode {
134    from_json_with_duration!(PauseConfig);
135}
136
137#[async_trait]
138impl BehaviorNode for PauseNode {
139    async fn tick(&mut self, _ctx: &Context) -> anyhow::Result<NodeStatus> {
140        if !self.timer.is_running() {
141            tracing::debug!("PauseNode: starting pause for {:?}", self.duration);
142        }
143
144        if self.timer.elapsed_since_start(self.duration) {
145            tracing::debug!("PauseNode: pause complete");
146            Ok(NodeStatus::Success)
147        } else {
148            Ok(NodeStatus::Running)
149        }
150    }
151
152    reset_timer_field!(timer);
153
154    fn name(&self) -> &str {
155        "PauseNode"
156    }
157}