async_context.rs

  1use crate::{
  2    AnyView, AnyWindowHandle, App, AppCell, AppContext, BackgroundExecutor, BorrowAppContext,
  3    Entity, EventEmitter, Focusable, ForegroundExecutor, Global, GpuiBorrow, PromptButton,
  4    PromptLevel, Render, Reservation, Result, Subscription, Task, VisualContext, Window,
  5    WindowHandle,
  6};
  7use anyhow::Context as _;
  8use derive_more::{Deref, DerefMut};
  9use futures::channel::oneshot;
 10use futures::future::FutureExt;
 11use std::{future::Future, rc::Weak};
 12
 13use super::{Context, WeakEntity};
 14
 15/// An async-friendly version of [App] with a static lifetime so it can be held across `await` points in async code.
 16/// You're provided with an instance when calling [App::spawn], and you can also create one with [App::to_async].
 17///
 18/// Internally, this holds a weak reference to an `App`. Methods will panic if the app has been dropped,
 19/// but this should not happen in practice when using foreground tasks spawned via `cx.spawn()`,
 20/// as the executor checks if the app is alive before running each task.
 21#[derive(Clone)]
 22pub struct AsyncApp {
 23    pub(crate) app: Weak<AppCell>,
 24    pub(crate) background_executor: BackgroundExecutor,
 25    pub(crate) foreground_executor: ForegroundExecutor,
 26}
 27
 28impl AsyncApp {
 29    fn app(&self) -> std::rc::Rc<AppCell> {
 30        self.app
 31            .upgrade()
 32            .expect("app was released before async operation completed")
 33    }
 34}
 35
 36impl AppContext for AsyncApp {
 37    fn new<T: 'static>(&mut self, build_entity: impl FnOnce(&mut Context<T>) -> T) -> Entity<T> {
 38        let app = self.app();
 39        let mut app = app.borrow_mut();
 40        app.new(build_entity)
 41    }
 42
 43    fn reserve_entity<T: 'static>(&mut self) -> Reservation<T> {
 44        let app = self.app();
 45        let mut app = app.borrow_mut();
 46        app.reserve_entity()
 47    }
 48
 49    fn insert_entity<T: 'static>(
 50        &mut self,
 51        reservation: Reservation<T>,
 52        build_entity: impl FnOnce(&mut Context<T>) -> T,
 53    ) -> Entity<T> {
 54        let app = self.app();
 55        let mut app = app.borrow_mut();
 56        app.insert_entity(reservation, build_entity)
 57    }
 58
 59    fn update_entity<T: 'static, R>(
 60        &mut self,
 61        handle: &Entity<T>,
 62        update: impl FnOnce(&mut T, &mut Context<T>) -> R,
 63    ) -> R {
 64        let app = self.app();
 65        let mut app = app.borrow_mut();
 66        app.update_entity(handle, update)
 67    }
 68
 69    fn as_mut<'a, T>(&'a mut self, _handle: &Entity<T>) -> GpuiBorrow<'a, T>
 70    where
 71        T: 'static,
 72    {
 73        panic!("Cannot as_mut with an async context. Try calling update() first")
 74    }
 75
 76    fn read_entity<T, R>(&self, handle: &Entity<T>, callback: impl FnOnce(&T, &App) -> R) -> R
 77    where
 78        T: 'static,
 79    {
 80        let app = self.app();
 81        let lock = app.borrow();
 82        lock.read_entity(handle, callback)
 83    }
 84
 85    fn update_window<T, F>(&mut self, window: AnyWindowHandle, f: F) -> Result<T>
 86    where
 87        F: FnOnce(AnyView, &mut Window, &mut App) -> T,
 88    {
 89        let app = self.app.upgrade().context("app was released")?;
 90        let mut lock = app.try_borrow_mut()?;
 91        lock.update_window(window, f)
 92    }
 93
 94    fn read_window<T, R>(
 95        &self,
 96        window: &WindowHandle<T>,
 97        read: impl FnOnce(Entity<T>, &App) -> R,
 98    ) -> Result<R>
 99    where
