Skip to content

Commit

Permalink
Implements spawn (#1187)
Browse files Browse the repository at this point in the history
  • Loading branch information
ultimaweapon authored Dec 15, 2024
1 parent 276520f commit 152f0df
Show file tree
Hide file tree
Showing 5 changed files with 93 additions and 71 deletions.
52 changes: 15 additions & 37 deletions gui/src/rt/context.rs
Original file line number Diff line number Diff line change
@@ -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<WindowId, Weak<dyn RuntimeWindow>>,
pub(super) on_close: &'a mut WindowEvent<()>,
pub struct Context<'a> {
pub el: &'a ActiveEventLoop,
pub proxy: &'a EventLoopProxy<Event>,
pub tasks: &'a mut TaskList,
pub windows: &'a mut HashMap<WindowId, Weak<dyn RuntimeWindow>>,
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<R>(f: impl FnOnce(&mut RuntimeContext) -> R) -> R {
pub fn with<R>(f: impl FnOnce(&mut Context) -> R) -> R {
// Take context to achieve exclusive access.
let cx = CONTEXT.replace(null_mut());

Expand All @@ -34,32 +35,9 @@ impl<'a> RuntimeContext<'a> {
r
}

pub fn create_window<T: RuntimeWindow + 'static>(
attrs: WindowAttributes,
f: impl FnOnce(Window) -> Result<Rc<T>, Box<dyn Error + Send + Sync>>,
) -> Result<Rc<T>, 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<Output = ()> {
self.on_close.wait(win)
}

/// # Panics
/// If this call has been nested.
pub(super) fn run<R>(&mut self, f: impl FnOnce() -> R) -> R {
pub fn run<R>(&mut self, f: impl FnOnce() -> R) -> R {
assert!(CONTEXT.get().is_null());

CONTEXT.set(unsafe { transmute(self) });
Expand All @@ -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());
}
78 changes: 63 additions & 15 deletions gui/src/rt/mod.rs
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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;
Expand Down Expand Up @@ -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<dyn Future<Output = ()>> = 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,
Expand All @@ -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<Output = ()> + 'static) {
let task: Box<dyn Future<Output = ()>> = 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<T: RuntimeWindow + 'static>(
attrs: WindowAttributes,
f: impl FnOnce(Window) -> Result<Rc<T>, Box<dyn Error + Send + Sync>>,
) -> Result<Rc<T>, 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<Output = ()> {
Context::with(move |cx| cx.on_close.wait(win))
}

/// Implementation of [`ApplicationHandler`] to drive [`Future`].
struct Runtime {
el: EventLoopProxy<Event>,
Expand All @@ -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
Expand All @@ -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
}

Expand All @@ -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,
};
Expand Down
28 changes: 13 additions & 15 deletions gui/src/rt/task.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
use std::collections::hash_map::OccupiedEntry;
use std::collections::HashMap;
use std::future::Future;
use std::pin::Pin;
Expand All @@ -11,24 +10,23 @@ pub struct TaskList {
}

impl TaskList {
pub fn insert(&mut self, f: Pin<Box<dyn Future<Output = ()>>>) -> u64 {
let id = self.next;
pub fn insert(&mut self, id: Option<u64>, task: Pin<Box<dyn Future<Output = ()>>>) -> 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<OccupiedEntry<u64, Pin<Box<dyn Future<Output = ()>>>>> {
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<Pin<Box<dyn Future<Output = ()>>>> {
self.list.remove(&id)
}
}
3 changes: 1 addition & 2 deletions gui/src/ui/backend/mod.rs
Original file line number Diff line number Diff line change
@@ -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};
Expand All @@ -26,7 +25,7 @@ impl SlintBackend {
impl slint::platform::Platform for SlintBackend {
fn create_window_adapter(&self) -> Result<Rc<dyn WindowAdapter>, 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();
Expand Down
3 changes: 1 addition & 2 deletions gui/src/ui/mod.rs
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -25,7 +24,7 @@ impl<T: ComponentHandle> 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(())
Expand Down

0 comments on commit 152f0df

Please sign in to comment.