Proof of concept: move the window and app context checks into the executor

This commit is contained in:
Mikayla Maki
2025-03-13 19:20:36 -07:00
parent 0081b816fe
commit d3ff40ef01
11 changed files with 508 additions and 475 deletions

358
Cargo.lock generated

File diff suppressed because it is too large Load Diff

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;
@@ -1054,7 +1054,9 @@ 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))
}
/// Schedules the given function to be run at the end of the current effect cycle, allowing entities
@@ -1596,8 +1598,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 +1621,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 +1629,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 +1655,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 +1700,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,
{

View File

@@ -1,9 +1,9 @@
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 derive_more::{Deref, DerefMut};
use futures::channel::oneshot;
use std::{future::Future, rc::Weak};
@@ -21,73 +21,55 @@ pub struct AsyncApp {
}
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"))?;
) -> Entity<T> {
let app = self.app.upgrade().unwrap();
let mut app = app.borrow_mut();
Ok(app.new(build_entity))
app.new(build_entity)
}
fn reserve_entity<T: 'static>(&mut self) -> Result<Reservation<T>> {
let app = self
.app
.upgrade()
.ok_or_else(|| anyhow!("app was released"))?;
fn reserve_entity<T: 'static>(&mut self) -> Reservation<T> {
let app = self.app.upgrade().unwrap();
let mut app = app.borrow_mut();
Ok(app.reserve_entity())
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()
.ok_or_else(|| anyhow!("app was released"))?;
) -> Entity<T> {
let app = self.app.upgrade().unwrap();
let mut app = app.borrow_mut();
Ok(app.insert_entity(reservation, build_entity))
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,
) -> Self::Result<R> {
let app = self
.app
.upgrade()
.ok_or_else(|| anyhow!("app was released"))?;
) -> R {
let app = self.app.upgrade().unwrap();
let mut app = app.borrow_mut();
Ok(app.update_entity(handle, update))
app.update_entity(handle, update)
}
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 app = self.app.upgrade().unwrap();
let lock = app.borrow();
Ok(lock.read_entity(handle, callback))
lock.read_entity(handle, callback)
}
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 app = self.app.upgrade().unwrap();
let mut lock = app.borrow_mut();
lock.update_window(window, f)
}
@@ -100,7 +82,7 @@ impl AppContext for AsyncApp {
where
T: 'static,
{
let app = self.app.upgrade().context("app was released")?;
let app = self.app.upgrade().unwrap();
let lock = app.borrow();
lock.read_window(window, read)
}
@@ -112,23 +94,20 @@ impl AppContext for AsyncApp {
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,
{
let app = self.app.upgrade().context("app was released")?;
let app = self.app.upgrade().unwrap();
let mut lock = app.borrow_mut();
Ok(lock.update(|this| this.read_global(callback)))
lock.update(|this| this.read_global(callback))
}
}
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 app = self.app.upgrade().unwrap();
let mut lock = app.borrow_mut();
lock.refresh_windows();
Ok(())
@@ -146,10 +125,7 @@ impl AsyncApp {
/// 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 app = self.app.upgrade().unwrap();
let mut lock = app.borrow_mut();
Ok(lock.update(f))
}
@@ -163,10 +139,7 @@ impl AsyncApp {
where
V: 'static + Render,
{
let app = self
.app
.upgrade()
.ok_or_else(|| anyhow!("app was released"))?;
let app = self.app.upgrade().unwrap();
let mut lock = app.borrow_mut();
lock.open_window(options, build_root_view)
}
@@ -178,31 +151,26 @@ impl AsyncApp {
Fut: Future<Output = R> + 'static,
R: 'static,
{
self.foreground_executor.spawn(f(self.clone()))
self.foreground_executor
.spawn_with_context(ForegroundContext::app(&self.app), f(self.clone()))
}
/// 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"))?;
pub fn has_global<G: Global>(&self) -> bool {
let app = self.app.upgrade().unwrap();
let app = app.borrow_mut();
Ok(app.has_global::<G>())
app.has_global::<G>()
}
/// 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"))?;
pub fn read_global<G: Global, R>(&self, read: impl FnOnce(&G, &App) -> R) -> R {
let app = self.app.upgrade().unwrap();
let app = app.borrow_mut();
Ok(read(app.global(), &app))
read(app.global(), &app)
}
/// Reads the global state of the specified type, passing it to the given callback.
@@ -223,10 +191,7 @@ impl AsyncApp {
&self,
update: impl FnOnce(&mut G, &mut App) -> R,
) -> Result<R> {
let app = self
.app
.upgrade()
.ok_or_else(|| anyhow!("app was released"))?;
let app = self.app.upgrade().unwrap();
let mut app = app.borrow_mut();
Ok(app.update(|cx| cx.update_global(update)))
}
@@ -304,7 +269,10 @@ impl AsyncWindowContext {
Fut: Future<Output = R> + 'static,
R: 'static,
{
self.foreground_executor.spawn(f(self.clone()))
self.foreground_executor.spawn_with_context(
ForegroundContext::window(&self.app.app, self.window.id),
f(self.clone()),
)
}
/// Present a platform dialog.
@@ -326,42 +294,42 @@ impl AsyncWindowContext {
}
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.window
.update(self, |_, _, cx| 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.window
.update(self, |_, _, cx| 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>> {
) -> Entity<T> {
self.window
.update(self, |_, _, cx| 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> {
) -> R {
self.window
.update(self, |_, _, cx| 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,
{
@@ -393,7 +361,7 @@ impl AppContext for AsyncWindowContext {
self.app.background_executor.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,
{
@@ -409,38 +377,44 @@ 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>> {
) -> Entity<T> {
self.window
.update(self, |_, window, cx| cx.new(|cx| build_entity(window, cx)))
.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.window
.update(self, |_, window, cx| {
view.update(cx, |entity, cx| update(entity, window, cx))
})
.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))
.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.window
.update(self, |_, window, cx| {
view.read(cx).focus_handle(cx).clone().focus(window);
})
.unwrap()
}
}

View File

@@ -679,9 +679,7 @@ impl<T> Context<'_, T> {
}
}
impl<T> AppContext for Context<'_, T> {
type Result<U> = U;
impl<T> AppContext for Context<T> {
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,
};
@@ -30,17 +30,15 @@ pub struct TestAppContext {
}
impl AppContext for TestAppContext {
type Result<T> = T;
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 +47,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 +56,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 +96,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 +323,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
@@ -868,16 +865,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 +880,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 +888,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 +927,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 +944,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 +956,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 +967,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 +978,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

@@ -1,10 +1,7 @@
use crate::{App, PlatformDispatcher};
use async_task::Runnable;
use crate::{App, ForegroundContext, 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,
@@ -17,6 +14,7 @@ use std::{
Arc,
},
task::{Context, Poll},
thread::ThreadId,
time::{Duration, Instant},
};
use util::TryFutureExt;
@@ -44,6 +42,7 @@ pub struct BackgroundExecutor {
pub struct ForegroundExecutor {
#[doc(hidden)]
pub dispatcher: Arc<dyn PlatformDispatcher>,
not_send: PhantomData<Rc<()>>,
}
@@ -64,6 +63,9 @@ enum TaskState<T> {
/// A task that is currently running.
Spawned(async_task::Task<T>),
/// A task that is currently running on the foreground
SpawnedOnForeground(async_task::Task<T, ForegroundContext>),
}
impl<T> Task<T> {
@@ -77,6 +79,7 @@ impl<T> Task<T> {
match self {
Task(TaskState::Ready(_)) => {}
Task(TaskState::Spawned(task)) => task.detach(),
Task(TaskState::SpawnedOnForeground(task)) => task.detach(),
}
}
}
@@ -92,7 +95,7 @@ where
pub fn detach_and_log_err(self, cx: &App) {
let location = core::panic::Location::caller();
cx.foreground_executor()
.spawn(self.log_tracked_err(*location))
.spawn_with_context(ForegroundContext::none(), self.log_tracked_err(*location))
.detach();
}
}
@@ -104,6 +107,7 @@ impl<T> Future for Task<T> {
match unsafe { self.get_unchecked_mut() } {
Task(TaskState::Ready(val)) => Poll::Ready(val.take().unwrap()),
Task(TaskState::Spawned(task)) => task.poll(cx),
Task(TaskState::SpawnedOnForeground(task)) => task.poll(cx),
}
}
}
@@ -127,8 +131,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.
@@ -441,6 +443,17 @@ impl BackgroundExecutor {
}
}
/// std::thread::current() doesn't always reliably return the same ID.
/// This does.
#[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())
}
/// ForegroundExecutor runs things on the main thread.
impl ForegroundExecutor {
/// Creates a new ForegroundExecutor from the given PlatformDispatcher.
@@ -457,88 +470,31 @@ impl ForegroundExecutor {
where
R: 'static,
{
self.spawn_with_context(ForegroundContext::none(), future)
}
/// 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
pub fn spawn_with_context<R>(
&self,
mut context: ForegroundContext,
future: impl Future<Output = R> + 'static,
) -> Task<R>
where
R: 'static,
{
context.spawning_thread = Some(thread_id());
let dispatcher = self.dispatcher.clone();
#[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))
}
inner::<R>(dispatcher, Box::pin(future))
let (runnable, task) = Builder::new().metadata(context).spawn_local(
|_| future,
move |runnable| dispatcher.dispatch_on_main_thread(runnable),
);
runnable.schedule();
Task(TaskState::SpawnedOnForeground(task))
}
}
/// 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();
}
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: 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) }
}
}
// 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`].
pub struct Scope<'a> {
executor: BackgroundExecutor,

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,12 @@ 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, thread_id, Action, AnyWindowHandle, App, AppCell, 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,
WindowId, DEFAULT_WINDOW_SIZE,
};
use anyhow::{anyhow, Result};
use async_task::Runnable;
@@ -47,6 +48,9 @@ use std::borrow::Cow;
use std::hash::{Hash, Hasher};
use std::io::Cursor;
use std::ops;
use std::panic::Location;
use std::rc::Weak;
use std::thread::ThreadId;
use std::time::{Duration, Instant};
use std::{
fmt::{self, Debug},
@@ -450,13 +454,104 @@ pub(crate) trait PlatformWindow: HasWindowHandle + HasDisplayHandle {
}
}
#[derive(Debug)]
enum Contexts {
App {
app: Weak<AppCell>,
},
Window {
app: Weak<AppCell>,
window: WindowId,
},
}
/// TODO
#[derive(Debug)]
pub struct ForegroundContext {
pub(crate) spawning_thread: Option<ThreadId>,
location: &'static Location<'static>,
inner: Option<Contexts>,
}
// SAFETY: Foreground context will safely panic before accessing non-thread safe data
unsafe impl Send for ForegroundContext {}
unsafe impl Sync for ForegroundContext {}
impl ForegroundContext {
/// TODO
#[track_caller]
pub fn app(app: &Weak<AppCell>) -> Self {
Self::contexts(Some(Contexts::App { app: app.clone() }))
}
/// TODO
#[track_caller]
pub fn window(app: &Weak<AppCell>, window: WindowId) -> Self {
Self::contexts(Some(Contexts::Window {
app: app.clone(),
window,
}))
}
/// TODO
#[track_caller]
pub fn none() -> Self {
Self::contexts(None)
}
#[track_caller]
fn contexts(contexts: Option<Contexts>) -> Self {
Self {
spawning_thread: None,
location: core::panic::Location::caller(),
inner: contexts,
}
}
fn assert_thread_is_valid(&self) {
if let Some(spawning_thread) = self.spawning_thread {
assert!(
spawning_thread == thread_id(),
"local task polled by a thread that didn't spawn it. Task spawned at {}",
self.location
);
}
}
/// TODO
pub fn context_is_valid(&self) -> bool {
self.assert_thread_is_valid();
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,
}
}
}
impl Drop for ForegroundContext {
fn drop(&mut self) {
self.assert_thread_is_valid();
}
}
/// 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;

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,14 @@ 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