Skip to main content

libpetri_runtime/
marking.rs

1use std::any::Any;
2use std::collections::{HashMap, VecDeque};
3use std::sync::Arc;
4
5use libpetri_core::place::Place;
6use libpetri_core::token::{ErasedToken, Token};
7
8/// Mutable token state of a Petri net during execution.
9///
10/// Stores type-erased tokens in FIFO queues keyed by place name.
11#[derive(Debug, Default)]
12pub struct Marking {
13    tokens: HashMap<Arc<str>, VecDeque<ErasedToken>>,
14}
15
16impl Marking {
17    pub fn new() -> Self {
18        Self::default()
19    }
20
21    /// Adds a typed token to a place.
22    pub fn add<T: Send + Sync + 'static>(&mut self, place: &Place<T>, token: Token<T>) {
23        let erased = ErasedToken::from_typed(&token);
24        self.tokens
25            .entry(Arc::clone(place.name_arc()))
26            .or_default()
27            .push_back(erased);
28    }
29
30    /// Adds a type-erased token to a place by name.
31    pub fn add_erased(&mut self, place_name: &Arc<str>, token: ErasedToken) {
32        self.tokens
33            .entry(Arc::clone(place_name))
34            .or_default()
35            .push_back(token);
36    }
37
38    /// Returns the number of tokens in a place.
39    pub fn count(&self, place_name: &str) -> usize {
40        self.tokens.get(place_name).map_or(0, |q| q.len())
41    }
42
43    /// Returns true if a place has any tokens.
44    pub fn has_tokens(&self, place_name: &str) -> bool {
45        self.count(place_name) > 0
46    }
47
48    /// Peeks at the first token value in a place without removing it.
49    pub fn peek<T: Send + Sync + 'static>(&self, place: &Place<T>) -> Option<Arc<T>> {
50        self.tokens
51            .get(place.name())
52            .and_then(|q| q.front())
53            .and_then(|t| t.downcast::<T>().map(|token| token.value_arc()))
54    }
55
56    /// Removes and returns the first token from a place (FIFO).
57    pub fn remove_first(&mut self, place_name: &str) -> Option<ErasedToken> {
58        self.tokens.get_mut(place_name).and_then(|q| q.pop_front())
59    }
60
61    /// Removes and returns the first token matching a guard predicate.
62    pub fn remove_matching(
63        &mut self,
64        place_name: &str,
65        guard: &dyn Fn(&dyn Any) -> bool,
66    ) -> Option<ErasedToken> {
67        let queue = self.tokens.get_mut(place_name)?;
68        let pos = queue.iter().position(|t| guard(t.value.as_ref()))?;
69        queue.remove(pos)
70    }
71
72    /// Removes and returns all tokens from a place.
73    pub fn remove_all(&mut self, place_name: &str) -> Vec<ErasedToken> {
74        self.tokens
75            .get_mut(place_name)
76            .map_or_else(Vec::new, |q| q.drain(..).collect())
77    }
78
79    /// Removes and returns all tokens matching a guard predicate.
80    pub fn remove_all_matching(
81        &mut self,
82        place_name: &str,
83        guard: &dyn Fn(&dyn Any) -> bool,
84    ) -> Vec<ErasedToken> {
85        let queue = match self.tokens.get_mut(place_name) {
86            Some(q) => q,
87            None => return Vec::new(),
88        };
89        let mut matched = Vec::new();
90        let mut remaining = VecDeque::new();
91        for token in queue.drain(..) {
92            if guard(token.value.as_ref()) {
93                matched.push(token);
94            } else {
95                remaining.push_back(token);
96            }
97        }
98        *queue = remaining;
99        matched
100    }
101
102    /// Counts tokens matching a guard predicate.
103    pub fn count_matching(&self, place_name: &str, guard: &dyn Fn(&dyn Any) -> bool) -> usize {
104        self.tokens
105            .get(place_name)
106            .map_or(0, |q| q.iter().filter(|t| guard(t.value.as_ref())).count())
107    }
108
109    /// Returns the internal token map (for snapshot/event purposes).
110    pub fn token_counts(&self) -> HashMap<Arc<str>, usize> {
111        self.tokens
112            .iter()
113            .filter(|(_, q)| !q.is_empty())
114            .map(|(k, q)| (Arc::clone(k), q.len()))
115            .collect()
116    }
117
118    /// Returns all place names that have tokens.
119    pub fn non_empty_places(&self) -> Vec<Arc<str>> {
120        self.tokens
121            .iter()
122            .filter(|(_, q)| !q.is_empty())
123            .map(|(k, _)| Arc::clone(k))
124            .collect()
125    }
126
127    /// Returns the raw queue for a place (for executor internal use).
128    pub fn queue(&self, place_name: &str) -> Option<&VecDeque<ErasedToken>> {
129        self.tokens.get(place_name)
130    }
131}
132
133#[cfg(test)]
134mod tests {
135    use super::*;
136
137    #[test]
138    fn add_and_count() {
139        let p = Place::<i32>::new("p");
140        let mut m = Marking::new();
141        assert_eq!(m.count("p"), 0);
142        assert!(!m.has_tokens("p"));
143
144        m.add(&p, Token::new(1));
145        m.add(&p, Token::new(2));
146        assert_eq!(m.count("p"), 2);
147        assert!(m.has_tokens("p"));
148    }
149
150    #[test]
151    fn peek() {
152        let p = Place::<i32>::new("p");
153        let mut m = Marking::new();
154        m.add(&p, Token::new(42));
155        assert_eq!(*m.peek(&p).unwrap(), 42);
156        assert_eq!(m.count("p"), 1); // peek doesn't consume
157    }
158
159    #[test]
160    fn remove_first_fifo() {
161        let p = Place::<i32>::new("p");
162        let mut m = Marking::new();
163        m.add(&p, Token::at(1, 100));
164        m.add(&p, Token::at(2, 200));
165
166        let t = m.remove_first("p").unwrap();
167        let recovered = t.downcast::<i32>().unwrap();
168        assert_eq!(*recovered.value(), 1);
169        assert_eq!(m.count("p"), 1);
170    }
171
172    #[test]
173    fn remove_all() {
174        let p = Place::<i32>::new("p");
175        let mut m = Marking::new();
176        m.add(&p, Token::new(1));
177        m.add(&p, Token::new(2));
178        m.add(&p, Token::new(3));
179
180        let tokens = m.remove_all("p");
181        assert_eq!(tokens.len(), 3);
182        assert_eq!(m.count("p"), 0);
183    }
184
185    #[test]
186    fn remove_matching() {
187        let p = Place::<i32>::new("p");
188        let mut m = Marking::new();
189        m.add(&p, Token::new(1));
190        m.add(&p, Token::new(2));
191        m.add(&p, Token::new(3));
192
193        let guard = |v: &dyn Any| v.downcast_ref::<i32>().is_some_and(|n| *n > 1);
194        let t = m.remove_matching("p", &guard).unwrap();
195        let recovered = t.downcast::<i32>().unwrap();
196        assert_eq!(*recovered.value(), 2); // first matching
197        assert_eq!(m.count("p"), 2);
198    }
199
200    #[test]
201    fn token_counts() {
202        let p1 = Place::<i32>::new("p1");
203        let p2 = Place::<String>::new("p2");
204        let mut m = Marking::new();
205        m.add(&p1, Token::new(1));
206        m.add(&p1, Token::new(2));
207        m.add(&p2, Token::new("hello".to_string()));
208
209        let counts = m.token_counts();
210        assert_eq!(counts.len(), 2);
211        assert_eq!(counts[&Arc::from("p1")], 2);
212        assert_eq!(counts[&Arc::from("p2")], 1);
213    }
214}