Skip to content

chore: CRDT bridge concurrency improvements#8968

Open
pravusjif wants to merge 1 commit into
devfrom
opti/crdt-bridge-concurrency
Open

chore: CRDT bridge concurrency improvements#8968
pravusjif wants to merge 1 commit into
devfrom
opti/crdt-bridge-concurrency

Conversation

@pravusjif

Copy link
Copy Markdown
Member

What

Three concurrency/allocation fixes on the scene ↔ renderer CRDT bridge, one PR of a series based on a CRDT pipeline audit (independent of #8967 — either can merge first; whichever lands second needs a small mechanical rebase in OutgoingCRDTMessagesProvider/EngineAPIImplementation):

  • Serialization out of the producers' lockOutgoingCRDTMessagesProvider.GetSerializationSyncBlock used to hold lock (messages) across protobuf CalculateSize + SerializeInto for every pending message, while every main-thread ECS system writing a Put/Delete/Append contends on that same lock once per scene tick. Now the pending list is swapped with a spare under the lock (a reference swap) and serialized lock-free; this is safe because the method is only invoked from the scene runtime thread (single consumer).
  • WorldSyncCommandBuffer reuseGetSyncCommandBuffer() allocated a new buffer object every CrdtSendToRenderer call (every tick of every scene, idle or not). The synchronizer's semaphore already guarantees one outstanding buffer, so a single instance is now kept and Renew()ed.
  • Idle fast path — a scene tick with nothing to apply (no incoming changes, no deleted entities) still acquired the MultiThreadSync world mutex — blocking the scene thread on main-thread availability — and ran an empty commandBuffer.Playback. IWorldSyncCommandBuffer.IsEmpty (valid after FinalizeAndDeserialize) now routes empty buffers through a new ReleaseSyncCommandBuffer that disposes and releases the rent without touching the world. The systems update gate still opens on every tick so scene-world systems keep producing outgoing data.

Why

These are per-tick fixed costs multiplied by scene count: with dozens of loaded scenes, mostly idle, every tick paid a main-thread lock stall, a wasted mutex round-trip, and a garbage buffer object.

QA / verification

  • Affected assemblies + DCL.EditMode.Tests compile clean locally (dotnet build, 0 errors).
  • New EditMode tests: IsEmpty semantics in WorldSyncCommandBufferShould (4 cases incl. add+delete merging to no-op), buffer instance reuse and release-without-apply in CrdtWorldSynchronizerShould. Please rely on the CI EditMode run for execution; a local Test Runner run was not possible (editor lock).
  • QA smoke: any SDK7 scene — verify components add/modify/delete and entity deletion still apply, and that idle scenes (e.g. standing in an empty parcel with neighbors loaded) behave normally.

🤖 Generated with Claude Code

- outgoing message serialization moved outside the producers' lock via list double-buffering
- WorldSyncCommandBuffer instance reused across batches (Renew) instead of per-tick allocation
- idle fast path: empty sync buffers skip the world mutex acquisition and command buffer playback

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@pravusjif
pravusjif requested review from a team as code owners June 12, 2026 01:00
@github-actions

github-actions Bot commented Jun 12, 2026

Copy link
Copy Markdown
Contributor

badge

New build in progress, come back later!

@pravusjif pravusjif changed the title opti: CRDT bridge concurrency improvements chore: CRDT bridge concurrency improvements Jun 12, 2026
@pravusjif pravusjif added enhancement Enhancement of an existing feature optimization Something isn't optimal. Lets optimize it! force-build Used to trigger a build on draft PR labels Jun 12, 2026
@pravusjif pravusjif moved this to In Progress in Explorer Alpha Jun 12, 2026
@m3taphysics

Copy link
Copy Markdown
Contributor

PR #8968, run #27390699039

Builds: Windows change, Windows baseline, macOS change, macOS baseline

How to read this table
  • Each build is measured 3 times. The values are the median, and (min–max) is the lowest and highest of those runs — a wide range means the metric is noisy and small differences are not trustworthy.
  • Δ is Change minus Baseline (a negative Δ means Change is faster).
  • 🟢 faster / 🔴 slower — a real difference: larger than both 3% and the run-to-run range.
  • ⚪ within noise — the difference is smaller than how much the build varies between its own runs, so it cannot be told apart from random variation. Treat it as no change.
  • Exceptions logged — exceptions found in the run logs; more than the baseline is flagged 🔴 even when frame times look fine. The Exception breakdown under each table groups them by the explorer's report category and exception type.
  • A run that logged unusually many exceptions (at least 10 and 5× the median of its build's runs — e.g. a service was down during it) is excluded from all numbers and called out under the table.

Framework 13 i7

Metric Baseline Change Δ Result
Samples 2701 (×3) 2701 (×3)
CPU average 33.3 ms 33.3 ms -0.0 ms ⚪ within noise
CPU 1% worst 33.8 ms (33.7–34.0) 33.7 ms (33.6–33.9) -0.1 ms ⚪ within noise
CPU 0.1% worst 37.1 ms (36.0–37.2) 36.5 ms (35.0–38.7) -0.7 ms ⚪ within noise
GPU average 7.5 ms (7.5–7.6) 7.9 ms (7.6–8.0) 0.3 ms ⚪ within noise
GPU 1% worst 19.7 ms (19.2–20.0) 20.1 ms (18.8–20.9) 0.4 ms ⚪ within noise
GPU 0.1% worst 24.8 ms (24.1–26.8) 25.4 ms (24.9–26.2) 0.7 ms ⚪ within noise
Exceptions logged 6 6 0 ⚪ none new
Exception breakdown
Exception Baseline Change
[ENGINE] NullReferenceException 6 6

@pravusjif pravusjif added the sdk label Jun 25, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

do not merge enhancement Enhancement of an existing feature force-build Used to trigger a build on draft PR optimization Something isn't optimal. Lets optimize it! sdk

Projects

Status: In Progress

Development

Successfully merging this pull request may close these issues.

3 participants