First pass at allowing multiple event types to be emitted by an entity

Mikayla created

Change summary

crates/gpui2/src/app.rs               |  54 ++++++++++-----
crates/gpui2/src/app/model_context.rs |  19 +++--
crates/gpui2/src/app/test_context.rs  |  13 +-
crates/gpui2/src/gpui2.rs             |   6 -
crates/gpui2/src/subscription.rs      |   2 
crates/gpui2/src/window.rs            | 103 ++++++++++++++++++----------
6 files changed, 123 insertions(+), 74 deletions(-)

Detailed changes

crates/gpui2/src/app.rs 🔗

@@ -201,7 +201,8 @@ pub struct AppContext {
     pub(crate) pending_notifications: HashSet<EntityId>,
     pub(crate) pending_global_notifications: HashSet<TypeId>,
     pub(crate) observers: SubscriberSet<EntityId, Handler>,
-    pub(crate) event_listeners: SubscriberSet<EntityId, Listener>,
+    // (Entity, Event Type)
+    pub(crate) event_listeners: SubscriberSet<EntityId, (TypeId, Listener)>,
     pub(crate) release_listeners: SubscriberSet<EntityId, ReleaseListener>,
     pub(crate) global_observers: SubscriberSet<TypeId, Handler>,
     pub(crate) quit_observers: SubscriberSet<(), QuitHandler>,
@@ -351,14 +352,15 @@ impl AppContext {
         )
     }
 
