From 1c49db1b8944517cf6870bd9d158a92bb2ee17c5 Mon Sep 17 00:00:00 2001 From: shadow cat Date: Mon, 10 Nov 2025 14:45:22 -0500 Subject: [PATCH] initial mask impl --- TODO | 6 +++ src/core/mask.rs | 12 +++++ src/core/mod.rs | 2 + src/core/trait_fns.rs | 14 ++++-- src/layout/layer.rs | 18 ++----- src/layout/mask.rs | 46 +++++++++++++++++ src/layout/painter.rs | 60 +++++++++++++--------- src/render/data.rs | 29 ++++++++--- src/render/mod.rs | 34 +++++++++++-- src/render/primitive.rs | 27 +++++++--- src/render/shader.wgsl | 31 ++++++++++-- src/testing/mod.rs | 2 +- src/util/arena.rs | 109 ++++++++++++++++++++++++++++++++++++++++ src/util/id.rs | 80 ++++++++++++++++++++++++----- src/util/mod.rs | 2 + 15 files changed, 398 insertions(+), 74 deletions(-) create mode 100644 src/core/mask.rs create mode 100644 src/layout/mask.rs create mode 100644 src/util/arena.rs diff --git a/TODO b/TODO index da9ade1..b0e9aa9 100644 --- a/TODO +++ b/TODO @@ -4,6 +4,9 @@ images text figure out ways to speed up / what costs the most resizing (per frame) is really slow (assuming painter isn't griefing) + bug where x offset doesn't shift texture correctly (eg typing a vs j) + +masks r just made to bare minimum work scaling could be just a simple scaling factor that multiplies abs @@ -30,3 +33,6 @@ really weird limitation: consider unsafe cell for layer transmute stuff I don't think it's needed bc of usage but I don't know optimizations :skull: + +tags +vecs for each widget type? diff --git a/src/core/mask.rs b/src/core/mask.rs new file mode 100644 index 0000000..bd977d7 --- /dev/null +++ b/src/core/mask.rs @@ -0,0 +1,12 @@ +use crate::prelude::*; + +pub struct Masked { + pub inner: WidgetId, +} + +impl Widget for Masked { + fn draw(&mut self, painter: &mut Painter) { + painter.set_mask(painter.region()); + painter.widget(&self.inner); + } +} diff --git a/src/core/mod.rs b/src/core/mod.rs index 4f393d5..9caa8b2 100644 --- a/src/core/mod.rs +++ b/src/core/mod.rs @@ -1,4 +1,5 @@ mod image; +mod mask; mod position; mod rect; mod sense; @@ -6,6 +7,7 @@ mod text; mod trait_fns; pub use image::*; +pub use mask::*; pub use position::*; pub use rect::*; pub use sense::*; diff --git a/src/core/trait_fns.rs b/src/core/trait_fns.rs index 8f8e3ec..a009698 100644 --- a/src/core/trait_fns.rs +++ b/src/core/trait_fns.rs @@ -9,6 +9,7 @@ pub trait CoreWidget { fn sized(self, size: impl Into) -> impl WidgetFn; fn offset(self, amt: impl Into) -> impl WidgetFn; fn scroll(self) -> impl WidgetIdFn; + fn masked(self) -> impl WidgetFn; } impl, Tag> CoreWidget for W { @@ -53,9 +54,16 @@ impl, Tag> CoreWidget for W { } fn scroll(self) -> impl WidgetIdFn { - self.offset(UiVec2::ZERO).edit_on(CursorSense::Scroll, |w, data| { - w.amt += UiVec2::abs(data.scroll_delta * 50.0); - }) + self.offset(UiVec2::ZERO) + .edit_on(CursorSense::Scroll, |w, data| { + w.amt += UiVec2::abs(data.scroll_delta * 50.0); + }) + } + + fn masked(self) -> impl WidgetFn { + move |ui| Masked { + inner: self.add(ui).any(), + } } } diff --git a/src/layout/layer.rs b/src/layout/layer.rs index b8233b9..ed57939 100644 --- a/src/layout/layer.rs +++ b/src/layout/layer.rs @@ -1,10 +1,6 @@ use std::ops::{Index, IndexMut}; -use crate::{ - layout::UiRegion, - render::{Primitive, PrimitiveHandle, Primitives}, - util::Id, -}; +use crate::render::{MaskIdx, Primitive, PrimitiveHandle, PrimitiveInst, Primitives}; struct LayerNode { next: Ptr, @@ -109,17 +105,11 @@ impl Layers { LayerIndexIterator::new(&self.vec, self.last) } - pub fn write( - &mut self, - layer: usize, - id: Id, - primitive: P, - region: UiRegion, - ) -> PrimitiveHandle { - self[layer].primitives.write(layer, id, primitive, region) + pub fn write(&mut self, layer: usize, info: PrimitiveInst

) -> PrimitiveHandle { + self[layer].primitives.write(layer, info) } - pub fn free(&mut self, h: &PrimitiveHandle) { + pub fn free(&mut self, h: &PrimitiveHandle) -> MaskIdx { self[h.layer].primitives.free(h) } } diff --git a/src/layout/mask.rs b/src/layout/mask.rs new file mode 100644 index 0000000..2860ac5 --- /dev/null +++ b/src/layout/mask.rs @@ -0,0 +1,46 @@ +//! tree structure for masking + +use crate::layout::UiRegion; + +pub struct Masks { + data: Vec, +} + +#[repr(C)] +#[derive(Clone, Copy)] +pub struct MaskPtr(u32); + +#[repr(C)] +pub struct MaskNode { + /// TODO: this is just a rect for now, + /// but would like to support arbitrary masks + /// at some point; custom shader + /// would probably handle that case + /// bc you'd need to render to a special target + /// anyways + region: UiRegion, + prev: MaskPtr, +} + +impl MaskPtr { + const NONE: Self = Self(u32::MAX); +} + +impl Masks { + pub fn push(&mut self, parent: MaskPtr, region: UiRegion) -> MaskPtr { + match parent.0 { + _ => { + } + u32::MAX => { + let i = self.data.len(); + self.data.push(MaskNode { + region, + prev: parent, + }); + MaskPtr(i as u32) + } + } + } + + pub fn pop(&mut self, i: usize) {} +} diff --git a/src/layout/painter.rs b/src/layout/painter.rs index 192de2b..c9b2f68 100644 --- a/src/layout/painter.rs +++ b/src/layout/painter.rs @@ -3,13 +3,14 @@ use crate::{ Layers, Modules, TextAttrs, TextBuffer, TextData, TextTexture, TextureHandle, Textures, UiRegion, UiVec2, Vec2, WidgetId, Widgets, }, - render::{Primitive, PrimitiveHandle}, - util::{HashMap, HashSet, Id}, + render::{Mask, MaskIdx, Primitive, PrimitiveHandle, PrimitiveInst}, + util::{HashMap, HashSet, Id, TrackedArena}, }; pub struct Painter<'a, 'c> { ctx: &'a mut PainterCtx<'c>, region: UiRegion, + mask: MaskIdx, textures: Vec, primitives: Vec, children: Vec, @@ -25,6 +26,7 @@ pub struct PainterCtx<'a> { pub active: &'a mut HashMap, pub layers: &'a mut Layers, pub textures: &'a mut Textures, + pub masks: &'a mut TrackedArena, pub text: &'a mut TextData, pub screen_size: Vec2, pub modules: &'a mut Modules, @@ -40,9 +42,11 @@ pub struct WidgetInstance { pub primitives: Vec, pub children: Vec, pub resize: Option<(Id, UiVec2)>, + pub mask: MaskIdx, pub layer: usize, } +#[derive(Default)] pub struct PainterData { pub widgets: Widgets, pub active: HashMap, @@ -52,21 +56,7 @@ pub struct PainterData { pub output_size: Vec2, pub modules: Modules, pub px_dependent: HashSet, -} - -impl Default for PainterData { - fn default() -> Self { - Self { - widgets: Widgets::new(), - layers: Default::default(), - textures: Textures::new(), - text: TextData::default(), - active: Default::default(), - output_size: Vec2::ZERO, - modules: Modules::default(), - px_dependent: Default::default(), - } - } + pub masks: TrackedArena, } impl<'a> PainterCtx<'a> { @@ -80,6 +70,7 @@ impl<'a> PainterCtx<'a> { screen_size: data.output_size, modules: &mut data.modules, px_dependent: &mut data.px_dependent, + masks: &mut data.masks, draw_started: HashSet::default(), } } @@ -122,6 +113,7 @@ impl<'a> PainterCtx<'a> { id, active.region, active.parent, + active.mask, Some(active.children), ); self.active.get_mut(&id).unwrap().resize = active.resize; @@ -130,7 +122,7 @@ impl<'a> PainterCtx<'a> { pub fn draw(&mut self, id: Id) { self.draw_started.clear(); self.layers.clear(); - self.draw_inner(0, id, UiRegion::full(), None, None); + self.draw_inner(0, id, UiRegion::full(), None, MaskIdx::NONE, None); } fn draw_inner( @@ -139,6 +131,7 @@ impl<'a> PainterCtx<'a> { id: Id, region: UiRegion, parent: Option, + mask: MaskIdx, old_children: Option>, ) { // I have no idea if these checks work lol @@ -173,6 +166,7 @@ impl<'a> PainterCtx<'a> { let mut painter = Painter { region, + mask, layer, id, textures: Vec::new(), @@ -196,6 +190,7 @@ impl<'a> PainterCtx<'a> { primitives: painter.primitives, children: painter.children, resize, + mask: painter.mask, layer, }; for (cid, size) in sized_children { @@ -238,7 +233,10 @@ impl<'a> PainterCtx<'a> { let mut inst = self.active.remove(&id); if let Some(inst) = &mut inst { for h in &inst.primitives { - self.layers.free(h); + let mask = self.layers.free(h); + if mask != MaskIdx::NONE { + self.masks.remove(mask); + } } inst.textures.clear(); self.textures.free(); @@ -263,10 +261,19 @@ impl<'a> PainterCtx<'a> { impl<'a, 'c> Painter<'a, 'c> { fn primitive_at(&mut self, primitive: P, region: UiRegion) { - let h = self - .ctx - .layers - .write(self.layer, self.id, primitive, region); + let h = self.ctx.layers.write( + self.layer, + PrimitiveInst { + id: self.id, + primitive, + region, + mask_idx: self.mask, + }, + ); + if self.mask != MaskIdx::NONE { + // TODO: I have no clue if this works at all :joy: + self.ctx.masks.push_ref(self.mask); + } self.primitives.push(h); } @@ -279,6 +286,11 @@ impl<'a, 'c> Painter<'a, 'c> { self.primitive_at(primitive, region.within(&self.region)); } + pub fn set_mask(&mut self, region: UiRegion) { + assert!(self.mask == MaskIdx::NONE); + self.mask = self.ctx.masks.push(Mask { region }); + } + /// Draws a widget within this widget's region. pub fn widget(&mut self, id: &WidgetId) { self.widget_at(id, self.region); @@ -293,7 +305,7 @@ impl<'a, 'c> Painter<'a, 'c> { fn widget_at(&mut self, id: &WidgetId, region: UiRegion) { self.children.push(id.id); self.ctx - .draw_inner(self.layer, id.id, region, Some(self.id), None); + .draw_inner(self.layer, id.id, region, Some(self.id), self.mask, None); } pub fn texture_within(&mut self, handle: &TextureHandle, region: UiRegion) { diff --git a/src/render/data.rs b/src/render/data.rs index 27fbebf..05eeb44 100644 --- a/src/render/data.rs +++ b/src/render/data.rs @@ -1,6 +1,5 @@ -use wgpu::VertexAttribute; - -use crate::layout::UiRegion; +use crate::{layout::UiRegion, util::Id}; +use wgpu::*; #[repr(C)] #[derive(Copy, Clone, bytemuck::Pod, bytemuck::Zeroable, Default)] @@ -15,23 +14,37 @@ pub struct PrimitiveInstance { pub region: UiRegion, pub binding: u32, pub idx: u32, + pub mask_idx: MaskIdx, } impl PrimitiveInstance { - const ATTRIBS: [VertexAttribute; 6] = wgpu::vertex_attr_array![ + const ATTRIBS: [VertexAttribute; 7] = vertex_attr_array![ 0 => Float32x2, 1 => Float32x2, 2 => Float32x2, 3 => Float32x2, 4 => Uint32, 5 => Uint32, + 6 => Uint32, ]; - pub fn desc() -> wgpu::VertexBufferLayout<'static> { - wgpu::VertexBufferLayout { - array_stride: std::mem::size_of::() as wgpu::BufferAddress, - step_mode: wgpu::VertexStepMode::Instance, + pub fn desc() -> VertexBufferLayout<'static> { + VertexBufferLayout { + array_stride: std::mem::size_of::() as BufferAddress, + step_mode: VertexStepMode::Instance, attributes: &Self::ATTRIBS, } } } + +pub type MaskIdx = Id; + +impl MaskIdx { + pub const NONE: Self = Self::preset(u32::MAX); +} + +#[repr(C)] +#[derive(Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)] +pub struct Mask { + pub region: UiRegion, +} diff --git a/src/render/mod.rs b/src/render/mod.rs index 0d57e57..d9b9195 100644 --- a/src/render/mod.rs +++ b/src/render/mod.rs @@ -17,6 +17,7 @@ mod primitive; mod texture; mod util; +pub use data::{Mask, MaskIdx}; pub use primitive::*; const SHAPE_SHADER: &str = include_str!("./shader.wgsl"); @@ -33,6 +34,7 @@ pub struct UiRenderer { active: Vec, window_buffer: Buffer, textures: GpuTextures, + masks: ArrBuf, } struct RenderLayer { @@ -99,7 +101,11 @@ impl UiRenderer { } } if self.textures.update(&mut ui.data.textures) { - self.rsc_group = Self::rsc_group(device, &self.rsc_layout, &self.textures) + self.rsc_group = Self::rsc_group(device, &self.rsc_layout, &self.textures, &self.masks) + } + if ui.data.masks.changed { + ui.data.masks.changed = false; + self.masks.update(device, queue, &ui.data.masks[..]); } } @@ -132,7 +138,7 @@ impl UiRenderer { let uniform_layout = device.create_bind_group_layout(&BindGroupLayoutDescriptor { entries: &[BindGroupLayoutEntry { binding: 0, - visibility: ShaderStages::VERTEX, + visibility: ShaderStages::VERTEX | ShaderStages::FRAGMENT, ty: BindingType::Buffer { ty: BufferBindingType::Uniform, has_dynamic_offset: false, @@ -162,8 +168,14 @@ impl UiRenderer { }); let tex_manager = GpuTextures::new(device, queue); + let masks = ArrBuf::new( + device, + BufferUsages::STORAGE | BufferUsages::COPY_DST, + "ui masks", + ); + let rsc_layout = Self::rsc_layout(device, &limits); - let rsc_group = Self::rsc_group(device, &rsc_layout, &tex_manager); + let rsc_group = Self::rsc_group(device, &rsc_layout, &tex_manager, &masks); let pipeline_layout = device.create_pipeline_layout(&PipelineLayoutDescriptor { label: Some("UI Shape Pipeline Layout"), @@ -218,6 +230,7 @@ impl UiRenderer { layers: HashMap::default(), active: Vec::new(), textures: tex_manager, + masks, } } @@ -270,6 +283,16 @@ impl UiRenderer { ty: BindingType::Sampler(SamplerBindingType::NonFiltering), count: Some(NonZero::new(limits.max_samplers).unwrap()), }, + BindGroupLayoutEntry { + binding: 2, + visibility: ShaderStages::FRAGMENT, + ty: BindingType::Buffer { + ty: BufferBindingType::Storage { read_only: true }, + has_dynamic_offset: false, + min_binding_size: None, + }, + count: None, + }, ], label: Some("ui rsc"), }) @@ -279,6 +302,7 @@ impl UiRenderer { device: &Device, layout: &BindGroupLayout, tex_manager: &GpuTextures, + masks: &ArrBuf, ) -> BindGroup { device.create_bind_group(&BindGroupDescriptor { layout, @@ -291,6 +315,10 @@ impl UiRenderer { binding: 1, resource: BindingResource::SamplerArray(&tex_manager.samplers()), }, + BindGroupEntry { + binding: 2, + resource: masks.buffer.as_entire_binding(), + }, ], label: Some("ui rsc"), }) diff --git a/src/render/primitive.rs b/src/render/primitive.rs index 5fa9b41..3dbb1bb 100644 --- a/src/render/primitive.rs +++ b/src/render/primitive.rs @@ -2,7 +2,10 @@ use std::ops::{Deref, DerefMut}; use crate::{ layout::{Color, UiRegion}, - render::{ArrBuf, data::PrimitiveInstance}, + render::{ + ArrBuf, + data::{MaskIdx, PrimitiveInstance}, + }, util::Id, }; use bytemuck::Pod; @@ -95,19 +98,30 @@ macro_rules! primitives { (@count $t:tt) => { 1 }; } +pub struct PrimitiveInst

