Outline a bunch of methods in gpui that leaked SubscriberSet types (#7430)

Piotr Osiewicz created

This takes down LLVM IR size of theme_selector from 316k to ~250k. Note
that I do not care about theme_selector in particular, though it acts as
a benchmark for smaller crates to me ("how much static overhead in
compile time does gpui have").

The title is a bit dramatic, so just to shed some light: by leaking a
type I mean forcing downstream crates to codegen it's methods/know about
it's drop code. Since SubscriberSet is no longer used directly in the
generic (==inlineable) methods, users no longer have to codegen `insert`
and co.

Release Notes:

- N/A

Change summary

crates/gpui/src/app.rs    | 53 ++++++++++++++++++++++--------
crates/gpui/src/window.rs | 70 ++++++++++++++++++----------------------
2 files changed, 70 insertions(+), 53 deletions(-)

Detailed changes

crates/gpui/src/app.rs 🔗

@@ -380,6 +380,11 @@ impl AppContext {
         })
     }
 
+    pub(crate) fn new_observer(&mut self, key: EntityId, value: Handler) -> Subscription {
+        let (subscription, activate) = self.observers.insert(key, value);
+        self.defer(move |_| activate());
+        subscription
+    }
     pub(crate) fn observe_internal<W, E>(
         &mut self,
         entity: &E,
@@ -391,7 +396,7 @@ impl AppContext {
     {
         let entity_id = entity.entity_id();
         let handle = entity.downgrade();
-        let (subscription, activate) = self.observers.insert(
+        self.new_observer(
             entity_id,
             Box::new(move |cx| {
                 if let Some(handle) = E::upgrade_from(&handle) {
@@ -400,9 +405,7 @@ impl AppContext {
                     false
                 }
             }),
-        );
-        self.defer(move |_| activate());
-        subscription
+        )
     }
 
     /// Arrange for the given callback to be invoked whenever the given model or view emits an event of a given type.
@@ -423,6 +426,15 @@ impl AppContext {
         })
     }
 
+    pub(crate) fn new_subscription(
+        &mut self,
+        key: EntityId,
+        value: (TypeId, Listener),
+    ) -> Subscription {
+        let (subscription, activate) = self.event_listeners.insert(key, value);
+        self.defer(move |_| activate());
+        subscription
+    }
     pub(crate) fn subscribe_internal<T, E, Evt>(
         &mut self,
         entity: &E,
@@ -435,7 +447,7 @@ impl AppContext {
     {
         let entity_id = entity.entity_id();
         let entity = entity.downgrade();
-        let (subscription, activate) = self.event_listeners.insert(
+        self.new_subscription(
             entity_id,
             (
                 TypeId::of::<Evt>(),
@@ -448,9 +460,7 @@ impl AppContext {
                     }
                 }),
             ),
-        );
-        self.defer(move |_| activate());
-        subscription
+        )
     }
 
     /// Returns handles to all open windows in the application.
@@ -930,13 +940,22 @@ impl AppContext {
         self.globals_by_type.insert(global_type, lease.global);
     }
 
+    pub(crate) fn new_view_observer(
+        &mut self,
+        key: TypeId,
+        value: NewViewListener,
+    ) -> Subscription {
+        let (subscription, activate) = self.new_view_observers.insert(key, value);
+        activate();
+        subscription
+    }
     /// Arrange for the given function to be invoked whenever a view of the specified type is created.
     /// The function will be passed a mutable reference to the view along with an appropriate context.
     pub fn observe_new_views<V: 'static>(
         &mut self,
         on_new: impl 'static + Fn(&mut V, &mut ViewContext<V>),
     ) -> Subscription {
-        let (subscription, activate) = self.new_view_observers.insert(
+        self.new_view_observer(
             TypeId::of::<V>(),
             Box::new(move |any_view: AnyView, cx: &mut WindowContext| {
                 any_view
@@ -946,9 +965,7 @@ impl AppContext {
                         on_new(view_state, cx);
                     })
             }),
-        );
-        activate();
-        subscription
+        )
     }
 
     /// Observe the release of a model or view. The callback is invoked after the model or view
