1
1
mirror of https://github.com/wez/wezterm.git synced 2024-11-22 22:42:48 +03:00

promise: more properly implement Future::poll

The future won't ever complete if you don't connect the waker
from the context!

Prove this out by making the windowops functions async and
verifying them in the async example
This commit is contained in:
Wez Furlong 2019-11-22 23:37:02 -08:00
parent 152874dd23
commit 14fbf43485
12 changed files with 254 additions and 102 deletions

View File

@ -45,6 +45,7 @@ enum FutureState<T> {
struct CoreData<T> {
result: Option<Result<T, Error>>,
propagate: Option<NextFunc<T>>,
waker: Option<std::task::Waker>,
}
struct Core<T> {
@ -78,6 +79,9 @@ impl<T> Drop for Promise<T> {
locked.result = Some(err);
}
core.cond.notify_one();
if let Some(waker) = locked.waker.take() {
waker.wake();
}
}
}
}
@ -88,6 +92,7 @@ impl<T> Promise<T> {
data: Mutex::new(CoreData {
result: None,
propagate: None,
waker: None,
}),
cond: Condvar::new(),
});
@ -118,7 +123,12 @@ impl<T> Promise<T> {
let mut locked = core.data.lock().unwrap();
match locked.propagate.take() {
Some(func) => func(result),
None => locked.result = Some(result),
None => {
locked.result = Some(result);
if let Some(waker) = locked.waker.take() {
waker.wake();
}
}
}
core.cond.notify_one();
}
@ -297,7 +307,7 @@ impl<T: Send + 'static> Future<T> {
impl<T: Send + 'static> std::future::Future for Future<T> {
type Output = Result<T, Error>;
fn poll(self: Pin<&mut Self>, _ctx: &mut Context) -> Poll<Self::Output> {
fn poll(self: Pin<&mut Self>, ctx: &mut Context) -> Poll<Self::Output> {
// This should be safe because we're not moving the Future,
// but instead replacing a field, and since no one is able to
// reference the state field, we should be ok with moving that.
@ -309,6 +319,8 @@ impl<T: Send + 'static> std::future::Future for Future<T> {
let mut locked = core.data.lock().unwrap();
if let Some(result) = locked.result.take() {
return Poll::Ready(result);
} else {
locked.waker = Some(ctx.waker().clone());
}
drop(locked);
myself.state = FutureState::Waiting(core);

View File

@ -192,7 +192,9 @@ impl WindowCallbacks for TermWindow {
self.update_title();
}
}
_ => context.invalidate(),
_ => {
context.invalidate();
}
}
// When hovering over a hyperlink, show an appropriate
@ -436,6 +438,7 @@ impl TermWindow {
};
window.show();
Ok(())
});
} else {
window.show();

View File

@ -71,9 +71,9 @@ async fn spawn_window() -> Result<(), Box<dyn std::error::Error>> {
}),
)?;
eprintln!("here I am");
win.show();
eprintln!("and here");
eprintln!("before show");
win.show().await?;
eprintln!("after show");
win.apply(|myself, _win| {
eprintln!("doing apply");
if let Some(myself) = myself.downcast_ref::<MyWindow>() {
@ -82,7 +82,9 @@ async fn spawn_window() -> Result<(), Box<dyn std::error::Error>> {
myself.allow_close, myself.cursor_pos
);
}
});
Ok(())
})
.await?;
eprintln!("done with spawn_window");
Ok(())
}
@ -92,6 +94,7 @@ fn main() -> Fallible<()> {
conn.spawn_task(async {
eprintln!("running this async block");
spawn_window().await.ok();
eprintln!("end of async block");
});
conn.run_message_loop()
}

View File

@ -103,6 +103,7 @@ fn spawn_window() -> Fallible<()> {
myself.allow_close, myself.cursor_pos
);
}
Ok(())
});
Ok(())
}

View File

@ -42,9 +42,12 @@ fn spawn_window() -> Fallible<()> {
)?;
#[cfg(feature = "opengl")]
win.enable_opengl(|_any, _window, maybe_ctx| match maybe_ctx {
Ok(_ctx) => eprintln!("opengl enabled!"),
Err(err) => eprintln!("opengl fail: {}", err),
win.enable_opengl(|_any, _window, maybe_ctx| {
match maybe_ctx {
Ok(_ctx) => eprintln!("opengl enabled!"),
Err(err) => eprintln!("opengl fail: {}", err),
};
Ok(())
});
#[cfg(not(feature = "opengl"))]
@ -60,6 +63,7 @@ fn spawn_window() -> Fallible<()> {
myself.allow_close, myself.cursor_pos
);
}
Ok(())
});
Ok(())
}

