diff --git a/crates/gpui/src/app.rs b/crates/gpui/src/app.rs index 43a2c49263..9b7ca35d5f 100644 --- a/crates/gpui/src/app.rs +++ b/crates/gpui/src/app.rs @@ -763,6 +763,7 @@ type GlobalActionCallback = dyn FnMut(&dyn AnyAction, &mut MutableAppContext); type SubscriptionCallback = Box bool>; type GlobalSubscriptionCallback = Box; type ObservationCallback = Box bool>; +type GlobalObservationCallback = Box bool>; type ReleaseObservationCallback = Box; pub struct MutableAppContext { @@ -782,12 +783,15 @@ pub struct MutableAppContext { global_subscriptions: Arc>>>>, observations: Arc>>>>, + global_observations: + Arc>>>>, release_observations: Arc>>>, presenters_and_platform_windows: HashMap>, Box)>, foreground: Rc, pending_effects: VecDeque, pending_notifications: HashSet, + pending_global_notifications: HashSet, pending_flushes: usize, flushing_effects: bool, next_cursor_style_handle_id: Arc, @@ -831,10 +835,12 @@ impl MutableAppContext { global_subscriptions: Default::default(), observations: Default::default(), release_observations: Default::default(), + global_observations: Default::default(), presenters_and_platform_windows: HashMap::new(), foreground, pending_effects: VecDeque::new(), pending_notifications: HashSet::new(), + pending_global_notifications: HashSet::new(), pending_flushes: 0, flushing_effects: false, next_cursor_style_handle_id: Default::default(), @@ -1197,6 +1203,27 @@ impl MutableAppContext { } } + pub fn observe_global(&mut self, observe: F) -> Subscription + where + G: Any, + F: 'static + FnMut(&mut MutableAppContext) -> bool, + { + let type_id = TypeId::of::(); + let id = post_inc(&mut self.next_subscription_id); + + self.global_observations + .lock() + .entry(type_id) + .or_default() + .insert(id, Some(Box::new(observe))); + + Subscription::GlobalObservation { + id, + type_id, + observations: Some(Arc::downgrade(&self.global_observations)), + } + } + pub fn observe_release(&mut self, handle: &H, mut callback: F) -> Subscription where E: Entity, @@ -1251,6 +1278,13 @@ impl MutableAppContext { } } + pub(crate) fn notify_global(&mut self, type_id: TypeId) { + if self.pending_global_notifications.insert(type_id) { + self.pending_effects + .push_back(Effect::GlobalNotification { type_id }); + } + } + pub fn dispatch_action( &mut self, window_id: usize, @@ -1380,16 +1414,22 @@ impl MutableAppContext { } pub fn default_global(&mut self) -> &T { + let type_id = TypeId::of::(); + if !self.cx.globals.contains_key(&type_id) { + self.notify_global(type_id); + } self.cx .globals - .entry(TypeId::of::()) + .entry(type_id) .or_insert_with(|| Box::new(T::default())) .downcast_ref() .unwrap() } pub fn set_global(&mut self, state: T) { - self.cx.globals.insert(TypeId::of::(), Box::new(state)); + let type_id = TypeId::of::(); + self.cx.globals.insert(type_id, Box::new(state)); + self.notify_global(type_id); } pub fn update_default_global(&mut self, update: F) -> U @@ -1405,6 +1445,7 @@ impl MutableAppContext { .unwrap_or_else(|| Box::new(T::default())); let result = update(state.downcast_mut().unwrap(), self); self.cx.globals.insert(type_id, state); + self.notify_global(type_id); result } @@ -1421,6 +1462,7 @@ impl MutableAppContext { .expect("no global has been added for this type"); let result = update(state.downcast_mut().unwrap(), self); self.cx.globals.insert(type_id, state); + self.notify_global(type_id); result } @@ -1686,6 +1728,9 @@ impl MutableAppContext { Effect::ViewNotification { window_id, view_id } => { self.notify_view_observers(window_id, view_id) } + Effect::GlobalNotification { type_id } => { + self.notify_global_observers(type_id) + } Effect::Deferred { callback, after_window_update, @@ -1734,6 +1779,7 @@ impl MutableAppContext { if self.pending_effects.is_empty() { self.flushing_effects = false; self.pending_notifications.clear(); + self.pending_global_notifications.clear(); break; } } @@ -2004,6 +2050,35 @@ impl MutableAppContext { } } + fn notify_global_observers(&mut self, observed_type_id: TypeId) { + let callbacks = self.global_observations.lock().remove(&observed_type_id); + if let Some(callbacks) = callbacks { + if self.cx.globals.contains_key(&observed_type_id) { + for (id, callback) in callbacks { + if let Some(mut callback) = callback { + let alive = callback(self); + if alive { + match self + .global_observations + .lock() + .entry(observed_type_id) + .or_default() + .entry(id) + { + collections::btree_map::Entry::Vacant(entry) => { + entry.insert(Some(callback)); + } + collections::btree_map::Entry::Occupied(entry) => { + entry.remove(); + } + } + } + } + } + } + } + } + fn notify_release_observers(&mut self, entity_id: usize, entity: &dyn Any) { let callbacks = self.release_observations.lock().remove(&entity_id); if let Some(callbacks) = callbacks { @@ -2377,6 +2452,9 @@ pub enum Effect { callback: Box, after_window_update: bool, }, + GlobalNotification { + type_id: TypeId, + }, ModelRelease { model_id: usize, model: Box, @@ -2442,6 +2520,10 @@ impl Debug for Effect { .field("window_id", window_id) .field("view_id", view_id) .finish(), + Effect::GlobalNotification { type_id } => f + .debug_struct("Effect::GlobalNotification") + .field("type_id", type_id) + .finish(), Effect::Deferred { .. } => f.debug_struct("Effect::Deferred").finish(), Effect::ModelRelease { model_id, .. } => f .debug_struct("Effect::ModelRelease") @@ -2621,6 +2703,15 @@ impl<'a, T: Entity> ModelContext<'a, T> { self.app.add_model(build_model) } + pub fn defer(&mut self, callback: impl 'static + FnOnce(&mut T, &mut ModelContext)) { + let handle = self.handle(); + self.app.defer(Box::new(move |cx| { + handle.update(cx, |model, cx| { + callback(model, cx); + }) + })) + } + pub fn emit(&mut self, payload: T::Event) { self.app.pending_effects.push_back(Effect::Event { entity_id: self.model_id, @@ -4178,6 +4269,13 @@ pub enum Subscription { observations: Option>>>>>, }, + GlobalObservation { + id: usize, + type_id: TypeId, + observations: Option< + Weak>>>>, + >, + }, ReleaseObservation { id: usize, entity_id: usize, @@ -4198,6 +4296,9 @@ impl Subscription { Subscription::Observation { observations, .. } => { observations.take(); } + Subscription::GlobalObservation { observations, .. } => { + observations.take(); + } Subscription::ReleaseObservation { observations, .. } => { observations.take(); } @@ -4266,6 +4367,22 @@ impl Drop for Subscription { } } } + Subscription::GlobalObservation { + id, + type_id, + observations, + } => { + if let Some(observations) = observations.as_ref().and_then(Weak::upgrade) { + match observations.lock().entry(*type_id).or_default().entry(*id) { + collections::btree_map::Entry::Vacant(entry) => { + entry.insert(None); + } + collections::btree_map::Entry::Occupied(entry) => { + entry.remove(); + } + } + } + } Subscription::ReleaseObservation { id, entity_id, @@ -5437,6 +5554,8 @@ mod tests { }); assert_eq!(*observation_count.borrow(), 1); + + // Global Observation } #[crate::test(self)]