Checkpoint

Antonio Scandurra created

Change summary

crates/gpui3/src/app.rs               |  39 ++++------
crates/gpui3/src/app/model_context.rs |  31 ++++----
crates/gpui3/src/gpui3.rs             |   2 
crates/gpui3/src/subscription.rs      | 103 +++++++++++++++++++++++++++++
crates/gpui3/src/window.rs            |  30 +++----
5 files changed, 150 insertions(+), 55 deletions(-)

Detailed changes

crates/gpui3/src/app.rs 🔗

@@ -9,15 +9,15 @@ use refineable::Refineable;
 
 use crate::{
     current_platform, image_cache::ImageCache, AssetSource, Context, DisplayId, Executor, LayoutId,
-    MainThread, MainThreadOnly, Platform, PlatformDisplayLinker, RootView, SvgRenderer, Task,
-    TextStyle, TextStyleRefinement, TextSystem, Window, WindowContext, WindowHandle, WindowId,
+    MainThread, MainThreadOnly, Platform, PlatformDisplayLinker, RootView, SubscriberSet,
+    SvgRenderer, Task, TextStyle, TextStyleRefinement, TextSystem, Window, WindowContext,
+    WindowHandle, WindowId,
 };
 use anyhow::{anyhow, Result};
 use collections::{HashMap, VecDeque};
 use futures::Future;
 use parking_lot::Mutex;
 use slotmap::SlotMap;
-use smallvec::SmallVec;
 use std::{
     any::{type_name, Any, TypeId},
     mem,
@@ -67,8 +67,8 @@ impl App {
                 entities,
                 windows: SlotMap::with_key(),
                 pending_effects: Default::default(),
-                observers: Default::default(),
-                event_handlers: Default::default(),
+                observers: SubscriberSet::new(),
+                event_handlers: SubscriberSet::new(),
                 layout_id_buffer: Default::default(),
             })
         }))
@@ -88,9 +88,8 @@ impl App {
     }
 }
 
