iris: fold render state into Ui

This commit is contained in:
iris committed 2026-09-10 23:58:43 -04:00
1 parent 44a5da378b
commit 9b4c690916
25 files changed
+421 -390

No files matched your search

+20 -17
View File
@@ -1,5 +1,5 @@
use crate::{
ActiveData, WidgetId,
UiRenderState, WidgetId,
util::{HashMap, HashSet},
};
use std::any::{Any, TypeId};
@@ -56,7 +56,6 @@ pub trait Controller<Rsc>: ControllerValue {
pub struct ControllerManager<Rsc> {
by_widget: HashMap<WidgetId, HashMap<TypeId, Box<dyn Controller<Rsc>>>>,
parents: HashMap<WidgetId, Option<WidgetId>>,
borrowed: HashSet<ControllerId>,
removed_while_borrowed: HashSet<WidgetId>,
command_target: Option<ControllerId>,
@@ -68,7 +67,6 @@ impl<Rsc> Default for ControllerManager<Rsc> {
fn default() -> Self {
Self {
by_widget: Default::default(),
parents: Default::default(),
borrowed: Default::default(),
removed_while_borrowed: Default::default(),
command_target: None,
@@ -111,7 +109,11 @@ impl<Rsc: 'static> ControllerManager<Rsc> {
.then_some(ControllerId { host, kind })
}
pub fn nearest_id<C: Controller<Rsc>>(&self, mut origin: WidgetId) -> Option<ControllerId> {
pub fn nearest_id<C: Controller<Rsc>>(
&self,
mut origin: WidgetId,
render_state: &UiRenderState,
) -> Option<ControllerId> {
let kind = TypeId::of::<C>();
loop {
let candidate = ControllerId { host: origin, kind };
@@ -122,13 +124,14 @@ impl<Rsc: 'static> ControllerManager<Rsc> {
if let Some(id) = self.id::<C>(origin) {
return Some(id);
}
origin = self.parents.get(&origin).copied().flatten()?;
origin = render_state.active.get(&origin)?.parent?;
}
}
pub fn path_to<C: Controller<Rsc>>(
&self,
mut origin: WidgetId,
render_state: &UiRenderState,
) -> Option<(ControllerId, Vec<WidgetId>)> {
let mut path = Vec::new();
loop {
@@ -136,18 +139,10 @@ impl<Rsc: 'static> ControllerManager<Rsc> {
if let Some(id) = self.id::<C>(origin) {
return Some((id, path));
}
origin = self.parents.get(&origin).copied().flatten()?;
origin = render_state.active.get(&origin)?.parent?;
}
}
pub fn draw(&mut self, active: &ActiveData) {
self.parents.insert(active.id, active.parent);
}
pub fn undraw(&mut self, active: &ActiveData) {
self.parents.remove(&active.id);
}
pub fn take<C: Controller<Rsc>>(&mut self, id: ControllerId) -> Option<C> {
if id.kind != TypeId::of::<C>() {
return None;
@@ -232,9 +227,18 @@ impl<Rsc: 'static> ControllerManager<Rsc> {
self.command_boundary
}
pub(crate) fn is_below(&self, mut widget: WidgetId, ancestor: WidgetId) -> bool {
pub(crate) fn is_below(
&self,
mut widget: WidgetId,
ancestor: WidgetId,
render_state: &UiRenderState,
) -> bool {
loop {
let Some(parent) = self.parents.get(&widget).copied().flatten() else {
let Some(parent) = render_state
.active
.get(&widget)
.and_then(|active| active.parent)
else {
return false;
};
if parent == ancestor {
@@ -250,7 +254,6 @@ impl<Rsc: 'static> ControllerManager<Rsc> {
pub fn remove(&mut self, host: WidgetId) {
self.by_widget.remove(&host);
self.parents.remove(&host);
if self.borrowed.iter().any(|id| id.host == host) {
self.removed_while_borrowed.insert(host);
}
-2
View File
@@ -60,14 +60,12 @@ impl<Rsc: HasEvents + 'static> EventsLike for EventManager<Rsc> {
}
fn draw(&mut self, active: &ActiveData) {
self.controllers.draw(active);
for t in self.widget_to_types.get(&active.id).into_flat_iter() {
self.types.get_mut(t).unwrap().draw(active);
}
}
fn undraw(&mut self, active: &ActiveData) {
self.controllers.undraw(active);
for t in self.widget_to_types.get(&active.id).into_flat_iter() {
self.types.get_mut(t).unwrap().undraw(active);
}
+27 -9
View File
@@ -44,7 +44,11 @@ pub trait HasEvents: Sized + UiRsc + HasState {
origin: impl IdLike,
f: impl FnOnce(ControllerId, &mut C, &mut Self) -> T,
) -> Option<T> {
let id = self.events().controllers.nearest_id::<C>(origin.id())?;
let render_handle = self.ui().render_state();
let id = self
.events()
.controllers
.nearest_id::<C>(origin.id(), &render_handle.get())?;
self.with_controller(id, |controller, rsc| f(id, controller, rsc))
}
@@ -54,14 +58,22 @@ pub trait HasEvents: Sized + UiRsc + HasState {
fn run_command(&mut self, command: Command) -> CommandResult {
let revision = self.events().controllers.command_target_revision();
if let Some(boundary) = self.events().controllers.command_boundary()
&& self
.events()
.controllers
.command_target()
.is_none_or(|target| !self.events().controllers.is_below(target.host(), boundary))
{
return CommandResult::Unused;
if let Some(boundary) = self.events().controllers.command_boundary() {
let render_handle = self.ui().render_state();
let outside_boundary =
self.events()
.controllers
.command_target()
.is_none_or(|target| {
!self.events().controllers.is_below(
target.host(),
boundary,
&render_handle.get(),
)
});
if outside_boundary {
return CommandResult::Unused;
}
}
let Some((id, mut controller)) = self.events_mut().controllers.take_command_target() else {
return CommandResult::Unused;
@@ -96,6 +108,12 @@ pub trait RunEvents: HasEvents {
data: <E::Event as Event>::Data<'_>,
state: &mut Self::State,
) {
// Keep the last completed frame read-locked for the whole callback.
// Rsc methods may take further shared reads through `render_state`,
// while any attempt to start a render from an event fails at the
// mutable-borrow boundary instead of exposing an in-progress tree.
let render_handle = self.ui().render_state();
let _render_state = render_handle.get();
let f = self.events_mut().get_type::<E>().run_fn(id);
f(EventCtx { state, data }, self)
}
+12 -12
View File
@@ -1,5 +1,5 @@
use crate::{
UiData, UiRenderState,
Ui, UiData,
render::{
data::{PrimitiveInstance, instance_slot_layout},
texture::GpuTextures,
@@ -180,13 +180,11 @@ impl UiRenderNode {
}
}
pub fn update(
&mut self,
device: &Device,
queue: &Queue,
ui: &mut UiData,
ui_render: &mut UiRenderState,
) -> FrameUpdateStats {
pub fn update(&mut self, device: &Device, queue: &Queue, ui: &mut Ui) -> FrameUpdateStats {
let render_handle = ui.render_state.clone();
let mut render_guard = render_handle.get_mut();
let ui_render = &mut *render_guard;
let ui_data: &mut UiData = ui;
self.active.clear();
for (i, order) in ui_render.layers.iter_mut() {
self.active.push(i);
@@ -233,11 +231,11 @@ impl UiRenderNode {
} else {
false
};
let (entries, dirty) = ui.masks.for_upload();
let (entries, dirty) = ui_data.masks.for_upload();
let masks_resized = self.masks.update(device, queue, entries, dirty);
let (entries, dirty) = ui.move_offsets.for_upload();
let (entries, dirty) = ui_data.move_offsets.for_upload();
let moves_resized = self.move_offsets.update(device, queue, entries, dirty);
let (entries, dirty) = ui.paints.for_upload();
let (entries, dirty) = ui_data.paints.for_upload();
let paints_resized = self.paints.update(device, queue, entries, dirty);
if masks_resized || moves_resized || instances_resized || paints_resized {
self.masks_group = Self::masks_group(
@@ -249,7 +247,9 @@ impl UiRenderNode {
&self.paints,
);
}
let rebuild_main = self.textures.update(&mut ui.textures, &self.rsc_layout);
let rebuild_main = self
.textures
.update(&mut ui_data.textures, &self.rsc_layout);
if rebuild_main {
self.rsc_group = Self::rsc_group(device, &self.rsc_layout, &self.textures);
}
+84 -2
View File
@@ -1,6 +1,11 @@
use crate::{
Mask, MoveOffset, Paints, TextData, Textures, WeakWidget, WidgetId, Widgets, util::TrackedArena,
};
use std::{
cell::{Ref, RefCell, RefMut},
ops::{Deref, DerefMut},
rc::Rc,
};
mod access;
mod active;
@@ -23,6 +28,75 @@ pub struct UiData {
animating: Vec<WidgetId>,
}
#[derive(Clone)]
pub struct RenderHandle {
pub(crate) render_state: Rc<RefCell<UiRenderState>>,
}
impl RenderHandle {
/// The retained result of the last completed frame. The framework holds
/// the corresponding mutable borrow for the whole of a render update, so
/// a read attempted while that state is incomplete fails at the boundary
/// instead of observing half a frame.
pub fn get(&self) -> Ref<'_, UiRenderState> {
self.render_state
.try_borrow()
.expect("render state cannot be read while a frame is being rendered")
}
pub(crate) fn get_mut(&self) -> RefMut<'_, UiRenderState> {
self.render_state
.try_borrow_mut()
.expect("render state cannot be mutated while it is being read")
}
}
impl Default for RenderHandle {
fn default() -> Self {
Self {
render_state: Rc::new(RefCell::new(UiRenderState::new())),
}
}
}
#[derive(Default)]
pub struct Ui {
data: UiData,
pub(crate) render_state: RenderHandle,
}
impl Ui {
/// A read-only handle to the retained result of the last completed frame.
/// The handle is owned so a caller may keep its read guard while mutating
/// unrelated resources on the `Rsc` that owns this `Ui`.
pub fn render_state(&self) -> RenderHandle {
self.render_state.clone()
}
pub fn resize(&self, size: impl Into<crate::util::Vec2>) {
self.render_state.get_mut().resize(size);
}
pub fn set_density(&mut self, density: f32) {
self.data.text.density = density;
self.render_state.get_mut().set_density(density);
}
}
impl Deref for Ui {
type Target = UiData;
fn deref(&self) -> &Self::Target {
&self.data
}
}
impl DerefMut for Ui {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.data
}
}
impl UiData {
/// Ask for `id`'s [`crate::Widget::tick`] to run every frame until it
/// says it is done. Idempotent -- registering an already-animating
@@ -48,8 +122,16 @@ impl UiData {
}
pub trait UiRsc {
fn ui(&self) -> &UiData;
fn ui_mut(&mut self) -> &mut UiData;
fn ui(&self) -> &Ui;
fn ui_mut(&mut self) -> &mut Ui;
fn draw<'a>(&mut self, root: impl Into<Option<&'a crate::StrongWidget>>)
where
Self: Sized,
{
let render_state = self.ui().render_state.clone();
render_state.get_mut().update(root, self);
}
#[allow(unused_variables)]
fn on_add(&mut self, id: WeakWidget) {}
+28 -25
View File
@@ -1,6 +1,6 @@
use crate::{
Axis, Len, MoveOffset, PaintId, RegionAlign, RenderedText, Size, StrongWidget, TextAttrs,
TextBuffer, TextData, TextureHandle, UiRegion, UiRenderState, UiRsc, UiScalar, UiVec2,
TextBuffer, TextData, TextureHandle, UiData, UiRegion, UiRenderState, UiRsc, UiScalar, UiVec2,
WidgetId,
render::{
Drawn, GlyphPrimitive, IMAGE_BINDING, Mask, MaskIdx, MoveIdx, NOT_DRAWN, Primitive,
@@ -11,7 +11,7 @@ use crate::{
};
pub struct Painter<'a> {
pub(super) state: &'a mut UiRenderState,
pub(super) render_state: &'a mut UiRenderState,
pub(super) rsc: &'a mut dyn UiRsc,
pub(super) region: UiRegion,
@@ -46,7 +46,7 @@ impl DrawResult<'_, '_> {
if !self.painter.size_dependencies.contains(&self.child) {
self.painter.size_dependencies.push(self.child);
}
self.painter.state.active[&self.child].size
self.painter.render_state.active[&self.child].size
}
}
@@ -94,10 +94,10 @@ impl<'a> Painter<'a> {
};
let h = match self.take_recycled(P::BINDING, drawn) {
Some(h) => {
self.state.primitives.recycle(&h, inst);
self.render_state.primitives.recycle(&h, inst);
h
}
None => self.state.write_primitive(self.layer, drawn, inst),
None => self.render_state.write_primitive(self.layer, drawn, inst),
};
if self.mask != MaskIdx::NONE {
self.rsc.ui_mut().masks.push_ref(self.mask);
@@ -109,7 +109,7 @@ impl<'a> Painter<'a> {
/// Take ownership of a handle this widget just wrote.
fn own(&mut self, h: PrimitiveHandle) {
self.state
self.render_state
.primitives
.set_handle_index(h.slot, self.primitives.len() as u32);
self.primitives.push(h);
@@ -156,7 +156,7 @@ impl<'a> Painter<'a> {
/// with no radius argument anywhere that could fall out of step with
/// the one being drawn.
pub fn set_mask_to_widget<W: ?Sized>(&mut self, shape: &StrongWidget<W>) {
let slot = self.state.first_primitive(shape.id()).unwrap_or_else(|| {
let slot = self.render_state.first_primitive(shape.id()).unwrap_or_else(|| {
panic!(
"'{}' was given as a mask's shape but drew no primitive, so there is nothing to \
clip to",
@@ -172,7 +172,7 @@ impl<'a> Painter<'a> {
"set_mask called twice while drawing one widget: the second would replace the first \
rather than nest inside it",
);
let binding = self.state.primitives.instance(shape).binding;
let binding = self.render_state.primitives.instance(shape).binding;
assert_eq!(
binding,
RectPrimitive::BINDING,
@@ -248,7 +248,7 @@ impl<'a> Painter<'a> {
let next = [offset.x, offset.y];
if self.rsc.ui().move_offsets[slot.idx()].delta != next {
self.rsc.ui_mut().move_offsets.get_mut(slot).delta = next;
self.state.note_move();
self.render_state.note_move();
}
}
@@ -262,7 +262,10 @@ impl<'a> Painter<'a> {
{
None
} else {
self.state.active.get(&id.id()).map(|a| a.size.axis(axis))
self.render_state
.active
.get(&id.id())
.map(|a| a.size.axis(axis))
};
if len.is_some() && !self.size_dependencies.contains(&id.id()) {
self.size_dependencies.push(id.id());
@@ -277,7 +280,7 @@ impl<'a> Painter<'a> {
) -> DrawResult<'p, 'a> {
self.children.push(id.id());
let parent_move_slot = self.child_move_slot.unwrap_or(self.move_slot);
self.state.draw_inner(
self.render_state.draw_inner(
self.layer,
id.id(),
region,
@@ -300,16 +303,16 @@ impl<'a> Painter<'a> {
) -> DrawResult<'p, 'a> {
let region = region.within(&self.region);
let retained = self
.state
.render_state
.active
.get(&id.id())
.map(|active| (active.layer, active.mask));
if self.state.place(id.id(), region, self.rsc).is_some() {
if self.render_state.place(id.id(), region, self.rsc).is_some() {
} else if let Some((layer, mask)) = retained {
self.children.push(id.id());
self.rsc.widgets_mut().needs_redraw.insert(id.id());
let parent_move_slot = self.child_move_slot.unwrap_or(self.move_slot);
self.state.draw_inner(
self.render_state.draw_inner(
layer,
id.id(),
region,
@@ -322,7 +325,7 @@ impl<'a> Painter<'a> {
} else {
self.children.push(id.id());
let parent_move_slot = self.child_move_slot.unwrap_or(self.move_slot);
self.state.draw_inner(
self.render_state.draw_inner(
self.layer,
id.id(),
region,
@@ -385,7 +388,7 @@ impl<'a> Painter<'a> {
fn write_image(&mut self, texture_idx: u32, region: UiRegion) {
let h = match self.take_recycled(IMAGE_BINDING, Drawn::Yes) {
Some(h) => {
self.state.primitives.recycle_image(
self.render_state.primitives.recycle_image(
&h,
self.id,
texture_idx,
@@ -395,7 +398,7 @@ impl<'a> Painter<'a> {
);
h
}
None => self.state.write_image(
None => self.render_state.write_image(
self.layer,
self.id,
texture_idx,
@@ -416,9 +419,9 @@ impl<'a> Painter<'a> {
attrs: &TextAttrs,
width: Option<f32>,
) -> RenderedText {
let density = self.state.density;
self.state.shape_count += 1;
let ui = self.rsc.ui_mut();
let density = self.render_state.density;
self.render_state.shape_count += 1;
let ui: &mut UiData = self.rsc.ui_mut();
ui.text
.render(buffer, attrs, width, &mut ui.textures, density)
}
@@ -477,17 +480,17 @@ impl<'a> Painter<'a> {
}
pub fn output_size(&self) -> Vec2 {
self.state.output_size
self.render_state.output_size
}
/// Physical pixels per `dp` -- see `UiRenderState::density`'s field
/// doc. What `Len::dp`'s `apply_rest` call resolves against.
pub fn density(&self) -> f32 {
self.state.density
self.render_state.density
}
pub fn px_size(&mut self) -> Vec2 {
self.region.size().to_abs(self.state.output_size)
self.region.size().to_abs(self.render_state.output_size)
}
pub fn text_data(&mut self) -> &mut TextData {
@@ -495,11 +498,11 @@ impl<'a> Painter<'a> {
}
pub fn child_layer(&mut self) {
self.layer = self.state.layers.child(self.layer);
self.layer = self.render_state.layers.child(self.layer);
}
pub fn next_layer(&mut self) {
self.layer = self.state.layers.next(self.layer);
self.layer = self.render_state.layers.next(self.layer);
}
pub fn label(&self) -> &str {
+4 -4
View File
@@ -441,7 +441,7 @@ impl UiRenderState {
]
});
let mut painter = Painter {
state: self,
render_state: self,
region,
mask,
move_slot,
@@ -466,7 +466,7 @@ impl UiRenderState {
widget.size_hint(Axis::X).map(|len| len.fold_dp(density)),
widget.size_hint(Axis::Y).map(|len| len.fold_dp(density)),
];
painter.state.draw_count += 1;
painter.render_state.draw_count += 1;
widget.draw(&mut painter);
let size = painter.size.unwrap_or_else(|| {
panic!(
@@ -491,10 +491,10 @@ impl UiRenderState {
);
}
drop(widget);
painter.state.draw_started.remove(&id);
painter.render_state.draw_started.remove(&id);
let Painter {
state: _,
render_state: _,
rsc: _,
region,
mask: _,