Skip to main content

libpetri_core/
token.rs

1use std::any::Any;
2use std::sync::Arc;
3use std::time::{SystemTime, UNIX_EPOCH};
4
5/// An immutable token carrying a typed value through the Petri net.
6///
7/// Tokens flow from place to place as transitions fire, carrying typed
8/// payloads that represent the state of a computation or workflow.
9#[derive(Debug, Clone)]
10pub struct Token<T> {
11    value: Arc<T>,
12    created_at: u64,
13}
14
15impl<T> Token<T> {
16    /// Creates a token with the given value and current timestamp.
17    pub fn new(value: T) -> Self {
18        Self {
19            value: Arc::new(value),
20            created_at: now_millis(),
21        }
22    }
23
24    /// Creates a token with a specific timestamp (for testing/replay).
25    pub fn at(value: T, created_at: u64) -> Self {
26        Self {
27            value: Arc::new(value),
28            created_at,
29        }
30    }
31
32    /// Creates a token wrapping an existing Arc value.
33    pub fn from_arc(value: Arc<T>, created_at: u64) -> Self {
34        Self { value, created_at }
35    }
36
37    /// Returns a reference to the token's value.
38    pub fn value(&self) -> &T {
39        &self.value
40    }
41
42    /// Returns a clone of the Arc-wrapped value (cheap).
43    pub fn value_arc(&self) -> Arc<T> {
44        Arc::clone(&self.value)
45    }
46
47    /// Returns the creation timestamp in epoch milliseconds.
48    pub fn created_at(&self) -> u64 {
49        self.created_at
50    }
51}
52
53/// Returns a unit token (marker with no meaningful value).
54/// Used for pure control flow where presence matters but data doesn't.
55pub fn unit_token() -> Token<()> {
56    Token {
57        value: Arc::new(()),
58        created_at: 0,
59    }
60}
61
62/// Type-erased token for marking storage.
63#[derive(Debug, Clone)]
64pub struct ErasedToken {
65    pub value: Arc<dyn Any + Send + Sync>,
66    pub created_at: u64,
67}
68
69impl ErasedToken {
70    /// Wraps a typed token into an erased token.
71    pub fn from_typed<T: Send + Sync + 'static>(token: &Token<T>) -> Self {
72        Self {
73            value: Arc::clone(&token.value) as Arc<dyn Any + Send + Sync>,
74            created_at: token.created_at,
75        }
76    }
77
78    /// Attempts to downcast back to a typed token.
79    pub fn downcast<T: Send + Sync + 'static>(&self) -> Option<Token<T>> {
80        self.value.downcast_ref::<T>().map(|_| {
81            // Safety: we verified the type matches, so the Arc contains T
82            let value = Arc::clone(&self.value);
83            // SAFETY: downcast_ref confirmed the type
84            let typed: Arc<T> = unsafe {
85                let raw = Arc::into_raw(value);
86                Arc::from_raw(raw.cast::<T>())
87            };
88            Token::from_arc(typed, self.created_at)
89        })
90    }
91}
92
93pub fn now_millis() -> u64 {
94    SystemTime::now()
95        .duration_since(UNIX_EPOCH)
96        .unwrap_or_default()
97        .as_millis() as u64
98}
99
100#[cfg(test)]
101mod tests {
102    use super::*;
103
104    #[test]
105    fn token_new_has_current_timestamp() {
106        let t = Token::new(42);
107        assert_eq!(*t.value(), 42);
108        assert!(t.created_at() > 0);
109    }
110
111    #[test]
112    fn token_at_preserves_timestamp() {
113        let t = Token::at("hello", 1000);
114        assert_eq!(*t.value(), "hello");
115        assert_eq!(t.created_at(), 1000);
116    }
117
118    #[test]
119    fn unit_token_has_zero_timestamp() {
120        let t = unit_token();
121        assert_eq!(*t.value(), ());
122        assert_eq!(t.created_at(), 0);
123    }
124
125    #[test]
126    fn token_clone_is_cheap() {
127        let t = Token::new(vec![1, 2, 3]);
128        let t2 = t.clone();
129        assert!(Arc::ptr_eq(&t.value, &t2.value));
130    }
131
132    #[test]
133    fn erased_token_roundtrip() {
134        let t = Token::new(42i32);
135        let erased = ErasedToken::from_typed(&t);
136        let recovered = erased.downcast::<i32>().unwrap();
137        assert_eq!(*recovered.value(), 42);
138        assert_eq!(recovered.created_at(), t.created_at());
139    }
140
141    #[test]
142    fn erased_token_wrong_type_returns_none() {
143        let t = Token::new(42i32);
144        let erased = ErasedToken::from_typed(&t);
145        assert!(erased.downcast::<String>().is_none());
146    }
147}