diff --git a/CHANGELOG.md b/CHANGELOG.md index 53725d521e..2470e1b5ba 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,8 @@ And please only add new entries to the top of this list, right below the `# Unre # Unreleased +- `EventLoopProxy` now implements `Sync` as well as `Send`. +- `EventLoopProxy` now implements `Send` on WebAssembly. - Build docs on `docs.rs` for iOS and Android as well. - **Breaking:** Removed the `WindowAttributes` struct, since all its functionality is accessible from `WindowBuilder`. - Added `WindowBuilder::transparent` getter to check if the user set `transparent` attribute. diff --git a/Cargo.toml b/Cargo.toml index 8709e748b6..581b220e24 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -136,8 +136,10 @@ features = [ 'WheelEvent' ] -[target.'cfg(target_arch = "wasm32")'.dependencies.wasm-bindgen] -version = "0.2.45" +[target.'cfg(target_arch = "wasm32")'.dependencies] +wasm-bindgen = "0.2.45" +wasm-bindgen-futures = "0.4.31" +async-channel = "1.6.1" [target.'cfg(target_arch = "wasm32")'.dev-dependencies] console_log = "0.2" diff --git a/src/platform_impl/ios/event_loop.rs b/src/platform_impl/ios/event_loop.rs index 1a32fe56b2..8b4bfa7f8c 100644 --- a/src/platform_impl/ios/event_loop.rs +++ b/src/platform_impl/ios/event_loop.rs @@ -4,7 +4,7 @@ use std::{ fmt::{self, Debug}, marker::PhantomData, mem, ptr, - sync::mpsc::{self, Receiver, Sender}, + sync::mpsc::{self, Receiver, SyncSender}, }; use crate::{ @@ -48,7 +48,7 @@ pub enum EventProxy { pub struct EventLoopWindowTarget { receiver: Receiver, - sender_to_clone: Sender, + sender_to_clone: SyncSender, } impl EventLoopWindowTarget { @@ -86,7 +86,7 @@ impl EventLoop { view::create_delegate_class(); } - let (sender_to_clone, receiver) = mpsc::channel(); + let (sender_to_clone, receiver) = mpsc::sync_channel(10); // this line sets up the main run loop before `UIApplicationMain` setup_control_flow_observers(); @@ -148,11 +148,17 @@ impl EventLoop { } pub struct EventLoopProxy { - sender: Sender, + sender: SyncSender, source: CFRunLoopSourceRef, } unsafe impl Send for EventLoopProxy {} +// Looking at the source code for `CFRunLoopSourceSignal` (https://github.com/opensource-apple/CF/blob/3cc41a76b1491f50813e28a4ec09954ffa359e6f/CFRunLoop.c#L3418-L3425), +// it locks the source before doing anything, so I think it should be fine to +// use from behind a reference. +// +// `SyncSender` is already `Sync`, so that's not an issue. +unsafe impl Sync for EventLoopProxy {} impl Clone for EventLoopProxy { fn clone(&self) -> EventLoopProxy { @@ -170,7 +176,7 @@ impl Drop for EventLoopProxy { } impl EventLoopProxy { - fn new(sender: Sender) -> EventLoopProxy { + fn new(sender: SyncSender) -> EventLoopProxy { unsafe { // just wake up the eventloop extern "C" fn event_loop_proxy_handler(_: *mut c_void) {} diff --git a/src/platform_impl/linux/wayland/event_loop/mod.rs b/src/platform_impl/linux/wayland/event_loop/mod.rs index 0f879ea2d9..87f6593465 100644 --- a/src/platform_impl/linux/wayland/event_loop/mod.rs +++ b/src/platform_impl/linux/wayland/event_loop/mod.rs @@ -82,7 +82,7 @@ pub struct EventLoop { pending_user_events: Rc>>, /// Sender of user events. - user_events_sender: calloop::channel::Sender, + user_events_sender: calloop::channel::SyncSender, /// Dispatcher of Wayland events. pub wayland_dispatcher: WinitDispatcher, @@ -139,7 +139,7 @@ impl EventLoop { // A source of user events. let pending_user_events = Rc::new(RefCell::new(Vec::new())); let pending_user_events_clone = pending_user_events.clone(); - let (user_events_sender, user_events_channel) = calloop::channel::channel(); + let (user_events_sender, user_events_channel) = calloop::channel::sync_channel(10); // User events channel. event_loop diff --git a/src/platform_impl/linux/wayland/event_loop/proxy.rs b/src/platform_impl/linux/wayland/event_loop/proxy.rs index dad64ef2c9..53808a343b 100644 --- a/src/platform_impl/linux/wayland/event_loop/proxy.rs +++ b/src/platform_impl/linux/wayland/event_loop/proxy.rs @@ -2,13 +2,13 @@ use std::sync::mpsc::SendError; -use sctk::reexports::calloop::channel::Sender; +use sctk::reexports::calloop::channel::SyncSender; use crate::event_loop::EventLoopClosed; /// A handle that can be sent across the threads and used to wake up the `EventLoop`. pub struct EventLoopProxy { - user_events_sender: Sender, + user_events_sender: SyncSender, } impl Clone for EventLoopProxy { @@ -20,7 +20,7 @@ impl Clone for EventLoopProxy { } impl EventLoopProxy { - pub fn new(user_events_sender: Sender) -> Self { + pub fn new(user_events_sender: SyncSender) -> Self { Self { user_events_sender } } diff --git a/src/platform_impl/linux/x11/mod.rs b/src/platform_impl/linux/x11/mod.rs index 9789f03625..2c7f6efaea 100644 --- a/src/platform_impl/linux/x11/mod.rs +++ b/src/platform_impl/linux/x11/mod.rs @@ -32,7 +32,7 @@ use std::{ ptr, rc::Rc, slice, - sync::mpsc::{Receiver, Sender, TryRecvError}, + sync::mpsc::{Receiver, Sender, SyncSender, TryRecvError}, sync::{mpsc, Arc, Weak}, time::{Duration, Instant}, }; @@ -118,12 +118,12 @@ pub struct EventLoop { event_processor: EventProcessor, redraw_receiver: PeekableReceiver, user_receiver: PeekableReceiver, //waker.wake needs to be called whenever something gets sent - user_sender: Sender, + user_sender: SyncSender, target: Rc>, } pub struct EventLoopProxy { - user_sender: Sender, + user_sender: SyncSender, waker: Arc, } @@ -230,7 +230,7 @@ impl EventLoop { .register(&mut SourceFd(&xconn.x11_fd), X_TOKEN, Interest::READABLE) .unwrap(); - let (user_sender, user_channel) = std::sync::mpsc::channel(); + let (user_sender, user_channel) = std::sync::mpsc::sync_channel(10); let (redraw_sender, redraw_channel) = std::sync::mpsc::channel(); let window_target = EventLoopWindowTarget { diff --git a/src/platform_impl/macos/event_loop.rs b/src/platform_impl/macos/event_loop.rs index c39036997d..a3145de0e7 100644 --- a/src/platform_impl/macos/event_loop.rs +++ b/src/platform_impl/macos/event_loop.rs @@ -65,13 +65,13 @@ impl PanicInfo { } pub struct EventLoopWindowTarget { - pub sender: mpsc::Sender, // this is only here to be cloned elsewhere + pub sender: mpsc::SyncSender, // this is only here to be cloned elsewhere pub receiver: mpsc::Receiver, } impl Default for EventLoopWindowTarget { fn default() -> Self { - let (sender, receiver) = mpsc::channel(); + let (sender, receiver) = mpsc::sync_channel(10); EventLoopWindowTarget { sender, receiver } } } @@ -282,11 +282,17 @@ pub fn stop_app_on_panic R + UnwindSafe, R>( } pub struct EventLoopProxy { - sender: mpsc::Sender, + sender: mpsc::SyncSender, source: CFRunLoopSourceRef, } unsafe impl Send for EventLoopProxy {} +// Looking at the source code for `CFRunLoopSourceSignal` (https://github.com/opensource-apple/CF/blob/3cc41a76b1491f50813e28a4ec09954ffa359e6f/CFRunLoop.c#L3418-L3425), +// it locks the source before doing anything, so I think it should be fine to +// use from behind a reference. +// +// `SyncSender` is already `Sync`, so that's not an issue. +unsafe impl Sync for EventLoopProxy {} impl Drop for EventLoopProxy { fn drop(&mut self) { @@ -303,7 +309,7 @@ impl Clone for EventLoopProxy { } impl EventLoopProxy { - fn new(sender: mpsc::Sender) -> Self { + fn new(sender: mpsc::SyncSender) -> Self { unsafe { // just wake up the eventloop extern "C" fn event_loop_proxy_handler(_: *mut c_void) {} diff --git a/src/platform_impl/web/event_loop/proxy.rs b/src/platform_impl/web/event_loop/proxy.rs index 1c70992317..19194bd4ba 100644 --- a/src/platform_impl/web/event_loop/proxy.rs +++ b/src/platform_impl/web/event_loop/proxy.rs @@ -1,26 +1,27 @@ -use super::runner; -use crate::event::Event; +use async_channel::{Sender, TrySendError}; + use crate::event_loop::EventLoopClosed; pub struct EventLoopProxy { - runner: runner::Shared, + pub sender: Sender, } impl EventLoopProxy { - pub fn new(runner: runner::Shared) -> Self { - Self { runner } - } - pub fn send_event(&self, event: T) -> Result<(), EventLoopClosed> { - self.runner.send_event(Event::UserEvent(event)); - Ok(()) + match self.sender.try_send(event) { + Ok(()) => Ok(()), + Err(TrySendError::Closed(val)) => Err(EventLoopClosed(val)), + // Note: `async-channel` has no way to block on sending something, + // so this is our only option for making this synchronous. + Err(TrySendError::Full(_)) => unreachable!("`EventLoopProxy` channels are unbounded"), + } } } impl Clone for EventLoopProxy { fn clone(&self) -> Self { Self { - runner: self.runner.clone(), + sender: self.sender.clone(), } } } diff --git a/src/platform_impl/web/event_loop/runner.rs b/src/platform_impl/web/event_loop/runner.rs index 840ba558e6..2541472d3d 100644 --- a/src/platform_impl/web/event_loop/runner.rs +++ b/src/platform_impl/web/event_loop/runner.rs @@ -1,8 +1,10 @@ +use super::EventLoopProxy; use super::{super::ScaleChangeArgs, backend, state::State}; use crate::event::{Event, StartCause}; use crate::event_loop::ControlFlow; use crate::window::WindowId; +use async_channel::Sender; use instant::{Duration, Instant}; use std::{ cell::RefCell, @@ -30,6 +32,9 @@ pub struct Execution { destroy_pending: RefCell>, scale_change_detector: RefCell>, unload_event_handle: RefCell>, + /// A channel through which user events can be sent, to be cloned and put + /// into `EventLoopProxy`s. + proxy_sender: Sender, } enum RunnerEnum { @@ -97,7 +102,9 @@ impl Runner { impl Shared { pub fn new() -> Self { - Shared(Rc::new(Execution { + let (proxy_sender, proxy_receiver) = async_channel::unbounded(); + + let this = Shared(Rc::new(Execution { runner: RefCell::new(RunnerEnum::Pending), events: RefCell::new(VecDeque::new()), id: RefCell::new(0), @@ -106,7 +113,22 @@ impl Shared { destroy_pending: RefCell::new(VecDeque::new()), scale_change_detector: RefCell::new(None), unload_event_handle: RefCell::new(None), - })) + proxy_sender, + })); + + { + let runner = this.clone(); + wasm_bindgen_futures::spawn_local(async move { + while let Ok(value) = proxy_receiver.recv().await { + runner.send_event(Event::UserEvent(value)) + } + + // An error was returned because the channel was closed, which + // happens when the event loop gets closed, so we can stop now. + }) + } + + this } pub fn add_canvas(&self, id: WindowId, canvas: &Rc>) { @@ -153,6 +175,12 @@ impl Shared { *id } + pub fn create_proxy(&self) -> EventLoopProxy { + EventLoopProxy { + sender: self.0.proxy_sender.clone(), + } + } + pub fn request_redraw(&self, id: WindowId) { self.0.redraw_pending.borrow_mut().insert(id); } @@ -463,6 +491,10 @@ impl Shared { fn handle_loop_destroyed(&self, control: &mut ControlFlow) { self.handle_event(Event::LoopDestroyed, control); + // Close the `EventLoopProxy` channel, causing any more calls to + // `EventLoopProxy::send_event` to fail and indirectly stopping the receiver + // task. + self.0.proxy_sender.close(); let all_canvases = std::mem::take(&mut *self.0.all_canvases.borrow_mut()); *self.0.scale_change_detector.borrow_mut() = None; *self.0.unload_event_handle.borrow_mut() = None; diff --git a/src/platform_impl/web/event_loop/window_target.rs b/src/platform_impl/web/event_loop/window_target.rs index 0ed7c37ace..0b804459e7 100644 --- a/src/platform_impl/web/event_loop/window_target.rs +++ b/src/platform_impl/web/event_loop/window_target.rs @@ -35,7 +35,7 @@ impl EventLoopWindowTarget { } pub fn proxy(&self) -> EventLoopProxy { - EventLoopProxy::new(self.runner.clone()) + self.runner.create_proxy() } pub fn run(&self, event_handler: Box, &mut ControlFlow)>) { diff --git a/src/platform_impl/windows/event_loop.rs b/src/platform_impl/windows/event_loop.rs index 7e4b7fbdb8..29de2648ce 100644 --- a/src/platform_impl/windows/event_loop.rs +++ b/src/platform_impl/windows/event_loop.rs @@ -12,7 +12,7 @@ use std::{ mem, panic, ptr, rc::Rc, sync::{ - mpsc::{self, Receiver, Sender}, + mpsc::{self, Receiver, SyncSender}, Arc, }, thread, @@ -150,7 +150,7 @@ impl ThreadMsgTargetData { } pub struct EventLoop { - thread_msg_sender: Sender, + thread_msg_sender: SyncSender, window_target: RootELW, msg_hook: Option bool + 'static>>, } @@ -552,7 +552,7 @@ type ThreadExecFn = Box>; pub struct EventLoopProxy { target_window: HWND, - event_send: Sender, + event_send: SyncSender, } unsafe impl Send for EventLoopProxy {} @@ -672,8 +672,8 @@ fn create_event_target_window() -> HWND { fn insert_event_target_window_data( thread_msg_target: HWND, event_loop_runner: EventLoopRunnerShared, -) -> Sender { - let (tx, rx) = mpsc::channel(); +) -> SyncSender { + let (tx, rx) = mpsc::sync_channel(10); let userdata = ThreadMsgTargetData { event_loop_runner, diff --git a/tests/send_objects.rs b/tests/send_objects.rs index 252b271a4b..da154a65b0 100644 --- a/tests/send_objects.rs +++ b/tests/send_objects.rs @@ -1,7 +1,6 @@ #[allow(dead_code)] fn needs_send() {} -#[cfg(not(target_arch = "wasm32"))] #[test] fn event_loop_proxy_send() { #[allow(dead_code)] diff --git a/tests/sync_object.rs b/tests/sync_object.rs index 56524cb2c4..839f6e74b9 100644 --- a/tests/sync_object.rs +++ b/tests/sync_object.rs @@ -1,6 +1,15 @@ #[allow(dead_code)] fn needs_sync() {} +#[test] +fn event_loop_proxy_sync() { + #[allow(dead_code)] + fn is_send() { + // ensures that `winit::EventLoopProxy` implements `Sync` + needs_sync::>(); + } +} + #[cfg(not(target_arch = "wasm32"))] #[test] fn window_sync() {