@@ -980,9 +997,15 @@ impl AppContext {
         &mut self,
         f: impl FnMut(&KeystrokeEvent, &mut WindowContext) + 'static,
     ) -> Subscription {
-        let (subscription, activate) = self.keystroke_observers.insert((), Box::new(f));
-        activate();
-        subscription
+        fn inner(
+            keystroke_observers: &mut SubscriberSet<(), KeystrokeObserver>,
+            handler: KeystrokeObserver,
+        ) -> Subscription {
+            let (subscription, activate) = keystroke_observers.insert((), handler);
+            activate();
+            subscription
+        }
+        inner(&mut self.keystroke_observers, Box::new(f))
     }
 
     pub(crate) fn push_text_style(&mut self, text_style: TextStyleRefinement) {

crates/gpui/src/window.rs 🔗

@@ -451,6 +451,12 @@ impl Window {
             pending_input: None,
         }
     }
+    fn new_focus_listener(
+        &mut self,
+        value: AnyWindowFocusListener,
+    ) -> (Subscription, impl FnOnce()) {
+        self.focus_listeners.insert((), value)
+    }
 }
 
 /// Indicates which region of the window is visible. Content falling outside of this mask will not be
@@ -635,7 +641,7 @@ impl<'a> WindowContext<'a> {
         let entity_id = entity.entity_id();
         let entity = entity.downgrade();
         let window_handle = self.window.handle;
-        let (subscription, activate) = self.app.event_listeners.insert(
+        self.app.new_subscription(
             entity_id,
             (
                 TypeId::of::<Evt>(),
@@ -653,9 +659,7 @@ impl<'a> WindowContext<'a> {
                         .unwrap_or(false)
                 }),
             ),
-        );
-        self.app.defer(move |_| activate());
-        subscription
+        )
     }
 
     /// Creates an [`AsyncWindowContext`], which has a static lifetime and can be held across
@@ -1747,13 +1751,15 @@ impl VisualContext for WindowContext<'_> {
         let entity = build_view_state(&mut cx);
         cx.entities.insert(slot, entity);
 
-        cx.new_view_observers
-            .clone()
-            .retain(&TypeId::of::<V>(), |observer| {
-                let any_view = AnyView::from(view.clone());
-                (observer)(any_view, self);
+        // Non-generic part to avoid leaking SubscriberSet to invokers of `new_view`.
+        fn notify_observers(cx: &mut WindowContext, tid: TypeId, view: AnyView) {
+            cx.new_view_observers.clone().retain(&tid, |observer| {
+                let any_view = view.clone();
+                (observer)(any_view, cx);
                 true
             });
+        }
+        notify_observers(self, TypeId::of::<V>(), AnyView::from(view.clone()));
 
         view
     }
@@ -1955,7 +1961,7 @@ impl<'a, V: 'static> ViewContext<'a, V> {
         let entity_id = entity.entity_id();
         let entity = entity.downgrade();
         let window_handle = self.window.handle;
-        let (subscription, activate) = self.app.observers.insert(
+        self.app.new_observer(
             entity_id,
             Box::new(move |cx| {
                 window_handle
@@ -1969,9 +1975,7 @@ impl<'a, V: 'static> ViewContext<'a, V> {
                     })
                     .unwrap_or(false)
             }),
-        );
-        self.app.defer(move |_| activate());
-        subscription
+        )
     }
 
     /// Subscribe to events emitted by another model or view.
@@ -1991,7 +1995,7 @@ impl<'a, V: 'static> ViewContext<'a, V> {
         let entity_id = entity.entity_id();
         let handle = entity.downgrade();
         let window_handle = self.window.handle;
