mirror of
https://github.com/PaperMC/Paper.git
synced 2024-12-02 13:07:06 +01:00
01a13871de
Patch documentation to come Issues with the old system that are fixed now: - World generation does not scale with cpu cores effectively. - Relies on the main thread for scheduling and maintaining chunk state, dropping chunk load/generate rates at lower tps. - Unreliable prioritisation of chunk gen/load calls that block the main thread. - Shutdown logic is utterly unreliable, as it has to wait for all chunks to unload - is it guaranteed that the chunk system is in a state on shutdown that it can reliably do this? Watchdog shutdown also typically failed due to thread checks, which is now resolved. - Saving of data is not unified (i.e can save chunk data without saving entity data, poses problems for desync if shutdown is really abnormal. - Entities are not loaded with chunks. This caused quite a bit of headache for Chunk#getEntities API, but now the new chunk system loads entities with chunks so that they are ready whenever the chunk loads in. Effectively brings the behavior back to 1.16 era, but still storing entities in their own separate regionfiles. The above list is not complete. The patch documentation will complete it. New chunk system hard relies on starlight and dataconverter, and most importantly the new concurrent utilities in ConcurrentUtil. Some of the old async chunk i/o interface (i.e the old file io thread reroutes _some_ calls to the new file io thread) is kept for plugin compat reasons. It will be removed in the next major version of minecraft. The old legacy chunk system patches have been moved to the removed folder in case we need them again.
215 lines
12 KiB
Diff
215 lines
12 KiB
Diff
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
|
From: Spottedleaf <Spottedleaf@users.noreply.github.com>
|
|
Date: Thu, 7 May 2020 05:48:54 -0700
|
|
Subject: [PATCH] Optimise chunk tick iteration
|
|
|
|
Use a dedicated list of entity ticking chunks to reduce the cost
|
|
|
|
diff --git a/src/main/java/net/minecraft/server/level/ChunkHolder.java b/src/main/java/net/minecraft/server/level/ChunkHolder.java
|
|
index 2add24517d38708a84e7f8ec25fbe9c62309375e..1fb298ff60b59a7074fb9d7a79709f05887ce32c 100644
|
|
--- a/src/main/java/net/minecraft/server/level/ChunkHolder.java
|
|
+++ b/src/main/java/net/minecraft/server/level/ChunkHolder.java
|
|
@@ -88,6 +88,11 @@ public class ChunkHolder {
|
|
this.playersInMobSpawnRange = this.chunkMap.playerMobSpawnMap.getObjectsInRange(key);
|
|
this.playersInChunkTickRange = this.chunkMap.playerChunkTickRangeMap.getObjectsInRange(key);
|
|
// Paper end - optimise anyPlayerCloseEnoughForSpawning
|
|
+ // Paper start - optimise chunk tick iteration
|
|
+ if (this.needsBroadcastChanges()) {
|
|
+ this.chunkMap.needsChangeBroadcasting.add(this);
|
|
+ }
|
|
+ // Paper end - optimise chunk tick iteration
|
|
}
|
|
|
|
public void onChunkRemove() {
|
|
@@ -95,6 +100,11 @@ public class ChunkHolder {
|
|
this.playersInMobSpawnRange = null;
|
|
this.playersInChunkTickRange = null;
|
|
// Paper end - optimise anyPlayerCloseEnoughForSpawning
|
|
+ // Paper start - optimise chunk tick iteration
|
|
+ if (this.needsBroadcastChanges()) {
|
|
+ this.chunkMap.needsChangeBroadcasting.remove(this);
|
|
+ }
|
|
+ // Paper end - optimise chunk tick iteration
|
|
}
|
|
// Paper end
|
|
|
|
@@ -210,7 +220,7 @@ public class ChunkHolder {
|
|
|
|
if (i < 0 || i >= this.changedBlocksPerSection.length) return; // CraftBukkit - SPIGOT-6086, SPIGOT-6296
|
|
if (this.changedBlocksPerSection[i] == null) {
|
|
- this.hasChangedSections = true;
|
|
+ this.hasChangedSections = true; this.addToBroadcastMap(); // Paper - optimise chunk tick iteration
|
|
this.changedBlocksPerSection[i] = new ShortOpenHashSet();
|
|
}
|
|
|
|
@@ -234,6 +244,7 @@ public class ChunkHolder {
|
|
int k = this.lightEngine.getMaxLightSection();
|
|
|
|
if (y >= j && y <= k) {
|
|
+ this.addToBroadcastMap(); // Paper - optimise chunk tick iteration
|
|
int l = y - j;
|
|
|
|
if (lightType == LightLayer.SKY) {
|
|
@@ -248,8 +259,19 @@ public class ChunkHolder {
|
|
}
|
|
}
|
|
|
|
+ // Paper start - optimise chunk tick iteration
|
|
+ public final boolean needsBroadcastChanges() {
|
|
+ return this.hasChangedSections || !this.skyChangedLightSectionFilter.isEmpty() || !this.blockChangedLightSectionFilter.isEmpty();
|
|
+ }
|
|
+
|
|
+ private void addToBroadcastMap() {
|
|
+ org.spigotmc.AsyncCatcher.catchOp("ChunkHolder update");
|
|
+ this.chunkMap.needsChangeBroadcasting.add(this);
|
|
+ }
|
|
+ // Paper end - optimise chunk tick iteration
|
|
+
|
|
public void broadcastChanges(LevelChunk chunk) {
|
|
- if (this.hasChangedSections || !this.skyChangedLightSectionFilter.isEmpty() || !this.blockChangedLightSectionFilter.isEmpty()) {
|
|
+ if (this.needsBroadcastChanges()) { // Paper - moved into above, other logic needs to call
|
|
Level world = chunk.getLevel();
|
|
int i = 0;
|
|
|
|
diff --git a/src/main/java/net/minecraft/server/level/ChunkMap.java b/src/main/java/net/minecraft/server/level/ChunkMap.java
|
|
index 37129601dfb8f2af897fb095786616b07e9acdd2..c5a76a6d589133f0f23a2b3b421399acaa7ceb8c 100644
|
|
--- a/src/main/java/net/minecraft/server/level/ChunkMap.java
|
|
+++ b/src/main/java/net/minecraft/server/level/ChunkMap.java
|
|
@@ -110,6 +110,8 @@ import org.bukkit.craftbukkit.generator.CustomChunkGenerator;
|
|
import org.bukkit.entity.Player;
|
|
// CraftBukkit end
|
|
|
|
+import it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet; // Paper
|
|
+
|
|
public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider {
|
|
|
|
private static final byte CHUNK_TYPE_REPLACEABLE = -1;
|
|
@@ -147,6 +149,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
|
|
private final Queue<Runnable> unloadQueue;
|
|
int viewDistance;
|
|
public final com.destroystokyo.paper.util.misc.PlayerAreaMap playerMobDistanceMap; // Paper
|
|
+ public final ReferenceOpenHashSet<ChunkHolder> needsChangeBroadcasting = new ReferenceOpenHashSet<>();
|
|
|
|
// Paper - rewrite chunk system
|
|
|
|
diff --git a/src/main/java/net/minecraft/server/level/ServerChunkCache.java b/src/main/java/net/minecraft/server/level/ServerChunkCache.java
|
|
index fa7801158b68eaa12d6322c9c0def9de37f03814..cfcb3ba96569f3eb24d1035d770c50085f60b772 100644
|
|
--- a/src/main/java/net/minecraft/server/level/ServerChunkCache.java
|
|
+++ b/src/main/java/net/minecraft/server/level/ServerChunkCache.java
|
|
@@ -47,6 +47,7 @@ import net.minecraft.world.level.levelgen.structure.templatesystem.StructureTemp
|
|
import net.minecraft.world.level.storage.DimensionDataStorage;
|
|
import net.minecraft.world.level.storage.LevelData;
|
|
import net.minecraft.world.level.storage.LevelStorageSource;
|
|
+import it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet; // Paper
|
|
|
|
public class ServerChunkCache extends ChunkSource {
|
|
|
|
@@ -723,42 +724,59 @@ public class ServerChunkCache extends ChunkSource {
|
|
|
|
this.lastSpawnState = spawnercreature_d;
|
|
gameprofilerfiller.popPush("filteringLoadedChunks");
|
|
- List<ServerChunkCache.ChunkAndHolder> list = Lists.newArrayListWithCapacity(l);
|
|
- Iterator iterator = this.chunkMap.getChunks().iterator();
|
|
+ // Paper - moved down
|
|
this.level.timings.chunkTicks.startTiming(); // Paper
|
|
|
|
- while (iterator.hasNext()) {
|
|
- ChunkHolder playerchunk = (ChunkHolder) iterator.next();
|
|
- LevelChunk chunk = playerchunk.getTickingChunk();
|
|
-
|
|
- if (chunk != null) {
|
|
- list.add(new ServerChunkCache.ChunkAndHolder(chunk, playerchunk));
|
|
- }
|
|
- }
|
|
+ // Paper - moved down
|
|
|
|
gameprofilerfiller.popPush("spawnAndTick");
|
|
boolean flag2 = this.level.getGameRules().getBoolean(GameRules.RULE_DOMOBSPAWNING) && !this.level.players().isEmpty(); // CraftBukkit
|
|
|
|
- Collections.shuffle(list);
|
|
+ // Paper - only shuffle if per-player mob spawning is disabled
|
|
// Paper - moved natural spawn event up
|
|
- Iterator iterator1 = list.iterator();
|
|
+ // Paper start - optimise chunk tick iteration
|
|
+ Iterator<LevelChunk> iterator1;
|
|
+ if (this.level.paperConfig().entities.spawning.perPlayerMobSpawns) {
|
|
+ iterator1 = this.entityTickingChunks.iterator();
|
|
+ } else {
|
|
+ iterator1 = this.entityTickingChunks.unsafeIterator();
|
|
+ List<LevelChunk> shuffled = Lists.newArrayListWithCapacity(this.entityTickingChunks.size());
|
|
+ while (iterator1.hasNext()) {
|
|
+ shuffled.add(iterator1.next());
|
|
+ }
|
|
+ Collections.shuffle(shuffled);
|
|
+ iterator1 = shuffled.iterator();
|
|
+ }
|
|
|
|
+ try {
|
|
while (iterator1.hasNext()) {
|
|
- ServerChunkCache.ChunkAndHolder chunkproviderserver_a = (ServerChunkCache.ChunkAndHolder) iterator1.next();
|
|
- LevelChunk chunk1 = chunkproviderserver_a.chunk;
|
|
+ LevelChunk chunk1 = iterator1.next();
|
|
+ ChunkHolder holder = chunk1.playerChunk;
|
|
+ if (holder != null) {
|
|
+ // Paper - move down
|
|
+ // Paper end - optimise chunk tick iteration
|
|
ChunkPos chunkcoordintpair = chunk1.getPos();
|
|
|
|
- if (this.level.isNaturalSpawningAllowed(chunkcoordintpair) && this.chunkMap.anyPlayerCloseEnoughForSpawning(chunkproviderserver_a.holder, chunkcoordintpair, false)) { // Paper - optimise anyPlayerCloseEnoughForSpawning
|
|
+ if ((true || this.level.isNaturalSpawningAllowed(chunkcoordintpair)) && this.chunkMap.anyPlayerCloseEnoughForSpawning(holder, chunkcoordintpair, false)) { // Paper - optimise anyPlayerCloseEnoughForSpawning // Paper - the chunk is known ticking
|
|
chunk1.incrementInhabitedTime(j);
|
|
- if (flag2 && (this.spawnEnemies || this.spawnFriendlies) && this.level.getWorldBorder().isWithinBounds(chunkcoordintpair) && this.chunkMap.anyPlayerCloseEnoughForSpawning(chunkproviderserver_a.holder, chunkcoordintpair, true)) { // Spigot // Paper - optimise anyPlayerCloseEnoughForSpawning
|
|
+ if (flag2 && (this.spawnEnemies || this.spawnFriendlies) && this.level.getWorldBorder().isWithinBounds(chunkcoordintpair) && this.chunkMap.anyPlayerCloseEnoughForSpawning(holder, chunkcoordintpair, true)) { // Spigot // Paper - optimise anyPlayerCloseEnoughForSpawning & optimise chunk tick iteration
|
|
NaturalSpawner.spawnForChunk(this.level, chunk1, spawnercreature_d, this.spawnFriendlies, this.spawnEnemies, flag1);
|
|
}
|
|
|
|
- if (this.level.shouldTickBlocksAt(chunkcoordintpair.toLong())) {
|
|
+ if (true || this.level.shouldTickBlocksAt(chunkcoordintpair.toLong())) { // Paper - the chunk is known ticking
|
|
this.level.tickChunk(chunk1, k);
|
|
}
|
|
}
|
|
+ // Paper start - optimise chunk tick iteration
|
|
+ }
|
|
+ }
|
|
+
|
|
+ } finally {
|
|
+ if (iterator1 instanceof io.papermc.paper.util.maplist.IteratorSafeOrderedReferenceSet.Iterator safeIterator) {
|
|
+ safeIterator.finishedIterating();
|
|
+ }
|
|
}
|
|
+ // Paper end - optimise chunk tick iteration
|
|
this.level.timings.chunkTicks.stopTiming(); // Paper
|
|
gameprofilerfiller.popPush("customSpawners");
|
|
if (flag2) {
|
|
@@ -766,15 +784,24 @@ public class ServerChunkCache extends ChunkSource {
|
|
this.level.tickCustomSpawners(this.spawnEnemies, this.spawnFriendlies);
|
|
} // Paper - timings
|
|
}
|
|
-
|
|
- gameprofilerfiller.popPush("broadcast");
|
|
- list.forEach((chunkproviderserver_a1) -> {
|
|
- this.level.timings.broadcastChunkUpdates.startTiming(); // Paper - timing
|
|
- chunkproviderserver_a1.holder.broadcastChanges(chunkproviderserver_a1.chunk);
|
|
- this.level.timings.broadcastChunkUpdates.stopTiming(); // Paper - timing
|
|
- });
|
|
gameprofilerfiller.pop();
|
|
+ // Paper start - use set of chunks requiring updates, rather than iterating every single one loaded
|
|
+ gameprofilerfiller.popPush("broadcast");
|
|
+ this.level.timings.broadcastChunkUpdates.startTiming(); // Paper - timing
|
|
+ if (!this.chunkMap.needsChangeBroadcasting.isEmpty()) {
|
|
+ ReferenceOpenHashSet<ChunkHolder> copy = this.chunkMap.needsChangeBroadcasting.clone();
|
|
+ this.chunkMap.needsChangeBroadcasting.clear();
|
|
+ for (ChunkHolder holder : copy) {
|
|
+ holder.broadcastChanges(holder.getFullChunkNowUnchecked()); // LevelChunks are NEVER unloaded
|
|
+ if (holder.needsBroadcastChanges()) {
|
|
+ // I DON'T want to KNOW what DUMB plugins might be doing.
|
|
+ this.chunkMap.needsChangeBroadcasting.add(holder);
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+ this.level.timings.broadcastChunkUpdates.stopTiming(); // Paper - timing
|
|
gameprofilerfiller.pop();
|
|
+ // Paper end - use set of chunks requiring updates, rather than iterating every single one loaded
|
|
this.chunkMap.tick();
|
|
}
|
|
}
|