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 everygetObjectByUUID().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). OptionalisValidUUIDStrict()kept for strict RFC validation where needed. - ObjectManager hot path: Removed
isValidUUID()fromgetObjectByUUID(),hasObject(),removeObject(),moveObject(). Use a simpletypeof+length === 36guard and directMaplookup. - Fewer lookups: Prefer passing object references or batching:
- BehaviorRunner (colonists):
getAllObjects()+ filter by type instead ofgetAllUUIDs()+ N×getObjectByUUID(). - ObjectLayer overlays: Store
objectRefin spriteuserDatawhen building the layer; use it inupdateOverlays()instead of looking up by UUID each frame. - syncColonistJobActionFromJobQueue: One
getAllObjects(), buildMap<uuid, ColonistObject>, then usemap.get()for each job.
Pointer and input¶
Problem¶
_onPointerMoveand 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:
pointermovecallback 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 calledrenderMapView()every frame, soObjectLayer.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), callrenderMapView()only every 2nd frame (counterstaticRenderFrameCounter). When the camera or zoom changes, we still full-render every frame. - Overlays (
ObjectLayer.render()): CallupdateOverlays()only every 2nd frame (counteroverlayFrameCounter).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.