View File

@ -1,3 +1,4 @@
use promise::Future;
use std::any::Any;
pub mod bitmaps;
pub mod color;
@ -151,54 +152,63 @@ pub trait WindowCallbacks: Any {
pub trait WindowOps {
/// Show a hidden window
fn show(&self);
fn show(&self) -> Future<()>;
/// Hide a visible window
fn hide(&self);
fn hide(&self) -> Future<()>;
/// Schedule the window to be closed
fn close(&self);
fn close(&self) -> Future<()>;
/// Change the cursor
fn set_cursor(&self, cursor: Option<MouseCursor>);
fn set_cursor(&self, cursor: Option<MouseCursor>) -> Future<()>;
/// Invalidate the window so that the entire client area will
/// be repainted shortly
fn invalidate(&self);
fn invalidate(&self) -> Future<()>;
/// Change the titlebar text for the window
fn set_title(&self, title: &str);
fn set_title(&self, title: &str) -> Future<()>;
/// Resize the inner or client area of the window
fn set_inner_size(&self, width: usize, height: usize);
fn set_inner_size(&self, width: usize, height: usize) -> Future<()>;
/// inform the windowing system of the current textual
/// cursor input location. This is used primarily for
/// the platform specific input method editor
fn set_text_cursor_position(&self, _cursor: Rect) {}
fn set_text_cursor_position(&self, _cursor: Rect) -> Future<()> {
Future::ok(())
}
/// Schedule a callback on the data associated with the window.
/// The `Any` that is passed in corresponds to the WindowCallbacks
/// impl you passed to `new_window`, pre-converted to Any so that
/// you can `downcast_ref` or `downcast_mut` it and operate on it.
fn apply<F: Send + 'static + Fn(&mut dyn Any, &dyn WindowOps)>(&self, func: F)
fn apply<R, F: Send + 'static + Fn(&mut dyn Any, &dyn WindowOps) -> failure::Fallible<R>>(
&self,
func: F,
) -> promise::Future<R>
where
Self: Sized;
Self: Sized,
R: Send + 'static;
#[cfg(feature = "opengl")]
fn enable_opengl<
R,
F: Send
+ 'static
+ Fn(
&mut dyn Any,
&dyn WindowOps,
failure::Fallible<std::rc::Rc<glium::backend::Context>>,
),
) -> failure::Fallible<R>,
>(
&self,
func: F,
) where
Self: Sized;
) -> promise::Future<R>
where
Self: Sized,
R: Send + 'static;
}
pub trait WindowOpsMut {

View File

@ -52,16 +52,23 @@ impl Connection {
self.windows.borrow().get(&window_id).map(Rc::clone)
}
pub(crate) fn with_window_inner<F: FnMut(&mut WindowInner) + Send + 'static>(
pub(crate) fn with_window_inner<R, F: FnMut(&mut WindowInner) -> Fallible<R> + Send + 'static>(
window_id: usize,
mut f: F,
) {
) -> promise::Future<R>
where
R: Send + 'static,
{
let mut prom = promise::Promise::new();
let future = prom.get_future().unwrap();
SpawnQueueExecutor {}.execute(Box::new(move || {
if let Some(handle) = Connection::get().unwrap().window_by_id(window_id) {
let mut inner = handle.borrow_mut();
f(&mut inner);
prom.result(f(&mut inner));
}
}));
future
}
pub fn executor() -> impl BasicExecutor {

View File

@ -23,6 +23,7 @@ use objc::declare::ClassDecl;
use objc::rc::{StrongPtr, WeakPtr};
use objc::runtime::{Class, Object, Protocol, Sel};
use objc::*;
use promise::Future;
use std::any::Any;
use std::cell::RefCell;
use std::ffi::c_void;
@ -342,64 +343,99 @@ impl Window {
}
impl WindowOps for Window {
fn close(&self) {
Connection::with_window_inner(self.0, |inner| inner.close());
fn close(&self) -> Future<()> {
Connection::with_window_inner(self.0, |inner| {
inner.close();
Ok(())
})
}
fn hide(&self) {
Connection::with_window_inner(self.0, |inner| inner.hide());
fn hide(&self) -> Future<()> {
Connection::with_window_inner(self.0, |inner| {
inner.hide();
Ok(())
})
}
fn show(&self) {
Connection::with_window_inner(self.0, |inner| inner.show());
fn show(&self) -> Future<()> {
Connection::with_window_inner(self.0, |inner| {
inner.show();
Ok(())
})
}
fn set_cursor(&self, cursor: Option<MouseCursor>) {
fn set_cursor(&self, cursor: Option<MouseCursor>) -> Future<()> {
Connection::with_window_inner(self.0, move |inner| {
let _ = inner.set_cursor(cursor);
});
Ok(())
})
}
fn invalidate(&self) {
Connection::with_window_inner(self.0, |inner| inner.invalidate());
fn invalidate(&self) -> Future<()> {
Connection::with_window_inner(self.0, |inner| {
inner.invalidate();
Ok(())
})
}
fn set_title(&self, title: &str) {
fn set_title(&self, title: &str) -> Future<()> {
let title = title.to_owned();
Connection::with_window_inner(self.0, move |inner| inner.set_title(&title));
Connection::with_window_inner(self.0, move |inner| {
inner.set_title(&title);
Ok(())
})
}
fn set_inner_size(&self, width: usize, height: usize) {
Connection::with_window_inner(self.0, move |inner| inner.set_inner_size(width, height));
fn set_inner_size(&self, width: usize, height: usize) -> Future<()> {
Connection::with_window_inner(self.0, move |inner| {
inner.set_inner_size(width, height);
Ok(())
})
}
fn set_text_cursor_position(&self, cursor: Rect) {
Connection::with_window_inner(self.0, move |inner| inner.set_text_cursor_position(cursor));
fn set_text_cursor_position(&self, cursor: Rect) -> Future<()> {
Connection::with_window_inner(self.0, move |inner| {
inner.set_text_cursor_position(cursor);
Ok(())
})
}
fn apply<F: Send + 'static + Fn(&mut dyn Any, &dyn WindowOps)>(&self, func: F)
fn apply<R, F: Send + 'static + Fn(&mut dyn Any, &dyn WindowOps) -> Fallible<R>>(
&self,
func: F,
) -> promise::Future<R>
where
Self: Sized,
R: Send + 'static,
{
Connection::with_window_inner(self.0, move |inner| {
let window = Window(inner.window_id);
if let Some(window_view) = WindowView::get_this(unsafe { &**inner.view }) {
func(window_view.inner.borrow_mut().callbacks.as_any(), &window);
func(window_view.inner.borrow_mut().callbacks.as_any(), &window)
} else {
failure::bail!("apply: window is invalid");
}
});
})
}
#[cfg(feature = "opengl")]
fn enable_opengl<
R,
F: Send
+ 'static
+ Fn(
&mut dyn Any,
&dyn WindowOps,
failure::Fallible<std::rc::Rc<glium::backend::Context>>,
),
) -> failure::Fallible<R>,
>(
&self,
func: F,
) where
) -> promise::Future<R>
where
Self: Sized,
R: Send + 'static,
{
Connection::with_window_inner(self.0, move |inner| {
let window = Window(inner.window_id);
@ -414,9 +450,11 @@ impl WindowOps for Window {
window_view.inner.borrow_mut().callbacks.as_any(),
&window,
glium_context.map(|pair| pair.context),
);
)
} else {
failure::bail!("enable_opengl: window is invalid");
}
});
})
}
}

