Make the gpui_tokio crate generic over the context it spawns (#23995)

Part of  #21092

Makes `Tokio::spawn` generic over any `AppContext`.

Also removes a stray `model_context` I missed

Release Notes:

- N/A
This commit is contained in:
Mikayla Maki 2025-01-30 18:00:55 -08:00 committed by GitHub
parent ff43b6875b
commit 517e519bdc
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 133 additions and 18 deletions

View file

@ -22,9 +22,9 @@ use slotmap::SlotMap;
pub use async_context::*;
use collections::{FxHashMap, FxHashSet, HashMap, VecDeque};
pub use context::*;
pub use entity_map::*;
use http_client::HttpClient;
pub use model_context::*;
#[cfg(any(test, feature = "test-support"))]
pub use test_context::*;
use util::ResultExt;
@ -41,8 +41,8 @@ use crate::{
};
mod async_context;
mod context;
mod entity_map;
mod model_context;
#[cfg(any(test, feature = "test-support"))]
mod test_context;
@ -1667,6 +1667,21 @@ impl AppContext for App {
Ok(read(view, self))
}
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) -> Self::Result<R>
where
G: Global,
{
let mut g = self.global::<G>();
callback(&g, self)
}
}
/// These effects are processed at the end of each application update cycle.

View file

@ -104,6 +104,22 @@ impl AppContext for AsyncApp {
let lock = app.borrow();
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) -> Self::Result<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)))
}
}
impl AsyncApp {
@ -367,6 +383,20 @@ impl AppContext for AsyncWindowContext {
{
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 read_global<G, R>(&self, callback: impl FnOnce(&G, &App) -> R) -> Result<R>
where
G: Global,
{
self.app.read_global(callback)
}
}
impl VisualContext for AsyncWindowContext {

View file

@ -1,7 +1,7 @@
use crate::{
AnyView, AnyWindowHandle, App, AppContext, AsyncApp, DispatchPhase, Effect, EntityId,
EventEmitter, FocusHandle, FocusOutEvent, Focusable, Global, KeystrokeObserver, Reservation,
SubscriberSet, Subscription, Task, WeakEntity, WeakFocusHandle, Window, WindowHandle,
AnyView, AnyWindowHandle, AppContext, AsyncApp, DispatchPhase, Effect, EntityId, EventEmitter,
FocusHandle, FocusOutEvent, Focusable, Global, KeystrokeObserver, Reservation, SubscriberSet,
Subscription, Task, WeakEntity, WeakFocusHandle, Window, WindowHandle,
};
use anyhow::Result;
use derive_more::{Deref, DerefMut};
@ -13,7 +13,7 @@ use std::{
sync::Arc,
};
use super::{AsyncWindowContext, Entity, KeystrokeEvent};
use super::{App, AsyncWindowContext, Entity, KeystrokeEvent};
/// The app context, with specialized behavior for the given model.
#[derive(Deref, DerefMut)]
@ -717,6 +717,20 @@ impl<'a, T> AppContext for Context<'a, T> {
{
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 read_global<G, R>(&self, callback: impl FnOnce(&G, &App) -> R) -> Self::Result<R>
where
G: Global,
{
self.app.read_global(callback)
}
}
impl<T> Borrow<App> for Context<'_, T> {

View file

@ -94,6 +94,21 @@ impl AppContext for TestAppContext {
let app = self.app.borrow();
app.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) -> Self::Result<R>
where
G: Global,
{
let app = self.app.borrow();
app.read_global(callback)
}
}
impl TestAppContext {
@ -906,6 +921,20 @@ impl AppContext for VisualTestContext {
{
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.cx.background_spawn(future)
}
fn read_global<G, R>(&self, callback: impl FnOnce(&G, &App) -> R) -> Self::Result<R>
where
G: Global,
{
self.cx.read_global(callback)
}
}
impl VisualContext for VisualTestContext {

View file

@ -155,7 +155,7 @@ pub use util::arc_cow::ArcCow;
pub use view::*;
pub use window::*;
use std::{any::Any, borrow::BorrowMut};
use std::{any::Any, borrow::BorrowMut, future::Future};
use taffy::TaffyLayoutEngine;
/// The context trait, allows the different contexts in GPUI to be used
@ -215,6 +215,16 @@ pub trait AppContext {
) -> Result<R>
where
T: 'static;
/// Spawn a future on a background thread
fn background_spawn<R>(&self, future: impl Future<Output = R> + Send + 'static) -> Task<R>
where
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>
where
G: Global;
}
/// Returned by [Context::reserve_entity] to later be passed to [Context::insert_model].

View file

@ -81,6 +81,20 @@ pub fn derive_app_context(input: TokenStream) -> TokenStream {
{
self.#app_variable.read_window(window, read)
}
fn background_spawn<R>(&self, future: impl std::future::Future<Output = R> + Send + 'static) -> gpui::Task<R>
where
R: Send + 'static,
{
self.#app_variable.background_spawn(future)
}
fn read_global<G, R>(&self, callback: impl FnOnce(&G, &gpui::App) -> R) -> Self::Result<R>
where
G: gpui::Global,
{
self.#app_variable.read_global(callback)
}
}
};

View file

@ -1,6 +1,6 @@
use std::future::Future;
use gpui::{App, Global, ReadGlobal, Task};
use gpui::{App, AppContext, Global, ReadGlobal, Task};
use tokio::task::JoinError;
use util::defer;
@ -32,20 +32,23 @@ pub struct Tokio {}
impl Tokio {
/// Spawns the given future on Tokio's thread pool, and returns it via a GPUI task
/// Note that the Tokio task will be cancelled if the GPUI task is dropped
pub fn spawn<Fut, R>(cx: &mut App, f: Fut) -> Task<Result<R, JoinError>>
pub fn spawn<C, Fut, R>(cx: &mut C, f: Fut) -> C::Result<Task<Result<R, JoinError>>>
where
C: AppContext,
Fut: Future<Output = R> + Send + 'static,
R: Send + 'static,
{
let join_handle = GlobalTokio::global(cx).runtime.spawn(f);
let abort_handle = join_handle.abort_handle();
let cancel = defer(move || {
abort_handle.abort();
});
cx.background_executor().spawn(async move {
let result = join_handle.await;
drop(cancel);
result
cx.read_global(|tokio: &GlobalTokio, cx| {
let join_handle = tokio.runtime.spawn(f);
let abort_handle = join_handle.abort_handle();
let cancel = defer(move || {
abort_handle.abort();
});
cx.background_spawn(async move {
let result = join_handle.await;
drop(cancel);
result
})
})
}