-    pub fn subscribe<T, E>(
+    pub fn subscribe<T, E, Evt>(
         &mut self,
         entity: &E,
-        mut on_event: impl FnMut(E, &T::Event, &mut AppContext) + 'static,
+        mut on_event: impl FnMut(E, &Evt, &mut AppContext) + 'static,
     ) -> Subscription
     where
-        T: 'static + EventEmitter,
+        T: 'static + EventEmitter<Evt>,
         E: Entity<T>,
+        Evt: 'static,
     {
         self.subscribe_internal(entity, move |entity, event, cx| {
             on_event(entity, event, cx);
@@ -366,27 +368,32 @@ impl AppContext {
         })
     }
 
-    pub(crate) fn subscribe_internal<T, E>(
+    pub(crate) fn subscribe_internal<T, E, Evt>(
         &mut self,
         entity: &E,
-        mut on_event: impl FnMut(E, &T::Event, &mut AppContext) -> bool + 'static,
+        mut on_event: impl FnMut(E, &Evt, &mut AppContext) -> bool + 'static,
     ) -> Subscription
     where
-        T: 'static + EventEmitter,
+        T: 'static + EventEmitter<Evt>,
         E: Entity<T>,
+        Evt: 'static,
     {
         let entity_id = entity.entity_id();
         let entity = entity.downgrade();
+
         self.event_listeners.insert(
             entity_id,
-            Box::new(move |event, cx| {
-                let event: &T::Event = event.downcast_ref().expect("invalid event type");
-                if let Some(handle) = E::upgrade_from(&entity) {
-                    on_event(handle, event, cx)
-                } else {
-                    false
-                }
-            }),
+            (
+                TypeId::of::<Evt>(),
+                Box::new(move |event, cx| {
+                    let event: &Evt = event.downcast_ref().expect("invalid event type");
+                    if let Some(handle) = E::upgrade_from(&entity) {
+                        on_event(handle, event, cx)
+                    } else {
+                        false
+                    }
+                }),
+            ),
         )
     }
 
@@ -509,7 +516,11 @@ impl AppContext {
                     Effect::Notify { emitter } => {
                         self.apply_notify_effect(emitter);
                     }
-                    Effect::Emit { emitter, event } => self.apply_emit_effect(emitter, event),
+                    Effect::Emit {
+                        emitter,
+                        event_type,
+                        event,
+                    } => self.apply_emit_effect(emitter, event_type, event),
                     Effect::FocusChanged {
                         window_handle,
                         focused,
@@ -604,10 +615,16 @@ impl AppContext {
             .retain(&emitter, |handler| handler(self));
     }
 
-    fn apply_emit_effect(&mut self, emitter: EntityId, event: Box<dyn Any>) {
+    fn apply_emit_effect(&mut self, emitter: EntityId, event_type: TypeId, event: Box<dyn Any>) {
         self.event_listeners
             .clone()
-            .retain(&emitter, |handler| handler(event.as_ref(), self));
+            .retain(&emitter, |(stored_type, handler)| {
+                if *stored_type == event_type {
+                    handler(event.as_ref(), self)
+                } else {
+                    true
+                }
+            });
     }
 
     fn apply_focus_changed_effect(
@@ -978,6 +995,7 @@ pub(crate) enum Effect {
     },
     Emit {
         emitter: EntityId,
+        event_type: TypeId,
         event: Box<dyn Any>,
     },
     FocusChanged {

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

@@ -59,15 +59,16 @@ impl<'a, T: 'static> ModelContext<'a, T> {
         })
     }
 
-    pub fn subscribe<T2, E>(
+    pub fn subscribe<T2, E, Evt>(
         &mut self,
         entity: &E,
-        mut on_event: impl FnMut(&mut T, E, &T2::Event, &mut ModelContext<'_, T>) + 'static,
+        mut on_event: impl FnMut(&mut T, E, &Evt, &mut ModelContext<'_, T>) + 'static,
     ) -> Subscription
     where
         T: 'static,
-        T2: 'static + EventEmitter,
+        T2: 'static + EventEmitter<Evt>,
         E: Entity<T2>,
+        Evt: 'static,
     {
         let this = self.weak_model();
         self.app.subscribe_internal(entity, move |e, event, cx| {
@@ -189,13 +190,15 @@ impl<'a, T: 'static> ModelContext<'a, T> {
     }
 }
 
-impl<'a, T> ModelContext<'a, T>
-where
-    T: EventEmitter,
-{
-    pub fn emit(&mut self, event: T::Event) {
+impl<'a, T> ModelContext<'a, T> {
+    pub fn emit<Evt>(&mut self, event: Evt)
+    where
+        T: EventEmitter<Evt>,
+        Evt: 'static,
+    {
         self.app.pending_effects.push_back(Effect::Emit {
             emitter: self.model_state.entity_id,
+            event_type: TypeId::of::<Evt>(),
             event: Box::new(event),
         });
     }

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

@@ -197,12 +197,12 @@ impl TestAppContext {
         rx
     }
 
-    pub fn events<T: 'static + EventEmitter>(
+    pub fn events<Evt, T: 'static + EventEmitter<Evt>>(
         &mut self,
         entity: &Model<T>,
-    ) -> futures::channel::mpsc::UnboundedReceiver<T::Event>
+    ) -> futures::channel::mpsc::UnboundedReceiver<Evt>
     where
-        T::Event: 'static + Clone,
+        Evt: 'static + Clone,
     {
         let (tx, rx) = futures::channel::mpsc::unbounded();
         entity
@@ -240,10 +240,11 @@ impl TestAppContext {
     }
 }
 
-impl<T: Send + EventEmitter> Model<T> {
-    pub fn next_event(&self, cx: &mut TestAppContext) -> T::Event
+impl<T: Send> Model<T> {
+    pub fn next_event<Evt>(&self, cx: &mut TestAppContext) -> Evt
     where
-        T::Event: Send + Clone,
+        Evt: Send + Clone + 'static,
+        T: EventEmitter<Evt>,
     {
         let (tx, mut rx) = futures::channel::mpsc::unbounded();
         let _subscription = self.update(cx, |_, cx| {

crates/gpui2/src/gpui2.rs 🔗

@@ -138,6 +138,8 @@ pub trait Entity<T>: Sealed {
         Self: Sized;
 }
 
+pub trait EventEmitter<E: Any>: 'static {}
+
 pub enum GlobalKey {
     Numeric(usize),
     View(EntityId),
@@ -171,10 +173,6 @@ where
     }
 }
 
-pub trait EventEmitter: 'static {
-    type Event: Any;
-}
-
 pub trait Flatten<T> {
     fn flatten(self) -> Result<T>;
 }

crates/gpui2/src/subscription.rs 🔗

@@ -75,6 +75,8 @@ where
             .flatten()
     }
 
+    /// Call the given callback for each subscriber to the given emitter.
+    /// If the callback returns false, the subscriber is removed.
     pub fn retain<F>(&self, emitter: &EmitterKey, mut f: F)
     where
         F: FnMut(&mut Callback) -> bool,

crates/gpui2/src/window.rs 🔗

@@ -439,33 +439,37 @@ impl<'a> WindowContext<'a> {
         });
     }
 
-    pub fn subscribe<Emitter, E>(
+    pub fn subscribe<Emitter, E, Evt>(
         &mut self,
         entity: &E,
-        mut on_event: impl FnMut(E, &Emitter::Event, &mut WindowContext<'_>) + 'static,
+        mut on_event: impl FnMut(E, &Evt, &mut WindowContext<'_>) + 'static,
     ) -> Subscription
     where
-        Emitter: EventEmitter,
+        Emitter: EventEmitter<Evt>,
         E: Entity<Emitter>,
+        Evt: 'static,
     {
         let entity_id = entity.entity_id();
         let entity = entity.downgrade();
         let window_handle = self.window.handle;
         self.app.event_listeners.insert(
             entity_id,
-            Box::new(move |event, cx| {
-                window_handle
-                    .update(cx, |_, cx| {
-                        if let Some(handle) = E::upgrade_from(&entity) {
-                            let event = event.downcast_ref().expect("invalid event type");
-                            on_event(handle, event, cx);
-                            true
-                        } else {
-                            false
-                        }
-                    })
-                    .unwrap_or(false)
-            }),
+            (
+                TypeId::of::<Evt>(),
+                Box::new(move |event, cx| {
+                    window_handle
+                        .update(cx, |_, cx| {
+                            if let Some(handle) = E::upgrade_from(&entity) {
+                                let event = event.downcast_ref().expect("invalid event type");
+                                on_event(handle, event, cx);
+                                true
+                            } else {
+                                false
+                            }
+                        })
+                        .unwrap_or(false)
+                }),
+            ),
         )
     }
 
@@ -1809,14 +1813,33 @@ impl<'a, V: 'static> ViewContext<'a, V> {
         )
     }
 
-    pub fn subscribe<V2, E>(
+    // Options for simplifying this new event API:
+    //
+    // - Make a new stlye of API which does partial application of the arguments to capture
+    //   the types involved e.g.
+    //      `cx.for_entity(handle).subscribe::<ItemEvents>(..)`
+    //
+    // - Make it so there are less types:
+    //   - Bail on this idea all together, go back to associated types.
+    //      causes our event enums to be a blob of anything that could happen ever, and
+    //      makes applications have some translation boilerplate
+    //
+    //   - Move some of the types into the method names,
+    //      `cx.subscribe_model::<_, ItemEvents>(handle)`
+    //
+    //   - Do something drastic like removing views and models, or removing the multiple
+    //      kind of contexts. (Not going to happen, we already tried this before.)
+    //
+    // - Accept it, and use `cx.subscribe::<_, _, ItemEvents>(handle, ...)`
+    pub fn subscribe<V2, E, Evt>(
         &mut self,
         entity: &E,
-        mut on_event: impl FnMut(&mut V, E, &V2::Event, &mut ViewContext<'_, V>) + 'static,
+        mut on_event: impl FnMut(&mut V, E, &Evt, &mut ViewContext<'_, V>) + 'static,
     ) -> Subscription
     where
-        V2: EventEmitter,
+        V2: EventEmitter<Evt>,
         E: Entity<V2>,
+        Evt: 'static,
     {
         let view = self.view().downgrade();
         let entity_id = entity.entity_id();
@@ -1824,19 +1847,22 @@ impl<'a, V: 'static> ViewContext<'a, V> {
         let window_handle = self.window.handle;
         self.app.event_listeners.insert(
             entity_id,
-            Box::new(move |event, cx| {
-                window_handle
-                    .update(cx, |_, cx| {
-                        if let Some(handle) = E::upgrade_from(&handle) {
-                            let event = event.downcast_ref().expect("invalid event type");
-                            view.update(cx, |this, cx| on_event(this, handle, event, cx))
-                                .is_ok()
-                        } else {
-                            false
-                        }
-                    })
-                    .unwrap_or(false)
-            }),
+            (
+                TypeId::of::<Evt>(),
+                Box::new(move |event, cx| {
+                    window_handle
+                        .update(cx, |_, cx| {
+                            if let Some(handle) = E::upgrade_from(&handle) {
+                                let event = event.downcast_ref().expect("invalid event type");
+                                view.update(cx, |this, cx| on_event(this, handle, event, cx))
+                                    .is_ok()
+                            } else {
+                                false
+                            }
+                        })
+                        .unwrap_or(false)
+                }),
+            ),
         )
     }
 
@@ -2181,15 +2207,16 @@ where
     }
 }
 
-impl<V> ViewContext<'_, V>
-where
-    V: EventEmitter,
-    V::Event: 'static,
-{
-    pub fn emit(&mut self, event: V::Event) {
+impl<V> ViewContext<'_, V> {
+    pub fn emit<Evt>(&mut self, event: Evt)
+    where
+        Evt: 'static,
+        V: EventEmitter<Evt>,
+    {
         let emitter = self.view.model.entity_id;
         self.app.push_effect(Effect::Emit {
             emitter,
+            event_type: TypeId::of::<Evt>(),
             event: Box::new(event),
         });
     }