switch away from handles to refs that must be upgraded once

This commit is contained in:
2025-12-20 01:36:07 -05:00
parent 32ca4ec5a6
commit c00ded78c0
11 changed files with 157 additions and 85 deletions

View File

@@ -18,7 +18,7 @@ impl<State: HasUi + StateLike<State>, WL: WidgetLike<State, Tag>, Tag>
) -> impl WidgetIdFn<State, WL::Widget> {
|state| {
let id = self.add(state);
A::run(state, id.weak(), input);
A::run(state, id, input);
id
}
}

View File

@@ -7,7 +7,7 @@ use crate::{
use image::DynamicImage;
use std::{
ops::{Index, IndexMut},
sync::mpsc::{Receiver, Sender, channel},
sync::mpsc::{Receiver, channel},
};
mod active;
@@ -38,7 +38,6 @@ pub struct Ui {
pub root: Option<WidgetHandle>,
old_root: Option<WidgetId>,
recv: Receiver<WidgetId>,
send: Sender<WidgetId>,
resized: bool,
}
@@ -73,24 +72,20 @@ impl Ui {
&self.widgets.data(id.id()).unwrap().label
}
pub fn add_widget<W: Widget>(&mut self, w: W) -> WidgetHandle<W> {
WidgetHandle::new(self.widgets.add(w), self.send.clone())
}
pub fn new() -> Self {
Self::default()
}
pub fn get<I: IdLike>(&self, id: &I) -> Option<&I::Widget>
where
I::Widget: Sized,
I::Widget: Sized + Widget,
{
self.widgets.get(id)
}
pub fn get_mut<I: IdLike>(&mut self, id: &I) -> Option<&mut I::Widget>
where
I::Widget: Sized,
I::Widget: Sized + Widget,
{
self.widgets.get_mut(id)
}
@@ -105,6 +100,20 @@ impl Ui {
}
pub fn update(&mut self, events: &mut dyn EventsLike) {
if !self.widgets.waiting.is_empty() {
let len = self.widgets.waiting.len();
let all: Vec<_> = self
.widgets
.waiting
.iter()
.map(|&w| format!("'{}' ({w:?})", self.label(w)))
.collect();
panic!(
"{len} widget(s) were never upgraded\n\
this is likely a memory leak; consider upgrading to strong if you plan on using it later\n\
weak widgets: {all:#?}"
);
}
if self.root_changed() {
DrawState::new(self, events).redraw_all();
self.old_root = self.root.as_ref().map(|r| r.id());
@@ -169,7 +178,7 @@ impl Ui {
impl<I: IdLike> Index<I> for Ui
where
I::Widget: Sized,
I::Widget: Sized + Widget,
{
type Output = I::Widget;
@@ -180,7 +189,7 @@ where
impl<I: IdLike> IndexMut<I> for Ui
where
I::Widget: Sized,
I::Widget: Sized + Widget,
{
fn index_mut(&mut self, id: I) -> &mut Self::Output {
self.get_mut(&id).unwrap()
@@ -191,7 +200,7 @@ impl Default for Ui {
fn default() -> Self {
let (send, recv) = channel();
Self {
widgets: Default::default(),
widgets: Widgets::new(send),
active: Default::default(),
layers: Default::default(),
masks: Default::default(),
@@ -201,7 +210,6 @@ impl Default for Ui {
output_size: Vec2::ZERO,
root: None,
old_root: None,
send,
recv,
resized: false,
}

View File

@@ -68,9 +68,21 @@ impl<W: ?Sized> WidgetHandle<W> {
}
impl<W: ?Sized> WidgetRef<W> {
pub(crate) fn new(id: WidgetId) -> Self {
Self {
id,
ty: unsafe { MaybeUninit::zeroed().assume_init() },
}
}
pub fn id(&self) -> WidgetId {
self.id
}
#[track_caller]
pub fn upgrade(self, ui: &mut impl HasUi) -> WidgetHandle<W> {
ui.ui_mut().widgets.upgrade(self)
}
}
impl<W: ?Sized> Drop for WidgetHandle<W> {
@@ -81,29 +93,29 @@ impl<W: ?Sized> Drop for WidgetHandle<W> {
}
}
pub trait WidgetIdFn<State, W: ?Sized = dyn Widget>: FnOnce(&mut State) -> WidgetHandle<W> {}
impl<State, W: ?Sized, F: FnOnce(&mut State) -> WidgetHandle<W>> WidgetIdFn<State, W> for F {}
pub trait WidgetIdFn<State, W: ?Sized = dyn Widget>: FnOnce(&mut State) -> WidgetRef<W> {}
impl<State, W: ?Sized, F: FnOnce(&mut State) -> WidgetRef<W>> WidgetIdFn<State, W> for F {}
pub trait IdLike {
type Widget: Widget + ?Sized + 'static;
type Widget: ?Sized;
fn id(&self) -> WidgetId;
}
impl<W: Widget + ?Sized> IdLike for &WidgetHandle<W> {
impl<W: ?Sized> IdLike for &WidgetHandle<W> {
type Widget = W;
fn id(&self) -> WidgetId {
self.id
}
}
impl<W: Widget + ?Sized> IdLike for WidgetHandle<W> {
impl<W: ?Sized> IdLike for WidgetHandle<W> {
type Widget = W;
fn id(&self) -> WidgetId {
self.id
}
}
impl<W: Widget + ?Sized> IdLike for WidgetRef<W> {
impl<W: ?Sized> IdLike for WidgetRef<W> {
type Widget = W;
fn id(&self) -> WidgetId {
self.id

View File

@@ -16,11 +16,15 @@ impl StateLike<Ui> for Ui {
pub trait WidgetLike<State: HasUi + StateLike<State>, Tag>: Sized {
type Widget: Widget + ?Sized + Unsize<dyn Widget>;
fn add(self, state: &mut impl StateLike<State>) -> WidgetHandle<Self::Widget>;
fn add(self, state: &mut impl StateLike<State>) -> WidgetRef<Self::Widget>;
fn add_strong(self, state: &mut impl StateLike<State>) -> WidgetHandle<Self::Widget> {
self.add(state).upgrade(state.as_state().ui_mut())
}
fn with_id<W2>(
self,
f: impl FnOnce(&mut State, WidgetHandle<Self::Widget>) -> WidgetHandle<W2>,
f: impl FnOnce(&mut State, WidgetRef<Self::Widget>) -> WidgetRef<W2>,
) -> impl WidgetIdFn<State, W2> {
move |state| {
let id = self.add(state);
@@ -29,15 +33,18 @@ pub trait WidgetLike<State: HasUi + StateLike<State>, Tag>: Sized {
}
fn set_root(self, state: &mut impl StateLike<State>) {
state.as_state().get_mut().root = Some(self.add(state));
let id = self.add(state);
let ui = state.as_state().ui_mut();
ui.root = Some(id.upgrade(ui));
}
fn handles(self, state: &mut impl StateLike<State>) -> WidgetHandles<Self::Widget> {
self.add(state).handles()
self.add(state).upgrade(state.as_state().ui_mut()).handles()
}
}
pub trait WidgetArrLike<State, const LEN: usize, Tag> {
#[track_caller]
fn add(self, state: &mut impl StateLike<State>) -> WidgetArr<LEN>;
}
@@ -58,7 +65,7 @@ macro_rules! impl_widget_arr {
#[allow(non_snake_case)]
let ($($W,)*) = self;
WidgetArr::new(
[$($W.add(state),)*],
[$($W.add(state).upgrade(state.as_state().ui_mut()),)*],
)
}
}

View File

@@ -5,8 +5,8 @@ use std::marker::Unsize;
pub struct WidgetTag;
impl<State: HasUi + StateLike<State>, W: Widget> WidgetLike<State, WidgetTag> for W {
type Widget = W;
fn add(self, state: &mut impl StateLike<State>) -> WidgetHandle<W> {
state.as_state().get_mut().add_widget(self)
fn add(self, state: &mut impl StateLike<State>) -> WidgetRef<W> {
state.as_state().get_mut().widgets.add_weak(self)
}
}
@@ -15,17 +15,30 @@ impl<State: HasUi + StateLike<State>, W: Widget, F: FnOnce(&mut State) -> W>
WidgetLike<State, FnTag> for F
{
type Widget = W;
fn add(self, state: &mut impl StateLike<State>) -> WidgetHandle<W> {
fn add(self, state: &mut impl StateLike<State>) -> WidgetRef<W> {
self(state.as_state()).add(state)
}
}
pub trait WidgetFnTrait<State> {
type Widget: Widget;
fn run(self, state: &mut State) -> Self::Widget;
}
pub struct FnTraitTag;
impl<State: HasUi + StateLike<State>, T: WidgetFnTrait<State>> WidgetLike<State, FnTraitTag> for T {
type Widget = T::Widget;
#[track_caller]
fn add(self, state: &mut impl StateLike<State>) -> WidgetRef<T::Widget> {
self.run(state.as_state()).add(state)
}
}
pub struct IdTag;
impl<State: HasUi + StateLike<State>, W: ?Sized + Widget + Unsize<dyn Widget>>
WidgetLike<State, IdTag> for WidgetHandle<W>
WidgetLike<State, IdTag> for WidgetRef<W>
{
type Widget = W;
fn add(self, _: &mut impl StateLike<State>) -> WidgetHandle<W> {
fn add(self, _: &mut impl StateLike<State>) -> WidgetRef<W> {
self
}
}
@@ -34,11 +47,11 @@ pub struct IdFnTag;
impl<
State: HasUi + StateLike<State>,
W: ?Sized + Widget + Unsize<dyn Widget>,
F: FnOnce(&mut State) -> WidgetHandle<W>,
F: FnOnce(&mut State) -> WidgetRef<W>,
> WidgetLike<State, IdFnTag> for F
{
type Widget = W;
fn add(self, state: &mut impl StateLike<State>) -> WidgetHandle<W> {
fn add(self, state: &mut impl StateLike<State>) -> WidgetRef<W> {
self(state.as_state())
}
}

View File

@@ -1,15 +1,27 @@
use std::sync::mpsc::Sender;
use crate::{
IdLike, Widget, WidgetData, WidgetId,
IdLike, Widget, WidgetData, WidgetHandle, WidgetId, WidgetRef,
util::{DynBorrower, HashSet, SlotVec, forget_mut, to_mut},
};
#[derive(Default)]
pub struct Widgets {
pub needs_redraw: HashSet<WidgetId>,
vec: SlotVec<WidgetData>,
send: Sender<WidgetId>,
pub(crate) waiting: HashSet<WidgetId>,
}
impl Widgets {
pub fn new(send: Sender<WidgetId>) -> Self {
Self {
needs_redraw: Default::default(),
vec: Default::default(),
waiting: Default::default(),
send,
}
}
pub fn has_updates(&self) -> bool {
!self.needs_redraw.is_empty()
}
@@ -37,20 +49,37 @@ impl Widgets {
pub fn get<I: IdLike>(&self, id: &I) -> Option<&I::Widget>
where
I::Widget: Sized,
I::Widget: Sized + Widget,
{
self.get_dyn(id.id())?.as_any().downcast_ref()
}
pub fn get_mut<I: IdLike>(&mut self, id: &I) -> Option<&mut I::Widget>
where
I::Widget: Sized,
I::Widget: Sized + Widget,
{
self.get_dyn_mut(id.id())?.as_any_mut().downcast_mut()
}
pub fn add<W: Widget>(&mut self, widget: W) -> WidgetId {
self.vec.add(WidgetData::new(widget))
pub fn add_strong<W: Widget>(&mut self, widget: W) -> WidgetHandle<W> {
let id = self.vec.add(WidgetData::new(widget));
WidgetHandle::new(id, self.send.clone())
}
pub fn add_weak<W: Widget>(&mut self, widget: W) -> WidgetRef<W> {
let id = self.vec.add(WidgetData::new(widget));
self.waiting.insert(id);
WidgetRef::new(id)
}
#[track_caller]
pub fn upgrade<W: ?Sized>(&mut self, rf: WidgetRef<W>) -> WidgetHandle<W> {
if !self.waiting.remove(&rf.id()) {
let label = self.label(rf);
let id = rf.id();
panic!("widget '{label}' ({id:?}) was already added\ncannot add a widget twice; consider creating two")
}
WidgetHandle::new(rf.id(), self.send.clone())
}
pub fn data(&self, id: impl IdLike) -> Option<&WidgetData> {