app_window 0.3.4

Cross-platform window library
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
// SPDX-License-Identifier: MPL-2.0

//! A cell type for main-thread-only values that can be shared across threads.
//!
//! `MainThreadCell<T>` is a thread-safe container that allows `T` to be shared across threads
//! while ensuring all access to the inner value happens on the main thread. This is useful for
//! wrapping platform-specific resources that must only be accessed from the main thread.
//!
//! # Example
//!
//! ```
//! # async fn example() {
//! use app_window::main_thread_cell::MainThreadCell;
//!
//! // Create a cell with a main-thread-only value
//! let cell = MainThreadCell::new(42);
//!
//! // Access from main thread
//! if app_window::application::is_main_thread() {
//!     let guard = cell.lock();
//!     println!("Value: {}", *guard);
//! }
//!
//! // Access from any thread via async
//! let result = cell.with(|value| {
//!     // This closure runs on the main thread
//!     *value * 2
//! }).await;
//! assert_eq!(result, 84);
//! # }
//! ```

use crate::application;
use send_cells::UnsafeSendCell;
use send_cells::unsafe_sync_cell::UnsafeSyncCell;
use std::fmt::{Debug, Formatter};
use std::future::Future;
use std::ops::{Deref, DerefMut};
use std::sync::{Arc, Mutex, MutexGuard};

/// Internal shared state for MainThreadCell
#[derive(Debug)]
struct Shared<T: 'static> {
    inner: Option<UnsafeSendCell<UnsafeSyncCell<T>>>,
    mutex: Mutex<()>,
}

impl<T> Drop for Shared<T> {
    fn drop(&mut self) {
        // When we're dropping the last value, we need to do so on the right thread
        if let Some(take) = self.inner.take() {
            if application::is_main_thread() {
                drop(take);
            } else if application::is_main_thread_running() {
                let drop_shared = format!("MainThreadCell::drop({})", std::any::type_name::<T>());
                // If dispatch itself panics, dropping its closure on this worker
                // must not also drop the thread-affine value here.
                let mut deferred = std::mem::ManuallyDrop::new(take);
                application::submit_to_main_thread(drop_shared, move || {
                    // SAFETY: this FnOnce closure is the sole owner and runs only
                    // on the main thread.
                    unsafe { std::mem::ManuallyDrop::drop(&mut deferred) };
                });
            } else {
                // Dropping T here could violate its thread-affinity invariant. With no
                // running main-thread dispatcher, leaking is the only safe option.
                logwise::error_sync!(
                    "Leaking {type_name}: its MainThreadCell was dropped off the main thread after the dispatcher stopped",
                    type_name = logwise::privacy::IPromiseItsNotPrivate(std::any::type_name::<T>())
                );
                std::mem::forget(take);
            }
        }
    }
}

/// A guard providing mutable access to the inner value of a MainThreadCell.
///
/// This guard ensures that the value is only accessed on the main thread and
/// holds a mutex lock for the duration of access.
pub struct MainThreadGuard<'a, T: 'static> {
    _guard: MutexGuard<'a, ()>,
    value: &'a mut T,
}

impl<'a, T> AsRef<T> for MainThreadGuard<'a, T> {
    fn as_ref(&self) -> &T {
        &*self.value
    }
}

impl<'a, T> AsMut<T> for MainThreadGuard<'a, T> {
    fn as_mut(&mut self) -> &mut T {
        &mut *self.value
    }
}

impl<'a, T> Deref for MainThreadGuard<'a, T> {
    type Target = T;

    fn deref(&self) -> &Self::Target {
        &*self.value
    }
}

impl<'a, T> DerefMut for MainThreadGuard<'a, T> {
    fn deref_mut(&mut self) -> &mut Self::Target {
        &mut *self.value
    }
}

impl<'a, T: Debug> Debug for MainThreadGuard<'a, T> {
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        f.debug_struct("MainThreadGuard")
            .field("value", &*self.value)
            .finish()
    }
}

/// A thread-safe cell that ensures all access to its contents happens on the main thread.
///
/// `MainThreadCell<T>` allows you to share `T` across threads while guaranteeing that
/// all access to the inner value occurs on the main thread. This is particularly useful
/// for platform-specific resources that have main-thread-only requirements.
///
/// # Thread Safety
///
/// - The cell itself can be cloned and sent across threads
/// - All access methods verify they're called from the main thread
/// - The `with` and `with_async` methods automatically dispatch to the main thread
/// - Drop operations are automatically handled on the main thread
pub struct MainThreadCell<T: 'static> {
    shared: Option<Arc<Shared<T>>>,
}