View File

@ -135,18 +135,25 @@ impl Connection {
self.windows.borrow().get(&handle).map(Rc::clone)
}
pub(crate) fn with_window_inner<F: FnMut(&mut WindowInner) + Send + 'static>(
pub(crate) fn with_window_inner<R, F: FnMut(&mut WindowInner) -> Fallible<R> + Send + 'static>(
window: HWindow,
mut f: F,
) {
) -> promise::Future<R>
where
R: Send + 'static,
{
let mut prom = promise::Promise::new();
let future = prom.get_future().unwrap();
SpawnQueueExecutor {}.execute(Box::new(move || {
if let Some(handle) = Connection::get()
.expect("Connection::init has not been called")
.get_window(window)
{
let mut inner = handle.borrow_mut();
f(&mut inner);
prom.result(f(&mut inner));
}
}));
future
}
}

View File

@ -211,7 +211,7 @@ impl Window {
}
}
fn schedule_show_window(hwnd: HWindow, show: bool) {
fn schedule_show_window(hwnd: HWindow, show: bool) -> Future<()> {
// ShowWindow can call to the window proc and may attempt
// to lock inner, so we avoid locking it ourselves here
Future::with_executor(Connection::executor(), move || {
@ -219,7 +219,7 @@ fn schedule_show_window(hwnd: HWindow, show: bool) {
ShowWindow(hwnd.0, if show { SW_NORMAL } else { SW_HIDE });
}
Ok(())
});
})
}
impl WindowOpsMut for WindowInner {
@ -282,60 +282,87 @@ impl WindowOpsMut for WindowInner {
}
impl WindowOps for Window {
fn close(&self) {
Connection::with_window_inner(self.0, |inner| inner.close());
fn close(&self) -> Future<()> {
Connection::with_window_inner(self.0, |inner| {
inner.close();
Ok(())
})
}
fn show(&self) {
schedule_show_window(self.0, true);
fn show(&self) -> Future<()> {
schedule_show_window(self.0, true)
}
fn hide(&self) {
schedule_show_window(self.0, false);
fn hide(&self) -> Future<()> {
schedule_show_window(self.0, false)
}
fn set_cursor(&self, cursor: Option<MouseCursor>) {
Connection::with_window_inner(self.0, move |inner| inner.set_cursor(cursor));
fn set_cursor(&self, cursor: Option<MouseCursor>) -> Future<()> {
Connection::with_window_inner(self.0, move |inner| {
inner.set_cursor(cursor);
Ok(())
})
}
fn invalidate(&self) {
Connection::with_window_inner(self.0, |inner| inner.invalidate());
fn invalidate(&self) -> Future<()> {
Connection::with_window_inner(self.0, |inner| {
inner.invalidate();
Ok(())
})
}
fn set_title(&self, title: &str) {
fn set_title(&self, title: &str) -> Future<()> {
let title = title.to_owned();
Connection::with_window_inner(self.0, move |inner| inner.set_title(&title));
}
fn set_text_cursor_position(&self, cursor: Rect) {
Connection::with_window_inner(self.0, move |inner| inner.set_text_cursor_position(cursor));
Connection::with_window_inner(self.0, move |inner| {
inner.set_title(&title);
Ok(())
})
}
fn set_inner_size(&self, width: usize, height: usize) {
Connection::with_window_inner(self.0, move |inner| inner.set_inner_size(width, height));
fn set_text_cursor_position(&self, cursor: Rect) -> Future<()> {
Connection::with_window_inner(self.0, move |inner| {
inner.set_text_cursor_position(cursor);
Ok(())
})
}
fn apply<F: Send + 'static + Fn(&mut dyn Any, &dyn WindowOps)>(&self, func: F)
fn set_inner_size(&self, width: usize, height: usize) -> Future<()> {
Connection::with_window_inner(self.0, move |inner| {
inner.set_inner_size(width, height);
Ok(())
})
}
fn apply<R, F: Send + 'static + Fn(&mut dyn Any, &dyn WindowOps) -> Fallible<R>>(
&self,
func: F,
) -> promise::Future<R>
where
Self: Sized,
R: Send + 'static,
{
Connection::with_window_inner(self.0, move |inner| {
let window = Window(inner.hwnd);
func(inner.callbacks.borrow_mut().as_any(), &window);
});
func(inner.callbacks.borrow_mut().as_any(), &window)
})
}
#[cfg(feature = "opengl")]
fn enable_opengl<
R,
F: Send
+ 'static
+ Fn(
&mut dyn Any,
&dyn WindowOps,
failure::Fallible<std::rc::Rc<glium::backend::Context>>,
),
) -> failure::Fallible<R>,
>(
&self,
func: F,
) where
) -> promise::Future<R>
where
Self: Sized,
R: Send + 'static,
{
Connection::with_window_inner(self.0, move |inner| {
let window = Window(inner.hwnd);
@ -356,8 +383,8 @@ impl WindowOps for Window {
inner.gl_state = gl_state.as_ref().map(Rc::clone).ok();
func(inner.callbacks.borrow_mut().as_any(), &window, gl_state);
});
func(inner.callbacks.borrow_mut().as_any(), &window, gl_state)
})
}
}

View File

@ -480,16 +480,24 @@ impl Connection {
LowPriSpawnQueueExecutor {}
}
pub(crate) fn with_window_inner<F: FnMut(&mut WindowInner) + Send + 'static>(
pub(crate) fn with_window_inner<R, F: FnMut(&mut WindowInner) -> Fallible<R> + Send + 'static>(
window: xcb::xproto::Window,
mut f: F,
) {
) -> promise::Future<R>
where
R: Send + 'static,
{
let mut prom = promise::Promise::new();
let future = prom.get_future().unwrap();
SpawnQueueExecutor {}.execute(Box::new(move || {
if let Some(handle) = Connection::get().unwrap().window_by_id(window) {
let mut inner = handle.lock().unwrap();
f(&mut inner);
prom.result(f(&mut inner));
}
}));
future
}
}

