1use std::collections::HashSet;
2use std::sync::Arc;
3use std::sync::atomic::{AtomicU64, Ordering};
4
5use crate::action::{BoxedAction, passthrough};
6use crate::arc::{Inhibitor, Read, Reset};
7use crate::input::In;
8use crate::output::{Out, all_places, find_forward_inputs, find_timeout};
9use crate::place::PlaceRef;
10use crate::timing::{Timing, immediate};
11
12#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
14pub struct TransitionId(u64);
15
16static NEXT_ID: AtomicU64 = AtomicU64::new(0);
17
18impl TransitionId {
19 fn next() -> Self {
20 Self(NEXT_ID.fetch_add(1, Ordering::Relaxed))
21 }
22}
23
24#[derive(Clone)]
29pub struct Transition {
30 id: TransitionId,
31 name: Arc<str>,
32 input_specs: Vec<In>,
33 output_spec: Option<Out>,
34 inhibitors: Vec<Inhibitor>,
35 reads: Vec<Read>,
36 resets: Vec<Reset>,
37 timing: Timing,
38 action_timeout: Option<u64>,
39 action: BoxedAction,
40 priority: i32,
41 input_places: HashSet<PlaceRef>,
42 read_places: HashSet<PlaceRef>,
43 output_places: HashSet<PlaceRef>,
44}
45
46impl Transition {
47 pub fn id(&self) -> TransitionId {
49 self.id
50 }
51
52 pub fn name(&self) -> &str {
54 &self.name
55 }
56
57 pub fn name_arc(&self) -> &Arc<str> {
59 &self.name
60 }
61
62 pub fn input_specs(&self) -> &[In] {
64 &self.input_specs
65 }
66
67 pub fn output_spec(&self) -> Option<&Out> {
69 self.output_spec.as_ref()
70 }
71
72 pub fn inhibitors(&self) -> &[Inhibitor] {
74 &self.inhibitors
75 }
76
77 pub fn reads(&self) -> &[Read] {
79 &self.reads
80 }
81
82 pub fn resets(&self) -> &[Reset] {
84 &self.resets
85 }
86
87 pub fn timing(&self) -> &Timing {
89 &self.timing
90 }
91
92 pub fn action_timeout(&self) -> Option<u64> {
94 self.action_timeout
95 }
96
97 pub fn has_action_timeout(&self) -> bool {
99 self.action_timeout.is_some()
100 }
101
102 pub fn action(&self) -> &BoxedAction {
104 &self.action
105 }
106
107 pub fn priority(&self) -> i32 {
109 self.priority
110 }
111
112 pub fn input_places(&self) -> &HashSet<PlaceRef> {
114 &self.input_places
115 }
116
117 pub fn read_places(&self) -> &HashSet<PlaceRef> {
119 &self.read_places
120 }
121
122 pub fn output_places(&self) -> &HashSet<PlaceRef> {
124 &self.output_places
125 }
126
127 pub fn builder(name: impl Into<Arc<str>>) -> TransitionBuilder {
129 TransitionBuilder::new(name)
130 }
131}
132
133impl std::fmt::Debug for Transition {
134 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
135 f.debug_struct("Transition")
136 .field("id", &self.id)
137 .field("name", &self.name)
138 .field("timing", &self.timing)
139 .field("priority", &self.priority)
140 .finish()
141 }
142}
143
144impl PartialEq for Transition {
145 fn eq(&self, other: &Self) -> bool {
146 self.id == other.id
147 }
148}
149
150impl Eq for Transition {}
151
152impl std::hash::Hash for Transition {
153 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
154 self.id.hash(state);
155 }
156}
157
158impl std::fmt::Display for Transition {
159 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
160 write!(f, "Transition[{}]", self.name)
161 }
162}
163
164pub struct TransitionBuilder {
166 name: Arc<str>,
167 input_specs: Vec<In>,
168 output_spec: Option<Out>,
169 inhibitors: Vec<Inhibitor>,
170 reads: Vec<Read>,
171 resets: Vec<Reset>,
172 timing: Timing,
173 action: BoxedAction,
174 priority: i32,
175}
176
177impl TransitionBuilder {
178 pub fn new(name: impl Into<Arc<str>>) -> Self {
179 Self {
180 name: name.into(),
181 input_specs: Vec::new(),
182 output_spec: None,
183 inhibitors: Vec::new(),
184 reads: Vec::new(),
185 resets: Vec::new(),
186 timing: immediate(),
187 action: passthrough(),
188 priority: 0,
189 }
190 }
191
192 pub fn input(mut self, spec: In) -> Self {
194 self.input_specs.push(spec);
195 self
196 }
197
198 pub fn inputs(mut self, specs: Vec<In>) -> Self {
200 self.input_specs.extend(specs);
201 self
202 }
203
204 pub fn output(mut self, spec: Out) -> Self {
206 self.output_spec = Some(spec);
207 self
208 }
209
210 pub fn inhibitor(mut self, inh: Inhibitor) -> Self {
212 self.inhibitors.push(inh);
213 self
214 }
215
216 pub fn inhibitors(mut self, inhs: Vec<Inhibitor>) -> Self {
218 self.inhibitors.extend(inhs);
219 self
220 }
221
222 pub fn read(mut self, r: Read) -> Self {
224 self.reads.push(r);
225 self
226 }
227
228 pub fn reads(mut self, rs: Vec<Read>) -> Self {
230 self.reads.extend(rs);
231 self
232 }
233
234 pub fn reset(mut self, r: Reset) -> Self {
236 self.resets.push(r);
237 self
238 }
239
240 pub fn resets(mut self, rs: Vec<Reset>) -> Self {
242 self.resets.extend(rs);
243 self
244 }
245
246 pub fn timing(mut self, timing: Timing) -> Self {
248 self.timing = timing;
249 self
250 }
251
252 pub fn action(mut self, action: BoxedAction) -> Self {
254 self.action = action;
255 self
256 }
257
258 pub fn priority(mut self, priority: i32) -> Self {
260 self.priority = priority;
261 self
262 }
263
264 pub fn build(self) -> Transition {
269 if let Some(ref out) = self.output_spec {
271 let input_place_names: HashSet<_> =
272 self.input_specs.iter().map(|s| s.place_name()).collect();
273 for (from, _) in find_forward_inputs(out) {
274 assert!(
275 input_place_names.contains(from.name()),
276 "Transition '{}': ForwardInput references non-input place '{}'",
277 self.name,
278 from.name()
279 );
280 }
281 }
282
283 let action_timeout = self
284 .output_spec
285 .as_ref()
286 .and_then(|o| find_timeout(o).map(|(ms, _)| ms));
287
288 let input_places: HashSet<PlaceRef> =
290 self.input_specs.iter().map(|s| s.place().clone()).collect();
291
292 let read_places: HashSet<PlaceRef> = self.reads.iter().map(|r| r.place.clone()).collect();
293
294 let output_places: HashSet<PlaceRef> = self
295 .output_spec
296 .as_ref()
297 .map(all_places)
298 .unwrap_or_default();
299
300 Transition {
301 id: TransitionId::next(),
302 name: self.name,
303 input_specs: self.input_specs,
304 output_spec: self.output_spec,
305 inhibitors: self.inhibitors,
306 reads: self.reads,
307 resets: self.resets,
308 timing: self.timing,
309 action_timeout,
310 action: self.action,
311 priority: self.priority,
312 input_places,
313 read_places,
314 output_places,
315 }
316 }
317}
318
319pub(crate) fn rebuild_with_action(t: &Transition, action: BoxedAction) -> Transition {
321 let mut builder = Transition::builder(Arc::clone(&t.name))
322 .timing(t.timing)
323 .priority(t.priority)
324 .action(action)
325 .inputs(t.input_specs.clone())
326 .inhibitors(t.inhibitors.clone())
327 .reads(t.reads.clone())
328 .resets(t.resets.clone());
329
330 if let Some(ref out) = t.output_spec {
331 builder = builder.output(out.clone());
332 }
333
334 builder.build()
335}
336
337#[cfg(test)]
338mod tests {
339 use super::*;
340 use crate::input::one;
341 use crate::output::out_place;
342 use crate::place::Place;
343
344 #[test]
345 fn transition_builder_basic() {
346 let p_in = Place::<i32>::new("in");
347 let p_out = Place::<i32>::new("out");
348
349 let t = Transition::builder("test")
350 .input(one(&p_in))
351 .output(out_place(&p_out))
352 .build();
353
354 assert_eq!(t.name(), "test");
355 assert_eq!(t.input_specs().len(), 1);
356 assert!(t.output_spec().is_some());
357 assert_eq!(t.timing(), &Timing::Immediate);
358 assert_eq!(t.priority(), 0);
359 }
360
361 #[test]
362 fn transition_identity() {
363 let t1 = Transition::builder("test").build();
364 let t2 = Transition::builder("test").build();
365 assert_ne!(t1, t2); }
367
368 #[test]
369 fn transition_places_computed() {
370 let p_in = Place::<i32>::new("in");
371 let p_out = Place::<i32>::new("out");
372 let p_read = Place::<String>::new("ctx");
373
374 let t = Transition::builder("test")
375 .input(one(&p_in))
376 .output(out_place(&p_out))
377 .read(crate::arc::read(&p_read))
378 .build();
379
380 assert!(t.input_places().contains(&PlaceRef::new("in")));
381 assert!(t.output_places().contains(&PlaceRef::new("out")));
382 assert!(t.read_places().contains(&PlaceRef::new("ctx")));
383 }
384
385 #[test]
386 #[should_panic(expected = "ForwardInput references non-input place")]
387 fn forward_input_validation() {
388 let from = Place::<i32>::new("not-an-input");
389 let to = Place::<i32>::new("to");
390
391 Transition::builder("test")
392 .output(crate::output::forward_input(&from, &to))
393 .build();
394 }
395
396 #[test]
397 fn action_timeout_detected() {
398 let p = Place::<i32>::new("timeout");
399 let t = Transition::builder("test")
400 .output(crate::output::timeout_place(5000, &p))
401 .build();
402 assert_eq!(t.action_timeout(), Some(5000));
403 }
404
405 #[test]
406 fn no_action_timeout() {
407 let p = Place::<i32>::new("out");
408 let t = Transition::builder("test").output(out_place(&p)).build();
409 assert_eq!(t.action_timeout(), None);
410 }
411}