Skip to content

Commit 3eb36d8

Browse files
committed
On Web, Window now implements Send + Sync
1 parent 7dcbbd2 commit 3eb36d8

22 files changed

Lines changed: 634 additions & 344 deletions

‎CHANGELOG.md‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,8 @@ And please only add new entries to the top of this list, right below the `# Unre
1111
- Bump MSRV from `1.60` to `1.64`.
1212
- Fix macOS memory leaks.
1313
- On Web: fix `Window::request_redraw` not waking the event loop when called from outside the loop.
14-
- On Web, `EventLoopProxy` now implements `Send`.
14+
- On Web, `EventLoopProxy` now implements `Send` and `Sync`.
15+
- On Web, `Window` now implements `Send` and `Sync`.
1516

1617
# 0.28.2
1718

‎Cargo.toml‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,8 @@ features = [
149149
]
150150

151151
[target.'cfg(target_arch = "wasm32")'.dependencies]
152+
atomic-waker = "1.1.0"
153+
js-sys = "0.3.22"
152154
wasm-bindgen = "0.2.45"
153155
wasm-bindgen-futures = "0.4.31"
154156

‎clippy.toml‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
disallowed-methods = [
2+
{ path = "web_sys::window", reason = "is not available in every context" },
3+
]

‎examples/web.rs‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
#![allow(clippy::single_match)]
1+
#![allow(clippy::disallowed_methods, clippy::single_match)]
22

33
use winit::{
44
event::{Event, WindowEvent},
@@ -52,7 +52,7 @@ mod wasm {
5252
pub fn insert_canvas_and_create_log_list(window: &Window) -> web_sys::Element {
5353
use winit::platform::web::WindowExtWebSys;
5454

55-
let canvas = window.canvas();
55+
let canvas = window.canvas().unwrap();
5656

5757
let window = web_sys::window().unwrap();
5858
let document = window.document().unwrap();

‎examples/web_aspect_ratio.rs‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
#![allow(clippy::disallowed_methods)]
2+
13
pub fn main() {
24
println!("This example must be run with cargo run-wasm --example web_aspect_ratio")
35
}
@@ -66,7 +68,7 @@ This example demonstrates the desired future functionality which will possibly b
6668
let body = document.body().unwrap();
6769

6870
// Set a background color for the canvas to make it easier to tell the where the canvas is for debugging purposes.
69-
let canvas = window.canvas();
71+
let canvas = window.canvas().unwrap();
7072
canvas
7173
.style()
7274
.set_css_text("display: block; background-color: crimson; margin: auto; width: 50%; aspect-ratio: 4 / 1;");

‎src/platform/web.rs‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,8 @@ use crate::window::WindowBuilder;
1212
use web_sys::HtmlCanvasElement;
1313

1414
pub trait WindowExtWebSys {
15-
fn canvas(&self) -> HtmlCanvasElement;
15+
/// Only returns the canvas if called from inside the window.
16+
fn canvas(&self) -> Option<HtmlCanvasElement>;
1617

1718
/// Whether the browser reports the preferred color scheme to be "dark".
1819
fn is_dark_mode(&self) -> bool;

‎src/platform_impl/web/async.rs‎

Lines changed: 266 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,266 @@
1+
use atomic_waker::AtomicWaker;
2+
use once_cell::unsync::Lazy;
3+
use std::cell::{Ref, RefCell, RefMut};
4+
use std::future;
5+
use std::mem::ManuallyDrop;
6+
use std::ops::{Deref, DerefMut};
7+
use std::rc::{Rc, Weak};
8+
use std::sync::mpsc::{self, Receiver, RecvError, SendError, Sender, TryRecvError};
9+
use std::sync::{Arc, Condvar, Mutex};
10+
use std::task::Poll;
11+
use wasm_bindgen::prelude::wasm_bindgen;
12+
use wasm_bindgen::{JsCast, JsValue};
13+
14+
// Unsafe wrapper type that allows us to use `T` when it's not `Send` from other threads.
15+
// `value` must *only* be accessed on the main thread.
16+
pub struct MainThreadSafe<T: 'static, E: 'static> {
17+
value: ManuallyDrop<Weak<RefCell<T>>>,
18+
handler: fn(&RefCell<T>, E),
19+
sender: AsyncSender<E>,
20+
}
21+
22+
impl<T, E> MainThreadSafe<T, E> {
23+
thread_local! {
24+
static MAIN_THREAD: Lazy<bool> = Lazy::new(|| {
25+
#[wasm_bindgen]
26+
extern "C" {
27+
#[derive(Clone)]
28+
pub(crate) type Global;
29+
30+
#[wasm_bindgen(method, getter, js_name = Window)]
31+
fn window(this: &Global) -> JsValue;
32+
}
33+
34+
let global: Global = js_sys::global().unchecked_into();
35+
!global.window().is_undefined()
36+
});
37+
}
38+
39+
#[track_caller]
40+
pub fn new(value: T, handler: fn(&RefCell<T>, E)) -> Option<Self> {
41+
Self::MAIN_THREAD.with(|safe| {
42+
if !*safe.deref() {
43+
panic!("only callable from inside the `Window`")
44+
}
45+
});
46+
47+
let value = Rc::new(RefCell::new(value));
48+
let weak = Rc::downgrade(&value);
49+
50+
let (sender, receiver) = channel::<E>();
51+
52+
wasm_bindgen_futures::spawn_local({
53+
async move {
54+
while let Ok(event) = receiver.next().await {
55+
handler(&value, event)
56+
}
57+
58+
// An error was returned because the channel was closed, which
59+
// happens when the window get dropped, so we can stop now.
60+
match Rc::try_unwrap(value) {
61+
Ok(value) => drop(value),
62+
Err(_) => panic!("couldn't enforce that value is dropped on the main thread"),
63+
}
64+
}
65+
});
66+
67+
Some(Self {
68+
value: ManuallyDrop::new(weak),
69+
handler,
70+
sender,
71+
})
72+
}
73+
74+
pub fn send(&self, event: E) -> Result<(), SendError<E>> {
75+
Self::MAIN_THREAD.with(|is_main_thread| {
76+
if *is_main_thread.deref() {
77+
(self.handler)(&self.value.upgrade().unwrap(), event);
78+
Ok(())
79+
} else {
80+
self.sender.send(event)
81+
}
82+
})
83+
}
84+
85+
fn is_main_thread(&self) -> bool {
86+
Self::MAIN_THREAD.with(|is_main_thread| *is_main_thread.deref())
87+
}
88+
89+
pub fn with<R>(&self, f: impl FnOnce(Ref<'_, T>) -> R) -> Option<R> {
90+
Self::MAIN_THREAD.with(|is_main_thread| {
91+
if *is_main_thread.deref() {
92+
Some(f(self.value.upgrade().unwrap().borrow()))
93+
} else {
94+
None
95+
}
96+
})
97+
}
98+
99+
fn with_mut<R>(&self, f: impl FnOnce(RefMut<'_, T>) -> R) -> Option<R> {
100+
Self::MAIN_THREAD.with(|is_main_thread| {
101+
if *is_main_thread.deref() {
102+
Some(f(self.value.upgrade().unwrap().borrow_mut()))
103+
} else {
104+
None
105+
}
106+
})
107+
}
108+
}
109+
110+
impl<T, E> Clone for MainThreadSafe<T, E> {
111+
fn clone(&self) -> Self {
112+
Self {
113+
value: self.value.clone(),
114+
handler: self.handler,
115+
sender: self.sender.clone(),
116+
}
117+
}
118+
}
119+
120+
unsafe impl<T, E> Send for MainThreadSafe<T, E> {}
121+
unsafe impl<T, E> Sync for MainThreadSafe<T, E> {}
122+
123+
pub struct Dispatcher<T: 'static>(MainThreadSafe<T, Closure<T>>);
124+
125+
pub enum Closure<T> {
126+
Ref(Box<dyn FnOnce(&T) + Send>),
127+
RefMut(Box<dyn FnOnce(&mut T) + Send>),
128+
}
129+
130+
impl<T> Dispatcher<T> {
131+
#[track_caller]
132+
pub fn new(value: T) -> Option<Self> {
133+
MainThreadSafe::new(value, |value, closure| {
134+
match closure {
135+
Closure::Ref(f) => f(value.borrow().deref()),
136+
Closure::RefMut(f) => f(value.borrow_mut().deref_mut()),
137+
}
138+
139+
// An error was returned because the channel was closed, which
140+
// happens when the window get dropped, so we can stop now.
141+
})
142+
.map(Self)
143+
}
144+
145+
pub fn dispatch(&self, f: impl 'static + FnOnce(&T) + Send) -> Result<(), SendError<()>> {
146+
if self.is_main_thread() {
147+
self.0.with(|value| f(value.deref())).unwrap();
148+
Ok(())
149+
} else {
150+
self.0
151+
.send(Closure::Ref(Box::new(f)))
152+
.map_err(|_| SendError(()))
153+
}
154+
}
155+
156+
pub fn dispatch_mut(
157+
&self,
158+
f: impl 'static + FnOnce(&mut T) + Send,
159+
) -> Result<(), SendError<()>> {
160+
if self.is_main_thread() {
161+
self.0.with_mut(|mut value| f(value.deref_mut())).unwrap();
162+
Ok(())
163+
} else {
164+
self.0
165+
.send(Closure::RefMut(Box::new(f)))
166+
.map_err(|_| SendError(()))
167+
}
168+
}
169+
170+
pub fn queue<R: 'static + Send>(
171+
&self,
172+
f: impl 'static + FnOnce(&T) -> R + Send,
173+
) -> Result<R, SendError<()>> {
174+
if self.is_main_thread() {
175+
Ok(self.0.with(|value| f(value.deref())).unwrap())
176+
} else {
177+
let pair = Arc::new((Mutex::new(None), Condvar::new()));
178+
let closure = Closure::Ref(Box::new({
179+
let pair = pair.clone();
180+
move |value| {
181+
*pair.0.lock().unwrap() = Some(f(value));
182+
pair.1.notify_one();
183+
}
184+
}));
185+
186+
self.0.send(closure).map_err(|_| SendError(()))?;
187+
188+
let mut started = pair.0.lock().unwrap();
189+
190+
while started.is_none() {
191+
started = pair.1.wait(started).unwrap();
192+
}
193+
194+
Ok(started.take().unwrap())
195+
}
196+
}
197+
}
198+
199+
impl<T> Deref for Dispatcher<T> {
200+
type Target = MainThreadSafe<T, Closure<T>>;
201+
202+
fn deref(&self) -> &Self::Target {
203+
&self.0
204+
}
205+
}
206+
207+
fn channel<T: 'static>() -> (AsyncSender<T>, AsyncReceiver<T>) {
208+
let (sender, receiver) = mpsc::channel();
209+
let sender = Mutex::new(sender);
210+
let waker = Arc::new(AtomicWaker::new());
211+
212+
let sender = AsyncSender {
213+
sender,
214+
waker: Arc::clone(&waker),
215+
};
216+
let receiver = AsyncReceiver { receiver, waker };
217+
218+
(sender, receiver)
219+
}
220+
221+
struct AsyncSender<T: 'static> {
222+
sender: Mutex<Sender<T>>,
223+
waker: Arc<AtomicWaker>,
224+
}
225+
226+
impl<T: 'static> AsyncSender<T> {
227+
pub fn send(&self, event: T) -> Result<(), SendError<T>> {
228+
self.sender.lock().unwrap().send(event)?;
229+
self.waker.wake();
230+
231+
Ok(())
232+
}
233+
}
234+
235+
impl<T: 'static> Clone for AsyncSender<T> {
236+
fn clone(&self) -> Self {
237+
Self {
238+
sender: Mutex::new(self.sender.lock().unwrap().clone()),
239+
waker: self.waker.clone(),
240+
}
241+
}
242+
}
243+
244+
struct AsyncReceiver<T: 'static> {
245+
receiver: Receiver<T>,
246+
waker: Arc<AtomicWaker>,
247+
}
248+
249+
impl<T: 'static> AsyncReceiver<T> {
250+
pub async fn next(&self) -> Result<T, RecvError> {
251+
future::poll_fn(|cx| match self.receiver.try_recv() {
252+
Ok(event) => Poll::Ready(Ok(event)),
253+
Err(TryRecvError::Empty) => {
254+
self.waker.register(cx.waker());
255+
256+
match self.receiver.try_recv() {
257+
Ok(event) => Poll::Ready(Ok(event)),
258+
Err(TryRecvError::Empty) => Poll::Pending,
259+
Err(TryRecvError::Disconnected) => Poll::Ready(Err(RecvError)),
260+
}
261+
}
262+
Err(TryRecvError::Disconnected) => Poll::Ready(Err(RecvError)),
263+
})
264+
.await
265+
}
266+
}

0 commit comments

Comments
 (0)