mecha10_behavior_runtime/actions/
timing.rs1use super::elapsed_timer::{reset_timer_field, ElapsedTimer};
4use crate::{BehaviorNode, Context, NodeStatus, TreeStatusSnapshot};
5use async_trait::async_trait;
6use serde::{Deserialize, Serialize};
7use serde_json::Value;
8use std::time::Duration;
9
10macro_rules! from_json_with_duration {
11 ($config:ident) => {
12 pub fn from_json(config: Value) -> anyhow::Result<Self> {
14 let cfg: $config = serde_json::from_value(config)?;
15 Ok(Self {
16 duration: Duration::from_secs_f64(cfg.duration_secs),
17 timer: ElapsedTimer::default(),
18 last_status: None,
19 })
20 }
21 };
22}
23
24#[derive(Debug, Default)]
25pub struct TimerNode {
26 duration: Duration,
27 timer: ElapsedTimer,
28 last_status: Option<NodeStatus>,
29}
30
31#[derive(Debug, Serialize, Deserialize)]
32struct TimerConfig {
33 duration_secs: f64,
34}
35
36impl TimerNode {
37 from_json_with_duration!(TimerConfig);
38}
39
40#[async_trait]
41impl BehaviorNode for TimerNode {
42 async fn tick(&mut self, _ctx: &Context) -> anyhow::Result<NodeStatus> {
43 let status = if self.timer.elapsed_since_start(self.duration) {
44 NodeStatus::Success
45 } else {
46 NodeStatus::Running
47 };
48 self.last_status = Some(status);
49 Ok(status)
50 }
51
52 reset_timer_field!(timer);
53
54 fn name(&self) -> &str {
55 "TimerNode"
56 }
57
58 fn status_snapshot(&self) -> TreeStatusSnapshot {
59 TreeStatusSnapshot {
60 name: self.name().to_string(),
61 status: self.last_status,
62 active_capability: self.active_capability().map(String::from),
63 children: Vec::new(),
64 }
65 }
66}
67
68#[derive(Debug, Default)]
69pub struct IdleNode {
70 last_status: Option<NodeStatus>,
71}
72
73impl IdleNode {
74 pub fn from_json(_config: Value) -> anyhow::Result<Self> {
76 Ok(Self::default())
77 }
78}
79
80#[async_trait]
81impl BehaviorNode for IdleNode {
82 async fn tick(&mut self, _ctx: &Context) -> anyhow::Result<NodeStatus> {
83 self.last_status = Some(NodeStatus::Success);
84 Ok(NodeStatus::Success)
85 }
86
87 fn name(&self) -> &str {
88 "IdleNode"
89 }
90
91 fn status_snapshot(&self) -> TreeStatusSnapshot {
92 TreeStatusSnapshot {
93 name: self.name().to_string(),
94 status: self.last_status,
95 active_capability: self.active_capability().map(String::from),
96 children: Vec::new(),
97 }
98 }
99}
100
101#[derive(Debug, Default)]
102pub struct PauseNode {
103 duration: Duration,
104 timer: ElapsedTimer,
105 last_status: Option<NodeStatus>,
106}
107
108#[derive(Debug, Serialize, Deserialize)]
109struct PauseConfig {
110 duration_secs: f64,
111}
112
113impl PauseNode {
114 from_json_with_duration!(PauseConfig);
115}
116
117#[async_trait]
118impl BehaviorNode for PauseNode {
119 async fn tick(&mut self, _ctx: &Context) -> anyhow::Result<NodeStatus> {
120 if !self.timer.is_running() {
121 tracing::debug!("PauseNode: starting pause for {:?}", self.duration);
122 }
123
124 let status = if self.timer.elapsed_since_start(self.duration) {
125 tracing::debug!("PauseNode: pause complete");
126 NodeStatus::Success
127 } else {
128 NodeStatus::Running
129 };
130 self.last_status = Some(status);
131 Ok(status)
132 }
133
134 reset_timer_field!(timer);
135
136 fn name(&self) -> &str {
137 "PauseNode"
138 }
139
140 fn status_snapshot(&self) -> TreeStatusSnapshot {
141 TreeStatusSnapshot {
142 name: self.name().to_string(),
143 status: self.last_status,
144 active_capability: self.active_capability().map(String::from),
145 children: Vec::new(),
146 }
147 }
148}
149
150#[cfg(test)]
151mod tests {
152 use super::*;
153
154 #[test]
158 fn timer_node_status_snapshot_is_none_before_any_tick() {
159 let node = TimerNode::from_json(serde_json::json!({ "duration_secs": 60.0 })).unwrap();
160 let snapshot = node.status_snapshot();
161 assert_eq!(snapshot.status, None);
162 assert!(snapshot.children.is_empty());
163 }
164
165 #[tokio::test]
168 #[ignore = "Requires Redis infrastructure"]
169 async fn timer_node_status_snapshot_matches_the_last_tick_result() {
170 let ctx = crate::test_helpers::create_test_context("timer_node_status_snapshot").await;
171 let mut node = TimerNode::from_json(serde_json::json!({ "duration_secs": 0.0 })).unwrap();
172
173 let status = node.tick(&ctx).await.unwrap();
174 assert_eq!(status, NodeStatus::Success);
175
176 let snapshot = node.status_snapshot();
177 assert_eq!(snapshot.status, Some(status));
178 assert_eq!(snapshot.name, "TimerNode");
179 assert!(snapshot.children.is_empty());
180 }
181}