Skip to content

Batch ready world columns - #339

Merged
InventivetalentDev merged 6 commits into
mainfrom
perf/batch-column-placement
Oct 17, 2026
Merged

InventivetalentDev merged 6 commits into
mainfrom
perf/batch-column-placement

Conversation

@InventivetalentDev

@InventivetalentDev InventivetalentDev commented Oct 17, 2026 •

Copy link
Copy Markdown
Owner

Streaming adjacent parsed columns currently rebuilds shared neighbor sections after each column. #337's placement profile shows the cost: of 965 section builds per pass on the Etho route, 537 happen while neighbouring columns are placed. This adds MineRenderWorld.placeChunks and lets WorldStreamer place the nearest ready columns together, so neighbor faces, fluids, lighting, ambient occlusion, and biome colors refresh once per group.

The placement profiler is provided by #337.

Behavior

  • Validate and snapshot all column/section coordinates before mutation, preserve input order and duplicate replacements, stop at the first failed column, and flush applied changes before rejecting with the first placement error.
  • Keep at most four pending or decoded source columns. Place only the nearest ready prefix without waiting for later unfinished reads. When the nearest column has no read and the window is full, the farthest decoded column gives up its slot. Adapters without placeChunks keep their existing serial path.
  • Serialize world mutations, cancel obsolete reads, keep the slots of reads that ignore cancellation until they settle, and clean the whole attempted placement group on failure. Placement errors reject promptly; disposal still waits for outstanding source work. Failed cleanup retains ownership for retry.
  • Yield between culling sections and mesh-build dispatch after YIELD_INTERVAL_MS (8 ms) of work.
  • Yield world work through MessageChannel instead of scheduler.yield(). Chrome runs scheduler.yield() continuations ahead of rendering, so a grouped placement's chained slices produced 66–100 ms frame-callback gaps; a trace of one 68 ms gap showed about 55 ms of back-to-back continuations with no frame between them. With MessageChannel tasks, frames render between slices.
  • Record grouped placement columns in the existing benchmark and in Profile world placement stages and per-section rebuilds #337's placement profile.

Measured result

With #337's profiler on the same route, each pass now builds 788 sections: 428 during their own column's placement, as before, and 360 during neighbouring placements instead of 537. The 126 placed columns take 70 placement operations instead of 126.

Three baseline (a53d0983) and three candidate invocations alternated in one session, using the same read-only Java 1.16.2 Etho save, route, settings, viewport, and hardware GPU. Warm results:

Metric Baseline Candidate
Streaming time, median 1,993.9 ms 1,638.7 ms (-17.8%)
Streaming time, range 1,814.9–2,008.0 ms 1,633.9–1,944.0 ms
Section builds, median 965 788 (-18.3%)
Section builds, range 965 784–788
Maximum callback gap, per-run range 16.8–33.4 ms 16.8 ms
Callback gaps above 50 ms, per run 0 / 0 / 0 0 / 0 / 0

The slowest candidate is faster than the slowest baseline but slower than the fastest baseline. Cold passes had a 16.8 ms maximum gap in every candidate run; their streaming time is dominated by asset loading and is similar for both. Median sampled peak JS heap was 269.6 → 257.4 MiB, including shared assets and benchmark records and excluding GPU memory.

Conditions: route [-15,8] → [-7,8] → [1,8] → [9,8] and return; seven stops per pass; load/unload radii 1/2; 1,000 ms settle; 1280×720 at pixel ratio 1; section meshing on; saved light, AO, blending, and mobs off; Chrome 155 on Apple M5 Pro (ANGLE Metal). The two raw regions fit in the source cache. Each pass placed 63 logical columns, unloaded 54 during streaming and nine at cleanup, and had nine loaded columns at every stop with no missing/failed/pending columns or worker fallback. Draw-call counts agree at every stop.

A heavier run with radii 2/3, saved light and AO, and blend radius 1 streamed in 1,810.5 ms warm here versus 2,285.6 ms on main. Main's maximum callback gap reached 50.1 ms warm and 83.4 ms cold; this branch stayed at 16.8 ms. That save lacks modern biome palettes, so blended-color equivalence is covered by the test fixture.

These results are specific to this route and cache state. Timing stages overlap; callback gaps are not displayed FPS. Raw reports and the save are not committed.

Validation

  • yarn test: 818 passed; yarn test:package (11), yarn test:api (18), yarn test:embed (15), and yarn test:node (6) pass.
  • yarn build, yarn typecheck, and yarn workspace @minerender/web build:production pass.

Regression tests cover input snapshots and signed/duplicate columns; sequential/grouped section geometry across fluid, light, AO, and biome boundaries; partial placement failure and retry; ready-prefix ordering and the four-column window; a full window holding a decoded column while the nearest column has no read, after a center move and after a retry; center changes and disposal; ignored cancellation; primitive errors; and cleanup failure ownership.

🤖 Generated with Claude Code

@github-actions github-actions Bot added library Public minerender package (packages/minerender) docs Documentation site, READMEs, and docs/ branches apps Any app under apps/ web Demo and test pages (apps/web) labels Oct 17, 2026
@InventivetalentDev InventivetalentDev changed the title Batch ready world columns (experimental) Batch ready world columns Oct 17, 2026
InventivetalentDev and others added 4 commits October 17, 2026 16:58
scheduler.yield() continuations ran back to back for about 55 ms without a
frame between them. Grouped placement chains 2-4 columns of placement,
culling, and dispatch without real async waits, so its 8 ms yields produced
66-83 ms frame-callback gaps. MessageChannel lets rendering run between slices.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… share placement cleanup, name the yield interval
@InventivetalentDev
InventivetalentDev force-pushed the perf/batch-column-placement branch from 502b5dd to 4bf8b10 Compare October 17, 2026 15:12
@InventivetalentDev
InventivetalentDev changed the base branch from main to perf/world-placement-profile October 17, 2026 15:12
@InventivetalentDev
InventivetalentDev marked this pull request as ready for review October 17, 2026 19:44
Base automatically changed from perf/world-placement-profile to main October 17, 2026 20:08
# Conflicts:
#	AGENTS.md
#	apps/web/benchmarks/world.ts
@InventivetalentDev
InventivetalentDev merged commit 941fc40 into main Oct 17, 2026
3 checks passed
@InventivetalentDev
InventivetalentDev deleted the perf/batch-column-placement branch October 17, 2026 20:14
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

apps Any app under apps/ docs Documentation site, READMEs, and docs/ branches library Public minerender package (packages/minerender) web Demo and test pages (apps/web)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant