mirror of
https://github.com/danbulant/cushy
synced 2026-07-06 19:50:40 +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.
|
that can be deallocated independently of other graphs it is connected with.
|
||||||
- `DynamicReader::on_disconnect` allows attaching a callback that is invoked
|
- `DynamicReader::on_disconnect` allows attaching a callback that is invoked
|
||||||
once the final source `Dynamic` is dropped.
|
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
|
[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 alot::OrderedLots;
|
||||||
|
|
||||||
use crate::value::{Dynamic, DynamicReader, ForEach};
|
use crate::value::{Dynamic, DynamicReader, ForEach, WeakDynamic};
|
||||||
use crate::widget::{Children, MakeWidget, WidgetInstance};
|
use crate::widget::{Children, MakeWidget, WidgetInstance};
|
||||||
use crate::widgets::grid::{Grid, GridWidgets};
|
use crate::widgets::grid::{Grid, GridWidgets};
|
||||||
use crate::window::Window;
|
use crate::window::Window;
|
||||||
|
|
@ -83,6 +83,15 @@ impl DebugContext {
|
||||||
.into_window()
|
.into_window()
|
||||||
.titled("Cushy Debugger")
|
.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 {
|
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 {
|
trait Observable: Send {
|
||||||
fn label(&self) -> &str;
|
fn label(&self) -> &str;
|
||||||
fn alive(&self) -> bool;
|
fn alive(&self) -> bool;
|
||||||
|
|
@ -132,6 +157,7 @@ struct DebugSection {
|
||||||
children: Dynamic<OrderedLots<Dynamic<DebugSection>>>,
|
children: Dynamic<OrderedLots<Dynamic<DebugSection>>>,
|
||||||
values: Dynamic<OrderedLots<Box<dyn Observable>>>,
|
values: Dynamic<OrderedLots<Box<dyn Observable>>>,
|
||||||
widget: WidgetInstance,
|
widget: WidgetInstance,
|
||||||
|
parent: Option<WeakDynamic<DebugSection>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for DebugSection {
|
impl Default for DebugSection {
|
||||||
|
|
@ -162,23 +188,13 @@ impl DebugSection {
|
||||||
|
|
||||||
let parent = parent.map(Dynamic::downgrade);
|
let parent = parent.map(Dynamic::downgrade);
|
||||||
// Create a cleanup task to remove this section once it becomes empty.
|
// 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();
|
let label = label.clone();
|
||||||
(&children, &values)
|
(&children, &values)
|
||||||
.for_each({
|
.for_each({
|
||||||
move |(children, values)| {
|
move |(children, values)| {
|
||||||
if children.is_empty() && values.is_empty() {
|
if children.is_empty() && values.is_empty() {
|
||||||
if let Some(parent) = parent.upgrade() {
|
Self::remove_child_section(&parent, &label);
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
@ -204,6 +220,26 @@ impl DebugSection {
|
||||||
children,
|
children,
|
||||||
values,
|
values,
|
||||||
widget,
|
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)
|
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
|
/// Returns a new dynamic that has its contents linked with `self` by the
|
||||||
/// pair of mapping functions provided.
|
/// pair of mapping functions provided.
|
||||||
///
|
///
|
||||||
|
|
@ -2801,3 +2825,32 @@ fn compare_swap() {
|
||||||
assert_eq!(dynamic.compare_swap(&2, 0), Ok(2));
|
assert_eq!(dynamic.compare_swap(&2, 0), Ok(2));
|
||||||
assert_eq!(dynamic.get(), 0);
|
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