Skip to content

perf: reuse Three.js scratch objects in the replay render loop#916

Open
Anexus5919 wants to merge 1 commit into
Somil450:mainfrom
Anexus5919:fix/replay-per-frame-allocations
Open

perf: reuse Three.js scratch objects in the replay render loop#916
Anexus5919 wants to merge 1 commit into
Somil450:mainfrom
Anexus5919:fix/replay-per-frame-allocations

Conversation

@Anexus5919

Copy link
Copy Markdown
Contributor

📌 Related Issue

Fixes #879


📝 Description

Replay3DModel's render loop allocated a fresh batch of Three.js objects every frame:

  • updateStressVectors cloned several vectors and built two THREE.Colors per joint.
  • buildSegmentScaleState round-tripped a Matrix4/Vector3/Quaternion to recover a scale it already knew.
  • updateSegmentScaleAdaptor allocated identity vectors as lerp targets.

Across many joints and segments at 60fps this churned the garbage collector (frame hitches).

🔹 What has been changed?

  • src/components/Replay3DModel.tsx:
    • Hoisted the constant axes/colours and reusable scratch Vector3/Color to module scope.
    • updateStressVectors: write directly into mesh.position, use the constant axis/colour objects, and reuse the stored previous-position vectors via .copy().
    • buildSegmentScaleState: set the scale components directly into a scratch vector (dropping the unused Matrix4 round-trip; only .scale was ever read).
    • Segment lerp targets use a shared identity vector.

🔹 Why are these changes needed?

  • A 60fps render loop should not allocate dozens of short-lived objects per frame. Reusing scratch objects and writing into existing targets keeps per-frame allocations near zero, removing the GC pressure. Each scratch is fully written before use and not retained (the previous-position vectors are reused in place), so behaviour is unchanged.

🛠️ Type of Change

  • ⚡ Performance Improvement

🧪 Testing

✅ Tests Performed

  • Tested locally
  • npx tsc --noEmit: Replay3DModel.tsx clean (the only pre-existing tsc errors are in exerciseEngine.ts, unrelated).
  • npx eslint src/components/Replay3DModel.tsx: 0 problems.
  • Reviewed the vector math for aliasing: each scratch is copied/written before use and not stored, and direction is copied into a separate scratch before being scaled, so it remains valid for the quaternion call.

🔹 Note on automated tests

No runtime unit test: Replay3DModel is a Three.js/WebGL component that does not run under the vitest/jsdom environment. Verification is by tsc, eslint, and the aliasing review above (allocation reduction is observable in the DevTools memory timeline).

🌐 Browsers Tested

Not applicable (allocation profile).


📷 Screenshots / Demo (if applicable)

Not applicable.


📋 Checklist

  • I have read the project's CONTRIBUTING guidelines
  • My code follows the project style guidelines
  • I have performed a self-review of my code
  • I have tested my changes locally
  • I have added/updated documentation where necessary (not applicable)
  • My changes do not introduce new warnings or errors
  • This PR is linked to an existing issue

💬 Additional Notes

Branched from latest upstream/main (5464425). This touches Replay3DModel.tsx, as do a few other replay PRs (#906, #907, #878); they change different lines and are independently mergeable, with at most a trivial rebase for whichever lands later.

@vercel

vercel Bot commented Jun 22, 2026

Copy link
Copy Markdown

@Anexus5919 is attempting to deploy a commit to the somiljain2024-4175's projects Team on Vercel.

A member of the Team first needs to authorize it.

@Anexus5919

Copy link
Copy Markdown
Contributor Author

@Somil450 @diksha78dev Kindly have a review on this pr. Thanks!

The per-frame render loop allocated a fresh stack of THREE.Vector3 and
THREE.Color objects every frame: updateStressVectors cloned several
vectors and built two colors per joint, buildSegmentScaleState round-
tripped a Matrix4/Vector3/Quaternion to recover a scale it already knew,
and the segment adaptor allocated identity vectors as lerp targets. At
60fps over many joints/segments this churned the GC.

Hoist the constants and reusable scratch vectors/colors to module scope,
write directly into mesh targets, reuse the stored previous-position
vectors, and compute the segment scale without the matrix round-trip.
Behaviour is unchanged.
@Anexus5919 Anexus5919 force-pushed the fix/replay-per-frame-allocations branch from d70fd66 to 93f5467 Compare June 23, 2026 17:34
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Replay3DModel render loop allocates many transient Three.js objects per frame (GC churn)

1 participant