Skip to main content

gpui/
subscription.rs

1use collections::BTreeMap;
2use gpui_util::post_inc;
3use std::{
4    cell::{Cell, RefCell},
5    fmt::Debug,
6    mem,
7    rc::Rc,
8};
9
10/// Stores subscriptions for a single emitter.
11pub(crate) struct SingleSubscriberSet<Callback>(Rc<RefCell<SingleSubscriberSetState<Callback>>>);
12
13/// Stores subscriptions for emitters.
14pub(crate) struct SubscriberSet<EmitterKey, Callback>(
15    Rc<RefCell<SubscriberSetState<EmitterKey, Callback>>>,
16);
17
18impl<EmitterKey, Callback> Clone for SubscriberSet<EmitterKey, Callback> {
19    fn clone(&self) -> Self {
20        SubscriberSet(self.0.clone())
21    }
22}
23
24struct SubscriberSetState<EmitterKey, Callback> {
25    subscribers: BTreeMap<EmitterKey, BTreeMap<usize, Subscriber<Callback>>>,
26    next_subscriber_id: usize,
27}
28
29#[derive(Clone, Copy, PartialEq, Eq)]
30enum SubscriberState {
31    Pending,
32    Active,
33    Dropped,
34}
35
36struct Subscriber<Callback> {
37    state: Rc<Cell<SubscriberState>>,
38    callback: Callback,
39}
40
41impl<EmitterKey, Callback> SubscriberSet<EmitterKey, Callback>
42where
43    EmitterKey: 'static + Ord + Clone + Debug,
44    Callback: 'static,
45{
46    pub fn new() -> Self {
47        Self(Rc::new(RefCell::new(SubscriberSetState {
48            subscribers: Default::default(),
49            next_subscriber_id: 0,
50        })))
51    }
52
53    /// Inserts an immediately active subscription for the given emitter.
54    pub fn insert(&self, emitter_key: EmitterKey, callback: Callback) -> Subscription {
55        let (subscription, activate) = self.insert_pending(emitter_key, callback);
56        activate();
57        subscription
58    }
59
60    /// Inserts a pending subscription and returns its handle and activation function.
61    ///
62    /// Pending callbacks are skipped by `retain` and excluded from `remove` until activated.
63    pub fn insert_pending(
64        &self,
65        emitter_key: EmitterKey,
66        callback: Callback,
67    ) -> (Subscription, impl FnOnce() + use<EmitterKey, Callback>) {
68        let state = Rc::new(Cell::new(SubscriberState::Pending));
69        let mut lock = self.0.borrow_mut();
70        let subscriber_id = post_inc(&mut lock.next_subscriber_id);
71        lock.subscribers
72            .entry(emitter_key.clone())
73            .or_default()
74            .insert(
75                subscriber_id,
76                Subscriber {
77                    state: state.clone(),
78                    callback,
79                },
80            );
81        let this = self.0.clone();
82
83        let subscription = Subscription {
84            unsubscribe: Some(Box::new({
85                let state = state.clone();
86                move || {
87                    state.set(SubscriberState::Dropped);
88
89                    let mut lock = this.borrow_mut();
90                    let Some(subscribers) = lock.subscribers.get_mut(&emitter_key) else {
91                        return;
92                    };
93
94                    subscribers.remove(&subscriber_id);
95                    if subscribers.is_empty() {
96                        lock.subscribers.remove(&emitter_key);
97                    }
98                }
99            })),
100        };
101        (subscription, move || {
102            if state.get() == SubscriberState::Pending {
103                state.set(SubscriberState::Active);
104            }
105        })
106    }
107
108    pub fn remove(
109        &self,
110        emitter: &EmitterKey,
111    ) -> impl IntoIterator<Item = Callback> + use<EmitterKey, Callback> {
112        let subscribers = self.0.borrow_mut().subscribers.remove(emitter);
113        subscribers
114            .map(|s| s.into_values())
115            .into_iter()
116            .flatten()
117            .filter_map(|subscriber| {
118                if subscriber.state.get() == SubscriberState::Active {
119                    Some(subscriber.callback)
120                } else {
121                    None
122                }
123            })
124    }
125
126    /// Call the given callback for each subscriber to the given emitter.
127    /// If the callback returns false, the subscriber is removed.
128    pub fn retain<F>(&self, emitter: &EmitterKey, mut f: F)
129    where
130        F: FnMut(&mut Callback) -> bool,
131    {
132        // We do not remove here upfront to reduce BTreeMap shuffling churn
133        let Some(mut subscribers) = self
134            .0
135            .borrow_mut()
136            .subscribers
137            .get_mut(emitter)
138            .map(mem::take)
139        else {
140            return;
141        };
142
143        subscribers.retain(|_, subscriber| subscriber.retain(&mut f));
144        let mut lock = self.0.borrow_mut();
145
146        if subscribers.is_empty() {
147            if lock
148                .subscribers
149                .get(emitter)
150                .is_some_and(|subscribers| subscribers.is_empty())
151            {
152                lock.subscribers.remove(emitter);
153            }
154            return;
155        }
156
157        // Add any new subscribers that were added while invoking the callback.
158        lock.subscribers
159            .entry(emitter.clone())
160            .or_default()
161            .append(&mut subscribers);
162    }
163}
164
165struct SingleSubscriberSetState<Callback> {
166    subscribers: BTreeMap<usize, Subscriber<Callback>>,
167    next_subscriber_id: usize,
168}
169
170impl<Callback> Clone for SingleSubscriberSet<Callback> {
171    fn clone(&self) -> Self {
172        Self(self.0.clone())
173    }
174}
175
176impl<Callback: 'static> SingleSubscriberSet<Callback> {
177    pub fn new() -> Self {
178        Self(Rc::new(RefCell::new(SingleSubscriberSetState {
179            subscribers: BTreeMap::new(),
180            next_subscriber_id: 0,
181        })))
182    }
183
184    /// Inserts an immediately active subscription.
185    pub fn insert(&self, callback: Callback) -> Subscription {
186        let (subscription, activate) = self.insert_pending(callback);
187        activate();
188        subscription
189    }
190
191    /// Inserts a pending subscription and returns its handle and activation function.
192    ///
193    /// Pending callbacks are skipped by `retain` and excluded from `remove` until activated.
194    pub fn insert_pending(
195        &self,
196        callback: Callback,
197    ) -> (Subscription, impl FnOnce() + use<Callback>) {
198        let state = Rc::new(Cell::new(SubscriberState::Pending));
199        let mut lock = self.0.borrow_mut();
200        let subscriber_id = post_inc(&mut lock.next_subscriber_id);
201        lock.subscribers.insert(
202            subscriber_id,
203            Subscriber {
204                state: state.clone(),
205                callback,
206            },
207        );
208        let this = self.0.clone();
209
210        let subscription = Subscription {
211            unsubscribe: Some(Box::new({
212                let state = state.clone();
213                move || {
214                    state.set(SubscriberState::Dropped);
215                    this.borrow_mut().subscribers.remove(&subscriber_id);
216                }
217            })),
218        };
219        (subscription, move || {
220            if state.get() == SubscriberState::Pending {
221                state.set(SubscriberState::Active);
222            }
223        })
224    }
225
226    /// Removes all subscriptions and returns the active callbacks in registration order.
227    pub fn remove(&self) -> impl IntoIterator<Item = Callback> + use<Callback> {
228        let subscribers = mem::take(&mut self.0.borrow_mut().subscribers);
229        subscribers.into_values().filter_map(|subscriber| {
230            if subscriber.state.get() == SubscriberState::Active {
231                Some(subscriber.callback)
232            } else {
233                None
234            }
235        })
236    }
237
238    /// Invokes each active callback, removing subscriptions for which `f` returns false.
239    pub fn retain(&self, mut f: impl FnMut(&mut Callback) -> bool) {
240        let mut subscribers = mem::take(&mut self.0.borrow_mut().subscribers);
241        subscribers.retain(|_, subscriber| subscriber.retain(&mut f));
242        self.0.borrow_mut().subscribers.append(&mut subscribers);
243    }
244}
245
246impl<Callback> Subscriber<Callback> {
247    fn retain(&mut self, f: &mut impl FnMut(&mut Callback) -> bool) -> bool {
248        match self.state.get() {
249            SubscriberState::Pending => true,
250            SubscriberState::Dropped => false,
251            SubscriberState::Active => {
252                let keep = f(&mut self.callback);
253                keep && self.state.get() != SubscriberState::Dropped
254            }
255        }
256    }
257}
258
259/// A handle to a subscription created by GPUI. When dropped, the subscription
260/// is cancelled and the callback will no longer be invoked.
261#[must_use]
262pub struct Subscription {
263    unsubscribe: Option<Box<dyn FnOnce() + 'static>>,
264}
265
266impl Subscription {
267    /// Creates a new subscription with a callback that gets invoked when
268    /// this subscription is dropped.
269    pub fn new(unsubscribe: impl 'static + FnOnce()) -> Self {
270        Self {
271            unsubscribe: Some(Box::new(unsubscribe)),
272        }
273    }
274
275    /// Detaches the subscription from this handle. The callback will
276    /// continue to be invoked until the entities it has been
277    /// subscribed to are dropped
278    pub fn detach(mut self) {
279        self.unsubscribe.take();
280    }
281
282    /// Joins two subscriptions into a single subscription. Detach will
283    /// detach both interior subscriptions.
284    pub fn join(mut subscription_a: Self, mut subscription_b: Self) -> Self {
285        let a_unsubscribe = subscription_a.unsubscribe.take();
286        let b_unsubscribe = subscription_b.unsubscribe.take();
287        Self {
288            unsubscribe: Some(Box::new(move || {
289                if let Some(self_unsubscribe) = a_unsubscribe {
290                    self_unsubscribe();
291                }
292                if let Some(other_unsubscribe) = b_unsubscribe {
293                    other_unsubscribe();
294                }
295            })),
296        }
297    }
298}
299
300impl Drop for Subscription {
301    fn drop(&mut self) {
302        if let Some(unsubscribe) = self.unsubscribe.take() {
303            unsubscribe();
304        }
305    }
306}
307
308impl std::fmt::Debug for Subscription {
309    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
310        f.debug_struct("Subscription").finish()
311    }
312}
313
314#[cfg(test)]
315mod tests {
316    use super::*;
317    use crate::{Global, TestApp};
318
319    #[test]
320    fn test_immediate_and_pending_insertions() {
321        let subscribers = SubscriberSet::new();
322        let first = subscribers.insert(1usize, 10);
323        let (second, activate_second) = subscribers.insert_pending(1, 20);
324        let mut visited = Vec::new();
325        subscribers.retain(&1, |callback| {
326            visited.push(*callback);
327            subscribers.insert(1, 30).detach();
328            true
329        });
330        assert_eq!(visited, [10]);
331        activate_second();
332        assert_eq!(
333            subscribers.remove(&1).into_iter().collect::<Vec<_>>(),
334            [10, 20, 30]
335        );
336        drop(first);
337        drop(second);
338
339        let (pending, _activate) = subscribers.insert_pending(1, 40);
340        assert_eq!(subscribers.remove(&1).into_iter().count(), 0);
341        drop(pending);
342    }
343
344    #[test]
345    fn test_single_immediate_and_pending_insertions() {
346        let subscribers = SingleSubscriberSet::new();
347        let first = subscribers.insert(10);
348        let (second, activate_second) = subscribers.insert_pending(20);
349        let mut visited = Vec::new();
350        subscribers.retain(|callback| {
351            visited.push(*callback);
352            subscribers.insert(30).detach();
353            true
354        });
355        assert_eq!(visited, [10]);
356        activate_second();
357        assert_eq!(
358            subscribers.remove().into_iter().collect::<Vec<_>>(),
359            [10, 20, 30]
360        );
361        drop(first);
362        drop(second);
363
364        let (pending, _activate) = subscribers.insert_pending(40);
365        assert_eq!(subscribers.remove().into_iter().count(), 0);
366        drop(pending);
367    }
368
369    #[test]
370    fn test_activation_after_drop_during_retain() {
371        let subscribers = SubscriberSet::new();
372        let (first, activate_first) = subscribers.insert_pending(1usize, 10);
373        let (second, activate_second) = subscribers.insert_pending(1, 20);
374        subscribers.retain(&1, |_| panic!("pending subscriber invoked"));
375        activate_first();
376        let mut second = Some((second, activate_second));
377        let mut visited = Vec::new();
378
379        subscribers.retain(&1, |callback| {
380            visited.push(*callback);
381            let (subscription, activate) = second.take().expect("only the first callback runs");
382            drop(subscription);
383            activate();
384            true
385        });
386
387        assert_eq!(visited, [10]);
388        assert_eq!(subscribers.remove(&1).into_iter().collect::<Vec<_>>(), [10]);
389        drop(first);
390    }
391
392    #[test]
393    fn test_single_activation_after_drop_during_retain() {
394        let subscribers = SingleSubscriberSet::new();
395        let (first, activate_first) = subscribers.insert_pending(10);
396        let (second, activate_second) = subscribers.insert_pending(20);
397        subscribers.retain(|_| panic!("pending subscriber invoked"));
398        activate_first();
399        let mut second = Some((second, activate_second));
400        let mut visited = Vec::new();
401
402        subscribers.retain(|callback| {
403            visited.push(*callback);
404            let (subscription, activate) = second.take().expect("only the first callback runs");
405            drop(subscription);
406            activate();
407            true
408        });
409
410        assert_eq!(visited, [10]);
411        assert_eq!(subscribers.remove().into_iter().collect::<Vec<_>>(), [10]);
412        drop(first);
413    }
414
415    #[test]
416    fn test_unsubscribe_during_callback_with_insert() {
417        struct TestGlobal;
418        impl Global for TestGlobal {}
419
420        let mut app = TestApp::new();
421        app.set_global(TestGlobal);
422
423        let observer_a_count = Rc::new(Cell::new(0usize));
424        let observer_b_count = Rc::new(Cell::new(0usize));
425
426        let sub_a: Rc<RefCell<Option<Subscription>>> = Default::default();
427        let sub_b: Rc<RefCell<Option<Subscription>>> = Default::default();
428
429        // Observer A fires first (lower subscriber_id). It drops itself and
430        // inserts a new observer for the same global.
431        *sub_a.borrow_mut() = Some(app.update({
432            let count = observer_a_count.clone();
433            let sub_a = sub_a.clone();
434            move |cx| {
435                cx.observe_global::<TestGlobal>(move |cx| {
436                    count.set(count.get() + 1);
437                    sub_a.borrow_mut().take();
438                    cx.observe_global::<TestGlobal>(|_| {}).detach();
439                })
440            }
441        }));
442
443        // Observer B fires second. It just drops itself.
444        *sub_b.borrow_mut() = Some(app.update({
445            let count = observer_b_count.clone();
446            let sub_b = sub_b.clone();
447            move |cx| {
448                cx.observe_global::<TestGlobal>(move |_cx| {
449                    count.set(count.get() + 1);
450                    sub_b.borrow_mut().take();
451                })
452            }
453        }));
454
455        // Both fire once.
456        app.update(|cx| cx.set_global(TestGlobal));
457        assert_eq!(observer_a_count.get(), 1);
458        assert_eq!(observer_b_count.get(), 1);
459
460        // Neither should fire again — both dropped their subscriptions.
461        app.update(|cx| cx.set_global(TestGlobal));
462        assert_eq!(observer_a_count.get(), 1);
463        assert_eq!(observer_b_count.get(), 1, "orphaned subscriber fired again");
464    }
465
466    #[test]
467    fn test_callback_dropped_by_earlier_callback_does_not_fire() {
468        struct TestGlobal;
469        impl Global for TestGlobal {}
470
471        let mut app = TestApp::new();
472        app.set_global(TestGlobal);
473
474        let observer_b_count = Rc::new(Cell::new(0usize));
475        let sub_b: Rc<RefCell<Option<Subscription>>> = Default::default();
476
477        // Observer A fires first and drops B's subscription.
478        app.update({
479            let sub_b = sub_b.clone();
480            move |cx| {
481                cx.observe_global::<TestGlobal>(move |_cx| {
482                    sub_b.borrow_mut().take();
483                })
484                .detach();
485            }
486        });
487
488        // Observer B fires second — but A already dropped it.
489        *sub_b.borrow_mut() = Some(app.update({
490            let count = observer_b_count.clone();
491            move |cx| {
492                cx.observe_global::<TestGlobal>(move |_cx| {
493                    count.set(count.get() + 1);
494                })
495            }
496        }));
497
498        app.update(|cx| cx.set_global(TestGlobal));
499        assert_eq!(
500            observer_b_count.get(),
501            0,
502            "B should not fire — A dropped its subscription"
503        );
504    }
505
506    #[test]
507    fn test_self_drop_during_callback() {
508        struct TestGlobal;
509        impl Global for TestGlobal {}
510
511        let mut app = TestApp::new();
512        app.set_global(TestGlobal);
513
514        let count = Rc::new(Cell::new(0usize));
515        let sub: Rc<RefCell<Option<Subscription>>> = Default::default();
516
517        *sub.borrow_mut() = Some(app.update({
518            let count = count.clone();
519            let sub = sub.clone();
520            move |cx| {
521                cx.observe_global::<TestGlobal>(move |_cx| {
522                    count.set(count.get() + 1);
523                    sub.borrow_mut().take();
524                })
525            }
526        }));
527
528        app.update(|cx| cx.set_global(TestGlobal));
529        assert_eq!(count.get(), 1);
530
531        app.update(|cx| cx.set_global(TestGlobal));
532        assert_eq!(count.get(), 1, "should not fire after self-drop");
533    }
534
535    #[test]
536    fn test_subscription_drop() {
537        struct TestGlobal;
538        impl Global for TestGlobal {}
539
540        let mut app = TestApp::new();
541        app.set_global(TestGlobal);
542
543        let count = Rc::new(Cell::new(0usize));
544
545        let subscription = app.update({
546            let count = count.clone();
547            move |cx| {
548                cx.observe_global::<TestGlobal>(move |_cx| {
549                    count.set(count.get() + 1);
550                })
551            }
552        });
553
554        drop(subscription);
555
556        app.update(|cx| cx.set_global(TestGlobal));
557        assert_eq!(count.get(), 0, "should not fire after drop");
558    }
559}