Winit: remove maybe_loop_instance thread local

In the future, the plain winit::event_loop::EventLoop won't be usable for window creation anymore. There are two places where we need it:

1. From places where we also have access to the shared backend data.
2. From the top-level create_winit_window() function.

The latter we have to eliminate in the future, so might as well do it now and clean up to have one thread local less.
This commit is contained in:
Simon Hausmann 2025-04-10 18:47:05 +02:00 committed by Simon Hausmann
parent fe56fdd5e4
commit cd29bdd367
3 changed files with 85 additions and 113 deletions

View file

@ -18,8 +18,6 @@ use corelib::platform::PlatformError;
use corelib::window::*;
use i_slint_core as corelib;
#[cfg(not(target_family = "wasm"))]
use raw_window_handle::HasDisplayHandle;
#[allow(unused_imports)]
use std::cell::{RefCell, RefMut};
use std::rc::Rc;
@ -28,17 +26,13 @@ use winit::event_loop::ActiveEventLoop;
use winit::event_loop::ControlFlow;
use winit::window::ResizeDirection;
pub(crate) struct NotRunningEventLoop {
#[cfg(not(target_arch = "wasm32"))]
pub(crate) clipboard: Rc<std::cell::RefCell<crate::clipboard::ClipboardPair>>,
pub(crate) instance: winit::event_loop::EventLoop<SlintUserEvent>,
}
impl NotRunningEventLoop {
pub(crate) fn new(
builder: Option<winit::event_loop::EventLoopBuilder<SlintUserEvent>>,
mut builder: winit::event_loop::EventLoopBuilder<SlintUserEvent>,
) -> Result<Self, PlatformError> {
let mut builder = builder.unwrap_or_else(winit::event_loop::EventLoop::with_user_event);
#[cfg(all(unix, not(target_vendor = "apple")))]
{
#[cfg(feature = "wayland")]
@ -71,22 +65,11 @@ impl NotRunningEventLoop {
let instance =
builder.build().map_err(|e| format!("Error initializing winit event loop: {e}"))?;
#[cfg(not(target_arch = "wasm32"))]
let clipboard = crate::clipboard::create_clipboard(
&instance
.display_handle()
.map_err(|display_err| PlatformError::OtherError(display_err.into()))?,
);
Ok(Self {
instance,
#[cfg(not(target_family = "wasm"))]
clipboard: Rc::new(clipboard.into()),
})
Ok(Self { instance })
}
}
struct RunningEventLoop<'a> {
pub(crate) struct RunningEventLoop<'a> {
active_event_loop: &'a ActiveEventLoop,
}
@ -131,28 +114,7 @@ impl EventLoopInterface for RunningEventLoop<'_> {
}
}
thread_local! {
pub(crate) static MAYBE_LOOP_INSTANCE: RefCell<Option<NotRunningEventLoop>> = RefCell::default();
}
scoped_tls_hkt::scoped_thread_local!(static CURRENT_WINDOW_TARGET : for<'a> &'a RunningEventLoop<'a>);
pub(crate) fn with_event_loop<T>(
callback: impl FnOnce(
&dyn EventLoopInterface,
) -> Result<T, Box<dyn std::error::Error + Send + Sync>>,
) -> Result<T, Box<dyn std::error::Error + Send + Sync>> {
if CURRENT_WINDOW_TARGET.is_set() {
CURRENT_WINDOW_TARGET.with(|current_target| callback(current_target))
} else {
MAYBE_LOOP_INSTANCE.with(|loop_instance| {
if loop_instance.borrow().is_none() {
*loop_instance.borrow_mut() = Some(NotRunningEventLoop::new(None)?);
}
callback(loop_instance.borrow().as_ref().unwrap())
})
}
}
scoped_tls_hkt::scoped_thread_local!(pub(crate) static CURRENT_WINDOW_TARGET : for<'a> &'a RunningEventLoop<'a>);
/// This enum captures run-time specific events that can be dispatched to the event loop in
/// addition to the winit events.
@ -644,13 +606,11 @@ impl EventLoopState {
#[allow(unused_mut)] // mut need changes for wasm
pub fn run(mut self) -> Result<Self, corelib::platform::PlatformError> {
let not_running_loop_instance = MAYBE_LOOP_INSTANCE
.with(|loop_instance| match loop_instance.borrow_mut().take() {
Some(instance) => Ok(instance),
None => NotRunningEventLoop::new(None),
})
.map_err(|e| format!("Error initializing winit event loop: {e}"))?;
let not_running_loop_instance = self
.shared_backend_data
.not_running_event_loop
.take()
.ok_or_else(|| PlatformError::from("Nested event loops are not supported"))?;
let mut winit_loop = not_running_loop_instance.instance;
#[cfg(all(not(target_arch = "wasm32"), not(target_os = "ios")))]
@ -662,11 +622,9 @@ impl EventLoopState {
// Keep the EventLoop instance alive and re-use it in future invocations of run_event_loop().
// Winit does not support creating multiple instances of the event loop.
let nre = NotRunningEventLoop {
instance: winit_loop,
clipboard: not_running_loop_instance.clipboard,
};
MAYBE_LOOP_INSTANCE.with(|loop_instance| *loop_instance.borrow_mut() = Some(nre));
self.shared_backend_data
.not_running_event_loop
.replace(Some(NotRunningEventLoop { instance: winit_loop }));
if let Some(error) = self.loop_error {
return Err(error);
@ -694,13 +652,11 @@ impl EventLoopState {
{
use winit::platform::pump_events::EventLoopExtPumpEvents;
let not_running_loop_instance = MAYBE_LOOP_INSTANCE
.with(|loop_instance| match loop_instance.borrow_mut().take() {
Some(instance) => Ok(instance),
None => NotRunningEventLoop::new(None),
})
.map_err(|e| format!("Error initializing winit event loop: {e}"))?;
let not_running_loop_instance = self
.shared_backend_data
.not_running_event_loop
.take()
.ok_or_else(|| PlatformError::from("Nested event loops are not supported"))?;
let mut winit_loop = not_running_loop_instance.instance;
self.pumping_events_instantly = timeout.is_some_and(|duration| duration.is_zero());
@ -712,11 +668,9 @@ impl EventLoopState {
// Keep the EventLoop instance alive and re-use it in future invocations of run_event_loop().
// Winit does not support creating multiple instances of the event loop.
let nre = NotRunningEventLoop {
instance: winit_loop,
clipboard: not_running_loop_instance.clipboard,
};
MAYBE_LOOP_INSTANCE.with(|loop_instance| *loop_instance.borrow_mut() = Some(nre));
self.shared_backend_data
.not_running_event_loop
.replace(Some(NotRunningEventLoop { instance: winit_loop }));
if let Some(error) = self.loop_error {
return Err(error);
@ -727,12 +681,11 @@ impl EventLoopState {
#[cfg(target_arch = "wasm32")]
pub fn spawn(self) -> Result<(), corelib::platform::PlatformError> {
use winit::platform::web::EventLoopExtWebSys;
let not_running_loop_instance = MAYBE_LOOP_INSTANCE
.with(|loop_instance| match loop_instance.borrow_mut().take() {
Some(instance) => Ok(instance),
None => NotRunningEventLoop::new(None),
})
.map_err(|e| format!("Error initializing winit event loop: {e}"))?;
let not_running_loop_instance = self
.shared_backend_data
.not_running_event_loop
.take()
.ok_or_else(|| PlatformError::from("Nested event loops are not supported"))?;
not_running_loop_instance
.instance

View file

@ -7,7 +7,7 @@
extern crate alloc;
use event_loop::{CustomEvent, EventLoopState};
use event_loop::{CustomEvent, EventLoopState, NotRunningEventLoop};
use i_slint_core::api::EventLoopError;
use i_slint_core::graphics::{RequestedGraphicsAPI, RequestedOpenGLVersion};
use i_slint_core::platform::{EventLoopProxy, PlatformError};
@ -267,16 +267,7 @@ impl BackendBuilder {
// Initialize the winit event loop and propagate errors if for example `DISPLAY` or `WAYLAND_DISPLAY` isn't set.
let nre = crate::event_loop::NotRunningEventLoop::new(Some(event_loop_builder))?;
let proxy = nre.instance.create_proxy();
#[cfg(not(target_arch = "wasm32"))]
let clipboard = Rc::downgrade(&nre.clipboard);
crate::event_loop::MAYBE_LOOP_INSTANCE.with(|loop_instance| {
*loop_instance.borrow_mut() = Some(nre);
});
let shared_data = Rc::new(SharedBackendData::new(event_loop_builder)?);
let renderer_factory_fn = match (
self.renderer_name.as_deref(),
@ -341,10 +332,7 @@ impl BackendBuilder {
renderer_factory_fn,
event_loop_state: Default::default(),
window_attributes_hook: self.window_attributes_hook,
#[cfg(not(target_arch = "wasm32"))]
clipboard,
proxy,
shared_data: Rc::new(SharedBackendData::default()),
shared_data,
#[cfg(all(muda, target_os = "macos"))]
muda_enable_default_menu_bar_bar: self.muda_enable_default_menu_bar_bar,
#[cfg(target_family = "wasm")]
@ -353,12 +341,57 @@ impl BackendBuilder {
}
}
#[derive(Default)]
pub(crate) struct SharedBackendData {
active_windows: RefCell<HashMap<winit::window::WindowId, Weak<WinitWindowAdapter>>>,
#[cfg(not(target_arch = "wasm32"))]
clipboard: std::cell::RefCell<clipboard::ClipboardPair>,
not_running_event_loop: RefCell<Option<crate::event_loop::NotRunningEventLoop>>,
event_loop_proxy: winit::event_loop::EventLoopProxy<SlintUserEvent>,
}
impl SharedBackendData {
fn new(
builder: winit::event_loop::EventLoopBuilder<SlintUserEvent>,
) -> Result<Self, PlatformError> {
#[cfg(not(target_arch = "wasm32"))]
use raw_window_handle::HasDisplayHandle;
let nre = NotRunningEventLoop::new(builder)?;
let event_loop_proxy = nre.instance.create_proxy();
#[cfg(not(target_arch = "wasm32"))]
let clipboard = crate::clipboard::create_clipboard(
&nre.instance
.display_handle()
.map_err(|display_err| PlatformError::OtherError(display_err.into()))?,
);
Ok(Self {
active_windows: Default::default(),
#[cfg(not(target_arch = "wasm32"))]
clipboard: RefCell::new(clipboard),
not_running_event_loop: RefCell::new(Some(nre)),
event_loop_proxy,
})
}
pub(crate) fn with_event_loop<T>(
&self,
callback: impl FnOnce(
&dyn crate::event_loop::EventLoopInterface,
) -> Result<T, Box<dyn std::error::Error + Send + Sync>>,
) -> Result<T, Box<dyn std::error::Error + Send + Sync>> {
if crate::event_loop::CURRENT_WINDOW_TARGET.is_set() {
crate::event_loop::CURRENT_WINDOW_TARGET.with(|current_target| callback(current_target))
} else {
match self.not_running_event_loop.borrow().as_ref() {
Some(event_loop) => callback(event_loop),
None => {
Err(PlatformError::from("Event loop functions called without event loop")
.into())
}
}
}
}
pub fn register_window(&self, id: winit::window::WindowId, window: Rc<WinitWindowAdapter>) {
self.active_windows.borrow_mut().insert(id, Rc::downgrade(&window));
}
@ -385,7 +418,6 @@ pub struct Backend {
requested_graphics_api: Option<RequestedGraphicsAPI>,
renderer_factory_fn: fn() -> Box<dyn WinitCompatibleRenderer>,
event_loop_state: std::cell::RefCell<Option<crate::event_loop::EventLoopState>>,
proxy: winit::event_loop::EventLoopProxy<SlintUserEvent>,
shared_data: Rc<SharedBackendData>,
/// This hook is called before a Window is created.
@ -404,9 +436,6 @@ pub struct Backend {
pub window_attributes_hook:
Option<Box<dyn Fn(winit::window::WindowAttributes) -> winit::window::WindowAttributes>>,
#[cfg(not(target_arch = "wasm32"))]
clipboard: Weak<std::cell::RefCell<clipboard::ClipboardPair>>,
#[cfg(all(muda, target_os = "macos"))]
muda_enable_default_menu_bar_bar: bool,
@ -468,7 +497,7 @@ impl i_slint_core::platform::Platform for Backend {
attrs.clone(),
self.requested_graphics_api.clone(),
#[cfg(any(enable_accesskit, muda))]
self.proxy.clone(),
self.shared_data.event_loop_proxy.clone(),
#[cfg(all(muda, target_os = "macos"))]
self.muda_enable_default_menu_bar_bar,
)
@ -476,7 +505,7 @@ impl i_slint_core::platform::Platform for Backend {
try_create_window_with_fallback_renderer(
&self.shared_data,
attrs,
&self.proxy,
&self.shared_data.event_loop_proxy.clone(),
#[cfg(all(muda, target_os = "macos"))]
self.muda_enable_default_menu_bar_bar,
)
@ -561,7 +590,7 @@ impl i_slint_core::platform::Platform for Backend {
.map_err(|_| EventLoopError::EventLoopTerminated)
}
}
Some(Box::new(Proxy(self.proxy.clone())))
Some(Box::new(Proxy(self.shared_data.event_loop_proxy.clone())))
}
#[cfg(target_arch = "wasm32")]
@ -571,8 +600,7 @@ impl i_slint_core::platform::Platform for Backend {
#[cfg(not(target_arch = "wasm32"))]
fn set_clipboard_text(&self, text: &str, clipboard: i_slint_core::platform::Clipboard) {
let Some(clipboard_pair) = self.clipboard.upgrade() else { return };
let mut pair = clipboard_pair.borrow_mut();
let mut pair = self.shared_data.clipboard.borrow_mut();
if let Some(clipboard) = clipboard::select_clipboard(&mut pair, clipboard) {
clipboard.set_contents(text.into()).ok();
}
@ -585,8 +613,7 @@ impl i_slint_core::platform::Platform for Backend {
#[cfg(not(target_arch = "wasm32"))]
fn clipboard_text(&self, clipboard: i_slint_core::platform::Clipboard) -> Option<String> {
let clipboard_pair = self.clipboard.upgrade()?;
let mut pair = clipboard_pair.borrow_mut();
let mut pair = self.shared_data.clipboard.borrow_mut();
clipboard::select_clipboard(&mut pair, clipboard).and_then(|c| c.get_contents().ok())
}
}
@ -659,13 +686,6 @@ impl WinitWindowAccessor for i_slint_core::api::Window {
impl private::WinitWindowAccessorSealed for i_slint_core::api::Window {}
/// Creates a non Slint aware window with winit
pub fn create_winit_window(
window_attributes: winit::window::WindowAttributes,
) -> Result<winit::window::Window, winit::error::OsError> {
event_loop::with_event_loop(|eli| Ok(eli.create_window(window_attributes))).unwrap()
}
#[cfg(test)]
mod testui {
slint::slint! {

View file

@ -342,10 +342,9 @@ impl WinitWindowAdapter {
muda_enable_default_menu_bar,
});
let winit_window =
crate::event_loop::with_event_loop(
|event_loop| Ok(self_rc.ensure_window(event_loop)?),
)?;
let winit_window = self_rc
.shared_backend_data
.with_event_loop(|event_loop| Ok(self_rc.ensure_window(event_loop)?))?;
debug_assert!(!self_rc.renderer.is_suspended());
self_rc.size.set(physical_size_to_slint(&winit_window.inner_size()));
@ -708,9 +707,9 @@ impl WindowAdapter for WinitWindowAdapter {
if visible {
let recreating_window = self.winit_window_or_none.borrow().as_window().is_none();
let winit_window = crate::event_loop::with_event_loop(|event_loop| {
Ok(self.ensure_window(event_loop)?)
})?;
let winit_window = self
.shared_backend_data
.with_event_loop(|event_loop| Ok(self.ensure_window(event_loop)?))?;
let runtime_window = WindowInner::from_pub(self.window());