From 454f7a570c00c88ec7ea58bd405cab44c12e3d35 Mon Sep 17 00:00:00 2001 From: Keith Simmons Date: Tue, 22 Mar 2022 16:38:17 -0700 Subject: [PATCH 1/2] Add global change observations --- crates/gpui/src/app.rs | 123 ++++++++++++++++++++++++++++++++++++++++- 1 file changed, 121 insertions(+), 2 deletions(-) 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)] From 0a8d543f6654c747511319a29fb4860381af10bb Mon Sep 17 00:00:00 2001 From: Keith Simmons Date: Wed, 23 Mar 2022 10:27:27 -0700 Subject: [PATCH 2/2] Add global tests and wrap global update functions in update call to flush effects Co-authored-by: Antonio Scandurra --- crates/gpui/src/app.rs | 173 +++++++++++++++++++++++++++++------------ 1 file changed, 124 insertions(+), 49 deletions(-) diff --git a/crates/gpui/src/app.rs b/crates/gpui/src/app.rs index 9b7ca35d5f..db21612439 100644 --- a/crates/gpui/src/app.rs +++ b/crates/gpui/src/app.rs @@ -763,7 +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 GlobalObservationCallback = Box; type ReleaseObservationCallback = Box; pub struct MutableAppContext { @@ -1206,7 +1206,7 @@ impl MutableAppContext { pub fn observe_global(&mut self, observe: F) -> Subscription where G: Any, - F: 'static + FnMut(&mut MutableAppContext) -> bool, + F: 'static + FnMut(&mut MutableAppContext), { let type_id = TypeId::of::(); let id = post_inc(&mut self.next_subscription_id); @@ -1415,21 +1415,25 @@ 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(type_id) - .or_insert_with(|| Box::new(T::default())) - .downcast_ref() - .unwrap() + self.update(|this| { + if !this.globals.contains_key(&type_id) { + this.notify_global(type_id); + } + + this.cx + .globals + .entry(type_id) + .or_insert_with(|| Box::new(T::default())); + }); + self.globals.get(&type_id).unwrap().downcast_ref().unwrap() } pub fn set_global(&mut self, state: T) { - let type_id = TypeId::of::(); - self.cx.globals.insert(type_id, Box::new(state)); - self.notify_global(type_id); + self.update(|this| { + let type_id = TypeId::of::(); + this.cx.globals.insert(type_id, Box::new(state)); + this.notify_global(type_id); + }); } pub fn update_default_global(&mut self, update: F) -> U @@ -1437,16 +1441,18 @@ impl MutableAppContext { T: 'static + Default, F: FnOnce(&mut T, &mut MutableAppContext) -> U, { - let type_id = TypeId::of::(); - let mut state = self - .cx - .globals - .remove(&type_id) - .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 + self.update(|this| { + let type_id = TypeId::of::(); + let mut state = this + .cx + .globals + .remove(&type_id) + .unwrap_or_else(|| Box::new(T::default())); + let result = update(state.downcast_mut().unwrap(), this); + this.cx.globals.insert(type_id, state); + this.notify_global(type_id); + result + }) } pub fn update_global(&mut self, update: F) -> U @@ -1454,16 +1460,18 @@ impl MutableAppContext { T: 'static, F: FnOnce(&mut T, &mut MutableAppContext) -> U, { - let type_id = TypeId::of::(); - let mut state = self - .cx - .globals - .remove(&type_id) - .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 + self.update(|this| { + let type_id = TypeId::of::(); + let mut state = this + .cx + .globals + .remove(&type_id) + .expect("no global has been added for this type"); + let result = update(state.downcast_mut().unwrap(), this); + this.cx.globals.insert(type_id, state); + this.notify_global(type_id); + result + }) } pub fn add_model(&mut self, build_model: F) -> ModelHandle @@ -2056,21 +2064,19 @@ impl MutableAppContext { 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(); - } + callback(self); + 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(); } } } @@ -5205,6 +5211,61 @@ mod tests { ); } + #[crate::test(self)] + fn test_global(cx: &mut MutableAppContext) { + type Global = usize; + + let observation_count = Rc::new(RefCell::new(0)); + let subscription = cx.observe_global::({ + let observation_count = observation_count.clone(); + move |_| { + *observation_count.borrow_mut() += 1; + } + }); + + assert!(!cx.has_global::()); + assert_eq!(cx.default_global::(), &0); + assert_eq!(*observation_count.borrow(), 1); + assert!(cx.has_global::()); + assert_eq!( + cx.update_global::(|global, _| { + *global = 1; + "Update Result" + }), + "Update Result" + ); + assert_eq!(*observation_count.borrow(), 2); + assert_eq!(cx.global::(), &1); + + drop(subscription); + cx.update_global::(|global, _| { + *global = 2; + }); + assert_eq!(*observation_count.borrow(), 2); + + type OtherGlobal = f32; + + let observation_count = Rc::new(RefCell::new(0)); + cx.observe_global::({ + let observation_count = observation_count.clone(); + move |_| { + *observation_count.borrow_mut() += 1; + } + }) + .detach(); + + assert_eq!( + cx.update_default_global::(|global, _| { + assert_eq!(global, &0.0); + *global = 2.0; + "Default update result" + }), + "Default update result" + ); + assert_eq!(cx.global::(), &2.0); + assert_eq!(*observation_count.borrow(), 1); + } + #[crate::test(self)] fn test_dropping_subscribers(cx: &mut MutableAppContext) { struct View; @@ -5556,6 +5617,20 @@ mod tests { assert_eq!(*observation_count.borrow(), 1); // Global Observation + let observation_count = Rc::new(RefCell::new(0)); + let subscription = Rc::new(RefCell::new(None)); + *subscription.borrow_mut() = Some(cx.observe_global::<(), _>({ + let observation_count = observation_count.clone(); + let subscription = subscription.clone(); + move |_| { + subscription.borrow_mut().take(); + *observation_count.borrow_mut() += 1; + } + })); + + cx.default_global::<()>(); + cx.set_global(()); + assert_eq!(*observation_count.borrow(), 1); } #[crate::test(self)]