November 10, 2025 By WBG Perf Team
rendering performance tooling

Buildable Highlight Strings, Be Gone

We cached highlight coords and culled by viewport so buildable overlays stopped splitting "x,y" strings every frame.

Buildable Highlight Strings, Be Gone

Buildable Highlight Strings, Be Gone

Our level editor paints several highlight layers—white buildable tiles, green previews, red non-buildable Xs, and purple barracks effects. Each layer used a Set<string> of "x,y" keys, and every render frame we split and parseInted each entry before drawing, even when most of the tiles were nowhere near the camera. The profiler showed String.split sitting inside the render loop’s hot path, so we swapped the whole system for numeric caches and viewport culling.

The Old Path

for (const key of highlightSet) {
  const [xStr, yStr] = key.split(",");
  const x = parseInt(xStr, 10);
  const y = parseInt(yStr, 10);
  drawHighlight(x, y);
}
  • Four separate highlight sets meant the above loop ran multiple times per frame.
  • Large highlight sets (1,000+ tiles) resulted in thousands of split/parseInt calls every tick.
  • Every tile did canvas math even if it was far outside the viewport; no early exits.
  • Net cost grew linearly with the total highlighted area, not the visible portion.

The New Strategy

  1. Coordinate caching — Each Set<string> now feeds a WeakMap cache that stores { key, x, y } structs. The string is parsed exactly once per key; subsequent frames reuse the numeric data unless the set’s membership changes.
  2. Viewport bounds — Before drawing we derive visible tile ranges from the camera offset and canvas dimensions. Cached coords outside that range are skipped without touching draw code.
const coords = getHighlightCoords(highlightSet); // cached structs
for (const coord of coords) {
  if (!isCoordWithinBounds(coord, visibleBounds)) continue;
  drawHighlight(coord.x, coord.y);
}

Results

ScenarioBeforeAfter
1,500 highlight tiles~6,000 string parses per frameStrings parsed once, then reused
60% off-screen highlightsStill computed canvas coords for all 1,500 tilesSkip ~900 tiles before any math
ComplexityO(n) string parsing + full canvas mathO(k) cache rebuilds (k ≤ n) + O(v) on-screen work

Highlights now contribute virtually nothing to frame time when the camera is stationary, and even during placements we only pay for tiles that actually intersect the viewport.

Takeaways

  • If you’re storing coordinates as strings for convenience, consider caching their numeric form as soon as the data stabilizes.
  • Pair spatial caches with viewport bounds so your render pass scales with what’s visible, not with the total dataset size.
  • Tooling-quality-of-life improvements (clearer highlight overlays) shouldn’t come at the cost of string-parsing hotspots; caching keeps both UX and performance happy.
WBG Logo

Written by WBG Perf Team

Part of the passionate team at Wrinkled Brain Games, creating innovative gaming experiences.