mirror of
https://github.com/danbulant/cushy
synced 2026-06-18 22:11:34 +00:00
Empty debug contexts are automatically cleaned up
This commit is contained in:
parent
3fc49d2424
commit
999f920f8c
3 changed files with 106 additions and 13 deletions
|
|
@ -39,6 +39,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||
that can be deallocated independently of other graphs it is connected with.
|
||||
- `DynamicReader::on_disconnect` allows attaching a callback that is invoked
|
||||
once the final source `Dynamic` is dropped.
|
||||
- `Dynamic::instances()` returns the number of clones the dynamic has in
|
||||
existence.
|
||||
- `Dynamic::readers()` returns the number of `DynamicReader`s for the dynamic in
|
||||
existence.
|
||||
|
||||
[99]: https://github.com/khonsulabs/cushy/issues/99
|
||||
|
||||
|
|
|
|||
62
src/debug.rs
62
src/debug.rs
|
|
@ -4,7 +4,7 @@ use std::fmt::Debug;
|
|||
|
||||
use alot::OrderedLots;
|
||||
|
||||
use crate::value::{Dynamic, DynamicReader, ForEach};
|
||||
use crate::value::{Dynamic, DynamicReader, ForEach, WeakDynamic};
|
||||
use crate::widget::{Children, MakeWidget, WidgetInstance};
|
||||
use crate::widgets::grid::{Grid, GridWidgets};
|
||||
use crate::window::Window;
|
||||
|
|
@ -83,6 +83,15 @@ impl DebugContext {
|
|||
.into_window()
|
||||
.titled("Cushy Debugger")
|
||||
}
|
||||
|
||||
/// Returns true if this debug context has no child sections or observed
|
||||
/// values.
|
||||
#[must_use]
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.section.map_ref(|section| {
|
||||
section.children.map_ref(OrderedLots::len) + section.values.map_ref(OrderedLots::len)
|
||||
}) == 0
|
||||
}
|
||||
}
|
||||
|
||||
impl Open for DebugContext {
|
||||
|
|
@ -98,6 +107,22 @@ impl Open for DebugContext {
|
|||
}
|
||||
}
|
||||
|
||||
impl Drop for DebugContext {
|
||||
fn drop(&mut self) {
|
||||
// If the only two references are this context and the parent owning the
|
||||
// child section, then we want to remove the section if nothing was
|
||||
// added to it.
|
||||
if self.section.instances() == 2 {
|
||||
let section = self.section.lock();
|
||||
if let Some(parent) = section.parent.clone() {
|
||||
let label = section.label.clone();
|
||||
drop(section);
|
||||
DebugSection::remove_child_section(&parent, &label);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
trait Observable: Send {
|
||||
fn label(&self) -> &str;
|
||||
fn alive(&self) -> bool;
|
||||
|
|
@ -132,6 +157,7 @@ struct DebugSection {
|
|||
children: Dynamic<OrderedLots<Dynamic<DebugSection>>>,
|
||||
values: Dynamic<OrderedLots<Box<dyn Observable>>>,
|
||||
widget: WidgetInstance,
|
||||
parent: Option<WeakDynamic<DebugSection>>,
|
||||
}
|
||||
|
||||
impl Default for DebugSection {
|
||||
|
|
@ -162,23 +188,13 @@ impl DebugSection {
|
|||
|
||||
let parent = parent.map(Dynamic::downgrade);
|
||||
// Create a cleanup task to remove this section once it becomes empty.
|
||||
if let Some(parent) = parent {
|
||||
if let Some(parent) = parent.clone() {
|
||||
let label = label.clone();
|
||||
(&children, &values)
|
||||
.for_each({
|
||||
move |(children, values)| {
|
||||
if children.is_empty() && values.is_empty() {
|
||||
if let Some(parent) = parent.upgrade() {
|
||||
let parent = parent.lock();
|
||||
let mut children = parent.children.lock();
|
||||
if let Some(index) =
|
||||
children.iter().enumerate().find_map(|(index, child)| {
|
||||
child.map_ref(|child| child.label == label).then_some(index)
|
||||
})
|
||||
{
|
||||
children.remove_by_index(index);
|
||||
}
|
||||
}
|
||||
Self::remove_child_section(&parent, &label);
|
||||
}
|
||||
}
|
||||
})
|
||||
|
|
@ -204,6 +220,26 @@ impl DebugSection {
|
|||
children,
|
||||
values,
|
||||
widget,
|
||||
parent,
|
||||
}
|
||||
}
|
||||
|
||||
fn remove_child_section(parent: &WeakDynamic<DebugSection>, label: &str) {
|
||||
if let Some(parent) = parent.upgrade() {
|
||||
let parent = parent.lock();
|
||||
let mut children = parent.children.lock();
|
||||
if let Some(index) = children.iter().enumerate().find_map(|(index, child)| {
|
||||
child.map_ref(|child| child.label == label).then_some(index)
|
||||
}) {
|
||||
children.remove_by_index(index);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn empty_child_clears_on_drop() {
|
||||
let root = DebugContext::default();
|
||||
drop(root.section("child"));
|
||||
assert!(root.is_empty());
|
||||
}
|
||||
|
|
|
|||
53
src/value.rs
53
src/value.rs
|
|
@ -60,6 +60,30 @@ impl<T> Dynamic<T> {
|
|||
WeakDynamic::from(self)
|
||||
}
|
||||
|
||||
/// Returns the number [`Dynamic`]s that point to this same value.
|
||||
///
|
||||
/// The returned count includes `self`.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// This function panics if this value is already locked by the current
|
||||
/// thread.
|
||||
#[must_use]
|
||||
pub fn instances(&self) -> usize {
|
||||
Arc::strong_count(&self.0) - self.readers()
|
||||
}
|
||||
|
||||
/// Returns the number of [`DynamicReader`]s for this value.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// This function panics if this value is already locked by the current
|
||||
/// thread.
|
||||
#[must_use]
|
||||
pub fn readers(&self) -> usize {
|
||||
self.state().expect("deadlocked").readers
|
||||
}
|
||||
|
||||
/// Returns a new dynamic that has its contents linked with `self` by the
|
||||
/// pair of mapping functions provided.
|
||||
///
|
||||
|
|
@ -2801,3 +2825,32 @@ fn compare_swap() {
|
|||
assert_eq!(dynamic.compare_swap(&2, 0), Ok(2));
|
||||
assert_eq!(dynamic.get(), 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ref_counts() {
|
||||
let dynamic = Dynamic::new(1);
|
||||
assert_eq!(dynamic.instances(), 1);
|
||||
|
||||
let second = dynamic.clone();
|
||||
assert_eq!(dynamic.instances(), 2);
|
||||
|
||||
assert_eq!(dynamic.readers(), 0);
|
||||
let reader = second.into_reader();
|
||||
assert_eq!(dynamic.instances(), 1);
|
||||
assert_eq!(dynamic.readers(), 1);
|
||||
|
||||
// Test that once the last instance is dropped that the reader is no longer
|
||||
// connected and that on_disconnect gets invoked.
|
||||
assert!(reader.connected());
|
||||
let invoked = Dynamic::new(false);
|
||||
reader.on_disconnect({
|
||||
let invoked = invoked.clone();
|
||||
move || {
|
||||
invoked.set(true);
|
||||
}
|
||||
});
|
||||
drop(dynamic);
|
||||
|
||||
assert!(invoked.get());
|
||||
assert!(!reader.connected());
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue