webtau/lib.rs
1/// Re-export the `#[command]` proc macro so users write `#[webtau::command]`.
2pub use webtau_macros::command;
3
4/// Generates thread-local state management boilerplate for WASM targets.
5///
6/// When building a Tauri game for the web, you need to replace
7/// `State<Mutex<T>>` (which requires threads) with thread-local storage
8/// (WASM is single-threaded). This macro generates the required
9/// `thread_local!` + `RefCell` pattern and accessor functions.
10///
11/// # Usage
12///
13/// ```rust
14/// use webtau::wasm_state;
15///
16/// struct GameWorld {
17/// score: i32,
18/// }
19///
20/// wasm_state!(GameWorld);
21/// ```
22///
23/// # Generated API
24///
25/// - `set_state(val: T)` — Initialize or replace the state.
26/// - `with_state(|state| ...)` — Read-only access to the state.
27/// - `with_state_mut(|state| ...)` — Mutable access to the state.
28///
29/// All three functions panic if called before `set_state()`.
30#[macro_export]
31macro_rules! wasm_state {
32 ($T:ty) => {
33 ::std::thread_local! {
34 static __WEBTAU_STATE: ::std::cell::RefCell<Option<$T>> =
35 ::std::cell::RefCell::new(None);
36 }
37
38 /// Initialize or replace the global game state.
39 fn set_state(val: $T) {
40 __WEBTAU_STATE.with(|cell| {
41 *cell.borrow_mut() = Some(val);
42 });
43 }
44
45 /// Read-only access to the game state.
46 ///
47 /// # Panics
48 /// Panics if `set_state()` has not been called.
49 fn with_state<__F, __R>(f: __F) -> __R
50 where
51 __F: FnOnce(&$T) -> __R,
52 {
53 __WEBTAU_STATE.with(|cell| {
54 let borrow = cell.borrow();
55 let state = borrow
56 .as_ref()
57 .expect("webtau: state not initialized — call set_state() first");
58 f(state)
59 })
60 }
61
62 /// Mutable access to the game state.
63 ///
64 /// # Panics
65 /// Panics if `set_state()` has not been called.
66 fn with_state_mut<__F, __R>(f: __F) -> __R
67 where
68 __F: FnOnce(&mut $T) -> __R,
69 {
70 __WEBTAU_STATE.with(|cell| {
71 let mut borrow = cell.borrow_mut();
72 let state = borrow
73 .as_mut()
74 .expect("webtau: state not initialized — call set_state() first");
75 f(state)
76 })
77 }
78 };
79}
80
81#[cfg(test)]
82mod tests {
83 #[derive(Debug, PartialEq)]
84 struct Counter {
85 value: i32,
86 }
87
88 wasm_state!(Counter);
89
90 #[test]
91 fn set_and_read_state() {
92 set_state(Counter { value: 42 });
93 let result = with_state(|c| c.value);
94 assert_eq!(result, 42);
95 }
96
97 #[test]
98 fn mutate_state() {
99 set_state(Counter { value: 0 });
100 with_state_mut(|c| c.value += 10);
101 let result = with_state(|c| c.value);
102 assert_eq!(result, 10);
103 }
104
105 #[test]
106 #[should_panic(expected = "state not initialized")]
107 fn panics_without_init() {
108 // Each test thread gets its own thread-local, so this is fresh.
109 // We need a separate type to avoid interference from other tests.
110 struct Uninitialized;
111 wasm_state!(Uninitialized);
112 with_state(|_| {});
113 }
114}