Restructure callback subscriptions
Fix a callback leak that would occur when dropping a subscription to a callback collection after triggering that callback, but before processing the effect of *adding* the handler. Co-authored-by: Kay Simmons <kay@zed.dev>
This commit is contained in:
parent
f669b8a029
commit
378f0c32fe
2 changed files with 207 additions and 350 deletions
|
@ -26,8 +26,8 @@ use smallvec::SmallVec;
|
||||||
use smol::prelude::*;
|
use smol::prelude::*;
|
||||||
|
|
||||||
pub use action::*;
|
pub use action::*;
|
||||||
use callback_collection::{CallbackCollection, Mapping};
|
use callback_collection::CallbackCollection;
|
||||||
use collections::{btree_map, hash_map::Entry, BTreeMap, HashMap, HashSet, VecDeque};
|
use collections::{hash_map::Entry, BTreeMap, HashMap, HashSet, VecDeque};
|
||||||
use keymap::MatchResult;
|
use keymap::MatchResult;
|
||||||
use platform::Event;
|
use platform::Event;
|
||||||
#[cfg(any(test, feature = "test-support"))]
|
#[cfg(any(test, feature = "test-support"))]
|
||||||
|
@ -1047,12 +1047,10 @@ impl MutableAppContext {
|
||||||
callback(payload, cx)
|
callback(payload, cx)
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
Subscription::GlobalSubscription(
|
||||||
Subscription::GlobalSubscription {
|
self.global_subscriptions
|
||||||
id: subscription_id,
|
.subscribe(type_id, subscription_id),
|
||||||
type_id,
|
)
|
||||||
subscriptions: Some(self.global_subscriptions.downgrade()),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn observe<E, H, F>(&mut self, handle: &H, mut callback: F) -> Subscription
|
pub fn observe<E, H, F>(&mut self, handle: &H, mut callback: F) -> Subscription
|
||||||
|
@ -1089,11 +1087,7 @@ impl MutableAppContext {
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
Subscription::Subscription {
|
Subscription::Subscription(self.subscriptions.subscribe(handle.id(), subscription_id))
|
||||||
id: subscription_id,
|
|
||||||
entity_id: handle.id(),
|
|
||||||
subscriptions: Some(self.subscriptions.downgrade()),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn observe_internal<E, H, F>(&mut self, handle: &H, mut callback: F) -> Subscription
|
fn observe_internal<E, H, F>(&mut self, handle: &H, mut callback: F) -> Subscription
|
||||||
|
@ -1117,11 +1111,7 @@ impl MutableAppContext {
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
Subscription::Observation {
|
Subscription::Observation(self.observations.subscribe(entity_id, subscription_id))
|
||||||
id: subscription_id,
|
|
||||||
entity_id,
|
|
||||||
observations: Some(self.observations.downgrade()),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn observe_focus<F, V>(&mut self, handle: &ViewHandle<V>, mut callback: F) -> Subscription
|
fn observe_focus<F, V>(&mut self, handle: &ViewHandle<V>, mut callback: F) -> Subscription
|
||||||
|
@ -1144,12 +1134,7 @@ impl MutableAppContext {
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
Subscription::FocusObservation(self.focus_observations.subscribe(view_id, subscription_id))
|
||||||
Subscription::FocusObservation {
|
|
||||||
id: subscription_id,
|
|
||||||
view_id,
|
|
||||||
observations: Some(self.focus_observations.downgrade()),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn observe_global<G, F>(&mut self, mut observe: F) -> Subscription
|
pub fn observe_global<G, F>(&mut self, mut observe: F) -> Subscription
|
||||||
|
@ -1165,12 +1150,7 @@ impl MutableAppContext {
|
||||||
id,
|
id,
|
||||||
Box::new(move |cx: &mut MutableAppContext| observe(cx)),
|
Box::new(move |cx: &mut MutableAppContext| observe(cx)),
|
||||||
);
|
);
|
||||||
|
Subscription::GlobalObservation(self.global_observations.subscribe(type_id, id))
|
||||||
Subscription::GlobalObservation {
|
|
||||||
id,
|
|
||||||
type_id,
|
|
||||||
observations: Some(self.global_observations.downgrade()),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn observe_default_global<G, F>(&mut self, observe: F) -> Subscription
|
pub fn observe_default_global<G, F>(&mut self, observe: F) -> Subscription
|
||||||
|
@ -1235,11 +1215,10 @@ impl MutableAppContext {
|
||||||
subscription_id,
|
subscription_id,
|
||||||
callback: Box::new(callback),
|
callback: Box::new(callback),
|
||||||
});
|
});
|
||||||
Subscription::WindowActivationObservation {
|
Subscription::WindowActivationObservation(
|
||||||
id: subscription_id,
|
self.window_activation_observations
|
||||||
window_id,
|
.subscribe(window_id, subscription_id),
|
||||||
observations: Some(self.window_activation_observations.downgrade()),
|
)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn observe_fullscreen<F>(&mut self, window_id: usize, callback: F) -> Subscription
|
fn observe_fullscreen<F>(&mut self, window_id: usize, callback: F) -> Subscription
|
||||||
|
@ -1253,11 +1232,10 @@ impl MutableAppContext {
|
||||||
subscription_id,
|
subscription_id,
|
||||||
callback: Box::new(callback),
|
callback: Box::new(callback),
|
||||||
});
|
});
|
||||||
Subscription::WindowFullscreenObservation {
|
Subscription::WindowActivationObservation(
|
||||||
id: subscription_id,
|
self.window_activation_observations
|
||||||
window_id,
|
.subscribe(window_id, subscription_id),
|
||||||
observations: Some(self.window_activation_observations.downgrade()),
|
)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn observe_keystrokes<F>(&mut self, window_id: usize, callback: F) -> Subscription
|
pub fn observe_keystrokes<F>(&mut self, window_id: usize, callback: F) -> Subscription
|
||||||
|
@ -1273,12 +1251,10 @@ impl MutableAppContext {
|
||||||
let subscription_id = post_inc(&mut self.next_subscription_id);
|
let subscription_id = post_inc(&mut self.next_subscription_id);
|
||||||
self.keystroke_observations
|
self.keystroke_observations
|
||||||
.add_callback(window_id, subscription_id, Box::new(callback));
|
.add_callback(window_id, subscription_id, Box::new(callback));
|
||||||
|
Subscription::KeystrokeObservation(
|
||||||
Subscription::KeystrokeObservation {
|
self.keystroke_observations
|
||||||
id: subscription_id,
|
.subscribe(window_id, subscription_id),
|
||||||
window_id,
|
)
|
||||||
observations: Some(self.keystroke_observations.downgrade()),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn defer(&mut self, callback: impl 'static + FnOnce(&mut MutableAppContext)) {
|
pub fn defer(&mut self, callback: impl 'static + FnOnce(&mut MutableAppContext)) {
|
||||||
|
@ -1999,15 +1975,13 @@ impl MutableAppContext {
|
||||||
entity_id,
|
entity_id,
|
||||||
subscription_id,
|
subscription_id,
|
||||||
callback,
|
callback,
|
||||||
} => self.subscriptions.add_or_remove_callback(
|
} => self
|
||||||
entity_id,
|
.subscriptions
|
||||||
subscription_id,
|
.add_callback(entity_id, subscription_id, callback),
|
||||||
callback,
|
|
||||||
),
|
|
||||||
|
|
||||||
Effect::Event { entity_id, payload } => {
|
Effect::Event { entity_id, payload } => {
|
||||||
let mut subscriptions = self.subscriptions.clone();
|
let mut subscriptions = self.subscriptions.clone();
|
||||||
subscriptions.emit_and_cleanup(entity_id, self, |callback, this| {
|
subscriptions.emit(entity_id, self, |callback, this| {
|
||||||
callback(payload.as_ref(), this)
|
callback(payload.as_ref(), this)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -2016,7 +1990,7 @@ impl MutableAppContext {
|
||||||
type_id,
|
type_id,
|
||||||
subscription_id,
|
subscription_id,
|
||||||
callback,
|
callback,
|
||||||
} => self.global_subscriptions.add_or_remove_callback(
|
} => self.global_subscriptions.add_callback(
|
||||||
type_id,
|
type_id,
|
||||||
subscription_id,
|
subscription_id,
|
||||||
callback,
|
callback,
|
||||||
|
@ -2028,16 +2002,13 @@ impl MutableAppContext {
|
||||||
entity_id,
|
entity_id,
|
||||||
subscription_id,
|
subscription_id,
|
||||||
callback,
|
callback,
|
||||||
} => self.observations.add_or_remove_callback(
|
} => self
|
||||||
entity_id,
|
.observations
|
||||||
subscription_id,
|
.add_callback(entity_id, subscription_id, callback),
|
||||||
callback,
|
|
||||||
),
|
|
||||||
|
|
||||||
Effect::ModelNotification { model_id } => {
|
Effect::ModelNotification { model_id } => {
|
||||||
let mut observations = self.observations.clone();
|
let mut observations = self.observations.clone();
|
||||||
observations
|
observations.emit(model_id, self, |callback, this| callback(this));
|
||||||
.emit_and_cleanup(model_id, self, |callback, this| callback(this));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Effect::ViewNotification { window_id, view_id } => {
|
Effect::ViewNotification { window_id, view_id } => {
|
||||||
|
@ -2046,7 +2017,7 @@ impl MutableAppContext {
|
||||||
|
|
||||||
Effect::GlobalNotification { type_id } => {
|
Effect::GlobalNotification { type_id } => {
|
||||||
let mut subscriptions = self.global_observations.clone();
|
let mut subscriptions = self.global_observations.clone();
|
||||||
subscriptions.emit_and_cleanup(type_id, self, |callback, this| {
|
subscriptions.emit(type_id, self, |callback, this| {
|
||||||
callback(this);
|
callback(this);
|
||||||
true
|
true
|
||||||
});
|
});
|
||||||
|
@ -2080,7 +2051,7 @@ impl MutableAppContext {
|
||||||
subscription_id,
|
subscription_id,
|
||||||
callback,
|
callback,
|
||||||
} => {
|
} => {
|
||||||
self.focus_observations.add_or_remove_callback(
|
self.focus_observations.add_callback(
|
||||||
view_id,
|
view_id,
|
||||||
subscription_id,
|
subscription_id,
|
||||||
callback,
|
callback,
|
||||||
|
@ -2099,7 +2070,7 @@ impl MutableAppContext {
|
||||||
window_id,
|
window_id,
|
||||||
subscription_id,
|
subscription_id,
|
||||||
callback,
|
callback,
|
||||||
} => self.window_activation_observations.add_or_remove_callback(
|
} => self.window_activation_observations.add_callback(
|
||||||
window_id,
|
window_id,
|
||||||
subscription_id,
|
subscription_id,
|
||||||
callback,
|
callback,
|
||||||
|
@ -2114,7 +2085,7 @@ impl MutableAppContext {
|
||||||
window_id,
|
window_id,
|
||||||
subscription_id,
|
subscription_id,
|
||||||
callback,
|
callback,
|
||||||
} => self.window_fullscreen_observations.add_or_remove_callback(
|
} => self.window_fullscreen_observations.add_callback(
|
||||||
window_id,
|
window_id,
|
||||||
subscription_id,
|
subscription_id,
|
||||||
callback,
|
callback,
|
||||||
|
@ -2158,7 +2129,17 @@ impl MutableAppContext {
|
||||||
self.pending_notifications.clear();
|
self.pending_notifications.clear();
|
||||||
self.remove_dropped_entities();
|
self.remove_dropped_entities();
|
||||||
} else {
|
} else {
|
||||||
|
self.focus_observations.gc();
|
||||||
|
self.global_subscriptions.gc();
|
||||||
|
self.global_observations.gc();
|
||||||
|
self.subscriptions.gc();
|
||||||
|
self.observations.gc();
|
||||||
|
self.window_activation_observations.gc();
|
||||||
|
self.window_fullscreen_observations.gc();
|
||||||
|
self.keystroke_observations.gc();
|
||||||
|
|
||||||
self.remove_dropped_entities();
|
self.remove_dropped_entities();
|
||||||
|
|
||||||
if refreshing {
|
if refreshing {
|
||||||
self.perform_window_refresh();
|
self.perform_window_refresh();
|
||||||
} else {
|
} else {
|
||||||
|
@ -2295,7 +2276,7 @@ impl MutableAppContext {
|
||||||
let type_id = (&*payload).type_id();
|
let type_id = (&*payload).type_id();
|
||||||
|
|
||||||
let mut subscriptions = self.global_subscriptions.clone();
|
let mut subscriptions = self.global_subscriptions.clone();
|
||||||
subscriptions.emit_and_cleanup(type_id, self, |callback, this| {
|
subscriptions.emit(type_id, self, |callback, this| {
|
||||||
callback(payload.as_ref(), this);
|
callback(payload.as_ref(), this);
|
||||||
true //Always alive
|
true //Always alive
|
||||||
});
|
});
|
||||||
|
@ -2320,7 +2301,7 @@ impl MutableAppContext {
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut observations = self.observations.clone();
|
let mut observations = self.observations.clone();
|
||||||
observations.emit_and_cleanup(observed_view_id, self, |callback, this| callback(this));
|
observations.emit(observed_view_id, self, |callback, this| callback(this));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2350,7 +2331,7 @@ impl MutableAppContext {
|
||||||
window.is_fullscreen = is_fullscreen;
|
window.is_fullscreen = is_fullscreen;
|
||||||
|
|
||||||
let mut observations = this.window_fullscreen_observations.clone();
|
let mut observations = this.window_fullscreen_observations.clone();
|
||||||
observations.emit_and_cleanup(window_id, this, |callback, this| {
|
observations.emit(window_id, this, |callback, this| {
|
||||||
callback(is_fullscreen, this)
|
callback(is_fullscreen, this)
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -2367,7 +2348,7 @@ impl MutableAppContext {
|
||||||
) {
|
) {
|
||||||
self.update(|this| {
|
self.update(|this| {
|
||||||
let mut observations = this.keystroke_observations.clone();
|
let mut observations = this.keystroke_observations.clone();
|
||||||
observations.emit_and_cleanup(window_id, this, {
|
observations.emit(window_id, this, {
|
||||||
move |callback, this| callback(&keystroke, &result, handled_by.as_ref(), this)
|
move |callback, this| callback(&keystroke, &result, handled_by.as_ref(), this)
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -2403,7 +2384,7 @@ impl MutableAppContext {
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut observations = this.window_activation_observations.clone();
|
let mut observations = this.window_activation_observations.clone();
|
||||||
observations.emit_and_cleanup(window_id, this, |callback, this| callback(active, this));
|
observations.emit(window_id, this, |callback, this| callback(active, this));
|
||||||
|
|
||||||
Some(())
|
Some(())
|
||||||
});
|
});
|
||||||
|
@ -2443,8 +2424,7 @@ impl MutableAppContext {
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut subscriptions = this.focus_observations.clone();
|
let mut subscriptions = this.focus_observations.clone();
|
||||||
subscriptions
|
subscriptions.emit(blurred_id, this, |callback, this| callback(false, this));
|
||||||
.emit_and_cleanup(blurred_id, this, |callback, this| callback(false, this));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(focused_id) = focused_id {
|
if let Some(focused_id) = focused_id {
|
||||||
|
@ -2456,8 +2436,7 @@ impl MutableAppContext {
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut subscriptions = this.focus_observations.clone();
|
let mut subscriptions = this.focus_observations.clone();
|
||||||
subscriptions
|
subscriptions.emit(focused_id, this, |callback, this| callback(true, this));
|
||||||
.emit_and_cleanup(focused_id, this, |callback, this| callback(true, this));
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -5106,46 +5085,14 @@ impl<T> Drop for ElementStateHandle<T> {
|
||||||
|
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub enum Subscription {
|
pub enum Subscription {
|
||||||
Subscription {
|
Subscription(callback_collection::Subscription<usize, SubscriptionCallback>),
|
||||||
id: usize,
|
Observation(callback_collection::Subscription<usize, ObservationCallback>),
|
||||||
entity_id: usize,
|
GlobalSubscription(callback_collection::Subscription<TypeId, GlobalSubscriptionCallback>),
|
||||||
subscriptions: Option<Weak<Mapping<usize, SubscriptionCallback>>>,
|
GlobalObservation(callback_collection::Subscription<TypeId, GlobalObservationCallback>),
|
||||||
},
|
FocusObservation(callback_collection::Subscription<usize, FocusObservationCallback>),
|
||||||
GlobalSubscription {
|
WindowActivationObservation(callback_collection::Subscription<usize, WindowActivationCallback>),
|
||||||
id: usize,
|
WindowFullscreenObservation(callback_collection::Subscription<usize, WindowFullscreenCallback>),
|
||||||
type_id: TypeId,
|
KeystrokeObservation(callback_collection::Subscription<usize, KeystrokeCallback>),
|
||||||
subscriptions: Option<Weak<Mapping<TypeId, GlobalSubscriptionCallback>>>,
|
|
||||||
},
|
|
||||||
Observation {
|
|
||||||
id: usize,
|
|
||||||
entity_id: usize,
|
|
||||||
observations: Option<Weak<Mapping<usize, ObservationCallback>>>,
|
|
||||||
},
|
|
||||||
GlobalObservation {
|
|
||||||
id: usize,
|
|
||||||
type_id: TypeId,
|
|
||||||
observations: Option<Weak<Mapping<TypeId, GlobalObservationCallback>>>,
|
|
||||||
},
|
|
||||||
FocusObservation {
|
|
||||||
id: usize,
|
|
||||||
view_id: usize,
|
|
||||||
observations: Option<Weak<Mapping<usize, FocusObservationCallback>>>,
|
|
||||||
},
|
|
||||||
WindowActivationObservation {
|
|
||||||
id: usize,
|
|
||||||
window_id: usize,
|
|
||||||
observations: Option<Weak<Mapping<usize, WindowActivationCallback>>>,
|
|
||||||
},
|
|
||||||
WindowFullscreenObservation {
|
|
||||||
id: usize,
|
|
||||||
window_id: usize,
|
|
||||||
observations: Option<Weak<Mapping<usize, WindowFullscreenCallback>>>,
|
|
||||||
},
|
|
||||||
KeystrokeObservation {
|
|
||||||
id: usize,
|
|
||||||
window_id: usize,
|
|
||||||
observations: Option<Weak<Mapping<usize, KeystrokeCallback>>>,
|
|
||||||
},
|
|
||||||
|
|
||||||
ReleaseObservation {
|
ReleaseObservation {
|
||||||
id: usize,
|
id: usize,
|
||||||
|
@ -5163,36 +5110,21 @@ pub enum Subscription {
|
||||||
impl Subscription {
|
impl Subscription {
|
||||||
pub fn detach(&mut self) {
|
pub fn detach(&mut self) {
|
||||||
match self {
|
match self {
|
||||||
Subscription::Subscription { subscriptions, .. } => {
|
Subscription::Subscription(subscription) => subscription.detach(),
|
||||||
subscriptions.take();
|
Subscription::GlobalSubscription(subscription) => subscription.detach(),
|
||||||
}
|
Subscription::Observation(subscription) => subscription.detach(),
|
||||||
Subscription::GlobalSubscription { subscriptions, .. } => {
|
Subscription::GlobalObservation(subscription) => subscription.detach(),
|
||||||
subscriptions.take();
|
Subscription::FocusObservation(subscription) => subscription.detach(),
|
||||||
}
|
Subscription::KeystrokeObservation(subscription) => subscription.detach(),
|
||||||
Subscription::Observation { observations, .. } => {
|
Subscription::WindowActivationObservation(subscription) => subscription.detach(),
|
||||||
observations.take();
|
Subscription::WindowFullscreenObservation(subscription) => subscription.detach(),
|
||||||
}
|
|
||||||
Subscription::GlobalObservation { observations, .. } => {
|
|
||||||
observations.take();
|
|
||||||
}
|
|
||||||
Subscription::ReleaseObservation { observations, .. } => {
|
Subscription::ReleaseObservation { observations, .. } => {
|
||||||
observations.take();
|
observations.take();
|
||||||
}
|
}
|
||||||
Subscription::FocusObservation { observations, .. } => {
|
|
||||||
observations.take();
|
|
||||||
}
|
|
||||||
Subscription::ActionObservation { observations, .. } => {
|
Subscription::ActionObservation { observations, .. } => {
|
||||||
observations.take();
|
observations.take();
|
||||||
}
|
}
|
||||||
Subscription::KeystrokeObservation { observations, .. } => {
|
|
||||||
observations.take();
|
|
||||||
}
|
|
||||||
Subscription::WindowActivationObservation { observations, .. } => {
|
|
||||||
observations.take();
|
|
||||||
}
|
|
||||||
Subscription::WindowFullscreenObservation { observations, .. } => {
|
|
||||||
observations.take();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -5200,80 +5132,6 @@ impl Subscription {
|
||||||
impl Drop for Subscription {
|
impl Drop for Subscription {
|
||||||
fn drop(&mut self) {
|
fn drop(&mut self) {
|
||||||
match self {
|
match self {
|
||||||
Subscription::Subscription {
|
|
||||||
id,
|
|
||||||
entity_id,
|
|
||||||
subscriptions,
|
|
||||||
} => {
|
|
||||||
if let Some(subscriptions) = subscriptions.as_ref().and_then(Weak::upgrade) {
|
|
||||||
match subscriptions
|
|
||||||
.lock()
|
|
||||||
.entry(*entity_id)
|
|
||||||
.or_default()
|
|
||||||
.entry(*id)
|
|
||||||
{
|
|
||||||
btree_map::Entry::Vacant(entry) => {
|
|
||||||
entry.insert(None);
|
|
||||||
}
|
|
||||||
btree_map::Entry::Occupied(entry) => {
|
|
||||||
entry.remove();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Subscription::GlobalSubscription {
|
|
||||||
id,
|
|
||||||
type_id,
|
|
||||||
subscriptions,
|
|
||||||
} => {
|
|
||||||
if let Some(subscriptions) = subscriptions.as_ref().and_then(Weak::upgrade) {
|
|
||||||
match subscriptions.lock().entry(*type_id).or_default().entry(*id) {
|
|
||||||
btree_map::Entry::Vacant(entry) => {
|
|
||||||
entry.insert(None);
|
|
||||||
}
|
|
||||||
btree_map::Entry::Occupied(entry) => {
|
|
||||||
entry.remove();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Subscription::Observation {
|
|
||||||
id,
|
|
||||||
entity_id,
|
|
||||||
observations,
|
|
||||||
} => {
|
|
||||||
if let Some(observations) = observations.as_ref().and_then(Weak::upgrade) {
|
|
||||||
match observations
|
|
||||||
.lock()
|
|
||||||
.entry(*entity_id)
|
|
||||||
.or_default()
|
|
||||||
.entry(*id)
|
|
||||||
{
|
|
||||||
btree_map::Entry::Vacant(entry) => {
|
|
||||||
entry.insert(None);
|
|
||||||
}
|
|
||||||
btree_map::Entry::Occupied(entry) => {
|
|
||||||
entry.remove();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
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 {
|
Subscription::ReleaseObservation {
|
||||||
id,
|
id,
|
||||||
entity_id,
|
entity_id,
|
||||||
|
@ -5285,90 +5143,12 @@ impl Drop for Subscription {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Subscription::FocusObservation {
|
|
||||||
id,
|
|
||||||
view_id,
|
|
||||||
observations,
|
|
||||||
} => {
|
|
||||||
if let Some(observations) = observations.as_ref().and_then(Weak::upgrade) {
|
|
||||||
match observations.lock().entry(*view_id).or_default().entry(*id) {
|
|
||||||
btree_map::Entry::Vacant(entry) => {
|
|
||||||
entry.insert(None);
|
|
||||||
}
|
|
||||||
btree_map::Entry::Occupied(entry) => {
|
|
||||||
entry.remove();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Subscription::ActionObservation { id, observations } => {
|
Subscription::ActionObservation { id, observations } => {
|
||||||
if let Some(observations) = observations.as_ref().and_then(Weak::upgrade) {
|
if let Some(observations) = observations.as_ref().and_then(Weak::upgrade) {
|
||||||
observations.lock().remove(id);
|
observations.lock().remove(id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Subscription::KeystrokeObservation {
|
_ => {}
|
||||||
id,
|
|
||||||
window_id,
|
|
||||||
observations,
|
|
||||||
} => {
|
|
||||||
if let Some(observations) = observations.as_ref().and_then(Weak::upgrade) {
|
|
||||||
match observations
|
|
||||||
.lock()
|
|
||||||
.entry(*window_id)
|
|
||||||
.or_default()
|
|
||||||
.entry(*id)
|
|
||||||
{
|
|
||||||
btree_map::Entry::Vacant(entry) => {
|
|
||||||
entry.insert(None);
|
|
||||||
}
|
|
||||||
btree_map::Entry::Occupied(entry) => {
|
|
||||||
entry.remove();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Subscription::WindowActivationObservation {
|
|
||||||
id,
|
|
||||||
window_id,
|
|
||||||
observations,
|
|
||||||
} => {
|
|
||||||
if let Some(observations) = observations.as_ref().and_then(Weak::upgrade) {
|
|
||||||
match observations
|
|
||||||
.lock()
|
|
||||||
.entry(*window_id)
|
|
||||||
.or_default()
|
|
||||||
.entry(*id)
|
|
||||||
{
|
|
||||||
btree_map::Entry::Vacant(entry) => {
|
|
||||||
entry.insert(None);
|
|
||||||
}
|
|
||||||
btree_map::Entry::Occupied(entry) => {
|
|
||||||
entry.remove();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Subscription::WindowFullscreenObservation {
|
|
||||||
id,
|
|
||||||
window_id,
|
|
||||||
observations,
|
|
||||||
} => {
|
|
||||||
if let Some(observations) = observations.as_ref().and_then(Weak::upgrade) {
|
|
||||||
match observations
|
|
||||||
.lock()
|
|
||||||
.entry(*window_id)
|
|
||||||
.or_default()
|
|
||||||
.entry(*id)
|
|
||||||
{
|
|
||||||
btree_map::Entry::Vacant(entry) => {
|
|
||||||
entry.insert(None);
|
|
||||||
}
|
|
||||||
btree_map::Entry::Occupied(entry) => {
|
|
||||||
entry.remove();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,19 +1,70 @@
|
||||||
|
use std::mem;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use std::{hash::Hash, sync::Weak};
|
use std::{hash::Hash, sync::Weak};
|
||||||
|
|
||||||
use parking_lot::Mutex;
|
use parking_lot::Mutex;
|
||||||
|
|
||||||
use collections::{btree_map, BTreeMap, HashMap};
|
use collections::{btree_map, BTreeMap, HashMap, HashSet};
|
||||||
|
|
||||||
use crate::MutableAppContext;
|
use crate::MutableAppContext;
|
||||||
|
|
||||||
pub type Mapping<K, F> = Mutex<HashMap<K, BTreeMap<usize, Option<F>>>>;
|
// Problem 5: Current bug callbacks currently called many times after being dropped
|
||||||
|
// update
|
||||||
|
// notify
|
||||||
|
// observe (push effect)
|
||||||
|
// subscription {id : 5}
|
||||||
|
// pending: [Effect::Notify, Effect::observe { id: 5 }]
|
||||||
|
// drop observation subscription (write None into subscriptions)
|
||||||
|
// flush effects
|
||||||
|
// notify
|
||||||
|
// observe
|
||||||
|
// Problem 6: Key-value pair is leaked if you drop a callback while calling it, and then never call that set of callbacks again
|
||||||
|
// -----------------
|
||||||
|
// Problem 1: Many very similar subscription enum variants
|
||||||
|
// Problem 2: Subscriptions and CallbackCollections use a shared mutex to update the callback status
|
||||||
|
// Problem 3: Current implementation is error prone with regard to uninitialized callbacks or dropping during callback
|
||||||
|
// Problem 4: Calling callbacks requires removing all of them from the list and adding them back
|
||||||
|
|
||||||
pub struct CallbackCollection<K: Hash + Eq, F> {
|
// Solution 1 CallbackState:
|
||||||
internal: Arc<Mapping<K, F>>,
|
// Add more state to the CallbackCollection map to communicate dropped and initialized status
|
||||||
|
// Solves: P5
|
||||||
|
// Solution 2 DroppedSubscriptionList:
|
||||||
|
// Store a parallel set of dropped subscriptions in the Mapping which stores the key and subscription id for all dropped subscriptions
|
||||||
|
// which can be
|
||||||
|
// Solution 3 GarbageCollection:
|
||||||
|
// Use some type of traditional garbage collection to handle dropping of callbacks
|
||||||
|
// atomic flag per callback which is looped over in remove dropped entities
|
||||||
|
|
||||||
|
// TODO:
|
||||||
|
// - Move subscription id counter to CallbackCollection
|
||||||
|
// - Consider adding a reverse map in Mapping from subscription id to key so that the dropped subscriptions
|
||||||
|
// can be a hashset of usize and the Subscription doesn't need the key
|
||||||
|
// - Investigate why the remaining two types of callback lists can't use the same callback collection and subscriptions
|
||||||
|
pub struct Subscription<K: Clone + Hash + Eq, F> {
|
||||||
|
key: K,
|
||||||
|
id: usize,
|
||||||
|
mapping: Option<Weak<Mutex<Mapping<K, F>>>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<K: Hash + Eq, F> Clone for CallbackCollection<K, F> {
|
struct Mapping<K, F> {
|
||||||
|
callbacks: HashMap<K, BTreeMap<usize, F>>,
|
||||||
|
dropped_subscriptions: HashSet<(K, usize)>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<K, F> Default for Mapping<K, F> {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
callbacks: Default::default(),
|
||||||
|
dropped_subscriptions: Default::default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) struct CallbackCollection<K: Clone + Hash + Eq, F> {
|
||||||
|
internal: Arc<Mutex<Mapping<K, F>>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<K: Clone + Hash + Eq, F> Clone for CallbackCollection<K, F> {
|
||||||
fn clone(&self) -> Self {
|
fn clone(&self) -> Self {
|
||||||
Self {
|
Self {
|
||||||
internal: self.internal.clone(),
|
internal: self.internal.clone(),
|
||||||
|
@ -21,7 +72,7 @@ impl<K: Hash + Eq, F> Clone for CallbackCollection<K, F> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<K: Hash + Eq + Copy, F> Default for CallbackCollection<K, F> {
|
impl<K: Clone + Hash + Eq + Copy, F> Default for CallbackCollection<K, F> {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
CallbackCollection {
|
CallbackCollection {
|
||||||
internal: Arc::new(Mutex::new(Default::default())),
|
internal: Arc::new(Mutex::new(Default::default())),
|
||||||
|
@ -29,75 +80,101 @@ impl<K: Hash + Eq + Copy, F> Default for CallbackCollection<K, F> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<K: Hash + Eq + Copy, F> CallbackCollection<K, F> {
|
impl<K: Clone + Hash + Eq + Copy, F> CallbackCollection<K, F> {
|
||||||
pub fn downgrade(&self) -> Weak<Mapping<K, F>> {
|
|
||||||
Arc::downgrade(&self.internal)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
pub fn is_empty(&self) -> bool {
|
pub fn is_empty(&self) -> bool {
|
||||||
self.internal.lock().is_empty()
|
self.internal.lock().callbacks.is_empty()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn add_callback(&mut self, id: K, subscription_id: usize, callback: F) {
|
pub fn subscribe(&mut self, key: K, subscription_id: usize) -> Subscription<K, F> {
|
||||||
|
Subscription {
|
||||||
|
key,
|
||||||
|
id: subscription_id,
|
||||||
|
mapping: Some(Arc::downgrade(&self.internal)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn count(&mut self, key: K) -> usize {
|
||||||
self.internal
|
self.internal
|
||||||
.lock()
|
.lock()
|
||||||
.entry(id)
|
.callbacks
|
||||||
.or_default()
|
.get(&key)
|
||||||
.insert(subscription_id, Some(callback));
|
.map_or(0, |callbacks| callbacks.len())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn remove(&mut self, id: K) {
|
pub fn add_callback(&mut self, key: K, subscription_id: usize, callback: F) {
|
||||||
self.internal.lock().remove(&id);
|
let mut this = self.internal.lock();
|
||||||
|
if !this.dropped_subscriptions.contains(&(key, subscription_id)) {
|
||||||
|
this.callbacks
|
||||||
|
.entry(key)
|
||||||
|
.or_default()
|
||||||
|
.insert(subscription_id, callback);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn add_or_remove_callback(&mut self, id: K, subscription_id: usize, callback: F) {
|
pub fn remove(&mut self, key: K) {
|
||||||
match self
|
self.internal.lock().callbacks.remove(&key);
|
||||||
.internal
|
}
|
||||||
.lock()
|
|
||||||
.entry(id)
|
|
||||||
.or_default()
|
|
||||||
.entry(subscription_id)
|
|
||||||
{
|
|
||||||
btree_map::Entry::Vacant(entry) => {
|
|
||||||
entry.insert(Some(callback));
|
|
||||||
}
|
|
||||||
|
|
||||||
btree_map::Entry::Occupied(entry) => {
|
pub fn emit<C: FnMut(&mut F, &mut MutableAppContext) -> bool>(
|
||||||
// TODO: This seems like it should never be called because no code
|
&mut self,
|
||||||
// should ever attempt to remove an existing callback
|
key: K,
|
||||||
debug_assert!(entry.get().is_none());
|
cx: &mut MutableAppContext,
|
||||||
entry.remove();
|
mut call_callback: C,
|
||||||
|
) {
|
||||||
|
let callbacks = self.internal.lock().callbacks.remove(&key);
|
||||||
|
if let Some(callbacks) = callbacks {
|
||||||
|
for (subscription_id, mut callback) in callbacks {
|
||||||
|
if !self
|
||||||
|
.internal
|
||||||
|
.lock()
|
||||||
|
.dropped_subscriptions
|
||||||
|
.contains(&(key, subscription_id))
|
||||||
|
{
|
||||||
|
if call_callback(&mut callback, cx) {
|
||||||
|
self.add_callback(key, subscription_id, callback);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn emit_and_cleanup<C: FnMut(&mut F, &mut MutableAppContext) -> bool>(
|
pub fn gc(&mut self) {
|
||||||
&mut self,
|
let mut this = self.internal.lock();
|
||||||
id: K,
|
|
||||||
cx: &mut MutableAppContext,
|
for (key, id) in mem::take(&mut this.dropped_subscriptions) {
|
||||||
mut call_callback: C,
|
if let Some(callbacks) = this.callbacks.get_mut(&key) {
|
||||||
) {
|
callbacks.remove(&id);
|
||||||
let callbacks = self.internal.lock().remove(&id);
|
}
|
||||||
if let Some(callbacks) = callbacks {
|
}
|
||||||
for (subscription_id, callback) in callbacks {
|
}
|
||||||
if let Some(mut callback) = callback {
|
}
|
||||||
let alive = call_callback(&mut callback, cx);
|
|
||||||
if alive {
|
impl<K: Clone + Hash + Eq, F> Subscription<K, F> {
|
||||||
match self
|
pub fn detach(&mut self) {
|
||||||
.internal
|
self.mapping.take();
|
||||||
.lock()
|
}
|
||||||
.entry(id)
|
}
|
||||||
.or_default()
|
|
||||||
.entry(subscription_id)
|
impl<K: Clone + Hash + Eq, F> Drop for Subscription<K, F> {
|
||||||
{
|
// If the callback has been initialized (no callback in the list for the key and id),
|
||||||
btree_map::Entry::Vacant(entry) => {
|
// add this subscription id and key to the dropped subscriptions list
|
||||||
entry.insert(Some(callback));
|
// Otherwise, just remove the associated callback from the callback collection
|
||||||
}
|
fn drop(&mut self) {
|
||||||
btree_map::Entry::Occupied(entry) => {
|
if let Some(mapping) = self.mapping.as_ref().and_then(|mapping| mapping.upgrade()) {
|
||||||
entry.remove();
|
let mut mapping = mapping.lock();
|
||||||
}
|
if let Some(callbacks) = mapping.callbacks.get_mut(&self.key) {
|
||||||
}
|
match callbacks.entry(self.id) {
|
||||||
|
btree_map::Entry::Vacant(_) => {
|
||||||
|
mapping
|
||||||
|
.dropped_subscriptions
|
||||||
|
.insert((self.key.clone(), self.id));
|
||||||
|
}
|
||||||
|
btree_map::Entry::Occupied(entry) => {
|
||||||
|
entry.remove();
|
||||||
|
mapping
|
||||||
|
.dropped_subscriptions
|
||||||
|
.insert((self.key.clone(), self.id));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue