Skip to content

Commit

Permalink
Implements RuntimeExt::exec for UI (#1179)
Browse files Browse the repository at this point in the history
  • Loading branch information
ultimaweapon authored Dec 13, 2024
1 parent 0996ba7 commit 48b7a04
Show file tree
Hide file tree
Showing 8 changed files with 150 additions and 27 deletions.
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions gui/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ futures = "0.3.31"
gdbstub = "0.7.3"
gdbstub_arch = "0.3.1"
humansize = "2.1.3"
i-slint-core = "=1.8.0"
i-slint-renderer-skia = { version = "=1.8.0", features = ["wayland", "x11"] }
libc = "0.2.164"
num_enum = "0.7.3"
Expand Down
38 changes: 25 additions & 13 deletions gui/src/rt/context.rs
Original file line number Diff line number Diff line change
@@ -1,37 +1,49 @@
use super::event::WindowEvent;
use std::cell::Cell;
use std::future::Future;
use std::mem::transmute;
use std::ptr::null;
use std::ptr::null_mut;
use winit::event_loop::ActiveEventLoop;
use winit::window::WindowId;

/// Execution context of the runtime.
pub struct RuntimeContext<'a> {
el: &'a ActiveEventLoop,
pub(super) el: &'a ActiveEventLoop,
pub(super) on_close: &'a mut WindowEvent<()>,
}

impl<'a> RuntimeContext<'a> {
pub(super) fn new(el: &'a ActiveEventLoop) -> Self {
Self { el }
}

/// # Panics
/// If called from the other thread than main thread.
pub fn with<R>(f: impl FnOnce(&Self) -> R) -> R {
let cx = CONTEXT.get();
/// - 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 {
// Take context to achieve exclusive access.
let cx = CONTEXT.replace(null_mut());

assert!(!cx.is_null());
unsafe { f(&*cx) }

// Execute action then put context back.
let r = unsafe { f(&mut *cx) };

CONTEXT.set(cx);
r
}

pub fn event_loop(&self) -> &ActiveEventLoop {
self.el
}

pub(super) fn run(&self, f: impl FnOnce()) {
pub fn on_close(&mut self, win: WindowId) -> impl Future<Output = ()> {
self.on_close.wait(win)
}

pub(super) fn run(&mut self, f: impl FnOnce()) {
assert!(CONTEXT.replace(unsafe { transmute(self) }).is_null());
f();
CONTEXT.set(null());
CONTEXT.set(null_mut());
}
}

thread_local! {
static CONTEXT: Cell<*const RuntimeContext<'static>> = Cell::new(null());
static CONTEXT: Cell<*mut RuntimeContext<'static>> = Cell::new(null_mut());
}
53 changes: 53 additions & 0 deletions gui/src/rt/event.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
use futures::channel::oneshot::{Receiver, Sender};
use futures::FutureExt;
use std::future::Future;
use std::pin::Pin;
use std::task::{Context, Poll};
use winit::window::WindowId;

/// List of one-shot channel waiting for a window event.
#[derive(Default)]
pub struct WindowEvent<T>(Vec<(WindowId, Sender<T>)>);

impl<T: Clone> WindowEvent<T> {
pub fn wait(&mut self, win: WindowId) -> impl Future<Output = T> {
let (tx, rx) = futures::channel::oneshot::channel();

self.0.push((win, tx));

Wait(rx)
}

pub fn raise(&mut self, win: WindowId, data: T) {
// TODO: https://github.com/rust-lang/rust/issues/43244
let mut i = 0;

while i < self.0.len() {
let s = if self.0[i].0 == win {
self.0.remove(i).1
} else {
i += 1;
continue;
};

s.send(data.clone()).ok();
}
}
}

/// Result of [`WindowEvent::wait()`].
struct Wait<T>(Receiver<T>);

impl<T> Future for Wait<T> {
type Output = T;

fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
let r = match self.0.poll_unpin(cx) {
Poll::Ready(v) => v,
Poll::Pending => return Poll::Pending,
};

// The future only driven when the event loop is running so this should never panic.
Poll::Ready(r.unwrap())
}
}
40 changes: 29 additions & 11 deletions gui/src/rt/mod.rs
Original file line number Diff line number Diff line change
@@ -1,52 +1,70 @@
pub use self::context::*;

use self::event::WindowEvent;
use futures::executor::LocalPool;
use futures::task::LocalSpawnExt;
use std::future::Future;
use thiserror::Error;
use winit::application::ApplicationHandler;
use winit::error::EventLoopError;
use winit::event::WindowEvent;
use winit::event_loop::{ActiveEventLoop, EventLoop};
use winit::window::WindowId;

mod context;
mod event;

pub fn block_on(main: impl Future<Output = ()> + 'static) -> Result<(), RuntimeError> {
// Setup winit event loop.
let mut el = EventLoop::<Event>::with_user_event();
let el = el.build().map_err(RuntimeError::CreateEventLoop)?;
let exe = LocalPool::new();
let executor = LocalPool::new();

exe.spawner()
executor
.spawner()
.spawn_local(async move {
main.await;
RuntimeContext::with(|cx| cx.event_loop().exit());
})
.unwrap();

// Run event loop.
el.run_app(&mut AsyncExecutor(exe))
.map_err(RuntimeError::RunEventLoop)
let mut rt = Runtime {
executor,
on_close: WindowEvent::default(),
};

el.run_app(&mut rt).map_err(RuntimeError::RunEventLoop)
}

/// Implementation of [`ApplicationHandler`] to drive [`Future`].
struct AsyncExecutor(LocalPool);
struct Runtime {
executor: LocalPool,
on_close: WindowEvent<()>,
}

impl ApplicationHandler<Event> for AsyncExecutor {
impl ApplicationHandler<Event> for Runtime {
fn resumed(&mut self, event_loop: &ActiveEventLoop) {
let cx = RuntimeContext::new(event_loop);
let mut cx = RuntimeContext {
el: event_loop,
on_close: &mut self.on_close,
};

cx.run(|| self.0.run_until_stalled());
cx.run(|| self.executor.run_until_stalled());
}

fn window_event(
&mut self,
event_loop: &ActiveEventLoop,
window_id: WindowId,
event: WindowEvent,
event: winit::event::WindowEvent,
) {
todo!()
use winit::event::WindowEvent;

match event {
WindowEvent::CloseRequested => self.on_close.raise(window_id, ()),
WindowEvent::RedrawRequested => todo!(),
_ => {}
}
}
}

Expand Down
3 changes: 2 additions & 1 deletion gui/src/ui/backend/mod.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use self::window::Window;
pub(super) use self::window::Window;

use crate::rt::RuntimeContext;
use i_slint_renderer_skia::SkiaRenderer;
use slint::platform::WindowAdapter;
Expand Down
24 changes: 23 additions & 1 deletion gui/src/ui/backend/window.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
use i_slint_core::window::WindowAdapterInternal;
use i_slint_core::InternalToken;
use i_slint_renderer_skia::SkiaRenderer;
use slint::platform::{Renderer, WindowAdapter};
use slint::PhysicalSize;
use slint::{PhysicalSize, PlatformError};
use std::any::Any;
use std::rc::Rc;
use winit::window::WindowId;

/// Implementation of [`WindowAdapter`].
pub struct Window {
Expand All @@ -22,13 +26,21 @@ impl Window {
renderer,
}
}

pub fn id(&self) -> WindowId {
self.winit.id()
}
}

impl WindowAdapter for Window {
fn window(&self) -> &slint::Window {
&self.slint
}

fn set_visible(&self, visible: bool) -> Result<(), PlatformError> {
todo!()
}

fn size(&self) -> PhysicalSize {
let s = self.winit.inner_size();

Expand All @@ -38,4 +50,14 @@ impl WindowAdapter for Window {
fn renderer(&self) -> &dyn Renderer {
&self.renderer
}

fn internal(&self, _: InternalToken) -> Option<&dyn WindowAdapterInternal> {
Some(self)
}
}

impl WindowAdapterInternal for Window {
fn as_any(&self) -> &dyn Any {
self
}
}
17 changes: 16 additions & 1 deletion gui/src/ui/mod.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
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;

mod backend;
Expand All @@ -13,7 +16,19 @@ pub trait RuntimeExt: ComponentHandle {

impl<T: ComponentHandle> RuntimeExt for T {
async fn exec(&self) -> Result<(), slint::PlatformError> {
todo!()
let win = WindowInner::from_pub(self.window()).window_adapter();
let win = win
.internal(InternalToken)
.unwrap()
.as_any()
.downcast_ref::<Window>()
.unwrap();

self.show()?;
RuntimeContext::with(|cx| cx.on_close(win.id())).await;
self.hide()?;

Ok(())
}
}

Expand Down

0 comments on commit 48b7a04

Please sign in to comment.