Skip to content

Async-read view for LevelChunkSection#733

Open
duranaaron wants to merge 2 commits into
Winds-Studio:ver/1.21.11from
duranaaron:ver/1.21.11
Open

Async-read view for LevelChunkSection#733
duranaaron wants to merge 2 commits into
Winds-Studio:ver/1.21.11from
duranaaron:ver/1.21.11

Conversation

@duranaaron

Copy link
Copy Markdown

Why

Plugins that read blocks off-thread currently have to call LevelChunkSection.copy() and
read through the returned wrapper. That works, but you end up with a full
LevelChunkSection including nonEmptyBlockCount / tickingBlockCount fields that go
stale as soon as a main-thread write happens, plus an object you have to keep around for
the duration of the read.

What you actually need is a stable PalettedContainer.Data reference for the block state
and biome containers, nothing else.

This adds a small API for that. LevelChunkSection.acquireAsyncReadView() returns an
AutoCloseable view backed by snapshot Data instances for both containers.

How it works

acquireAsyncSnapshot clones the current Data, publishes the clone as the new live
container, and returns the previous instance to the caller. That instance is no longer
referenced by the container and is treated as read-only.

All writes after that point, synchronised or unchecked, go to the new live instance. The
snapshot held by the caller is never mutated.

The write path is unchanged. No read counters, no copy-on-write branches, no interaction
with getAndSetUnchecked.

Benchmarks

Each acquire does one Data clone, same as LevelChunkSection.copy(). Most of the cost
in copy() already comes from BitStorage.copy() and Palette.copy(), so this does not
reduce the raw cloning cost.

The benefit is in usage. Callers no longer deal with a full LevelChunkSection that has
internally inconsistent state, and the API leaves room for improving how snapshots are
backed later without changing call sites.

Notes

  • releaseAsyncSnapshot is currently a no-op. It exists so a future implementation can
    track readers without changing the API.
  • Block counts are not exposed. They are updated outside the snapshot boundary by
    setBlockState, so any value here would be misleading.

Context

I don’t usually work on server forks, but I ran into this while building a plugin that
does a lot of off-thread block sampling. This approach worked well for that use case, so
I figured it was worth sharing.

@MartijnMuijsers

Copy link
Copy Markdown
Collaborator

For your goal, do you

  • absolutely require a single consistent snapshot of the chunk data
    or
  • require reading the chunk off-thread, with some blocks randomly syncing up with the main thread at unpredictable times being okay (so view of data may be inconsistent, some blocks are the way they were at the time of acquiring the snapshot, some have been updated)
    ?

@duranaaron

Copy link
Copy Markdown
Author

Option 2 is enough for my use case.

The consumer is a worker thread that extracts terrain collision data for the physics engine by sweeping a section and sampling BlockStates. It doesn't require the entire section to represent one atomic point in time. If some blocks reflect an older state and others a newer one while the read is in progress, that's perfectly acceptable.

Every real block change already triggers a re-extraction for that section, so any stale collision data is corrected almost immediately. The generated collision mesh is only a cached representation, not authoritative world state.

The only thing I need is that each individual getBlockState(x, y, z) returns a coherent BlockState. In other words, no torn reads where the storage and palette no longer match due to a concurrent resize or Data swap. As long as each read is internally consistent, I don't need the entire section to be.

If there's a lighter-weight way to guarantee that per-read coherence without cloning the Data on acquire, I'd definitely prefer that. The snapshot approach in this PR was simply the most straightforward way I found to provide those guarantees.

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.

2 participants