PaperMC/paper-server/patches/sources/net/minecraft/server/level/ChunkMap.java.patch
Aikar d7f24e6729 Fire PlayerJoinEvent when Player is actually ready
For years, plugin developers have had to delay many things they do
inside of the PlayerJoinEvent by 1 tick to make it actually work.

This all boiled down to 1 reason why: The event fired before the
player was fully ready and joined to the world!

Additionally, if that player logged out on a vehicle, the event
fired before the vehicle was even loaded, so that plugins had no
access to the vehicle during this event either.

This change finally fixes this issue, fully preparing the player
into the world as a fully ready entity, vehicle included.

There should be no plugins that break because of this change, but might
improve consistency with other plugins instead.

For example, if 2 plugins listens to this event, and the first one
teleported the player in the event, then the 2nd plugin actually
would be getting a valid player!

This was very non deterministic. This change will ensure every plugin
receives a deterministic result, and should no longer require 1 tick
delays anymore.

== AT ==
public net.minecraft.server.level.ChunkMap addEntity(Lnet/minecraft/world/entity/Entity;)V
2020-04-19 00:05:46 -04:00

404 lines
22 KiB
Diff

--- a/net/minecraft/server/level/ChunkMap.java
+++ b/net/minecraft/server/level/ChunkMap.java
@@ -104,6 +104,10 @@
import org.apache.commons.lang3.mutable.MutableBoolean;
import org.slf4j.Logger;
+// CraftBukkit start
+import org.bukkit.craftbukkit.generator.CustomChunkGenerator;
+// CraftBukkit end
+
public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider, GeneratingChunkMap {
private static final ChunkResult<List<ChunkAccess>> UNLOADED_CHUNK_LIST_RESULT = ChunkResult.error("Unloaded chunks found in range");
@@ -149,6 +153,33 @@
public int serverViewDistance;
private final WorldGenContext worldGenContext;
+ // CraftBukkit start - recursion-safe executor for Chunk loadCallback() and unloadCallback()
+ public final CallbackExecutor callbackExecutor = new CallbackExecutor();
+ public static final class CallbackExecutor implements java.util.concurrent.Executor, Runnable {
+
+ private final java.util.Queue<Runnable> queue = new java.util.ArrayDeque<>();
+
+ @Override
+ public void execute(Runnable runnable) {
+ this.queue.add(runnable);
+ }
+
+ @Override
+ public void run() {
+ Runnable task;
+ while ((task = this.queue.poll()) != null) {
+ task.run();
+ }
+ }
+ };
+ // CraftBukkit end
+
+ // Paper start
+ public final ChunkHolder getUnloadingChunkHolder(int chunkX, int chunkZ) {
+ return this.pendingUnloads.get(ca.spottedleaf.moonrise.common.util.CoordinateUtils.getChunkKey(chunkX, chunkZ));
+ }
+ // Paper end
+
public ChunkMap(ServerLevel world, LevelStorageSource.LevelStorageAccess session, DataFixer dataFixer, StructureTemplateManager structureTemplateManager, Executor executor, BlockableEventLoop<Runnable> mainThreadExecutor, LightChunkGetter chunkProvider, ChunkGenerator chunkGenerator, ChunkProgressListener worldGenerationProgressListener, ChunkStatusUpdateListener chunkStatusChangeListener, Supplier<DimensionDataStorage> persistentStateManagerFactory, int viewDistance, boolean dsync) {
super(new RegionStorageInfo(session.getLevelId(), world.dimension(), "chunk"), session.getDimensionPath(world.dimension()).resolve("region"), dataFixer, dsync);
this.visibleChunkMap = this.updatingChunkMap.clone();
@@ -170,13 +201,19 @@
RegistryAccess iregistrycustom = world.registryAccess();
long j = world.getSeed();
- if (chunkGenerator instanceof NoiseBasedChunkGenerator chunkgeneratorabstract) {
+ // CraftBukkit start - SPIGOT-7051: It's a rigged game! Use delegate for random state creation, otherwise it is not so random.
+ ChunkGenerator randomGenerator = chunkGenerator;
+ if (randomGenerator instanceof CustomChunkGenerator customChunkGenerator) {
+ randomGenerator = customChunkGenerator.getDelegate();
+ }
+ if (randomGenerator instanceof NoiseBasedChunkGenerator chunkgeneratorabstract) {
+ // CraftBukkit end
this.randomState = RandomState.create((NoiseGeneratorSettings) chunkgeneratorabstract.generatorSettings().value(), (HolderGetter) iregistrycustom.lookupOrThrow(Registries.NOISE), j);
} else {
this.randomState = RandomState.create(NoiseGeneratorSettings.dummy(), (HolderGetter) iregistrycustom.lookupOrThrow(Registries.NOISE), j);
}
- this.chunkGeneratorState = chunkGenerator.createState(iregistrycustom.lookupOrThrow(Registries.STRUCTURE_SET), this.randomState, j);
+ this.chunkGeneratorState = chunkGenerator.createState(iregistrycustom.lookupOrThrow(Registries.STRUCTURE_SET), this.randomState, j, world.spigotConfig); // Spigot
this.mainThreadExecutor = mainThreadExecutor;
ConsecutiveExecutor consecutiveexecutor = new ConsecutiveExecutor(executor, "worldgen");
@@ -198,6 +235,12 @@
this.chunksToEagerlySave.add(pos.toLong());
}
+ // Paper start
+ public int getMobCountNear(final ServerPlayer player, final net.minecraft.world.entity.MobCategory mobCategory) {
+ return -1;
+ }
+ // Paper end
+
protected ChunkGenerator generator() {
return this.worldGenContext.generator();
}
@@ -325,7 +368,7 @@
throw this.debugFuturesAndCreateReportedException(new IllegalStateException("At least one of the chunk futures were null"), "n/a");
}
- ChunkAccess ichunkaccess = (ChunkAccess) chunkresult.orElse((Object) null);
+ ChunkAccess ichunkaccess = (ChunkAccess) chunkresult.orElse(null); // CraftBukkit - decompile error
if (ichunkaccess == null) {
return ChunkMap.UNLOADED_CHUNK_LIST_RESULT;
@@ -354,9 +397,9 @@
};
stringbuilder.append("Updating:").append(System.lineSeparator());
- this.updatingChunkMap.values().forEach(consumer);
+ ca.spottedleaf.moonrise.common.util.ChunkSystem.getUpdatingChunkHolders(this.level).forEach(consumer); // Paper
stringbuilder.append("Visible:").append(System.lineSeparator());
- this.visibleChunkMap.values().forEach(consumer);
+ ca.spottedleaf.moonrise.common.util.ChunkSystem.getVisibleChunkHolders(this.level).forEach(consumer); // Paper
CrashReport crashreport = CrashReport.forThrowable(exception, "Chunk loading");
CrashReportCategory crashreportsystemdetails = crashreport.addCategory("Chunk loading");
@@ -398,6 +441,9 @@
holder.setTicketLevel(level);
} else {
holder = new ChunkHolder(new ChunkPos(pos), level, this.level, this.lightEngine, this::onLevelChange, this);
+ // Paper start
+ ca.spottedleaf.moonrise.common.util.ChunkSystem.onChunkHolderCreate(this.level, holder);
+ // Paper end
}
this.updatingChunkMap.put(pos, holder);
@@ -427,7 +473,7 @@
protected void saveAllChunks(boolean flush) {
if (flush) {
- List<ChunkHolder> list = this.visibleChunkMap.values().stream().filter(ChunkHolder::wasAccessibleSinceLastSave).peek(ChunkHolder::refreshAccessibility).toList();
+ List<ChunkHolder> list = ca.spottedleaf.moonrise.common.util.ChunkSystem.getVisibleChunkHolders(this.level).stream().filter(ChunkHolder::wasAccessibleSinceLastSave).peek(ChunkHolder::refreshAccessibility).toList(); // Paper
MutableBoolean mutableboolean = new MutableBoolean();
do {
@@ -453,7 +499,7 @@
} else {
this.nextChunkSaveTime.clear();
long i = Util.getMillis();
- ObjectIterator objectiterator = this.visibleChunkMap.values().iterator();
+ Iterator<ChunkHolder> objectiterator = ca.spottedleaf.moonrise.common.util.ChunkSystem.getVisibleChunkHolders(this.level).iterator(); // Paper
while (objectiterator.hasNext()) {
ChunkHolder playerchunk = (ChunkHolder) objectiterator.next();
@@ -478,7 +524,7 @@
}
public boolean hasWork() {
- return this.lightEngine.hasLightWork() || !this.pendingUnloads.isEmpty() || !this.updatingChunkMap.isEmpty() || this.poiManager.hasWork() || !this.toDrop.isEmpty() || !this.unloadQueue.isEmpty() || this.worldgenTaskDispatcher.hasWork() || this.lightTaskDispatcher.hasWork() || this.distanceManager.hasTickets();
+ return this.lightEngine.hasLightWork() || !this.pendingUnloads.isEmpty() || ca.spottedleaf.moonrise.common.util.ChunkSystem.hasAnyChunkHolders(this.level) || !this.updatingChunkMap.isEmpty() || this.poiManager.hasWork() || !this.toDrop.isEmpty() || !this.unloadQueue.isEmpty() || this.worldgenTaskDispatcher.hasWork() || this.lightTaskDispatcher.hasWork() || this.distanceManager.hasTickets();
}
private void processUnloads(BooleanSupplier shouldKeepTicking) {
@@ -537,8 +583,11 @@
this.scheduleUnload(pos, chunk);
} else {
ChunkAccess ichunkaccess = chunk.getLatestChunk();
-
- if (this.pendingUnloads.remove(pos, chunk) && ichunkaccess != null) {
+ // Paper start
+ boolean removed;
+ if ((removed = this.pendingUnloads.remove(pos, chunk)) && ichunkaccess != null) {
+ ca.spottedleaf.moonrise.common.util.ChunkSystem.onChunkHolderDelete(this.level, chunk);
+ // Paper end
LevelChunk chunk1;
if (ichunkaccess instanceof LevelChunk) {
@@ -556,7 +605,9 @@
this.lightEngine.tryScheduleUpdate();
this.progressListener.onStatusChange(ichunkaccess.getPos(), (ChunkStatus) null);
this.nextChunkSaveTime.remove(ichunkaccess.getPos().toLong());
- }
+ } else if (removed) { // Paper start
+ ca.spottedleaf.moonrise.common.util.ChunkSystem.onChunkHolderDelete(this.level, chunk);
+ } // Paper end
}
};
@@ -905,7 +956,7 @@
}
}
- protected void setServerViewDistance(int watchDistance) {
+ public void setServerViewDistance(int watchDistance) { // Paper - public
int j = Mth.clamp(watchDistance, 2, 32);
if (j != this.serverViewDistance) {
@@ -922,7 +973,7 @@
}
- int getPlayerViewDistance(ServerPlayer player) {
+ public int getPlayerViewDistance(ServerPlayer player) { // Paper - public
return Mth.clamp(player.requestedViewDistance(), 2, this.serverViewDistance);
}
@@ -951,7 +1002,7 @@
}
public int size() {
- return this.visibleChunkMap.size();
+ return ca.spottedleaf.moonrise.common.util.ChunkSystem.getVisibleChunkHolderCount(this.level); // Paper
}
public DistanceManager getDistanceManager() {
@@ -959,25 +1010,26 @@
}
protected Iterable<ChunkHolder> getChunks() {
- return Iterables.unmodifiableIterable(this.visibleChunkMap.values());
+ return Iterables.unmodifiableIterable(ca.spottedleaf.moonrise.common.util.ChunkSystem.getVisibleChunkHolders(this.level)); // Paper
}
void dumpChunks(Writer writer) throws IOException {
CsvOutput csvwriter = CsvOutput.builder().addColumn("x").addColumn("z").addColumn("level").addColumn("in_memory").addColumn("status").addColumn("full_status").addColumn("accessible_ready").addColumn("ticking_ready").addColumn("entity_ticking_ready").addColumn("ticket").addColumn("spawning").addColumn("block_entity_count").addColumn("ticking_ticket").addColumn("ticking_level").addColumn("block_ticks").addColumn("fluid_ticks").build(writer);
TickingTracker tickingtracker = this.distanceManager.tickingTracker();
- ObjectBidirectionalIterator objectbidirectionaliterator = this.visibleChunkMap.long2ObjectEntrySet().iterator();
+ Iterator<ChunkHolder> objectbidirectionaliterator = ca.spottedleaf.moonrise.common.util.ChunkSystem.getVisibleChunkHolders(this.level).iterator(); // Paper
while (objectbidirectionaliterator.hasNext()) {
- Entry<ChunkHolder> entry = (Entry) objectbidirectionaliterator.next();
- long i = entry.getLongKey();
+ ChunkHolder playerchunk = objectbidirectionaliterator.next(); // Paper
+ long i = playerchunk.pos.toLong(); // Paper
ChunkPos chunkcoordintpair = new ChunkPos(i);
- ChunkHolder playerchunk = (ChunkHolder) entry.getValue();
+ // Paper - move up
Optional<ChunkAccess> optional = Optional.ofNullable(playerchunk.getLatestChunk());
Optional<LevelChunk> optional1 = optional.flatMap((ichunkaccess) -> {
return ichunkaccess instanceof LevelChunk ? Optional.of((LevelChunk) ichunkaccess) : Optional.empty();
});
- csvwriter.writeRow(chunkcoordintpair.x, chunkcoordintpair.z, playerchunk.getTicketLevel(), optional.isPresent(), optional.map(ChunkAccess::getPersistedStatus).orElse((Object) null), optional1.map(LevelChunk::getFullStatus).orElse((Object) null), ChunkMap.printFuture(playerchunk.getFullChunkFuture()), ChunkMap.printFuture(playerchunk.getTickingChunkFuture()), ChunkMap.printFuture(playerchunk.getEntityTickingChunkFuture()), this.distanceManager.getTicketDebugString(i), this.anyPlayerCloseEnoughForSpawning(chunkcoordintpair), optional1.map((chunk) -> {
+ // CraftBukkit - decompile error
+ csvwriter.writeRow(chunkcoordintpair.x, chunkcoordintpair.z, playerchunk.getTicketLevel(), optional.isPresent(), optional.map(ChunkAccess::getPersistedStatus).orElse(null), optional1.map(LevelChunk::getFullStatus).orElse(null), ChunkMap.printFuture(playerchunk.getFullChunkFuture()), ChunkMap.printFuture(playerchunk.getTickingChunkFuture()), ChunkMap.printFuture(playerchunk.getEntityTickingChunkFuture()), this.distanceManager.getTicketDebugString(i), this.anyPlayerCloseEnoughForSpawning(chunkcoordintpair), optional1.map((chunk) -> {
return chunk.getBlockEntities().size();
}).orElse(0), tickingtracker.getTicketDebugString(i), tickingtracker.getLevel(i), optional1.map((chunk) -> {
return chunk.getBlockTicks().count();
@@ -990,7 +1042,7 @@
private static String printFuture(CompletableFuture<ChunkResult<LevelChunk>> future) {
try {
- ChunkResult<LevelChunk> chunkresult = (ChunkResult) future.getNow((Object) null);
+ ChunkResult<LevelChunk> chunkresult = (ChunkResult) future.getNow(null); // CraftBukkit - decompile error
return chunkresult != null ? (chunkresult.isSuccess() ? "done" : "unloaded") : "not completed";
} catch (CompletionException completionexception) {
@@ -1002,12 +1054,14 @@
private CompletableFuture<Optional<CompoundTag>> readChunk(ChunkPos chunkPos) {
return this.read(chunkPos).thenApplyAsync((optional) -> {
- return optional.map(this::upgradeChunkTag);
+ return optional.map((nbttagcompound) -> this.upgradeChunkTag(nbttagcompound, chunkPos)); // CraftBukkit
}, Util.backgroundExecutor().forName("upgradeChunk"));
}
- private CompoundTag upgradeChunkTag(CompoundTag nbt) {
- return this.upgradeChunkTag(this.level.dimension(), this.overworldDataStorage, nbt, this.generator().getTypeNameForDataFixer());
+ // CraftBukkit start
+ private CompoundTag upgradeChunkTag(CompoundTag nbttagcompound, ChunkPos chunkcoordintpair) {
+ return this.upgradeChunkTag(this.level.getTypeKey(), this.overworldDataStorage, nbttagcompound, this.generator().getTypeNameForDataFixer(), chunkcoordintpair, this.level);
+ // CraftBukkit end
}
void forEachSpawnCandidateChunk(Consumer<ChunkHolder> callback) {
@@ -1025,10 +1079,23 @@
}
public boolean anyPlayerCloseEnoughForSpawning(ChunkPos pos) {
- return !this.distanceManager.hasPlayersNearby(pos.toLong()) ? false : this.anyPlayerCloseEnoughForSpawningInternal(pos);
+ // Spigot start
+ return this.anyPlayerCloseEnoughForSpawning(pos, false);
}
+ boolean anyPlayerCloseEnoughForSpawning(ChunkPos chunkcoordintpair, boolean reducedRange) {
+ return !this.distanceManager.hasPlayersNearby(chunkcoordintpair.toLong()) ? false : this.anyPlayerCloseEnoughForSpawningInternal(chunkcoordintpair, reducedRange);
+ // Spigot end
+ }
+
private boolean anyPlayerCloseEnoughForSpawningInternal(ChunkPos pos) {
+ // Spigot start
+ return this.anyPlayerCloseEnoughForSpawningInternal(pos, false);
+ }
+
+ private boolean anyPlayerCloseEnoughForSpawningInternal(ChunkPos chunkcoordintpair, boolean reducedRange) {
+ double blockRange; // Paper - use from event
+ // Spigot end
Iterator iterator = this.playerMap.getAllPlayers().iterator();
ServerPlayer entityplayer;
@@ -1039,7 +1106,16 @@
}
entityplayer = (ServerPlayer) iterator.next();
- } while (!this.playerIsCloseEnoughForSpawning(entityplayer, pos));
+ // Paper start - PlayerNaturallySpawnCreaturesEvent
+ com.destroystokyo.paper.event.entity.PlayerNaturallySpawnCreaturesEvent event;
+ blockRange = 16384.0D;
+ if (reducedRange) {
+ event = entityplayer.playerNaturallySpawnedEvent;
+ if (event == null || event.isCancelled()) continue;
+ blockRange = (double) ((event.getSpawnRadius() << 4) * (event.getSpawnRadius() << 4));
+ }
+ // Paper end - PlayerNaturallySpawnCreaturesEvent
+ } while (!this.playerIsCloseEnoughForSpawning(entityplayer, chunkcoordintpair, blockRange)); // Spigot
return true;
}
@@ -1056,7 +1132,7 @@
while (iterator.hasNext()) {
ServerPlayer entityplayer = (ServerPlayer) iterator.next();
- if (this.playerIsCloseEnoughForSpawning(entityplayer, pos)) {
+ if (this.playerIsCloseEnoughForSpawning(entityplayer, pos, 16384.0D)) { // Spigot
builder.add(entityplayer);
}
}
@@ -1065,13 +1141,13 @@
}
}
- private boolean playerIsCloseEnoughForSpawning(ServerPlayer player, ChunkPos pos) {
- if (player.isSpectator()) {
+ private boolean playerIsCloseEnoughForSpawning(ServerPlayer entityplayer, ChunkPos chunkcoordintpair, double range) { // Spigot
+ if (entityplayer.isSpectator()) {
return false;
} else {
- double d0 = ChunkMap.euclideanDistanceSquared(pos, player);
+ double d0 = ChunkMap.euclideanDistanceSquared(chunkcoordintpair, entityplayer);
- return d0 < 16384.0D;
+ return d0 < range; // Spigot
}
}
@@ -1215,9 +1291,19 @@
}
public void addEntity(Entity entity) {
+ org.spigotmc.AsyncCatcher.catchOp("entity track"); // Spigot
+ // Paper start - ignore and warn about illegal addEntity calls instead of crashing server
+ if (!entity.valid || entity.level() != this.level || this.entityMap.containsKey(entity.getId())) {
+ LOGGER.error("Illegal ChunkMap::addEntity for world " + this.level.getWorld().getName()
+ + ": " + entity + (this.entityMap.containsKey(entity.getId()) ? " ALREADY CONTAINED (This would have crashed your server)" : ""), new Throwable());
+ return;
+ }
+ // Paper end - ignore and warn about illegal addEntity calls instead of crashing server
+ if (entity instanceof ServerPlayer && ((ServerPlayer) entity).supressTrackerForLogin) return; // Paper - Fire PlayerJoinEvent when Player is actually ready; Delay adding to tracker until after list packets
if (!(entity instanceof EnderDragonPart)) {
EntityType<?> entitytypes = entity.getType();
int i = entitytypes.clientTrackingRange() * 16;
+ i = org.spigotmc.TrackingRange.getEntityTrackingRange(entity, i); // Spigot
if (i != 0) {
int j = entitytypes.updateInterval();
@@ -1250,6 +1336,7 @@
}
protected void removeEntity(Entity entity) {
+ org.spigotmc.AsyncCatcher.catchOp("entity untrack"); // Spigot
if (entity instanceof ServerPlayer entityplayer) {
this.updatePlayerStatus(entityplayer, false);
ObjectIterator objectiterator = this.entityMap.values().iterator();
@@ -1391,7 +1478,7 @@
});
}
- private class ChunkDistanceManager extends DistanceManager {
+ public class ChunkDistanceManager extends DistanceManager { // Paper - public
protected ChunkDistanceManager(final Executor workerExecutor, final Executor mainThreadExecutor) {
super(workerExecutor, mainThreadExecutor);
@@ -1424,7 +1511,7 @@
public final Set<ServerPlayerConnection> seenBy = Sets.newIdentityHashSet();
public TrackedEntity(final Entity entity, final int i, final int j, final boolean flag) {
- this.serverEntity = new ServerEntity(ChunkMap.this.level, entity, j, flag, this::broadcast);
+ this.serverEntity = new ServerEntity(ChunkMap.this.level, entity, j, flag, this::broadcast, this.seenBy); // CraftBukkit
this.entity = entity;
this.range = i;
this.lastSectionPos = SectionPos.of((EntityAccess) entity);
@@ -1469,6 +1556,7 @@
}
public void removePlayer(ServerPlayer player) {
+ org.spigotmc.AsyncCatcher.catchOp("player tracker clear"); // Spigot
if (this.seenBy.remove(player.connection)) {
this.serverEntity.removePairing(player);
}
@@ -1476,6 +1564,7 @@
}
public void updatePlayer(ServerPlayer player) {
+ org.spigotmc.AsyncCatcher.catchOp("player tracker update"); // Spigot
if (player != this.entity) {
Vec3 vec3d = player.position().subtract(this.entity.position());
int i = ChunkMap.this.getPlayerViewDistance(player);
@@ -1484,6 +1573,11 @@
double d2 = d0 * d0;
boolean flag = d1 <= d2 && this.entity.broadcastToPlayer(player) && ChunkMap.this.isChunkTracked(player, this.entity.chunkPosition().x, this.entity.chunkPosition().z);
+ // CraftBukkit start - respect vanish API
+ if (!player.getBukkitEntity().canSee(this.entity.getBukkitEntity())) {
+ flag = false;
+ }
+ // CraftBukkit end
if (flag) {
if (this.seenBy.add(player.connection)) {
this.serverEntity.addPairing(player);
@@ -1506,6 +1600,7 @@
while (iterator.hasNext()) {
Entity entity = (Entity) iterator.next();
int j = entity.getType().clientTrackingRange() * 16;
+ j = org.spigotmc.TrackingRange.getEntityTrackingRange(entity, j); // Paper
if (j > i) {
i = j;