Skip to main content

sprite_core/
actor.rs

1use std::any::Any;
2use std::collections::HashMap;
3use std::marker::PhantomData;
4use std::sync::Arc;
5use std::time::Duration;
6use parking_lot::RwLock;
7use crossbeam_channel::{Receiver, Sender};
8
9use crate::engine::EngineInner;
10use crate::message::Message;
11use crate::metrics::ActorMetrics;
12
13pub(crate) type StateStore = Arc<RwLock<HashMap<String, Box<dyn Any + Send + Sync>>>>;
14
15#[derive(Clone)]
16pub struct State<T: Clone + Send + Sync + 'static> {
17    key: String,
18    store: StateStore,
19    _phantom: PhantomData<T>,
20}
21
22impl<T: Clone + Send + Sync + 'static> State<T> {
23    pub fn get(&self) -> T {
24        let store = self.store.read();
25        store.get(&self.key)
26            .and_then(|v| v.downcast_ref::<T>())
27            .cloned()
28            .unwrap_or_else(|| panic!("state '{}' not found", self.key))
29    }
30    pub fn set(&self, value: T) {
31        let mut store = self.store.write();
32        store.insert(self.key.clone(), Box::new(value));
33    }
34    pub fn update<F>(&self, f: F)
35    where F: FnOnce(T) -> T,
36    {
37        let mut store = self.store.write();
38        let current = store.get(&self.key)
39            .and_then(|v| v.downcast_ref::<T>())
40            .cloned();
41        if let Some(c) = current {
42            store.insert(self.key.clone(), Box::new(f(c)));
43        }
44    }
45}
46
47pub struct Scratch<T> {
48    val: Option<T>,
49}
50
51impl<T> Scratch<T> {
52    pub fn new(val: T) -> Self { Self { val: Some(val) } }
53    pub fn get(&self) -> Option<&T> { self.val.as_ref() }
54    pub fn set(&mut self, val: T) { self.val = Some(val); }
55    pub fn take(&mut self) -> Option<T> { self.val.take() }
56}
57
58pub struct Context {
59    #[allow(dead_code)]
60    pub(crate) id: u64,
61    #[allow(dead_code)]
62    pub(crate) name: String,
63    pub(crate) state_store: StateStore,
64    pub(crate) rx: Receiver<Message>,
65    #[allow(dead_code)]
66    pub(crate) tx: Sender<Message>,
67    pub(crate) message_handler: Option<Arc<dyn Fn(Message) + Send + Sync>>,
68    pub(crate) panic_handler: Option<Arc<dyn Fn() + Send + Sync>>,
69    pub(crate) mount_handler: Option<Arc<dyn Fn() + Send + Sync>>,
70    pub(crate) unmount_handler: Option<Arc<dyn Fn() + Send + Sync>>,
71    pub(crate) engine: Arc<EngineInner>,
72    pub(crate) metrics: ActorMetrics,
73    pub(crate) is_first_mount: bool,
74}
75
76impl Context {
77    pub(crate) fn new(
78        id: u64, name: String, state_store: StateStore,
79        rx: Receiver<Message>, tx: Sender<Message>,
80        engine: Arc<EngineInner>,
81    ) -> Self {
82        Self {
83            id, name, state_store, rx, tx,
84            message_handler: None,
85            panic_handler: None,
86            mount_handler: None,
87            unmount_handler: None,
88            engine,
89            metrics: ActorMetrics::new(),
90            is_first_mount: true,
91        }
92    }
93
94    pub fn use_state<T: Clone + Send + Sync + 'static>(&self, key: &str, initial: T) -> State<T> {
95        {
96            let mut store = self.state_store.write();
97            if !store.contains_key(key) {
98                store.insert(key.to_string(), Box::new(initial));
99            }
100        }
101        State { key: key.to_string(), store: self.state_store.clone(), _phantom: PhantomData }
102    }
103
104    pub fn use_scratch<T>(&self, initial: T) -> Scratch<T> {
105        Scratch::new(initial)
106    }
107
108    pub fn on_message<F>(&mut self, f: F)
109    where F: Fn(Message) + Send + Sync + 'static,
110    {
111        self.message_handler = Some(Arc::new(f));
112    }
113
114    pub fn on_panic<F>(&mut self, f: F)
115    where F: Fn() + Send + Sync + 'static,
116    {
117        self.panic_handler = Some(Arc::new(f));
118    }
119
120    pub fn on_mount<F>(&mut self, f: F)
121    where F: Fn() + Send + Sync + 'static,
122    {
123        self.mount_handler = Some(Arc::new(f));
124    }
125
126    pub fn on_unmount<F>(&mut self, f: F)
127    where F: Fn() + Send + Sync + 'static,
128    {
129        self.unmount_handler = Some(Arc::new(f));
130    }
131
132    pub fn spawn<F>(&self, name: &str, setup: F) -> crate::engine::Handle
133    where F: Fn(&mut Context) + Send + Sync + 'static,
134    {
135        self.engine.spawn_simple(name, setup)
136    }
137
138    pub fn send_to(&self, id: u64, msg: Message) {
139        self.engine.send_to(id, msg);
140    }
141
142    pub fn send_named(&self, name: &str, msg: Message) {
143        if let Some(id) = self.engine.registry.lookup(name) {
144            self.engine.send_to(id, msg);
145        }
146    }
147
148    pub fn request(&self, id: u64, msg: Message, timeout: Duration) -> Option<Message> {
149        self.engine.request(id, msg, timeout)
150    }
151
152    pub fn reply(&self, _msg: Message) {
153        // Placeholder
154    }
155
156    pub fn metrics(&self) -> &ActorMetrics {
157        &self.metrics
158    }
159
160    pub fn sleep(&self, duration: Duration) {
161        let _ = self.rx.recv_timeout(duration);
162    }
163
164    pub fn poll(&self) -> Option<Message> {
165        self.rx.try_recv().ok()
166    }
167
168    pub fn id(&self) -> u64 { self.id }
169    pub fn name(&self) -> &str { &self.name }
170}
171
172#[cfg(test)]
173mod tests {
174    use super::*;
175    use crossbeam_channel::unbounded;
176
177    fn dummy_ctx() -> Context {
178        let (tx, rx) = unbounded();
179        Context::new(1, "test".to_string(), Arc::new(RwLock::new(HashMap::new())), rx, tx, Arc::new(EngineInner::new()))
180    }
181
182    #[test]
183    fn use_state_get_set() {
184        let ctx = dummy_ctx();
185        let count = ctx.use_state("count", 42i64);
186        assert_eq!(count.get(), 42);
187        count.set(100);
188        assert_eq!(count.get(), 100);
189    }
190
191    #[test]
192    fn use_state_update() {
193        let ctx = dummy_ctx();
194        let count = ctx.use_state("count", 10i64);
195        count.update(|c| c * 2);
196        assert_eq!(count.get(), 20);
197    }
198
199    #[test]
200    fn state_survives_context_drop() {
201        let store = Arc::new(RwLock::new(HashMap::new()));
202        {
203            let (tx, rx) = unbounded();
204            let ctx = Context::new(1, "test".to_string(), store.clone(), rx, tx, Arc::new(EngineInner::new()));
205            let count = ctx.use_state("count", 42i64);
206            count.set(99);
207        }
208        {
209            let (tx, rx) = unbounded();
210            let ctx = Context::new(1, "test".to_string(), store.clone(), rx, tx, Arc::new(EngineInner::new()));
211            let count = ctx.use_state::<i64>("count", 0);
212            assert_eq!(count.get(), 99);
213        }
214    }
215
216    #[test]
217    fn scratch_does_not_persist() {
218        let ctx = dummy_ctx();
219        let mut s = ctx.use_scratch(42i64);
220        assert_eq!(s.get(), Some(&42));
221        s.set(100);
222        assert_eq!(s.get(), Some(&100));
223    }
224}