diff --git a/examples/theme.rs b/examples/theme.rs index 0a81a4f..3e8d112 100644 --- a/examples/theme.rs +++ b/examples/theme.rs @@ -213,28 +213,26 @@ fn optional_editor(label: &str, color: &Dynamic) -> (Dynamic, } fn color_editor(color: &Dynamic) -> impl MakeWidget { - let hue = color.map_each(|color| color.hue.into_positive_degrees()); + let hue = color.map_each_cloned(|color| color.hue.into_positive_degrees()); hue.for_each_cloned({ let color = color.clone(); move |hue| { - if let Ok(mut source) = color.try_get() { - source.hue = OklabHue::new(hue); - color.set(source); - } + let mut source = color.get(); + source.hue = OklabHue::new(hue); + color.set(source); } }) .persist(); let hue_text = hue.linked_string(); - let saturation = color.map_each(|color| color.saturation); + let saturation = color.map_each_cloned(|color| color.saturation); saturation .for_each_cloned({ let color = color.clone(); move |saturation| { - if let Ok(mut source) = color.try_get() { - source.saturation = saturation; - color.set(source); - } + let mut source = color.get(); + source.saturation = saturation; + color.set(source); } }) .persist(); diff --git a/src/debug.rs b/src/debug.rs index 95c3052..a8896d8 100644 --- a/src/debug.rs +++ b/src/debug.rs @@ -53,7 +53,7 @@ impl DebugContext { section.values.lock().push(Box::new(RegisteredValue { label: label.into(), value: reader.clone(), - widget: make_observer(value.clone()).make_widget(), + widget: make_observer(value.weak_clone()).make_widget(), })) }); let this = self.clone(); diff --git a/src/value.rs b/src/value.rs index 0f7ead2..acece66 100644 --- a/src/value.rs +++ b/src/value.rs @@ -290,6 +290,30 @@ pub trait Source { mapped } + /// Returns a new [`Dynamic`] that contains a clone of each value from + /// `self`. + /// + /// The returned dynamic does not hold a strong reference to `self`, + /// ensuring that `self` can be cleaned up even if the returned dynamic + /// still exists. + fn weak_clone(&self) -> Dynamic + where + T: Clone + Send + 'static, + { + let mapped = Dynamic::new(self.get()); + let mapped_weak = mapped.downgrade(); + + mapped.set_source( + self.for_each_cloned_try(move |value| { + let mapped = mapped_weak.upgrade().ok_or(CallbackDisconnected)?; + *mapped.lock() = value.clone(); + Ok(()) + }) + .weak(), + ); + mapped + } + /// Returns a new dynamic that is updated using `U::from(T.clone())` each /// time `self` is updated. #[must_use] @@ -510,7 +534,7 @@ impl Source for Arc> { + 'static, { let this = WeakDynamic(Arc::downgrade(self)); - DynamicData::for_each(self, move || { + dynamic_for_each(self, move || { let this = this.upgrade().ok_or(CallbackDisconnected)?; this.map_generational(&mut for_each)?; Ok(()) @@ -523,7 +547,7 @@ impl Source for Arc> { F: FnMut(GenerationalValue) -> Result<(), CallbackDisconnected> + Send + 'static, { let this = WeakDynamic(Arc::downgrade(self)); - DynamicData::for_each(self, move || { + dynamic_for_each(self, move || { let this = this.upgrade().ok_or(CallbackDisconnected)?; if let Ok(value) = this.try_map_generational(GenerationalValue::clone) { @@ -726,6 +750,7 @@ impl Source for Owned { let mut callbacks = self.callbacks.active.lock().ignore_poison(); CallbackHandle(CallbackHandleInner::Single(CallbackHandleData { id: Some(callbacks.push(Box::new(for_each))), + owner: None, callbacks: self.callbacks.clone(), })) } @@ -944,9 +969,10 @@ impl Dynamic { Ok(()) })); - let t_weak = self.downgrade(); + // The linked dynamic holds a reference to the original, since it's + // being created from the original. + let t = self.clone(); self.set_source(r.for_each_try(move |r| { - let t = t_weak.upgrade().ok_or(CallbackDisconnected)?; if let Some(update) = r_into_t(r).into() { let _result = t.replace(update); } @@ -1388,18 +1414,20 @@ impl DynamicData { Ok(old) } +} - pub fn for_each(&self, map: F) -> CallbackHandle - where - F: for<'a> FnMut() -> Result<(), CallbackDisconnected> + Send + 'static, - { - let state = self.state().expect("deadlocked"); - let mut data = state.callbacks.callbacks.lock().ignore_poison(); - CallbackHandle(CallbackHandleInner::Single(CallbackHandleData { - id: Some(data.callbacks.push(Box::new(map))), - callbacks: state.callbacks.clone(), - })) - } +fn dynamic_for_each(this: &Arc>, map: F) -> CallbackHandle +where + F: for<'a> FnMut() -> Result<(), CallbackDisconnected> + Send + 'static, + T: Send + 'static, +{ + let state = this.state().expect("deadlocked"); + let mut data = state.callbacks.callbacks.lock().ignore_poison(); + CallbackHandle(CallbackHandleInner::Single(CallbackHandleData { + id: Some(data.callbacks.push(Box::new(map))), + owner: Some(this.clone()), + callbacks: state.callbacks.clone(), + })) } /// A callback function is no longer connected to its source. @@ -1467,8 +1495,12 @@ enum CallbackHandleInner { Multi(Vec), } +trait ReferencedDynamic: Sync + Send + 'static {} +impl ReferencedDynamic for T where T: Sync + Send + 'static {} + struct CallbackHandleData { id: Option, + owner: Option>, callbacks: Arc, } @@ -1508,6 +1540,33 @@ impl CallbackHandle { } } } + + /// Drops any references to owning [`Dynamic`]s associated with this + /// callback. + /// + /// This enables creating weak connections between callback graphs. + pub fn forget_owners(&mut self) { + match &mut self.0 { + CallbackHandleInner::None => {} + CallbackHandleInner::Single(handle) => { + handle.owner = None; + } + CallbackHandleInner::Multi(handles) => { + for handle in handles { + handle.owner = None; + } + } + } + } + + /// Drops any references to owning [`Dynamic`]s associated with this + /// callback, and returns self. + /// + /// This uses [`Self::forget_owners()`]. + pub fn weak(mut self) -> Self { + self.forget_owners(); + self + } } impl Eq for CallbackHandle {} @@ -1539,6 +1598,7 @@ impl Drop for CallbackHandleData { } } } + impl PartialEq for CallbackHandleData { fn eq(&self, other: &Self) -> bool { self.id == other.id && Arc::ptr_eq(&self.callbacks, &other.callbacks) @@ -2932,7 +2992,7 @@ macro_rules! impl_tuple_for_each_cloned { } }; ($self:ident $for_each:ident $handles:ident [] [$type:ident $field:tt $var:ident]) => { - $handles += $self.$field.for_each(move |field: &$type| $for_each((field.clone(),))); + $handles += $self.$field.for_each_cloned(move |field| $for_each((field,))); }; ($self:ident $for_each:ident $handles:ident [] [$($type:ident $field:tt $var:ident),+]) => { let $for_each = Arc::new(Mutex::new($for_each));