Compare commits

...

10 Commits

Author SHA1 Message Date
Mikayla Maki
aac6168d74 Add the concept of external tasks, to expose the fallibility of GPUI tasks 2025-03-13 20:35:23 -07:00
Mikayla Maki
91da475b64 Remove foreground task and option return type 2025-03-13 20:26:23 -07:00
Mikayla
fcc82019fb ContextTask -> ForegroundTask 2025-03-13 19:33:07 -07:00
Mikayla
adce2dda14 Restore executor cloning APIs for Zed's test infrastructure
but hide them from general use, as using these APIs can cause panics
2025-03-13 19:31:53 -07:00
Mikayla
51390ab00c Document ForegroundContext 2025-03-13 19:31:53 -07:00
Mikayla
d6f960b68c Add Weak variants of AsyncApp and AsyncWindow context 2025-03-13 19:31:51 -07:00
Mikayla
c5688fde73 Move thread id check inside spawned futures.
for some reason, it seems like the metadata drop can sometimes happen on other threads
2025-03-13 19:26:50 -07:00
Mikayla
66eb3d6dc4 Fix some potential runtime panics with rust lifetimes 2025-03-13 19:26:49 -07:00
Mikayla
5231937f3c Add a ContextTask type, and thread it through GPUI 2025-03-13 19:25:30 -07:00
Mikayla Maki
d3ff40ef01 Proof of concept: move the window and app context checks into the executor 2025-03-13 19:20:36 -07:00
16 changed files with 1113 additions and 598 deletions

358
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -3,7 +3,8 @@ use std::{fs, path::PathBuf, time::Duration};
use anyhow::Result;
use gpui::{
div, hsla, img, point, prelude::*, px, rgb, size, svg, App, Application, AssetSource, Bounds,
BoxShadow, ClickEvent, Context, SharedString, Task, Timer, Window, WindowBounds, WindowOptions,
BoxShadow, ClickEvent, Context, ForegroundTask, SharedString, Timer, Window, WindowBounds,
WindowOptions,
};
struct Assets {
@@ -34,7 +35,7 @@ impl AssetSource for Assets {
}
struct HelloWorld {
_task: Option<Task<()>>,
_task: Option<ForegroundTask<()>>,
opacity: f32,
}

View File

@@ -32,12 +32,12 @@ use util::ResultExt;
use crate::{
current_platform, hash, init_app_menus, Action, ActionBuildError, ActionRegistry, Any, AnyView,
AnyWindowHandle, AppContext, Asset, AssetSource, BackgroundExecutor, Bounds, ClipboardItem,
DispatchPhase, DisplayId, EventEmitter, FocusHandle, FocusMap, ForegroundExecutor, Global,
KeyBinding, Keymap, Keystroke, LayoutId, Menu, MenuItem, OwnedMenu, PathPromptOptions, Pixels,
Platform, PlatformDisplay, Point, PromptBuilder, PromptHandle, PromptLevel, Render,
RenderablePromptHandle, Reservation, ScreenCaptureSource, SharedString, SubscriberSet,
Subscription, SvgRenderer, Task, TextSystem, Window, WindowAppearance, WindowHandle, WindowId,
WindowInvalidator,
DispatchPhase, DisplayId, EventEmitter, FocusHandle, FocusMap, ForegroundContext,
ForegroundExecutor, Global, KeyBinding, Keymap, Keystroke, LayoutId, Menu, MenuItem, OwnedMenu,
PathPromptOptions, Pixels, Platform, PlatformDisplay, Point, PromptBuilder, PromptHandle,
PromptLevel, Render, RenderablePromptHandle, Reservation, ScreenCaptureSource, SharedString,
SubscriberSet, Subscription, SvgRenderer, Task, TextSystem, Window, WindowAppearance,
WindowHandle, WindowId, WindowInvalidator,
};
mod async_context;
@@ -1026,10 +1026,10 @@ impl App {
Ok(result)
})
}
/// Creates an `AsyncApp`, which can be cloned and has a static lifetime
/// Creates a `WeakAsyncApp`, which can be cloned and has a static lifetime
/// so it can be held across `await` points.
pub fn to_async(&self) -> AsyncApp {
AsyncApp {
pub fn to_async(&self) -> WeakAsyncApp {
WeakAsyncApp {
app: self.this.clone(),
background_executor: self.background_executor.clone(),
foreground_executor: self.foreground_executor.clone(),
@@ -1054,7 +1054,11 @@ impl App {
Fut: Future<Output = R> + 'static,
R: 'static,
{
self.foreground_executor.spawn(f(self.to_async()))
let async_app = self.to_async();
self.foreground_executor.spawn_with_context(
ForegroundContext::app(&async_app.app),
f(async_app.upgrade()),
)
}
/// Schedules the given function to be run at the end of the current effect cycle, allowing entities
@@ -1596,8 +1600,6 @@ impl App {
}
impl AppContext for App {
type Result<T> = T;
/// Build an entity that is owned by the application. The given function will be invoked with
/// a `Context` and must return an object representing the entity. A `Entity` handle will be returned,
/// which can be used to access the entity in a context.
@@ -1621,7 +1623,7 @@ impl AppContext for App {
})
}
fn reserve_entity<T: 'static>(&mut self) -> Self::Result<Reservation<T>> {
fn reserve_entity<T: 'static>(&mut self) -> Reservation<T> {
Reservation(self.entities.reserve())
}
@@ -1629,7 +1631,7 @@ impl AppContext for App {
&mut self,
reservation: Reservation<T>,
build_entity: impl FnOnce(&mut Context<'_, T>) -> T,
) -> Self::Result<Entity<T>> {
) -> Entity<T> {
self.update(|cx| {
let slot = reservation.0;
let entity = build_entity(&mut Context::new_context(cx, slot.downgrade()));
@@ -1655,11 +1657,7 @@ impl AppContext for App {
})
}
fn read_entity<T, R>(
&self,
handle: &Entity<T>,
read: impl FnOnce(&T, &App) -> R,
) -> Self::Result<R>
fn read_entity<T, R>(&self, handle: &Entity<T>, read: impl FnOnce(&T, &App) -> R) -> R
where
T: 'static,
{
@@ -1704,7 +1702,7 @@ impl AppContext for App {
self.background_executor.spawn(future)
}
fn read_global<G, R>(&self, callback: impl FnOnce(&G, &App) -> R) -> Self::Result<R>
fn read_global<G, R>(&self, callback: impl FnOnce(&G, &App) -> R) -> R
where
G: Global,
{
@@ -1842,3 +1840,38 @@ impl HttpClient for NullHttpClient {
type_name::<Self>()
}
}
/// The AsyncApp, AsyncWindowContext, and executor types must not be clone, in order to
/// protect against panics that are caused when moving an AsyncApp across an
/// await point, in a task that doesn't share it's context.
///
/// For example, an issue with AsyncWindowContext:
///
/// ```rs
/// fn test(cx: AsyncWindowContext) {
/// let cx_2 = cx.clone();
/// cx.update(|_, app: &mut App| {
/// app.spawn(|_| {
/// my_async_thing().await;
/// cx_2.update(/**/) // 💥 Window wasn't checked after `.await`, this might panic
/// })
/// })
/// }
///```
///
/// Or using ForegroundExecutor:
///
/// ```rs
/// fn test(cx: AsyncApp) {
/// let executor = cx.foreground_executor().clone();
/// executor.spawn(async {
/// my_async_thing().await;
/// cx.update(/**/) // 💥 Didn't check that the app still existed after await, this might panic
/// })
/// }
/// ```
///
/// Having these types be !Clone, ensures that they'll be locked behind a `&`
/// when calling `spawn`, which means they won't be able to be moved inside
/// the spawn call (which has a + 'static bound)
pub(crate) struct NotClone;

View File

@@ -1,95 +1,63 @@
use crate::{
AnyView, AnyWindowHandle, App, AppCell, AppContext, BackgroundExecutor, BorrowAppContext,
Entity, Focusable, ForegroundExecutor, Global, PromptLevel, Render, Reservation, Result, Task,
VisualContext, Window, WindowHandle,
Entity, Focusable, ForegroundContext, ForegroundExecutor, Global, PromptLevel, Render,
Reservation, Result, Task, VisualContext, Window, WindowHandle,
};
use anyhow::{anyhow, Context as _};
use anyhow::Context as _;
use derive_more::{Deref, DerefMut};
use futures::channel::oneshot;
use std::{future::Future, rc::Weak};
use super::Context;
use super::{Context, NotClone};
/// An async-friendly version of [App] with a static lifetime so it can be held across `await` points in async code.
/// You're provided with an instance when calling [App::spawn], and you can also create one with [App::to_async].
/// Internally, this holds a weak reference to an `App`, so its methods are fallible to protect against cases where the [App] is dropped.
#[derive(Clone)]
pub struct AsyncApp {
pub(crate) app: Weak<AppCell>,
pub(crate) background_executor: BackgroundExecutor,
pub(crate) foreground_executor: ForegroundExecutor,
pub(crate) app: WeakAsyncApp,
_not_clone: NotClone,
}
impl AppContext for AsyncApp {
type Result<T> = Result<T>;
fn new<T: 'static>(
&mut self,
build_entity: impl FnOnce(&mut Context<'_, T>) -> T,
) -> Self::Result<Entity<T>> {
let app = self
.app
.upgrade()
.ok_or_else(|| anyhow!("app was released"))?;
let mut app = app.borrow_mut();
Ok(app.new(build_entity))
) -> Entity<T> {
self.app.new(build_entity).unwrap()
}
fn reserve_entity<T: 'static>(&mut self) -> Result<Reservation<T>> {
let app = self
.app
.upgrade()
.ok_or_else(|| anyhow!("app was released"))?;
let mut app = app.borrow_mut();
Ok(app.reserve_entity())
fn reserve_entity<T: 'static>(&mut self) -> Reservation<T> {
self.app.reserve_entity().unwrap()
}
fn insert_entity<T: 'static>(
&mut self,
reservation: Reservation<T>,
build_entity: impl FnOnce(&mut Context<'_, T>) -> T,
) -> Result<Entity<T>> {
let app = self
.app
.upgrade()
.ok_or_else(|| anyhow!("app was released"))?;
let mut app = app.borrow_mut();
Ok(app.insert_entity(reservation, build_entity))
) -> Entity<T> {
self.app.insert_entity(reservation, build_entity).unwrap()
}
fn update_entity<T: 'static, R>(
&mut self,
handle: &Entity<T>,
update: impl FnOnce(&mut T, &mut Context<'_, T>) -> R,
) -> Self::Result<R> {
let app = self
.app
.upgrade()
.ok_or_else(|| anyhow!("app was released"))?;
let mut app = app.borrow_mut();
Ok(app.update_entity(handle, update))
) -> R {
self.app.update_entity(handle, update).unwrap()
}
fn read_entity<T, R>(
&self,
handle: &Entity<T>,
callback: impl FnOnce(&T, &App) -> R,
) -> Self::Result<R>
fn read_entity<T, R>(&self, handle: &Entity<T>, callback: impl FnOnce(&T, &App) -> R) -> R
where
T: 'static,
{
let app = self.app.upgrade().context("app was released")?;
let lock = app.borrow();
Ok(lock.read_entity(handle, callback))
self.app.read_entity(handle, callback).unwrap()
}
fn update_window<T, F>(&mut self, window: AnyWindowHandle, f: F) -> Result<T>
where
F: FnOnce(AnyView, &mut Window, &mut App) -> T,
{
let app = self.app.upgrade().context("app was released")?;
let mut lock = app.borrow_mut();
lock.update_window(window, f)
self.app.update_window(window, f).unwrap()
}
fn read_window<T, R>(
@@ -100,58 +68,44 @@ impl AppContext for AsyncApp {
where
T: 'static,
{
let app = self.app.upgrade().context("app was released")?;
let lock = app.borrow();
lock.read_window(window, read)
self.app.read_window(window, read).unwrap()
}
fn background_spawn<R>(&self, future: impl Future<Output = R> + Send + 'static) -> Task<R>
where
R: Send + 'static,
{
self.background_executor.spawn(future)
self.app.background_spawn(future)
}
fn read_global<G, R>(&self, callback: impl FnOnce(&G, &App) -> R) -> Self::Result<R>
fn read_global<G, R>(&self, callback: impl FnOnce(&G, &App) -> R) -> R
where
G: Global,
{
let app = self.app.upgrade().context("app was released")?;
let mut lock = app.borrow_mut();
Ok(lock.update(|this| this.read_global(callback)))
self.app.read_global(callback).unwrap()
}
}
impl AsyncApp {
/// Schedules all windows in the application to be redrawn.
pub fn refresh(&self) -> Result<()> {
let app = self
.app
.upgrade()
.ok_or_else(|| anyhow!("app was released"))?;
let mut lock = app.borrow_mut();
lock.refresh_windows();
Ok(())
pub fn refresh(&self) {
self.app.refresh().unwrap()
}
/// Get an executor which can be used to spawn futures in the background.
pub fn background_executor(&self) -> &BackgroundExecutor {
&self.background_executor
self.app.background_executor()
}
/// Get an executor which can be used to spawn futures in the foreground.
pub fn foreground_executor(&self) -> &ForegroundExecutor {
&self.foreground_executor
self.app.foreground_executor()
}
/// Invoke the given function in the context of the app, then flush any effects produced during its invocation.
pub fn update<R>(&self, f: impl FnOnce(&mut App) -> R) -> Result<R> {
let app = self
.app
.upgrade()
.ok_or_else(|| anyhow!("app was released"))?;
let mut lock = app.borrow_mut();
Ok(lock.update(f))
/// Panics if the app has been dropped since this was created
pub fn update<R>(&self, f: impl FnOnce(&mut App) -> R) -> R {
self.app.update(f).unwrap()
}
/// Open a window with the given options based on the root view returned by the given function.
@@ -163,12 +117,7 @@ impl AsyncApp {
where
V: 'static + Render,
{
let app = self
.app
.upgrade()
.ok_or_else(|| anyhow!("app was released"))?;
let mut lock = app.borrow_mut();
lock.open_window(options, build_root_view)
self.app.open_window(options, build_root_view).unwrap()
}
/// Schedule a future to be polled in the background.
@@ -178,31 +127,21 @@ impl AsyncApp {
Fut: Future<Output = R> + 'static,
R: 'static,
{
self.foreground_executor.spawn(f(self.clone()))
self.app.spawn(f)
}
/// Determine whether global state of the specified type has been assigned.
/// Returns an error if the `App` has been dropped.
pub fn has_global<G: Global>(&self) -> Result<bool> {
let app = self
.app
.upgrade()
.ok_or_else(|| anyhow!("app was released"))?;
let app = app.borrow_mut();
Ok(app.has_global::<G>())
pub fn has_global<G: Global>(&self) -> bool {
self.app.has_global::<G>().unwrap()
}
/// Reads the global state of the specified type, passing it to the given callback.
///
/// Panics if no global state of the specified type has been assigned.
/// Returns an error if the `App` has been dropped.
pub fn read_global<G: Global, R>(&self, read: impl FnOnce(&G, &App) -> R) -> Result<R> {
let app = self
.app
.upgrade()
.ok_or_else(|| anyhow!("app was released"))?;
let app = app.borrow_mut();
Ok(read(app.global(), &app))
pub fn read_global<G: Global, R>(&self, read: impl FnOnce(&G, &App) -> R) -> R {
self.app.read_global(read).unwrap()
}
/// Reads the global state of the specified type, passing it to the given callback.
@@ -212,39 +151,32 @@ impl AsyncApp {
///
/// Returns an error if no state of the specified type has been assigned the `App` has been dropped.
pub fn try_read_global<G: Global, R>(&self, read: impl FnOnce(&G, &App) -> R) -> Option<R> {
let app = self.app.upgrade()?;
let app = app.borrow_mut();
Some(read(app.try_global()?, &app))
self.app.try_read_global(read).unwrap()
}
/// A convenience method for [App::update_global]
/// for updating the global state of the specified type.
pub fn update_global<G: Global, R>(
&self,
update: impl FnOnce(&mut G, &mut App) -> R,
) -> Result<R> {
let app = self
.app
.upgrade()
.ok_or_else(|| anyhow!("app was released"))?;
let mut app = app.borrow_mut();
Ok(app.update(|cx| cx.update_global(update)))
pub fn update_global<G: Global, R>(&self, update: impl FnOnce(&mut G, &mut App) -> R) -> R {
self.app.update_global(update).unwrap()
}
}
/// A cloneable, owned handle to the application context,
/// composed with the window associated with the current task.
#[derive(Clone, Deref, DerefMut)]
#[derive(Deref, DerefMut)]
pub struct AsyncWindowContext {
#[deref]
#[deref_mut]
app: AsyncApp,
window: AnyWindowHandle,
cx: WeakAsyncWindowContext,
_not_clone: NotClone,
}
impl AsyncWindowContext {
pub(crate) fn new_context(app: AsyncApp, window: AnyWindowHandle) -> Self {
Self { app, window }
Self {
cx: WeakAsyncWindowContext::new_context(app.app, window),
_not_clone: NotClone,
}
}
/// Get the handle of the window this context is associated with.
@@ -253,33 +185,26 @@ impl AsyncWindowContext {
}
/// A convenience method for [`App::update_window`].
pub fn update<R>(&mut self, update: impl FnOnce(&mut Window, &mut App) -> R) -> Result<R> {
self.app
.update_window(self.window, |_, window, cx| update(window, cx))
pub fn update<R>(&mut self, update: impl FnOnce(&mut Window, &mut App) -> R) -> R {
self.cx.update(update).unwrap()
}
/// A convenience method for [`App::update_window`].
pub fn update_root<R>(
&mut self,
update: impl FnOnce(AnyView, &mut Window, &mut App) -> R,
) -> Result<R> {
self.app.update_window(self.window, update)
) -> R {
self.cx.update_root(update).unwrap()
}
/// A convenience method for [`Window::on_next_frame`].
pub fn on_next_frame(&mut self, f: impl FnOnce(&mut Window, &mut App) + 'static) {
self.window
.update(self, |_, window, _| window.on_next_frame(f))
.ok();
self.cx.on_next_frame(f).unwrap()
}
/// A convenience method for [`App::global`].
pub fn read_global<G: Global, R>(
&mut self,
read: impl FnOnce(&G, &Window, &App) -> R,
) -> Result<R> {
self.window
.update(self, |_, window, cx| read(cx.global(), window, cx))
pub fn read_global<G: Global, R>(&mut self, read: impl FnOnce(&G, &Window, &App) -> R) -> R {
self.cx.read_global(read).unwrap()
}
/// A convenience method for [`App::update_global`].
@@ -287,13 +212,11 @@ impl AsyncWindowContext {
pub fn update_global<G, R>(
&mut self,
update: impl FnOnce(&mut G, &mut Window, &mut App) -> R,
) -> Result<R>
) -> R
where
G: Global,
{
self.window.update(self, |_, window, cx| {
cx.update_global(|global, cx| update(global, window, cx))
})
self.cx.update_global(update).unwrap()
}
/// Schedule a future to be executed on the main thread. This is used for collecting
@@ -304,7 +227,7 @@ impl AsyncWindowContext {
Fut: Future<Output = R> + 'static,
R: 'static,
{
self.foreground_executor.spawn(f(self.clone()))
self.cx.spawn(f)
}
/// Present a platform dialog.
@@ -321,58 +244,50 @@ impl AsyncWindowContext {
.update(self, |_, window, cx| {
window.prompt(level, message, detail, answers, cx)
})
.unwrap_or_else(|_| oneshot::channel().1)
.unwrap()
}
}
impl AppContext for AsyncWindowContext {
type Result<T> = Result<T>;
fn new<T>(&mut self, build_entity: impl FnOnce(&mut Context<'_, T>) -> T) -> Result<Entity<T>>
fn new<T>(&mut self, build_entity: impl FnOnce(&mut Context<'_, T>) -> T) -> Entity<T>
where
T: 'static,
{
self.window.update(self, |_, _, cx| cx.new(build_entity))
self.cx.new(build_entity).unwrap()
}
fn reserve_entity<T: 'static>(&mut self) -> Result<Reservation<T>> {
self.window.update(self, |_, _, cx| cx.reserve_entity())
fn reserve_entity<T: 'static>(&mut self) -> Reservation<T> {
self.cx.reserve_entity().unwrap()
}
fn insert_entity<T: 'static>(
&mut self,
reservation: Reservation<T>,
build_entity: impl FnOnce(&mut Context<'_, T>) -> T,
) -> Self::Result<Entity<T>> {
self.window
.update(self, |_, _, cx| cx.insert_entity(reservation, build_entity))
) -> Entity<T> {
self.cx.insert_entity(reservation, build_entity).unwrap()
}
fn update_entity<T: 'static, R>(
&mut self,
handle: &Entity<T>,
update: impl FnOnce(&mut T, &mut Context<'_, T>) -> R,
) -> Result<R> {
self.window
.update(self, |_, _, cx| cx.update_entity(handle, update))
) -> R {
self.cx.update_entity(handle, update).unwrap()
}
fn read_entity<T, R>(
&self,
handle: &Entity<T>,
read: impl FnOnce(&T, &App) -> R,
) -> Self::Result<R>
fn read_entity<T, R>(&self, handle: &Entity<T>, read: impl FnOnce(&T, &App) -> R) -> R
where
T: 'static,
{
self.app.read_entity(handle, read)
self.cx.read_entity(handle, read).unwrap()
}
fn update_window<T, F>(&mut self, window: AnyWindowHandle, update: F) -> Result<T>
where
F: FnOnce(AnyView, &mut Window, &mut App) -> T,
{
self.app.update_window(window, update)
self.cx.update_window(window, update)
}
fn read_window<T, R>(
@@ -383,21 +298,21 @@ impl AppContext for AsyncWindowContext {
where
T: 'static,
{
self.app.read_window(window, read)
self.cx.read_window(window, read)
}
fn background_spawn<R>(&self, future: impl Future<Output = R> + Send + 'static) -> Task<R>
where
R: Send + 'static,
{
self.app.background_executor.spawn(future)
self.cx.background_spawn(future)
}
fn read_global<G, R>(&self, callback: impl FnOnce(&G, &App) -> R) -> Result<R>
fn read_global<G, R>(&self, callback: impl FnOnce(&G, &App) -> R) -> R
where
G: Global,
{
self.app.read_global(callback)
self.app.read_global(callback).unwrap()
}
}
@@ -409,38 +324,432 @@ impl VisualContext for AsyncWindowContext {
fn new_window_entity<T: 'static>(
&mut self,
build_entity: impl FnOnce(&mut Window, &mut Context<T>) -> T,
) -> Self::Result<Entity<T>> {
self.window
.update(self, |_, window, cx| cx.new(|cx| build_entity(window, cx)))
) -> Entity<T> {
self.cx.new_window_entity(build_entity).unwrap()
}
fn update_window_entity<T: 'static, R>(
&mut self,
view: &Entity<T>,
update: impl FnOnce(&mut T, &mut Window, &mut Context<T>) -> R,
) -> Self::Result<R> {
self.window.update(self, |_, window, cx| {
view.update(cx, |entity, cx| update(entity, window, cx))
})
) -> R {
self.cx.update_window_entity(view, update).unwrap()
}
fn replace_root_view<V>(
&mut self,
build_view: impl FnOnce(&mut Window, &mut Context<V>) -> V,
) -> Self::Result<Entity<V>>
) -> Entity<V>
where
V: 'static + Render,
{
self.window
.update(self, |_, window, cx| window.replace_root(cx, build_view))
self.cx.replace_root_view(build_view).unwrap()
}
fn focus<V>(&mut self, view: &Entity<V>) -> Self::Result<()>
fn focus<V>(&mut self, view: &Entity<V>)
where
V: Focusable,
{
self.window.update(self, |_, window, cx| {
view.read(cx).focus_handle(cx).clone().focus(window);
self.cx.focus(view).unwrap()
}
}
/// An async-friendly version of [App] with a static lifetime so it can be held across `await` points in async code.
/// You're provided with an instance when calling [App::spawn], and you can also create one with [App::to_async].
/// Internally, this holds a weak reference to an `App`, so its methods are fallible to protect against cases where the [App] is dropped.
pub struct WeakAsyncApp {
pub(crate) app: Weak<AppCell>,
pub(crate) background_executor: BackgroundExecutor,
pub(crate) foreground_executor: ForegroundExecutor,
}
impl Clone for WeakAsyncApp {
fn clone(&self) -> Self {
Self {
app: self.app.clone(),
background_executor: self.background_executor.clone(),
foreground_executor: self.foreground_executor.clone(),
}
}
}
impl WeakAsyncApp {
pub(crate) fn upgrade(self) -> AsyncApp {
AsyncApp {
app: self,
_not_clone: NotClone,
}
}
fn new<T: 'static>(
&mut self,
build_entity: impl FnOnce(&mut Context<'_, T>) -> T,
) -> Result<Entity<T>> {
let app = self.app.upgrade().context("App dropped")?;
let mut app = app.borrow_mut();
Ok(app.new(build_entity))
}
fn reserve_entity<T: 'static>(&mut self) -> Result<Reservation<T>> {
let app = self.app.upgrade().context("App dropped")?;
let mut app = app.borrow_mut();
Ok(app.reserve_entity())
}
fn insert_entity<T: 'static>(
&mut self,
reservation: Reservation<T>,
build_entity: impl FnOnce(&mut Context<'_, T>) -> T,
) -> Result<Entity<T>> {
let app = self.app.upgrade().context("App dropped")?;
let mut app = app.borrow_mut();
Ok(app.insert_entity(reservation, build_entity))
}
fn update_entity<T: 'static, R>(
&mut self,
handle: &Entity<T>,
update: impl FnOnce(&mut T, &mut Context<'_, T>) -> R,
) -> Result<R> {
let app = self.app.upgrade().context("App dropped")?;
let mut app = app.borrow_mut();
Ok(app.update_entity(handle, update))
}
fn read_entity<T, R>(
&self,
handle: &Entity<T>,
callback: impl FnOnce(&T, &App) -> R,
) -> Result<R>
where
T: 'static,
{
let app = self.app.upgrade().context("App dropped")?;
let lock = app.borrow();
Ok(lock.read_entity(handle, callback))
}
fn update_window<T, F>(&mut self, window: AnyWindowHandle, f: F) -> Result<Result<T>>
where
F: FnOnce(AnyView, &mut Window, &mut App) -> T,
{
let app = self.app.upgrade().context("App dropped")?;
let mut lock = app.borrow_mut();
Ok(lock.update_window(window, f))
}
fn read_window<T, R>(
&self,
window: &WindowHandle<T>,
read: impl FnOnce(Entity<T>, &App) -> R,
) -> Result<Result<R>>
where
T: 'static,
{
let app = self.app.upgrade().context("App dropped")?;
let lock = app.borrow();
Ok(lock.read_window(window, read))
}
fn background_spawn<R>(&self, future: impl Future<Output = R> + Send + 'static) -> Task<R>
where
R: Send + 'static,
{
self.background_executor.spawn(future)
}
fn read_global<G, R>(&self, callback: impl FnOnce(&G, &App) -> R) -> Result<R>
where
G: Global,
{
let app = self.app.upgrade().context("App dropped")?;
let mut lock = app.borrow_mut();
Ok(lock.update(|this| this.read_global(callback)))
}
/// Schedules all windows in the application to be redrawn.
pub fn refresh(&self) -> Result<()> {
let app = self.app.upgrade().context("App dropped")?;
let mut lock = app.borrow_mut();
Ok(lock.refresh_windows())
}
/// Get an executor which can be used to spawn futures in the background.
pub fn background_executor(&self) -> &BackgroundExecutor {
&self.background_executor
}
/// Get an executor which can be used to spawn futures in the foreground.
pub fn foreground_executor(&self) -> &ForegroundExecutor {
&self.foreground_executor
}
/// Invoke the given function in the context of the app, then flush any effects produced during its invocation.
/// Panics if the app has been dropped since this was created
pub fn update<R>(&self, f: impl FnOnce(&mut App) -> R) -> Result<R> {
let app = self.app.upgrade().context("App dropped")?;
let mut lock = app.borrow_mut();
Ok(f(&mut lock))
}
/// Open a window with the given options based on the root view returned by the given function.
pub fn open_window<V>(
&self,
options: crate::WindowOptions,
build_root_view: impl FnOnce(&mut Window, &mut App) -> Entity<V>,
) -> Result<Result<WindowHandle<V>>>
where
V: 'static + Render,
{
let app = self.app.upgrade().context("App dropped")?;
let mut lock = app.borrow_mut();
Ok(lock.open_window(options, build_root_view))
}
/// Schedule a future to be polled in the background.
#[track_caller]
pub fn spawn<Fut, R>(&self, f: impl FnOnce(AsyncApp) -> Fut) -> Task<R>
where
Fut: Future<Output = R> + 'static,
R: 'static,
{
self.foreground_executor
.spawn_with_context(ForegroundContext::app(&self.app), f(self.clone().upgrade()))
}
/// Determine whether global state of the specified type has been assigned.
/// Returns an error if the `App` has been dropped.
pub fn has_global<G: Global>(&self) -> Result<bool> {
let app = self.app.upgrade().context("App dropped")?;
let app = app.borrow_mut();
Ok(app.has_global::<G>())
}
/// Reads the global state of the specified type, passing it to the given callback.
///
/// Similar to [`AsyncApp::read_global`], but returns an error instead of panicking
/// if no state of the specified type has been assigned.
///
/// Returns an error if no state of the specified type has been assigned the `App` has been dropped.
pub fn try_read_global<G: Global, R>(
&self,
read: impl FnOnce(&G, &App) -> R,
) -> Result<Option<R>> {
let app = self.app.upgrade().context("App dropped")?;
let app = app.borrow_mut();
let Some(global) = app.try_global::<G>() else {
return Ok(None);
};
Ok(Some(read(global, &app)))
}
/// A convenience method for [App::update_global]
/// for updating the global state of the specified type.
pub fn update_global<G: Global, R>(
&self,
update: impl FnOnce(&mut G, &mut App) -> R,
) -> Result<R> {
let app = self.app.upgrade().context("App dropped")?;
let mut app = app.borrow_mut();
Ok(app.update(|cx| cx.update_global(update)))
}
}
/// A cloneable, owned handle to the application context,
/// composed with the window associated with the current task.
#[derive(Clone, Deref, DerefMut)]
pub struct WeakAsyncWindowContext {
#[deref]
#[deref_mut]
app: WeakAsyncApp,
window: AnyWindowHandle,
}
impl WeakAsyncWindowContext {
pub(crate) fn new_context(app: WeakAsyncApp, window: AnyWindowHandle) -> Self {
Self { app, window }
}
pub(crate) fn upgrade(self) -> AsyncWindowContext {
AsyncWindowContext {
cx: self,
_not_clone: NotClone,
}
}
/// Get the handle of the window this context is associated with.
pub fn window_handle(&self) -> AnyWindowHandle {
self.window
}
/// A convenience method for [`App::update_window`].
pub fn update<R>(&mut self, update: impl FnOnce(&mut Window, &mut App) -> R) -> Result<R> {
crate::Flatten::flatten(
self.app
.update_window(self.window, |_, window, cx| update(window, cx)),
)
}
/// A convenience method for [`App::update_window`].
pub fn update_root<R>(
&mut self,
update: impl FnOnce(AnyView, &mut Window, &mut App) -> R,
) -> Result<R> {
crate::Flatten::flatten(self.app.update_window(self.window, update))
}
/// A convenience method for [`Window::on_next_frame`].
pub fn on_next_frame(&mut self, f: impl FnOnce(&mut Window, &mut App) + 'static) -> Result<()> {
self.update_window(self.window, |_, window, _| window.on_next_frame(f))
}
/// A convenience method for [`App::global`].
pub fn read_global<G: Global, R>(
&mut self,
read: impl FnOnce(&G, &Window, &App) -> R,
) -> Result<R> {
self.update_window(self.window, |_, window, cx| read(cx.global(), window, cx))
}
/// A convenience method for [`App::update_global`].
/// for updating the global state of the specified type.
pub fn update_global<G, R>(
&mut self,
update: impl FnOnce(&mut G, &mut Window, &mut App) -> R,
) -> Result<R>
where
G: Global,
{
self.update_window(self.window, |_, window, cx| {
cx.update_global(|global, cx| update(global, window, cx))
})
}
/// Schedule a future to be executed on the main thread. This is used for collecting
/// the results of background tasks and updating the UI.
#[track_caller]
pub fn spawn<Fut, R>(&self, f: impl FnOnce(AsyncWindowContext) -> Fut) -> Task<R>
where
Fut: Future<Output = R> + 'static,
R: 'static,
{
self.foreground_executor.spawn_with_context(
ForegroundContext::window(&self.app.app, self.window.id),
f(self.clone().upgrade()),
)
}
/// Present a platform dialog.
/// The provided message will be presented, along with buttons for each answer.
/// When a button is clicked, the returned Receiver will receive the index of the clicked button.
pub fn prompt(
&mut self,
level: PromptLevel,
message: &str,
detail: Option<&str>,
answers: &[&str],
) -> Result<oneshot::Receiver<usize>> {
self.update_window(self.window, |_, window, cx| {
window.prompt(level, message, detail, answers, cx)
})
}
fn new<T>(&mut self, build_entity: impl FnOnce(&mut Context<'_, T>) -> T) -> Result<Entity<T>>
where
T: 'static,
{
self.update_window(self.window, |_, _, cx| cx.new(build_entity))
}
fn reserve_entity<T: 'static>(&mut self) -> Result<Reservation<T>> {
self.update_window(self.window, |_, _, cx| cx.reserve_entity())
}
fn insert_entity<T: 'static>(
&mut self,
reservation: Reservation<T>,
build_entity: impl FnOnce(&mut Context<'_, T>) -> T,
) -> Result<Entity<T>> {
self.update_window(self.window, |_, _, cx| {
cx.insert_entity(reservation, build_entity)
})
}
fn update_entity<T: 'static, R>(
&mut self,
handle: &Entity<T>,
update: impl FnOnce(&mut T, &mut Context<'_, T>) -> R,
) -> Result<R> {
self.update_window(self.window, |_, _, cx| cx.update_entity(handle, update))
}
fn read_entity<T, R>(&self, handle: &Entity<T>, read: impl FnOnce(&T, &App) -> R) -> Result<R>
where
T: 'static,
{
self.app.read_entity(handle, read)
}
fn update_window<T, F>(&mut self, window: AnyWindowHandle, update: F) -> Result<T>
where
F: FnOnce(AnyView, &mut Window, &mut App) -> T,
{
crate::Flatten::flatten(self.app.update_window(window, update))
}
fn read_window<T, R>(
&self,
window: &WindowHandle<T>,
read: impl FnOnce(Entity<T>, &App) -> R,
) -> Result<R>
where
T: 'static,
{
crate::Flatten::flatten(self.app.read_window(window, read))
}
fn background_spawn<R>(&self, future: impl Future<Output = R> + Send + 'static) -> Task<R>
where
R: Send + 'static,
{
self.app.background_executor.spawn(future)
}
fn new_window_entity<T: 'static>(
&mut self,
build_entity: impl FnOnce(&mut Window, &mut Context<T>) -> T,
) -> Result<Entity<T>> {
self.update_window(self.window, |_, window, cx| {
cx.new(|cx| build_entity(window, cx))
})
}
fn update_window_entity<T: 'static, R>(
&mut self,
view: &Entity<T>,
update: impl FnOnce(&mut T, &mut Window, &mut Context<T>) -> R,
) -> Result<R> {
self.update(|window, cx| view.update(cx, |entity, cx| update(entity, window, cx)))
}
fn replace_root_view<V>(
&mut self,
build_view: impl FnOnce(&mut Window, &mut Context<V>) -> V,
) -> Result<Entity<V>>
where
V: 'static + Render,
{
self.update_window(self.window, |_, window, cx| {
window.replace_root(cx, build_view)
})
}
fn focus<V>(&mut self, view: &Entity<V>) -> Result<()>
where
V: Focusable,
{
self.update_window(self.window, |_, window, cx| {
view.read(cx).focus_handle(cx).clone().focus(window)
})
}
}

View File

@@ -680,8 +680,6 @@ impl<T> Context<'_, T> {
}
impl<T> AppContext for Context<'_, T> {
type Result<U> = U;
fn new<U: 'static>(
&mut self,
build_entity: impl FnOnce(&mut Context<'_, U>) -> U,
@@ -697,7 +695,7 @@ impl<T> AppContext for Context<'_, T> {
&mut self,
reservation: Reservation<U>,
build_entity: impl FnOnce(&mut Context<'_, U>) -> U,
) -> Self::Result<Entity<U>> {
) -> Entity<U> {
self.app.insert_entity(reservation, build_entity)
}
@@ -709,11 +707,7 @@ impl<T> AppContext for Context<'_, T> {
self.app.update_entity(handle, update)
}
fn read_entity<U, R>(
&self,
handle: &Entity<U>,
read: impl FnOnce(&U, &App) -> R,
) -> Self::Result<R>
fn read_entity<U, R>(&self, handle: &Entity<U>, read: impl FnOnce(&U, &App) -> R) -> R
where
U: 'static,
{
@@ -745,7 +739,7 @@ impl<T> AppContext for Context<'_, T> {
self.app.background_executor.spawn(future)
}
fn read_global<G, R>(&self, callback: impl FnOnce(&G, &App) -> R) -> Self::Result<R>
fn read_global<G, R>(&self, callback: impl FnOnce(&G, &App) -> R) -> R
where
G: Global,
{

View File

@@ -418,11 +418,7 @@ impl<T: 'static> Entity<T> {
}
/// Read the entity referenced by this handle with the given function.
pub fn read_with<R, C: AppContext>(
&self,
cx: &C,
f: impl FnOnce(&T, &App) -> R,
) -> C::Result<R> {
pub fn read_with<R, C: AppContext>(&self, cx: &C, f: impl FnOnce(&T, &App) -> R) -> R {
cx.read_entity(self, f)
}
@@ -435,7 +431,7 @@ impl<T: 'static> Entity<T> {
&self,
cx: &mut C,
update: impl FnOnce(&mut T, &mut Context<'_, T>) -> R,
) -> C::Result<R>
) -> R
where
C: AppContext,
{
@@ -449,7 +445,7 @@ impl<T: 'static> Entity<T> {
&self,
cx: &mut C,
update: impl FnOnce(&mut T, &mut Window, &mut Context<'_, T>) -> R,
) -> C::Result<R>
) -> R
where
C: VisualContext,
{
@@ -646,13 +642,10 @@ impl<T: 'static> WeakEntity<T> {
) -> Result<R>
where
C: AppContext,
Result<C::Result<R>>: crate::Flatten<R>,
{
crate::Flatten::flatten(
self.upgrade()
.ok_or_else(|| anyhow!("entity released"))
.map(|this| cx.update_entity(&this, update)),
)
self.upgrade()
.ok_or_else(|| anyhow!("entity released"))
.map(|this| cx.update_entity(&this, update))
}
/// Updates the entity referenced by this handle with the given function if
@@ -665,14 +658,13 @@ impl<T: 'static> WeakEntity<T> {
) -> Result<R>
where
C: VisualContext,
Result<C::Result<R>>: crate::Flatten<R>,
{
let window = cx.window_handle();
let this = self.upgrade().ok_or_else(|| anyhow!("entity released"))?;
crate::Flatten::flatten(window.update(cx, |_, window, cx| {
window.update(cx, |_, window, cx| {
this.update(cx, |entity, cx| update(entity, window, cx))
}))
})
}
/// Reads the entity referenced by this handle with the given function if
@@ -681,13 +673,10 @@ impl<T: 'static> WeakEntity<T> {
pub fn read_with<C, R>(&self, cx: &C, read: impl FnOnce(&T, &App) -> R) -> Result<R>
where
C: AppContext,
Result<C::Result<R>>: crate::Flatten<R>,
{
crate::Flatten::flatten(
self.upgrade()
.ok_or_else(|| anyhow!("entity release"))
.map(|this| cx.read_entity(&this, read)),
)
self.upgrade()
.ok_or_else(|| anyhow!("entity release"))
.map(|this| cx.read_entity(&this, read))
}
}

View File

@@ -1,9 +1,9 @@
use crate::{
Action, AnyView, AnyWindowHandle, App, AppCell, AppContext, AsyncApp, AvailableSpace,
BackgroundExecutor, BorrowAppContext, Bounds, ClipboardItem, DrawPhase, Drawable, Element,
Empty, EventEmitter, ForegroundExecutor, Global, InputEvent, Keystroke, Modifiers,
ModifiersChangedEvent, MouseButton, MouseDownEvent, MouseMoveEvent, MouseUpEvent, Pixels,
Platform, Point, Render, Result, Size, Task, TestDispatcher, TestPlatform,
Empty, EventEmitter, ForegroundContext, ForegroundExecutor, Global, InputEvent, Keystroke,
Modifiers, ModifiersChangedEvent, MouseButton, MouseDownEvent, MouseMoveEvent, MouseUpEvent,
Pixels, Platform, Point, Render, Result, Size, Task, TestDispatcher, TestPlatform,
TestScreenCaptureSource, TestWindow, TextSystem, VisualContext, Window, WindowBounds,
WindowHandle, WindowOptions,
};
@@ -13,7 +13,6 @@ use std::{cell::RefCell, future::Future, ops::Deref, rc::Rc, sync::Arc, time::Du
/// A TestAppContext is provided to tests created with `#[gpui::test]`, it provides
/// an implementation of `Context` with additional methods that are useful in tests.
#[derive(Clone)]
pub struct TestAppContext {
#[doc(hidden)]
pub app: Rc<AppCell>,
@@ -29,18 +28,31 @@ pub struct TestAppContext {
on_quit: Rc<RefCell<Vec<Box<dyn FnOnce() + 'static>>>>,
}
impl AppContext for TestAppContext {
type Result<T> = T;
impl Clone for TestAppContext {
fn clone(&self) -> Self {
Self {
app: self.app.clone(),
background_executor: self.background_executor.clone(),
foreground_executor: self.foreground_executor.clone(),
dispatcher: self.dispatcher.clone(),
test_platform: self.test_platform.clone(),
text_system: self.text_system.clone(),
fn_name: self.fn_name.clone(),
on_quit: self.on_quit.clone(),
}
}
}
impl AppContext for TestAppContext {
fn new<T: 'static>(
&mut self,
build_entity: impl FnOnce(&mut Context<'_, T>) -> T,
) -> Self::Result<Entity<T>> {
) -> Entity<T> {
let mut app = self.app.borrow_mut();
app.new(build_entity)
}
fn reserve_entity<T: 'static>(&mut self) -> Self::Result<crate::Reservation<T>> {
fn reserve_entity<T: 'static>(&mut self) -> crate::Reservation<T> {
let mut app = self.app.borrow_mut();
app.reserve_entity()
}
@@ -49,7 +61,7 @@ impl AppContext for TestAppContext {
&mut self,
reservation: crate::Reservation<T>,
build_entity: impl FnOnce(&mut Context<'_, T>) -> T,
) -> Self::Result<Entity<T>> {
) -> Entity<T> {
let mut app = self.app.borrow_mut();
app.insert_entity(reservation, build_entity)
}
@@ -58,16 +70,12 @@ impl AppContext for TestAppContext {
&mut self,
handle: &Entity<T>,
update: impl FnOnce(&mut T, &mut Context<'_, T>) -> R,
) -> Self::Result<R> {
) -> R {
let mut app = self.app.borrow_mut();
app.update_entity(handle, update)
}
fn read_entity<T, R>(
&self,
handle: &Entity<T>,
read: impl FnOnce(&T, &App) -> R,
) -> Self::Result<R>
fn read_entity<T, R>(&self, handle: &Entity<T>, read: impl FnOnce(&T, &App) -> R) -> R
where
T: 'static,
{
@@ -102,7 +110,7 @@ impl AppContext for TestAppContext {
self.background_executor.spawn(future)
}
fn read_global<G, R>(&self, callback: impl FnOnce(&G, &App) -> R) -> Self::Result<R>
fn read_global<G, R>(&self, callback: impl FnOnce(&G, &App) -> R) -> R
where
G: Global,
{
@@ -329,7 +337,10 @@ impl TestAppContext {
Fut: Future<Output = R> + 'static,
R: 'static,
{
self.foreground_executor.spawn(f(self.to_async()))
self.foreground_executor.spawn_with_context(
ForegroundContext::app(&Rc::downgrade(&self.app)),
f(self.to_async()),
)
}
/// true if the given global is defined
@@ -363,14 +374,15 @@ impl TestAppContext {
lock.update(|cx| cx.update_global(update))
}
/// Returns an `AsyncApp` which can be used to run tasks that expect to be on a background
/// Returns a `AsyncApp` which can be used to run tasks that expect to be on a background
/// thread on the current thread in tests.
pub fn to_async(&self) -> AsyncApp {
AsyncApp {
WeakAsyncApp {
app: Rc::downgrade(&self.app),
background_executor: self.background_executor.clone(),
foreground_executor: self.foreground_executor.clone(),
}
.upgrade()
}
/// Wait until there are no more pending tasks.
@@ -644,7 +656,7 @@ impl<V> Entity<V> {
use derive_more::{Deref, DerefMut};
use super::{Context, Entity};
use super::{Context, Entity, WeakAsyncApp};
#[derive(Deref, DerefMut, Clone)]
/// A VisualTestContext is the test-equivalent of a `Window` and `App`. It allows you to
/// run window-specific test code. It can be dereferenced to a `TextAppContext`.
@@ -868,16 +880,14 @@ impl VisualTestContext {
}
impl AppContext for VisualTestContext {
type Result<T> = <TestAppContext as AppContext>::Result<T>;
fn new<T: 'static>(
&mut self,
build_entity: impl FnOnce(&mut Context<'_, T>) -> T,
) -> Self::Result<Entity<T>> {
) -> Entity<T> {
self.cx.new(build_entity)
}
fn reserve_entity<T: 'static>(&mut self) -> Self::Result<crate::Reservation<T>> {
fn reserve_entity<T: 'static>(&mut self) -> crate::Reservation<T> {
self.cx.reserve_entity()
}
@@ -885,7 +895,7 @@ impl AppContext for VisualTestContext {
&mut self,
reservation: crate::Reservation<T>,
build_entity: impl FnOnce(&mut Context<'_, T>) -> T,
) -> Self::Result<Entity<T>> {
) -> Entity<T> {
self.cx.insert_entity(reservation, build_entity)
}
@@ -893,18 +903,14 @@ impl AppContext for VisualTestContext {
&mut self,
handle: &Entity<T>,
update: impl FnOnce(&mut T, &mut Context<'_, T>) -> R,
) -> Self::Result<R>
) -> R
where
T: 'static,
{
self.cx.update_entity(handle, update)
}
fn read_entity<T, R>(
&self,
handle: &Entity<T>,
read: impl FnOnce(&T, &App) -> R,
) -> Self::Result<R>
fn read_entity<T, R>(&self, handle: &Entity<T>, read: impl FnOnce(&T, &App) -> R) -> R
where
T: 'static,
{
@@ -936,7 +942,7 @@ impl AppContext for VisualTestContext {
self.cx.background_spawn(future)
}
fn read_global<G, R>(&self, callback: impl FnOnce(&G, &App) -> R) -> Self::Result<R>
fn read_global<G, R>(&self, callback: impl FnOnce(&G, &App) -> R) -> R
where
G: Global,
{
@@ -953,7 +959,7 @@ impl VisualContext for VisualTestContext {
fn new_window_entity<T: 'static>(
&mut self,
build_entity: impl FnOnce(&mut Window, &mut Context<'_, T>) -> T,
) -> Self::Result<Entity<T>> {
) -> Entity<T> {
self.window
.update(&mut self.cx, |_, window, cx| {
cx.new(|cx| build_entity(window, cx))
@@ -965,7 +971,7 @@ impl VisualContext for VisualTestContext {
&mut self,
view: &Entity<V>,
update: impl FnOnce(&mut V, &mut Window, &mut Context<V>) -> R,
) -> Self::Result<R> {
) -> R {
self.window
.update(&mut self.cx, |_, window, cx| {
view.update(cx, |v, cx| update(v, window, cx))
@@ -976,7 +982,7 @@ impl VisualContext for VisualTestContext {
fn replace_root_view<V>(
&mut self,
build_view: impl FnOnce(&mut Window, &mut Context<V>) -> V,
) -> Self::Result<Entity<V>>
) -> Entity<V>
where
V: 'static + Render,
{
@@ -987,7 +993,7 @@ impl VisualContext for VisualTestContext {
.unwrap()
}
fn focus<V: crate::Focusable>(&mut self, view: &Entity<V>) -> Self::Result<()> {
fn focus<V: crate::Focusable>(&mut self, view: &Entity<V>) {
self.window
.update(&mut self.cx, |_, window, cx| {
view.read(cx).focus_handle(cx).clone().focus(window)

View File

@@ -2509,8 +2509,7 @@ fn handle_tooltip_mouse_move(
});
*active_tooltip.borrow_mut() = new_tooltip;
window.refresh();
})
.ok();
});
}
});
active_tooltip
@@ -2577,7 +2576,7 @@ fn handle_tooltip_check_visible_and_update(
.timer(HOVERABLE_TOOLTIP_HIDE_DELAY)
.await;
if active_tooltip.borrow_mut().take().is_some() {
cx.update(|window, _cx| window.refresh()).ok();
cx.update(|window, _cx| window.refresh());
}
}
});

View File

@@ -361,8 +361,7 @@ impl Element for Img {
cx.background_executor().timer(LOADING_DELAY).await;
cx.update(move |_, cx| {
cx.notify(current_view);
})
.ok();
});
});
state.started_loading = Some((Instant::now(), task));
}

View File

@@ -1,14 +1,11 @@
use crate::{App, PlatformDispatcher};
use async_task::Runnable;
use crate::{App, ForegroundContext, NotClone, PlatformDispatcher};
use async_task::Builder;
use futures::channel::mpsc;
use smol::prelude::*;
use std::mem::ManuallyDrop;
use std::panic::Location;
use std::thread::{self, ThreadId};
use std::{
fmt::Debug,
marker::PhantomData,
mem,
mem::{self, ManuallyDrop},
num::NonZeroUsize,
pin::Pin,
rc::Rc,
@@ -17,6 +14,7 @@ use std::{
Arc,
},
task::{Context, Poll},
thread::ThreadId,
time::{Duration, Instant},
};
use util::TryFutureExt;
@@ -27,10 +25,10 @@ use rand::rngs::StdRng;
/// A pointer to the executor that is currently running,
/// for spawning background tasks.
#[derive(Clone)]
pub struct BackgroundExecutor {
#[doc(hidden)]
pub dispatcher: Arc<dyn PlatformDispatcher>,
_not_clone: NotClone,
}
/// A pointer to the executor that is currently running,
@@ -40,10 +38,10 @@ pub struct BackgroundExecutor {
/// `ForegroundExecutor::spawn` does not require `Send` but checks at runtime that the future is
/// only polled from the same thread it was spawned from. These checks would fail when spawning
/// foreground tasks from from background threads.
#[derive(Clone)]
pub struct ForegroundExecutor {
#[doc(hidden)]
pub dispatcher: Arc<dyn PlatformDispatcher>,
_not_clone: NotClone,
not_send: PhantomData<Rc<()>>,
}
@@ -55,15 +53,18 @@ pub struct ForegroundExecutor {
/// the task to continue running, but with no way to return a value.
#[must_use]
#[derive(Debug)]
pub struct Task<T>(TaskState<T>);
pub struct Task<T>(TaskState<T, async_task::Task<T, ForegroundContext>, async_task::Task<T>>);
#[derive(Debug)]
enum TaskState<T> {
enum TaskState<T, F, B> {
/// A task that is ready to return a value
Ready(Option<T>),
/// A task that is currently running.
Spawned(async_task::Task<T>),
/// A task that is currently running on the foreground.
ForegroundSpawned(F),
/// A task that is currently running on the background
BackgroundSpawned(B),
}
impl<T> Task<T> {
@@ -76,7 +77,23 @@ impl<T> Task<T> {
pub fn detach(self) {
match self {
Task(TaskState::Ready(_)) => {}
Task(TaskState::Spawned(task)) => task.detach(),
Task(TaskState::ForegroundSpawned(task)) => task.detach(),
Task(TaskState::BackgroundSpawned(task)) => task.detach(),
}
}
/// Create a task that can be safely awaited outside of GPUI.
/// If the app quits while this task is incomplete, it will
/// produce None instead of panicking.
pub fn external(self) -> ExternalTask<T> {
match self.0 {
TaskState::Ready(r) => ExternalTask(TaskState::Ready(r)),
TaskState::ForegroundSpawned(task) => {
ExternalTask(TaskState::ForegroundSpawned(task.fallible()))
}
TaskState::BackgroundSpawned(task) => {
ExternalTask(TaskState::BackgroundSpawned(task.fallible()))
}
}
}
}
@@ -96,14 +113,34 @@ where
.detach();
}
}
impl<T> Future for Task<T> {
type Output = T;
fn poll(self: Pin<&mut Self>, cx: &mut Context) -> Poll<Self::Output> {
match unsafe { self.get_unchecked_mut() } {
Task(TaskState::Ready(val)) => Poll::Ready(val.take().unwrap()),
Task(TaskState::Spawned(task)) => task.poll(cx),
Task(TaskState::ForegroundSpawned(task)) => task.poll(cx),
Task(TaskState::BackgroundSpawned(task)) => task.poll(cx),
}
}
}
/// An ExternalTask is the same as a task, except it can be safely
/// awaited outside of GPUI, without panicking.
#[must_use]
#[derive(Debug)]
pub struct ExternalTask<T>(
TaskState<T, async_task::FallibleTask<T, ForegroundContext>, async_task::FallibleTask<T>>,
);
impl<T> Future for ExternalTask<T> {
type Output = Option<T>;
fn poll(self: Pin<&mut Self>, cx: &mut Context) -> Poll<Self::Output> {
match unsafe { self.get_unchecked_mut() } {
ExternalTask(TaskState::Ready(val)) => Poll::Ready(val.take()),
ExternalTask(TaskState::ForegroundSpawned(task)) => task.poll(cx),
ExternalTask(TaskState::BackgroundSpawned(task)) => task.poll(cx),
}
}
}
@@ -127,8 +164,6 @@ impl TaskLabel {
}
}
type AnyLocalFuture<R> = Pin<Box<dyn 'static + Future<Output = R>>>;
type AnyFuture<R> = Pin<Box<dyn 'static + Send + Future<Output = R>>>;
/// BackgroundExecutor lets you run things on background threads.
@@ -138,7 +173,20 @@ type AnyFuture<R> = Pin<Box<dyn 'static + Send + Future<Output = R>>>;
impl BackgroundExecutor {
#[doc(hidden)]
pub fn new(dispatcher: Arc<dyn PlatformDispatcher>) -> Self {
Self { dispatcher }
Self {
dispatcher,
_not_clone: NotClone,
}
}
/// Cloning executors can cause runtime panics, see the documentation on `NotClone` for details.
/// Use this power wisely.
#[doc(hidden)]
pub fn clone(&self) -> Self {
Self {
dispatcher: self.dispatcher.clone(),
_not_clone: NotClone,
}
}
/// Enqueues the given future to be run to completion on a background thread.
@@ -171,7 +219,7 @@ impl BackgroundExecutor {
let (runnable, task) =
async_task::spawn(future, move |runnable| dispatcher.dispatch(runnable, label));
runnable.schedule();
Task(TaskState::Spawned(task))
Task(TaskState::BackgroundSpawned(task))
}
/// Used by the test harness to run an async test in a synchronous fashion.
@@ -348,7 +396,7 @@ impl BackgroundExecutor {
move |runnable| dispatcher.dispatch_after(duration, runnable)
});
runnable.schedule();
Task(TaskState::Spawned(task))
Task(TaskState::BackgroundSpawned(task))
}
/// in tests, start_waiting lets you indicate which task is waiting (for debugging only)
@@ -447,6 +495,18 @@ impl ForegroundExecutor {
pub fn new(dispatcher: Arc<dyn PlatformDispatcher>) -> Self {
Self {
dispatcher,
_not_clone: NotClone,
not_send: PhantomData,
}
}
/// Cloning executors can cause runtime panics, see the documentation on `NotClone` for details.
/// Use this power wisely.
#[doc(hidden)]
pub fn clone(&self) -> Self {
Self {
dispatcher: self.dispatcher.clone(),
_not_clone: NotClone,
not_send: PhantomData,
}
}
@@ -457,86 +517,92 @@ impl ForegroundExecutor {
where
R: 'static,
{
let dispatcher = self.dispatcher.clone();
let mut context = ForegroundContext::none();
let task = self.spawn_internal(future, context);
Task(TaskState::ForegroundSpawned(task))
}
#[track_caller]
fn inner<R: 'static>(
dispatcher: Arc<dyn PlatformDispatcher>,
future: AnyLocalFuture<R>,
) -> Task<R> {
let (runnable, task) = spawn_local_with_source_location(future, move |runnable| {
dispatcher.dispatch_on_main_thread(runnable)
});
runnable.schedule();
Task(TaskState::Spawned(task))
/// Enqueues the given Task to run on the main thread at some point in the future,
/// with a context parameter that will be checked before each turn
#[track_caller]
pub(crate) fn spawn_with_context<R>(
&self,
mut context: ForegroundContext,
future: impl Future<Output = R> + 'static,
) -> Task<R>
where
R: 'static,
{
let task = self.spawn_internal(future, context);
Task(TaskState::ForegroundSpawned(task))
}
#[track_caller]
fn spawn_internal<R>(
&self,
future: impl Future<Output = R> + 'static,
mut context: ForegroundContext,
) -> smol::Task<R, ForegroundContext>
where
R: 'static,
{
/// Declarations here are copy-modified from:
/// https://github.com/smol-rs/async-task/blob/ca9dbe1db9c422fd765847fa91306e30a6bb58a9/src/runnable.rs#L405
#[inline]
pub(crate) fn thread_id() -> ThreadId {
std::thread_local! {
static ID: ThreadId = std::thread::current().id();
}
ID.try_with(|id| *id)
.unwrap_or_else(|_| std::thread::current().id())
}
inner::<R>(dispatcher, Box::pin(future))
}
}
/// Variant of `async_task::spawn_local` that includes the source location of the spawn in panics.
///
/// Copy-modified from:
/// https://github.com/smol-rs/async-task/blob/ca9dbe1db9c422fd765847fa91306e30a6bb58a9/src/runnable.rs#L405
#[track_caller]
fn spawn_local_with_source_location<Fut, S>(
future: Fut,
schedule: S,
) -> (Runnable<()>, async_task::Task<Fut::Output, ()>)
where
Fut: Future + 'static,
Fut::Output: 'static,
S: async_task::Schedule<()> + Send + Sync + 'static,
{
#[inline]
fn thread_id() -> ThreadId {
std::thread_local! {
static ID: ThreadId = thread::current().id();
struct Checked<F> {
id: ThreadId,
location: core::panic::Location<'static>,
inner: ManuallyDrop<F>,
}
ID.try_with(|id| *id)
.unwrap_or_else(|_| thread::current().id())
}
struct Checked<F> {
id: ThreadId,
inner: ManuallyDrop<F>,
location: &'static Location<'static>,
}
impl<F> Drop for Checked<F> {
fn drop(&mut self) {
assert!(
self.id == thread_id(),
"local task dropped by a thread that didn't spawn it. Task spawned at {}",
self.location
);
unsafe {
ManuallyDrop::drop(&mut self.inner);
impl<F> Drop for Checked<F> {
fn drop(&mut self) {
assert!(
self.id == thread_id(),
"local task dropped by a thread that didn't spawn it. Task spawned at {}",
self.location
);
unsafe {
ManuallyDrop::drop(&mut self.inner);
}
}
}
}
impl<F: Future> Future for Checked<F> {
type Output = F::Output;
impl<F: Future> Future for Checked<F> {
type Output = F::Output;
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
assert!(
self.id == thread_id(),
"local task polled by a thread that didn't spawn it. Task spawned at {}",
self.location
);
unsafe { self.map_unchecked_mut(|c| &mut *c.inner).poll(cx) }
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
assert!(
self.id == thread_id(),
"local task polled by a thread that didn't spawn it. Task spawned at {}",
self.location
);
unsafe { self.map_unchecked_mut(|c| &mut *c.inner).poll(cx) }
}
}
let checked = Checked {
id: thread_id(),
location: *core::panic::Location::caller(),
inner: ManuallyDrop::new(future),
};
let dispatcher = self.dispatcher.clone();
let (runnable, task) = Builder::new().metadata(context).spawn_local(
|_| checked,
move |runnable| dispatcher.dispatch_on_main_thread(runnable),
);
runnable.schedule();
task
}
// Wrap the future into one that checks which thread it's on.
let future = Checked {
id: thread_id(),
inner: ManuallyDrop::new(future),
location: Location::caller(),
};
unsafe { async_task::spawn_unchecked(future, schedule) }
}
/// Scope manages a set of tasks that are enqueued and waited on together. See [`BackgroundExecutor::scoped`].

View File

@@ -161,19 +161,13 @@ use taffy::TaffyLayoutEngine;
/// The context trait, allows the different contexts in GPUI to be used
/// interchangeably for certain operations.
pub trait AppContext {
/// The result type for this context, used for async contexts that
/// can't hold a direct reference to the application context.
type Result<T>;
/// Create a new entity in the app context.
fn new<T: 'static>(
&mut self,
build_entity: impl FnOnce(&mut Context<'_, T>) -> T,
) -> Self::Result<Entity<T>>;
fn new<T: 'static>(&mut self, build_entity: impl FnOnce(&mut Context<'_, T>) -> T)
-> Entity<T>;
/// Reserve a slot for a entity to be inserted later.
/// The returned [Reservation] allows you to obtain the [EntityId] for the future entity.
fn reserve_entity<T: 'static>(&mut self) -> Self::Result<Reservation<T>>;
fn reserve_entity<T: 'static>(&mut self) -> Reservation<T>;
/// Insert a new entity in the app context based on a [Reservation] previously obtained from [`reserve_entity`].
///
@@ -182,23 +176,19 @@ pub trait AppContext {
&mut self,
reservation: Reservation<T>,
build_entity: impl FnOnce(&mut Context<'_, T>) -> T,
) -> Self::Result<Entity<T>>;
) -> Entity<T>;
/// Update a entity in the app context.
fn update_entity<T, R>(
&mut self,
handle: &Entity<T>,
update: impl FnOnce(&mut T, &mut Context<'_, T>) -> R,
) -> Self::Result<R>
) -> R
where
T: 'static;
/// Read a entity from the app context.
fn read_entity<T, R>(
&self,
handle: &Entity<T>,
read: impl FnOnce(&T, &App) -> R,
) -> Self::Result<R>
fn read_entity<T, R>(&self, handle: &Entity<T>, read: impl FnOnce(&T, &App) -> R) -> R
where
T: 'static;
@@ -222,7 +212,7 @@ pub trait AppContext {
R: Send + 'static;
/// Read a global from this app context
fn read_global<G, R>(&self, callback: impl FnOnce(&G, &App) -> R) -> Self::Result<R>
fn read_global<G, R>(&self, callback: impl FnOnce(&G, &App) -> R) -> R
where
G: Global;
}
@@ -249,24 +239,24 @@ pub trait VisualContext: AppContext {
&mut self,
entity: &Entity<T>,
update: impl FnOnce(&mut T, &mut Window, &mut Context<T>) -> R,
) -> Self::Result<R>;
) -> R;
/// Update a view with the given callback
fn new_window_entity<T: 'static>(
&mut self,
build_entity: impl FnOnce(&mut Window, &mut Context<'_, T>) -> T,
) -> Self::Result<Entity<T>>;
) -> Entity<T>;
/// Replace the root view of a window with a new view.
fn replace_root_view<V>(
&mut self,
build_view: impl FnOnce(&mut Window, &mut Context<V>) -> V,
) -> Self::Result<Entity<V>>
) -> Entity<V>
where
V: 'static + Render;
/// Focus a entity in the window, if it implements the [`Focusable`] trait.
fn focus<V>(&mut self, entity: &Entity<V>) -> Self::Result<()>
fn focus<V>(&mut self, entity: &Entity<V>)
where
V: Focusable;
}

View File

@@ -27,11 +27,11 @@ mod test;
mod windows;
use crate::{
point, Action, AnyWindowHandle, App, AsyncWindowContext, BackgroundExecutor, Bounds,
DevicePixels, DispatchEventResult, Font, FontId, FontMetrics, FontRun, ForegroundExecutor,
GlyphId, GpuSpecs, ImageSource, Keymap, LineLayout, Pixels, PlatformInput, Point,
RenderGlyphParams, RenderImage, RenderImageParams, RenderSvgParams, ScaledPixels, Scene,
SharedString, Size, SvgRenderer, SvgSize, Task, TaskLabel, Window, DEFAULT_WINDOW_SIZE,
point, Action, AnyWindowHandle, App, AppCell, BackgroundExecutor, Bounds, DevicePixels,
DispatchEventResult, Font, FontId, FontMetrics, FontRun, ForegroundExecutor, GlyphId, GpuSpecs,
ImageSource, Keymap, LineLayout, Pixels, PlatformInput, Point, RenderGlyphParams, RenderImage,
RenderImageParams, RenderSvgParams, ScaledPixels, Scene, SharedString, Size, SvgRenderer,
SvgSize, Task, TaskLabel, WeakAsyncWindowContext, Window, WindowId, DEFAULT_WINDOW_SIZE,
};
use anyhow::{anyhow, Result};
use async_task::Runnable;
@@ -47,6 +47,7 @@ use std::borrow::Cow;
use std::hash::{Hash, Hasher};
use std::io::Cursor;
use std::ops;
use std::rc::Weak;
use std::time::{Duration, Instant};
use std::{
fmt::{self, Debug},
@@ -450,13 +451,80 @@ pub(crate) trait PlatformWindow: HasWindowHandle + HasDisplayHandle {
}
}
#[derive(Debug)]
enum Contexts {
App {
app: Weak<AppCell>,
},
Window {
app: Weak<AppCell>,
window: WindowId,
},
}
/// The context of a foreground future, to be checked before polling the future
#[derive(Debug)]
pub struct ForegroundContext {
inner: Option<Contexts>,
}
// SAFETY: These fields are only accessed during `Runnable::run()` calls on the foreground,
// As enforced by the GPUI foreground executor and platform dispatcher implementations
unsafe impl Send for ForegroundContext {}
unsafe impl Sync for ForegroundContext {}
impl ForegroundContext {
/// This ForegroundContext will enforce that the app exists before polling
#[track_caller]
pub fn app(app: &Weak<AppCell>) -> Self {
Self {
inner: Some(Contexts::App { app: app.clone() }),
}
}
/// This ForegroundContext will enforce that the app and window exists before polling
#[track_caller]
pub fn window(app: &Weak<AppCell>, window: WindowId) -> Self {
Self {
inner: Some(Contexts::Window {
app: app.clone(),
window,
}),
}
}
/// This foreground context will do nothing
#[track_caller]
pub fn none() -> Self {
Self { inner: None }
}
/// Check if the context is currently valid
pub fn context_is_valid(&self) -> bool {
match &self.inner {
Some(Contexts::App { app }) => app.upgrade().is_some(),
Some(Contexts::Window { app, window }) => {
if let Some(app) = app.upgrade() {
let lock = app.borrow();
let result = lock.windows.contains_key(*window);
drop(lock);
result
} else {
false
}
}
None => true,
}
}
}
/// This type is public so that our test macro can generate and use it, but it should not
/// be considered part of our public API.
#[doc(hidden)]
pub trait PlatformDispatcher: Send + Sync {
fn is_main_thread(&self) -> bool;
fn dispatch(&self, runnable: Runnable, label: Option<TaskLabel>);
fn dispatch_on_main_thread(&self, runnable: Runnable);
fn dispatch_on_main_thread(&self, runnable: Runnable<ForegroundContext>);
fn dispatch_after(&self, duration: Duration, runnable: Runnable);
fn park(&self, timeout: Option<Duration>) -> bool;
fn unparker(&self) -> Unparker;
@@ -683,7 +751,7 @@ impl From<TileId> for etagere::AllocId {
}
pub(crate) struct PlatformInputHandler {
cx: AsyncWindowContext,
cx: WeakAsyncWindowContext,
handler: Box<dyn InputHandler>,
}
@@ -695,7 +763,7 @@ pub(crate) struct PlatformInputHandler {
allow(dead_code)
)]
impl PlatformInputHandler {
pub fn new(cx: AsyncWindowContext, handler: Box<dyn InputHandler>) -> Self {
pub fn new(cx: WeakAsyncWindowContext, handler: Box<dyn InputHandler>) -> Self {
Self { cx, handler }
}

View File

@@ -1,6 +1,7 @@
use crate::{Action, App, Platform, SharedString};
use util::ResultExt;
use crate::{Action, App, Platform, SharedString};
/// A menu of the application, either a main menu or a submenu
pub struct Menu {
/// The name of the menu

View File

@@ -2,7 +2,7 @@
#![allow(non_camel_case_types)]
#![allow(non_snake_case)]
use crate::{PlatformDispatcher, TaskLabel};
use crate::{ForegroundContext, PlatformDispatcher, TaskLabel};
use async_task::Runnable;
use objc::{
class, msg_send,
@@ -63,12 +63,12 @@ impl PlatformDispatcher for MacDispatcher {
}
}
fn dispatch_on_main_thread(&self, runnable: Runnable) {
fn dispatch_on_main_thread(&self, runnable: Runnable<ForegroundContext>) {
unsafe {
dispatch_async_f(
dispatch_get_main_queue(),
runnable.into_raw().as_ptr() as *mut c_void,
Some(trampoline),
Some(context_trampoline),
);
}
}
@@ -105,3 +105,15 @@ extern "C" fn trampoline(runnable: *mut c_void) {
let task = unsafe { Runnable::<()>::from_raw(NonNull::new_unchecked(runnable as *mut ())) };
task.run();
}
extern "C" fn context_trampoline(runnable: *mut c_void) {
let task = unsafe {
Runnable::<ForegroundContext>::from_raw(NonNull::new_unchecked(runnable as *mut ()))
};
if task.metadata().context_is_valid() {
task.run();
} else {
drop(task);
}
}

View File

@@ -1,4 +1,4 @@
use crate::{PlatformDispatcher, TaskLabel};
use crate::{platform::ForegroundContext, PlatformDispatcher, TaskLabel};
use async_task::Runnable;
use backtrace::Backtrace;
use collections::{HashMap, HashSet, VecDeque};
@@ -26,9 +26,13 @@ pub struct TestDispatcher {
unparker: Unparker,
}
// SAFETY: The test dispatcher is only ever run single threaded
unsafe impl Send for TestDispatcher {}
unsafe impl Sync for TestDispatcher {}
struct TestDispatcherState {
random: StdRng,
foreground: HashMap<TestDispatcherId, VecDeque<Runnable>>,
foreground: HashMap<TestDispatcherId, VecDeque<Runnable<ForegroundContext>>>,
background: Vec<Runnable>,
deprioritized_background: Vec<Runnable>,
delayed: Vec<(Duration, Runnable)>,
@@ -135,7 +139,8 @@ impl TestDispatcher {
};
let background_len = state.background.len();
let runnable;
let mut background_runnable = None;
let mut foreground_runnable = None;
let main_thread;
if foreground_len == 0 && background_len == 0 {
let deprioritized_background_len = state.deprioritized_background.len();
@@ -144,7 +149,7 @@ impl TestDispatcher {
}
let ix = state.random.gen_range(0..deprioritized_background_len);
main_thread = false;
runnable = state.deprioritized_background.swap_remove(ix);
background_runnable = Some(state.deprioritized_background.swap_remove(ix));
} else {
main_thread = state.random.gen_ratio(
foreground_len as u32,
@@ -152,24 +157,36 @@ impl TestDispatcher {
);
if main_thread {
let state = &mut *state;
runnable = state
.foreground
.values_mut()
.filter(|runnables| !runnables.is_empty())
.choose(&mut state.random)
.unwrap()
.pop_front()
.unwrap();
foreground_runnable = Some(
state
.foreground
.values_mut()
.filter(|runnables| !runnables.is_empty())
.choose(&mut state.random)
.unwrap()
.pop_front()
.unwrap(),
);
} else {
let ix = state.random.gen_range(0..background_len);
runnable = state.background.swap_remove(ix);
background_runnable = Some(state.background.swap_remove(ix));
};
};
let was_main_thread = state.is_main_thread;
state.is_main_thread = main_thread;
drop(state);
runnable.run();
if let Some(background_runnable) = background_runnable {
background_runnable.run();
} else if let Some(foreground_runnable) = foreground_runnable {
if foreground_runnable.metadata().context_is_valid() {
foreground_runnable.run();
} else {
drop(foreground_runnable);
}
}
self.state.lock().is_main_thread = was_main_thread;
true
@@ -272,7 +289,7 @@ impl PlatformDispatcher for TestDispatcher {
self.unparker.unpark();
}
fn dispatch_on_main_thread(&self, runnable: Runnable) {
fn dispatch_on_main_thread(&self, runnable: Runnable<ForegroundContext>) {
self.state
.lock()
.foreground

View File

@@ -1,19 +1,20 @@
use crate::{
point, prelude::*, px, size, transparent_black, Action, AnyDrag, AnyElement, AnyTooltip,
AnyView, App, AppContext, Arena, Asset, AsyncWindowContext, AvailableSpace, Background, Bounds,
BoxShadow, Context, Corners, CursorStyle, Decorations, DevicePixels, DispatchActionListener,
DispatchNodeId, DispatchTree, DisplayId, Edges, Effect, Entity, EntityId, EventEmitter,
FileDropEvent, FontId, Global, GlobalElementId, GlyphId, GpuSpecs, Hsla, InputHandler, IsZero,
KeyBinding, KeyContext, KeyDownEvent, KeyEvent, Keystroke, KeystrokeEvent, LayoutId,
LineLayoutIndex, Modifiers, ModifiersChangedEvent, MonochromeSprite, MouseButton, MouseEvent,
MouseMoveEvent, MouseUpEvent, Path, Pixels, PlatformAtlas, PlatformDisplay, PlatformInput,
PlatformInputHandler, PlatformWindow, Point, PolychromeSprite, PromptLevel, Quad, Render,
RenderGlyphParams, RenderImage, RenderImageParams, RenderSvgParams, Replay, ResizeEdge,
ScaledPixels, Scene, Shadow, SharedString, Size, StrikethroughStyle, Style, SubscriberSet,
Subscription, TaffyLayoutEngine, Task, TextStyle, TextStyleRefinement, TransformationMatrix,
Underline, UnderlineStyle, WindowAppearance, WindowBackgroundAppearance, WindowBounds,
point, prelude::*, px, size, svg_renderer::SMOOTH_SVG_SCALE_FACTOR, transparent_black, Action,
AnyDrag, AnyElement, AnyTooltip, AnyView, App, AppContext, Arena, Asset, AsyncWindowContext,
AvailableSpace, Background, Bounds, BoxShadow, Context, Corners, CursorStyle, Decorations,
DevicePixels, DispatchActionListener, DispatchNodeId, DispatchTree, DisplayId, Edges, Effect,
Entity, EntityId, EventEmitter, FileDropEvent, FontId, Global, GlobalElementId, GlyphId,
GpuSpecs, Hsla, InputHandler, IsZero, KeyBinding, KeyContext, KeyDownEvent, KeyEvent,
Keystroke, KeystrokeEvent, LayoutId, LineLayoutIndex, Modifiers, ModifiersChangedEvent,
MonochromeSprite, MouseButton, MouseEvent, MouseMoveEvent, MouseUpEvent, Path, Pixels,
PlatformAtlas, PlatformDisplay, PlatformInput, PlatformInputHandler, PlatformWindow, Point,
PolychromeSprite, PromptLevel, Quad, Render, RenderGlyphParams, RenderImage, RenderImageParams,
RenderSvgParams, Replay, ResizeEdge, ScaledPixels, Scene, Shadow, SharedString, Size,
StrikethroughStyle, Style, SubscriberSet, Subscription, TaffyLayoutEngine, Task, TextStyle,
TextStyleRefinement, TransformationMatrix, Underline, UnderlineStyle, WeakAsyncApp,
WeakAsyncWindowContext, WindowAppearance, WindowBackgroundAppearance, WindowBounds,
WindowControls, WindowDecorations, WindowOptions, WindowParams, WindowTextSystem,
SMOOTH_SVG_SCALE_FACTOR, SUBPIXEL_VARIANTS,
SUBPIXEL_VARIANTS,
};
use anyhow::{anyhow, Context as _, Result};
use collections::{FxHashMap, FxHashSet};
@@ -765,7 +766,7 @@ impl Window {
platform_window.on_close(Box::new({
let mut cx = cx.to_async();
move || {
let _ = handle.update(&mut cx, |_, window, _| window.remove_window());
let _ = handle.update_weak(&mut cx, |_, window, _| window.remove_window());
}
}));
platform_window.on_request_frame(Box::new({
@@ -779,7 +780,7 @@ impl Window {
let next_frame_callbacks = next_frame_callbacks.take();
if !next_frame_callbacks.is_empty() {
handle
.update(&mut cx, |_, window, cx| {
.update_weak(&mut cx, |_, window, cx| {
for callback in next_frame_callbacks {
callback(window, cx);
}
@@ -797,7 +798,7 @@ impl Window {
if invalidator.is_dirty() {
measure("frame duration", || {
handle
.update(&mut cx, |_, window, cx| {
.update_weak(&mut cx, |_, window, cx| {
window.draw(cx);
window.present();
})
@@ -805,12 +806,12 @@ impl Window {
})
} else if needs_present {
handle
.update(&mut cx, |_, window, _| window.present())
.update_weak(&mut cx, |_, window, _| window.present())
.log_err();
}
handle
.update(&mut cx, |_, window, _| {
.update_weak(&mut cx, |_, window, _| {
window.complete_frame();
})
.log_err();
@@ -820,7 +821,7 @@ impl Window {
let mut cx = cx.to_async();
move |_, _| {
handle
.update(&mut cx, |_, window, cx| window.bounds_changed(cx))
.update_weak(&mut cx, |_, window, cx| window.bounds_changed(cx))
.log_err();
}
}));
@@ -828,7 +829,7 @@ impl Window {
let mut cx = cx.to_async();
move || {
handle
.update(&mut cx, |_, window, cx| window.bounds_changed(cx))
.update_weak(&mut cx, |_, window, cx| window.bounds_changed(cx))
.log_err();
}
}));
@@ -836,7 +837,7 @@ impl Window {
let mut cx = cx.to_async();
move || {
handle
.update(&mut cx, |_, window, cx| window.appearance_changed(cx))
.update_weak(&mut cx, |_, window, cx| window.appearance_changed(cx))
.log_err();
}
}));
@@ -844,7 +845,7 @@ impl Window {
let mut cx = cx.to_async();
move |active| {
handle
.update(&mut cx, |_, window, cx| {
.update_weak(&mut cx, |_, window, cx| {
window.active.set(active);
window
.activation_observers
@@ -859,7 +860,7 @@ impl Window {
let mut cx = cx.to_async();
move |active| {
handle
.update(&mut cx, |_, window, _| {
.update_weak(&mut cx, |_, window, _| {
window.hovered.set(active);
window.refresh();
})
@@ -870,7 +871,7 @@ impl Window {
let mut cx = cx.to_async();
Box::new(move |event| {
handle
.update(&mut cx, |_, window, cx| window.dispatch_event(event, cx))
.update_weak(&mut cx, |_, window, cx| window.dispatch_event(event, cx))
.log_err()
.unwrap_or(DispatchEventResult::default())
})
@@ -1268,8 +1269,8 @@ impl Window {
/// Creates an [`AsyncWindowContext`], which has a static lifetime and can be held across
/// await points in async code.
pub fn to_async(&self, cx: &App) -> AsyncWindowContext {
AsyncWindowContext::new_context(cx.to_async(), self.handle)
pub fn to_async(&self, cx: &App) -> WeakAsyncWindowContext {
WeakAsyncWindowContext::new_context(cx.to_async(), self.handle)
}
/// Schedule the given closure to be run directly after the current frame is rendered.
@@ -3260,8 +3261,7 @@ impl Window {
.flush_dispatch(currently_pending.keystrokes, &dispatch_path);
window.replay_pending_input(to_replay, cx)
})
.log_err();
});
}));
self.pending_input = Some(currently_pending);
self.pending_input_changed(cx);
@@ -3946,6 +3946,17 @@ impl AnyWindowHandle {
cx.update_window(self, update)
}
/// Updates the state of the root view of this window.
///
/// This will fail if the window has been closed.
pub fn update_weak<R>(
self,
cx: &mut WeakAsyncApp,
update: impl FnOnce(AnyView, &mut Window, &mut App) -> R,
) -> Result<R> {
cx.update(|cx| cx.update_window(self, update))?
}
/// Read the state of the root view of this window.
///
/// This will fail if the window has been closed.