Async-read view for LevelChunkSection#733
Conversation
|
For your goal, do you
|
|
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. |
Why
Plugins that read blocks off-thread currently have to call
LevelChunkSection.copy()andread through the returned wrapper. That works, but you end up with a full
LevelChunkSectionincludingnonEmptyBlockCount/tickingBlockCountfields that gostale 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.Datareference for the block stateand biome containers, nothing else.
This adds a small API for that.
LevelChunkSection.acquireAsyncReadView()returns anAutoCloseableview backed by snapshotDatainstances for both containers.How it works
acquireAsyncSnapshotclones the currentData, publishes the clone as the new livecontainer, 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
Dataclone, same asLevelChunkSection.copy(). Most of the costin
copy()already comes fromBitStorage.copy()andPalette.copy(), so this does notreduce the raw cloning cost.
The benefit is in usage. Callers no longer deal with a full
LevelChunkSectionthat hasinternally inconsistent state, and the API leaves room for improving how snapshots are
backed later without changing call sites.
Notes
releaseAsyncSnapshotis currently a no-op. It exists so a future implementation cantrack readers without changing the API.
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.