iris: share resource handle bookkeeping

This commit is contained in:
iris committed 2026-09-12 17:13:49 -04:00
1 parent 51719ed121
commit 1be6cc2248
9 files changed
+631 -217

No files matched your search

+26 -56
View File
@@ -1,10 +1,5 @@
use crate::util::{Dirty, RefCounter}; use crate::util::{Dirty, Resources, StrongRscId};
use std::{ use std::{cell::RefCell, fmt, rc::Rc};
cell::RefCell,
fmt,
rc::Rc,
sync::mpsc::{Receiver, Sender, channel},
};
/// Encoded, straight-alpha sRGB at an input boundary. /// Encoded, straight-alpha sRGB at an input boundary.
/// ///
@@ -167,30 +162,7 @@ mod private {
impl Sealed for super::PaintId {} impl Sealed for super::PaintId {}
} }
#[derive(Debug)] struct PaintRsc;
struct PaintLease {
slot: u32,
counter: RefCounter,
send: Sender<u32>,
}
impl Clone for PaintLease {
fn clone(&self) -> Self {
Self {
slot: self.slot,
counter: self.counter.clone(),
send: self.send.clone(),
}
}
}
impl Drop for PaintLease {
fn drop(&mut self) {
if self.counter.drop() {
let _ = self.send.send(self.slot);
}
}
}
/// A stable reference to one entry in a UI's paint table. /// A stable reference to one entry in a UI's paint table.
/// ///
@@ -200,7 +172,7 @@ impl Drop for PaintLease {
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub struct PaintId { pub struct PaintId {
slot: u32, slot: u32,
lease: Option<PaintLease>, strong: Option<StrongRscId<PaintRsc>>,
} }
impl PaintId { impl PaintId {
@@ -221,7 +193,7 @@ impl PaintId {
pub const NONE: Self = Self::builtin(14); pub const NONE: Self = Self::builtin(14);
const fn builtin(slot: u32) -> Self { const fn builtin(slot: u32) -> Self {
Self { slot, lease: None } Self { slot, strong: None }
} }
pub(crate) fn slot(&self) -> u32 { pub(crate) fn slot(&self) -> u32 {
@@ -229,7 +201,7 @@ impl PaintId {
} }
fn is_managed(&self) -> bool { fn is_managed(&self) -> bool {
self.lease.is_some() self.strong.is_some()
} }
} }
@@ -346,22 +318,22 @@ const BUILTIN_PAINTS: [Srgba8; 15] = [
/// CPU-side paint table and the dirty set for its GPU mirror. /// CPU-side paint table and the dirty set for its GPU mirror.
pub struct Paints { pub struct Paints {
resources: Resources<PaintRsc>,
entries: Vec<LinearRgba>, entries: Vec<LinearRgba>,
free: Vec<u32>,
dirty: Dirty, dirty: Dirty,
send: Sender<u32>,
recv: Receiver<u32>,
} }
impl Paints { impl Paints {
pub fn new() -> Self { pub fn new() -> Self {
let (send, recv) = channel(); let mut resources = Resources::new();
for (slot, _) in BUILTIN_PAINTS.iter().enumerate() {
let id = resources.add_static(PaintRsc);
assert_eq!(id.slot(), slot as u32);
}
Self { Self {
resources,
entries: BUILTIN_PAINTS.map(Srgba8::to_linear).to_vec(), entries: BUILTIN_PAINTS.map(Srgba8::to_linear).to_vec(),
free: Vec::new(),
dirty: Dirty::new_all(), dirty: Dirty::new_all(),
send,
recv,
} }
} }
@@ -371,23 +343,19 @@ impl Paints {
fn add_linear(&mut self, value: LinearRgba) -> PaintId { fn add_linear(&mut self, value: LinearRgba) -> PaintId {
self.free_released(); self.free_released();
let slot = if let Some(slot) = self.free.pop() { let old_capacity = self.resources.capacity();
let strong = self.resources.add(PaintRsc);
let slot = strong.slot();
if (slot as usize) < old_capacity {
self.entries[slot as usize] = value; self.entries[slot as usize] = value;
self.dirty.mark(slot as usize); self.dirty.mark(slot as usize);
slot
} else { } else {
let slot = self.entries.len() as u32;
self.entries.push(value); self.entries.push(value);
self.dirty.mark(slot as usize); self.dirty.mark(slot as usize);
slot }
};
PaintId { PaintId {
slot, slot,
lease: Some(PaintLease { strong: Some(strong),
slot,
counter: RefCounter::new(),
send: self.send.clone(),
}),
} }
} }
@@ -411,11 +379,13 @@ impl Paints {
} }
pub fn free_released(&mut self) { pub fn free_released(&mut self) {
for slot in self.recv.try_iter() { let entries = &mut self.entries;
self.entries[slot as usize] = LinearRgba::NONE; let dirty = &mut self.dirty;
self.dirty.mark(slot as usize); self.resources.apply(|id, _| {
self.free.push(slot); let slot = id.slot();
} entries[slot as usize] = LinearRgba::NONE;
dirty.mark(slot as usize);
});
} }
/// A new GPU device has no copy of this table even when the CPU-side UI /// A new GPU device has no copy of this table even when the CPU-side UI
+62 -82
View File
@@ -1,6 +1,6 @@
use crate::{ use crate::{
Align, GlyphAtlas, GlyphKey, PaintId, PlacedGlyph, RegionAlign, Textures, WidgetId, Align, GlyphAtlas, GlyphKey, PaintId, PlacedGlyph, RegionAlign, Textures, WidgetId,
util::{SlotId, SlotVec, Vec2}, util::{Resources, RscHandle, StrongRscId, Vec2, WeakRscId},
}; };
use parley::{ use parley::{
Alignment, AlignmentOptions, FontContext, FontFamily, FontStyle, FontWeight, GenericFamily, Alignment, AlignmentOptions, FontContext, FontFamily, FontStyle, FontWeight, GenericFamily,
@@ -8,15 +8,12 @@ use parley::{
fontique::{Blob, FontInfoOverride}, fontique::{Blob, FontInfoOverride},
}; };
use std::{ use std::{
cell::{Ref, RefCell}, cell::{Ref, RefCell, RefMut},
collections::HashMap, collections::HashMap,
fmt, fmt,
ops::{Deref, DerefMut, Range}, ops::{Deref, DerefMut, Range},
rc::Rc, rc::Rc,
sync::{ sync::Arc,
Arc,
mpsc::{Receiver, Sender, channel},
},
}; };
use swash::{ use swash::{
FontRef, FontRef,
@@ -610,7 +607,7 @@ pub struct RenderedText {
pub generation: u64, pub generation: u64,
} }
pub struct TextResource { pub struct TextRsc {
buffer: TextBuffer, buffer: TextBuffer,
attrs: TextAttrs, attrs: TextAttrs,
rendered: Option<RenderedText>, rendered: Option<RenderedText>,
@@ -619,7 +616,7 @@ pub struct TextResource {
owner: Option<WidgetId>, owner: Option<WidgetId>,
} }
impl TextResource { impl TextRsc {
fn new(buffer: TextBuffer, attrs: TextAttrs) -> Self { fn new(buffer: TextBuffer, attrs: TextAttrs) -> Self {
Self { Self {
buffer, buffer,
@@ -641,57 +638,54 @@ impl TextResource {
/// compact [`TextHandle`] into this arena rather than owning Parley layouts. /// compact [`TextHandle`] into this arena rather than owning Parley layouts.
pub struct TextResources { pub struct TextResources {
data: TextData, data: TextData,
entries: SlotVec<TextResource>, entries: Rc<RefCell<Resources<TextRsc>>>,
send: Sender<SlotId>,
recv: Receiver<SlotId>,
} }
impl TextResources { impl TextResources {
pub fn new() -> Self { pub fn new() -> Self {
let (send, recv) = channel();
Self { Self {
data: TextData::default(), data: TextData::default(),
entries: SlotVec::new(), entries: Rc::new(RefCell::new(Resources::new())),
send,
recv,
} }
} }
pub fn add(resources: Rc<RefCell<Self>>, buffer: TextBuffer, attrs: TextAttrs) -> TextHandle { pub fn add(resources: Rc<RefCell<Self>>, buffer: TextBuffer, attrs: TextAttrs) -> TextHandle {
let (id, send) = { let entries = {
let mut resources = resources.borrow_mut(); let mut resources = resources.borrow_mut();
resources.free_released(); resources.free_released();
let id = resources.entries.add(TextResource::new(buffer, attrs)); resources.entries.clone()
(id, resources.send.clone())
}; };
TextHandle { TextHandle {
id, rsc: RscHandle::add(entries, TextRsc::new(buffer, attrs)),
resources, resources,
send,
} }
} }
fn entry(&self, id: SlotId) -> &TextResource { pub fn handle(resources: Rc<RefCell<Self>>, id: StrongRscId<TextRsc>) -> TextHandle {
self.entries let entries = resources.borrow().entries.clone();
.get(id) TextHandle {
.expect("text resource handle points at a released slot") rsc: RscHandle::new(id, entries),
resources,
}
} }
fn entry_mut(&mut self, id: SlotId) -> &mut TextResource { pub fn upgrade(resources: Rc<RefCell<Self>>, id: WeakRscId<TextRsc>) -> Option<TextHandle> {
self.entries let entries = {
.get_mut(id) let mut resources = resources.borrow_mut();
.expect("text resource handle points at a released slot") resources.free_released();
resources.entries.clone()
};
let id = entries.borrow_mut().upgrade(id)?;
Some(Self::handle(resources, id))
} }
pub fn free_released(&mut self) { pub fn free_released(&mut self) {
for id in self.recv.try_iter() { self.entries.borrow_mut().apply(|_, _| {});
self.entries.free(id);
}
} }
pub fn invalidate_all(&mut self) -> Vec<WidgetId> { pub fn invalidate_all(&mut self) -> Vec<WidgetId> {
let mut owners = Vec::new(); let mut owners = Vec::new();
for resource in self.entries.values_mut() { for resource in self.entries.borrow_mut().values_mut() {
resource.invalidate(); resource.invalidate();
if let Some(owner) = resource.owner if let Some(owner) = resource.owner
&& !owners.contains(&owner) && !owners.contains(&owner)
@@ -702,27 +696,22 @@ impl TextResources {
owners owners
} }
fn shape(&mut self, id: SlotId) { fn shape(&mut self, resource: &mut TextRsc) {
let density = self.data.density; let density = self.data.density;
let Self { data, entries, .. } = self;
let resource = entries
.get_mut(id)
.expect("text resource handle points at a released slot");
resource resource
.buffer .buffer
.shape(data, &resource.attrs, resource.width, density); .shape(&mut self.data, &resource.attrs, resource.width, density);
} }
fn render( fn render(
&mut self, &mut self,
id: SlotId, resource: &mut TextRsc,
width: Option<f32>, width: Option<f32>,
owner: WidgetId, owner: WidgetId,
textures: &mut Textures, textures: &mut Textures,
density: f32, density: f32,
) -> (RenderedText, bool) { ) -> (RenderedText, bool) {
let atlas_generation = self.data.atlas.generation(); let atlas_generation = self.data.atlas.generation();
let resource = self.entry_mut(id);
resource.owner = Some(owner); resource.owner = Some(owner);
if resource.width == width if resource.width == width
&& resource.density == density && resource.density == density
@@ -733,11 +722,7 @@ impl TextResources {
} }
resource.width = width; resource.width = width;
resource.density = density; resource.density = density;
let Self { data, entries, .. } = self; let rendered = self.data.render(
let resource = entries
.get_mut(id)
.expect("text resource handle points at a released slot");
let rendered = data.render(
&mut resource.buffer, &mut resource.buffer,
&resource.attrs, &resource.attrs,
width, width,
@@ -773,28 +758,30 @@ impl DerefMut for TextResources {
/// shared once per UI; constructing a handle does not allocate a resource of /// shared once per UI; constructing a handle does not allocate a resource of
/// its own. /// its own.
pub struct TextHandle { pub struct TextHandle {
id: SlotId, rsc: RscHandle<TextRsc>,
resources: Rc<RefCell<TextResources>>, resources: Rc<RefCell<TextResources>>,
send: Sender<SlotId>,
} }
impl TextHandle { impl TextHandle {
pub fn strong(&self) -> StrongRscId<TextRsc> {
self.rsc.strong()
}
pub fn weak(&self) -> WeakRscId<TextRsc> {
self.rsc.weak()
}
pub fn text(&self) -> Ref<'_, str> { pub fn text(&self) -> Ref<'_, str> {
Ref::map(self.resources.borrow(), |resources| { Ref::map(self.rsc.get(), |resource| resource.buffer.text())
resources.entry(self.id).buffer.text()
})
} }
pub fn attrs(&self) -> Ref<'_, TextAttrs> { pub fn attrs(&self) -> Ref<'_, TextAttrs> {
Ref::map(self.resources.borrow(), |resources| { Ref::map(self.rsc.get(), |resource| &resource.attrs)
&resources.entry(self.id).attrs
})
} }
pub fn set_text(&mut self, text: impl Into<String>) -> bool { pub fn set_text(&mut self, text: impl Into<String>) -> bool {
let text = text.into(); let text = text.into();
let mut resources = self.resources.borrow_mut(); let mut resource = self.rsc.get_mut();
let resource = resources.entry_mut(self.id);
if resource.buffer.text() == text { if resource.buffer.text() == text {
return false; return false;
} }
@@ -804,46 +791,45 @@ impl TextHandle {
} }
pub fn edit_text<R>(&mut self, edit: impl FnOnce(&mut String) -> R) -> R { pub fn edit_text<R>(&mut self, edit: impl FnOnce(&mut String) -> R) -> R {
let mut resources = self.resources.borrow_mut(); let mut resource = self.rsc.get_mut();
let resource = resources.entry_mut(self.id);
resource.rendered = None; resource.rendered = None;
edit(resource.buffer.edit()) edit(resource.buffer.edit())
} }
pub fn set_spans(&mut self, spans: Vec<SpanStyle>) { pub fn set_spans(&mut self, spans: Vec<SpanStyle>) {
let mut resources = self.resources.borrow_mut(); let mut resource = self.rsc.get_mut();
let resource = resources.entry_mut(self.id);
resource.buffer.set_spans(spans); resource.buffer.set_spans(spans);
resource.rendered = None; resource.rendered = None;
} }
pub fn update_attrs<R>(&mut self, update: impl FnOnce(&mut TextAttrs) -> R) -> R { pub fn attrs_mut(&mut self) -> RefMut<'_, TextAttrs> {
let mut resources = self.resources.borrow_mut(); let mut resource = self.rsc.get_mut();
let resource = resources.entry_mut(self.id);
resource.invalidate(); resource.invalidate();
update(&mut resource.attrs) RefMut::map(resource, |resource| &mut resource.attrs)
} }
pub fn rendered(&self) -> Option<RenderedText> { pub fn rendered(&self) -> Option<RenderedText> {
self.resources.borrow().entry(self.id).rendered.clone() self.rsc.get().rendered.clone()
} }
pub fn width(&self) -> Option<f32> { pub fn width(&self) -> Option<f32> {
self.resources.borrow().entry(self.id).width self.rsc.get().width
} }
pub fn with_layout<R>(&self, f: impl FnOnce(&Layout<PaintId>, &str) -> R) -> R { pub fn with_layout<R>(&self, f: impl FnOnce(&Layout<PaintId>, &str) -> R) -> R {
let mut resources = self.resources.borrow_mut(); let mut resources = self.resources.borrow_mut();
resources.shape(self.id); let mut resource = self.rsc.get_mut_shared();
let resource = resources.entry(self.id); resources.shape(&mut resource);
f(resource.buffer.layout(), resource.buffer.text()) f(resource.buffer.layout(), resource.buffer.text())
} }
pub fn layout(&self) -> Ref<'_, Layout<PaintId>> { pub fn layout(&self) -> Ref<'_, Layout<PaintId>> {
self.resources.borrow_mut().shape(self.id); {
Ref::map(self.resources.borrow(), |resources| { let mut resources = self.resources.borrow_mut();
resources.entry(self.id).buffer.layout() let mut resource = self.rsc.get_mut_shared();
}) resources.shape(&mut resource);
}
Ref::map(self.rsc.get(), |resource| resource.buffer.layout())
} }
pub fn render( pub fn render(
@@ -853,15 +839,9 @@ impl TextHandle {
textures: &mut Textures, textures: &mut Textures,
density: f32, density: f32,
) -> (RenderedText, bool) { ) -> (RenderedText, bool) {
self.resources let mut resources = self.resources.borrow_mut();
.borrow_mut() let mut resource = self.rsc.get_mut_shared();
.render(self.id, width, owner, textures, density) resources.render(&mut resource, width, owner, textures, density)
}
}
impl Drop for TextHandle {
fn drop(&mut self) {
let _ = self.send.send(self.id);
} }
} }
@@ -886,11 +866,11 @@ mod tests {
TextBuffer::new("temporary"), TextBuffer::new("temporary"),
TextAttrs::default(), TextAttrs::default(),
); );
assert_eq!(resources.borrow().entries.len(), 1); assert_eq!(resources.borrow().entries.borrow().len(), 1);
drop(text); drop(text);
resources.borrow_mut().free_released(); resources.borrow_mut().free_released();
assert!(resources.borrow().entries.is_empty()); assert!(resources.borrow().entries.borrow().is_empty());
} }
} }
+85 -67
View File
@@ -1,10 +1,6 @@
use crate::util::{RefCounter, Vec2}; use crate::util::{Resources, RscHandle, StrongRscId, Vec2, WeakRscId};
use image::{DynamicImage, GenericImageView}; use image::{DynamicImage, GenericImageView};
use std::{ use std::{cell::RefCell, collections::HashMap, ops::Index, rc::Rc};
collections::HashMap,
ops::Index,
sync::mpsc::{Receiver, Sender, channel},
};
/// Which of the two things a texture slot holds. See TEXTURES.md's /// Which of the two things a texture slot holds. See TEXTURES.md's
/// "Recommended shape" for why these are drawn so differently: a page is a /// "Recommended shape" for why these are drawn so differently: a page is a
@@ -28,19 +24,20 @@ pub struct SharedTextureKey {
pub id: u64, pub id: u64,
} }
#[derive(Debug, Clone)] pub struct TextureRsc {
pub struct TextureHandle {
slot: u32,
kind: TextureKind, kind: TextureKind,
size: Vec2, size: Vec2,
counter: RefCounter, }
send: Sender<(TextureKind, u32)>,
#[derive(Debug, Clone)]
pub struct TextureHandle {
rsc: RscHandle<TextureRsc>,
} }
/// a texture manager for a ui /// a texture manager for a ui
/// note that this is heavily oriented towards wgpu's renderer so the primitives don't need mapped /// note that this is heavily oriented towards wgpu's renderer so the primitives don't need mapped
pub struct Textures { pub struct Textures {
free: Vec<u32>, resources: Rc<RefCell<Resources<TextureRsc>>>,
images: Vec<Option<DynamicImage>>, images: Vec<Option<DynamicImage>>,
kinds: Vec<TextureKind>, kinds: Vec<TextureKind>,
/// Textures built from a description rather than from a file, one per /// Textures built from a description rather than from a file, one per
@@ -48,12 +45,10 @@ pub struct Textures {
/// reference of its own, so a shared texture outlives every widget /// reference of its own, so a shared texture outlives every widget
/// drawing it and its slot is never recycled underneath one. /// drawing it and its slot is never recycled underneath one.
shared: HashMap<SharedTextureKey, TextureHandle>, shared: HashMap<SharedTextureKey, TextureHandle>,
/// Next layer to hand out to an atlas page. Pages are never freed (no /// Next layer to hand out to an atlas page. Page layers and resource slots
/// atlas eviction), so this only grows and `free` never holds one. /// are never reused, even if an explicit atlas clear drops their handles.
next_page_layer: u32, next_page_layer: u32,
updates: Vec<Update>, updates: Vec<Update>,
send: Sender<(TextureKind, u32)>,
recv: Receiver<(TextureKind, u32)>,
} }
pub enum TextureUpdate<'a> { pub enum TextureUpdate<'a> {
@@ -86,16 +81,13 @@ enum Update {
impl Textures { impl Textures {
pub fn new() -> Self { pub fn new() -> Self {
let (send, recv) = channel();
Self { Self {
free: Vec::new(), resources: Rc::new(RefCell::new(Resources::new())),
images: Vec::new(), images: Vec::new(),
kinds: Vec::new(), kinds: Vec::new(),
shared: HashMap::new(), shared: HashMap::new(),
next_page_layer: 0, next_page_layer: 0,
updates: Vec::new(), updates: Vec::new(),
send,
recv,
} }
} }
@@ -103,14 +95,7 @@ impl Textures {
let image = image.into(); let image = image.into();
let size = image.dimensions().into(); let size = image.dimensions().into();
let kind = TextureKind::Image; let kind = TextureKind::Image;
let slot = self.push(kind, image); self.push(kind, size, image, true)
TextureHandle {
slot,
kind,
size,
counter: RefCounter::new(),
send: self.send.clone(),
}
} }
pub fn add_page(&mut self, image: impl Into<DynamicImage>) -> TextureHandle { pub fn add_page(&mut self, image: impl Into<DynamicImage>) -> TextureHandle {
@@ -119,28 +104,49 @@ impl Textures {
let layer = self.next_page_layer; let layer = self.next_page_layer;
self.next_page_layer += 1; self.next_page_layer += 1;
let kind = TextureKind::Page { layer }; let kind = TextureKind::Page { layer };
let slot = self.push(kind, image); self.push(kind, size, image, false)
}
pub fn handle(&self, id: StrongRscId<TextureRsc>) -> TextureHandle {
TextureHandle { TextureHandle {
slot, rsc: RscHandle::new(id, self.resources.clone()),
kind,
size,
counter: RefCounter::new(),
send: self.send.clone(),
} }
} }
fn push(&mut self, kind: TextureKind, image: DynamicImage) -> u32 { pub fn upgrade(&mut self, id: WeakRscId<TextureRsc>) -> Option<TextureHandle> {
if let Some(i) = self.free.pop() { self.free();
let id = self.resources.borrow_mut().upgrade(id)?;
Some(self.handle(id))
}
fn push(
&mut self,
kind: TextureKind,
size: Vec2,
image: DynamicImage,
recycle: bool,
) -> TextureHandle {
self.free();
let old_capacity = self.resources.borrow().capacity();
let id = if recycle {
self.resources.borrow_mut().add(TextureRsc { kind, size })
} else {
self.resources
.borrow_mut()
.add_unrecycled(TextureRsc { kind, size })
};
let i = id.slot();
if (i as usize) < old_capacity {
self.images[i as usize] = Some(image); self.images[i as usize] = Some(image);
self.kinds[i as usize] = kind; self.kinds[i as usize] = kind;
self.updates.push(Update::Set(kind, i)); self.updates.push(Update::Set(kind, i));
i
} else { } else {
let i = self.images.len() as u32;
self.images.push(Some(image)); self.images.push(Some(image));
self.kinds.push(kind); self.kinds.push(kind);
self.updates.push(Update::Push(kind, i)); self.updates.push(Update::Push(kind, i));
i }
TextureHandle {
rsc: RscHandle::new(id, self.resources.clone()),
} }
} }
@@ -161,13 +167,14 @@ impl Textures {
} }
pub fn image_mut(&mut self, handle: &TextureHandle) -> &mut DynamicImage { pub fn image_mut(&mut self, handle: &TextureHandle) -> &mut DynamicImage {
self.images[handle.slot as usize] self.images[handle.rsc.id().slot() as usize]
.as_mut() .as_mut()
.expect("texture was freed while still held") .expect("texture was freed while still held")
} }
pub fn patch(&mut self, handle: &TextureHandle, rect: PatchRect) { pub fn patch(&mut self, handle: &TextureHandle, rect: PatchRect) {
self.updates.push(Update::Patch(handle.slot, rect)); self.updates
.push(Update::Patch(handle.rsc.id().slot(), rect));
} }
/// A new device starts with no textures, and the renderer-side mirror /// A new device starts with no textures, and the renderer-side mirror
@@ -185,24 +192,20 @@ impl Textures {
/// ones after them still land where they were. /// ones after them still land where they were.
pub fn reupload(&mut self) { pub fn reupload(&mut self) {
self.updates.clear(); self.updates.clear();
self.updates self.updates.extend(
.extend((0..self.images.len() as u32).map(|i| Update::Push(self.kinds[i as usize], i))); (0..self.resources.borrow().capacity() as u32)
.map(|i| Update::Push(self.kinds[i as usize], i)),
);
} }
pub fn free(&mut self) { pub fn free(&mut self) {
for (kind, idx) in self.recv.try_iter() { let updates = &mut self.updates;
self.images[idx as usize] = None; let images = &mut self.images;
self.updates.push(Update::Free(idx)); self.resources.borrow_mut().apply(|id, _| {
// A page's slot is never reclaimed: `GlyphAtlas` never drops the let idx = id.slot();
// handles it holds, and there is no eviction path for a hole in images[idx as usize] = None;
// the middle of the array's layers. If that ever changes, this updates.push(Update::Free(idx));
// is where a freed page's layer would need to go on a free list });
// of its own, separate from `free`, which only ever holds
// ordinary image slots today.
if kind == TextureKind::Image {
self.free.push(idx);
}
}
} }
pub fn updates(&mut self) -> impl Iterator<Item = TextureUpdate<'_>> { pub fn updates(&mut self) -> impl Iterator<Item = TextureUpdate<'_>> {
@@ -226,7 +229,7 @@ impl Textures {
impl TextureHandle { impl TextureHandle {
pub fn size(&self) -> Vec2 { pub fn size(&self) -> Vec2 {
self.size self.rsc.get().size
} }
/// The bind-group index this handle draws with. Only valid for a /// The bind-group index this handle draws with. Only valid for a
@@ -235,25 +238,25 @@ impl TextureHandle {
/// a caller bug (the wrong kind of handle reached the wrong draw path), /// a caller bug (the wrong kind of handle reached the wrong draw path),
/// not a recoverable condition, so it panics rather than drawing garbage. /// not a recoverable condition, so it panics rather than drawing garbage.
pub fn image_index(&self) -> u32 { pub fn image_index(&self) -> u32 {
match self.kind { match self.rsc.get().kind {
TextureKind::Image => self.slot, TextureKind::Image => self.rsc.id().slot(),
TextureKind::Page { .. } => panic!("image_index() called on an atlas page handle"), TextureKind::Page { .. } => panic!("image_index() called on an atlas page handle"),
} }
} }
pub fn layer(&self) -> u32 { pub fn layer(&self) -> u32 {
match self.kind { match self.rsc.get().kind {
TextureKind::Page { layer } => layer, TextureKind::Page { layer } => layer,
TextureKind::Image => panic!("layer() called on a standalone image handle"), TextureKind::Image => panic!("layer() called on a standalone image handle"),
} }
} }
}
impl Drop for TextureHandle { pub fn strong(&self) -> StrongRscId<TextureRsc> {
fn drop(&mut self) { self.rsc.strong()
if self.counter.drop() { }
let _ = self.send.send((self.kind, self.slot));
} pub fn weak(&self) -> WeakRscId<TextureRsc> {
self.rsc.weak()
} }
} }
@@ -261,7 +264,9 @@ impl Index<&TextureHandle> for Textures {
type Output = DynamicImage; type Output = DynamicImage;
fn index(&self, index: &TextureHandle) -> &Self::Output { fn index(&self, index: &TextureHandle) -> &Self::Output {
self.images[index.slot as usize].as_ref().unwrap() self.images[index.rsc.id().slot() as usize]
.as_ref()
.unwrap()
} }
} }
@@ -315,6 +320,19 @@ mod tests {
); );
} }
#[test]
fn a_released_atlas_page_slot_is_not_reused_by_an_image() {
let mut textures = Textures::new();
let page = textures.add_page(image(4));
let page_slot = page.rsc.id().slot();
drop(page);
textures.free();
let plain = textures.add(image(4));
assert_ne!(plain.image_index(), page_slot);
}
#[test] #[test]
fn reupload_replays_every_slot_in_order_including_the_empty_ones() { fn reupload_replays_every_slot_in_order_including_the_empty_ones() {
let mut textures = Textures::new(); let mut textures = Textures::new();
+1 -2
View File
@@ -81,14 +81,13 @@ impl GlyphAtlas {
let rgba = img.as_mut_rgba8().expect("atlas page is rgba8"); let rgba = img.as_mut_rgba8().expect("atlas page is rgba8");
write_glyph(rgba, image, x, y); write_glyph(rgba, image, x, y);
let handle = page.handle.clone();
let rect = PatchRect { let rect = PatchRect {
x, x,
y, y,
width: w, width: w,
height: h, height: h,
}; };
textures.patch(&handle, rect); textures.patch(&page.handle, rect);
let page = &self.pages[page_idx]; let page = &self.pages[page_idx];
let scale = 1.0 / PAGE as f32; let scale = 1.0 / PAGE as f32;
+1 -1
View File
@@ -182,8 +182,8 @@ pub trait UiRsc {
while let Some(id) = self.widgets_mut().free_next() { while let Some(id) = self.widgets_mut().free_next() {
self.on_remove(id); self.on_remove(id);
} }
self.ui_mut().text.borrow_mut().free_released();
self.ui_mut().textures.free(); self.ui_mut().textures.free();
self.ui_mut().paints.free_released(); self.ui_mut().paints.free_released();
self.ui_mut().text.borrow_mut().free_released();
} }
} }
+2
View File
@@ -5,6 +5,7 @@ mod dirty;
mod id; mod id;
mod math; mod math;
mod refcount; mod refcount;
mod resources;
mod slot; mod slot;
mod trust; mod trust;
mod typemap; mod typemap;
@@ -17,6 +18,7 @@ pub use dirty::*;
pub use id::*; pub use id::*;
pub use math::*; pub use math::*;
pub use refcount::*; pub use refcount::*;
pub use resources::*;
pub use slot::*; pub use slot::*;
pub use trust::*; pub use trust::*;
pub use typemap::*; pub use typemap::*;
+414
View File
@@ -0,0 +1,414 @@
use super::{SlotId, SlotVec};
use std::{
cell::{Ref, RefCell, RefMut},
fmt,
hash::{Hash, Hasher},
marker::PhantomData,
rc::Rc,
sync::mpsc::{Receiver, Sender, channel},
};
enum Event {
Clone(SlotId),
Drop(SlotId),
}
struct Entry<T> {
value: T,
strong: Option<u32>,
recycle: bool,
}
/// Generational storage and deferred strong-reference accounting for one kind
/// of UI resource.
pub struct Resources<T> {
entries: SlotVec<Entry<T>>,
send: Sender<Event>,
recv: Receiver<Event>,
}
impl<T> Resources<T> {
pub fn new() -> Self {
let (send, recv) = channel();
Self {
entries: SlotVec::new(),
send,
recv,
}
}
pub fn add(&mut self, value: T) -> StrongRscId<T> {
self.add_with(value, true)
}
pub fn add_unrecycled(&mut self, value: T) -> StrongRscId<T> {
self.add_with(value, false)
}
fn add_with(&mut self, value: T, recycle: bool) -> StrongRscId<T> {
let id = self.entries.add(Entry {
value,
strong: Some(1),
recycle,
});
StrongRscId::new(id, self.send.clone())
}
/// Add an entry whose lifetime is the lifetime of the arena itself.
pub fn add_static(&mut self, value: T) -> WeakRscId<T> {
WeakRscId::new(self.entries.add(Entry {
value,
strong: None,
recycle: false,
}))
}
pub fn apply(&mut self, mut dropped: impl FnMut(SlotId, T)) {
for event in self.recv.try_iter() {
match event {
Event::Clone(id) => {
let entry = self
.entries
.get_mut(id)
.expect("cloned resource id points at a released slot");
let strong = entry
.strong
.as_mut()
.expect("a static resource cannot have a strong id");
*strong = strong.checked_add(1).expect("resource reference overflow");
}
Event::Drop(id) => {
let remove = {
let entry = self
.entries
.get_mut(id)
.expect("dropped resource id points at a released slot");
let strong = entry
.strong
.as_mut()
.expect("a static resource cannot have a strong id");
*strong = strong.checked_sub(1).expect("resource reference underflow");
*strong == 0
};
if remove {
let recycle = self.entries.get(id).unwrap().recycle;
let entry = if recycle {
self.entries.remove(id)
} else {
self.entries.remove_unrecycled(id)
}
.unwrap();
dropped(id, entry.value);
}
}
}
}
}
/// The owning manager must call [`Self::apply`] first so a queued final
/// drop cannot be mistaken for a still-live entry.
pub fn upgrade(&mut self, id: WeakRscId<T>) -> Option<StrongRscId<T>> {
let entry = self.entries.get_mut(id.id)?;
let strong = entry.strong.as_mut()?;
*strong = strong.checked_add(1).expect("resource reference overflow");
Some(StrongRscId::new(id.id, self.send.clone()))
}
pub fn get(&self, id: impl RscId<T>) -> Option<&T> {
Some(&self.entries.get(id.rsc_id())?.value)
}
pub fn get_mut(&mut self, id: impl RscId<T>) -> Option<&mut T> {
Some(&mut self.entries.get_mut(id.rsc_id())?.value)
}
pub fn values_mut(&mut self) -> impl Iterator<Item = &mut T> {
self.entries.values_mut().map(|entry| &mut entry.value)
}
pub fn capacity(&self) -> usize {
self.entries.capacity()
}
pub fn len(&self) -> usize {
self.entries.len()
}
pub fn is_empty(&self) -> bool {
self.entries.is_empty()
}
}
impl<T> Default for Resources<T> {
fn default() -> Self {
Self::new()
}
}
pub trait RscId<T> {
fn rsc_id(&self) -> SlotId;
}
/// A sendable owning ID for one entry in [`Resources`]. It keeps the entry
/// alive but does not provide access to its value.
pub struct StrongRscId<T> {
id: SlotId,
send: Sender<Event>,
ty: PhantomData<fn() -> T>,
}
impl<T> StrongRscId<T> {
fn new(id: SlotId, send: Sender<Event>) -> Self {
Self {
id,
send,
ty: PhantomData,
}
}
pub fn weak(&self) -> WeakRscId<T> {
WeakRscId::new(self.id)
}
pub fn id(&self) -> SlotId {
self.id
}
pub(crate) fn slot(&self) -> u32 {
self.id.slot()
}
}
impl<T> Clone for StrongRscId<T> {
fn clone(&self) -> Self {
let _ = self.send.send(Event::Clone(self.id));
Self::new(self.id, self.send.clone())
}
}
impl<T> Drop for StrongRscId<T> {
fn drop(&mut self) {
let _ = self.send.send(Event::Drop(self.id));
}
}
impl<T> RscId<T> for StrongRscId<T> {
fn rsc_id(&self) -> SlotId {
self.id
}
}
impl<T> RscId<T> for &StrongRscId<T> {
fn rsc_id(&self) -> SlotId {
self.id
}
}
impl<T> fmt::Debug for StrongRscId<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.id.fmt(f)
}
}
impl<T> PartialEq for StrongRscId<T> {
fn eq(&self, other: &Self) -> bool {
self.id == other.id
}
}
impl<T> Eq for StrongRscId<T> {}
impl<T> Hash for StrongRscId<T> {
fn hash<H: Hasher>(&self, state: &mut H) {
self.id.hash(state);
}
}
/// A sendable, copyable ID that does not keep its [`Resources`] entry alive.
pub struct WeakRscId<T> {
id: SlotId,
ty: PhantomData<fn() -> T>,
}
impl<T> WeakRscId<T> {
fn new(id: SlotId) -> Self {
Self {
id,
ty: PhantomData,
}
}
pub fn id(self) -> SlotId {
self.id
}
pub(crate) fn slot(self) -> u32 {
self.id.slot()
}
}
impl<T> Clone for WeakRscId<T> {
fn clone(&self) -> Self {
*self
}
}
impl<T> Copy for WeakRscId<T> {}
impl<T> RscId<T> for WeakRscId<T> {
fn rsc_id(&self) -> SlotId {
self.id
}
}
impl<T> RscId<T> for &WeakRscId<T> {
fn rsc_id(&self) -> SlotId {
self.id
}
}
impl<T> fmt::Debug for WeakRscId<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.id.fmt(f)
}
}
impl<T> PartialEq for WeakRscId<T> {
fn eq(&self, other: &Self) -> bool {
self.id == other.id
}
}
impl<T> Eq for WeakRscId<T> {}
impl<T> Hash for WeakRscId<T> {
fn hash<H: Hasher>(&self, state: &mut H) {
self.id.hash(state);
}
}
/// Convenient UI-thread access to a resource through an owning ID. The `Rc`
/// deliberately makes this local; tasks carry strong or weak IDs instead.
pub struct RscHandle<T> {
id: StrongRscId<T>,
resources: Rc<RefCell<Resources<T>>>,
}
impl<T> RscHandle<T> {
pub fn new(id: StrongRscId<T>, resources: Rc<RefCell<Resources<T>>>) -> Self {
Self { id, resources }
}
pub fn add(resources: Rc<RefCell<Resources<T>>>, value: T) -> Self {
let id = resources.borrow_mut().add(value);
Self::new(id, resources)
}
pub fn get(&self) -> Ref<'_, T> {
Ref::map(self.resources.borrow(), |resources| {
resources
.get(&self.id)
.expect("resource handle points at a released slot")
})
}
pub fn get_mut(&mut self) -> RefMut<'_, T> {
RefMut::map(self.resources.borrow_mut(), |resources| {
resources
.get_mut(&self.id)
.expect("resource handle points at a released slot")
})
}
pub(crate) fn get_mut_shared(&self) -> RefMut<'_, T> {
RefMut::map(self.resources.borrow_mut(), |resources| {
resources
.get_mut(&self.id)
.expect("resource handle points at a released slot")
})
}
pub fn strong(&self) -> StrongRscId<T> {
self.id.clone()
}
pub fn weak(&self) -> WeakRscId<T> {
self.id.weak()
}
pub fn id(&self) -> SlotId {
self.id.id()
}
}
impl<T> Clone for RscHandle<T> {
fn clone(&self) -> Self {
Self::new(self.id.clone(), self.resources.clone())
}
}
impl<T> fmt::Debug for RscHandle<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.id.fmt(f)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn a_slot_lives_until_every_strong_id_is_dropped() {
let mut resources = Resources::new();
let first = resources.add("value");
let weak = first.weak();
let second = first.clone();
drop(first);
resources.apply(|_, _| {});
assert_eq!(resources.get(weak), Some(&"value"));
drop(second);
resources.apply(|_, _| {});
assert_eq!(resources.get(weak), None);
}
#[test]
fn a_weak_id_can_be_upgraded_while_the_resource_is_alive() {
let mut resources = Resources::new();
let first = resources.add("value");
let weak = first.weak();
let second = resources.upgrade(weak).unwrap();
drop(first);
resources.apply(|_, _| {});
assert_eq!(resources.get(&second), Some(&"value"));
}
#[test]
fn strong_and_weak_ids_can_cross_threads() {
fn assert_send<T: Send>() {}
assert_send::<StrongRscId<String>>();
assert_send::<WeakRscId<String>>();
let mut resources = Resources::new();
let first = resources.add("value");
let second = first.clone();
std::thread::spawn(move || drop(second)).join().unwrap();
drop(first);
resources.apply(|_, _| {});
assert!(resources.is_empty());
}
#[test]
fn an_unrecycled_slot_stays_a_hole() {
let mut resources = Resources::new();
let first = resources.add_unrecycled("first");
let first_slot = first.slot();
drop(first);
resources.apply(|_, _| {});
let second = resources.add("second");
assert_ne!(second.slot(), first_slot);
assert_eq!(resources.len(), 1);
}
}
+36 -5
View File
@@ -5,6 +5,10 @@ pub struct SlotId {
} }
impl SlotId { impl SlotId {
pub(crate) fn slot(self) -> u32 {
self.idx
}
/// A stable, collision-free `u64` encoding of this id -- for a caller /// A stable, collision-free `u64` encoding of this id -- for a caller
/// (accesskit's `NodeId`, today) that wants a flat integer key rather /// (accesskit's `NodeId`, today) that wants a flat integer key rather
/// than the two `u32`s. `idx` is offset by one so no real id ever /// than the two `u32`s. `idx` is offset by one so no real id ever
@@ -18,6 +22,7 @@ impl SlotId {
pub struct SlotVec<T> { pub struct SlotVec<T> {
data: Vec<(u32, Option<T>)>, data: Vec<(u32, Option<T>)>,
free: Vec<u32>, free: Vec<u32>,
len: usize,
} }
impl<T> SlotVec<T> { impl<T> SlotVec<T> {
@@ -25,11 +30,12 @@ impl<T> SlotVec<T> {
Self { Self {
data: Default::default(), data: Default::default(),
free: Default::default(), free: Default::default(),
len: 0,
} }
} }
pub fn add(&mut self, x: T) -> SlotId { pub fn add(&mut self, x: T) -> SlotId {
if let Some(idx) = self.free.pop() { let id = if let Some(idx) = self.free.pop() {
let (genr, data) = &mut self.data[idx as usize]; let (genr, data) = &mut self.data[idx as usize];
*data = Some(x); *data = Some(x);
SlotId { idx, genr: *genr } SlotId { idx, genr: *genr }
@@ -38,14 +44,35 @@ impl<T> SlotVec<T> {
let genr = 0; let genr = 0;
self.data.push((genr, Some(x))); self.data.push((genr, Some(x)));
SlotId { idx, genr } SlotId { idx, genr }
} };
self.len += 1;
id
} }
pub fn free(&mut self, id: SlotId) { pub fn free(&mut self, id: SlotId) {
let _ = self.remove(id);
}
pub fn remove(&mut self, id: SlotId) -> Option<T> {
self.remove_inner(id, true)
}
pub fn remove_unrecycled(&mut self, id: SlotId) -> Option<T> {
self.remove_inner(id, false)
}
fn remove_inner(&mut self, id: SlotId, recycle: bool) -> Option<T> {
let (genr, data) = &mut self.data[id.idx as usize]; let (genr, data) = &mut self.data[id.idx as usize];
if *genr != id.genr {
return None;
}
*genr += 1; *genr += 1;
*data = None; let value = data.take()?;
self.free.push(id.idx); self.len -= 1;
if recycle {
self.free.push(id.idx);
}
Some(value)
} }
pub fn get(&self, id: SlotId) -> Option<&T> { pub fn get(&self, id: SlotId) -> Option<&T> {
@@ -65,7 +92,7 @@ impl<T> SlotVec<T> {
} }
pub fn len(&self) -> usize { pub fn len(&self) -> usize {
self.data.len() - self.free.len() self.len
} }
pub fn is_empty(&self) -> bool { pub fn is_empty(&self) -> bool {
@@ -75,6 +102,10 @@ impl<T> SlotVec<T> {
pub fn values_mut(&mut self) -> impl Iterator<Item = &mut T> { pub fn values_mut(&mut self) -> impl Iterator<Item = &mut T> {
self.data.iter_mut().filter_map(|(_, value)| value.as_mut()) self.data.iter_mut().filter_map(|(_, value)| value.as_mut())
} }
pub fn capacity(&self) -> usize {
self.data.len()
}
} }
impl<T> Default for SlotVec<T> { impl<T> Default for SlotVec<T> {
+4 -4
View File
@@ -117,8 +117,8 @@ impl TextView {
self.text.text().to_string() self.text.text().to_string()
} }
pub fn update_attrs<R>(&mut self, update: impl FnOnce(&mut TextAttrs) -> R) -> R { pub fn attrs_mut(&mut self) -> std::cell::RefMut<'_, TextAttrs> {
self.text.update_attrs(update) self.text.attrs_mut()
} }
} }
@@ -147,8 +147,8 @@ impl Text {
self.view.content() self.view.content()
} }
pub fn update_attrs<R>(&mut self, update: impl FnOnce(&mut TextAttrs) -> R) -> R { pub fn attrs_mut(&mut self) -> std::cell::RefMut<'_, TextAttrs> {
self.view.update_attrs(update) self.view.attrs_mut()
} }
} }