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}