impl<T> PartialEq for MainThreadCell<T> {
    fn eq(&self, other: &Self) -> bool {
        let s = self.shared.as_ref().unwrap();
        let o = other.shared.as_ref().unwrap();
        Arc::ptr_eq(s, o)
    }
}

impl<T> Clone for MainThreadCell<T> {
    fn clone(&self) -> Self {
        MainThreadCell {
            shared: self.shared.clone(),
        }
    }
}

impl<T> MainThreadCell<T> {
    /// Creates a new MainThreadCell containing the given value.
    ///
    /// This must be called on the main thread. Constructing the cell elsewhere would
    /// allow a non-`Send` value (and any aliases it contains) to cross threads.
    ///
    /// # Panics
    ///
    /// Panics if called from a non-main thread.
    #[inline]
    pub fn new(t: T) -> Self {
        Self::verify_main_thread();
        // SAFETY: the value is being created on the only thread where it can be
        // accessed or destroyed.
        unsafe { Self::new_unchecked(t) }
    }

    /// Constructs a cell after the caller has established main-thread affinity.
    unsafe fn new_unchecked(t: T) -> Self {
        let cell = unsafe { UnsafeSendCell::new_unchecked(UnsafeSyncCell::new(t)) };
        MainThreadCell {
            shared: Some(Arc::new(Shared {
                inner: Some(cell),
                mutex: Mutex::new(()),
            })),
        }
    }

    /// Verifies that the current thread is the main thread.
    ///
    /// # Panics
    ///
    /// Panics if called from a non-main thread.
    #[inline]
    fn verify_main_thread() {
        assert!(
            application::is_main_thread(),
            "MainThreadCell accessed from non-main thread"
        );
    }

    /// Locks the cell and returns a guard providing mutable access to the inner value.
    ///
    /// This method can only be called from the main thread.
    ///
    /// # Panics
    ///
    /// Panics if called from a non-main thread.
    pub fn lock(&self) -> MainThreadGuard<'_, T> {
        Self::verify_main_thread();
        let guard = self.shared.as_ref().unwrap().mutex.lock().unwrap();
        let value = unsafe {
            let inner = self.shared.as_ref().unwrap().inner.as_ref().unwrap();
            inner.get().get_mut_unchecked()
        };
        MainThreadGuard {
            _guard: guard,
            value,
        }
    }

    /// Runs a closure with immutable access to the inner value.
    ///
    /// This method can only be called from the main thread.
    ///
    /// # Panics
    ///
    /// Panics if called from a non-main thread.
    pub fn assume<C, R>(&self, c: C) -> R
    where
        C: FnOnce(&T) -> R,
    {
        Self::verify_main_thread();
        let guard = self.shared.as_ref().unwrap().mutex.lock().unwrap();
        let r = c(unsafe {
            self.shared
                .as_ref()
                .unwrap()
                .inner
                .as_ref()
                .unwrap()
                .get()
                .get()
        });
        drop(guard);
        r
    }

    /// Runs a closure with the inner value, ensuring execution on the main thread.
    ///
    /// If called from the main thread, the closure executes immediately.
    /// If called from another thread, it's dispatched to the main thread.
    ///
    /// # Panics
    ///
    /// For the duration of this function, the cell may not be otherwise used.
    pub async fn with<C, R>(&self, c: C) -> R
    where
        C: FnOnce(&T) -> R + Send + 'static,
        R: Send + 'static,
        T: 'static,
    {
        let shared = self.shared.clone();
        let main_thread_cell = format!("MainThreadCell({})", std::any::type_name::<T>());
        application::on_main_thread(main_thread_cell, move || {
            Self::verify_main_thread();
            let guard = shared.as_ref().unwrap().mutex.lock().unwrap();
            let r = c(unsafe { shared.as_ref().unwrap().inner.as_ref().unwrap().get().get() });
            drop(guard);
            r
        })
        .await
    }

    /// Runs an async closure with the inner value, ensuring execution on the main thread.
    ///
    /// If called from the main thread, the closure executes immediately.
    /// If called from another thread, it's dispatched to the main thread.
    ///
    /// This method is more restrictive than `with_async` - it requires that access to the
    /// inner value doesn't cross async boundaries within the closure.
    ///
    /// # Panics
    ///
    /// For the duration of this function, the cell may not be otherwise used.
    pub async fn with_async<C, R, F>(&self, c: C) -> R
    where
        C: FnOnce(&T) -> F + Send + 'static,
        F: Future<Output = R> + Send + 'static,
        R: Send + 'static,
        T: 'static,
    {
        let shared = self.shared.clone();

        let main_thread_cell = format!("MainThreadCell({})", std::any::type_name::<T>());
        // First, get the future from the closure on the main thread
        let future = application::on_main_thread(main_thread_cell, move || {
            Self::verify_main_thread();
            let guard = shared.as_ref().unwrap().mutex.lock().unwrap();
            let future = c(unsafe { shared.as_ref().unwrap().inner.as_ref().unwrap().get().get() });
            drop(guard);
            future
        })
        .await;

        // Then await the future (this can happen on any thread since F: Send)
        future.await
    }

    /// Creates a new MainThreadCell by running a constructor closure on the main thread.
    ///
    /// This function ensures the value is created on the main thread, which is useful
    /// for resources that must be constructed there.
    pub async fn new_on_main_thread<C, F>(c: C) -> MainThreadCell<T>
    where
        C: FnOnce() -> F + Send + 'static,
        F: Future<Output = T> + Send + 'static,
    {
        logwise::info_sync!("MainThreadCell::new_on_main_thread() started");
        let new_on_main_thread = format!(
            "MainThreadCell::new_on_main_thread({})",
            std::any::type_name::<T>()
        );
        let value = crate::executor::on_main_thread_async(new_on_main_thread, async move {
            logwise::info_sync!("Inside main thread closure");
            let f = c();
            logwise::info_sync!("Calling provided closure f()...");
            let r = f.await;
            logwise::info_sync!("Closure completed, creating MainThreadCell...");
            MainThreadCell::new(r)
        })
        .await;
        logwise::info_sync!("Main thread execution completed, returning value");
        value
    }
}

