Skip to content

perf(net-five): reduce CloneManager per-frame overhead, stagger scene update culling, add clone creation budget#4065

Open
ShamZy9 wants to merge 1 commit into
citizenfx:masterfrom
ShamZy9:perf/onesync-clone-manager
Open

perf(net-five): reduce CloneManager per-frame overhead, stagger scene update culling, add clone creation budget#4065
ShamZy9 wants to merge 1 commit into
citizenfx:masterfrom
ShamZy9:perf/onesync-clone-manager

Conversation

@ShamZy9

@ShamZy9 ShamZy9 commented Jul 13, 2026

Copy link
Copy Markdown

PR: perf(net-five): reduce per-frame CloneManager overhead and clone creation hitches

Goal of this PR

Reduce client main-thread cost of the OneSync clone manager, which scales poorly with the number of synchronized entities (most noticeably peds). Three symptoms addressed:

  1. A periodic frame time spike (once every 50 frames) proportional to the number of remote entities beyond the scene-update cull distance.
  2. Constant per-frame overhead in CloneManagerLocal::WriteUpdates / Update for every tracked object, even when no data is sent.
  3. A multi-second hitch when many entities enter scope at once (e.g. approaching a building with many players inside): all clone creations were processed in a single frame, and freemode ped creation (clothing/variation setup) is expensive.

How is this PR achieving the goal

Scene update culling (CloneManagerLocal::Update):

  • Re-adds to fwSceneUpdate for entities beyond 424m were all happening on the same 2 out of every 50 frames (frameCount % 50 < 2), causing a synchronized periodic spike. Re-adds are now staggered per object ID.
  • removedFlags is now a std::unordered_map (the fwSceneUpdate__AddToSceneUpdate/RemoveFromSceneUpdate tracking hooks run for every scene update list mutation in the game, so lookups are hot).
  • New opt-in convar game_sceneUpdateThrottling (default off): remote entities beyond 150m that are not on screen (checked via the existing _isSphereVisibleForLocalPlayer helper, cached ~10 frames staggered) get scene updates 1 out of every 4 frames. Visible entities are never throttled, since skipping scene updates also skips network position blending and causes visible warping.

CloneManagerLocal::WriteUpdates per-object overhead:

  • The 2.4KB zero-initialized packetStub buffer was allocated for every local object every frame; it is now only allocated when the object actually syncs (syncType != 0).
  • The visibility sphere checks (_isSphereVisibleForLocalPlayer + _isSphereVisibleForAnyRemotePlayer, the latter iterating all remote physical players) ran per object per frame just to choose a 50ms vs 250ms sync latency; the result is now cached in ExtendedCloneData and refreshed every ~100-164ms, staggered by object ID.
  • msec() (high_resolution_clock::now()) hoisted out of the per-object callback.
  • m_extendedData was looked up by key up to 4 times per object per frame; now looked up once.

Bookkeeping data structures:

  • m_savedEntityVec removal was erase(std::remove(...)) (O(n) per removal, O(n²) aggregate under OneSync population churn); now O(1) swap-and-pop via an index map. Note: this makes GetObjectList() ordering arbitrary instead of insertion-ordered; no current consumer relies on ordering.
  • DestroyNetworkObject iterated all 256 m_netObjects lists; it now erases from the owner's list and list 31 directly (the only lists an object is ever inserted into), with a full-sweep fallback if not found.

Clone creation budget (HandleCloneCreate):

  • New convar onesync_maxCloneCreatesPerFrame (default 5, 0 = unlimited/previous behavior). Clone creations over the per-frame budget return false, which routes them through the existing recreateList NAck path — the server resends them on a later frame. This turns the single multi-second hitch when many entities enter scope simultaneously into a few frames of staggered creation. The deferral path is already exercised in the wild (it is the same path used when a player entity is not yet available, and for CanApplyToObject failures).

This PR applies to the following area(s)

FiveM (OneSync client)

Successfully tested on

Game builds: 3258

Platforms: Windows

Tested on a local OneSync server with two clients and 150 networked wandering peds:

  • No entity leaks/ghosts across repeated spawn/clear cycles and scope enter/exit cycles.
  • Periodic frame spike from synchronized scene-update re-adds no longer observable.
  • Entering scope of 150 clones at once: with budget 5, the previous single long hitch becomes staggered creation over ~0.5s.
  • game_sceneUpdateThrottling false and onesync_maxCloneCreatesPerFrame 0 reproduce previous behavior.

Checklist

  • Code compiles and has been tested successfully.
  • Code explains itself well and/or is documented.
  • My commit message explains what the changes do and what they are for.
  • No extra compilation warnings are added by these changes.

@github-actions github-actions Bot added the invalid Requires changes before it's considered valid and can be (re)triaged label Jul 13, 2026
@ShamZy9
ShamZy9 force-pushed the perf/onesync-clone-manager branch from 27e387f to a4a0a49 Compare July 13, 2026 18:50
@github-actions github-actions Bot added triage Needs a preliminary assessment to determine the urgency and required action invalid Requires changes before it's considered valid and can be (re)triaged and removed invalid Requires changes before it's considered valid and can be (re)triaged triage Needs a preliminary assessment to determine the urgency and required action labels Jul 13, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

invalid Requires changes before it's considered valid and can be (re)triaged

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant