diff --git a/gui/src/rt/context.rs b/gui/src/rt/context.rs index 4a8be562b..8385a5e6c 100644 --- a/gui/src/rt/context.rs +++ b/gui/src/rt/context.rs @@ -1,27 +1,28 @@ use super::event::WindowEvent; -use super::{RuntimeError, RuntimeWindow}; +use super::task::TaskList; +use super::{Event, RuntimeWindow}; use std::cell::Cell; use std::collections::HashMap; -use std::error::Error; -use std::future::Future; use std::mem::transmute; use std::ptr::null_mut; -use std::rc::{Rc, Weak}; -use winit::event_loop::ActiveEventLoop; -use winit::window::{Window, WindowAttributes, WindowId}; +use std::rc::Weak; +use winit::event_loop::{ActiveEventLoop, EventLoopProxy}; +use winit::window::WindowId; /// Execution context of the runtime. -pub struct RuntimeContext<'a> { - pub(super) el: &'a ActiveEventLoop, - pub(super) windows: &'a mut HashMap>, - pub(super) on_close: &'a mut WindowEvent<()>, +pub struct Context<'a> { + pub el: &'a ActiveEventLoop, + pub proxy: &'a EventLoopProxy, + pub tasks: &'a mut TaskList, + pub windows: &'a mut HashMap>, + pub on_close: &'a mut WindowEvent<()>, } -impl<'a> RuntimeContext<'a> { +impl<'a> Context<'a> { /// # Panics /// - If called from the other thread than main thread. /// - If this call has been nested. - pub fn with(f: impl FnOnce(&mut RuntimeContext) -> R) -> R { + pub fn with(f: impl FnOnce(&mut Context) -> R) -> R { // Take context to achieve exclusive access. let cx = CONTEXT.replace(null_mut()); @@ -34,32 +35,9 @@ impl<'a> RuntimeContext<'a> { r } - pub fn create_window( - attrs: WindowAttributes, - f: impl FnOnce(Window) -> Result, Box>, - ) -> Result, RuntimeError> { - Self::with(move |cx| { - let win = cx - .el - .create_window(attrs) - .map_err(RuntimeError::CreateWinitWindow)?; - let id = win.id(); - let win = f(win).map_err(RuntimeError::CreateRuntimeWindow)?; - let weak = Rc::downgrade(&win); - - assert!(cx.windows.insert(id, weak).is_none()); - - Ok(win) - }) - } - - pub fn on_close(&mut self, win: WindowId) -> impl Future { - self.on_close.wait(win) - } - /// # Panics /// If this call has been nested. - pub(super) fn run(&mut self, f: impl FnOnce() -> R) -> R { + pub fn run(&mut self, f: impl FnOnce() -> R) -> R { assert!(CONTEXT.get().is_null()); CONTEXT.set(unsafe { transmute(self) }); @@ -71,5 +49,5 @@ impl<'a> RuntimeContext<'a> { } thread_local! { - static CONTEXT: Cell<*mut RuntimeContext<'static>> = Cell::new(null_mut()); + static CONTEXT: Cell<*mut Context<'static>> = Cell::new(null_mut()); } diff --git a/gui/src/rt/mod.rs b/gui/src/rt/mod.rs index daf56334d..11d144e7e 100644 --- a/gui/src/rt/mod.rs +++ b/gui/src/rt/mod.rs @@ -1,7 +1,7 @@ pub use self::app::*; -pub use self::context::*; pub use self::window::*; +use self::context::Context; use self::event::WindowEvent; use self::task::TaskList; use self::waker::Waker; @@ -14,7 +14,7 @@ use thiserror::Error; use winit::application::ApplicationHandler; use winit::error::{EventLoopError, OsError}; use winit::event_loop::{ActiveEventLoop, EventLoop, EventLoopProxy}; -use winit::window::WindowId; +use winit::window::{Window, WindowAttributes, WindowId}; mod app; mod context; @@ -42,14 +42,14 @@ where app.error(e).await; } - RuntimeContext::with(|cx| cx.el.exit()); + Context::with(|cx| cx.el.exit()); } }; // Run event loop. let mut tasks = TaskList::default(); let main: Box> = Box::new(main); - let main = tasks.insert(Box::into_pin(main)); + let main = tasks.insert(None, Box::into_pin(main)); let mut rt = Runtime { el: el.create_proxy(), tasks, @@ -61,6 +61,47 @@ where el.run_app(&mut rt).map_err(RuntimeError::RunEventLoop) } +/// # Panics +/// If called from the other thread than main thread. +pub fn spawn(task: impl Future + 'static) { + let task: Box> = Box::new(task); + + Context::with(move |cx| { + let id = cx.tasks.insert(None, Box::into_pin(task)); + + // We have a context so there is an event loop for sure. + assert!(cx.proxy.send_event(Event::TaskReady(id)).is_ok()); + }) +} + +/// # Panics +/// - If called from the other thread than main thread. +/// - If called from `f`. +pub fn create_window( + attrs: WindowAttributes, + f: impl FnOnce(Window) -> Result, Box>, +) -> Result, RuntimeError> { + Context::with(move |cx| { + let win = cx + .el + .create_window(attrs) + .map_err(RuntimeError::CreateWinitWindow)?; + let id = win.id(); + let win = f(win).map_err(RuntimeError::CreateRuntimeWindow)?; + let weak = Rc::downgrade(&win); + + assert!(cx.windows.insert(id, weak).is_none()); + + Ok(win) + }) +} + +/// # Panics +/// If called from the other thread than main thread. +pub fn on_close(win: WindowId) -> impl Future { + Context::with(move |cx| cx.on_close.wait(win)) +} + /// Implementation of [`ApplicationHandler`] to drive [`Future`]. struct Runtime { el: EventLoopProxy, @@ -71,9 +112,9 @@ struct Runtime { } impl Runtime { - fn dispatch_task(&mut self, el: &ActiveEventLoop, task: u64) -> bool { - // Get target task. - let mut task = match self.tasks.get(task) { + fn dispatch_task(&mut self, el: &ActiveEventLoop, id: u64) -> bool { + // Take target task so can mutable borrow the task list for context. + let mut task = match self.tasks.remove(id) { Some(v) => v, None => { // It is possible for the waker to wake the same task multiple times. In this case @@ -82,23 +123,28 @@ impl Runtime { } }; - // Poll the task. - let waker = Arc::new(Waker::new(self.el.clone(), *task.key())); - let mut cx = RuntimeContext { + // Setup context. + let waker = Arc::new(Waker::new(self.el.clone(), id)); + let mut cx = Context { el, + proxy: &self.el, + tasks: &mut self.tasks, windows: &mut self.windows, on_close: &mut self.on_close, }; - cx.run(|| { + // Poll the task. + let r = cx.run(|| { let waker = std::task::Waker::from(waker); let mut cx = std::task::Context::from_waker(&waker); - if task.get_mut().as_mut().poll(&mut cx).is_ready() { - drop(task.remove()); - } + task.as_mut().poll(&mut cx) }); + if r.is_pending() { + self.tasks.insert(Some(id), task); + } + true } @@ -115,8 +161,10 @@ impl Runtime { }; // Setup context. - let mut cx = RuntimeContext { + let mut cx = Context { el, + proxy: &self.el, + tasks: &mut self.tasks, windows: &mut self.windows, on_close: &mut self.on_close, }; diff --git a/gui/src/rt/task.rs b/gui/src/rt/task.rs index 65565ffaa..e95a00c6c 100644 --- a/gui/src/rt/task.rs +++ b/gui/src/rt/task.rs @@ -1,4 +1,3 @@ -use std::collections::hash_map::OccupiedEntry; use std::collections::HashMap; use std::future::Future; use std::pin::Pin; @@ -11,24 +10,23 @@ pub struct TaskList { } impl TaskList { - pub fn insert(&mut self, f: Pin>>) -> u64 { - let id = self.next; + pub fn insert(&mut self, id: Option, task: Pin>>) -> u64 { + // Get ID. + let id = match id { + Some(v) => v, + None => { + let v = self.next; + self.next = self.next.checked_add(1).unwrap(); + v + } + }; - assert!(self.list.insert(id, f).is_none()); - self.next = self.next.checked_add(1).unwrap(); + assert!(self.list.insert(id, task).is_none()); id } - pub fn get( - &mut self, - id: u64, - ) -> Option>>>> { - use std::collections::hash_map::Entry; - - match self.list.entry(id) { - Entry::Occupied(e) => Some(e), - Entry::Vacant(_) => None, - } + pub fn remove(&mut self, id: u64) -> Option>>> { + self.list.remove(&id) } } diff --git a/gui/src/ui/backend/mod.rs b/gui/src/ui/backend/mod.rs index ff0d62df0..a351a6627 100644 --- a/gui/src/ui/backend/mod.rs +++ b/gui/src/ui/backend/mod.rs @@ -1,6 +1,5 @@ pub(super) use self::window::Window; -use crate::rt::RuntimeContext; use i_slint_renderer_skia::SkiaRenderer; use slint::platform::WindowAdapter; use slint::{PhysicalSize, PlatformError}; @@ -26,7 +25,7 @@ impl SlintBackend { impl slint::platform::Platform for SlintBackend { fn create_window_adapter(&self) -> Result, PlatformError> { let attrs = winit::window::Window::default_attributes().with_visible(false); - let win = RuntimeContext::create_window(attrs, move |win| { + let win = crate::rt::create_window(attrs, move |win| { // Create renderer. let win = Rc::new(win); let size = win.inner_size(); diff --git a/gui/src/ui/mod.rs b/gui/src/ui/mod.rs index 0bd68f66e..62375a614 100644 --- a/gui/src/ui/mod.rs +++ b/gui/src/ui/mod.rs @@ -1,7 +1,6 @@ pub use self::backend::*; pub use self::profile::*; -use crate::rt::RuntimeContext; use i_slint_core::window::WindowInner; use i_slint_core::InternalToken; use slint::ComponentHandle; @@ -25,7 +24,7 @@ impl RuntimeExt for T { .unwrap(); self.show()?; - RuntimeContext::with(|cx| cx.on_close(win.id())).await; + crate::rt::on_close(win.id()).await; self.hide()?; Ok(())