// Safety: MainThreadCell ensures all access happens on the main thread
unsafe impl<T> Send for MainThreadCell<T> {}

impl<T: Debug> Debug for MainThreadCell<T> {
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        f.debug_struct("MainThreadCell").finish()
    }
}

impl<T: Default> Default for MainThreadCell<T> {
    fn default() -> Self {
        MainThreadCell::new(Default::default())
    }
}

impl<T> From<T> for MainThreadCell<T> {
    fn from(value: T) -> Self {
        MainThreadCell::new(value)
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    #[cfg(not(target_arch = "wasm32"))]
    use std::thread;
    #[cfg(target_arch = "wasm32")]
    use wasm_lite_std as thread;

    fn new_for_test<T>(value: T) -> MainThreadCell<T> {
        // Native libtest cases run on worker threads. These tests only inspect the
        // container and deliberately leak it, so no inner access crosses threads.
        unsafe { MainThreadCell::new_unchecked(value) }
    }

    #[cfg_attr(target_arch = "wasm32", wasm_lite::wasm_lite_test)]
    #[test]
    fn test_cell_construction() {
        // Verify we can construct cells
        let cell = new_for_test(42);
        let cell_from = new_for_test(42);
        let cell_default = new_for_test(i32::default());
        //these require drop on the main thread, so let's not!
        std::mem::forget(cell);
        std::mem::forget(cell_from);
        std::mem::forget(cell_default);
    }

    #[cfg_attr(target_arch = "wasm32", wasm_lite::wasm_lite_test)]
    #[test]
    fn test_debug_impl() {
        let cell = new_for_test(42);
        let debug_str = format!("{:?}", cell);
        assert!(debug_str.contains("MainThreadCell"));
        std::mem::forget(cell);
    }

    #[wasm_lite::wasm_lite_test]
    async fn construction_off_main_thread_panics() {
        let result = wasm_lite_std::spawn(|| MainThreadCell::new(42))
            .join_async()
            .await;
        assert!(result.is_err());
    }

    #[wasm_lite::wasm_lite_test]
    async fn test_send_across_threads() {
        //wasm_lite's runner always drives a real browser
        //see https://github.com/rustwasm/wasm-bindgen/issues/4534,
        //and threading comes from wasm_lite_std.
        let cell = new_for_test(42);
        let (c, f) = r#continue::continuation();

        // Verify we can send the cell to another thread
        thread::spawn(move || {
            // We can hold the cell in another thread, just not access it
            let held_cell = cell;
            c.send(());
            std::mem::forget(held_cell);
        });

        f.await;
    }
}