Repository navigation
perf(replay): encode screenshots and batches off the main thread - #148
Conversation
Move the screenshot JPEG and base64 encoding, and the replay batch JSON serialization and gzip, from the main thread to worker tasks. A new ScreenshotCapture.Poll() hands the encoded screenshot back on the main thread, and CancelPending() drops an in-flight capture when replay stops or the session rotates.
marandaneto
left a comment
There was a problem hiding this comment.
Automated advisory code review.
…oding The capture loop delivers a finished encode before it checks for a session rotation, so after Reset() the previous user's frame could be enqueued under the new session and distinct id. Each capture now records its session and the result is dropped if the session is no longer current.
|
@ioannisj @turnipdabeets probably something for you to validate very mobile specific |
|
Thanks @rbzn-play-perfect, great change, and the numbers hold up on an iPhone 14 Pro Max (Unity 6000.0.85f1, IL2CPP, 6,000-cube scene):
"without PostHog" is the same app with the SDK never initialized, so this PR is as smooth as not running the SDK at all. Screenshots are pixel-identical to One request: the replay batch sent when the app goes to the background now waits until the app comes back. On Could Happy to re-run the device test once it's in! |
…uspended Pause() flushes when the app goes to the background, but the batch was prepared on a worker while the coroutine yielded. iOS stops Unity's loop right after, so the request only went out when the app came back. The pause flush now prepares its first batch on the main thread, so the request starts before the first yield, as it did on main. Timed and threshold flushes stay on the worker.
|
@turnipdabeets thanks for the device run! Done in 36c804e. For the One note: Would be great if you could re-run the background test. |
|
@rbzn-play-perfect thanks! this looks good to me. Do you mind signing your commits so that we can merge this? They need to be verified to merge. |
36c804e to
ed75602
Compare
💡 Motivation and Context
With session replay on, every 1 s capture does about 20 ms of main-thread work:
Texture2Dupload,EncodeToJPGand base64. Each upload batch is serialized and gzipped on the main thread too, which caused 118–157 ms stalls on an iPhone 16e. That shows up as visible hitches in games.This PR moves that work to worker threads:
byte[]and encoded withImageConversion.EncodeArrayToJPG(thread-safe) onTask.Run. The data URL is built in onestring.Createcall. A newScreenshotCapture.Poll(), called by the capture loop every frame, hands the result back on the main thread, soEnqueuestill runs there.CancelPending()drops a readback or encode still in flight when replay stops or the session rotates, so a frame from the previous session is never enqueued under the new session id.Task.Runwhile theSendBatchcoroutine yields. A batch that fails to serialize is dropped (reported as 400) instead of retried forever, the same way analytics events that fail serialization are dropped.This builds on #126: the vertical flip already happens in the blit, so the readback needs no CPU pass. Replay stays disabled on WebGL (
PostHogSDK), so no threadless platform reaches the new code. No public API changes;bin/check-public-apiis clean.💚 How did you test it?
Measured on an iPhone 16e with a 6,000-cube scene (one draw call per cube) in a Unity 6000.0.84 development build, 30 s runs with the default replay settings (1 s, quality 80, scale 0.75):
The "This PR" column comes from this branch (rebased on
main), built and run on the device. The per-capture main-thread cost was measured with temporary step timing on the same change applied to v1.1.1; that timing code is not in this PR.bin/testpasses (517). New tests coverPrepareBody(gzip round-trip equals the JSON) andToJpegDataUrl(matchesConvert.ToBase64Stringfor lengths 0–5 and 100,001 bytes).bin/fmt --checkis clean.📝 Checklist
If releasing new changes
pnpm changesetto generate a changeset file🤖 Agent context
Autonomy: Human-driven (agent-assisted)
mainto reuse fix(replay): flip capture frames only when the graphics API needs it #126's GPU flip and fix: align durable queue retries with SDK specs #134's retry handling, and measured again on the device.