{ + pub id: Id, + pub primitive: P, + pub region: UiRegion, + pub mask_idx: MaskIdx, +} + impl Primitives { pub fn write( &mut self, layer: usize, - id: Id, - data: P, - region: UiRegion, + PrimitiveInst { + id, + primitive, + region, + mask_idx, + }: PrimitiveInst

, ) -> PrimitiveHandle { let vec = P::vec(&mut self.data); - let i = vec.add(data); + let i = vec.add(primitive); let inst = PrimitiveInstance { region, idx: i as u32, + mask_idx, binding: P::BINDING, }; let inst_i = if let Some(i) = self.free.pop() { @@ -138,9 +152,10 @@ impl Primitives { }) } - pub fn free(&mut self, h: &PrimitiveHandle) { + pub fn free(&mut self, h: &PrimitiveHandle) -> MaskIdx { self.data.free(h.binding, h.data_idx); self.free.push(h.inst_idx); + self.instances[h.inst_idx].mask_idx } pub fn data(&self) -> &PrimitiveData { diff --git a/src/render/shader.wgsl b/src/render/shader.wgsl index 456390b..0861d6b 100644 --- a/src/render/shader.wgsl +++ b/src/render/shader.wgsl @@ -20,10 +20,22 @@ struct TextureInfo { sampler_idx: u32, } +struct Mask { + top_left: UiVec2, + bot_right: UiVec2, +} + +struct UiVec2 { + rel: vec2, + abs: vec2, +} + @group(2) @binding(0) var views: binding_array>; @group(2) @binding(1) var samplers: binding_array; +@group(2) @binding(2) +var masks: array; struct WindowUniform { dim: vec2, @@ -36,6 +48,7 @@ struct InstanceInput { @location(3) bottom_right_offset: vec2, @location(4) binding: u32, @location(5) idx: u32, + @location(6) mask_idx: u32, } struct VertexOutput { @@ -44,6 +57,7 @@ struct VertexOutput { @location(2) uv: vec2, @location(3) binding: u32, @location(4) idx: u32, + @location(5) mask_idx: u32, @builtin(position) clip_position: vec4, }; @@ -76,6 +90,7 @@ fn vs_main( out.idx = in.idx; out.top_left = top_left; out.bot_right = bot_right; + out.mask_idx = in.mask_idx; return out; } @@ -87,17 +102,27 @@ fn fs_main( let pos = in.clip_position.xy; let region = Region(pos, in.uv, in.top_left, in.bot_right); let i = in.idx; + var color: vec4; switch in.binding { case RECT: { - return draw_rounded_rect(region, rects[i]); + color = draw_rounded_rect(region, rects[i]); } case TEXTURE: { - return draw_texture(region, textures[i]); + color = draw_texture(region, textures[i]); } default: { - return vec4(1.0, 0.0, 1.0, 1.0); + color = vec4(1.0, 0.0, 1.0, 1.0); } } + if in.mask_idx != 4294967295u { + let mask = masks[in.mask_idx]; + let top_left = floor(mask.top_left.rel * window.dim) + floor(mask.top_left.abs); + let bot_right = floor(mask.bot_right.rel * window.dim) + floor(mask.bot_right.abs); + if pos.x < top_left.x || pos.x > bot_right.x || pos.y < top_left.y || pos.y > bot_right.y { + color *= 0.0; + } + } + return color; } // TODO: this seems really inefficient (per frag indexing)? diff --git a/src/testing/mod.rs b/src/testing/mod.rs index 8bbbe17..7d7fdc0 100644 --- a/src/testing/mod.rs +++ b/src/testing/mod.rs @@ -129,7 +129,7 @@ impl Client { .add_static(&mut ui); let texts = Span::empty(Dir::DOWN).add_static(&mut ui); - let msg_area = (Rect::new(Color::SKY), texts.scroll()).stack(); + let msg_area = (Rect::new(Color::SKY), texts.scroll().masked()).stack(); let add_text = text("add") .editable() .text_align(Align::Left) diff --git a/src/util/arena.rs b/src/util/arena.rs new file mode 100644 index 0000000..9ddfd99 --- /dev/null +++ b/src/util/arena.rs @@ -0,0 +1,109 @@ +use std::ops::Deref; + +use crate::util::{Id, IdNum, IdTracker}; + +pub struct Arena { + data: Vec, + tracker: IdTracker, +} + +impl Arena { + pub fn new() -> Self { + Self { + data: Vec::new(), + tracker: IdTracker::default(), + } + } + + pub fn push(&mut self, value: T) -> Id { + let id = self.tracker.next(); + let i = id.idx(); + if i == self.data.len() { + self.data.push(value); + } else { + self.data[i] = value; + } + id + } + + pub fn remove(&mut self, id: Id) -> T + where + T: Copy, + { + let i = id.idx(); + self.tracker.free(id); + self.data[i] + } +} + +impl Default for Arena { + fn default() -> Self { + Self::new() + } +} + +pub struct TrackedArena { + inner: Arena, + refs: Vec, + pub changed: bool, +} + +impl TrackedArena { + pub fn new() -> Self { + Self { + inner: Arena::default(), + refs: Vec::new(), + changed: true, + } + } + + pub fn push(&mut self, value: T) -> Id { + self.changed = true; + let id = self.inner.push(value); + let i = id.idx(); + if i == self.refs.len() { + self.refs.push(0); + } + id + } + + pub fn push_ref(&mut self, i: Id) { + self.refs[i.idx()] += 1; + } + + pub fn remove(&mut self, id: Id) -> T + where + T: Copy, + { + let i = id.idx(); + self.refs[i] -= 1; + if self.refs[i] == 0 { + self.changed = true; + self.inner.remove(id) + } else { + self[i] + } + } +} + +impl Default for TrackedArena { + fn default() -> Self { + Self::new() + } +} + +impl Deref for TrackedArena { + type Target = Vec; + + fn deref(&self) -> &Self::Target { + &self.inner.data + } +} + +impl Deref for Arena { + type Target = Vec; + + fn deref(&self) -> &Self::Target { + &self.data + } +} diff --git a/src/util/id.rs b/src/util/id.rs index f3d73b1..078a30b 100644 --- a/src/util/id.rs +++ b/src/util/id.rs @@ -1,25 +1,81 @@ -#[derive(Eq, Hash, PartialEq, Debug, Clone, Copy)] -pub struct Id(u64); +#[repr(C)] +#[derive(Eq, Hash, PartialEq, Debug, Clone, Copy, bytemuck::Zeroable)] +pub struct Id(I); -#[derive(Default)] -pub struct IdTracker { - free: Vec, - cur: u64, +unsafe impl bytemuck::Pod for Id {} + +pub struct IdTracker { + free: Vec>, + cur: Id, } -impl IdTracker { +impl IdTracker { #[allow(clippy::should_implement_trait)] - pub fn next(&mut self) -> Id { + pub fn next(&mut self) -> Id { if let Some(id) = self.free.pop() { return id; } - let id = Id(self.cur); - self.cur += 1; - id + let next = self.cur.next(); + std::mem::replace(&mut self.cur, next) } #[allow(dead_code)] - pub fn free(&mut self, id: Id) { + pub fn free(&mut self, id: Id) { self.free.push(id); } } + +impl Id { + pub fn idx(&self) -> usize { + self.0.idx() + } + pub fn next(&self) -> Id { + Self(self.0.next()) + } + pub const fn preset(value: I) -> Self { + Self(value) + } +} + +impl Default for IdTracker { + fn default() -> Self { + Self { + free: Vec::new(), + cur: Id(I::first()), + } + } +} + +pub trait IdNum { + fn first() -> Self; + fn next(&self) -> Self; + fn idx(&self) -> usize; +} + +impl IdNum for u64 { + fn first() -> Self { + 0 + } + + fn next(&self) -> Self { + self + 1 + } + + fn idx(&self) -> usize { + *self as usize + } +} + +impl IdNum for u32 { + fn first() -> Self { + 0 + } + + fn next(&self) -> Self { + self + 1 + } + + fn idx(&self) -> usize { + *self as usize + } +} diff --git a/src/util/mod.rs b/src/util/mod.rs index e5df3da..e014fd6 100644 --- a/src/util/mod.rs +++ b/src/util/mod.rs @@ -1,9 +1,11 @@ +mod arena; mod borrow; mod change; mod id; mod math; mod refcount; +pub(crate) use arena::*; pub(crate) use borrow::*; pub use change::*; pub(crate) use id::*;