1use std::collections::HashMap;
2use std::sync::atomic::{AtomicU64, Ordering};
3use std::sync::Mutex;
4
5use serde::{Deserialize, Serialize};
6use wasm_bindgen::JsValue;
7
8#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)]
9pub struct RpcInstanceHandle {
10 pub id: String,
11}
12
13impl RpcInstanceHandle {
14 pub fn new(id: impl Into<String>) -> Self {
15 Self { id: id.into() }
16 }
17}
18
19#[derive(Debug, Default)]
20pub struct RpcInstanceRegistry {
21 next_id: AtomicU64,
22 instances: Mutex<HashMap<String, JsValue>>,
23}
24
25impl RpcInstanceRegistry {
26 pub fn new() -> Self {
27 Self::default()
28 }
29
30 pub fn register(&self, instance: JsValue) -> RpcInstanceHandle {
31 let id = format!("rpc-instance-{}", self.next_id.fetch_add(1, Ordering::Relaxed));
32 self.instances
33 .lock()
34 .expect("lock instance registry")
35 .insert(id.clone(), instance);
36 RpcInstanceHandle { id }
37 }
38
39 pub fn get(&self, handle: &RpcInstanceHandle) -> Option<JsValue> {
40 self.instances
41 .lock()
42 .expect("lock instance registry")
43 .get(&handle.id)
44 .cloned()
45 }
46
47 pub fn release(&self, handle: &RpcInstanceHandle) -> bool {
48 self.instances
49 .lock()
50 .expect("lock instance registry")
51 .remove(&handle.id)
52 .is_some()
53 }
54}
55
56#[cfg(all(test, target_arch = "wasm32"))]
57mod tests {
58 use super::*;
59
60 #[test]
61 fn register_get_release_cycle() {
62 let registry = RpcInstanceRegistry::new();
63 let instance = JsValue::UNDEFINED;
64
65 let handle = registry.register(instance.clone());
66 let fetched = registry.get(&handle).expect("instance exists");
67 assert!(fetched.is_undefined());
68
69 assert!(registry.release(&handle));
70 assert!(registry.get(&handle).is_none());
71 }
72
73 #[test]
74 fn double_release_returns_false_second_time() {
75 let registry = RpcInstanceRegistry::new();
76 let handle = registry.register(JsValue::UNDEFINED);
77
78 assert!(registry.release(&handle));
79 assert!(!registry.release(&handle));
80 }
81}