From 4431e257a2bee2f82955e964660edad7059bb00b Mon Sep 17 00:00:00 2001 From: RicardoDeZoete Date: Thu, 5 Mar 2026 17:52:54 +0100 Subject: [PATCH] perf(client): cache parsed chunk/batch origin coordinates MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit chunkIdToOriginCoordinate() and batchIdToBatchOrigin() are called in hot per-frame loops (view distance culling, batch sorting, light volumes). Each call does split(',') + .map(Number) which allocates an intermediate array, a closure, a mapped array, and an {x,y,z} object — 5 allocations per call. With ~200 active batches, applyBatchViewDistance() alone produces ~1 000 throwaway objects per frame. Add a Map cache for each method so the parsing and allocation happen only once per unique ID. All existing callers are read-only so caching the returned objects is safe. Made-with: Cursor --- client/src/chunks/Chunk.ts | 24 ++++++++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/client/src/chunks/Chunk.ts b/client/src/chunks/Chunk.ts index d0d2a50c..c6e03e11 100644 --- a/client/src/chunks/Chunk.ts +++ b/client/src/chunks/Chunk.ts @@ -12,6 +12,12 @@ export interface LightSource { // Working variables const vec3like: Vector3LikeMutable = { x: 0, y: 0, z: 0 }; +// Caches for parsed string-ID → numeric-origin lookups. +// These are called in hot per-frame loops (view distance, sorting, culling). +// Each uncached call does split(',') + .map(Number) — 5 allocations per call. +const chunkOriginCache = new Map(); +const batchOriginCache = new Map(); + export default class Chunk { public readonly originCoordinate: Vector3Like; private _chunkId: ChunkId; @@ -44,8 +50,13 @@ export default class Chunk { } public static chunkIdToOriginCoordinate(chunkId: ChunkId): Vector3Like { - const [x, y, z] = chunkId.split(',').map(str => Number(str)) as [number, number, number]; - return { x, y, z }; + let cached = chunkOriginCache.get(chunkId); + if (!cached) { + const [x, y, z] = chunkId.split(',').map(str => Number(str)) as [number, number, number]; + cached = { x, y, z }; + chunkOriginCache.set(chunkId, cached); + } + return cached; } public static originCoordinateToChunkId(originCoordinate: Vector3Like): ChunkId { @@ -89,8 +100,13 @@ export default class Chunk { } public static batchIdToBatchOrigin(batchId: BatchId): Vector3Like { - const [x, y, z] = batchId.split(',').map(str => Number(str)) as [number, number, number]; - return { x, y, z }; + let cached = batchOriginCache.get(batchId); + if (!cached) { + const [x, y, z] = batchId.split(',').map(str => Number(str)) as [number, number, number]; + cached = { x, y, z }; + batchOriginCache.set(batchId, cached); + } + return cached; } // Get all chunk IDs that belong to a batch (some may not exist in world)