mirror of
https://github.com/PaperMC/Paper.git
synced 2025-01-10 10:17:38 +01:00
373ed1ddd5
properly removed from the chunk. This could lead to dead entities accumulating in memory over time if the chunk never gets fully unloaded (as it is the case for chunks around the spawn region). The issue is that Minecraft processes the removal of these entities during the next tick, when the chunk has already switched to state INACCESSIBLE and can no longer be retrieved as usual. For the purpose of removing dead entities from their still loaded but no longer accessible chunk, this adds and uses a new method with which a chunk can be accessed without checking its current state first. By: blablubbabc <lukas@wirsindwir.de>
128 lines
6.7 KiB
Diff
128 lines
6.7 KiB
Diff
--- a/net/minecraft/server/PlayerChunk.java
|
|
+++ b/net/minecraft/server/PlayerChunk.java
|
|
@@ -44,7 +44,7 @@
|
|
this.fullChunkFuture = PlayerChunk.UNLOADED_CHUNK_FUTURE;
|
|
this.tickingFuture = PlayerChunk.UNLOADED_CHUNK_FUTURE;
|
|
this.entityTickingFuture = PlayerChunk.UNLOADED_CHUNK_FUTURE;
|
|
- this.chunkSave = CompletableFuture.completedFuture((Object) null);
|
|
+ this.chunkSave = CompletableFuture.completedFuture(null); // CraftBukkit - decompile error
|
|
this.dirtyBlocks = new ShortSet[16];
|
|
this.location = chunkcoordintpair;
|
|
this.lightEngine = lightengine;
|
|
@@ -56,6 +56,19 @@
|
|
this.a(i);
|
|
}
|
|
|
|
+ // CraftBukkit start
|
|
+ public Chunk getFullChunk() {
|
|
+ if (!getChunkState(this.oldTicketLevel).isAtLeast(PlayerChunk.State.BORDER)) return null; // note: using oldTicketLevel for isLoaded checks
|
|
+ return this.getFullChunkUnchecked();
|
|
+ }
|
|
+
|
|
+ public Chunk getFullChunkUnchecked() {
|
|
+ CompletableFuture<Either<IChunkAccess, PlayerChunk.Failure>> statusFuture = this.getStatusFutureUnchecked(ChunkStatus.FULL);
|
|
+ Either<IChunkAccess, PlayerChunk.Failure> either = (Either<IChunkAccess, PlayerChunk.Failure>) statusFuture.getNow(null);
|
|
+ return (either == null) ? null : (Chunk) either.left().orElse(null);
|
|
+ }
|
|
+ // CraftBukkit end
|
|
+
|
|
public CompletableFuture<Either<IChunkAccess, PlayerChunk.Failure>> getStatusFutureUnchecked(ChunkStatus chunkstatus) {
|
|
CompletableFuture<Either<IChunkAccess, PlayerChunk.Failure>> completablefuture = (CompletableFuture) this.statusFutures.get(chunkstatus.c());
|
|
|
|
@@ -81,9 +94,9 @@
|
|
@Nullable
|
|
public Chunk getChunk() {
|
|
CompletableFuture<Either<Chunk, PlayerChunk.Failure>> completablefuture = this.a();
|
|
- Either<Chunk, PlayerChunk.Failure> either = (Either) completablefuture.getNow((Object) null);
|
|
+ Either<Chunk, PlayerChunk.Failure> either = (Either) completablefuture.getNow(null); // CraftBukkit - decompile error
|
|
|
|
- return either == null ? null : (Chunk) either.left().orElse((Object) null);
|
|
+ return either == null ? null : (Chunk) either.left().orElse(null); // CraftBukkit - decompile error
|
|
}
|
|
|
|
@Nullable
|
|
@@ -114,6 +127,7 @@
|
|
if (chunk != null) {
|
|
byte b0 = (byte) SectionPosition.a(blockposition.getY());
|
|
|
|
+ if (b0 < 0 || b0 >= this.dirtyBlocks.length) return; // CraftBukkit - SPIGOT-6086, SPIGOT-6296
|
|
if (this.dirtyBlocks[b0] == null) {
|
|
this.p = true;
|
|
this.dirtyBlocks[b0] = new ShortArraySet();
|
|
@@ -216,7 +230,7 @@
|
|
CompletableFuture<Either<IChunkAccess, PlayerChunk.Failure>> completablefuture = (CompletableFuture) this.statusFutures.get(i);
|
|
|
|
if (completablefuture != null) {
|
|
- Either<IChunkAccess, PlayerChunk.Failure> either = (Either) completablefuture.getNow((Object) null);
|
|
+ Either<IChunkAccess, PlayerChunk.Failure> either = (Either) completablefuture.getNow(null); // CraftBukkit - decompile error
|
|
|
|
if (either == null || either.left().isPresent()) {
|
|
return completablefuture;
|
|
@@ -271,6 +285,30 @@
|
|
boolean flag1 = this.ticketLevel <= PlayerChunkMap.GOLDEN_TICKET;
|
|
PlayerChunk.State playerchunk_state = getChunkState(this.oldTicketLevel);
|
|
PlayerChunk.State playerchunk_state1 = getChunkState(this.ticketLevel);
|
|
+ // CraftBukkit start
|
|
+ // ChunkUnloadEvent: Called before the chunk is unloaded: isChunkLoaded is still true and chunk can still be modified by plugins.
|
|
+ if (playerchunk_state.isAtLeast(PlayerChunk.State.BORDER) && !playerchunk_state1.isAtLeast(PlayerChunk.State.BORDER)) {
|
|
+ this.getStatusFutureUnchecked(ChunkStatus.FULL).thenAccept((either) -> {
|
|
+ Chunk chunk = (Chunk)either.left().orElse(null);
|
|
+ if (chunk != null) {
|
|
+ playerchunkmap.callbackExecutor.execute(() -> {
|
|
+ // Minecraft will apply the chunks tick lists to the world once the chunk got loaded, and then store the tick
|
|
+ // lists again inside the chunk once the chunk becomes inaccessible and set the chunk's needsSaving flag.
|
|
+ // These actions may however happen deferred, so we manually set the needsSaving flag already here.
|
|
+ chunk.setNeedsSaving(true);
|
|
+ chunk.unloadCallback();
|
|
+ });
|
|
+ }
|
|
+ }).exceptionally((throwable) -> {
|
|
+ // ensure exceptions are printed, by default this is not the case
|
|
+ MinecraftServer.LOGGER.fatal("Failed to schedule unload callback for chunk " + PlayerChunk.this.location, throwable);
|
|
+ return null;
|
|
+ });
|
|
+
|
|
+ // Run callback right away if the future was already done
|
|
+ playerchunkmap.callbackExecutor.run();
|
|
+ }
|
|
+ // CraftBukkit end
|
|
CompletableFuture completablefuture;
|
|
|
|
if (flag) {
|
|
@@ -302,7 +340,7 @@
|
|
if (flag2 && !flag3) {
|
|
completablefuture = this.fullChunkFuture;
|
|
this.fullChunkFuture = PlayerChunk.UNLOADED_CHUNK_FUTURE;
|
|
- this.a(completablefuture.thenApply((either1) -> {
|
|
+ this.a(((CompletableFuture<Either<Chunk, PlayerChunk.Failure>>) completablefuture).thenApply((either1) -> { // CraftBukkit - decompile error
|
|
playerchunkmap.getClass();
|
|
return either1.ifLeft(playerchunkmap::a);
|
|
}));
|
|
@@ -340,6 +378,26 @@
|
|
|
|
this.u.a(this.location, this::k, this.ticketLevel, this::d);
|
|
this.oldTicketLevel = this.ticketLevel;
|
|
+ // CraftBukkit start
|
|
+ // ChunkLoadEvent: Called after the chunk is loaded: isChunkLoaded returns true and chunk is ready to be modified by plugins.
|
|
+ if (!playerchunk_state.isAtLeast(PlayerChunk.State.BORDER) && playerchunk_state1.isAtLeast(PlayerChunk.State.BORDER)) {
|
|
+ this.getStatusFutureUnchecked(ChunkStatus.FULL).thenAccept((either) -> {
|
|
+ Chunk chunk = (Chunk)either.left().orElse(null);
|
|
+ if (chunk != null) {
|
|
+ playerchunkmap.callbackExecutor.execute(() -> {
|
|
+ chunk.loadCallback();
|
|
+ });
|
|
+ }
|
|
+ }).exceptionally((throwable) -> {
|
|
+ // ensure exceptions are printed, by default this is not the case
|
|
+ MinecraftServer.LOGGER.fatal("Failed to schedule load callback for chunk " + PlayerChunk.this.location, throwable);
|
|
+ return null;
|
|
+ });
|
|
+
|
|
+ // Run callback right away if the future was already done
|
|
+ playerchunkmap.callbackExecutor.run();
|
|
+ }
|
|
+ // CraftBukkit end
|
|
}
|
|
|
|
public static ChunkStatus getChunkStatus(int i) {
|