-        let (subscription, activate) = self.app.event_listeners.insert(
+        self.app.new_subscription(
             entity_id,
             (
                 TypeId::of::<Evt>(),
@@ -2009,9 +2013,7 @@ impl<'a, V: 'static> ViewContext<'a, V> {
                         .unwrap_or(false)
                 }),
             ),
-        );
-        self.app.defer(move |_| activate());
-        subscription
+        )
     }
 
     /// Register a callback to be invoked when the view is released.
@@ -2136,9 +2138,8 @@ impl<'a, V: 'static> ViewContext<'a, V> {
     ) -> Subscription {
         let view = self.view.downgrade();
         let focus_id = handle.id;
-        let (subscription, activate) = self.window.focus_listeners.insert(
-            (),
-            Box::new(move |event, cx| {
+        let (subscription, activate) =
+            self.window.new_focus_listener(Box::new(move |event, cx| {
                 view.update(cx, |view, cx| {
                     if event.previous_focus_path.last() != Some(&focus_id)
                         && event.current_focus_path.last() == Some(&focus_id)
@@ -2147,9 +2148,8 @@ impl<'a, V: 'static> ViewContext<'a, V> {
                     }
                 })
                 .is_ok()
-            }),
-        );
-        self.app.defer(move |_| activate());
+            }));
+        self.app.defer(|_| activate());
         subscription
     }
 
@@ -2162,9 +2162,8 @@ impl<'a, V: 'static> ViewContext<'a, V> {
     ) -> Subscription {
         let view = self.view.downgrade();
         let focus_id = handle.id;
-        let (subscription, activate) = self.window.focus_listeners.insert(
-            (),
-            Box::new(move |event, cx| {
+        let (subscription, activate) =
+            self.window.new_focus_listener(Box::new(move |event, cx| {
                 view.update(cx, |view, cx| {
                     if !event.previous_focus_path.contains(&focus_id)
                         && event.current_focus_path.contains(&focus_id)
@@ -2173,8 +2172,7 @@ impl<'a, V: 'static> ViewContext<'a, V> {
                     }
                 })
                 .is_ok()
-            }),
-        );
+            }));
         self.app.defer(move |_| activate());
         subscription
     }
@@ -2188,9 +2186,8 @@ impl<'a, V: 'static> ViewContext<'a, V> {
     ) -> Subscription {
         let view = self.view.downgrade();
         let focus_id = handle.id;
-        let (subscription, activate) = self.window.focus_listeners.insert(
-            (),
-            Box::new(move |event, cx| {
+        let (subscription, activate) =
+            self.window.new_focus_listener(Box::new(move |event, cx| {
                 view.update(cx, |view, cx| {
                     if event.previous_focus_path.last() == Some(&focus_id)
                         && event.current_focus_path.last() != Some(&focus_id)
@@ -2199,8 +2196,7 @@ impl<'a, V: 'static> ViewContext<'a, V> {
                     }
                 })
                 .is_ok()
-            }),
-        );
+            }));
         self.app.defer(move |_| activate());
         subscription
     }
@@ -2231,9 +2227,8 @@ impl<'a, V: 'static> ViewContext<'a, V> {
     ) -> Subscription {
         let view = self.view.downgrade();
         let focus_id = handle.id;
-        let (subscription, activate) = self.window.focus_listeners.insert(
-            (),
-            Box::new(move |event, cx| {
+        let (subscription, activate) =
+            self.window.new_focus_listener(Box::new(move |event, cx| {
                 view.update(cx, |view, cx| {
                     if event.previous_focus_path.contains(&focus_id)
                         && !event.current_focus_path.contains(&focus_id)
@@ -2242,8 +2237,7 @@ impl<'a, V: 'static> ViewContext<'a, V> {
                     }
                 })
                 .is_ok()
-            }),
-        );
+            }));
         self.app.defer(move |_| activate());
         subscription
     }