1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
|
--- a/net/minecraft/server/level/ChunkHolder.java
+++ b/net/minecraft/server/level/ChunkHolder.java
@@ -36,6 +36,10 @@
import net.minecraft.world.level.chunk.ProtoChunk;
import net.minecraft.world.level.lighting.LevelLightEngine;
+// CraftBukkit start
+import net.minecraft.server.MinecraftServer;
+// CraftBukkit end
+
public class ChunkHolder {
public static final Either<ChunkAccess, ChunkHolder.ChunkLoadingFailure> UNLOADED_CHUNK = Either.right(ChunkHolder.ChunkLoadingFailure.UNLOADED);
@@ -90,9 +94,23 @@
this.changedBlocksPerSection = new ShortSet[levelheightaccessor.getSectionsCount()];
}
- public CompletableFuture<Either<ChunkAccess, ChunkHolder.ChunkLoadingFailure>> getFutureIfPresentUnchecked(ChunkStatus chunkstatus) {
- CompletableFuture<Either<ChunkAccess, ChunkHolder.ChunkLoadingFailure>> completablefuture = (CompletableFuture) this.futures.get(chunkstatus.getIndex());
+ // CraftBukkit start
+ public LevelChunk getFullChunkNow() {
+ // Note: We use the oldTicketLevel for isLoaded checks.
+ if (!ChunkLevel.fullStatus(this.oldTicketLevel).isOrAfter(FullChunkStatus.FULL)) return null;
+ return this.getFullChunkNowUnchecked();
+ }
+ public LevelChunk getFullChunkNowUnchecked() {
+ CompletableFuture<Either<ChunkAccess, ChunkHolder.Failure>> statusFuture = this.getFutureIfPresentUnchecked(ChunkStatus.FULL);
+ Either<ChunkAccess, ChunkHolder.Failure> either = (Either<ChunkAccess, ChunkHolder.Failure>) statusFuture.getNow(null);
+ return (either == null) ? null : (LevelChunk) either.left().orElse(null);
+ }
+ // CraftBukkit end
+
+ public CompletableFuture<Either<ChunkAccess, ChunkHolder.Failure>> getFutureIfPresentUnchecked(ChunkStatus chunkStatus) {
+ CompletableFuture<Either<ChunkAccess, ChunkHolder.Failure>> completablefuture = (CompletableFuture) this.futures.get(chunkStatus.getIndex());
+
return completablefuture == null ? ChunkHolder.UNLOADED_CHUNK_FUTURE : completablefuture;
}
@@ -179,6 +197,7 @@
if (levelchunk != null) {
int i = this.levelHeightAccessor.getSectionIndex(blockpos.getY());
+ if (i < 0 || i >= this.changedBlocksPerSection.length) return; // CraftBukkit - SPIGOT-6086, SPIGOT-6296
if (this.changedBlocksPerSection[i] == null) {
this.hasChangedSections = true;
this.changedBlocksPerSection[i] = new ShortOpenHashSet();
@@ -256,9 +275,12 @@
LevelChunkSection levelchunksection = levelchunk.getSection(i);
ClientboundSectionBlocksUpdatePacket clientboundsectionblocksupdatepacket = new ClientboundSectionBlocksUpdatePacket(sectionpos, shortset, levelchunksection);
- this.broadcast(list, clientboundsectionblocksupdatepacket);
- clientboundsectionblocksupdatepacket.runUpdates((blockpos1, blockstate1) -> {
- this.broadcastBlockEntityIfNeeded(list, level, blockpos1, blockstate1);
+ this.broadcast(list, packetplayoutmultiblockchange);
+ // CraftBukkit start
+ List finalList = list;
+ packetplayoutmultiblockchange.runUpdates((blockposition1, iblockdata1) -> {
+ this.broadcastBlockEntityIfNeeded(finalList, world, blockposition1, iblockdata1);
+ // CraftBukkit end
});
}
}
@@ -411,7 +433,31 @@
boolean flag1 = ChunkLevel.isLoaded(this.ticketLevel);
FullChunkStatus fullchunkstatus = ChunkLevel.fullStatus(this.oldTicketLevel);
FullChunkStatus fullchunkstatus1 = ChunkLevel.fullStatus(this.ticketLevel);
+ // CraftBukkit start
+ // ChunkUnloadEvent: Called before the chunk is unloaded: isChunkLoaded is still true and chunk can still be modified by plugins.
+ if (fullchunkstatus.isOrAfter(FullChunkStatus.FULL) && !fullchunkstatus1.isOrAfter(FullChunkStatus.FULL)) {
+ this.getFutureIfPresentUnchecked(ChunkStatus.FULL).thenAccept((either) -> {
+ LevelChunk chunk = (LevelChunk)either.left().orElse(null);
+ if (chunk != null) {
+ chunkMap.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.setUnsaved(true);
+ chunk.unloadCallback();
+ });
+ }
+ }).exceptionally((throwable) -> {
+ // ensure exceptions are printed, by default this is not the case
+ MinecraftServer.LOGGER.error("Failed to schedule unload callback for chunk " + ChunkHolder.this.pos, throwable);
+ return null;
+ });
+ // Run callback right away if the future was already done
+ chunkMap.callbackExecutor.run();
+ }
+ // CraftBukkit end
+
if (flag) {
Either<ChunkAccess, ChunkHolder.ChunkLoadingFailure> either = Either.right(new ChunkHolder.ChunkLoadingFailure() {
@Override
@@ -482,6 +527,26 @@
this.onLevelChange.onLevelChange(this.pos, this::getQueueLevel, this.ticketLevel, this::setQueueLevel);
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 (!fullchunkstatus.isOrAfter(FullChunkStatus.FULL) && fullchunkstatus1.isOrAfter(FullChunkStatus.FULL)) {
+ this.getFutureIfPresentUnchecked(ChunkStatus.FULL).thenAccept((either) -> {
+ LevelChunk chunk = (LevelChunk)either.left().orElse(null);
+ if (chunk != null) {
+ chunkMap.callbackExecutor.execute(() -> {
+ chunk.loadCallback();
+ });
+ }
+ }).exceptionally((throwable) -> {
+ // ensure exceptions are printed, by default this is not the case
+ MinecraftServer.LOGGER.error("Failed to schedule load callback for chunk " + ChunkHolder.this.pos, throwable);
+ return null;
+ });
+
+ // Run callback right away if the future was already done
+ chunkMap.callbackExecutor.run();
+ }
+ // CraftBukkit end
}
public boolean wasAccessibleSinceLastSave() {
|