Make canvas in WindowBuilder safe (#3320)
This commit is contained in:
parent
843d7904d6
commit
e0fea25b06
10 changed files with 214 additions and 169 deletions
|
|
@ -34,7 +34,6 @@ use crate::event_loop::EventLoopWindowTarget;
|
||||||
use crate::platform_impl::PlatformCustomCursorBuilder;
|
use crate::platform_impl::PlatformCustomCursorBuilder;
|
||||||
use crate::window::CustomCursor;
|
use crate::window::CustomCursor;
|
||||||
use crate::window::{Window, WindowBuilder};
|
use crate::window::{Window, WindowBuilder};
|
||||||
use crate::SendSyncWrapper;
|
|
||||||
|
|
||||||
use web_sys::HtmlCanvasElement;
|
use web_sys::HtmlCanvasElement;
|
||||||
|
|
||||||
|
|
@ -105,7 +104,7 @@ pub trait WindowBuilderExtWebSys {
|
||||||
|
|
||||||
impl WindowBuilderExtWebSys for WindowBuilder {
|
impl WindowBuilderExtWebSys for WindowBuilder {
|
||||||
fn with_canvas(mut self, canvas: Option<HtmlCanvasElement>) -> Self {
|
fn with_canvas(mut self, canvas: Option<HtmlCanvasElement>) -> Self {
|
||||||
self.platform_specific.canvas = SendSyncWrapper(canvas);
|
self.platform_specific.set_canvas(canvas);
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
use super::super::main_thread::MainThreadMarker;
|
||||||
use super::{channel, AsyncReceiver, AsyncSender, Wrapper};
|
use super::{channel, AsyncReceiver, AsyncSender, Wrapper};
|
||||||
use std::{
|
use std::{
|
||||||
cell::Ref,
|
cell::Ref,
|
||||||
|
|
@ -10,10 +11,11 @@ struct Closure<T>(Box<dyn FnOnce(&T) + Send>);
|
||||||
|
|
||||||
impl<T> Dispatcher<T> {
|
impl<T> Dispatcher<T> {
|
||||||
#[track_caller]
|
#[track_caller]
|
||||||
pub fn new(value: T) -> Option<(Self, DispatchRunner<T>)> {
|
pub fn new(main_thread: MainThreadMarker, value: T) -> Option<(Self, DispatchRunner<T>)> {
|
||||||
let (sender, receiver) = channel::<Closure<T>>();
|
let (sender, receiver) = channel::<Closure<T>>();
|
||||||
|
|
||||||
Wrapper::new(
|
Wrapper::new(
|
||||||
|
main_thread,
|
||||||
value,
|
value,
|
||||||
|value, Closure(closure)| {
|
|value, Closure(closure)| {
|
||||||
// SAFETY: The given `Closure` here isn't really `'static`, so we shouldn't do anything
|
// SAFETY: The given `Closure` here isn't really `'static`, so we shouldn't do anything
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
use super::super::main_thread::MainThreadMarker;
|
||||||
use super::Wrapper;
|
use super::Wrapper;
|
||||||
use atomic_waker::AtomicWaker;
|
use atomic_waker::AtomicWaker;
|
||||||
use std::future;
|
use std::future;
|
||||||
|
|
@ -19,7 +20,7 @@ struct Sender(Arc<Inner>);
|
||||||
|
|
||||||
impl<T> WakerSpawner<T> {
|
impl<T> WakerSpawner<T> {
|
||||||
#[track_caller]
|
#[track_caller]
|
||||||
pub fn new(value: T, handler: fn(&T, usize)) -> Option<Self> {
|
pub fn new(main_thread: MainThreadMarker, value: T, handler: fn(&T, usize)) -> Option<Self> {
|
||||||
let inner = Arc::new(Inner {
|
let inner = Arc::new(Inner {
|
||||||
counter: AtomicUsize::new(0),
|
counter: AtomicUsize::new(0),
|
||||||
waker: AtomicWaker::new(),
|
waker: AtomicWaker::new(),
|
||||||
|
|
@ -31,6 +32,7 @@ impl<T> WakerSpawner<T> {
|
||||||
let sender = Sender(Arc::clone(&inner));
|
let sender = Sender(Arc::clone(&inner));
|
||||||
|
|
||||||
let wrapper = Wrapper::new(
|
let wrapper = Wrapper::new(
|
||||||
|
main_thread,
|
||||||
handler,
|
handler,
|
||||||
|handler, count| {
|
|handler, count| {
|
||||||
let handler = handler.borrow();
|
let handler = handler.borrow();
|
||||||
|
|
@ -86,7 +88,7 @@ impl<T> WakerSpawner<T> {
|
||||||
|
|
||||||
pub fn fetch(&self) -> usize {
|
pub fn fetch(&self) -> usize {
|
||||||
debug_assert!(
|
debug_assert!(
|
||||||
self.0.is_main_thread(),
|
MainThreadMarker::new().is_some(),
|
||||||
"this should only be called from the main thread"
|
"this should only be called from the main thread"
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,8 @@
|
||||||
|
use super::super::main_thread::MainThreadMarker;
|
||||||
use std::cell::{Ref, RefCell};
|
use std::cell::{Ref, RefCell};
|
||||||
use std::future::Future;
|
use std::future::Future;
|
||||||
use std::marker::PhantomData;
|
use std::marker::PhantomData;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use wasm_bindgen::prelude::wasm_bindgen;
|
|
||||||
use wasm_bindgen::{JsCast, JsValue};
|
|
||||||
|
|
||||||
// Unsafe wrapper type that allows us to use `T` when it's not `Send` from other threads.
|
// Unsafe wrapper type that allows us to use `T` when it's not `Send` from other threads.
|
||||||
// `value` **must** only be accessed on the main thread.
|
// `value` **must** only be accessed on the main thread.
|
||||||
|
|
@ -34,36 +33,15 @@ unsafe impl<const SYNC: bool, V> Send for Value<SYNC, V> {}
|
||||||
unsafe impl<V> Sync for Value<true, V> {}
|
unsafe impl<V> Sync for Value<true, V> {}
|
||||||
|
|
||||||
impl<const SYNC: bool, V, S: Clone + Send, E> Wrapper<SYNC, V, S, E> {
|
impl<const SYNC: bool, V, S: Clone + Send, E> Wrapper<SYNC, V, S, E> {
|
||||||
thread_local! {
|
|
||||||
static MAIN_THREAD: bool = {
|
|
||||||
#[wasm_bindgen]
|
|
||||||
extern "C" {
|
|
||||||
#[derive(Clone)]
|
|
||||||
type Global;
|
|
||||||
|
|
||||||
#[wasm_bindgen(method, getter, js_name = Window)]
|
|
||||||
fn window(this: &Global) -> JsValue;
|
|
||||||
}
|
|
||||||
|
|
||||||
let global: Global = js_sys::global().unchecked_into();
|
|
||||||
!global.window().is_undefined()
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
#[track_caller]
|
#[track_caller]
|
||||||
pub fn new<R: Future<Output = ()>>(
|
pub fn new<R: Future<Output = ()>>(
|
||||||
|
_: MainThreadMarker,
|
||||||
value: V,
|
value: V,
|
||||||
handler: fn(&RefCell<Option<V>>, E),
|
handler: fn(&RefCell<Option<V>>, E),
|
||||||
receiver: impl 'static + FnOnce(Arc<RefCell<Option<V>>>) -> R,
|
receiver: impl 'static + FnOnce(Arc<RefCell<Option<V>>>) -> R,
|
||||||
sender_data: S,
|
sender_data: S,
|
||||||
sender_handler: fn(&S, E),
|
sender_handler: fn(&S, E),
|
||||||
) -> Option<Self> {
|
) -> Option<Self> {
|
||||||
Self::MAIN_THREAD.with(|safe| {
|
|
||||||
if !safe {
|
|
||||||
panic!("only callable from inside the `Window`")
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
let value = Arc::new(RefCell::new(Some(value)));
|
let value = Arc::new(RefCell::new(Some(value)));
|
||||||
|
|
||||||
wasm_bindgen_futures::spawn_local({
|
wasm_bindgen_futures::spawn_local({
|
||||||
|
|
@ -86,29 +64,16 @@ impl<const SYNC: bool, V, S: Clone + Send, E> Wrapper<SYNC, V, S, E> {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn send(&self, event: E) {
|
pub fn send(&self, event: E) {
|
||||||
Self::MAIN_THREAD.with(|is_main_thread| {
|
if MainThreadMarker::new().is_some() {
|
||||||
if *is_main_thread {
|
(self.handler)(&self.value.value, event)
|
||||||
(self.handler)(&self.value.value, event)
|
} else {
|
||||||
} else {
|
(self.sender_handler)(&self.sender_data, event)
|
||||||
(self.sender_handler)(&self.sender_data, event)
|
}
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn is_main_thread(&self) -> bool {
|
|
||||||
Self::MAIN_THREAD.with(|is_main_thread| *is_main_thread)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn value(&self) -> Option<Ref<'_, V>> {
|
pub fn value(&self) -> Option<Ref<'_, V>> {
|
||||||
Self::MAIN_THREAD.with(|is_main_thread| {
|
MainThreadMarker::new()
|
||||||
if *is_main_thread {
|
.map(|_| Ref::map(self.value.value.borrow(), |value| value.as_ref().unwrap()))
|
||||||
Some(Ref::map(self.value.value.borrow(), |value| {
|
|
||||||
value.as_ref().unwrap()
|
|
||||||
}))
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn with_sender_data<T>(&self, f: impl FnOnce(&S) -> T) -> T {
|
pub fn with_sender_data<T>(&self, f: impl FnOnce(&S) -> T) -> T {
|
||||||
|
|
|
||||||
|
|
@ -9,12 +9,8 @@ use std::{
|
||||||
task::{Poll, Waker},
|
task::{Poll, Waker},
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::{
|
use crate::cursor::{BadImage, Cursor, CursorImage};
|
||||||
cursor::{BadImage, Cursor, CursorImage},
|
|
||||||
platform_impl::platform::r#async,
|
|
||||||
};
|
|
||||||
use cursor_icon::CursorIcon;
|
use cursor_icon::CursorIcon;
|
||||||
use once_cell::sync::Lazy;
|
|
||||||
use wasm_bindgen::{closure::Closure, JsCast};
|
use wasm_bindgen::{closure::Closure, JsCast};
|
||||||
use wasm_bindgen_futures::JsFuture;
|
use wasm_bindgen_futures::JsFuture;
|
||||||
use web_sys::{
|
use web_sys::{
|
||||||
|
|
@ -22,9 +18,9 @@ use web_sys::{
|
||||||
ImageBitmapRenderingContext, ImageData, PremultiplyAlpha, Url, Window,
|
ImageBitmapRenderingContext, ImageData, PremultiplyAlpha, Url, Window,
|
||||||
};
|
};
|
||||||
|
|
||||||
use self::thread_safe::ThreadSafe;
|
use super::backend::Style;
|
||||||
|
use super::main_thread::{MainThreadMarker, MainThreadSafe};
|
||||||
use super::{backend::Style, r#async::AsyncSender, EventLoopWindowTarget};
|
use super::EventLoopWindowTarget;
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub(crate) enum CustomCursorBuilder {
|
pub(crate) enum CustomCursorBuilder {
|
||||||
|
|
@ -51,7 +47,7 @@ impl CustomCursorBuilder {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct CustomCursor(Arc<Inner>);
|
pub struct CustomCursor(Arc<MainThreadSafe<RefCell<ImageState>>>);
|
||||||
|
|
||||||
impl Hash for CustomCursor {
|
impl Hash for CustomCursor {
|
||||||
fn hash<H: Hasher>(&self, state: &mut H) {
|
fn hash<H: Hasher>(&self, state: &mut H) {
|
||||||
|
|
@ -68,14 +64,22 @@ impl PartialEq for CustomCursor {
|
||||||
impl Eq for CustomCursor {}
|
impl Eq for CustomCursor {}
|
||||||
|
|
||||||
impl CustomCursor {
|
impl CustomCursor {
|
||||||
|
fn new(main_thread: MainThreadMarker) -> Self {
|
||||||
|
Self(Arc::new(MainThreadSafe::new(
|
||||||
|
main_thread,
|
||||||
|
RefCell::new(ImageState::Loading(None)),
|
||||||
|
)))
|
||||||
|
}
|
||||||
|
|
||||||
pub(crate) fn build<T>(
|
pub(crate) fn build<T>(
|
||||||
builder: CustomCursorBuilder,
|
builder: CustomCursorBuilder,
|
||||||
window_target: &EventLoopWindowTarget<T>,
|
window_target: &EventLoopWindowTarget<T>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
Lazy::force(&DROP_HANDLER);
|
let main_thread = window_target.runner.main_thread();
|
||||||
|
|
||||||
Self(match builder {
|
match builder {
|
||||||
CustomCursorBuilder::Image(image) => ImageState::from_rgba(
|
CustomCursorBuilder::Image(image) => ImageState::from_rgba(
|
||||||
|
main_thread,
|
||||||
window_target.runner.window(),
|
window_target.runner.window(),
|
||||||
window_target.runner.document().clone(),
|
window_target.runner.document().clone(),
|
||||||
&image,
|
&image,
|
||||||
|
|
@ -84,47 +88,7 @@ impl CustomCursor {
|
||||||
url,
|
url,
|
||||||
hotspot_x,
|
hotspot_x,
|
||||||
hotspot_y,
|
hotspot_y,
|
||||||
} => ImageState::from_url(url, hotspot_x, hotspot_y),
|
} => ImageState::from_url(main_thread, url, hotspot_x, hotspot_y),
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
struct Inner(Option<ThreadSafe<RefCell<ImageState>>>);
|
|
||||||
|
|
||||||
static DROP_HANDLER: Lazy<AsyncSender<ThreadSafe<RefCell<ImageState>>>> = Lazy::new(|| {
|
|
||||||
let (sender, receiver) = r#async::channel();
|
|
||||||
wasm_bindgen_futures::spawn_local(async move { while receiver.next().await.is_ok() {} });
|
|
||||||
|
|
||||||
sender
|
|
||||||
});
|
|
||||||
|
|
||||||
impl Inner {
|
|
||||||
fn new() -> Arc<Self> {
|
|
||||||
Arc::new(Inner(Some(ThreadSafe::new(RefCell::new(
|
|
||||||
ImageState::Loading(None),
|
|
||||||
)))))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get(&self) -> &RefCell<ImageState> {
|
|
||||||
self.0
|
|
||||||
.as_ref()
|
|
||||||
.expect("value has accidently already been dropped")
|
|
||||||
.get()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Drop for Inner {
|
|
||||||
fn drop(&mut self) {
|
|
||||||
let value = self
|
|
||||||
.0
|
|
||||||
.take()
|
|
||||||
.expect("value has accidently already been dropped");
|
|
||||||
|
|
||||||
if !value.in_origin_thread() {
|
|
||||||
DROP_HANDLER
|
|
||||||
.send(value)
|
|
||||||
.expect("sender dropped in main thread")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -133,8 +97,9 @@ impl Drop for Inner {
|
||||||
pub struct CursorState(Rc<RefCell<State>>);
|
pub struct CursorState(Rc<RefCell<State>>);
|
||||||
|
|
||||||
impl CursorState {
|
impl CursorState {
|
||||||
pub fn new(style: Style) -> Self {
|
pub fn new(main_thread: MainThreadMarker, style: Style) -> Self {
|
||||||
Self(Rc::new(RefCell::new(State {
|
Self(Rc::new(RefCell::new(State {
|
||||||
|
main_thread,
|
||||||
style,
|
style,
|
||||||
visible: true,
|
visible: true,
|
||||||
cursor: SelectedCursor::default(),
|
cursor: SelectedCursor::default(),
|
||||||
|
|
@ -147,7 +112,9 @@ impl CursorState {
|
||||||
match cursor {
|
match cursor {
|
||||||
Cursor::Icon(icon) => {
|
Cursor::Icon(icon) => {
|
||||||
if let SelectedCursor::ImageLoading { state, .. } = &this.cursor {
|
if let SelectedCursor::ImageLoading { state, .. } = &this.cursor {
|
||||||
if let ImageState::Loading(state) = state.get().borrow_mut().deref_mut() {
|
if let ImageState::Loading(state) =
|
||||||
|
state.0.get(this.main_thread).borrow_mut().deref_mut()
|
||||||
|
{
|
||||||
state.take();
|
state.take();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -155,10 +122,16 @@ impl CursorState {
|
||||||
this.cursor = SelectedCursor::Named(icon);
|
this.cursor = SelectedCursor::Named(icon);
|
||||||
this.set_style();
|
this.set_style();
|
||||||
}
|
}
|
||||||
Cursor::Custom(cursor) => match cursor.inner.0.get().borrow_mut().deref_mut() {
|
Cursor::Custom(cursor) => match cursor
|
||||||
|
.inner
|
||||||
|
.0
|
||||||
|
.get(this.main_thread)
|
||||||
|
.borrow_mut()
|
||||||
|
.deref_mut()
|
||||||
|
{
|
||||||
ImageState::Loading(state) => {
|
ImageState::Loading(state) => {
|
||||||
this.cursor = SelectedCursor::ImageLoading {
|
this.cursor = SelectedCursor::ImageLoading {
|
||||||
state: cursor.inner.0.clone(),
|
state: cursor.inner.clone(),
|
||||||
previous: mem::take(&mut this.cursor).into(),
|
previous: mem::take(&mut this.cursor).into(),
|
||||||
};
|
};
|
||||||
*state = Some(Rc::downgrade(&self.0));
|
*state = Some(Rc::downgrade(&self.0));
|
||||||
|
|
@ -187,6 +160,7 @@ impl CursorState {
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
struct State {
|
struct State {
|
||||||
|
main_thread: MainThreadMarker,
|
||||||
style: Style,
|
style: Style,
|
||||||
visible: bool,
|
visible: bool,
|
||||||
cursor: SelectedCursor,
|
cursor: SelectedCursor,
|
||||||
|
|
@ -210,7 +184,7 @@ impl State {
|
||||||
enum SelectedCursor {
|
enum SelectedCursor {
|
||||||
Named(CursorIcon),
|
Named(CursorIcon),
|
||||||
ImageLoading {
|
ImageLoading {
|
||||||
state: Arc<Inner>,
|
state: CustomCursor,
|
||||||
previous: Previous,
|
previous: Previous,
|
||||||
},
|
},
|
||||||
ImageReady(Rc<Image>),
|
ImageReady(Rc<Image>),
|
||||||
|
|
@ -264,7 +238,12 @@ enum ImageState {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ImageState {
|
impl ImageState {
|
||||||
fn from_rgba(window: &Window, document: Document, image: &CursorImage) -> Arc<Inner> {
|
fn from_rgba(
|
||||||
|
main_thread: MainThreadMarker,
|
||||||
|
window: &Window,
|
||||||
|
document: Document,
|
||||||
|
image: &CursorImage,
|
||||||
|
) -> CustomCursor {
|
||||||
// 1. Create an `ImageData` from the RGBA data.
|
// 1. Create an `ImageData` from the RGBA data.
|
||||||
// 2. Create an `ImageBitmap` from the `ImageData`.
|
// 2. Create an `ImageBitmap` from the `ImageData`.
|
||||||
// 3. Draw `ImageBitmap` on an `HTMLCanvasElement`.
|
// 3. Draw `ImageBitmap` on an `HTMLCanvasElement`.
|
||||||
|
|
@ -316,10 +295,11 @@ impl ImageState {
|
||||||
.expect("unexpected exception in `createImageBitmap()`"),
|
.expect("unexpected exception in `createImageBitmap()`"),
|
||||||
);
|
);
|
||||||
|
|
||||||
let this = Inner::new();
|
#[allow(clippy::arc_with_non_send_sync)]
|
||||||
|
let this = CustomCursor::new(main_thread);
|
||||||
|
|
||||||
wasm_bindgen_futures::spawn_local({
|
wasm_bindgen_futures::spawn_local({
|
||||||
let weak = Arc::downgrade(&this);
|
let weak = Arc::downgrade(&this.0);
|
||||||
let CursorImage {
|
let CursorImage {
|
||||||
width,
|
width,
|
||||||
height,
|
height,
|
||||||
|
|
@ -394,7 +374,7 @@ impl ImageState {
|
||||||
let Some(this) = weak.upgrade() else {
|
let Some(this) = weak.upgrade() else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
let mut this = this.get().borrow_mut();
|
let mut this = this.get(main_thread).borrow_mut();
|
||||||
|
|
||||||
let Some(blob) = blob else {
|
let Some(blob) = blob else {
|
||||||
log::error!("creating custom cursor failed");
|
log::error!("creating custom cursor failed");
|
||||||
|
|
@ -422,17 +402,24 @@ impl ImageState {
|
||||||
.expect("unexpected exception in `URL.createObjectURL()`")
|
.expect("unexpected exception in `URL.createObjectURL()`")
|
||||||
};
|
};
|
||||||
|
|
||||||
Self::decode(weak, url, true, hotspot_x, hotspot_y).await;
|
Self::decode(main_thread, weak, url, true, hotspot_x, hotspot_y).await;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
this
|
this
|
||||||
}
|
}
|
||||||
|
|
||||||
fn from_url(url: String, hotspot_x: u16, hotspot_y: u16) -> Arc<Inner> {
|
fn from_url(
|
||||||
let this = Inner::new();
|
main_thread: MainThreadMarker,
|
||||||
|
url: String,
|
||||||
|
hotspot_x: u16,
|
||||||
|
hotspot_y: u16,
|
||||||
|
) -> CustomCursor {
|
||||||
|
#[allow(clippy::arc_with_non_send_sync)]
|
||||||
|
let this = CustomCursor::new(main_thread);
|
||||||
wasm_bindgen_futures::spawn_local(Self::decode(
|
wasm_bindgen_futures::spawn_local(Self::decode(
|
||||||
Arc::downgrade(&this),
|
main_thread,
|
||||||
|
Arc::downgrade(&this.0),
|
||||||
url,
|
url,
|
||||||
false,
|
false,
|
||||||
hotspot_x,
|
hotspot_x,
|
||||||
|
|
@ -443,7 +430,8 @@ impl ImageState {
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn decode(
|
async fn decode(
|
||||||
weak: sync::Weak<Inner>,
|
main_thread: MainThreadMarker,
|
||||||
|
weak: sync::Weak<MainThreadSafe<RefCell<ImageState>>>,
|
||||||
url: String,
|
url: String,
|
||||||
object: bool,
|
object: bool,
|
||||||
hotspot_x: u16,
|
hotspot_x: u16,
|
||||||
|
|
@ -462,7 +450,7 @@ impl ImageState {
|
||||||
let Some(this) = weak.upgrade() else {
|
let Some(this) = weak.upgrade() else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
let mut this = this.get().borrow_mut();
|
let mut this = this.get(main_thread).borrow_mut();
|
||||||
|
|
||||||
let ImageState::Loading(state) = this.deref_mut() else {
|
let ImageState::Loading(state) = this.deref_mut() else {
|
||||||
unreachable!("found invalid state");
|
unreachable!("found invalid state");
|
||||||
|
|
@ -533,46 +521,3 @@ impl Image {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
mod thread_safe {
|
|
||||||
use std::mem;
|
|
||||||
use std::thread::{self, ThreadId};
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct ThreadSafe<T> {
|
|
||||||
origin_thread: ThreadId,
|
|
||||||
value: T,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T> ThreadSafe<T> {
|
|
||||||
pub fn new(value: T) -> Self {
|
|
||||||
Self {
|
|
||||||
origin_thread: thread::current().id(),
|
|
||||||
value,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get(&self) -> &T {
|
|
||||||
if self.origin_thread == thread::current().id() {
|
|
||||||
&self.value
|
|
||||||
} else {
|
|
||||||
panic!("value not accessible outside its origin thread")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn in_origin_thread(&self) -> bool {
|
|
||||||
self.origin_thread == thread::current().id()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T> Drop for ThreadSafe<T> {
|
|
||||||
fn drop(&mut self) {
|
|
||||||
if mem::needs_drop::<T>() && self.origin_thread != thread::current().id() {
|
|
||||||
panic!("value can't be dropped outside its origin thread")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
unsafe impl<T> Send for ThreadSafe<T> {}
|
|
||||||
unsafe impl<T> Sync for ThreadSafe<T> {}
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
use super::super::main_thread::MainThreadMarker;
|
||||||
use super::super::DeviceId;
|
use super::super::DeviceId;
|
||||||
use super::{backend, state::State};
|
use super::{backend, state::State};
|
||||||
use crate::dpi::PhysicalSize;
|
use crate::dpi::PhysicalSize;
|
||||||
|
|
@ -37,6 +38,7 @@ impl Clone for Shared {
|
||||||
type OnEventHandle<T> = RefCell<Option<EventListenerHandle<dyn FnMut(T)>>>;
|
type OnEventHandle<T> = RefCell<Option<EventListenerHandle<dyn FnMut(T)>>>;
|
||||||
|
|
||||||
pub struct Execution {
|
pub struct Execution {
|
||||||
|
main_thread: MainThreadMarker,
|
||||||
proxy_spawner: WakerSpawner<Weak<Self>>,
|
proxy_spawner: WakerSpawner<Weak<Self>>,
|
||||||
control_flow: Cell<ControlFlow>,
|
control_flow: Cell<ControlFlow>,
|
||||||
poll_strategy: Cell<PollStrategy>,
|
poll_strategy: Cell<PollStrategy>,
|
||||||
|
|
@ -143,13 +145,14 @@ impl Runner {
|
||||||
|
|
||||||
impl Shared {
|
impl Shared {
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
|
let main_thread = MainThreadMarker::new().expect("only callable from inside the `Window`");
|
||||||
#[allow(clippy::disallowed_methods)]
|
#[allow(clippy::disallowed_methods)]
|
||||||
let window = web_sys::window().expect("only callable from inside the `Window`");
|
let window = web_sys::window().expect("only callable from inside the `Window`");
|
||||||
#[allow(clippy::disallowed_methods)]
|
#[allow(clippy::disallowed_methods)]
|
||||||
let document = window.document().expect("Failed to obtain document");
|
let document = window.document().expect("Failed to obtain document");
|
||||||
|
|
||||||
Shared(Rc::<Execution>::new_cyclic(|weak| {
|
Shared(Rc::<Execution>::new_cyclic(|weak| {
|
||||||
let proxy_spawner = WakerSpawner::new(weak.clone(), |runner, count| {
|
let proxy_spawner = WakerSpawner::new(main_thread, weak.clone(), |runner, count| {
|
||||||
if let Some(runner) = runner.upgrade() {
|
if let Some(runner) = runner.upgrade() {
|
||||||
Shared(runner).send_events(iter::repeat(Event::UserEvent(())).take(count))
|
Shared(runner).send_events(iter::repeat(Event::UserEvent(())).take(count))
|
||||||
}
|
}
|
||||||
|
|
@ -157,6 +160,7 @@ impl Shared {
|
||||||
.expect("`EventLoop` has to be created in the main thread");
|
.expect("`EventLoop` has to be created in the main thread");
|
||||||
|
|
||||||
Execution {
|
Execution {
|
||||||
|
main_thread,
|
||||||
proxy_spawner,
|
proxy_spawner,
|
||||||
control_flow: Cell::new(ControlFlow::default()),
|
control_flow: Cell::new(ControlFlow::default()),
|
||||||
poll_strategy: Cell::new(PollStrategy::default()),
|
poll_strategy: Cell::new(PollStrategy::default()),
|
||||||
|
|
@ -184,6 +188,10 @@ impl Shared {
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn main_thread(&self) -> MainThreadMarker {
|
||||||
|
self.0.main_thread
|
||||||
|
}
|
||||||
|
|
||||||
pub fn window(&self) -> &web_sys::Window {
|
pub fn window(&self) -> &web_sys::Window {
|
||||||
&self.0.window
|
&self.0.window
|
||||||
}
|
}
|
||||||
|
|
|
||||||
96
src/platform_impl/web/main_thread.rs
Normal file
96
src/platform_impl/web/main_thread.rs
Normal file
|
|
@ -0,0 +1,96 @@
|
||||||
|
use std::fmt::{self, Debug, Formatter};
|
||||||
|
use std::marker::PhantomData;
|
||||||
|
use std::mem;
|
||||||
|
use std::sync::OnceLock;
|
||||||
|
|
||||||
|
use wasm_bindgen::prelude::wasm_bindgen;
|
||||||
|
use wasm_bindgen::{JsCast, JsValue};
|
||||||
|
|
||||||
|
use super::r#async::{self, AsyncSender};
|
||||||
|
|
||||||
|
thread_local! {
|
||||||
|
static MAIN_THREAD: bool = {
|
||||||
|
#[wasm_bindgen]
|
||||||
|
extern "C" {
|
||||||
|
#[derive(Clone)]
|
||||||
|
type Global;
|
||||||
|
|
||||||
|
#[wasm_bindgen(method, getter, js_name = Window)]
|
||||||
|
fn window(this: &Global) -> JsValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let global: Global = js_sys::global().unchecked_into();
|
||||||
|
!global.window().is_undefined()
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug)]
|
||||||
|
pub struct MainThreadMarker(PhantomData<*const ()>);
|
||||||
|
|
||||||
|
impl MainThreadMarker {
|
||||||
|
pub fn new() -> Option<Self> {
|
||||||
|
MAIN_THREAD.with(|is| is.then_some(Self(PhantomData)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct MainThreadSafe<T: 'static>(Option<T>);
|
||||||
|
|
||||||
|
impl<T> MainThreadSafe<T> {
|
||||||
|
pub fn new(_: MainThreadMarker, value: T) -> Self {
|
||||||
|
DROP_HANDLER.get_or_init(|| {
|
||||||
|
let (sender, receiver) = r#async::channel();
|
||||||
|
wasm_bindgen_futures::spawn_local(
|
||||||
|
async move { while receiver.next().await.is_ok() {} },
|
||||||
|
);
|
||||||
|
|
||||||
|
sender
|
||||||
|
});
|
||||||
|
|
||||||
|
Self(Some(value))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn into_inner(mut self, _: MainThreadMarker) -> T {
|
||||||
|
self.0.take().expect("already taken or dropped")
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get(&self, _: MainThreadMarker) -> &T {
|
||||||
|
self.0.as_ref().expect("already taken or dropped")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: Debug> Debug for MainThreadSafe<T> {
|
||||||
|
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
||||||
|
if MainThreadMarker::new().is_some() {
|
||||||
|
f.debug_tuple("MainThreadSafe").field(&self.0).finish()
|
||||||
|
} else {
|
||||||
|
f.debug_struct("MainThreadSafe").finish_non_exhaustive()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> Drop for MainThreadSafe<T> {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
if let Some(value) = self.0.take() {
|
||||||
|
if mem::needs_drop::<T>() && MainThreadMarker::new().is_none() {
|
||||||
|
DROP_HANDLER
|
||||||
|
.get()
|
||||||
|
.expect("drop handler not initialized when setting canvas")
|
||||||
|
.send(DropBox(Box::new(value)))
|
||||||
|
.expect("sender dropped in main thread")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe impl<T> Send for MainThreadSafe<T> {}
|
||||||
|
unsafe impl<T> Sync for MainThreadSafe<T> {}
|
||||||
|
|
||||||
|
static DROP_HANDLER: OnceLock<AsyncSender<DropBox>> = OnceLock::new();
|
||||||
|
|
||||||
|
struct DropBox(Box<dyn Any>);
|
||||||
|
|
||||||
|
unsafe impl Send for DropBox {}
|
||||||
|
unsafe impl Sync for DropBox {}
|
||||||
|
|
||||||
|
trait Any {}
|
||||||
|
impl<T> Any for T {}
|
||||||
|
|
@ -23,6 +23,7 @@ mod device;
|
||||||
mod error;
|
mod error;
|
||||||
mod event_loop;
|
mod event_loop;
|
||||||
mod keyboard;
|
mod keyboard;
|
||||||
|
mod main_thread;
|
||||||
mod monitor;
|
mod monitor;
|
||||||
mod window;
|
mod window;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,7 @@ use crate::keyboard::{Key, KeyLocation, ModifiersState, PhysicalKey};
|
||||||
use crate::platform_impl::{OsError, PlatformSpecificWindowBuilderAttributes};
|
use crate::platform_impl::{OsError, PlatformSpecificWindowBuilderAttributes};
|
||||||
use crate::window::{WindowAttributes, WindowId as RootWindowId};
|
use crate::window::{WindowAttributes, WindowId as RootWindowId};
|
||||||
|
|
||||||
|
use super::super::main_thread::MainThreadMarker;
|
||||||
use super::super::WindowId;
|
use super::super::WindowId;
|
||||||
use super::animation_frame::AnimationFrameHandler;
|
use super::animation_frame::AnimationFrameHandler;
|
||||||
use super::event_handle::EventListenerHandle;
|
use super::event_handle::EventListenerHandle;
|
||||||
|
|
@ -66,13 +67,18 @@ pub struct Style {
|
||||||
|
|
||||||
impl Canvas {
|
impl Canvas {
|
||||||
pub fn create(
|
pub fn create(
|
||||||
|
main_thread: MainThreadMarker,
|
||||||
id: WindowId,
|
id: WindowId,
|
||||||
window: web_sys::Window,
|
window: web_sys::Window,
|
||||||
document: Document,
|
document: Document,
|
||||||
attr: &WindowAttributes,
|
attr: &WindowAttributes,
|
||||||
platform_attr: PlatformSpecificWindowBuilderAttributes,
|
mut platform_attr: PlatformSpecificWindowBuilderAttributes,
|
||||||
) -> Result<Self, RootOE> {
|
) -> Result<Self, RootOE> {
|
||||||
let canvas = match platform_attr.canvas.0 {
|
let canvas = match platform_attr.canvas.take().map(|canvas| {
|
||||||
|
Arc::try_unwrap(canvas)
|
||||||
|
.map(|canvas| canvas.into_inner(main_thread))
|
||||||
|
.unwrap_or_else(|canvas| canvas.get(main_thread).clone())
|
||||||
|
}) {
|
||||||
Some(canvas) => canvas,
|
Some(canvas) => canvas,
|
||||||
None => document
|
None => document
|
||||||
.create_element("canvas")
|
.create_element("canvas")
|
||||||
|
|
|
||||||
|
|
@ -5,9 +5,9 @@ use crate::window::{
|
||||||
Cursor, CursorGrabMode, ImePurpose, ResizeDirection, Theme, UserAttentionType,
|
Cursor, CursorGrabMode, ImePurpose, ResizeDirection, Theme, UserAttentionType,
|
||||||
WindowAttributes, WindowButtons, WindowId as RootWI, WindowLevel,
|
WindowAttributes, WindowButtons, WindowId as RootWI, WindowLevel,
|
||||||
};
|
};
|
||||||
use crate::SendSyncWrapper;
|
|
||||||
|
|
||||||
use super::cursor::CursorState;
|
use super::cursor::CursorState;
|
||||||
|
use super::main_thread::{MainThreadMarker, MainThreadSafe};
|
||||||
use super::r#async::Dispatcher;
|
use super::r#async::Dispatcher;
|
||||||
use super::{backend, monitor::MonitorHandle, EventLoopWindowTarget, Fullscreen};
|
use super::{backend, monitor::MonitorHandle, EventLoopWindowTarget, Fullscreen};
|
||||||
use web_sys::HtmlCanvasElement;
|
use web_sys::HtmlCanvasElement;
|
||||||
|
|
@ -15,6 +15,7 @@ use web_sys::HtmlCanvasElement;
|
||||||
use std::cell::RefCell;
|
use std::cell::RefCell;
|
||||||
use std::collections::VecDeque;
|
use std::collections::VecDeque;
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
pub struct Window {
|
pub struct Window {
|
||||||
inner: Dispatcher<Inner>,
|
inner: Dispatcher<Inner>,
|
||||||
|
|
@ -38,10 +39,16 @@ impl Window {
|
||||||
|
|
||||||
let window = target.runner.window();
|
let window = target.runner.window();
|
||||||
let document = target.runner.document();
|
let document = target.runner.document();
|
||||||
let canvas =
|
let canvas = backend::Canvas::create(
|
||||||
backend::Canvas::create(id, window.clone(), document.clone(), &attr, platform_attr)?;
|
target.runner.main_thread(),
|
||||||
|
id,
|
||||||
|
window.clone(),
|
||||||
|
document.clone(),
|
||||||
|
&attr,
|
||||||
|
platform_attr,
|
||||||
|
)?;
|
||||||
let canvas = Rc::new(RefCell::new(canvas));
|
let canvas = Rc::new(RefCell::new(canvas));
|
||||||
let cursor = CursorState::new(canvas.borrow().style().clone());
|
let cursor = CursorState::new(target.runner.main_thread(), canvas.borrow().style().clone());
|
||||||
|
|
||||||
target.register(&canvas, id);
|
target.register(&canvas, id);
|
||||||
|
|
||||||
|
|
@ -62,7 +69,7 @@ impl Window {
|
||||||
inner.set_window_icon(attr.window_icon);
|
inner.set_window_icon(attr.window_icon);
|
||||||
|
|
||||||
let canvas = Rc::downgrade(&inner.canvas);
|
let canvas = Rc::downgrade(&inner.canvas);
|
||||||
let (dispatcher, runner) = Dispatcher::new(inner).unwrap();
|
let (dispatcher, runner) = Dispatcher::new(target.runner.main_thread(), inner).unwrap();
|
||||||
target.runner.add_canvas(RootWI(id), canvas, runner);
|
target.runner.add_canvas(RootWI(id), canvas, runner);
|
||||||
|
|
||||||
Ok(Window { inner: dispatcher })
|
Ok(Window { inner: dispatcher })
|
||||||
|
|
@ -465,16 +472,30 @@ impl From<u64> for WindowId {
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct PlatformSpecificWindowBuilderAttributes {
|
pub struct PlatformSpecificWindowBuilderAttributes {
|
||||||
pub(crate) canvas: SendSyncWrapper<Option<backend::RawCanvasType>>,
|
pub(crate) canvas: Option<Arc<MainThreadSafe<backend::RawCanvasType>>>,
|
||||||
pub(crate) prevent_default: bool,
|
pub(crate) prevent_default: bool,
|
||||||
pub(crate) focusable: bool,
|
pub(crate) focusable: bool,
|
||||||
pub(crate) append: bool,
|
pub(crate) append: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl PlatformSpecificWindowBuilderAttributes {
|
||||||
|
pub(crate) fn set_canvas(&mut self, canvas: Option<backend::RawCanvasType>) {
|
||||||
|
let Some(canvas) = canvas else {
|
||||||
|
self.canvas = None;
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
let main_thread = MainThreadMarker::new()
|
||||||
|
.expect("received a `HtmlCanvasElement` outside the window context");
|
||||||
|
|
||||||
|
self.canvas = Some(Arc::new(MainThreadSafe::new(main_thread, canvas)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Default for PlatformSpecificWindowBuilderAttributes {
|
impl Default for PlatformSpecificWindowBuilderAttributes {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self {
|
Self {
|
||||||
canvas: SendSyncWrapper(None),
|
canvas: None,
|
||||||
prevent_default: true,
|
prevent_default: true,
|
||||||
focusable: true,
|
focusable: true,
|
||||||
append: false,
|
append: false,
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue