FIX SIZE CACHE

This commit is contained in:
2025-12-17 00:09:20 -05:00
parent ecbb9e56e2
commit 1363f31fcd
9 changed files with 144 additions and 178 deletions

View File

@@ -68,3 +68,48 @@ impl Vec2 {
} }
} }
} }
pub const trait AxisT {
fn get() -> Axis;
}
pub struct XAxis;
impl const AxisT for XAxis {
fn get() -> Axis {
Axis::X
}
}
pub struct YAxis;
impl const AxisT for YAxis {
fn get() -> Axis {
Axis::Y
}
}
#[derive(Clone, Copy, Debug, Default)]
pub struct BothAxis<T> {
pub x: T,
pub y: T,
}
impl<T> BothAxis<T> {
pub const fn axis<A: const AxisT>(&mut self) -> &mut T {
match A::get() {
Axis::X => &mut self.x,
Axis::Y => &mut self.y,
}
}
pub fn take_axis<A: const AxisT>(self) -> T {
match A::get() {
Axis::X => self.x,
Axis::Y => self.y,
}
}
pub fn axis_dyn(&mut self, axis: Axis) -> &mut T {
match axis {
Axis::X => &mut self.x,
Axis::Y => &mut self.y,
}
}
}

View File

@@ -58,7 +58,7 @@ pub type TextBuffer = Buffer;
impl Default for TextAttrs { impl Default for TextAttrs {
fn default() -> Self { fn default() -> Self {
let size = 14.0; let size = 16.0;
Self { Self {
color: UiColor::WHITE, color: UiColor::WHITE,
font_size: size, font_size: size,

View File

@@ -1,4 +1,4 @@
use crate::{LayerId, Len, MaskIdx, PrimitiveHandle, TextureHandle, UiRegion, UiVec2, WidgetId}; use crate::{LayerId, MaskIdx, PrimitiveHandle, TextureHandle, UiRegion, WidgetId};
/// important non rendering data for retained drawing /// important non rendering data for retained drawing
#[derive(Debug)] #[derive(Debug)]
@@ -9,15 +9,6 @@ pub struct ActiveData {
pub textures: Vec<TextureHandle>, pub textures: Vec<TextureHandle>,
pub primitives: Vec<PrimitiveHandle>, pub primitives: Vec<PrimitiveHandle>,
pub children: Vec<WidgetId>, pub children: Vec<WidgetId>,
pub resize: ResizeRef,
pub mask: MaskIdx, pub mask: MaskIdx,
pub layer: LayerId, pub layer: LayerId,
} }
/// stores information for children about the highest level parent that needed their size
/// so that they can redraw the parent if their size changes
#[derive(Clone, Copy, Debug, Default)]
pub struct ResizeRef {
pub x: Option<(WidgetId, (UiVec2, Len))>,
pub y: Option<(WidgetId, (UiVec2, Len))>,
}

18
core/src/ui/cache.rs Normal file
View File

@@ -0,0 +1,18 @@
use crate::{BothAxis, Len, UiVec2, WidgetId, util::HashMap};
#[derive(Default)]
pub struct Cache {
pub size: BothAxis<HashMap<WidgetId, (UiVec2, Len)>>,
}
impl Cache {
pub fn remove(&mut self, id: WidgetId) {
self.size.x.remove(&id);
self.size.y.remove(&id);
}
pub fn clear(&mut self) {
self.size.x.clear();
self.size.y.clear();
}
}

View File

@@ -1,16 +1,12 @@
use crate::{ use crate::{
ActiveData, Len, Painter, SizeCtx, Ui, UiRegion, UiVec2, WidgetId, ActiveData, Axis, Painter, SizeCtx, Ui, UiRegion, UiVec2, WidgetId, render::MaskIdx,
render::MaskIdx, util::HashSet,
ui::ResizeRef,
util::{HashMap, HashSet},
}; };
use std::ops::{Deref, DerefMut}; use std::ops::{Deref, DerefMut};
/// state maintained between widgets during painting /// state maintained between widgets during painting
pub struct DrawState<'a, State> { pub struct DrawState<'a, State> {
pub(super) ui: &'a mut Ui<State>, pub(super) ui: &'a mut Ui<State>,
cache_width: HashMap<WidgetId, (UiVec2, Len)>,
cache_height: HashMap<WidgetId, (UiVec2, Len)>,
draw_started: HashSet<WidgetId>, draw_started: HashSet<WidgetId>,
} }
@@ -18,8 +14,6 @@ impl<'a, State: 'static> DrawState<'a, State> {
pub fn new(ui: &'a mut Ui<State>) -> Self { pub fn new(ui: &'a mut Ui<State>) -> Self {
Self { Self {
ui, ui,
cache_width: Default::default(),
cache_height: Default::default(),
draw_started: Default::default(), draw_started: Default::default(),
} }
} }
@@ -39,64 +33,23 @@ impl<'a, State: 'static> DrawState<'a, State> {
if self.draw_started.contains(&id) { if self.draw_started.contains(&id) {
return; return;
} }
let Some(active) = self.ui.active.get(&id) else { // check if parent depends on the desired size of this, if so then redraw it first
for axis in [Axis::X, Axis::Y] {
if let Some(&(outer, old)) = self.cache.size.axis_dyn(axis).get(&id)
&& let Some(current) = self.active.get(&id)
&& let Some(pid) = current.parent
{
self.cache.size.axis_dyn(axis).remove(&id);
let new = self.size_ctx(id, outer).len_axis(id, axis);
self.cache.size.axis_dyn(axis).insert(id, (outer, new));
if new != old {
self.redraw(pid);
}
}
}
if self.draw_started.contains(&id) {
return; return;
};
let mut resize = active.resize;
// set resize back after redrawing
let finish = |s: &mut Self, resize| {
if let Some(active) = s.active.get_mut(&id) {
// might need to get_or_insert here instead of just assuming
active.resize = resize;
}
};
// check if a parent depends on the desired size of this, if so then redraw it first
// TODO: this is stupid having 2 of these, don't ask me what the consequences are
let mut ret = false;
if let Some((rid, (outer, old_desired))) = &mut resize.x {
let new_desired = self
.size_ctx(
id,
*outer,
id,
&mut Default::default(),
&mut Default::default(),
)
.width_inner(id);
if new_desired != *old_desired {
// unsure if I need to walk down the tree here
self.redraw(*rid);
*old_desired = new_desired;
if self.draw_started.contains(&id) {
ret = true;
}
}
}
if let Some((rid, (outer, old_desired))) = &mut resize.y {
// NOTE: might need hack in Span here (or also do it properly here)
let new_desired = self
.size_ctx(
id,
*outer,
id,
&mut Default::default(),
&mut Default::default(),
)
.height_inner(id);
if new_desired != *old_desired {
self.redraw(*rid);
*old_desired = new_desired;
if self.draw_started.contains(&id) {
ret = true;
}
}
}
if ret {
return finish(self, resize);
} }
let Some(active) = self.remove(id, false) else { let Some(active) = self.remove(id, false) else {
@@ -111,43 +64,36 @@ impl<'a, State: 'static> DrawState<'a, State> {
active.mask, active.mask,
Some(active.children), Some(active.children),
); );
finish(self, resize);
} }
pub(super) fn size_ctx<'b>( pub(super) fn size_ctx<'b>(
&'b mut self, &'b mut self,
source: WidgetId, source: WidgetId,
outer: UiVec2, outer: UiVec2,
id: WidgetId,
checked_width: &'b mut HashMap<WidgetId, (UiVec2, Len)>,
checked_height: &'b mut HashMap<WidgetId, (UiVec2, Len)>,
) -> SizeCtx<'b, State> { ) -> SizeCtx<'b, State> {
SizeCtx { SizeCtx {
source, source,
cache_width: &mut self.cache_width, cache: &mut self.ui.cache,
cache_height: &mut self.cache_height,
text: &mut self.ui.text, text: &mut self.ui.text,
textures: &mut self.ui.textures, textures: &mut self.ui.textures,
widgets: &self.ui.widgets, widgets: &self.ui.widgets,
outer, outer,
output_size: self.ui.output_size, output_size: self.ui.output_size,
checked_width, id: source,
checked_height,
id,
} }
} }
pub fn redraw_all(&mut self) { pub fn redraw_all(&mut self) {
// update event managers // free all resources & cache
for (id, active) in self.ui.active.drain() { for (id, active) in self.ui.active.drain() {
let data = self.ui.widgets.data(id).unwrap(); let data = self.ui.widgets.data(id).unwrap();
self.ui.events.undraw(data, &active); self.ui.events.undraw(data, &active);
} }
// free before bc nothing should exist self.ui.cache.clear();
self.free(); self.free();
self.layers.clear(); self.layers.clear();
self.widgets.needs_redraw.clear(); self.widgets.needs_redraw.clear();
if let Some(id) = &self.ui.root { if let Some(id) = &self.ui.root {
self.draw_inner(0, id.id(), UiRegion::FULL, None, MaskIdx::NONE, None); self.draw_inner(0, id.id(), UiRegion::FULL, None, MaskIdx::NONE, None);
} }
@@ -175,7 +121,6 @@ impl<'a, State: 'static> DrawState<'a, State> {
// ); // );
// } // }
let mut old_children = old_children.unwrap_or_default(); let mut old_children = old_children.unwrap_or_default();
let mut resize = ResizeRef::default();
if let Some(active) = self.ui.active.get_mut(&id) if let Some(active) = self.ui.active.get_mut(&id)
&& !self.ui.widgets.needs_redraw.contains(&id) && !self.ui.widgets.needs_redraw.contains(&id)
{ {
@@ -194,7 +139,6 @@ impl<'a, State: 'static> DrawState<'a, State> {
// if not, then maintain resize and track old children to remove unneeded // if not, then maintain resize and track old children to remove unneeded
let active = self.remove(id, false).unwrap(); let active = self.remove(id, false).unwrap();
old_children = active.children; old_children = active.children;
resize = active.resize;
} }
// draw widget // draw widget
@@ -209,8 +153,6 @@ impl<'a, State: 'static> DrawState<'a, State> {
textures: Vec::new(), textures: Vec::new(),
primitives: Vec::new(), primitives: Vec::new(),
children: Vec::new(), children: Vec::new(),
children_width: Default::default(),
children_height: Default::default(),
}; };
let mut widget = painter.state.widgets.get_dyn_dynamic(id); let mut widget = painter.state.widgets.get_dyn_dynamic(id);
@@ -224,8 +166,6 @@ impl<'a, State: 'static> DrawState<'a, State> {
textures, textures,
primitives, primitives,
children, children,
children_width,
children_height,
layer, layer,
id, id,
} = painter; } = painter;
@@ -238,27 +178,10 @@ impl<'a, State: 'static> DrawState<'a, State> {
textures, textures,
primitives, primitives,
children, children,
resize,
mask, mask,
layer, layer,
}; };
// set resize for children who's size this widget depends on
for (cid, outer) in children_width {
if let Some(w) = self.active.get_mut(&cid)
&& w.resize.x.is_none()
{
w.resize.x = Some((id, outer))
}
}
for (cid, outer) in children_height {
if let Some(w) = self.active.get_mut(&cid)
&& w.resize.y.is_none()
{
w.resize.y = Some((id, outer))
}
}
// remove old children that weren't kept // remove old children that weren't kept
for c in &old_children { for c in &old_children {
if !active.children.contains(c) { if !active.children.contains(c) {
@@ -308,6 +231,7 @@ impl<'a, State: 'static> DrawState<'a, State> {
} }
fn remove_rec(&mut self, id: WidgetId) -> Option<ActiveData> { fn remove_rec(&mut self, id: WidgetId) -> Option<ActiveData> {
self.cache.remove(id);
let inst = self.remove(id, true); let inst = self.remove(id, true);
if let Some(inst) = &inst { if let Some(inst) = &inst {
for c in &inst.children { for c in &inst.children {

View File

@@ -11,12 +11,14 @@ use std::{
}; };
mod active; mod active;
mod cache;
mod draw_state; mod draw_state;
mod painter; mod painter;
mod size; mod size;
mod state; mod state;
pub use active::*; pub use active::*;
use cache::*;
pub use painter::Painter; pub use painter::Painter;
pub use size::*; pub use size::*;
@@ -32,6 +34,7 @@ pub struct Ui<State> {
pub text: TextData, pub text: TextData,
output_size: Vec2, output_size: Vec2,
pub masks: TrackedArena<Mask, u32>, pub masks: TrackedArena<Mask, u32>,
pub cache: Cache,
root: Option<WidgetHandle<State>>, root: Option<WidgetHandle<State>>,
recv: Receiver<WidgetId>, recv: Receiver<WidgetId>,
@@ -202,6 +205,7 @@ impl<State: 'static> Default for Ui<State> {
masks: Default::default(), masks: Default::default(),
text: Default::default(), text: Default::default(),
textures: Default::default(), textures: Default::default(),
cache: Default::default(),
output_size: Vec2::ZERO, output_size: Vec2::ZERO,
root: Default::default(), root: Default::default(),
full_redraw: false, full_redraw: false,

View File

@@ -1,9 +1,9 @@
use crate::{ use crate::{
Axis, Len, RenderedText, Size, SizeCtx, TextAttrs, TextBuffer, TextData, TextureHandle, Axis, Len, RenderedText, Size, SizeCtx, TextAttrs, TextBuffer, TextData, TextureHandle,
UiRegion, UiVec2, WidgetHandle, WidgetId, UiRegion, Widget, WidgetHandle, WidgetId,
render::{Mask, MaskIdx, Primitive, PrimitiveHandle, PrimitiveInst}, render::{Mask, MaskIdx, Primitive, PrimitiveHandle, PrimitiveInst},
ui::draw_state::DrawState, ui::draw_state::DrawState,
util::{HashMap, Vec2}, util::Vec2,
}; };
/// makes your surfaces look pretty /// makes your surfaces look pretty
@@ -15,8 +15,6 @@ pub struct Painter<'a, 'b, State> {
pub(super) textures: Vec<TextureHandle>, pub(super) textures: Vec<TextureHandle>,
pub(super) primitives: Vec<PrimitiveHandle>, pub(super) primitives: Vec<PrimitiveHandle>,
pub(super) children: Vec<WidgetId>, pub(super) children: Vec<WidgetId>,
pub(super) children_width: HashMap<WidgetId, (UiVec2, Len)>,
pub(super) children_height: HashMap<WidgetId, (UiVec2, Len)>,
pub layer: usize, pub layer: usize,
pub(super) id: WidgetId, pub(super) id: WidgetId,
} }
@@ -97,11 +95,15 @@ impl<'a, 'c, State: 'static> Painter<'a, 'c, State> {
self.region self.region
} }
pub fn size<W: ?Sized>(&mut self, id: &WidgetHandle<State, W>) -> Size { pub fn size<W: ?Sized + Widget<State>>(&mut self, id: &WidgetHandle<State, W>) -> Size {
self.size_ctx().size(id) self.size_ctx().size(id)
} }
pub fn len_axis<W: ?Sized>(&mut self, id: &WidgetHandle<State, W>, axis: Axis) -> Len { pub fn len_axis<W: ?Sized + Widget<State>>(
&mut self,
id: &WidgetHandle<State, W>,
axis: Axis,
) -> Len {
match axis { match axis {
Axis::X => self.size_ctx().width(id), Axis::X => self.size_ctx().width(id),
Axis::Y => self.size_ctx().height(id), Axis::Y => self.size_ctx().height(id),
@@ -137,12 +139,6 @@ impl<'a, 'c, State: 'static> Painter<'a, 'c, State> {
} }
pub fn size_ctx(&mut self) -> SizeCtx<'_, State> { pub fn size_ctx(&mut self) -> SizeCtx<'_, State> {
self.state.size_ctx( self.state.size_ctx(self.id, self.region.size())
self.id,
self.region.size(),
self.id,
&mut self.children_width,
&mut self.children_height,
)
} }
} }

View File

@@ -1,7 +1,6 @@
use crate::{ use crate::{
Axis, Len, RenderedText, Size, TextAttrs, TextBuffer, TextData, Textures, UiVec2, WidgetHandle, Axis, AxisT, IdLike, Len, RenderedText, Size, TextAttrs, TextBuffer, TextData, Textures,
WidgetId, Widgets, UiVec2, WidgetAxisFns, WidgetId, Widgets, XAxis, YAxis, ui::cache::Cache, util::Vec2,
util::{HashMap, Vec2},
}; };
pub struct SizeCtx<'a, State> { pub struct SizeCtx<'a, State> {
@@ -9,10 +8,7 @@ pub struct SizeCtx<'a, State> {
pub textures: &'a mut Textures, pub textures: &'a mut Textures,
pub(super) source: WidgetId, pub(super) source: WidgetId,
pub(super) widgets: &'a Widgets<State>, pub(super) widgets: &'a Widgets<State>,
pub(super) cache_width: &'a mut HashMap<WidgetId, (UiVec2, Len)>, pub(super) cache: &'a mut Cache,
pub(super) cache_height: &'a mut HashMap<WidgetId, (UiVec2, Len)>,
pub(super) checked_width: &'a mut HashMap<WidgetId, (UiVec2, Len)>,
pub(super) checked_height: &'a mut HashMap<WidgetId, (UiVec2, Len)>,
/// TODO: should this be pub? rn used for sized /// TODO: should this be pub? rn used for sized
pub outer: UiVec2, pub outer: UiVec2,
pub(super) output_size: Vec2, pub(super) output_size: Vec2,
@@ -28,65 +24,44 @@ impl<State: 'static> SizeCtx<'_, State> {
&self.source &self.source
} }
pub(super) fn width_inner(&mut self, id: WidgetId) -> Len { pub(super) fn len_inner<A: const AxisT>(&mut self, id: WidgetId) -> Len {
// first check cache if let Some((_, len)) = self.cache.size.axis::<A>().get(&id) {
// TODO: is this needed? broken rn bc does not store children during upper size check, return *len;
// so if something actually using check_* hits cache it fails to add them }
// if let Some(&(outer, len)) = self.cache_width.get(&id) let len = self
// && outer == self.outer .widgets
// { .get_dyn_dynamic(id)
// self.checked_width.insert(id, (self.outer, len)); .desired_len::<A>(&mut SizeCtx {
// return len; text: self.text,
// } textures: self.textures,
// store self vars that need to be maintained source: self.source,
let self_outer = self.outer; widgets: self.widgets,
let self_id = self.id; cache: self.cache,
// get size of input id outer: self.outer,
self.id = id; output_size: self.output_size,
let len = self.widgets.get_dyn_dynamic(id).desired_width(self); id,
// restore vars & update cache + checked });
self.outer = self_outer; self.cache.size.axis::<A>().insert(id, (self.outer, len));
self.id = self_id;
self.cache_width.insert(id, (self.outer, len));
self.checked_width.insert(id, (self.outer, len));
len len
} }
// TODO: should be refactored to share code w width_inner pub fn width(&mut self, id: impl IdLike<State>) -> Len {
pub(super) fn height_inner(&mut self, id: WidgetId) -> Len { self.len_inner::<XAxis>(id.id())
// if let Some(&(outer, len)) = self.cache_height.get(&id)
// && outer == self.outer
// {
// self.checked_height.insert(id, (self.outer, len));
// return len;
// }
let self_outer = self.outer;
let self_id = self.id;
self.id = id;
let len = self.widgets.get_dyn_dynamic(id).desired_height(self);
self.outer = self_outer;
self.id = self_id;
self.cache_height.insert(id, (self.outer, len));
self.checked_height.insert(id, (self.outer, len));
len
} }
pub fn width<W: ?Sized>(&mut self, id: &WidgetHandle<State, W>) -> Len { pub fn height(&mut self, id: impl IdLike<State>) -> Len {
self.width_inner(id.id()) self.len_inner::<YAxis>(id.id())
} }
pub fn height<W: ?Sized>(&mut self, id: &WidgetHandle<State, W>) -> Len { pub fn len_axis(&mut self, id: impl IdLike<State>, axis: Axis) -> Len {
self.height_inner(id.id())
}
pub fn len_axis<W: ?Sized>(&mut self, id: &WidgetHandle<State, W>, axis: Axis) -> Len {
match axis { match axis {
Axis::X => self.width(id), Axis::X => self.width(id),
Axis::Y => self.height(id), Axis::Y => self.height(id),
} }
} }
pub fn size<W: ?Sized>(&mut self, id: &WidgetHandle<State, W>) -> Size { pub fn size(&mut self, id: impl IdLike<State>) -> Size {
let id = id.id();
Size { Size {
x: self.width(id), x: self.width(id),
y: self.height(id), y: self.height(id),

View File

@@ -1,4 +1,4 @@
use crate::{Len, Painter, SizeCtx, Ui}; use crate::{Axis, AxisT, Len, Painter, SizeCtx, Ui};
use std::any::Any; use std::any::Any;
mod data; mod data;
@@ -19,6 +19,19 @@ pub trait Widget<State>: Any {
fn desired_height(&mut self, ctx: &mut SizeCtx<State>) -> Len; fn desired_height(&mut self, ctx: &mut SizeCtx<State>) -> Len;
} }
pub trait WidgetAxisFns<State> {
fn desired_len<A: AxisT>(&mut self, ctx: &mut SizeCtx<State>) -> Len;
}
impl<State, W: Widget<State> + ?Sized> WidgetAxisFns<State> for W {
fn desired_len<A: AxisT>(&mut self, ctx: &mut SizeCtx<State>) -> Len {
match A::get() {
Axis::X => self.desired_width(ctx),
Axis::Y => self.desired_height(ctx),
}
}
}
impl<State> Widget<State> for () { impl<State> Widget<State> for () {
fn draw(&mut self, _: &mut Painter<State>) {} fn draw(&mut self, _: &mut Painter<State>) {}
fn desired_width(&mut self, _: &mut SizeCtx<State>) -> Len { fn desired_width(&mut self, _: &mut SizeCtx<State>) -> Len {