AI-assisted issue. Filed by agent driven by @soloturn via GDD.
Context
Found while reviewing #4879 and its base branch feat/chunk-ordering-reactor to decide whether either was still worth reviving. Filing this so the underlying problem is tracked independently of the PRs that tried to fix it — it has now outlived two of them.
This is not a newly discovered bug. It has a documented history, is still reproducible on develop, and the last serious attempt (#4822) is still open and was never rejected on technical grounds — it was reviewed favourably and simply stalled.
Problem / Current State
Chunk generation order goes stale as the player moves. The canonical description is @naalit's, from #4773:
You'll probably notice that chunks near you often generate after chunks far away from you, especially if you're moving. Right now, the ChunkProcessingPipeline orders chunks by storing them in a priority queue indexed by their distance to a player. The problem is, when the player moves the priority queue has cached some priorities, and so a chunk that used to be far away and is now close will still have a low priority.
In extreme cases, if you move to right next to a chunk that used to be at the edge of your view distance, all of the chunks that are now in range that didn't used to be will generate before the one right next to you.
Still accurate on current develop. The mechanism:
RelevanceSystem eagerly calls chunkProvider.createOrLoadChunk(pos) for every position entering a relevance region (RelevanceSystem.java:137, :189).
- Each becomes a
PositionFuture in ChunkProcessingPipeline's PriorityBlockingQueue(800, comparator), ordered by ChunkTaskRelevanceComparator (distance from region centres).
- That heap is ordered at insertion time.
PriorityBlockingQueue only re-compares during sift operations, so entries already placed keep positions derived from scores computed when the player was somewhere else.
Related smell in the same file, untouched since 2021:
.sorted(new PositionRelevanceComparator()) //<-- this is n^2 cost. not sure why this needs to be sorted like this.
(RelevanceSystem.java:182)
Prior art — read this before starting
Three attempts, none merged:
|
|
|
| #4773 |
closed |
feat: order chunks on demand. Original fix. Replaced the priority queue with a re-sorted list plus a new InitialChunkProvider that generator threads pull from. Closed in favour of the Reactor rewrite. |
| #4822 |
OPEN |
feat: rework chunk ordering using Reactor. Rewrite of #4773 using Reactor, per #4798. Reviewed by @keturn, @skaldarnar, @pollend, @DarkWeird; @skaldarnar recorded before/after comparison videos; @pollend asked "anything else blocking this?" — and it stopped there. Now conflicting. |
| #4879 |
draft |
Late light merging, stacked on #4822's branch. Separate idea, being pursued separately. |
Umbrella context: #4798 (Refactor usage of concurrency with reactor) is still open with unchecked boxes; this work is part of it.
The design question that actually blocked #4822 is still unanswered. @keturn proposed the shape you would expect:
Flux<Chunk> fullChunks = locations.parallel().map(chunkProvider);
fullChunks.subscribe(cacheChunk);
@naalit's objection — "when there are no more chunks to generate right now and execution pauses, there's no way to wake it up again from the main thread" — is what the implemented version works around with notifyRelevanceChanged() (called on relevance-region update, i.e. roughly every player move). @keturn: "This is the part I keep getting stuck on when trying to think about this design."
Any new attempt should answer that explicitly rather than rediscover it.
Acceptance Criteria
Technical Notes
2f05f3132 on feat/chunk-ordering-reactor is worth reading as a design reference even though it no longer applies. It inverts the flow to pull-based: RelevanceSystem stops creating chunks and only exposes neededChunks(); LocalChunkProvider.chunkFlux() is a Flux with backpressure; the pipeline requests CHUNKS_AT_ONCE at a time, and on each request the pending list is re-sorted by current relevance and only the nearest N handed over.
Reviving #4822 by rebasing is not viable — it predates the org.terasology.engine.* restructure (#5192) and conflicts on its first commit. Treat it as a specification, not a patch.
Constraints a replacement must respect that #4822 predates:
Two loose ends inherited from the old discussions:
Sequencing: this gets easier once light merging moves out of the pipeline. Light merging is currently the only multi-chunk stage (the sole production uses of ChunkTaskProvider.createMulti), and it is what forces the pipeline to hold many chunks in flight pending neighbours — the branch's processingInfoReactor() defer-loop exists largely to service exactly that. With it gone the pipeline is a straight per-chunk sequence and a bounded pull model is considerably simpler.
Related
Context
Found while reviewing #4879 and its base branch
feat/chunk-ordering-reactorto decide whether either was still worth reviving. Filing this so the underlying problem is tracked independently of the PRs that tried to fix it — it has now outlived two of them.This is not a newly discovered bug. It has a documented history, is still reproducible on
develop, and the last serious attempt (#4822) is still open and was never rejected on technical grounds — it was reviewed favourably and simply stalled.Problem / Current State
Chunk generation order goes stale as the player moves. The canonical description is @naalit's, from #4773:
Still accurate on current
develop. The mechanism:RelevanceSystemeagerly callschunkProvider.createOrLoadChunk(pos)for every position entering a relevance region (RelevanceSystem.java:137,:189).PositionFutureinChunkProcessingPipeline'sPriorityBlockingQueue(800, comparator), ordered byChunkTaskRelevanceComparator(distance from region centres).PriorityBlockingQueueonly re-compares during sift operations, so entries already placed keep positions derived from scores computed when the player was somewhere else.Related smell in the same file, untouched since 2021:
(
RelevanceSystem.java:182)Prior art — read this before starting
Three attempts, none merged:
feat: order chunks on demand. Original fix. Replaced the priority queue with a re-sorted list plus a newInitialChunkProviderthat generator threads pull from. Closed in favour of the Reactor rewrite.feat: rework chunk ordering using Reactor. Rewrite of #4773 using Reactor, per #4798. Reviewed by @keturn, @skaldarnar, @pollend, @DarkWeird; @skaldarnar recorded before/after comparison videos; @pollend asked "anything else blocking this?" — and it stopped there. Now conflicting.Umbrella context: #4798 (
Refactor usage of concurrency with reactor) is still open with unchecked boxes; this work is part of it.The design question that actually blocked #4822 is still unanswered. @keturn proposed the shape you would expect:
@naalit's objection — "when there are no more chunks to generate right now and execution pauses, there's no way to wake it up again from the main thread" — is what the implemented version works around with
notifyRelevanceChanged()(called on relevance-region update, i.e. roughly every player move). @keturn: "This is the part I keep getting stuck on when trying to think about this design."Any new attempt should answer that explicitly rather than rediscover it.
Acceptance Criteria
n^2sort inRelevanceSystem.addRelevanceEntityis gone or justified.Technical Notes
2f05f3132onfeat/chunk-ordering-reactoris worth reading as a design reference even though it no longer applies. It inverts the flow to pull-based:RelevanceSystemstops creating chunks and only exposesneededChunks();LocalChunkProvider.chunkFlux()is aFluxwith backpressure; the pipeline requestsCHUNKS_AT_ONCEat a time, and on each request the pending list is re-sorted by current relevance and only the nearest N handed over.Reviving #4822 by rebasing is not viable — it predates the
org.terasology.engine.*restructure (#5192) and conflicts on its first commit. Treat it as a specification, not a patch.Constraints a replacement must respect that #4822 predates:
NUM_TASK_THREADS = 2.develophas since made chunk threads configurable (feat: make number of chunk processing threads configurable #5237) and pool sizing processor-based (fix(ChunkProcessingPipeline): base thread pool size on available processors #5014).c645e8154fixed the never-arriving-neighbour stall differently from the branch'seb48853fe.Two loose ends inherited from the old discussions:
feat/chunk-ordering-reactoronly and never reacheddevelop— confirmed, not an ancestor, no equivalent commit. Worth redoing independently of the ordering work if that contention is still real.Sequencing: this gets easier once light merging moves out of the pipeline. Light merging is currently the only multi-chunk stage (the sole production uses of
ChunkTaskProvider.createMulti), and it is what forces the pipeline to hold many chunks in flight pending neighbours — the branch'sprocessingInfoReactor()defer-loop exists largely to service exactly that. With it gone the pipeline is a straight per-chunk sequence and a bounded pull model is considerably simpler.Related
DynamicPriorityBlockingQueue, an earlier attempt at keeping ordering fresh, two weeks before feat: rework chunk ordering using Reactor #4822 started.develop.developa replacement must not regress.