perf(server): reuse Entity.tick() event payload to eliminate per-tick allocations#29
Open
RZDESIGN wants to merge 1 commit intohytopiagg:mainfrom
Open
perf(server): reuse Entity.tick() event payload to eliminate per-tick allocations#29RZDESIGN wants to merge 1 commit intohytopiagg:mainfrom
RZDESIGN wants to merge 1 commit intohytopiagg:mainfrom
Conversation
Entity.tick() previously allocated a new { entity, tickDeltaMs } object
on every tick for every entity. At 20 ticks/s with 100+ entities this
produces 2 000+ short-lived objects per second, all of which pressure
the garbage collector.
Cache the payload as a private field on each Entity and only update the
tickDeltaMs value before emitting. This reduces GC pressure on the
server hot path with zero behavioral change.
Made-with: Cursor
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Entity.tick()is the server's hottest path — it fires for every entity on every tick (default 20 ticks/s). The current implementation allocates a fresh{ entity, tickDeltaMs }object each time and immediately discards it after the event listeners return. With 100+ entities in a world this creates 2 000+ short-lived objects per second, all of which become garbage and pressure the GC into more frequent pauses.This PR caches the payload as a private field on each
Entityinstance and only mutates thetickDeltaMsvalue before emitting. The object reference stays stable across ticks — no allocation, no GC pressure.Changes
server/src/worlds/entities/Entity.ts_tickPayloadfield, reuse it intick()instead of allocating a new objectNet diff: +1 field declaration, +1 assignment, −1 object literal (3 meaningful lines).
Risk Assessment
{ entity: Entity, tickDeltaMs: number }is unchanged — listeners receive the exact same shape.emit()and only read duringemit()— there is no window where stale data could be observed.tickDeltaMsinstead of the one from their tick. This is a theoretical concern only — all existing listeners and the documented pattern consume the value synchronously. If this ever becomes a concern, aObject.freeze()in dev mode could catch it.Test Plan
EntityEvent.TICKlisteners still receive correcttickDeltaMsvaluesBaseEntityController.tickstill called correctly)