swarm_engine_core/events/
action.rs1use std::time::Duration;
7
8use crate::types::{LoraConfig, WorkerId};
9
10#[derive(Debug, Clone)]
15pub struct ActionEvent {
16 pub id: ActionEventId,
18 pub tick: u64,
20 pub worker_id: WorkerId,
22 pub action: String,
24 pub target: Option<String>,
26 pub result: ActionEventResult,
28 pub duration: Duration,
30 pub context: ActionContext,
32}
33
34#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
36pub struct ActionEventId(pub u64);
37
38impl ActionEventId {
39 pub fn new(tick: u64, worker_id: WorkerId, sequence: u32) -> Self {
41 let id = (tick << 24) | ((worker_id.0 as u64) << 8) | (sequence as u64 & 0xFF);
43 Self(id)
44 }
45}
46
47#[derive(Debug, Clone)]
49pub struct ActionEventResult {
50 pub success: bool,
52 pub output: Option<String>,
54 pub error: Option<String>,
56 pub discoveries: u32,
58}
59
60impl ActionEventResult {
61 pub fn success() -> Self {
62 Self {
63 success: true,
64 output: None,
65 error: None,
66 discoveries: 0,
67 }
68 }
69
70 pub fn success_with_output(output: impl Into<String>) -> Self {
71 Self {
72 success: true,
73 output: Some(output.into()),
74 error: None,
75 discoveries: 0,
76 }
77 }
78
79 pub fn failure(error: impl Into<String>) -> Self {
80 Self {
81 success: false,
82 output: None,
83 error: Some(error.into()),
84 discoveries: 0,
85 }
86 }
87
88 pub fn with_discoveries(mut self, count: u32) -> Self {
89 self.discoveries = count;
90 self
91 }
92}
93
94#[derive(Debug, Clone, Default)]
99pub struct ActionContext {
100 pub selection_logic: Option<String>,
102 pub exploration_node_id: Option<u64>,
104 pub from_guidance: bool,
106 pub previous_action: Option<String>,
108 pub lora: Option<LoraConfig>,
110 pub metadata: std::collections::HashMap<String, String>,
112}
113
114impl ActionContext {
115 pub fn new() -> Self {
116 Self::default()
117 }
118
119 pub fn with_selection_logic(mut self, logic: impl Into<String>) -> Self {
120 self.selection_logic = Some(logic.into());
121 self
122 }
123
124 pub fn with_exploration_node(mut self, node_id: u64) -> Self {
125 self.exploration_node_id = Some(node_id);
126 self
127 }
128
129 pub fn with_guidance(mut self) -> Self {
130 self.from_guidance = true;
131 self
132 }
133
134 pub fn with_previous_action(mut self, action: impl Into<String>) -> Self {
135 self.previous_action = Some(action.into());
136 self
137 }
138
139 pub fn with_lora(mut self, lora: LoraConfig) -> Self {
140 self.lora = Some(lora);
141 self
142 }
143
144 pub fn with_metadata(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
145 self.metadata.insert(key.into(), value.into());
146 self
147 }
148}
149
150pub struct ActionEventBuilder {
152 tick: u64,
153 worker_id: WorkerId,
154 sequence: u32,
155 action: String,
156 target: Option<String>,
157 result: ActionEventResult,
158 duration: Duration,
159 context: ActionContext,
160}
161
162impl ActionEventBuilder {
163 pub fn new(tick: u64, worker_id: WorkerId, action: impl Into<String>) -> Self {
164 Self {
165 tick,
166 worker_id,
167 sequence: 0,
168 action: action.into(),
169 target: None,
170 result: ActionEventResult::success(),
171 duration: Duration::ZERO,
172 context: ActionContext::default(),
173 }
174 }
175
176 pub fn sequence(mut self, seq: u32) -> Self {
177 self.sequence = seq;
178 self
179 }
180
181 pub fn target(mut self, target: impl Into<String>) -> Self {
182 self.target = Some(target.into());
183 self
184 }
185
186 pub fn result(mut self, result: ActionEventResult) -> Self {
187 self.result = result;
188 self
189 }
190
191 pub fn duration(mut self, duration: Duration) -> Self {
192 self.duration = duration;
193 self
194 }
195
196 pub fn context(mut self, context: ActionContext) -> Self {
197 self.context = context;
198 self
199 }
200
201 pub fn build(self) -> ActionEvent {
202 ActionEvent {
203 id: ActionEventId::new(self.tick, self.worker_id, self.sequence),
204 tick: self.tick,
205 worker_id: self.worker_id,
206 action: self.action,
207 target: self.target,
208 result: self.result,
209 duration: self.duration,
210 context: self.context,
211 }
212 }
213}
214
215#[cfg(test)]
216mod tests {
217 use super::*;
218
219 #[test]
220 fn test_action_event_id_uniqueness() {
221 let id1 = ActionEventId::new(100, WorkerId(0), 0);
222 let id2 = ActionEventId::new(100, WorkerId(0), 1);
223 let id3 = ActionEventId::new(100, WorkerId(1), 0);
224 let id4 = ActionEventId::new(101, WorkerId(0), 0);
225
226 assert_ne!(id1, id2);
227 assert_ne!(id1, id3);
228 assert_ne!(id1, id4);
229 }
230
231 #[test]
232 fn test_action_event_builder() {
233 let event = ActionEventBuilder::new(10, WorkerId(1), "CheckStatus")
234 .target("user-service")
235 .result(ActionEventResult::success_with_output("Service is running"))
236 .duration(Duration::from_millis(50))
237 .context(
238 ActionContext::new()
239 .with_selection_logic("UCB1")
240 .with_guidance(),
241 )
242 .build();
243
244 assert_eq!(event.tick, 10);
245 assert_eq!(event.worker_id, WorkerId(1));
246 assert_eq!(event.action, "CheckStatus");
247 assert_eq!(event.target, Some("user-service".to_string()));
248 assert!(event.result.success);
249 assert_eq!(event.duration, Duration::from_millis(50));
250 assert_eq!(event.context.selection_logic, Some("UCB1".to_string()));
251 assert!(event.context.from_guidance);
252 }
253}