Skip to main content

wasm_actor_bridge/
context.rs

1//! Response context for worker actors.
2//!
3//! On WASM, responses are posted to the main thread via `postMessage`.
4//! On native (non-WASM), responses are collected in memory for testing.
5
6// ── Native implementation (for testing) ──────────────────────
7
8#[cfg(not(target_arch = "wasm32"))]
9mod native_impl {
10    use std::cell::RefCell;
11    use std::rc::Rc;
12
13    struct ContextInner<Evt> {
14        bytes: Option<Vec<u8>>,
15        responses: RefCell<Vec<(Evt, Option<Vec<u8>>)>>,
16    }
17
18    /// Response context for dispatching events back to the main thread.
19    ///
20    /// On native targets, responses are collected in memory for testing.
21    /// `Clone + 'static` — safe to move into spawned tasks.
22    pub struct Context<Evt> {
23        inner: Rc<ContextInner<Evt>>,
24    }
25
26    impl<Evt> Clone for Context<Evt> {
27        fn clone(&self) -> Self {
28            Self {
29                inner: Rc::clone(&self.inner),
30            }
31        }
32    }
33
34    impl<Evt> Context<Evt> {
35        /// Create a test context with optional incoming bytes.
36        pub fn new(bytes: Option<Vec<u8>>) -> Self {
37            Self {
38                inner: Rc::new(ContextInner {
39                    bytes,
40                    responses: RefCell::new(Vec::new()),
41                }),
42            }
43        }
44
45        /// Access the binary payload from the incoming command (if any).
46        pub fn bytes(&self) -> Option<&[u8]> {
47            self.inner.bytes.as_deref()
48        }
49
50        /// Send an event back to the main thread.
51        pub fn respond(&self, evt: Evt) {
52            self.inner.responses.borrow_mut().push((evt, None));
53        }
54
55        /// Send an event with a binary sidecar back to the main thread.
56        pub fn respond_bytes(&self, evt: Evt, bytes: Vec<u8>) {
57            self.inner.responses.borrow_mut().push((evt, Some(bytes)));
58        }
59
60        /// Number of responses sent so far.
61        pub fn response_count(&self) -> usize {
62            self.inner.responses.borrow().len()
63        }
64    }
65
66    impl<Evt: Clone> Context<Evt> {
67        /// Collect all responses (test helper).
68        pub fn responses(&self) -> Vec<(Evt, Option<Vec<u8>>)> {
69            self.inner.responses.borrow().clone()
70        }
71    }
72}
73
74#[cfg(not(target_arch = "wasm32"))]
75pub use native_impl::*;
76
77// ── WASM implementation ──────────────────────────────────────
78
79#[cfg(target_arch = "wasm32")]
80mod wasm_impl {
81    use std::cell::Cell;
82    use std::marker::PhantomData;
83    use std::rc::Rc;
84
85    use serde::Serialize;
86
87    struct ContextInner {
88        correlation_id: Option<u64>,
89        bytes: Option<Vec<u8>>,
90        replied_correlated: Cell<bool>,
91    }
92
93    /// Response context for dispatching events back to the main thread.
94    ///
95    /// `Clone + 'static` — safe to move into spawned tasks on the Worker.
96    pub struct Context<Evt> {
97        inner: Rc<ContextInner>,
98        _phantom: PhantomData<fn(Evt)>,
99    }
100
101    impl<Evt> Clone for Context<Evt> {
102        fn clone(&self) -> Self {
103            Self {
104                inner: Rc::clone(&self.inner),
105                _phantom: PhantomData,
106            }
107        }
108    }
109
110    impl<Evt> Context<Evt> {
111        pub(crate) fn new(correlation_id: Option<u64>, bytes: Option<Vec<u8>>) -> Self {
112            Self {
113                inner: Rc::new(ContextInner {
114                    correlation_id,
115                    bytes,
116                    replied_correlated: Cell::new(false),
117                }),
118                _phantom: PhantomData,
119            }
120        }
121
122        /// Access the binary payload from the incoming command (if any).
123        pub fn bytes(&self) -> Option<&[u8]> {
124            self.inner.bytes.as_deref()
125        }
126    }
127
128    #[allow(clippy::needless_pass_by_value)] // Taking ownership mirrors the main-thread API.
129    impl<Evt: Serialize + 'static> Context<Evt> {
130        /// Take the correlation ID for the first reply (RPC routing).
131        fn take_correlation_id(&self) -> Option<u64> {
132            if self.inner.replied_correlated.get() {
133                return None;
134            }
135            self.inner.replied_correlated.set(true);
136            self.inner.correlation_id
137        }
138
139        /// Send an event back to the main thread.
140        pub fn respond(&self, evt: Evt) {
141            let corr_id = self.take_correlation_id();
142            if let Err(e) = crate::transfer::post_to_main(corr_id, &evt, None) {
143                tracing::error!("respond failed: {e}");
144            }
145        }
146
147        /// Send an event with a binary sidecar back to the main thread.
148        pub fn respond_bytes(&self, evt: Evt, bytes: Vec<u8>) {
149            let corr_id = self.take_correlation_id();
150            if let Err(e) = crate::transfer::post_to_main(corr_id, &evt, Some(&bytes)) {
151                tracing::error!("respond failed: {e}");
152            }
153        }
154    }
155}
156
157#[cfg(target_arch = "wasm32")]
158pub use wasm_impl::*;