100        T: 'static,
101    {
102        let app = self.app.upgrade().context("app was released")?;
103        let lock = app.borrow();
104        lock.read_window(window, read)
105    }
106
107    #[track_caller]
108    fn background_spawn<R>(&self, future: impl Future<Output = R> + Send + 'static) -> Task<R>
109    where
110        R: Send + 'static,
111    {
112        self.background_executor.spawn(future)
113    }
114
115    fn read_global<G, R>(&self, callback: impl FnOnce(&G, &App) -> R) -> R
116    where
117        G: Global,
118    {
119        let app = self.app();
120        let mut lock = app.borrow_mut();
121        lock.update(|this| this.read_global(callback))
122    }
123}
124
125impl AsyncApp {
126    /// Schedules all windows in the application to be redrawn.
127    pub fn refresh(&self) {
128        let app = self.app();
129        let mut lock = app.borrow_mut();
130        lock.refresh_windows();
131    }
132
133    /// Get an executor which can be used to spawn futures in the background.
134    pub fn background_executor(&self) -> &BackgroundExecutor {
135        &self.background_executor
136    }
137
138    /// Get an executor which can be used to spawn futures in the foreground.
139    pub fn foreground_executor(&self) -> &ForegroundExecutor {
140        &self.foreground_executor
141    }
142
143    /// Invoke the given function in the context of the app, then flush any effects produced during its invocation.
144    pub fn update<R>(&self, f: impl FnOnce(&mut App) -> R) -> R {
145        let app = self.app();
146        let mut lock = app.borrow_mut();
147        lock.update(f)
148    }
149
150    /// Arrange for the given callback to be invoked whenever the given entity emits an event of a given type.
151    /// The callback is provided a handle to the emitting entity and a reference to the emitted event.
152    pub fn subscribe<T, Event>(
153        &mut self,
154        entity: &Entity<T>,
155        on_event: impl FnMut(Entity<T>, &Event, &mut App) + 'static,
156    ) -> Subscription
157    where
158        T: 'static + EventEmitter<Event>,
159        Event: 'static,
160    {
161        let app = self.app();
162        let mut lock = app.borrow_mut();
163        lock.subscribe(entity, on_event)
164    }
165
166    /// Open a window with the given options based on the root view returned by the given function.
167    pub fn open_window<V>(
168        &self,
169        options: crate::WindowOptions,
170        build_root_view: impl FnOnce(&mut Window, &mut App) -> Entity<V>,
171    ) -> Result<WindowHandle<V>>
172    where
173        V: 'static + Render,
174    {
175        let app = self.app();
176        let mut lock = app.borrow_mut();
177        lock.open_window(options, build_root_view)
178    }
179
180    /// Schedule a future to be polled in the foreground.
181    #[track_caller]
182    pub fn spawn<AsyncFn, R>(&self, f: AsyncFn) -> Task<R>
183    where
184        AsyncFn: AsyncFnOnce(&mut AsyncApp) -> R + 'static,
185        R: 'static,
186    {
187        let mut cx = self.clone();
188        self.foreground_executor
189            .spawn(async move { f(&mut cx).await }.boxed_local())
190    }
191
192    /// Determine whether global state of the specified type has been assigned.
193    pub fn has_global<G: Global>(&self) -> bool {
194        let app = self.app();
195        let app = app.borrow_mut();
196        app.has_global::<G>()
197    }
198
199    /// Reads the global state of the specified type, passing it to the given callback.
200    ///
201    /// Panics if no global state of the specified type has been assigned.
202    pub fn read_global<G: Global, R>(&self, read: impl FnOnce(&G, &App) -> R) -> R {
203        let app = self.app();
204        let app = app.borrow_mut();
205        read(app.global(), &app)
206    }
207
208    /// Reads the global state of the specified type, passing it to the given callback.
209    ///
210    /// Similar to [`AsyncApp::read_global`], but returns an error instead of panicking
211    pub fn try_read_global<G: Global, R>(&self, read: impl FnOnce(&G, &App) -> R) -> Option<R> {
212        let app = self.app();
213        let app = app.borrow_mut();
214        Some(read(app.try_global()?, &app))
215    }
216
217    /// Reads the global state of the specified type, passing it to the given callback.
218    /// A default value is assigned if a global of this type has not yet been assigned.
219    pub fn read_default_global<G: Global + Default, R>(
220        &self,
221        read: impl FnOnce(&G, &App) -> R,
222    ) -> R {
223        let app = self.app();
224        let mut app = app.borrow_mut();
225        app.update(|cx| {
226            cx.default_global::<G>();
227        });
228        read(app.global(), &app)
229    }
230
231    /// A convenience method for [`App::update_global`](BorrowAppContext::update_global)
232    /// for updating the global state of the specified type.
233    pub fn update_global<G: Global, R>(&self, update: impl FnOnce(&mut G, &mut App) -> R) -> R {
234        let app = self.app();
235        let mut app = app.borrow_mut();
236        app.update(|cx| cx.update_global(update))
237    }
238
239    /// Run something using this entity and cx, when the returned struct is dropped
240    pub fn on_drop<T: 'static, Callback: FnOnce(&mut T, &mut Context<T>) + 'static>(
241        &self,
242        entity: &WeakEntity<T>,
243        f: Callback,
244    ) -> gpui_util::Deferred<impl FnOnce() + use<T, Callback>> {
245        let entity = entity.clone();
246        let mut cx = self.clone();
247        gpui_util::defer(move || {
248            entity.update(&mut cx, f).ok();
249        })
250    }
251}
252
253/// A cloneable, owned handle to the application context,
254/// composed with the window associated with the current task.
255#[derive(Clone, Deref, DerefMut)]
256pub struct AsyncWindowContext {
257    #[deref]
258    #[deref_mut]
259    app: AsyncApp,
260    window: AnyWindowHandle,
261}
262
263impl AsyncWindowContext {
264    pub(crate) fn new_context(app: AsyncApp, window: AnyWindowHandle) -> Self {
265        Self { app, window }
266    }
267
268    /// Get the handle of the window this context is associated with.
269    pub fn window_handle(&self) -> AnyWindowHandle {
270        self.window
271    }
272
273    /// A convenience method for [`App::update_window`].
274    pub fn update<R>(&mut self, update: impl FnOnce(&mut Window, &mut App) -> R) -> Result<R> {
275        self.app
276            .update_window(self.window, |_, window, cx| update(window, cx))
277    }
278
279    /// A convenience method for [`App::update_window`].
280    pub fn update_root<R>(
281        &mut self,
282        update: impl FnOnce(AnyView, &mut Window, &mut App) -> R,
283    ) -> Result<R> {
284        self.app.update_window(self.window, update)
285    }
286
287    /// A convenience method for [`Window::on_next_frame`].
288    pub fn on_next_frame(&mut self, f: impl FnOnce(&mut Window, &mut App) + 'static) {
289        self.app
290            .update_window(self.window, |_, window, _| window.on_next_frame(f))
291            .ok();
292    }
293
294    /// A convenience method for [`App::global`].
295    pub fn read_global<G: Global, R>(
296        &mut self,
297        read: impl FnOnce(&G, &Window, &App) -> R,
298    ) -> Result<R> {
299        self.app
300            .update_window(self.window, |_, window, cx| read(cx.global(), window, cx))
301    }
302
303    /// A convenience method for [`App::update_global`](BorrowAppContext::update_global).
304    /// for updating the global state of the specified type.
305    pub fn update_global<G, R>(
306        &mut self,
307        update: impl FnOnce(&mut G, &mut Window, &mut App) -> R,
308    ) -> Result<R>
309    where
310        G: Global,
311    {
312        self.app.update_window(self.window, |_, window, cx| {
313            cx.update_global(|global, cx| update(global, window, cx))
314        })
315    }
316
317    /// Schedule a future to be executed on the main thread. This is used for collecting
318    /// the results of background tasks and updating the UI.
319    #[track_caller]
320    pub fn spawn<AsyncFn, R>(&self, f: AsyncFn) -> Task<R>
321    where
322        AsyncFn: AsyncFnOnce(&mut AsyncWindowContext) -> R + 'static,
323        R: 'static,
324    {
325        let mut cx = self.clone();
326        self.foreground_executor
327            .spawn(async move { f(&mut cx).await }.boxed_local())
328    }
329
330    /// Present a platform dialog.
331    /// The provided message will be presented, along with buttons for each answer.
332    /// When a button is clicked, the returned Receiver will receive the index of the clicked button.
333    pub fn prompt<T>(
334        &mut self,
335        level: PromptLevel,
336        message: &str,
337        detail: Option<&str>,
338        answers: &[T],
339    ) -> oneshot::Receiver<usize>
340    where
341        T: Clone + Into<PromptButton>,
342    {
343        self.app
344            .update_window(self.window, |_, window, cx| {
345                window.prompt(level, message, detail, answers, cx)
346            })
347            .unwrap_or_else(|_| oneshot::channel().1)
348    }
349}
350
351impl AppContext for AsyncWindowContext {
352    fn new<T>(&mut self, build_entity: impl FnOnce(&mut Context<T>) -> T) -> Entity<T>
353    where
354        T: 'static,
355    {
356        self.app.new(build_entity)
357    }
358
359    fn reserve_entity<T: 'static>(&mut self) -> Reservation<T> {
360        self.app.reserve_entity()
361    }
362
363    fn insert_entity<T: 'static>(
364        &mut self,
365        reservation: Reservation<T>,
366        build_entity: impl FnOnce(&mut Context<T>) -> T,
367    ) -> Entity<T> {
368        self.app.insert_entity(reservation, build_entity)
369    }
370
371    fn update_entity<T: 'static, R>(
372        &mut self,
373        handle: &Entity<T>,
374        update: impl FnOnce(&mut T, &mut Context<T>) -> R,
375    ) -> R {
376        self.app.update_entity(handle, update)
377    }
378
379    fn as_mut<'a, T>(&'a mut self, _: &Entity<T>) -> GpuiBorrow<'a, T>
380    where
381        T: 'static,
382    {
383        panic!("Cannot use as_mut() from an async context, call `update`")
384    }
385
386    fn read_entity<T, R>(&self, handle: &Entity<T>, read: impl FnOnce(&T, &App) -> R) -> R
387    where
388        T: 'static,
389    {
390        self.app.read_entity(handle, read)
391    }
392
393    fn update_window<T, F>(&mut self, window: AnyWindowHandle, update: F) -> Result<T>
394    where
395        F: FnOnce(AnyView, &mut Window, &mut App) -> T,
396    {
397        self.app.update_window(window, update)
398    }
399
400    fn read_window<T, R>(
401        &self,
402        window: &WindowHandle<T>,
403        read: impl FnOnce(Entity<T>, &App) -> R,
404    ) -> Result<R>
405    where
406        T: 'static,
407    {
408        self.app.read_window(window, read)
409    }
410
411    #[track_caller]
412    fn background_spawn<R>(&self, future: impl Future<Output = R> + Send + 'static) -> Task<R>
413    where
414        R: Send + 'static,
415    {
416        self.app.background_executor.spawn(future)
417    }
418
419    fn read_global<G, R>(&self, callback: impl FnOnce(&G, &App) -> R) -> R
420    where
421        G: Global,
422    {
423        self.app.read_global(callback)
424    }
425}
426
427impl VisualContext for AsyncWindowContext {
428    type Result<T> = Result<T>;
429
430    fn window_handle(&self) -> AnyWindowHandle {
431        self.window
432    }
433
434    fn new_window_entity<T: 'static>(
435        &mut self,
436        build_entity: impl FnOnce(&mut Window, &mut Context<T>) -> T,
437    ) -> Result<Entity<T>> {
438        self.app.update_window(self.window, |_, window, cx| {
439            cx.new(|cx| build_entity(window, cx))
440        })
441    }
442
443    fn update_window_entity<T: 'static, R>(
444        &mut self,
445        view: &Entity<T>,
446        update: impl FnOnce(&mut T, &mut Window, &mut Context<T>) -> R,
447    ) -> Result<R> {
448        self.app.update_window(self.window, |_, window, cx| {
449            view.update(cx, |entity, cx| update(entity, window, cx))
450        })
451    }
452
453    fn replace_root_view<V>(
454        &mut self,
455        build_view: impl FnOnce(&mut Window, &mut Context<V>) -> V,
456    ) -> Result<Entity<V>>
457    where
458        V: 'static + Render,
459    {
460        self.app.update_window(self.window, |_, window, cx| {
461            window.replace_root(cx, build_view)
462        })
463    }
464
465    fn focus<V>(&mut self, view: &Entity<V>) -> Result<()>
466    where
467        V: Focusable,
468    {
469        self.app.update_window(self.window, |_, window, cx| {
470            view.read(cx).focus_handle(cx).focus(window, cx);
471        })
472    }
473}