iris: share resource handle bookkeeping
This commit is contained in:
1 parent
a8093b002b
commit
c97df72015
10 files changed
+640
-223
No files matched your search
+11
-8
@@ -818,14 +818,17 @@ event types or manually request redraws.
|
|||||||
them by the same string used by text widgets. The public boundaries accept
|
them by the same string used by text widgets. The public boundaries accept
|
||||||
`AsRef<str>`, so an application can use bare strings or put its own semantic
|
`AsRef<str>`, so an application can use bare strings or put its own semantic
|
||||||
enum in front of them without Iris hardcoding the roles. Text buffers, layouts,
|
enum in front of them without Iris hardcoding the roles. Text buffers, layouts,
|
||||||
and prepared glyphs live in a per-`Ui` generational arena; a text widget holds
|
and prepared glyphs live in a per-`Ui` `Resources<TextRsc>` generational arena;
|
||||||
one compact handle into it, while the arena and its shaping state are shared by
|
a text widget's `TextHandle` wraps the UI-local `RscHandle` into it. The same
|
||||||
one `Rc<RefCell<_>>`. Registering another family invalidates the live arena
|
generic arena owns texture and managed-paint lifetimes. Its `StrongRscId` and
|
||||||
entries and dirties their active owner widgets, so registration is also valid
|
`WeakRscId` are sendable IDs rather than data pointers; clone and drop events
|
||||||
after the first shape. ai-app owns the `ai-app-icons` family, its Nerd Fonts
|
cross one arena-owned standard channel, and reference counts remain in the
|
||||||
subset, its codepoints, its license and the script that rebuilds it; the CSS
|
arena instead of allocating one atomic counter per resource. Registering
|
||||||
generic names `sans-serif` and `monospace` continue to resolve through the
|
another family invalidates the live text entries and dirties their active owner
|
||||||
platform.
|
widgets, so registration is also valid after the first shape. ai-app owns the
|
||||||
|
`ai-app-icons` family, its Nerd Fonts subset, its codepoints, its license and
|
||||||
|
the script that rebuilds it; the CSS generic names `sans-serif` and `monospace`
|
||||||
|
continue to resolve through the platform.
|
||||||
|
|
||||||
`cargo-iris` is an installable Cargo subcommand, rather than a script callers
|
`cargo-iris` is an installable Cargo subcommand, rather than a script callers
|
||||||
must find inside an Iris checkout. `cargo iris apk` builds the Rust `cdylib`
|
must find inside an Iris checkout. `cargo iris apk` builds the Rust `cdylib`
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -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"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn strong(&self) -> StrongRscId<TextureRsc> {
|
||||||
|
self.rsc.strong()
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Drop for TextureHandle {
|
pub fn weak(&self) -> WeakRscId<TextureRsc> {
|
||||||
fn drop(&mut self) {
|
self.rsc.weak()
|
||||||
if self.counter.drop() {
|
|
||||||
let _ = self.send.send((self.kind, self.slot));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -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();
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -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::*;
|
||||||
|
|||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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,15 +44,36 @@ 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.len -= 1;
|
||||||
|
if recycle {
|
||||||
self.free.push(id.idx);
|
self.free.push(id.idx);
|
||||||
}
|
}
|
||||||
|
Some(value)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn get(&self, id: SlotId) -> Option<&T> {
|
pub fn get(&self, id: SlotId) -> Option<&T> {
|
||||||
let slot = &self.data[id.idx as usize];
|
let slot = &self.data[id.idx as usize];
|
||||||
@@ -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> {
|
||||||
|
|||||||
@@ -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()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in new issue
Block a user