Another project
0

Configure Feed

Select the types of activity you want to include in your feed.

fix: frame budget test & better clippy allow reasoning

Lewis: May this revision serve well! <lu5a@proton.me>

author
Lewis
date (May 22, 2026, 5:16 PM +0300) commit cb69a936 parent 44e53ae3 change-id klzswuqo
+390 -66
+4 -4
crates/bone-app/src/shell.rs
··· 3985 3985 let size = layout_size(1600.0, 900.0); 3986 3986 let ltr = render_with_locale(size, Locale::EnUs); 3987 3987 let rtl = render_with_locale(size, Locale::ArXb); 3988 - let ltr_rect = label_rect(&ltr.paints, key) 3989 - .unwrap_or_else(|| panic!("ltr paint missing for {key}")); 3990 - let rtl_rect = label_rect(&rtl.paints, key) 3991 - .unwrap_or_else(|| panic!("rtl paint missing for {key}")); 3988 + let ltr_rect = 3989 + label_rect(&ltr.paints, key).unwrap_or_else(|| panic!("ltr paint missing for {key}")); 3990 + let rtl_rect = 3991 + label_rect(&rtl.paints, key).unwrap_or_else(|| panic!("rtl paint missing for {key}")); 3992 3992 let half = size.width.value() * 0.5; 3993 3993 assert!( 3994 3994 ltr_rect.origin.x.value() < half,
+3 -7
crates/bone-document/src/sketch/mod.rs
··· 1707 1707 _ => panic!("expected entity outcomes"), 1708 1708 }) 1709 1709 .collect(); 1710 - let Ok((mut sketch, EditOutcome::Entity(line_id))) = 1711 - sketch.apply(SketchEdit::AddEntity(SketchEntity::line( 1712 - entity_ids[0], 1713 - entity_ids[1], 1714 - false, 1715 - ))) 1716 - else { 1710 + let Ok((mut sketch, EditOutcome::Entity(line_id))) = sketch.apply(SketchEdit::AddEntity( 1711 + SketchEntity::line(entity_ids[0], entity_ids[1], false), 1712 + )) else { 1717 1713 panic!("line should add cleanly"); 1718 1714 }; 1719 1715 let entities = Arc::make_mut(&mut sketch.entities);
+8 -2
crates/bone-render/src/camera.rs
··· 144 144 } 145 145 146 146 #[must_use] 147 - #[allow(clippy::cast_possible_truncation)] 147 + #[allow( 148 + clippy::cast_possible_truncation, 149 + reason = "mm coordinates fit f32 mantissa at CAD scales" 150 + )] 148 151 pub fn clip_from_world_mm(self) -> [f32; 16] { 149 152 let (sx, sy) = self.scale(); 150 153 let (px, py) = self.pan_mm.coords_mm(); ··· 154 157 } 155 158 156 159 #[must_use] 157 - #[allow(clippy::cast_possible_truncation)] 160 + #[allow( 161 + clippy::cast_possible_truncation, 162 + reason = "mm coordinates fit f32 mantissa at CAD scales" 163 + )] 158 164 pub fn world_mm_from_clip(self) -> [f32; 16] { 159 165 let (sx, sy) = self.scale(); 160 166 let inv_x = 1.0 / sx;
+5 -1
crates/bone-render/src/diff.rs
··· 20 20 } 21 21 22 22 #[must_use] 23 - #[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)] 23 + #[allow( 24 + clippy::cast_possible_truncation, 25 + clippy::cast_sign_loss, 26 + reason = "value is clamped to [0, 1] before scaling to u8" 27 + )] 24 28 pub fn as_u8(self) -> u8 { 25 29 (self.0.clamp(0.0, 1.0) * 255.0).round() as u8 26 30 }
+8 -2
crates/bone-render/src/pick.rs
··· 104 104 } 105 105 106 106 #[must_use] 107 - #[allow(clippy::cast_possible_truncation)] 107 + #[allow( 108 + clippy::cast_possible_truncation, 109 + reason = "right-shift by TAG_SHIFT (28) leaves the high nibble in the low 4 bits; value is in 0..16 so u8 fits" 110 + )] 108 111 pub const fn tag(self) -> Option<EntityKindTag> { 109 112 EntityKindTag::from_bits((self.0 >> Self::TAG_SHIFT) as u8) 110 113 } ··· 557 560 } 558 561 } 559 562 560 - #[allow(clippy::cast_possible_truncation)] 563 + #[allow( 564 + clippy::cast_possible_truncation, 565 + reason = "high 32 bits are masked off before the u64-to-u32 cast, so the low 32 bits fit u32 exactly" 566 + )] 561 567 fn slot_index<K: Key>(key: K) -> Result<u32, PickIdError> { 562 568 let slot = (key.data().as_ffi() & 0xFFFF_FFFF_u64) as u32; 563 569 if slot > PickId::INDEX_MASK {
+20 -5
crates/bone-render/src/pipelines/arc.rs
··· 229 229 .collect() 230 230 } 231 231 232 - #[allow(clippy::cast_possible_truncation)] 232 + #[allow( 233 + clippy::cast_possible_truncation, 234 + reason = "mm coordinates and radians fit f32 mantissa at CAD scales" 235 + )] 233 236 fn arc_instance(arc: SceneArc, style: &Style) -> ArcInstance { 234 237 let (cx, cy) = arc.center().coords_mm(); 235 238 let radius_mm = arc.radius().get::<millimeter>() as f32; ··· 259 262 } 260 263 } 261 264 262 - #[allow(clippy::cast_possible_truncation)] 265 + #[allow( 266 + clippy::cast_possible_truncation, 267 + reason = "mm coordinates and radians fit f32 mantissa at CAD scales" 268 + )] 263 269 fn circle_instance(circle: SceneCircle, style: &Style) -> ArcInstance { 264 270 let (cx, cy) = circle.center().coords_mm(); 265 271 let radius_mm = circle.radius().get::<millimeter>() as f32; ··· 281 287 } 282 288 } 283 289 284 - #[allow(clippy::cast_possible_truncation)] 290 + #[allow( 291 + clippy::cast_possible_truncation, 292 + reason = "mm coordinates and radians fit f32 mantissa at CAD scales" 293 + )] 285 294 fn preview_circle_instance(circle: PreviewCircle, style: &Style) -> ArcInstance { 286 295 let (cx, cy) = circle.center.coords_mm(); 287 296 let radius_mm = circle.radius.get::<millimeter>() as f32; ··· 298 307 } 299 308 } 300 309 301 - #[allow(clippy::cast_possible_truncation)] 310 + #[allow( 311 + clippy::cast_possible_truncation, 312 + reason = "mm coordinates and radians fit f32 mantissa at CAD scales" 313 + )] 302 314 fn preview_arc_instance(arc: PreviewArc, style: &Style) -> ArcInstance { 303 315 let (cx, cy) = arc.center.coords_mm(); 304 316 let radius_mm = arc.radius.get::<millimeter>() as f32; ··· 319 331 } 320 332 } 321 333 322 - #[allow(clippy::cast_possible_truncation)] 334 + #[allow( 335 + clippy::cast_possible_truncation, 336 + reason = "mm coordinates and radians fit f32 mantissa at CAD scales" 337 + )] 323 338 fn arc_aabb_offsets_mm( 324 339 center: Point2, 325 340 radius: Length,
+10 -3
crates/bone-render/src/pipelines/glyph.rs
··· 258 258 }) 259 259 } 260 260 261 - #[allow(clippy::cast_possible_truncation)] 261 + #[allow( 262 + clippy::cast_possible_truncation, 263 + reason = "mm coordinates fit f32 mantissa at CAD scales" 264 + )] 262 265 fn build_instance(glyph: SceneRelationGlyph) -> GlyphInstance { 263 266 let (ax, ay) = glyph.anchor_mm().coords_mm(); 264 267 let (dx, dy) = glyph.offset_dir().coords_mm(); ··· 270 273 } 271 274 } 272 275 273 - #[allow(clippy::cast_possible_truncation)] 276 + #[allow( 277 + clippy::cast_possible_truncation, 278 + reason = "mm coordinates fit f32 mantissa at CAD scales" 279 + )] 274 280 fn build_uniform(camera: Camera2, glyphs: GlyphStyle) -> GlyphUniform { 275 281 GlyphUniform { 276 282 clip_from_world: camera.clip_from_world_mm(), ··· 362 368 #[allow( 363 369 clippy::cast_possible_truncation, 364 370 clippy::cast_precision_loss, 365 - clippy::cast_sign_loss 371 + clippy::cast_sign_loss, 372 + reason = "atlas tile coords fit f32 mantissa and coverage is clamped to [0, 1] before u8 scaling" 366 373 )] 367 374 fn pixel_rgba(x: u32, y: u32) -> [u8; 4] { 368 375 let col = x / TILE_SIDE;
+5 -1
crates/bone-render/src/pipelines/grid.rs
··· 151 151 } 152 152 } 153 153 154 - #[allow(clippy::cast_possible_truncation, clippy::cast_precision_loss)] 154 + #[allow( 155 + clippy::cast_possible_truncation, 156 + clippy::cast_precision_loss, 157 + reason = "viewport extents and grid spacing fit f32 mantissa" 158 + )] 155 159 fn build_uniform(camera: Camera2, spacing: GridSpacing, style: &Style) -> GridUniform { 156 160 let grid = style.grid(); 157 161 let extent = camera.extent();
+20 -5
crates/bone-render/src/pipelines/lines.rs
··· 226 226 .collect() 227 227 } 228 228 229 - #[allow(clippy::cast_possible_truncation)] 229 + #[allow( 230 + clippy::cast_possible_truncation, 231 + reason = "mm coordinates fit f32 mantissa at CAD scales" 232 + )] 230 233 fn line_instance(line: SceneLine, style: &Style) -> LineInstance { 231 234 let (ax, ay) = line.a().coords_mm(); 232 235 let (bx, by) = line.b().coords_mm(); ··· 244 247 } 245 248 } 246 249 247 - #[allow(clippy::cast_possible_truncation)] 250 + #[allow( 251 + clippy::cast_possible_truncation, 252 + reason = "mm coordinates fit f32 mantissa at CAD scales" 253 + )] 248 254 fn point_instance(point: ScenePoint, style: &Style) -> LineInstance { 249 255 let (x, y) = point.at().coords_mm(); 250 256 let xy = [x as f32, y as f32]; ··· 257 263 } 258 264 } 259 265 260 - #[allow(clippy::cast_possible_truncation)] 266 + #[allow( 267 + clippy::cast_possible_truncation, 268 + reason = "mm coordinates fit f32 mantissa at CAD scales" 269 + )] 261 270 fn preview_line_instance(a: Point2, b: Point2, style: &Style) -> LineInstance { 262 271 let (ax, ay) = a.coords_mm(); 263 272 let (bx, by) = b.coords_mm(); ··· 270 279 } 271 280 } 272 281 273 - #[allow(clippy::cast_possible_truncation)] 282 + #[allow( 283 + clippy::cast_possible_truncation, 284 + reason = "mm coordinates fit f32 mantissa at CAD scales" 285 + )] 274 286 fn preview_point_instance(at: Point2, style: &Style) -> LineInstance { 275 287 let (x, y) = at.coords_mm(); 276 288 let xy = [x as f32, y as f32]; ··· 285 297 286 298 const SNAP_RADIUS_RATIO: f32 = 1.8; 287 299 288 - #[allow(clippy::cast_possible_truncation)] 300 + #[allow( 301 + clippy::cast_possible_truncation, 302 + reason = "mm coordinates fit f32 mantissa at CAD scales" 303 + )] 289 304 fn preview_snap_instance(at: Point2, style: &Style) -> LineInstance { 290 305 let (x, y) = at.coords_mm(); 291 306 let xy = [x as f32, y as f32];
+5 -1
crates/bone-render/src/pipelines/mod.rs
··· 34 34 35 35 pub(crate) const CONSTRUCTION_BIT: u32 = 1; 36 36 37 - #[allow(clippy::cast_possible_truncation, clippy::cast_precision_loss)] 37 + #[allow( 38 + clippy::cast_possible_truncation, 39 + clippy::cast_precision_loss, 40 + reason = "zoom factor and stroke widths fit f32 mantissa at CAD scales" 41 + )] 38 42 pub(crate) fn build_frame_uniform(camera: Camera2, style: &Style) -> FrameUniform { 39 43 let strokes = style.strokes(); 40 44 FrameUniform {
+8 -2
crates/bone-render/src/pipelines/text.rs
··· 289 289 }) 290 290 } 291 291 292 - #[allow(clippy::cast_possible_truncation)] 292 + #[allow( 293 + clippy::cast_possible_truncation, 294 + reason = "mm coordinates and pixel offsets fit f32 mantissa at CAD scales" 295 + )] 293 296 fn build_uniform(camera: Camera2, text: TextStyle) -> TextUniform { 294 297 TextUniform { 295 298 clip_from_world: camera.clip_from_world_mm(), ··· 301 304 } 302 305 } 303 306 304 - #[allow(clippy::cast_possible_truncation)] 307 + #[allow( 308 + clippy::cast_possible_truncation, 309 + reason = "mm coordinates and pixel offsets fit f32 mantissa at CAD scales" 310 + )] 305 311 fn assemble_geometry( 306 312 scene: &SketchScene, 307 313 cache: &HashMap<SketchDimensionId, Cached>,
+9 -2
crates/bone-render/src/snapshot.rs
··· 32 32 } 33 33 34 34 #[must_use] 35 - #[allow(clippy::cast_possible_truncation)] 35 + #[allow( 36 + clippy::cast_possible_truncation, 37 + reason = "clear-color channels are bounded [0, 1]" 38 + )] 36 39 pub fn to_rgba_array(self) -> [f32; 4] { 37 40 [self.r as f32, self.g as f32, self.b as f32, self.a as f32] 38 41 } ··· 49 52 } 50 53 } 51 54 52 - #[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)] 55 + #[allow( 56 + clippy::cast_possible_truncation, 57 + clippy::cast_sign_loss, 58 + reason = "value is clamped to [0, 1] before scaling to u8" 59 + )] 53 60 fn channel_to_u8(value: f64) -> u8 { 54 61 (value.clamp(0.0, 1.0) * 255.0).round() as u8 55 62 }
+208
crates/bone-render/tests/frame_budget.rs
··· 1 + use bone_document::{ 2 + DimensionKind, EditOutcome, Sketch, SketchDimension, SketchEdit, SketchEntity, SketchRelation, 3 + }; 4 + use bone_render::{Camera2, OffscreenContext, SketchRenderer, SketchScene, Style}; 5 + use bone_types::{ 6 + BudgetCeiling, Point2, Point3, SketchEntityId, SketchPlaneBasis, Tolerance, UnitVec3, 7 + }; 8 + use std::time::{Duration, Instant}; 9 + use uom::si::f64::Length as UomLength; 10 + use uom::si::length::millimeter; 11 + 12 + mod common; 13 + 14 + use common::{extent_square as extent, make_context}; 15 + 16 + fn xy_plane() -> SketchPlaneBasis { 17 + let Ok(basis) = SketchPlaneBasis::new( 18 + Point3::origin(), 19 + UnitVec3::x_axis(), 20 + UnitVec3::y_axis(), 21 + Tolerance::new(1e-9), 22 + ) else { 23 + panic!("xy plane basis is orthogonal"); 24 + }; 25 + basis 26 + } 27 + 28 + fn add_point(s: Sketch, x: f64, y: f64) -> (Sketch, SketchEntityId) { 29 + let Ok((next, EditOutcome::Entity(id))) = s.apply(SketchEdit::AddEntity(SketchEntity::point( 30 + Point2::from_mm(x, y), 31 + ))) else { 32 + panic!("add point"); 33 + }; 34 + (next, id) 35 + } 36 + 37 + fn add_line(s: Sketch, a: SketchEntityId, b: SketchEntityId) -> (Sketch, SketchEntityId) { 38 + let Ok((next, EditOutcome::Entity(id))) = 39 + s.apply(SketchEdit::AddEntity(SketchEntity::line(a, b, false))) 40 + else { 41 + panic!("add line"); 42 + }; 43 + (next, id) 44 + } 45 + 46 + fn add_circle(s: Sketch, center: SketchEntityId, radius_mm: f64) -> Sketch { 47 + let Ok((next, _)) = s.apply(SketchEdit::AddEntity(SketchEntity::circle( 48 + center, 49 + UomLength::new::<millimeter>(radius_mm), 50 + false, 51 + ))) else { 52 + panic!("add circle"); 53 + }; 54 + next 55 + } 56 + 57 + fn add_arc( 58 + s: Sketch, 59 + center: SketchEntityId, 60 + start: SketchEntityId, 61 + end: SketchEntityId, 62 + ) -> Sketch { 63 + let Ok((next, _)) = s.apply(SketchEdit::AddEntity(SketchEntity::arc( 64 + center, start, end, false, 65 + ))) else { 66 + panic!("add arc"); 67 + }; 68 + next 69 + } 70 + 71 + fn add_relation(s: Sketch, r: SketchRelation) -> Sketch { 72 + let Ok((next, _)) = s.apply(SketchEdit::AddRelation(r)) else { 73 + panic!("add relation"); 74 + }; 75 + next 76 + } 77 + 78 + fn add_linear_dim(s: Sketch, a: SketchEntityId, b: SketchEntityId, value_mm: f64) -> Sketch { 79 + let Ok((next, _)) = s.apply(SketchEdit::AddDimension(SketchDimension::Linear { 80 + a, 81 + b, 82 + value: UomLength::new::<millimeter>(value_mm), 83 + kind: DimensionKind::Driving, 84 + })) else { 85 + panic!("add linear dimension"); 86 + }; 87 + next 88 + } 89 + 90 + fn reference_sketch() -> (Sketch, SketchEntityId) { 91 + let s = Sketch::new(xy_plane()); 92 + let (s, p0) = add_point(s, 0.0, 0.0); 93 + let (s, p1) = add_point(s, 10.0, 0.0); 94 + let (s, p2) = add_point(s, 10.0, 5.0); 95 + let (s, p3) = add_point(s, 0.0, 5.0); 96 + let (s, e_bottom) = add_line(s, p0, p1); 97 + let (s, e_right) = add_line(s, p1, p2); 98 + let (s, e_top) = add_line(s, p2, p3); 99 + let (s, e_left) = add_line(s, p3, p0); 100 + let s = add_relation(s, SketchRelation::Horizontal(e_bottom)); 101 + let s = add_relation(s, SketchRelation::Horizontal(e_top)); 102 + let s = add_relation(s, SketchRelation::Vertical(e_right)); 103 + let s = add_relation(s, SketchRelation::Vertical(e_left)); 104 + let s = add_relation(s, SketchRelation::Fix(p0)); 105 + let s = add_linear_dim(s, p0, p1, 10.0); 106 + let s = add_circle(s, p0, 5.0); 107 + let (s, arc_center) = add_point(s, -12.0, 0.0); 108 + let (s, arc_start) = add_point(s, -8.0, 0.0); 109 + let (s, arc_end) = add_point(s, -12.0, 4.0); 110 + let s = add_arc(s, arc_center, arc_start, arc_end); 111 + (s, p2) 112 + } 113 + 114 + #[allow( 115 + clippy::too_many_arguments, 116 + reason = "stepper bundles per-frame pipeline state alongside per-step drag inputs" 117 + )] 118 + fn drag_resolve_render( 119 + current: &Sketch, 120 + dragged: SketchEntityId, 121 + target: Point2, 122 + solver_budget: BudgetCeiling, 123 + renderer: &mut SketchRenderer, 124 + ctx: &OffscreenContext, 125 + camera: Camera2, 126 + style: &Style, 127 + ) -> (Sketch, Duration) { 128 + let started = Instant::now(); 129 + let Ok(next) = current.solve_with_drag(dragged, target, solver_budget) else { 130 + panic!("drag must converge inside solver budget"); 131 + }; 132 + let Ok(scene) = SketchScene::extract(&next) else { 133 + panic!("scene extract must succeed for solved sketch"); 134 + }; 135 + let Ok(_frame) = renderer.render(ctx, &scene, camera, style) else { 136 + panic!("render must succeed for solved sketch"); 137 + }; 138 + (next, started.elapsed()) 139 + } 140 + 141 + const DRAG_STEPS: u32 = 16; 142 + 143 + #[test] 144 + #[cfg_attr( 145 + debug_assertions, 146 + ignore = "frame budget assertions are only meaningful in release builds" 147 + )] 148 + fn drag_resolve_plus_render_fits_frame_budget_on_reference_sketch() { 149 + let (sketch, dragged) = reference_sketch(); 150 + let Ok(sketch) = sketch.solve() else { 151 + panic!("reference sketch must solve at rest"); 152 + }; 153 + let size = extent(256); 154 + let ctx = make_context(size); 155 + let mut renderer = SketchRenderer::new(ctx.gpu(), ctx.color_format()); 156 + let camera = Camera2::new(size); 157 + let style = Style::default(); 158 + let frame_budget = Duration::from_millis(16); 159 + let worst_ceiling = frame_budget * 2; 160 + let solver_budget = BudgetCeiling::new(frame_budget / 2_u32); 161 + let warmup_target = Point2::from_mm(10.0, 4.0); 162 + let (warmed, _warmup_elapsed) = drag_resolve_render( 163 + &sketch, 164 + dragged, 165 + warmup_target, 166 + solver_budget, 167 + &mut renderer, 168 + &ctx, 169 + camera, 170 + &style, 171 + ); 172 + let (_final_sketch, durations) = (0..DRAG_STEPS).fold( 173 + (warmed, Vec::<Duration>::new()), 174 + |(current, durations), i| { 175 + let target = Point2::from_mm(10.0, 5.0 + 0.1 * f64::from(i)); 176 + let (next, elapsed) = drag_resolve_render( 177 + &current, 178 + dragged, 179 + target, 180 + solver_budget, 181 + &mut renderer, 182 + &ctx, 183 + camera, 184 + &style, 185 + ); 186 + let next_durations = durations.into_iter().chain([elapsed]).collect(); 187 + (next, next_durations) 188 + }, 189 + ); 190 + let sorted = { 191 + let mut v = durations.clone(); 192 + v.sort(); 193 + v 194 + }; 195 + let median = sorted[sorted.len() / 2]; 196 + let Some(&worst) = sorted.last() else { 197 + panic!("drag loop produced zero samples"); 198 + }; 199 + assert!( 200 + median <= frame_budget, 201 + "median drag+render step {median:?} exceeds {frame_budget:?} frame budget; samples {durations:?}", 202 + ); 203 + assert!( 204 + worst <= worst_ceiling, 205 + "worst drag+render step {worst:?} exceeds {worst_ceiling:?} relaxed ceiling; samples {durations:?}", 206 + ); 207 + println!("median {median:?}, worst {worst:?}"); 208 + }
+5 -1
crates/bone-render/tests/picker.rs
··· 71 71 (next, id) 72 72 } 73 73 74 - #[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)] 74 + #[allow( 75 + clippy::cast_possible_truncation, 76 + clippy::cast_sign_loss, 77 + reason = "fixture coordinates are bounded within the small test viewport" 78 + )] 75 79 fn world_to_px(x_mm: f32, y_mm: f32) -> PickQuery { 76 80 let x_px = (CENTER_PX + x_mm * PX_PER_MM).round() as u32; 77 81 let y_px = (CENTER_PX - y_mm * PX_PER_MM).round() as u32;
+3 -2
crates/bone-ui/src/layout/geometry.rs
··· 215 215 super::axis::LayoutDirection::Rtl => { 216 216 let container_left = container.origin.x.value(); 217 217 let container_right = container_left + container.size.width.value(); 218 - let mirrored_x = 219 - container_right - (self.origin.x.value() - container_left) - self.size.width.value(); 218 + let mirrored_x = container_right 219 + - (self.origin.x.value() - container_left) 220 + - self.size.width.value(); 220 221 Self::new( 221 222 LayoutPos::new(LayoutPx::new(mirrored_x), self.origin.y), 222 223 self.size,
+17 -4
crates/bone-ui/src/text/raster/sdf.rs
··· 238 238 bearing_top: f32, 239 239 } 240 240 241 - #[allow(clippy::cast_precision_loss)] 241 + #[allow( 242 + clippy::cast_precision_loss, 243 + reason = "atlas tile dimensions fit f32 mantissa" 244 + )] 242 245 fn rasterise_glyph_sdf( 243 246 font: &FontRef<'_>, 244 247 glyph: u16, ··· 318 321 319 322 const FAR: i32 = i32::MAX / 4; 320 323 321 - #[allow(clippy::cast_precision_loss)] 324 + #[allow( 325 + clippy::cast_precision_loss, 326 + reason = "atlas dimensions fit f32 mantissa" 327 + )] 322 328 fn compute_sdf_bytes(mask: &[bool], width: u32, height: u32, spread: u32) -> Vec<u8> { 323 329 let outside = distance_field_to(mask, width, height, true); 324 330 let inside = distance_field_to(mask, width, height, false); ··· 329 335 let di = (inside[i] as f32).sqrt(); 330 336 let signed = if mask[i] { -di } else { do_ }; 331 337 let normalised = ((signed / spread_f) * 0.5 + 0.5).clamp(0.0, 1.0); 332 - #[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)] 338 + #[allow( 339 + clippy::cast_possible_truncation, 340 + clippy::cast_sign_loss, 341 + reason = "normalised is clamped to [0, 1] before scaling to u8" 342 + )] 333 343 let byte = (normalised * 255.0 + 0.5) as u8; 334 344 byte 335 345 }) ··· 491 501 }); 492 502 } 493 503 494 - #[allow(clippy::cast_precision_loss)] 504 + #[allow( 505 + clippy::cast_precision_loss, 506 + reason = "atlas extents and tile dimensions fit f32 mantissa" 507 + )] 495 508 fn atlas_entry(params: SdfAtlasParams, placed: PlacedTile, tile: &GlyphTile) -> AtlasEntry { 496 509 let extent = params.atlas_extent as f32; 497 510 let uv_min = [placed.x as f32 / extent, placed.y as f32 / extent];
+8 -2
crates/bone-ui/src/theme/color.rs
··· 72 72 73 73 #[must_use] 74 74 pub(in crate::theme) fn from_srgb_u8(r: u8, g: u8, b: u8) -> Self { 75 - #[allow(clippy::disallowed_methods)] 75 + #[allow( 76 + clippy::disallowed_methods, 77 + reason = "the theme color constructor is the chosen entry point for raw palette construction" 78 + )] 76 79 let lin: LinSrgb<f32> = Srgb::new( 77 80 f32::from(r) / 255.0, 78 81 f32::from(g) / 255.0, ··· 166 169 const GAMUT_BISECT_STEPS: u32 = 24; 167 170 168 171 fn oklch_to_linsrgb_raw(l: f32, c: f32, h_degrees: f32) -> [f32; 3] { 169 - #[allow(clippy::disallowed_methods)] 172 + #[allow( 173 + clippy::disallowed_methods, 174 + reason = "the theme color constructor is the chosen entry point for raw palette construction" 175 + )] 170 176 let lin = LinSrgb::<f32>::from_color_unclamped(Oklch::<f32>::new(l, c, h_degrees)); 171 177 [lin.red, lin.green, lin.blue] 172 178 }
+4 -1
crates/bone-ui/src/theme/typography.rs
··· 39 39 } 40 40 41 41 #[must_use] 42 - #[allow(clippy::cast_possible_truncation)] 42 + #[allow( 43 + clippy::cast_possible_truncation, 44 + reason = "px sizes fit f32 mantissa at typography scales" 45 + )] 43 46 pub fn as_px_f32(self) -> f32 { 44 47 length_to_px(self.0) as f32 45 48 }
+7 -1
crates/bone-ui/src/widgets/ribbon.rs
··· 268 268 .iter() 269 269 .map(|r| r.mirror_horizontally_within(body_rect, direction)) 270 270 .collect(); 271 - paint.extend(group_dividers(&raw_layouts, body_rect, group_gap, direction, ctx)); 271 + paint.extend(group_dividers( 272 + &raw_layouts, 273 + body_rect, 274 + group_gap, 275 + direction, 276 + ctx, 277 + )); 272 278 groups 273 279 .iter() 274 280 .zip(layouts.iter())
+12 -3
crates/bone-ui/src/widgets/slider.rs
··· 300 300 LayoutSize::new(rect.size.width, track_height), 301 301 ); 302 302 let unit = value.to_unit(range).clamp(0.0, 1.0); 303 - #[allow(clippy::cast_possible_truncation)] 303 + #[allow( 304 + clippy::cast_possible_truncation, 305 + reason = "unit is clamped to [0, 1] before f32 narrowing" 306 + )] 304 307 let unit_f32 = unit as f32; 305 308 let filled_width = LayoutPx::new(rect.size.width.value() * unit_f32); 306 309 let filled_rect = LayoutRect::new( ··· 398 401 f64::from((self - range.min) / (range.max - range.min)).clamp(0.0, 1.0) 399 402 } 400 403 401 - #[allow(clippy::cast_possible_truncation)] 404 + #[allow( 405 + clippy::cast_possible_truncation, 406 + reason = "unit is clamped to [0, 1] before f32 narrowing" 407 + )] 402 408 fn from_unit(unit: f64, range: SliderRange<Self>) -> Self { 403 409 range.min + (range.max - range.min) * unit.clamp(0.0, 1.0) as f32 404 410 } ··· 408 414 } 409 415 410 416 fn step_by(self, step: SliderStep<Self>, sign: i32) -> Self { 411 - #[allow(clippy::cast_precision_loss)] 417 + #[allow( 418 + clippy::cast_precision_loss, 419 + reason = "sign values fit f32 mantissa exactly" 420 + )] 412 421 let mul = sign as f32; 413 422 self + step.value() * mul 414 423 }
+5 -17
crates/bone-ui/src/widgets/toolbar.rs
··· 201 201 overflow_toggled = result.activated; 202 202 if cfg.open && plan.hidden_count > 0 { 203 203 let hidden_items = &items[items.len() - plan.hidden_count..]; 204 - let popup = render_overflow_popup( 205 - ctx, 206 - chevron_rect, 207 - hidden_items, 208 - item_size, 209 - orientation, 210 - ); 204 + let popup = 205 + render_overflow_popup(ctx, chevron_rect, hidden_items, item_size, orientation); 211 206 popover_paint.extend(popup.paint); 212 207 popup_consumed_click = popup.consumed_click; 213 208 if let Some(act) = popup.activated ··· 633 628 items 634 629 .iter() 635 630 .enumerate() 636 - .map(|(i, it)| { 637 - item_extent(it, item_size) + if i == 0 { 0.0 } else { gap.value() } 638 - }) 631 + .map(|(i, it)| item_extent(it, item_size) + if i == 0 { 0.0 } else { gap.value() }) 639 632 .sum() 640 633 } 641 634 ··· 702 695 mod tests { 703 696 use std::sync::Arc; 704 697 705 - use super::{ 706 - Toolbar, ToolbarItem, ToolbarOverflowConfig, overflow_chevron_id, show_toolbar, 707 - }; 698 + use super::{Toolbar, ToolbarItem, ToolbarOverflowConfig, overflow_chevron_id, show_toolbar}; 708 699 use crate::focus::FocusManager; 709 700 use crate::frame::FrameCtx; 710 701 use crate::hit_test::{HitFrame, HitState, resolve}; ··· 907 898 908 899 #[test] 909 900 fn clicking_disabled_popup_item_consumes_click_but_does_not_activate() { 910 - let items: Vec<ToolbarItem> = items(6) 911 - .into_iter() 912 - .map(|it| it.disabled(true)) 913 - .collect(); 901 + let items: Vec<ToolbarItem> = items(6).into_iter().map(|it| it.disabled(true)).collect(); 914 902 let rect = LayoutRect::new( 915 903 LayoutPos::new(LayoutPx::ZERO, LayoutPx::ZERO), 916 904 LayoutSize::new(LayoutPx::new(100.0), LayoutPx::new(28.0)),
+16
justfile
··· 35 35 cargo fmt --all -- --check 36 36 37 37 lint: fmt-check clippy 38 + 39 + determinism: 40 + @find crates -name '*.snap.new' -delete 41 + @echo "run 1 / 2 (debug)" 42 + CARGO_INCREMENTAL=0 INSTA_UPDATE=no cargo test --workspace --all-features --locked --quiet 43 + @if find crates -name '*.snap.new' -print -quit | grep -q .; then echo "snapshot drift in run 1" >&2; exit 1; fi 44 + @echo "run 2 / 2 (debug)" 45 + CARGO_INCREMENTAL=0 INSTA_UPDATE=no cargo test --workspace --all-features --locked --quiet 46 + @if find crates -name '*.snap.new' -print -quit | grep -q .; then echo "snapshot drift in run 2" >&2; exit 1; fi 47 + @echo "run 1 / 2 (release)" 48 + CARGO_INCREMENTAL=0 INSTA_UPDATE=no cargo test --workspace --all-features --locked --release --quiet 49 + @if find crates -name '*.snap.new' -print -quit | grep -q .; then echo "snapshot drift in release run 1" >&2; exit 1; fi 50 + @echo "run 2 / 2 (release)" 51 + CARGO_INCREMENTAL=0 INSTA_UPDATE=no cargo test --workspace --all-features --locked --release --quiet 52 + @if find crates -name '*.snap.new' -print -quit | grep -q .; then echo "snapshot drift in release run 2" >&2; exit 1; fi 53 + @echo "determinism: clean across two debug + two release runs"