Drop the refcount a widget handle can never raise

StrongWidget has no Clone on purpose, so the RefCounter in it never counted
anything: the count stayed at zero, RefCounter::drop always answered true,
and StrongWidget::refs had no caller -- while every widget made paid a heap
allocation for an Arc<AtomicU32> and every one created or dropped paid two
atomic read-modify-writes. The handle is the id, the sender and the type now,
and Drop sends. It is 32 bytes rather than 40, and 40 rather than 48 for a
dyn one.

RefCounter stays for TextureHandle, which does clone -- several widgets
showing one picture share its slot -- and is trimmed to what that needs:
quiet_clone and refs had no callers at all, and new was Default spelt out.

Bryan asked for this in #19 rather than in the review of the code written
before the gate, where the fourteenth sweep had left it.
This commit is contained in:
iris-ai committed 2026-09-21 00:16:44 -04:00
1 parent 97fca76108
commit 429a4f1330
3 files changed
+11 -30

No files matched your search

+1 -1
View File
@@ -66,7 +66,7 @@ impl Textures {
TextureHandle {
slot: self.push(image),
size,
counter: RefCounter::new(),
counter: RefCounter::default(),
send: self.send.clone(),
}
}
+5 -16
View File
@@ -3,29 +3,18 @@ use std::sync::{
atomic::{AtomicU32, Ordering},
};
#[derive(Debug)]
/// How many handles to one thing there are, less one: a fresh counter is a
/// single handle, which is what the derived default gives. Only
/// [`crate::TextureHandle`] has any, a texture slot being shared by every
/// widget drawing that picture.
#[derive(Debug, Default)]
pub struct RefCounter(Arc<AtomicU32>);
impl RefCounter {
pub fn new() -> Self {
Self(Arc::new(0.into()))
}
pub fn refs(&self) -> u32 {
self.0.load(Ordering::Acquire)
}
pub fn drop(&mut self) -> bool {
let refs = self.0.fetch_sub(1, Ordering::Release);
refs == 0
}
pub fn quiet_clone(&self) -> Self {
Self(self.0.clone())
}
}
impl Default for RefCounter {
fn default() -> Self {
Self::new()
}
}
impl Clone for RefCounter {
+5 -13
View File
@@ -1,9 +1,6 @@
use std::{marker::Unsize, ops::CoerceUnsized, sync::mpsc::Sender};
use crate::{
UiRsc, Widget,
util::{RefCounter, SlotId},
};
use crate::{UiRsc, Widget, util::SlotId};
pub type WidgetId = SlotId;
@@ -11,10 +8,12 @@ pub type WidgetId = SlotId;
/// This is a strong handle that does not impl Clone, and when it is dropped,
/// a signal is sent to the owning UI to clean up the resources.
///
/// There is nothing to count: not being `Clone` is what makes one handle the
/// only one, so the drop is the last one and the widget is one container's.
///
/// TODO: ergonomic clones when they get put in rust-analyzer & don't cause ICEs?
pub struct StrongWidget<W: ?Sized = dyn Widget> {
pub(super) id: WidgetId,
counter: RefCounter,
send: Sender<WidgetId>,
ty: *const W,
}
@@ -37,7 +36,6 @@ impl<W: ?Sized> StrongWidget<W> {
pub(crate) fn new(id: WidgetId, send: Sender<WidgetId>) -> Self {
Self {
id,
counter: RefCounter::new(),
send,
ty: null_ptr(),
}
@@ -47,10 +45,6 @@ impl<W: ?Sized> StrongWidget<W> {
self.id
}
pub fn refs(&self) -> u32 {
self.counter.refs()
}
pub fn weak(&self) -> WeakWidget<W> {
let Self { ty, id, .. } = *self;
WeakWidget { ty, id }
@@ -74,9 +68,7 @@ impl<W: ?Sized> WeakWidget<W> {
impl<W: ?Sized> Drop for StrongWidget<W> {
fn drop(&mut self) {
if self.counter.drop() {
let _ = self.send.send(self.id);
}
let _ = self.send.send(self.id);
}
}