Skip to main content

libpetri_event/
net_event.rs

1use std::collections::HashMap;
2use std::sync::Arc;
3
4use crate::token_payload::TokenPayload;
5
6/// All observable events during Petri net execution.
7///
8/// 13 event types as a discriminated union matching the TypeScript/Java implementations.
9#[derive(Debug, Clone)]
10pub enum NetEvent {
11    /// Net execution started.
12    ExecutionStarted { net_name: Arc<str>, timestamp: u64 },
13    /// Net execution completed (no more enabled transitions, no in-flight actions).
14    ExecutionCompleted { net_name: Arc<str>, timestamp: u64 },
15    /// Transition became enabled (all input/inhibitor/read conditions satisfied).
16    TransitionEnabled {
17        transition_name: Arc<str>,
18        timestamp: u64,
19    },
20    /// Transition's enabling clock restarted (input place tokens changed while enabled).
21    TransitionClockRestarted {
22        transition_name: Arc<str>,
23        timestamp: u64,
24    },
25    /// Transition started firing (tokens consumed, action dispatched).
26    TransitionStarted {
27        transition_name: Arc<str>,
28        timestamp: u64,
29    },
30    /// Transition completed successfully (outputs produced).
31    TransitionCompleted {
32        transition_name: Arc<str>,
33        timestamp: u64,
34    },
35    /// Transition action failed with error.
36    TransitionFailed {
37        transition_name: Arc<str>,
38        error: String,
39        timestamp: u64,
40    },
41    /// Transition exceeded its timing deadline and was force-disabled.
42    TransitionTimedOut {
43        transition_name: Arc<str>,
44        timestamp: u64,
45    },
46    /// Transition action exceeded its action timeout.
47    ActionTimedOut {
48        transition_name: Arc<str>,
49        timeout_ms: u64,
50        timestamp: u64,
51    },
52    /// Token added to a place.
53    ///
54    /// The optional `token` payload is populated only by event stores that opt in
55    /// via [`EventStore::CAPTURES_TOKENS`](crate::event_store::EventStore::CAPTURES_TOKENS).
56    /// Production stores (`NoopEventStore`, `InMemoryEventStore`) leave it `None`
57    /// so the capture path is monomorphized out — preserving the zero-cost event
58    /// recording invariant.
59    ///
60    /// Marked `#[non_exhaustive]` so future fields (e.g. token identity,
61    /// source-transition attribution) can be added without a breaking change;
62    /// downstream matches must use `{ .., .. }`.
63    #[non_exhaustive]
64    TokenAdded {
65        place_name: Arc<str>,
66        timestamp: u64,
67        token: Option<Arc<dyn TokenPayload>>,
68    },
69    /// Token removed from a place.
70    ///
71    /// See [`TokenAdded`](Self::TokenAdded) for payload semantics and for the
72    /// `#[non_exhaustive]` contract.
73    #[non_exhaustive]
74    TokenRemoved {
75        place_name: Arc<str>,
76        timestamp: u64,
77        token: Option<Arc<dyn TokenPayload>>,
78    },
79    /// Log message emitted by a transition action.
80    LogMessage {
81        transition_name: Arc<str>,
82        level: String,
83        message: String,
84        timestamp: u64,
85    },
86    /// Snapshot of the current marking (token counts per place).
87    MarkingSnapshot {
88        marking: HashMap<Arc<str>, usize>,
89        timestamp: u64,
90    },
91}
92
93impl NetEvent {
94    /// Constructs a [`TokenAdded`](Self::TokenAdded) event without a payload —
95    /// the default for production event stores.
96    pub fn token_added(place_name: Arc<str>, timestamp: u64) -> Self {
97        Self::TokenAdded { place_name, timestamp, token: None }
98    }
99
100    /// Constructs a [`TokenAdded`](Self::TokenAdded) event carrying a token
101    /// payload — used by debug-aware event stores that set
102    /// [`EventStore::CAPTURES_TOKENS = true`](crate::event_store::EventStore::CAPTURES_TOKENS).
103    pub fn token_added_with(
104        place_name: Arc<str>,
105        timestamp: u64,
106        token: Arc<dyn TokenPayload>,
107    ) -> Self {
108        Self::TokenAdded { place_name, timestamp, token: Some(token) }
109    }
110
111    /// Constructs a [`TokenRemoved`](Self::TokenRemoved) event without a payload.
112    pub fn token_removed(place_name: Arc<str>, timestamp: u64) -> Self {
113        Self::TokenRemoved { place_name, timestamp, token: None }
114    }
115
116    /// Constructs a [`TokenRemoved`](Self::TokenRemoved) event carrying a token
117    /// payload. See [`token_added_with`](Self::token_added_with).
118    pub fn token_removed_with(
119        place_name: Arc<str>,
120        timestamp: u64,
121        token: Arc<dyn TokenPayload>,
122    ) -> Self {
123        Self::TokenRemoved { place_name, timestamp, token: Some(token) }
124    }
125
126    /// Returns the timestamp of this event.
127    pub fn timestamp(&self) -> u64 {
128        match self {
129            NetEvent::ExecutionStarted { timestamp, .. }
130            | NetEvent::ExecutionCompleted { timestamp, .. }
131            | NetEvent::TransitionEnabled { timestamp, .. }
132            | NetEvent::TransitionClockRestarted { timestamp, .. }
133            | NetEvent::TransitionStarted { timestamp, .. }
134            | NetEvent::TransitionCompleted { timestamp, .. }
135            | NetEvent::TransitionFailed { timestamp, .. }
136            | NetEvent::TransitionTimedOut { timestamp, .. }
137            | NetEvent::ActionTimedOut { timestamp, .. }
138            | NetEvent::TokenAdded { timestamp, .. }
139            | NetEvent::TokenRemoved { timestamp, .. }
140            | NetEvent::LogMessage { timestamp, .. }
141            | NetEvent::MarkingSnapshot { timestamp, .. } => *timestamp,
142        }
143    }
144
145    /// Returns the transition name if this event is transition-related.
146    pub fn transition_name(&self) -> Option<&str> {
147        match self {
148            NetEvent::TransitionEnabled {
149                transition_name, ..
150            }
151            | NetEvent::TransitionClockRestarted {
152                transition_name, ..
153            }
154            | NetEvent::TransitionStarted {
155                transition_name, ..
156            }
157            | NetEvent::TransitionCompleted {
158                transition_name, ..
159            }
160            | NetEvent::TransitionFailed {
161                transition_name, ..
162            }
163            | NetEvent::TransitionTimedOut {
164                transition_name, ..
165            }
166            | NetEvent::ActionTimedOut {
167                transition_name, ..
168            }
169            | NetEvent::LogMessage {
170                transition_name, ..
171            } => Some(transition_name),
172            _ => None,
173        }
174    }
175
176    /// Returns true if this is a failure event.
177    pub fn is_failure(&self) -> bool {
178        matches!(
179            self,
180            NetEvent::TransitionFailed { .. }
181                | NetEvent::TransitionTimedOut { .. }
182                | NetEvent::ActionTimedOut { .. }
183        )
184    }
185
186    /// Returns true if this event carries any error signal — superset of
187    /// [`is_failure`](Self::is_failure) that additionally treats a
188    /// [`LogMessage`](NetEvent::LogMessage) at level `ERROR` (case-insensitive)
189    /// as an error. Used by the v2 archive writer to pre-compute the
190    /// `hasErrors` flag so listing/sampling tools can filter archives without
191    /// scanning their event bodies. (libpetri 1.7.0+)
192    pub fn has_error_signal(&self) -> bool {
193        match self {
194            NetEvent::TransitionFailed { .. }
195            | NetEvent::TransitionTimedOut { .. }
196            | NetEvent::ActionTimedOut { .. } => true,
197            NetEvent::LogMessage { level, .. } => level.eq_ignore_ascii_case("ERROR"),
198            _ => false,
199        }
200    }
201}