-type Handlers = SmallVec<[Arc<dyn Fn(&mut AppContext) -> bool + Send + Sync + 'static>; 2]>;
-type EventHandlers =
-    SmallVec<[Arc<dyn Fn(&dyn Any, &mut AppContext) -> bool + Send + Sync + 'static>; 2]>;
+type Handler = Arc<dyn Fn(&mut AppContext) -> bool + Send + Sync + 'static>;
+type EventHandler = Arc<dyn Fn(&dyn Any, &mut AppContext) -> bool + Send + Sync + 'static>;
 type FrameCallback = Box<dyn FnOnce(&mut WindowContext) + Send>;
 
 pub struct AppContext {
@@ -109,8 +108,8 @@ pub struct AppContext {
     pub(crate) entities: EntityMap,
     pub(crate) windows: SlotMap<WindowId, Option<Window>>,
     pub(crate) pending_effects: VecDeque<Effect>,
-    pub(crate) observers: HashMap<EntityId, Handlers>,
-    pub(crate) event_handlers: HashMap<EntityId, EventHandlers>,
+    pub(crate) observers: SubscriberSet<EntityId, Handler>,
+    pub(crate) event_handlers: SubscriberSet<EntityId, EventHandler>,
     pub(crate) layout_id_buffer: Vec<LayoutId>, // We recycle this memory across layout requests.
 }
 
@@ -176,23 +175,15 @@ impl AppContext {
     }
 
     fn apply_notify_effect(&mut self, updated_entity: EntityId) {
-        if let Some(mut handlers) = self.observers.remove(&updated_entity) {
-            handlers.retain(|handler| handler(self));
-            if let Some(new_handlers) = self.observers.remove(&updated_entity) {
-                handlers.extend(new_handlers);
-            }
-            self.observers.insert(updated_entity, handlers);
-        }
+        self.observers
+            .clone()
+            .retain(&updated_entity, |handler| handler(self));
     }
 
     fn apply_emit_effect(&mut self, updated_entity: EntityId, event: Box<dyn Any>) {
-        if let Some(mut handlers) = self.event_handlers.remove(&updated_entity) {
-            handlers.retain(|handler| handler(&event, self));
-            if let Some(new_handlers) = self.event_handlers.remove(&updated_entity) {
-                handlers.extend(new_handlers);
-            }
-            self.event_handlers.insert(updated_entity, handlers);
-        }
+        self.event_handlers
+            .clone()
+            .retain(&updated_entity, |handler| handler(&event, self));
     }
 
     pub fn to_async(&self) -> AsyncAppContext {

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

@@ -1,4 +1,7 @@
-use crate::{AppContext, Context, Effect, EntityId, EventEmitter, Handle, Reference, WeakHandle};
+use crate::{
+    AppContext, Context, Effect, EntityId, EventEmitter, Handle, Reference, Subscription,
+    WeakHandle,
+};
 use std::{marker::PhantomData, sync::Arc};
 
 pub struct ModelContext<'a, T> {
@@ -42,21 +45,20 @@ impl<'a, T: Send + Sync + 'static> ModelContext<'a, T> {
         &mut self,
         handle: &Handle<E>,
         on_notify: impl Fn(&mut T, Handle<E>, &mut ModelContext<'_, T>) + Send + Sync + 'static,
-    ) {
+    ) -> Subscription {
         let this = self.handle();
         let handle = handle.downgrade();
-        self.app
-            .observers
-            .entry(handle.id)
-            .or_default()
-            .push(Arc::new(move |cx| {
+        self.app.observers.insert(
+            handle.id,
+            Arc::new(move |cx| {
                 if let Some((this, handle)) = this.upgrade(cx).zip(handle.upgrade(cx)) {
                     this.update(cx, |this, cx| on_notify(this, handle, cx));
                     true
                 } else {
                     false
                 }
-            }));
+            }),
+        )
     }
 
     pub fn subscribe<E: EventEmitter + Send + Sync + 'static>(
@@ -66,14 +68,12 @@ impl<'a, T: Send + Sync + 'static> ModelContext<'a, T> {
             + Send
             + Sync
             + 'static,
-    ) {
+    ) -> Subscription {
         let this = self.handle();
         let handle = handle.downgrade();
-        self.app
-            .event_handlers
-            .entry(handle.id)
-            .or_default()
-            .push(Arc::new(move |event, cx| {
+        self.app.event_handlers.insert(
+            handle.id,
+            Arc::new(move |event, cx| {
                 let event = event.downcast_ref().expect("invalid event type");
                 if let Some((this, handle)) = this.upgrade(cx).zip(handle.upgrade(cx)) {
                     this.update(cx, |this, cx| on_event(this, handle, event, cx));
@@ -81,7 +81,8 @@ impl<'a, T: Send + Sync + 'static> ModelContext<'a, T> {
                 } else {
                     false
                 }
-            }));
+            }),
+        )
     }
 
     pub fn notify(&mut self) {

crates/gpui3/src/gpui3.rs 🔗

@@ -13,6 +13,7 @@ mod scene;
 mod style;
 mod style_helpers;
 mod styled;
+mod subscription;
 mod svg_renderer;
 mod taffy;
 mod text_system;
@@ -42,6 +43,7 @@ pub use smol::Timer;
 pub use style::*;
 pub use style_helpers::*;
 pub use styled::*;
+pub use subscription::*;
 pub use svg_renderer::*;
 pub use taffy::{AvailableSpace, LayoutId};
 pub use text_system::*;

crates/gpui3/src/subscription.rs 🔗

@@ -0,0 +1,103 @@
+use collections::{BTreeMap, BTreeSet};
+use parking_lot::Mutex;
+use std::{fmt::Debug, mem, sync::Arc};
+use util::post_inc;
+
+#[derive(Clone)]
+pub(crate) struct SubscriberSet<EmitterKey, Callback>(
+    Arc<Mutex<SubscriberSetState<EmitterKey, Callback>>>,
+);
+
+struct SubscriberSetState<EmitterKey, Callback> {
+    subscribers: BTreeMap<EmitterKey, BTreeMap<usize, Callback>>,
+    dropped_subscribers: BTreeSet<(EmitterKey, usize)>,
+    next_subscriber_id: usize,
+}
+
+impl<EmitterKey, Callback> SubscriberSet<EmitterKey, Callback>
+where
+    EmitterKey: 'static + Ord + Clone + Debug,
+    Callback: 'static,
+{
+    pub fn new() -> Self {
+        Self(Arc::new(Mutex::new(SubscriberSetState {
+            subscribers: Default::default(),
+            dropped_subscribers: Default::default(),
+            next_subscriber_id: 0,
+        })))
+    }
+
+    pub fn insert(&self, emitter: EmitterKey, callback: Callback) -> Subscription {
+        let mut lock = self.0.lock();
+        let subscriber_id = post_inc(&mut lock.next_subscriber_id);
+        lock.subscribers
+            .entry(emitter.clone())
+            .or_default()
+            .insert(subscriber_id, callback);
+        let this = self.0.clone();
+        Subscription {
+            unsubscribe: Some(Box::new(move || {
+                let mut lock = this.lock();
+                if let Some(subscribers) = lock.subscribers.get_mut(&emitter) {
+                    subscribers.remove(&subscriber_id);
+                    if subscribers.is_empty() {
+                        lock.subscribers.remove(&emitter);
+                        return;
+                    }
+                }
+
+                // We didn't manage to remove the subscription, which means it was dropped
+                // while invoking the callback. Mark it as dropped so that we can remove it
+                // later.
+                lock.dropped_subscribers.insert((emitter, subscriber_id));
+            })),
+        }
+    }
+
+    pub fn retain<F>(&self, emitter: &EmitterKey, mut f: F)
+    where
+        F: FnMut(&mut Callback) -> bool,
+    {
+        let entry = self.0.lock().subscribers.remove_entry(emitter);
+        if let Some((emitter, mut subscribers)) = entry {
+            subscribers.retain(|_, callback| f(callback));
+            let mut lock = self.0.lock();
+
+            // Add any new subscribers that were added while invoking the callback.
+            if let Some(new_subscribers) = lock.subscribers.remove(&emitter) {
+                subscribers.extend(new_subscribers);
+            }
+
+            // Remove any dropped subscriptions that were dropped while invoking the callback.
+            for (dropped_emitter, dropped_subscription_id) in
+                mem::take(&mut lock.dropped_subscribers)
+            {
+                debug_assert_eq!(emitter, dropped_emitter);
+                subscribers.remove(&dropped_subscription_id);
+            }
+
+            if !subscribers.is_empty() {
+                lock.subscribers.insert(emitter, subscribers);
+            }
+        }
+    }
+}
+
+#[must_use]
+pub struct Subscription {
+    unsubscribe: Option<Box<dyn FnOnce()>>,
+}
+
+impl Subscription {
+    pub fn detach(mut self) {
+        self.unsubscribe.take();
+    }
+}
+
+impl Drop for Subscription {
+    fn drop(&mut self) {
+        if let Some(unsubscribe) = self.unsubscribe.take() {
+            unsubscribe();
+        }
+    }
+}

crates/gpui3/src/window.rs 🔗

@@ -5,8 +5,8 @@ use crate::{
     LayoutId, MainThread, MainThreadOnly, MonochromeSprite, MouseMoveEvent, Path, Pixels,
     PlatformAtlas, PlatformWindow, Point, PolychromeSprite, Quad, Reference, RenderGlyphParams,
     RenderImageParams, RenderSvgParams, ScaledPixels, SceneBuilder, Shadow, SharedString, Size,
-    Style, TaffyLayoutEngine, Task, Underline, UnderlineStyle, WeakHandle, WindowOptions,
-    SUBPIXEL_VARIANTS,
+    Style, Subscription, TaffyLayoutEngine, Task, Underline, UnderlineStyle, WeakHandle,
+    WindowOptions, SUBPIXEL_VARIANTS,
 };
 use anyhow::Result;
 use collections::HashMap;
@@ -903,15 +903,13 @@ impl<'a, 'w, S: Send + Sync + 'static> ViewContext<'a, 'w, S> {
         &mut self,
         handle: &Handle<E>,
         on_notify: impl Fn(&mut S, Handle<E>, &mut ViewContext<'_, '_, S>) + Send + Sync + 'static,
-    ) {
+    ) -> Subscription {
         let this = self.handle();
         let handle = handle.downgrade();
         let window_handle = self.window.handle;
-        self.app
-            .observers
-            .entry(handle.id)
-            .or_default()
-            .push(Arc::new(move |cx| {
+        self.app.observers.insert(
+            handle.id,
+            Arc::new(move |cx| {
                 cx.update_window(window_handle.id, |cx| {
                     if let Some(handle) = handle.upgrade(cx) {
                         this.update(cx, |this, cx| on_notify(this, handle, cx))
@@ -921,7 +919,8 @@ impl<'a, 'w, S: Send + Sync + 'static> ViewContext<'a, 'w, S> {
                     }
                 })
                 .unwrap_or(false)
-            }));
+            }),
+        )
     }
 
     pub fn subscribe<E: EventEmitter + Send + Sync + 'static>(
@@ -931,15 +930,13 @@ impl<'a, 'w, S: Send + Sync + 'static> ViewContext<'a, 'w, S> {
             + Send
             + Sync
             + 'static,
-    ) {
+    ) -> Subscription {
         let this = self.handle();
         let handle = handle.downgrade();
         let window_handle = self.window.handle;
-        self.app
-            .event_handlers
-            .entry(handle.id)
-            .or_default()
-            .push(Arc::new(move |event, cx| {
+        self.app.event_handlers.insert(
+            handle.id,
+            Arc::new(move |event, cx| {
                 cx.update_window(window_handle.id, |cx| {
                     if let Some(handle) = handle.upgrade(cx) {
                         let event = event.downcast_ref().expect("invalid event type");
@@ -950,7 +947,8 @@ impl<'a, 'w, S: Send + Sync + 'static> ViewContext<'a, 'w, S> {
                     }
                 })
                 .unwrap_or(false)
-            }));
+            }),
+        )
     }
 
     pub fn notify(&mut self) {