The collision pass is main-thread-bound only because it measures labels with getBoundingClientRect - thousands of forced reflows at every gesture end on a big map. Text metrics do not need the DOM: OffscreenCanvas measureText in a worker returns the same advance widths for the same font string.
Main thread sends label text, font, letter spacing, anchor position and rank. The worker runs the existing selectNonOverlapping and obstacle logic (already pure) and posts back the keep set of ids. Main thread toggles hidden classes in one write pass. Stamp requests with a generation counter and drop stale replies.
Risk: metric drift vs rendered SVG text. Port the existing letter-spacing and shadow fudge factors. Verify same keep sets on the same seed, then wall clock at settle.
Recommended first: smallest surface, biggest settle-time win.
The collision pass is main-thread-bound only because it measures labels with getBoundingClientRect - thousands of forced reflows at every gesture end on a big map. Text metrics do not need the DOM: OffscreenCanvas measureText in a worker returns the same advance widths for the same font string.
Main thread sends label text, font, letter spacing, anchor position and rank. The worker runs the existing selectNonOverlapping and obstacle logic (already pure) and posts back the keep set of ids. Main thread toggles hidden classes in one write pass. Stamp requests with a generation counter and drop stale replies.
Risk: metric drift vs rendered SVG text. Port the existing letter-spacing and shadow fudge factors. Verify same keep sets on the same seed, then wall clock at settle.
Recommended first: smallest surface, biggest settle-time win.