Skip to content

Prime Terra cache before tpll teleports - #79

Open
robinferch wants to merge 2 commits into
mainfrom
feature/tpll-region-cache-priming
Open

Prime Terra cache before tpll teleports#79
robinferch wants to merge 2 commits into
mainfrom
feature/tpll-region-cache-priming

Conversation

@robinferch

Copy link
Copy Markdown
Member

Summary

  • add a public Terraplusminus API for checking and priming /tpll target regions
  • wait for a 16-chunk-radius Terra-- cache prewarm before standalone /tpll teleports
  • add retry/invalidation around failed Terra-- chunk data loads
  • bound prewarm concurrency to avoid unbounded Terra-- loader fanout

Verification

  • mvn -q -DskipTests package

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR adds a public API to check/prime Terra-- chunk-data cache for a target teleport region, integrates a 16-chunk-radius prewarm into /tpll teleports, and adds retry/invalidation plus bounded concurrency when loading Terra-- chunk data.

Changes:

  • Expose Terraplusminus APIs to check whether a teleport region is generated and to prime Terra-- cache for that region.
  • Add cache prewarm + user messaging before executing /tpll teleports (including linked-world teleports).
  • Add retry + cache invalidation for failed Terra-- chunk-data loads, and batch prewarming to bound concurrent loads.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 2 comments.

File Description
src/main/java/de/btegermany/terraplusminus/Terraplusminus.java Adds public region-generation check and cache-priming/prepare APIs for optional integrations.
src/main/java/de/btegermany/terraplusminus/commands/TpllCommand.java Wraps teleport execution with region prewarm logic (16-chunk radius) and improved main-thread scheduling.
src/main/java/de/btegermany/terraplusminus/gen/RealWorldGenerator.java Introduces chunk-data retry/invalidation and a bounded-concurrency cache prewarm API; adds isParallelCapable().
src/main/java/de/btegermany/terraplusminus/gen/tree/TreePopulator.java Refactors chunk-data loading to an injected provider and updates biome-based tree selection + RNG handling.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

// <editor-fold desc="Constants and Fields">
public static final String LAT_LON_HEIGHT = "latLonHeight";
public static final String TPLL_OTHERS_PERMISSION = "t+-.forcetpll";
private static final int TELEPORT_PREP_RADIUS_CHUNKS = 16;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please make that configurable

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This default value also seems quite high. The default render distance spherical radius is 10, and with a square radius of 16 this generates approximately (32²)/(pi*10²) = 3.26 times the surface it will actually need to load after the teleportation.

An alternative from having it configurable would be to have it match the render distance radius.

* This method is intentionally public so optional teleportation plugins can call it without
* Terraplusminus depending on them.
*/
public boolean isTeleportRegionGenerated(@NotNull World world, double x, double z, int radiusChunks) {

@Zoriot Zoriot Jun 6, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As i have suspected this call is relative resource intense
https://mclo.gs/s7oXM5W
I would prefer to only check the chunk where you are teleporting to or make it opt in/out & configurable

Generally performance seems not to be that much better, not not done extensive testing trough.

* Primes Terra-- chunk data for the requested teleport region when the world uses Terraplusminus.
* Non-Terraplusminus worlds complete immediately so this API is safe for optional integrations.
*/
public @NotNull CompletableFuture<Void> primeTeleportRegionCache(@NotNull World world, double x, double z, int radiusChunks) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Both of these method shouldn't be in that class. Imo it should be also abstracted as a real api

@SmylerMC SmylerMC left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What exactly is this PR trying to achieve? Its description explains what it changes but not why, and I wonder how much of it actually improves performance and how much of it is premature optimization. World generation within the plugin goes through multiple abstraction layers, multiple of which have their own concurrency or caching systems. This makes things quite hard to grasp, and mandating rigorous goals and benchmarking when introducing new optimization mechanisms. Otherwise, we risk adding complexity for no actual benefit. The fact this PR also includes changes that not directly related to its main point (warming the cache) also makes it harder to review.

BlockData surfaceBlock = this.blockMapper.map(terraData.surfaceBlock(x, z));
if (surfaceBlock == null) {
// We do that for each column, so it does not depend on the configuration but only on the seed
int startMountainHeight = random.nextInt(7500, 7520);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As hinted by the comment, moving this random computation after the vertical bounds early return changes the generation behavior as the random value picked for startMountainHeight now depends on both the world seed and which columns were skipped. Ideally, randomization should only depend on the seed. For example, worlds with different Y offsets but otherwise identical configurations will have different mountain patterns.

// <editor-fold desc="Constants and Fields">
public static final String LAT_LON_HEIGHT = "latLonHeight";
public static final String TPLL_OTHERS_PERMISSION = "t+-.forcetpll";
private static final int TELEPORT_PREP_RADIUS_CHUNKS = 16;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This default value also seems quite high. The default render distance spherical radius is 10, and with a square radius of 16 this generates approximately (32²)/(pi*10²) = 3.26 times the surface it will actually need to load after the teleportation.

An alternative from having it configurable would be to have it match the render distance radius.

int radius = Math.max(0, radiusChunks);
CompletableFuture<Void> result = CompletableFuture.completedFuture(null);
List<ChunkPos> batch = new ArrayList<>(PRIME_CACHE_CONCURRENCY);
for (int chunkX = centerChunkX - radius; chunkX <= centerChunkX + radius; chunkX++) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Optimization suggestion: make this a disk instead of a square, to reduce the area by a factor of (2r)² / (pi*r)² = 1.3.

@@ -204,7 +215,92 @@ private CachedChunkData getTerraChunkData(int chunkX, int chunkZ) {
* @return A CompletableFuture containing the CachedChunkData
*/
public CompletableFuture<CachedChunkData> getBaseHeightAsync(int chunkX, int chunkZ) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Out of scope for this PR, but this method is badly named.

}).thenCompose(result -> result);
}

private CompletableFuture<CachedChunkData> retryChunkData(ChunkPos chunkPos, int attempt, Throwable throwable) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Issue: a retry mechanism seems out of scope for this PR, and this specific implementations is likely to cause retry-storms.

for (int chunkX = centerChunkX - radius; chunkX <= centerChunkX + radius; chunkX++) {
for (int chunkZ = centerChunkZ - radius; chunkZ <= centerChunkZ + radius; chunkZ++) {
batch.add(new ChunkPos(chunkX, chunkZ));
if (batch.size() >= PRIME_CACHE_CONCURRENCY) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Question: is the addition of this batching mechanism the result of some benchmarking? If so, explaining why it is necessary in a comment would be nice. If not, it looks like it could be premature optimization.

*/
public CompletableFuture<Void> primeCache(int centerChunkX, int centerChunkZ, int radiusChunks) {
int radius = Math.max(0, radiusChunks);
CompletableFuture<Void> result = CompletableFuture.completedFuture(null);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggestion: using a semaphore here instead of this CompletableFuture chaining would likely improve readability.

List<ChunkPos> chunkBatch = List.copyOf(batch);
return previous.thenCompose(unused -> CompletableFuture.allOf(
chunkBatch.stream()
.map(chunkPos -> this.getChunkDataAsync(chunkPos).thenApply(data -> null))

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

.thenApply(data -> null) is unnecessary



} catch (InterruptedException | ExecutionException e) {
} catch (InterruptedException e) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Question: why handle InterruptedException separately?

import java.util.function.Function;


public class TreePopulator extends BlockPopulator {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sharing the chunk data provider with the world generator is a great change for this PR, but there is also lots of refactoring in this class that seems out of scope.

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.

4 participants