View File

@ -6,6 +6,7 @@ use crate::{
Operator, PaintContext, Point, Rect, Size, WindowCallbacks, WindowOps, WindowOpsMut,
};
use failure::Fallible;
use promise::Future;
use std::any::Any;
use std::collections::VecDeque;
use std::convert::TryInto;
@ -485,56 +486,87 @@ impl WindowOpsMut for WindowInner {
}
impl WindowOps for Window {
fn close(&self) {
Connection::with_window_inner(self.0, |inner| inner.close());
fn close(&self) -> Future<()> {
Connection::with_window_inner(self.0, |inner| {
inner.close();
Ok(())
})
}
fn hide(&self) {
Connection::with_window_inner(self.0, |inner| inner.hide());
fn hide(&self) -> Future<()> {
Connection::with_window_inner(self.0, |inner| {
inner.hide();
Ok(())
})
}
fn show(&self) {
Connection::with_window_inner(self.0, |inner| inner.show());
fn show(&self) -> Future<()> {
Connection::with_window_inner(self.0, |inner| {
inner.show();
Ok(())
})
}
fn set_cursor(&self, cursor: Option<MouseCursor>) {
fn set_cursor(&self, cursor: Option<MouseCursor>) -> Future<()> {
Connection::with_window_inner(self.0, move |inner| {
let _ = inner.set_cursor(cursor);
});
Ok(())
})
}
fn invalidate(&self) {
Connection::with_window_inner(self.0, |inner| inner.invalidate());
fn invalidate(&self) -> Future<()> {
Connection::with_window_inner(self.0, |inner| {
inner.invalidate();
Ok(())
})
}
fn set_title(&self, title: &str) {
fn set_title(&self, title: &str) -> Future<()> {
let title = title.to_owned();
Connection::with_window_inner(self.0, move |inner| inner.set_title(&title));
Connection::with_window_inner(self.0, move |inner| {
inner.set_title(&title);
Ok(())
})
}
fn set_inner_size(&self, width: usize, height: usize) {
Connection::with_window_inner(self.0, move |inner| inner.set_inner_size(width, height));
fn set_inner_size(&self, width: usize, height: usize) -> Future<()> {
Connection::with_window_inner(self.0, move |inner| {
inner.set_inner_size(width, height);
Ok(())
})
}
fn apply<F: Send + 'static + Fn(&mut dyn Any, &dyn WindowOps)>(&self, func: F)
fn apply<R, F: Send + 'static + Fn(&mut dyn Any, &dyn WindowOps) -> Fallible<R>>(
&self,
func: F,
) -> promise::Future<R>
where
Self: Sized,
R: Send + 'static,
{
Connection::with_window_inner(self.0, move |inner| {
let window = Window(inner.window_id);
func(inner.callbacks.as_any(), &window);
});
func(inner.callbacks.as_any(), &window)
})
}
#[cfg(feature = "opengl")]
fn enable_opengl<
R,
F: Send
+ 'static
+ Fn(
&mut dyn Any,
&dyn WindowOps,
failure::Fallible<std::rc::Rc<glium::backend::Context>>,
),
) -> failure::Fallible<R>,
>(
&self,
func: F,
) where
) -> promise::Future<R>
where
Self: Sized,
R: Send + 'static,
{
Connection::with_window_inner(self.0, move |inner| {
let window = Window(inner.window_id);
@ -558,7 +590,7 @@ impl WindowOps for Window {
inner.gl_state = gl_state.as_ref().map(Rc::clone).ok();
func(inner.callbacks.as_any(), &window, gl_state);
});
func(inner.callbacks.as_any(), &window, gl_state)
})
}
}