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    rc::Rc,
7};
8
9pub(crate) struct SubscriberSet<EmitterKey, Callback>(
10    Rc<RefCell<SubscriberSetState<EmitterKey, Callback>>>,
11);
12
13impl<EmitterKey, Callback> Clone for SubscriberSet<EmitterKey, Callback> {
14    fn clone(&self) -> Self {
15        SubscriberSet(self.0.clone())
16    }
17}
18
19struct SubscriberSetState<EmitterKey, Callback> {
20    subscribers: BTreeMap<EmitterKey, Option<BTreeMap<usize, Subscriber<Callback>>>>,
21    next_subscriber_id: usize,
22}
23
24struct Subscriber<Callback> {
25    active: Rc<Cell<bool>>,
26    dropped: Rc<Cell<bool>>,
27    callback: Callback,
28}
29
30impl<EmitterKey, Callback> SubscriberSet<EmitterKey, Callback>
31where
32    EmitterKey: 'static + Ord + Clone + Debug,
33    Callback: 'static,
34{
35    pub fn new() -> Self {
36        Self(Rc::new(RefCell::new(SubscriberSetState {
37            subscribers: Default::default(),
38            next_subscriber_id: 0,
39        })))
40    }
41
42    /// Inserts a new [`Subscription`] for the given `emitter_key`. By default, subscriptions
43    /// are inert, meaning that they won't be listed when calling `[SubscriberSet::remove]` or `[SubscriberSet::retain]`.
44    /// This method returns a tuple of a [`Subscription`] and an `impl FnOnce`, and you can use the latter
45    /// to activate the [`Subscription`].
46    pub fn insert(
47        &self,
48        emitter_key: EmitterKey,
49        callback: Callback,
50    ) -> (Subscription, impl FnOnce() + use<EmitterKey, Callback>) {
51        let active = Rc::new(Cell::new(false));
52        let dropped = Rc::new(Cell::new(false));
53        let mut lock = self.0.borrow_mut();
54        let subscriber_id = post_inc(&mut lock.next_subscriber_id);
55        lock.subscribers
56            .entry(emitter_key.clone())
57            .or_default()
58            .get_or_insert_with(Default::default)
59            .insert(
60                subscriber_id,
61                Subscriber {
62                    active: active.clone(),
63                    dropped: dropped.clone(),
64                    callback,
65                },
66            );
67        let this = self.0.clone();
68
69        let subscription = Subscription {
70            unsubscribe: Some(Box::new(move || {
71                dropped.set(true);
72
73                let mut lock = this.borrow_mut();
74                let Some(subscribers) = lock.subscribers.get_mut(&emitter_key) else {
75                    return;
76                };
77
78                if let Some(subscribers) = subscribers {
79                    subscribers.remove(&subscriber_id);
80                    if subscribers.is_empty() {
81                        lock.subscribers.remove(&emitter_key);
82                    }
83                }
84            })),
85        };
86        (subscription, move || active.set(true))
87    }
88
89    pub fn remove(
90        &self,
91        emitter: &EmitterKey,
92    ) -> impl IntoIterator<Item = Callback> + use<EmitterKey, Callback> {
93        let subscribers = self.0.borrow_mut().subscribers.remove(emitter);
94        subscribers
95            .unwrap_or_default()
96            .map(|s| s.into_values())
97            .into_iter()
98            .flatten()
99            .filter_map(|subscriber| {
100                if subscriber.active.get() {
101                    Some(subscriber.callback)
102                } else {
103                    None
104                }
105            })
106    }
107
108    /// Call the given callback for each subscriber to the given emitter.
109    /// If the callback returns false, the subscriber is removed.
110    pub fn retain<F>(&self, emitter: &EmitterKey, mut f: F)
111    where
112        F: FnMut(&mut Callback) -> bool,
113    {
114        let Some(mut subscribers) = self
115            .0
116            .borrow_mut()
117            .subscribers
118            .get_mut(emitter)
119            .and_then(|s| s.take())
120        else {
121            return;
122        };
123
124        subscribers.retain(|_, subscriber| {
125            if !subscriber.active.get() {
126                return true;
127            }
128            if subscriber.dropped.get() {
129                return false;
130            }
131            let keep = f(&mut subscriber.callback);
132            keep && !subscriber.dropped.get()
133        });
134        let mut lock = self.0.borrow_mut();
135
136        // Add any new subscribers that were added while invoking the callback.
137        if let Some(Some(new_subscribers)) = lock.subscribers.remove(emitter) {
138            subscribers.extend(new_subscribers);
139        }
140
141        if !subscribers.is_empty() {
142            lock.subscribers.insert(emitter.clone(), Some(subscribers));
143        }
144    }
145}
146
147/// A handle to a callback that GPUI calls until the handle is dropped.
148///
149/// Methods such as [`Context::observe`](crate::Context::observe),
150/// [`Context::subscribe`](crate::Context::subscribe) and
151/// [`App::observe`](crate::App::observe) return a `Subscription`. Dropping it cancels the
152/// callback. Keep it in a field to tie the callback to the lifetime of the entity that owns the
153/// field. Call [`Subscription::detach`] to keep the callback registered without holding the
154/// handle.
155#[must_use]
156pub struct Subscription {
157    unsubscribe: Option<Box<dyn FnOnce() + 'static>>,
158}
159
160impl Subscription {
161    /// Creates a subscription that runs `unsubscribe` when it's dropped.
162    ///
163    /// Use it to give your own resource the same drop-to-cancel behavior as the subscriptions that GPUI
164    /// returns.
165    ///
166    /// # Examples
167    ///
168    /// ```
169    /// # use gpui::Subscription;
170    /// # use std::{cell::Cell, rc::Rc};
171    /// let cleaned_up = Rc::new(Cell::new(false));
172    ///
173    /// let subscription = Subscription::new({
174    ///     let cleaned_up = cleaned_up.clone();
175    ///     move || cleaned_up.set(true)
176    /// });
177    /// assert!(!cleaned_up.get());
178    ///
179    /// drop(subscription);
180    /// assert!(cleaned_up.get());
181    /// ```
182    pub fn new(unsubscribe: impl 'static + FnOnce()) -> Self {
183        Self {
184            unsubscribe: Some(Box::new(unsubscribe)),
185        }
186    }
187
188    /// Forgets the cancellation handle and leaves the callback registered.
189    ///
190    /// A callback created by GPUI may still remove itself when an entity it depends on is released.
191    /// Detaching does not run the `unsubscribe` callback supplied to [`Subscription::new`].
192    pub fn detach(mut self) {
193        self.unsubscribe.take();
194    }
195
196    /// Combines two subscriptions into one cancellation handle.
197    ///
198    /// Dropping the result cancels both subscriptions. Calling [`Subscription::detach`] on the result
199    /// detaches both.
200    pub fn join(mut subscription_a: Self, mut subscription_b: Self) -> Self {
201        let a_unsubscribe = subscription_a.unsubscribe.take();
202        let b_unsubscribe = subscription_b.unsubscribe.take();
203        Self {
204            unsubscribe: Some(Box::new(move || {
205                if let Some(self_unsubscribe) = a_unsubscribe {
206                    self_unsubscribe();
207                }
208                if let Some(other_unsubscribe) = b_unsubscribe {
209                    other_unsubscribe();
210                }
211            })),
212        }
213    }
214}
215
216impl Drop for Subscription {
217    fn drop(&mut self) {
218        if let Some(unsubscribe) = self.unsubscribe.take() {
219            unsubscribe();
220        }
221    }
222}
223
224impl std::fmt::Debug for Subscription {
225    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
226        f.debug_struct("Subscription").finish()
227    }
228}
229
230#[cfg(test)]
231mod tests {
232    use super::*;
233    use crate::{Global, TestApp};
234
235    #[test]
236    fn test_unsubscribe_during_callback_with_insert() {
237        struct TestGlobal;
238        impl Global for TestGlobal {}
239
240        let mut app = TestApp::new();
241        app.set_global(TestGlobal);
242
243        let observer_a_count = Rc::new(Cell::new(0usize));
244        let observer_b_count = Rc::new(Cell::new(0usize));
245
246        let sub_a: Rc<RefCell<Option<Subscription>>> = Default::default();
247        let sub_b: Rc<RefCell<Option<Subscription>>> = Default::default();
248
249        // Observer A fires first (lower subscriber_id). It drops itself and
250        // inserts a new observer for the same global.
251        *sub_a.borrow_mut() = Some(app.update({
252            let count = observer_a_count.clone();
253            let sub_a = sub_a.clone();
254            move |cx| {
255                cx.observe_global::<TestGlobal>(move |cx| {
256                    count.set(count.get() + 1);
257                    sub_a.borrow_mut().take();
258                    cx.observe_global::<TestGlobal>(|_| {}).detach();
259                })
260            }
261        }));
262
263        // Observer B fires second. It just drops itself.
264        *sub_b.borrow_mut() = Some(app.update({
265            let count = observer_b_count.clone();
266            let sub_b = sub_b.clone();
267            move |cx| {
268                cx.observe_global::<TestGlobal>(move |_cx| {
269                    count.set(count.get() + 1);
270                    sub_b.borrow_mut().take();
271                })
272            }
273        }));
274
275        // Both fire once.
276        app.update(|cx| cx.set_global(TestGlobal));
277        assert_eq!(observer_a_count.get(), 1);
278        assert_eq!(observer_b_count.get(), 1);
279
280        // Neither should fire again — both dropped their subscriptions.
281        app.update(|cx| cx.set_global(TestGlobal));
282        assert_eq!(observer_a_count.get(), 1);
283        assert_eq!(observer_b_count.get(), 1, "orphaned subscriber fired again");
284    }
285
286    #[test]
287    fn test_callback_dropped_by_earlier_callback_does_not_fire() {
288        struct TestGlobal;
289        impl Global for TestGlobal {}
290
291        let mut app = TestApp::new();
292        app.set_global(TestGlobal);
293
294        let observer_b_count = Rc::new(Cell::new(0usize));
295        let sub_b: Rc<RefCell<Option<Subscription>>> = Default::default();
296
297        // Observer A fires first and drops B's subscription.
298        app.update({
299            let sub_b = sub_b.clone();
300            move |cx| {
301                cx.observe_global::<TestGlobal>(move |_cx| {
302                    sub_b.borrow_mut().take();
303                })
304                .detach();
305            }
306        });
307
308        // Observer B fires second — but A already dropped it.
309        *sub_b.borrow_mut() = Some(app.update({
310            let count = observer_b_count.clone();
311            move |cx| {
312                cx.observe_global::<TestGlobal>(move |_cx| {
313                    count.set(count.get() + 1);
314                })
315            }
316        }));
317
318        app.update(|cx| cx.set_global(TestGlobal));
319        assert_eq!(
320            observer_b_count.get(),
321            0,
322            "B should not fire — A dropped its subscription"
323        );
324    }
325
326    #[test]
327    fn test_self_drop_during_callback() {
328        struct TestGlobal;
329        impl Global for TestGlobal {}
330
331        let mut app = TestApp::new();
332        app.set_global(TestGlobal);
333
334        let count = Rc::new(Cell::new(0usize));
335        let sub: Rc<RefCell<Option<Subscription>>> = Default::default();
336
337        *sub.borrow_mut() = Some(app.update({
338            let count = count.clone();
339            let sub = sub.clone();
340            move |cx| {
341                cx.observe_global::<TestGlobal>(move |_cx| {
342                    count.set(count.get() + 1);
343                    sub.borrow_mut().take();
344                })
345            }
346        }));
347
348        app.update(|cx| cx.set_global(TestGlobal));
349        assert_eq!(count.get(), 1);
350
351        app.update(|cx| cx.set_global(TestGlobal));
352        assert_eq!(count.get(), 1, "should not fire after self-drop");
353    }
354
355    #[test]
356    fn test_subscription_drop() {
357        struct TestGlobal;
358        impl Global for TestGlobal {}
359
360        let mut app = TestApp::new();
361        app.set_global(TestGlobal);
362
363        let count = Rc::new(Cell::new(0usize));
364
365        let subscription = app.update({
366            let count = count.clone();
367            move |cx| {
368                cx.observe_global::<TestGlobal>(move |_cx| {
369                    count.set(count.get() + 1);
370                })
371            }
372        });
373
374        drop(subscription);
375
376        app.update(|cx| cx.set_global(TestGlobal));
377        assert_eq!(count.get(), 0, "should not fire after drop");
378    }
379}