Skip to content

Performance optimizations

Overview

This document describes client-side performance work to reduce CPU usage and FPS drops, especially when hovering the map or with many objects/jobs.

UUID and ObjectManager

Problem

  • isValidUUID() using a regex was a major hotspot (~35% self time in profiles) when called on every getObjectByUUID().
  • getObjectByUUID() was called very frequently from tick, overlays, job system, and pointer handlers.

Changes

  • Fast UUID validation (src/sim/core/utils/uuid.ts): Replaced regex with a length + structure + hex check (no regex in hot path). Optional isValidUUIDStrict() kept for strict RFC validation where needed.
  • ObjectManager hot path: Removed isValidUUID() from getObjectByUUID(), hasObject(), removeObject(), moveObject(). Use a simple typeof + length === 36 guard and direct Map lookup.
  • Fewer lookups: Prefer passing object references or batching:
  • BehaviorRunner (colonists): getAllObjects() + filter by type instead of getAllUUIDs() + N×getObjectByUUID().
  • ObjectLayer overlays: Store objectRef in sprite userData when building the layer; use it in updateOverlays() instead of looking up by UUID each frame.
  • syncColonistJobActionFromJobQueue: One getAllObjects(), build Map<uuid, ColonistObject>, then use map.get() for each job.

Pointer and input

Problem

  • _onPointerMove and related handlers ran on every mouse move, contributing to high CPU and FPS drops when hovering the map.

Changes

  • Throttle utility (src/client/utils/throttle.ts): throttle(fn, intervalMs) – runs at most once per interval, uses latest args (trailing).
  • MapTooltipHandler: pointermove callback throttled to 32 ms (~30 fps).
  • IsometricMap cursor label: pointermove (cursor label position) throttled to 32 ms.
  • MapInputHandler: globalpointermove (drag/area selection) throttled to 32 ms.

Map render and overlays

Problem

  • When the camera was static, update() still called renderMapView() every frame, so ObjectLayer.render() ran 60×/s (terrain, zones, objects, updateColonistInterpolation, updateOverlays). Combined with pointer events, this caused severe FPS drops on hover.
  • updateOverlays() (bubbles for Zzz, needs) was expensive: iterate all object sprites, get descriptor, draw Graphics/Text.

Changes

  • Static camera (IsometricMap.update()): In the branch where the camera did not move (needsRerender === false), call renderMapView() only every 2nd frame (counter staticRenderFrameCounter). When the camera or zoom changes, we still full-render every frame.
  • Overlays (ObjectLayer.render()): Call updateOverlays() only every 2nd frame (counter overlayFrameCounter). updateColonistInterpolation() still runs every frame for smooth walk animation.

Telemetry

  • Client telemetry (job lifecycle, screen views, zones, session, behaviors) is documented in the code and in deployment/API docs. Telemetry does not run in the hot path (batched, async).

Profiling

  • Use Chrome DevTools Performance tab, Bottom-up view, to find hotspots.
  • Typical hot areas: getObjectByUUID, _onPointerMove / pointerover, updateOverlays, updateColonistInterpolation, render / renderMapView.