Skip to main content

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}