Checkpoint

Nathan Sobo created

Change summary

crates/gpui3/src/app.rs               | 46 ++++++++++++--------
crates/gpui3/src/app/async_context.rs | 56 +++++++++++++++++++++----
crates/gpui3/src/app/entity_map.rs    | 63 ++++++++++++++++++++--------
crates/gpui3/src/elements/img.rs      | 16 ++++---
crates/gpui3/src/executor.rs          |  9 +--
crates/gpui3/src/gpui3.rs             |  6 --
crates/gpui3/src/window.rs            | 58 +++++++++++---------------
7 files changed, 156 insertions(+), 98 deletions(-)

Detailed changes

crates/gpui3/src/app.rs 🔗

@@ -29,7 +29,7 @@ use util::{
 };
 
 #[derive(Clone)]
-pub struct App(Arc<Mutex<MainThread<AppContext>>>);
+pub struct App(Arc<Mutex<AppContext>>);
 
 impl App {
     pub fn production(asset_source: Arc<dyn AssetSource>) -> Self {
@@ -53,9 +53,9 @@ impl App {
         let executor = platform.executor();
         let text_system = Arc::new(TextSystem::new(platform.text_system()));
         let entities = EntityMap::new();
-        let unit_entity = entities.redeem(entities.reserve(), ());
+        let unit_entity = entities.insert(entities.reserve(), ());
         Self(Arc::new_cyclic(|this| {
-            Mutex::new(MainThread::new(AppContext {
+            Mutex::new(AppContext {
                 this: this.clone(),
                 platform: MainThreadOnly::new(platform, executor.clone()),
                 executor,
@@ -71,7 +71,7 @@ impl App {
                 pending_effects: Default::default(),
                 observers: Default::default(),
                 layout_id_buffer: Default::default(),
-            }))
+            })
         }))
     }
 
@@ -83,6 +83,7 @@ impl App {
         let platform = self.0.lock().platform.clone();
         platform.borrow_on_main_thread().run(Box::new(move || {
             let cx = &mut *this.0.lock();
+            let cx = unsafe { mem::transmute::<&mut AppContext, &mut MainThread<AppContext>>(cx) };
             on_finish_launching(cx);
         }));
     }
@@ -91,7 +92,7 @@ impl App {
 type Handlers = SmallVec<[Arc<dyn Fn(&mut AppContext) -> bool + Send + Sync + 'static>; 2]>;
 
 pub struct AppContext {
-    this: Weak<Mutex<MainThread<AppContext>>>,
+    this: Weak<Mutex<AppContext>>,
     platform: MainThreadOnly<dyn Platform>,
     text_system: Arc<TextSystem>,
     pending_updates: usize,
@@ -109,7 +110,7 @@ pub struct AppContext {
 }
 
 impl AppContext {
-    fn update<R>(&mut self, update: impl FnOnce(&mut Self) -> R) -> R {
+    pub(crate) fn update<R>(&mut self, update: impl FnOnce(&mut Self) -> R) -> R {
         self.pending_updates += 1;
         let result = update(self);
         if self.pending_updates == 1 {
@@ -144,6 +145,7 @@ impl AppContext {
     }
 
     fn flush_effects(&mut self) {
+        dbg!("flush effects");
         while let Some(effect) = self.pending_effects.pop_front() {
             match effect {
                 Effect::Notify(entity_id) => self.apply_notify_effect(entity_id),
@@ -163,6 +165,8 @@ impl AppContext {
             })
             .collect::<Vec<_>>();
 
+        dbg!(&dirty_window_ids);
+
         for dirty_window_id in dirty_window_ids {
             self.update_window(dirty_window_id, |cx| cx.draw())
                 .unwrap()
@@ -180,8 +184,8 @@ impl AppContext {
         }
     }
 
-    pub fn to_async(&self) -> AsyncContext {
-        AsyncContext(unsafe { mem::transmute(self.this.clone()) })
+    pub fn to_async(&self) -> AsyncAppContext {
+        AsyncAppContext(unsafe { mem::transmute(self.this.clone()) })
     }
 
     pub fn executor(&self) -> &Executor {
@@ -213,7 +217,7 @@ impl AppContext {
         f: impl FnOnce(&mut MainThread<AppContext>) -> F + Send + 'static,
     ) -> Task<R>
     where
-        F: Future<Output = R> + 'static,
+        F: Future<Output = R> + Send + 'static,
         R: Send + 'static,
     {
         let this = self.this.upgrade().unwrap();
@@ -225,14 +229,14 @@ impl AppContext {
         })
     }
 
-    pub fn spawn<Fut, R>(&self, f: impl FnOnce(&mut AppContext) -> Fut + Send + 'static) -> Task<R>
+    pub fn spawn<Fut, R>(&self, f: impl FnOnce(AsyncAppContext) -> Fut + Send + 'static) -> Task<R>
     where
         Fut: Future<Output = R> + Send + 'static,
         R: Send + 'static,
     {
-        let this = self.this.upgrade().unwrap();
+        let cx = self.to_async();
         self.executor.spawn(async move {
-            let future = f(&mut this.lock());
+            let future = f(cx);
             future.await
         })
     }
@@ -298,9 +302,11 @@ impl Context for AppContext {
         &mut self,
         build_entity: impl FnOnce(&mut Self::EntityContext<'_, '_, T>) -> T,
     ) -> Handle<T> {
-        let slot = self.entities.reserve();
-        let entity = build_entity(&mut ModelContext::mutable(self, slot.id));
-        self.entities.redeem(slot, entity)
+        self.update(|cx| {
+            let slot = cx.entities.reserve();
+            let entity = build_entity(&mut ModelContext::mutable(cx, slot.id));
+            cx.entities.insert(slot, entity)
+        })
     }
 
     fn update_entity<T: Send + Sync + 'static, R>(
@@ -308,10 +314,12 @@ impl Context for AppContext {
         handle: &Handle<T>,
         update: impl FnOnce(&mut T, &mut Self::EntityContext<'_, '_, T>) -> R,
     ) -> R {
-        let mut entity = self.entities.lease(handle);
-        let result = update(&mut *entity, &mut ModelContext::mutable(self, handle.id));
-        self.entities.end_lease(entity);
-        result
+        self.update(|cx| {
+            let mut entity = cx.entities.lease(handle);
+            let result = update(&mut entity, &mut ModelContext::mutable(cx, handle.id));
+            cx.entities.end_lease(entity);
+            result
+        })
     }
 }
 

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

@@ -1,13 +1,15 @@
-use crate::{AnyWindowHandle, AppContext, Context, Handle, ModelContext, Result, WindowContext};
+use crate::{
+    AnyWindowHandle, AppContext, Context, Handle, ModelContext, Result, ViewContext, WindowContext,
+};
 use anyhow::anyhow;
 use parking_lot::Mutex;
 use std::sync::Weak;
 
 #[derive(Clone)]
-pub struct AsyncContext(pub(crate) Weak<Mutex<AppContext>>);
+pub struct AsyncAppContext(pub(crate) Weak<Mutex<AppContext>>);
 
-impl Context for AsyncContext {
-    type EntityContext<'a, 'b, T: 'static + Send + Sync> = ModelContext<'a, T>;
+impl Context for AsyncAppContext {
+    type EntityContext<'a, 'w, T: 'static + Send + Sync> = ModelContext<'a, T>;
     type Result<T> = Result<T>;
 
     fn entity<T: Send + Sync + 'static>(
@@ -18,7 +20,7 @@ impl Context for AsyncContext {
             .0
             .upgrade()
             .ok_or_else(|| anyhow!("app was released"))?;
-        let mut lock = app.lock();
+        let mut lock = app.lock(); // Does not compile without this variable.
         Ok(lock.entity(build_entity))
     }
 
@@ -31,17 +33,17 @@ impl Context for AsyncContext {
             .0
             .upgrade()
             .ok_or_else(|| anyhow!("app was released"))?;
-        let mut lock = app.lock();
+        let mut lock = app.lock(); // Does not compile without this variable.
         Ok(lock.update_entity(handle, update))
     }
 }
 
-impl AsyncContext {
-    pub fn update_window<T>(
+impl AsyncAppContext {
+    pub fn update_window<R>(
         &self,
         handle: AnyWindowHandle,
-        update: impl FnOnce(&mut WindowContext) -> T + Send + Sync,
-    ) -> Result<T> {
+        update: impl FnOnce(&mut WindowContext) -> R,
+    ) -> Result<R> {
         let app = self
             .0
             .upgrade()
@@ -50,3 +52,37 @@ impl AsyncContext {
         app_context.update_window(handle.id, update)
     }
 }
+
+#[derive(Clone)]
+pub struct AsyncWindowContext {
+    app: AsyncAppContext,
+    window: AnyWindowHandle,
+}
+
+impl AsyncWindowContext {
+    pub fn new(app: AsyncAppContext, window: AnyWindowHandle) -> Self {
+        Self { app, window }
+    }
+}
+
+impl Context for AsyncWindowContext {
+    type EntityContext<'a, 'w, T: 'static + Send + Sync> = ViewContext<'a, 'w, T>;
+    type Result<T> = Result<T>;
+
+    fn entity<R: Send + Sync + 'static>(
+        &mut self,
+        build_entity: impl FnOnce(&mut Self::EntityContext<'_, '_, R>) -> R,
+    ) -> Result<Handle<R>> {
+        self.app
+            .update_window(self.window, |cx| cx.entity(build_entity))
+    }
+
+    fn update_entity<T: Send + Sync + 'static, R>(
+        &mut self,
+        handle: &Handle<T>,
+        update: impl FnOnce(&mut T, &mut Self::EntityContext<'_, '_, T>) -> R,
+    ) -> Result<R> {
+        self.app
+            .update_window(self.window, |cx| cx.update_entity(handle, update))
+    }
+}

crates/gpui3/src/app/entity_map.rs 🔗

@@ -14,14 +14,6 @@ use std::{
 
 slotmap::new_key_type! { pub struct EntityId; }
 
-#[derive(Deref, DerefMut)]
-pub struct Lease<T> {
-    #[deref]
-    #[deref_mut]
-    entity: Box<T>,
-    pub id: EntityId,
-}
-
 pub(crate) struct EntityMap {
     ref_counts: Arc<RwLock<RefCounts>>,
     entities: Arc<Mutex<SecondaryMap<EntityId, Box<dyn Any + Send + Sync>>>>,
@@ -35,31 +27,38 @@ impl EntityMap {
         }
     }
 
+    /// Reserve a slot for an entity, which you can subsequently use with `insert`.
     pub fn reserve<T: 'static + Send + Sync>(&self) -> Slot<T> {
         let id = self.ref_counts.write().insert(1.into());
         Slot(Handle::new(id, Arc::downgrade(&self.ref_counts)))
     }
 
-    pub fn redeem<T: 'static + Any + Send + Sync>(&self, slot: Slot<T>, entity: T) -> Handle<T> {
+    /// Insert an entity into a slot obtained by calling `reserve`.
+    pub fn insert<T: 'static + Any + Send + Sync>(&self, slot: Slot<T>, entity: T) -> Handle<T> {
         let handle = slot.0;
         self.entities.lock().insert(handle.id, Box::new(entity));
         handle
     }
 
+    /// Move an entity to the stack.
     pub fn lease<T: 'static + Send + Sync>(&self, handle: &Handle<T>) -> Lease<T> {
         let id = handle.id;
-        let entity = self
-            .entities
-            .lock()
-            .remove(id)
-            .expect("Circular entity lease. Is the entity already being updated?")
-            .downcast::<T>()
-            .unwrap();
+        let entity = Some(
+            self.entities
+                .lock()
+                .remove(id)
+                .expect("Circular entity lease. Is the entity already being updated?")
+                .downcast::<T>()
+                .unwrap(),
+        );
         Lease { id, entity }
     }
 
-    pub fn end_lease<T: 'static + Send + Sync>(&mut self, lease: Lease<T>) {
-        self.entities.lock().insert(lease.id, lease.entity);
+    /// Return an entity after moving it to the stack.
+    pub fn end_lease<T: 'static + Send + Sync>(&mut self, mut lease: Lease<T>) {
+        self.entities
+            .lock()
+            .insert(lease.id, lease.entity.take().unwrap());
     }
 
     pub fn weak_handle<T: 'static + Send + Sync>(&self, id: EntityId) -> WeakHandle<T> {
@@ -71,6 +70,34 @@ impl EntityMap {
     }
 }
 
+pub struct Lease<T> {
+    entity: Option<Box<T>>,
+    pub id: EntityId,
+}
+
+impl<T> core::ops::Deref for Lease<T> {
+    type Target = T;
+
+    fn deref(&self) -> &Self::Target {
+        self.entity.as_ref().unwrap()
+    }
+}
+
+impl<T> core::ops::DerefMut for Lease<T> {
+    fn deref_mut(&mut self) -> &mut Self::Target {
+        self.entity.as_mut().unwrap()
+    }
+}
+
+impl<T> Drop for Lease<T> {
+    fn drop(&mut self) {
+        assert!(
+            self.entity.is_none(),
+            "Leases must be ended with EntityMap::end_lease"
+        );
+    }
+}
+
 #[derive(Deref, DerefMut)]
 pub struct Slot<T: Send + Sync + 'static>(Handle<T>);
 

crates/gpui3/src/elements/img.rs 🔗

@@ -35,7 +35,7 @@ impl<S> Img<S> {
     }
 }
 
-impl<S: 'static> Element for Img<S> {
+impl<S: Send + Sync + 'static> Element for Img<S> {
     type State = S;
     type FrameState = ();
 
@@ -75,16 +75,18 @@ impl<S: 'static> Element for Img<S> {
                 let corner_radii = style.corner_radii.to_pixels(bounds, cx.rem_size());
                 cx.paint_image(bounds, corner_radii, order, data, self.grayscale)?;
             } else {
-                log::warn!("image not loaded yet");
-                cx.spawn(|cx| async move {
+                dbg!("not loaded");
+                cx.spawn(|view, mut cx| async move {
+                    dbg!("awaiting image future");
                     if image_future.await.log_err().is_some() {
-                        // this.update(&mut cx, |_, cx| cx.notify()).ok();
+                        view.update(&mut cx, |_, cx| {
+                            dbg!("image future loaded");
+                            cx.notify();
+                        })
+                        .ok();
                     }
                 })
                 .detach()
-                // cx.spawn(|this, mut cx| async move {
-                // })
-                // .detach();
             }
         }
         Ok(())

crates/gpui3/src/executor.rs 🔗

@@ -76,14 +76,13 @@ impl Executor {
     /// closure returns a future which will be run to completion on the main thread.
     pub fn spawn_on_main<F, R>(&self, func: impl FnOnce() -> F + Send + 'static) -> Task<R>
     where
-        F: Future<Output = R> + 'static,
+        F: Future<Output = R> + Send + 'static,
         R: Send + 'static,
     {
         let dispatcher = self.dispatcher.clone();
-        let (runnable, task) =
-            async_task::spawn_local(async move { func().await }, move |runnable| {
-                dispatcher.dispatch_on_main_thread(runnable)
-            });
+        let (runnable, task) = async_task::spawn(async move { func().await }, move |runnable| {
+            dispatcher.dispatch_on_main_thread(runnable)
+        });
         runnable.schedule();
         Task::Spawned(task)
     }

crates/gpui3/src/gpui3.rs 🔗

@@ -69,12 +69,6 @@ pub trait Context {
 #[repr(transparent)]
 pub struct MainThread<T>(T);
 
-impl<T> MainThread<T> {
-    fn new(value: T) -> Self {
-        Self(value)
-    }
-}
-
 impl<T> Deref for MainThread<T> {
     type Target = T;
 

crates/gpui3/src/window.rs 🔗

@@ -1,10 +1,10 @@
 use crate::{
-    image_cache::RenderImageParams, px, AnyView, AppContext, AvailableSpace, BorrowAppContext,
-    Bounds, Context, Corners, DevicePixels, Effect, Element, EntityId, FontId, GlyphId, Handle,
-    Hsla, ImageData, IsZero, LayerId, LayoutId, MainThread, MainThreadOnly, MonochromeSprite,
-    Pixels, PlatformAtlas, PlatformWindow, Point, PolychromeSprite, Reference, RenderGlyphParams,
-    RenderSvgParams, ScaledPixels, Scene, SharedString, Size, Style, TaffyLayoutEngine, Task,
-    WeakHandle, WindowOptions, SUBPIXEL_VARIANTS,
+    image_cache::RenderImageParams, px, AnyView, AppContext, AsyncWindowContext, AvailableSpace,
+    BorrowAppContext, Bounds, Context, Corners, DevicePixels, Effect, Element, EntityId, FontId,
+    GlyphId, Handle, Hsla, ImageData, IsZero, LayerId, LayoutId, MainThread, MainThreadOnly,
+    MonochromeSprite, Pixels, PlatformAtlas, PlatformWindow, Point, PolychromeSprite, Reference,
+    RenderGlyphParams, RenderSvgParams, ScaledPixels, Scene, SharedString, Size, Style,
+    TaffyLayoutEngine, Task, WeakHandle, WindowOptions, SUBPIXEL_VARIANTS,
 };
 use anyhow::Result;
 use smallvec::SmallVec;
@@ -109,6 +109,7 @@ impl<'a, 'w> WindowContext<'a, 'w> {
     }
 
     pub fn notify(&mut self) {
+        dbg!("ViewContext::notify");
         self.window.dirty = true;
     }
 
@@ -129,33 +130,23 @@ impl<'a, 'w> WindowContext<'a, 'w> {
         }
     }
 
-    pub fn spawn<Fut, R>(
-        &mut self,
-        f: impl FnOnce(&mut WindowContext<'_, '_>) -> Fut + Send + 'static,
-    ) -> Task<Result<R>>
-    where
-        R: Send + 'static,
-        Fut: Future<Output = R> + Send + 'static,
-    {
-        let id = self.window.handle.id;
-        self.app.spawn(move |cx| {
-            let future = cx.update_window(id, f);
-            async move { Ok(future?.await) }
-        })
+    pub fn to_async(&self) -> AsyncWindowContext {
+        AsyncWindowContext::new(self.app.to_async(), self.window.handle)
     }
 
-    pub fn try_spawn<Fut, R>(
+    pub fn spawn<Fut, R>(
         &mut self,
-        f: impl FnOnce(&mut WindowContext<'_, '_>) -> Fut + Send + 'static,
-    ) -> Task<Result<R>>
+        f: impl FnOnce(AnyWindowHandle, AsyncWindowContext) -> Fut + Send + 'static,
+    ) -> Task<R>
     where
         R: Send + 'static,
-        Fut: Future<Output = Result<R>> + Send + 'static,
+        Fut: Future<Output = R> + Send + 'static,
     {
-        let id = self.window.handle.id;
-        self.app.spawn(move |cx| {
-            let future = cx.update_window(id, f);
-            async move { future?.await }
+        let window = self.window.handle;
+        self.app.spawn(move |app| {
+            let cx = AsyncWindowContext::new(app, window);
+            let future = f(window, cx);
+            async move { future.await }
         })
     }
 
@@ -448,7 +439,7 @@ impl Context for WindowContext<'_, '_> {
             &mut self.window,
             slot.id,
         ));
-        self.entities.redeem(slot, entity)
+        self.entities.insert(slot, entity)
     }
 
     fn update_entity<T: Send + Sync + 'static, R>(
@@ -607,6 +598,7 @@ impl<'a, 'w, S: Send + Sync + 'static> ViewContext<'a, 'w, S> {
     }
 
     pub fn notify(&mut self) {
+        dbg!("ViewContext::notify");
         self.window_cx.notify();
         self.window_cx
             .app
@@ -633,16 +625,16 @@ impl<'a, 'w, S: Send + Sync + 'static> ViewContext<'a, 'w, S> {
 
     pub fn spawn<Fut, R>(
         &mut self,
-        f: impl FnOnce(&mut S, &mut ViewContext<'_, '_, S>) -> Fut + Send + 'static,
-    ) -> Task<Result<R>>
+        f: impl FnOnce(WeakHandle<S>, AsyncWindowContext) -> Fut + Send + 'static,
+    ) -> Task<R>
     where
         R: Send + 'static,
         Fut: Future<Output = R> + Send + 'static,
     {
         let handle = self.handle();
-        self.window_cx.try_spawn(move |cx| {
-            let result = handle.update(cx, f);
-            async move { Ok(result?.await) }
+        self.window_cx.spawn(move |_, cx| {
+            let result = f(handle, cx);
+            async move { result.await }
         })
     }