leptos_leaflet/core/
thread_safe_jsvalue.rs1use std::{
9 mem::{self, ManuallyDrop},
10 ops::Deref,
11};
12
13use paste::paste;
14use wasm_bindgen::{convert::IntoWasmAbi, JsValue};
15
16const NOT_ON_CURRENT_THREAD: &str = "ThreadSafeJsValue is not on the current thread";
17
18pub struct ThreadSafeJsValue<T> {
23 value: ManuallyDrop<T>,
24 thread_id: std::thread::ThreadId,
25}
26
27impl<T> ThreadSafeJsValue<T> {
28 pub fn new(value: T) -> Self {
43 Self {
44 value: ManuallyDrop::new(value),
45 thread_id: std::thread::current().id(),
46 }
47 }
48}
49
50impl<T> Drop for ThreadSafeJsValue<T> {
51 #[track_caller]
57 fn drop(&mut self) {
58 if !mem::needs_drop::<T>() || self.thread_id == std::thread::current().id() {
59 unsafe {
60 ManuallyDrop::drop(&mut self.value);
61 }
62 } else {
63 invalid_thread();
64 }
65 }
66}
67
68impl<T> ThreadSafeJsValue<T> {
69 #[track_caller]
75 fn check_thread(&self) {
76 #[cfg(not(target_arch = "wasm32"))]
79 if self.thread_id != std::thread::current().id() {
80 invalid_thread();
81 }
82 }
83
84 pub fn is_valid(&self) -> bool {
86 self.thread_id == std::thread::current().id()
87 }
88
89 #[track_caller]
95 pub fn value(&self) -> &T {
96 self.check_thread();
97 &self.value
98 }
99
100 #[track_caller]
102 pub fn try_value(&self) -> Result<&T, std::io::Error> {
103 if self.thread_id == std::thread::current().id() {
104 Ok(&self.value)
105 } else {
106 Err(std::io::Error::new(
107 std::io::ErrorKind::Other,
108 NOT_ON_CURRENT_THREAD,
109 ))
110 }
111 }
112}
113
114unsafe impl<T> Send for ThreadSafeJsValue<T> {}
118
119unsafe impl<T> Sync for ThreadSafeJsValue<T> {}
123
124impl<T> Clone for ThreadSafeJsValue<T>
125where
126 T: Clone,
127{
128 fn clone(&self) -> Self {
130 Self {
131 value: self.value.clone(),
132 thread_id: self.thread_id,
133 }
134 }
135}
136
137impl<T> std::fmt::Debug for ThreadSafeJsValue<T>
138where
139 T: std::fmt::Debug,
140{
141 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
147 f.write_fmt(format_args!("ThreadId:{:?}", self.thread_id))?;
148 f.write_fmt(format_args!("Value:{:?}", self.value.deref()))
149 }
150}
151
152impl<T> std::fmt::Display for ThreadSafeJsValue<T>
153where
154 T: std::fmt::Display,
155{
156 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
162 self.value.deref().fmt(f)
163 }
164}
165
166impl<T> PartialEq for ThreadSafeJsValue<T>
167where
168 T: PartialEq,
169{
170 fn eq(&self, other: &Self) -> bool {
176 self.value.deref() == other.value.deref()
177 }
178}
179
180impl<T> Eq for ThreadSafeJsValue<T> where T: Eq {}
181
182impl<T> std::hash::Hash for ThreadSafeJsValue<T>
183where
184 T: std::hash::Hash,
185{
186 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
192 self.value.deref().hash(state)
193 }
194}
195
196impl<T> std::cmp::PartialOrd for ThreadSafeJsValue<T>
197where
198 T: std::cmp::PartialOrd,
199{
200 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
206 self.value.deref().partial_cmp(other.value.deref())
207 }
208}
209
210impl<T> std::cmp::Ord for ThreadSafeJsValue<T>
211where
212 T: std::cmp::Ord,
213{
214 fn cmp(&self, other: &Self) -> std::cmp::Ordering {
220 self.value.deref().cmp(other.value.deref())
221 }
222}
223
224impl<T> std::ops::Deref for ThreadSafeJsValue<T> {
225 type Target = T;
226
227 #[track_caller]
233 fn deref(&self) -> &Self::Target {
234 self.check_thread();
235 &self.value
236 }
237}
238
239impl<T> std::ops::DerefMut for ThreadSafeJsValue<T> {
240 #[track_caller]
246 fn deref_mut(&mut self) -> &mut Self::Target {
247 self.check_thread();
248 &mut self.value
249 }
250}
251
252pub trait IntoThreadSafeJsValue: Sized {
257 fn into_thread_safe_js_value(self) -> ThreadSafeJsValue<Self>
259 where
260 Self: IntoWasmAbi;
261}
262
263impl<T> IntoThreadSafeJsValue for T
264where
265 T: IntoWasmAbi,
266{
267 fn into_thread_safe_js_value(self) -> ThreadSafeJsValue<Self> {
268 ThreadSafeJsValue::new(self)
269 }
270}
271
272#[cold]
273#[track_caller]
274#[inline(never)]
275fn invalid_thread() -> ! {
276 panic!("{}", NOT_ON_CURRENT_THREAD);
277}
278
279#[allow(dead_code)]
284#[macro_export]
285macro_rules! impl_thread_safe_js_value {
286 ($type:ty) => {
287 impl From<$type> for ThreadSafeJsValue<$type> {
288 fn from(value: $type) -> Self {
289 Self::new(value)
290 }
291 }
292 paste! {pub type [<$type TS>] = ThreadSafeJsValue<$type>;}
293 };
294 ($type:ty, $suffix:expr) => {
295 impl From<$type> for ThreadSafeJsValue<$type> {
296 fn from(value: $type) -> Self {
297 Self::new(value)
298 }
299 }
300 paste! {pub type [< $type $suffix>] = ThreadSafeJsValue<$type>;}
301 };
302}
303
304impl_thread_safe_js_value!(JsValue);
305
306#[cfg(test)]
307mod tests {
308 use super::*;
309 #[cfg(target_arch = "wasm32")]
310 use wasm_bindgen::JsValue;
311 #[cfg(target_arch = "wasm32")]
312 use wasm_bindgen_test::wasm_bindgen_test;
313
314 #[cfg(target_arch = "wasm32")]
315 #[wasm_bindgen_test]
316 fn test_thread_safe_js_value() {
317 let js_value = JsValue::from(42);
318 let thread_safe_js_value = ThreadSafeJsValue::new(js_value);
319 assert_eq!(thread_safe_js_value.value(), &JsValue::from(42));
320 }
321
322 #[cfg(target_arch = "wasm32")]
323 #[wasm_bindgen_test]
324 fn test_thread_safe_js_value_clone() {
325 let js_value = JsValue::from(42);
326 let thread_safe_js_value = ThreadSafeJsValue::new(js_value);
327 let cloned_thread_safe_js_value = thread_safe_js_value.clone();
328 assert_eq!(cloned_thread_safe_js_value.value(), &JsValue::from(42));
329 }
330
331 #[cfg(target_arch = "wasm32")]
332 #[wasm_bindgen_test]
333 fn test_thread_safe_js_value_try_value() {
334 let js_value = JsValue::from(42);
335 let thread_safe_js_value = ThreadSafeJsValue::new(js_value);
336 assert_eq!(
337 thread_safe_js_value.try_value().unwrap(),
338 &JsValue::from(42)
339 );
340 }
341
342 #[cfg(target_arch = "wasm32")]
343 #[wasm_bindgen_test]
344 fn test_thread_into_thread_safe_js_value() {
345 let js_value = JsValue::from(42);
346 let thread_safe_js_value = js_value.into_thread_safe_js_value();
347 assert_eq!(thread_safe_js_value.value(), &JsValue::from(42));
348 }
349
350 #[test]
351 fn test_thread_safe_value() {
352 let value = 42;
353 let thread_safe_value = ThreadSafeJsValue::new(value);
354 assert_eq!(thread_safe_value.value(), &42);
355 }
356
357 #[test]
358 fn test_thread_safe_value_clone() {
359 let value = 42;
360 let thread_safe_value = ThreadSafeJsValue::new(value);
361 let cloned_thread_safe_value = thread_safe_value.clone();
362 assert_eq!(cloned_thread_safe_value.value(), &42);
363 }
364
365 #[test]
366 fn test_thread_safe_value_try_value() {
367 let value = 42;
368 let thread_safe_value = ThreadSafeJsValue::new(value);
369 assert_eq!(thread_safe_value.try_value().unwrap(), &42);
370 }
371
372 #[test]
373 fn test_into_thread_safe_value() {
374 let value = 42;
375 let thread_safe_value = value.into_thread_safe_js_value();
376 assert_eq!(thread_safe_value.value(), &42);
377 }
378}