Skip to content

Commit 5b27c40

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

File tree

22 files changed

+591
-341
lines changed

22 files changed

+591
-341
lines changed

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: 253 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,253 @@
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) {
75+
Self::MAIN_THREAD.with(|is_main_thread| {
76+
if *is_main_thread.deref() {
77+
(self.handler)(&self.value.upgrade().unwrap(), event)
78+
} else {
79+
self.sender.send(event).unwrap()
80+
}
81+
})
82+
}
83+
84+
fn is_main_thread(&self) -> bool {
85+
Self::MAIN_THREAD.with(|is_main_thread| *is_main_thread.deref())
86+
}
87+
88+
pub fn with<R>(&self, f: impl FnOnce(Ref<'_, T>) -> R) -> Option<R> {
89+
Self::MAIN_THREAD.with(|is_main_thread| {
90+
if *is_main_thread.deref() {
91+
Some(f(self.value.upgrade().unwrap().borrow()))
92+
} else {
93+
None
94+
}
95+
})
96+
}
97+
98+
fn with_mut<R>(&self, f: impl FnOnce(RefMut<'_, T>) -> R) -> Option<R> {
99+
Self::MAIN_THREAD.with(|is_main_thread| {
100+
if *is_main_thread.deref() {
101+
Some(f(self.value.upgrade().unwrap().borrow_mut()))
102+
} else {
103+
None
104+
}
105+
})
106+
}
107+
}
108+
109+
impl<T, E> Clone for MainThreadSafe<T, E> {
110+
fn clone(&self) -> Self {
111+
Self {
112+
value: self.value.clone(),
113+
handler: self.handler,
114+
sender: self.sender.clone(),
115+
}
116+
}
117+
}
118+
119+
unsafe impl<T, E> Send for MainThreadSafe<T, E> {}
120+
unsafe impl<T, E> Sync for MainThreadSafe<T, E> {}
121+
122+
pub struct Dispatcher<T: 'static>(MainThreadSafe<T, Closure<T>>);
123+
124+
pub enum Closure<T> {
125+
Ref(Box<dyn FnOnce(&T) + Send>),
126+
RefMut(Box<dyn FnOnce(&mut T) + Send>),
127+
}
128+
129+
impl<T> Dispatcher<T> {
130+
#[track_caller]
131+
pub fn new(value: T) -> Option<Self> {
132+
MainThreadSafe::new(value, |value, closure| {
133+
match closure {
134+
Closure::Ref(f) => f(value.borrow().deref()),
135+
Closure::RefMut(f) => f(value.borrow_mut().deref_mut()),
136+
}
137+
138+
// An error was returned because the channel was closed, which
139+
// happens when the window get dropped, so we can stop now.
140+
})
141+
.map(Self)
142+
}
143+
144+
pub fn dispatch(&self, f: impl 'static + FnOnce(&T) + Send) {
145+
if self.is_main_thread() {
146+
self.0.with(|value| f(value.deref())).unwrap()
147+
} else {
148+
self.0.send(Closure::Ref(Box::new(f)))
149+
}
150+
}
151+
152+
pub fn dispatch_mut(&self, f: impl 'static + FnOnce(&mut T) + Send) {
153+
if self.is_main_thread() {
154+
self.0.with_mut(|mut value| f(value.deref_mut())).unwrap()
155+
} else {
156+
self.0.send(Closure::RefMut(Box::new(f)))
157+
}
158+
}
159+
160+
pub fn queue<R: 'static + Send>(&self, f: impl 'static + FnOnce(&T) -> R + Send) -> R {
161+
if self.is_main_thread() {
162+
self.0.with(|value| f(value.deref())).unwrap()
163+
} else {
164+
let pair = Arc::new((Mutex::new(None), Condvar::new()));
165+
let closure = Closure::Ref(Box::new({
166+
let pair = pair.clone();
167+
move |value| {
168+
*pair.0.lock().unwrap() = Some(f(value));
169+
pair.1.notify_one();
170+
}
171+
}));
172+
173+
self.0.send(closure);
174+
175+
let mut started = pair.0.lock().unwrap();
176+
177+
while started.is_none() {
178+
started = pair.1.wait(started).unwrap();
179+
}
180+
181+
started.take().unwrap()
182+
}
183+
}
184+
}
185+
186+
impl<T> Deref for Dispatcher<T> {
187+
type Target = MainThreadSafe<T, Closure<T>>;
188+
189+
fn deref(&self) -> &Self::Target {
190+
&self.0
191+
}
192+
}
193+
194+
fn channel<T: 'static>() -> (AsyncSender<T>, AsyncReceiver<T>) {
195+
let (sender, receiver) = mpsc::channel();
196+
let sender = Mutex::new(sender);
197+
let waker = Arc::new(AtomicWaker::new());
198+
199+
let sender = AsyncSender {
200+
sender,
201+
waker: Arc::clone(&waker),
202+
};
203+
let receiver = AsyncReceiver { receiver, waker };
204+
205+
(sender, receiver)
206+
}
207+
208+
struct AsyncSender<T: 'static> {
209+
sender: Mutex<Sender<T>>,
210+
waker: Arc<AtomicWaker>,
211+
}
212+
213+
impl<T: 'static> AsyncSender<T> {
214+
pub fn send(&self, event: T) -> Result<(), SendError<T>> {
215+
self.sender.lock().unwrap().send(event)?;
216+
self.waker.wake();
217+
218+
Ok(())
219+
}
220+
}
221+
222+
impl<T: 'static> Clone for AsyncSender<T> {
223+
fn clone(&self) -> Self {
224+
Self {
225+
sender: Mutex::new(self.sender.lock().unwrap().clone()),
226+
waker: self.waker.clone(),
227+
}
228+
}
229+
}
230+
231+
struct AsyncReceiver<T: 'static> {
232+
receiver: Receiver<T>,
233+
waker: Arc<AtomicWaker>,
234+
}
235+
236+
impl<T: 'static> AsyncReceiver<T> {
237+
pub async fn next(&self) -> Result<T, RecvError> {
238+
future::poll_fn(|cx| match self.receiver.try_recv() {
239+
Ok(event) => Poll::Ready(Ok(event)),
240+
Err(TryRecvError::Empty) => {
241+
self.waker.register(cx.waker());
242+
243+
match self.receiver.try_recv() {
244+
Ok(event) => Poll::Ready(Ok(event)),
245+
Err(TryRecvError::Empty) => Poll::Pending,
246+
Err(TryRecvError::Disconnected) => Poll::Ready(Err(RecvError)),
247+
}
248+
}
249+
Err(TryRecvError::Disconnected) => Poll::Ready(Err(RecvError)),
250+
})
251+
.await
252+
}
253+
}

0 commit comments

Comments
 (0)