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

Mikayla Maki created

Part of  #21092

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

Also removes a stray `model_context` I missed

Release Notes:

- N/A

Change summary

crates/gpui/src/app.rs                       | 19 ++++++++++++-
crates/gpui/src/app/async_context.rs         | 30 ++++++++++++++++++++++
crates/gpui/src/app/context.rs               | 22 +++++++++++++--
crates/gpui/src/app/test_context.rs          | 29 +++++++++++++++++++++
crates/gpui/src/gpui.rs                      | 12 ++++++++
crates/gpui_macros/src/derive_app_context.rs | 14 ++++++++++
crates/gpui_tokio/src/gpui_tokio.rs          | 25 ++++++++++--------
7 files changed, 133 insertions(+), 18 deletions(-)

Detailed changes

crates/gpui/src/app.rs 🔗

@@ -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.

crates/gpui/src/app/async_context.rs 🔗

@@ -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 {

crates/gpui/src/app/model_context.rs → crates/gpui/src/app/context.rs 🔗

@@ -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> {

crates/gpui/src/app/test_context.rs 🔗

@@ -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 {

crates/gpui/src/gpui.rs 🔗

@@ -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].

crates/gpui_macros/src/derive_app_context.rs 🔗

@@ -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)
+            }
         }
     };
 

crates/gpui_tokio/src/gpui_tokio.rs 🔗

@@ -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
+            })
         })
     }