Drag instrumentation — render counters, hot-path signposts, and the invariants that prove the gates
BoardRenderMetrics (DEBUG-only, the pathfinder's counter bag plus a strip-body discriminator that tells a failed gate from a direct Observation invalidation) counted at BoardView/LaneView/CardFaceView/ TrashLaneView bodies and MasonryLayout's callbacks. DragSignposts wraps dropUpdated, retargetCards, commitDrop, and the commit-to-covering- snapshot release pause; input latency reports honestly against NSApp.currentEvent's mach base or labels itself base=none — no event timestamp rides the drop path. BoardRenderPerformanceTests hosts the real BoardView off-screen: a value-equal reload runs zero lane and zero card bodies, a one-card edit repaints one card of 180. The lane-body budget is <= laneCount with the headerInk chain documented and a two-way tripwire that fails when the fix lands. Methodology in RENDER-INSTRUMENTATION.md. Release build proves it all compiles out. Drag-perf card a450ad09. Claude-Session: https://claude.ai/code/session_01CqjXB7ASoWtbyoGod68k97
This commit is contained in:
@@ -210,6 +210,10 @@ struct MasonryLayout: Layout {
|
||||
// change, which is the only way a card's measured height can change at a fixed column width
|
||||
// (its content changed → its view tree was rebuilt → the layout's content is new). Clearing
|
||||
// there means the cache never outlives the content it measured.
|
||||
//
|
||||
// Both halves are counted in DEBUG builds (`BoardRenderMetrics.masonryMeasurements` /
|
||||
// `masonryCacheHits`), which is what makes "the cache absorbs SwiftUI's repeat probes" a number
|
||||
// a test can read rather than a claim — see RENDER-INSTRUMENTATION.md.
|
||||
struct Cache {
|
||||
var columnWidth: CGFloat = .nan
|
||||
var heights: [CGFloat] = []
|
||||
@@ -229,8 +233,14 @@ struct MasonryLayout: Layout {
|
||||
cache.heights = [CGFloat](repeating: .nan, count: subviews.count)
|
||||
}
|
||||
if !cache.heights[index].isNaN {
|
||||
#if DEBUG
|
||||
BoardRenderMetrics.masonryCacheHits += 1
|
||||
#endif
|
||||
return cache.heights[index]
|
||||
}
|
||||
#if DEBUG
|
||||
BoardRenderMetrics.masonryMeasurements += 1
|
||||
#endif
|
||||
let height = subviews[index].sizeThatFits(ProposedViewSize(width: column, height: nil)).height
|
||||
cache.heights[index] = height
|
||||
return height
|
||||
@@ -248,6 +258,9 @@ struct MasonryLayout: Layout {
|
||||
}
|
||||
|
||||
func sizeThatFits(proposal: ProposedViewSize, subviews: Subviews, cache: inout Cache) -> CGSize {
|
||||
#if DEBUG
|
||||
BoardRenderMetrics.masonrySizeThatFitsCalls += 1
|
||||
#endif
|
||||
let width = proposal.width ?? 0
|
||||
let placement = placement(width: width, origin: .zero)
|
||||
let heights = measuredHeights(of: subviews, at: placement.columnWidth, cache: &cache)
|
||||
@@ -255,6 +268,9 @@ struct MasonryLayout: Layout {
|
||||
}
|
||||
|
||||
func placeSubviews(in bounds: CGRect, proposal: ProposedViewSize, subviews: Subviews, cache: inout Cache) {
|
||||
#if DEBUG
|
||||
BoardRenderMetrics.masonryPlaceCalls += 1
|
||||
#endif
|
||||
let placement = placement(width: bounds.width, origin: bounds.origin)
|
||||
let heights = measuredHeights(of: subviews, at: placement.columnWidth, cache: &cache)
|
||||
for (index, frame) in placement.frames(heights: heights).enumerated() {
|
||||
|
||||
Reference in New Issue
Block a user